@styleflow.app/flowpress-astro-core 0.2.0-beta.4 → 0.2.0-beta.7

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,20 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.0-beta.5
6
+
7
+ - Split Gutenberg rendering into dedicated hero, responsive layout, text,
8
+ list/template and CF7 modules.
9
+ - Added semantic foreground/modifier propagation and core text block
10
+ rendering.
11
+ - Added responsive 12-column inheritance, token-based partial containers,
12
+ hero media/scrim rendering and reduced-motion handling.
13
+ - Added repeated query-item template rendering with the previous plain-list
14
+ fallback.
15
+ - Added an accessible Turnstile/CF7 browser form compatible with static and
16
+ server builds.
17
+ - Added Astro component checking to the release gate.
18
+
5
19
  ## 0.2.0-beta.4
6
20
 
7
21
  - Updated the exact StyleFlow Astro dependency to `0.3.0-beta.2`.
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.4
12
+ pnpm add @styleflow.app/flowpress-astro-core@0.2.0-beta.5
13
13
  ```
14
14
 
15
15
  Prereleases are pinned exactly. The package expects Astro 7 or newer and the
@@ -30,6 +30,12 @@ For local Cloudflare development, prefer an HTTP loopback endpoint dedicated
30
30
  to server-to-server traffic. Keep the public HTTPS backend URL separate for
31
31
  canonical URL rewriting.
32
32
 
33
+ `GutenbergRenderer.astro` renders the responsive hero/grid/column contract,
34
+ StyleFlow text axes and resolved content/query templates. CF7 schemas
35
+ serialized by WordPress become accessible client forms that submit directly
36
+ to the protected FlowPress REST endpoint, so the same component works in
37
+ static and server output modes.
38
+
33
39
  ## Release
34
40
 
35
41
  ```bash
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import type { SfSeo } from "../types.js";
3
+ import S from "@styleflow.app/astro/S.astro";
3
4
 
4
5
  interface Props {
5
6
  lang?: string;
@@ -44,6 +45,8 @@ const jsonLd = seo?.jsonLd ? JSON.stringify(seo.jsonLd) : null;
44
45
  <slot name="head" />
45
46
  </head>
46
47
  <body>
47
- <slot />
48
+ <S.main tone="neutral" intensity={1} surface="default" layoutRole="none" density="md">
49
+ <slot />
50
+ </S.main>
48
51
  </body>
49
52
  </html>
@@ -7,28 +7,38 @@ import type { FlowpressTemplateProps } from "../types.js";
7
7
  const { route, content, styleflowContract } = Astro.props as FlowpressTemplateProps;
8
8
  const title = content?.title || route.seo?.title || "Contenuto";
9
9
  const isNotFound = route.queryType === "404" || route.status === 404;
10
+ const hasGutembergBlocks = Array.isArray(content?.contentBlocks) && content.contentBlocks.length > 0 ? true : false;
11
+ //TODO SET GLOBAL LOCAL ON ENV
10
12
  ---
11
13
 
12
14
  <BaseLayout lang={route.locale || "it"} seo={route.seo}>
13
- <S.main tone="neutral" intensity={1} surface="default" layoutRole="container" density="md">
14
- <S.section tone="neutral" intensity={1} surface="default" layoutRole="section" density="md">
15
- <S.h1 ty="heading" v="1" w="strong">{isNotFound ? "Pagina non trovata" : title}</S.h1>
16
- {content?.excerpt && <S.p ty="body" v="md" w="default">{content.excerpt}</S.p>}
17
- {Array.isArray(content?.contentBlocks) && content.contentBlocks.length > 0 ? (
18
- <GutenbergRenderer
19
- blocks={content.contentBlocks as import("../types.js").FlowpressBlock[]}
20
- contract={styleflowContract}
21
- />
22
- ) : content?.content && (
23
- <S.div
24
- tone="neutral"
25
- intensity={1}
26
- surface="default"
27
- layoutRole="stack"
28
- density="md"
29
- set:html={String(content.content)}
30
- />
31
- )}
32
- </S.section>
33
- </S.main>
15
+
16
+ {hasGutembergBlocks ? (
17
+ <GutenbergRenderer
18
+ blocks={content?.contentBlocks as import("../types.js").FlowpressBlock[]}
19
+ contract={styleflowContract}
20
+ />
21
+ ) : (
22
+ <S.section tone="neutral" intensity={1} surface="default" layoutRole="section" density="md">
23
+ <S.h1 ty="heading" v="1" w="strong">{isNotFound ? "Pagina non trovata" : title}</S.h1>
24
+ {content?.excerpt && <S.p ty="body" v="md" w="default">{content.excerpt}</S.p>}
25
+ {Array.isArray(content?.contentBlocks) && content.contentBlocks.length > 0 ? (
26
+ <GutenbergRenderer
27
+ blocks={content.contentBlocks as import("../types.js").FlowpressBlock[]}
28
+ contract={styleflowContract}
29
+ />
30
+ ) : content?.content && (
31
+ <S.div
32
+ tone="neutral"
33
+ intensity={1}
34
+ surface="default"
35
+ layoutRole="stack"
36
+ density="md"
37
+ set:html={String(content.content)}
38
+ />
39
+ )}
40
+ </S.section>
41
+ )}
42
+
43
+
34
44
  </BaseLayout>
@@ -1,10 +1,19 @@
1
1
  ---
2
2
  import S from "@styleflow.app/astro/S.astro";
3
- import {
4
- resolveDynamicStyleflowProps,
5
- type StyleflowContract
6
- } from "../styleflow.js";
3
+ import type { StyleflowContract } from "../styleflow.js";
7
4
  import type { FlowpressBlock } from "../types.js";
5
+ import ColumnBlock from "./blocks/ColumnBlock.astro";
6
+ import FormBlock from "./blocks/FormBlock.astro";
7
+ import GridBlock from "./blocks/GridBlock.astro";
8
+ import HeroBlock from "./blocks/HeroBlock.astro";
9
+ import ListItemFallback from "./blocks/ListItemFallback.astro";
10
+ import TextBlock from "./blocks/TextBlock.astro";
11
+ import {
12
+ linkValue,
13
+ objectValue,
14
+ semanticProps,
15
+ textValue
16
+ } from "./blocks/helpers.js";
8
17
 
9
18
  interface Props {
10
19
  blocks: FlowpressBlock[];
@@ -13,57 +22,6 @@ interface Props {
13
22
 
14
23
  const { blocks, contract } = Astro.props;
15
24
 
16
- const defaults = {
17
- surface: "default",
18
- layoutRole: "stack",
19
- density: "md"
20
- };
21
-
22
- function styleflowProps(attributes: Record<string, unknown>) {
23
- if (!contract) return defaults;
24
- return resolveDynamicStyleflowProps(attributes, defaults, contract);
25
- }
26
-
27
- function dynamicProps(
28
- attributes: Record<string, unknown>,
29
- fallback: Record<string, unknown>
30
- ) {
31
- if (!contract) return fallback;
32
- return resolveDynamicStyleflowProps(attributes, fallback, contract);
33
- }
34
-
35
- function text(block: FlowpressBlock, key = "text"): string {
36
- return String(block.attributes[key] ?? "");
37
- }
38
-
39
- function objectValue(
40
- block: FlowpressBlock,
41
- key: string
42
- ): Record<string, unknown> {
43
- const value = block.attributes[key];
44
- return value && typeof value === "object" && !Array.isArray(value)
45
- ? value as Record<string, unknown>
46
- : {};
47
- }
48
-
49
- function linkValue(block: FlowpressBlock, key = "link"): string {
50
- const value = block.attributes[key];
51
- const href = typeof value === "string"
52
- ? value
53
- : String(objectValue(block, key).url ?? "");
54
- if (/^(?:\/(?!\/)|#|https?:\/\/|mailto:|tel:)/i.test(href)) {
55
- return href;
56
- }
57
- return "";
58
- }
59
-
60
- function itemImage(item: Record<string, unknown>): Record<string, unknown> {
61
- const value = item.image;
62
- return value && typeof value === "object" && !Array.isArray(value)
63
- ? value as Record<string, unknown>
64
- : {};
65
- }
66
-
67
25
  function youtubeEmbedUrl(value: unknown): string | null {
68
26
  try {
69
27
  const url = new URL(String(value ?? ""));
@@ -85,52 +43,62 @@ function youtubeEmbedUrl(value: unknown): string | null {
85
43
  {blocks.map((block) => {
86
44
  const name = block.name.replace(/^sf\//, "");
87
45
  const children = block.innerBlocks ?? [];
88
- const semantic = styleflowProps(block.attributes);
46
+ const semantic = semanticProps(block.attributes, contract);
89
47
 
90
- if (name === "heading") {
91
- const level = Math.min(6, Math.max(1, Number(block.attributes.level ?? 2)));
92
- const headingTag = `h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
93
- const typography = dynamicProps(
94
- {
95
- ty: "heading",
96
- v: String(block.attributes.variant ?? level),
97
- w: String(block.attributes.weight ?? "strong")
98
- },
99
- { ty: "heading", v: String(level), w: "strong" }
48
+ if (name === "hero") {
49
+ return (
50
+ <HeroBlock block={block} contract={contract}>
51
+ <Astro.self blocks={children} contract={contract} />
52
+ </HeroBlock>
100
53
  );
54
+ }
55
+
56
+ if (name === "grid") {
101
57
  return (
102
- <S
103
- as={headingTag}
104
- {...semantic}
105
- {...typography}
106
- set:html={text(block)}
107
- />
58
+ <GridBlock block={block} contract={contract}>
59
+ <Astro.self blocks={children} contract={contract} />
60
+ </GridBlock>
61
+ );
62
+ }
63
+
64
+ if (name === "column") {
65
+ return (
66
+ <ColumnBlock block={block} contract={contract}>
67
+ <Astro.self blocks={children} contract={contract} />
68
+ </ColumnBlock>
108
69
  );
109
70
  }
110
71
 
111
- if (name === "rich-text" || name === "core/paragraph") {
112
- const html = String(block.attributes.content ?? block.innerHtml ?? "");
113
- return <S as="div" {...semantic} set:html={html} />;
72
+ if ([
73
+ "heading",
74
+ "rich-text",
75
+ "quote",
76
+ "core/paragraph",
77
+ "core/heading",
78
+ "core/list",
79
+ "core/list-item",
80
+ "core/quote"
81
+ ].includes(name)) {
82
+ return (
83
+ <TextBlock block={block} contract={contract} hasChildren={children.length > 0}>
84
+ <Astro.self blocks={children} contract={contract} />
85
+ </TextBlock>
86
+ );
114
87
  }
115
88
 
116
89
  if (name === "button") {
117
90
  const href = linkValue(block, "href") || "#";
118
- const interaction = dynamicProps(
119
- {
120
- interactivePriority: String(
121
- block.attributes.interactivePriority ?? "primary"
122
- )
123
- },
124
- { interactivePriority: "primary" }
125
- );
91
+ const link = objectValue(block, "href");
92
+ const opensInNewTab = Boolean(link.opensInNewTab);
126
93
  return (
127
94
  <S
128
95
  as="a"
129
96
  href={href}
97
+ target={opensInNewTab ? "_blank" : undefined}
98
+ rel={opensInNewTab ? "noopener noreferrer" : undefined}
130
99
  {...semantic}
131
- {...interaction}
132
100
  >
133
- {text(block, "label")}
101
+ {textValue(block, "label")}
134
102
  </S>
135
103
  );
136
104
  }
@@ -205,17 +173,8 @@ function youtubeEmbedUrl(value: unknown): string | null {
205
173
  ) : null;
206
174
  }
207
175
 
208
- if (name === "quote") {
209
- return (
210
- <S as="blockquote" {...semantic}>
211
- <S as="p" set:html={text(block, "quote")} />
212
- {block.attributes.citation && <cite>{String(block.attributes.citation)}</cite>}
213
- </S>
214
- );
215
- }
216
-
217
176
  if (name === "chip") {
218
- return <S as="span" {...semantic}>{text(block, "label")}</S>;
177
+ return <S as="span" {...semantic}>{textValue(block, "label")}</S>;
219
178
  }
220
179
 
221
180
  if (name === "card") {
@@ -237,31 +196,32 @@ function youtubeEmbedUrl(value: unknown): string | null {
237
196
  const items = Array.isArray(block.attributes.resolvedItems)
238
197
  ? block.attributes.resolvedItems as Array<Record<string, unknown>>
239
198
  : [];
199
+ const resolvedBlocks = Array.isArray(block.attributes.resolvedBlocks)
200
+ ? block.attributes.resolvedBlocks as FlowpressBlock[][]
201
+ : [];
240
202
  return (
241
203
  <S as="ul" {...semantic}>
242
- {items.map((item) => (
204
+ {items.map((item, index) => (
243
205
  <S as="li">
244
- {itemImage(item).url && (
245
- <img
246
- src={String(itemImage(item).url)}
247
- alt={String(itemImage(item).alt ?? "")}
248
- loading="lazy"
249
- decoding="async"
250
- />
251
- )}
252
- {item.uri ? (
253
- <S as="a" href={String(item.uri)}>{String(item.title ?? "")}</S>
206
+ {resolvedBlocks[index]?.length ? (
207
+ <Astro.self blocks={resolvedBlocks[index]} contract={contract} />
254
208
  ) : (
255
- <S as="span">{String(item.title ?? "")}</S>
209
+ <ListItemFallback item={item} />
256
210
  )}
257
- {item.excerpt && <S as="p">{String(item.excerpt)}</S>}
258
- {item.priceHtml && <S as="div" set:html={String(item.priceHtml)} />}
259
211
  </S>
260
212
  ))}
261
213
  </S>
262
214
  );
263
215
  }
264
216
 
217
+ if (name === "template") {
218
+ return <Astro.self blocks={children} contract={contract} />;
219
+ }
220
+
221
+ if (name === "form") {
222
+ return <FormBlock block={block} contract={contract} />;
223
+ }
224
+
265
225
  if (name === "accordion-item") {
266
226
  return (
267
227
  <S as="div" {...semantic}>
@@ -313,7 +273,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
313
273
  ) : null;
314
274
  }
315
275
 
316
- const layoutTag = ["section", "hero"].includes(name) ? "section" : "div";
276
+ const layoutTag = name === "section" ? "section" : "div";
317
277
  return (
318
278
  <S as={layoutTag} {...semantic}>
319
279
  {block.innerHtml && children.length === 0
@@ -0,0 +1,78 @@
1
+ ---
2
+ import S from "@styleflow.app/astro/S.astro";
3
+ import type { FlowpressBlock } from "../../types.js";
4
+ import type { StyleflowContract } from "../../styleflow.js";
5
+ import { responsiveValues, semanticProps } from "./helpers.js";
6
+
7
+ interface Props {
8
+ block: FlowpressBlock;
9
+ contract?: StyleflowContract;
10
+ }
11
+
12
+ const { block, contract } = Astro.props;
13
+ const values = responsiveValues(block.attributes, "span", 1);
14
+ const partial = ["start", "end"].includes(String(block.attributes.partialContainer))
15
+ ? String(block.attributes.partialContainer)
16
+ : "none";
17
+ const partialBreakpoint = ["base", "sm", "md", "lg", "xl", "2xl"].includes(
18
+ String(block.attributes.partialContainerBreakpoint)
19
+ ) ? String(block.attributes.partialContainerBreakpoint) : "lg";
20
+ const semantic = semanticProps(block.attributes, contract);
21
+ const style = [
22
+ `--flowpress-span-base:${values.base}`,
23
+ `--flowpress-span-sm:${values.sm}`,
24
+ `--flowpress-span-md:${values.md}`,
25
+ `--flowpress-span-lg:${values.lg}`,
26
+ `--flowpress-span-xl:${values.xl}`,
27
+ `--flowpress-span-2xl:${values["2xl"]}`
28
+ ].join(";");
29
+ ---
30
+
31
+ <S
32
+ as="div"
33
+ {...semantic}
34
+ class="flowpress-column"
35
+ data-partial={partial}
36
+ data-partial-breakpoint={partialBreakpoint}
37
+ style={style}
38
+ >
39
+ <slot />
40
+ </S>
41
+
42
+ <style>
43
+ .flowpress-column {
44
+ grid-column: span var(--flowpress-span-base) / span var(--flowpress-span-base);
45
+ min-width: 0;
46
+ }
47
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="base"] {
48
+ padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2));
49
+ }
50
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="base"] {
51
+ padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2));
52
+ }
53
+ @media (min-width: 40rem) {
54
+ .flowpress-column { grid-column: span var(--flowpress-span-sm) / span var(--flowpress-span-sm); }
55
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="sm"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
56
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="sm"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
57
+ }
58
+ @media (min-width: 48rem) {
59
+ .flowpress-column { grid-column: span var(--flowpress-span-md) / span var(--flowpress-span-md); }
60
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="md"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
61
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="md"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
62
+ }
63
+ @media (min-width: 64rem) {
64
+ .flowpress-column { grid-column: span var(--flowpress-span-lg) / span var(--flowpress-span-lg); }
65
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="lg"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
66
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="lg"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
67
+ }
68
+ @media (min-width: 80rem) {
69
+ .flowpress-column { grid-column: span var(--flowpress-span-xl) / span var(--flowpress-span-xl); }
70
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="xl"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
71
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="xl"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
72
+ }
73
+ @media (min-width: 96rem) {
74
+ .flowpress-column { grid-column: span var(--flowpress-span-2xl) / span var(--flowpress-span-2xl); }
75
+ .flowpress-column[data-partial="start"][data-partial-breakpoint="2xl"] { padding-inline-start: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
76
+ .flowpress-column[data-partial="end"][data-partial-breakpoint="2xl"] { padding-inline-end: max(var(--sf-padding-inline, 0px), calc((100vw - var(--sf-container-max-width, 100vw)) / 2)); }
77
+ }
78
+ </style>
@@ -0,0 +1,232 @@
1
+ ---
2
+ import S from "@styleflow.app/astro/S.astro";
3
+ import type { FlowpressBlock } from "../../types.js";
4
+ import type { StyleflowContract } from "../../styleflow.js";
5
+ import { semanticProps } from "./helpers.js";
6
+
7
+ interface FormField {
8
+ name: string;
9
+ type: string;
10
+ required?: boolean;
11
+ values?: string[];
12
+ labels?: string[];
13
+ options?: string[];
14
+ }
15
+
16
+ interface FormSchema {
17
+ id: number;
18
+ title: string;
19
+ fields: FormField[];
20
+ submitUrl: string;
21
+ turnstileSiteKey: string;
22
+ available: boolean;
23
+ }
24
+
25
+ interface Props {
26
+ block: FlowpressBlock;
27
+ contract?: StyleflowContract;
28
+ }
29
+
30
+ const { block, contract } = Astro.props;
31
+ const schema = block.attributes.formSchema as FormSchema | undefined;
32
+ const semantic = semanticProps(block.attributes, contract);
33
+ const labelFor = (field: FormField): string => field.name
34
+ .replace(/[-_]+/g, " ")
35
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
36
+ const optionLabel = (field: FormField, index: number): string =>
37
+ field.labels?.[index] || field.values?.[index] || "";
38
+ ---
39
+
40
+ {schema?.available ? (
41
+ <S as="div" {...semantic}>
42
+ <flowpress-cf7-form>
43
+ <form action={schema.submitUrl} method="post" enctype="multipart/form-data" novalidate>
44
+ {block.attributes.title || schema.title ? (
45
+ <S as="h2">{String(block.attributes.title || schema.title)}</S>
46
+ ) : null}
47
+ {schema.fields.map((field) => {
48
+ if (field.type === "submit") {
49
+ return (
50
+ <button type="submit">
51
+ {field.values?.[0] || "Invia"}
52
+ </button>
53
+ );
54
+ }
55
+ if (field.type === "hidden") {
56
+ return <input type="hidden" name={field.name} value={field.values?.[0] || ""} />;
57
+ }
58
+ if (["checkbox", "radio"].includes(field.type)) {
59
+ return (
60
+ <fieldset>
61
+ <legend>{labelFor(field)}</legend>
62
+ {(field.values || []).map((value, index) => (
63
+ <label>
64
+ <input
65
+ type={field.type}
66
+ name={field.type === "checkbox" ? `${field.name}[]` : field.name}
67
+ value={value}
68
+ required={field.required}
69
+ />
70
+ {optionLabel(field, index)}
71
+ </label>
72
+ ))}
73
+ <span data-field-error={field.name}></span>
74
+ </fieldset>
75
+ );
76
+ }
77
+ if (field.type === "acceptance") {
78
+ return (
79
+ <label>
80
+ <input type="checkbox" name={field.name} value="1" required={field.required} />
81
+ {field.values?.[0] || labelFor(field)}
82
+ <span data-field-error={field.name}></span>
83
+ </label>
84
+ );
85
+ }
86
+ if (field.type === "select") {
87
+ return (
88
+ <label>
89
+ {labelFor(field)}
90
+ <select
91
+ name={field.options?.includes("multiple") ? `${field.name}[]` : field.name}
92
+ required={field.required}
93
+ multiple={field.options?.includes("multiple")}
94
+ >
95
+ {(field.values || []).map((value, index) => (
96
+ <option value={value}>{optionLabel(field, index)}</option>
97
+ ))}
98
+ </select>
99
+ <span data-field-error={field.name}></span>
100
+ </label>
101
+ );
102
+ }
103
+ if (field.type === "textarea") {
104
+ return (
105
+ <label>
106
+ {labelFor(field)}
107
+ <textarea name={field.name} required={field.required}></textarea>
108
+ <span data-field-error={field.name}></span>
109
+ </label>
110
+ );
111
+ }
112
+ return (
113
+ <label>
114
+ {labelFor(field)}
115
+ <input
116
+ type={field.type === "file" ? "file" : field.type}
117
+ name={field.name}
118
+ required={field.required}
119
+ />
120
+ <span data-field-error={field.name}></span>
121
+ </label>
122
+ );
123
+ })}
124
+ <div
125
+ data-turnstile
126
+ data-sitekey={schema.turnstileSiteKey}
127
+ aria-label="Verifica anti-spam"
128
+ ></div>
129
+ <p data-form-status role="status" aria-live="polite"></p>
130
+ </form>
131
+ </flowpress-cf7-form>
132
+ </S>
133
+ ) : (
134
+ <S as="p" {...semantic} role="status">
135
+ Il modulo non è temporaneamente disponibile.
136
+ </S>
137
+ )}
138
+
139
+ <script>
140
+ declare global {
141
+ interface Window {
142
+ turnstile?: {
143
+ render(target: Element, options: Record<string, unknown>): string;
144
+ reset(widgetId?: string): void;
145
+ };
146
+ }
147
+ }
148
+
149
+ let turnstilePromise: Promise<void> | undefined;
150
+ const loadTurnstile = (): Promise<void> => {
151
+ if (window.turnstile) return Promise.resolve();
152
+ if (turnstilePromise) return turnstilePromise;
153
+ turnstilePromise = new Promise((resolve, reject) => {
154
+ const existing = document.querySelector<HTMLScriptElement>(
155
+ 'script[data-flowpress-turnstile]'
156
+ );
157
+ if (existing) {
158
+ existing.addEventListener("load", () => resolve(), { once: true });
159
+ existing.addEventListener("error", () => reject(new Error("Turnstile unavailable")), { once: true });
160
+ return;
161
+ }
162
+ const script = document.createElement("script");
163
+ script.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
164
+ script.async = true;
165
+ script.defer = true;
166
+ script.dataset.flowpressTurnstile = "true";
167
+ script.addEventListener("load", () => resolve(), { once: true });
168
+ script.addEventListener("error", () => reject(new Error("Turnstile unavailable")), { once: true });
169
+ document.head.append(script);
170
+ });
171
+ return turnstilePromise;
172
+ };
173
+
174
+ if (!customElements.get("flowpress-cf7-form")) {
175
+ customElements.define("flowpress-cf7-form", class extends HTMLElement {
176
+ private widgetId?: string;
177
+
178
+ connectedCallback() {
179
+ const form = this.querySelector<HTMLFormElement>("form");
180
+ const target = this.querySelector<HTMLElement>("[data-turnstile]");
181
+ const status = this.querySelector<HTMLElement>("[data-form-status]");
182
+ if (!form || !target || !status) return;
183
+
184
+ loadTurnstile()
185
+ .then(() => {
186
+ if (!window.turnstile || this.widgetId) return;
187
+ this.widgetId = window.turnstile.render(target, {
188
+ sitekey: target.dataset.sitekey
189
+ });
190
+ })
191
+ .catch(() => {
192
+ status.textContent = "La verifica anti-spam non è disponibile.";
193
+ });
194
+
195
+ form.addEventListener("submit", async (event) => {
196
+ event.preventDefault();
197
+ status.textContent = "Invio in corso…";
198
+ form.querySelectorAll<HTMLElement>("[data-field-error]")
199
+ .forEach((node) => { node.textContent = ""; });
200
+ const submit = form.querySelector<HTMLButtonElement>('[type="submit"]');
201
+ if (submit) submit.disabled = true;
202
+ try {
203
+ const response = await fetch(form.action, {
204
+ method: "POST",
205
+ body: new FormData(form),
206
+ headers: { Accept: "application/json" }
207
+ });
208
+ const result = await response.json() as {
209
+ message?: string;
210
+ invalidFields?: Array<{ field?: string; into?: string; message?: string }>;
211
+ };
212
+ status.textContent = result.message || (response.ok ? "Messaggio inviato." : "Invio non riuscito.");
213
+ for (const invalid of result.invalidFields || []) {
214
+ const name = invalid.field || invalid.into?.replace(/^span\./, "") || "";
215
+ const node = name
216
+ ? form.querySelector<HTMLElement>(`[data-field-error="${CSS.escape(name)}"]`)
217
+ : null;
218
+ if (node) node.textContent = invalid.message || "";
219
+ }
220
+ if (response.ok) form.reset();
221
+ if (window.turnstile) window.turnstile.reset(this.widgetId);
222
+ } catch {
223
+ status.textContent = "Impossibile inviare il modulo. Riprova.";
224
+ if (window.turnstile) window.turnstile.reset(this.widgetId);
225
+ } finally {
226
+ if (submit) submit.disabled = false;
227
+ }
228
+ });
229
+ }
230
+ });
231
+ }
232
+ </script>
@@ -0,0 +1,65 @@
1
+ ---
2
+ import S from "@styleflow.app/astro/S.astro";
3
+ import type { FlowpressBlock } from "../../types.js";
4
+ import type { StyleflowContract } from "../../styleflow.js";
5
+ import { responsiveValues, semanticProps } from "./helpers.js";
6
+
7
+ interface Props {
8
+ block: FlowpressBlock;
9
+ contract?: StyleflowContract;
10
+ }
11
+
12
+ const { block, contract } = Astro.props;
13
+ const values = responsiveValues(
14
+ block.attributes,
15
+ "columns",
16
+ Number(block.attributes.columns) || 12
17
+ );
18
+ const align = ["start", "center", "end", "stretch"].includes(
19
+ String(block.attributes.align)
20
+ ) ? String(block.attributes.align) : "stretch";
21
+ const semantic = semanticProps(block.attributes, contract, {
22
+ surface: "default",
23
+ layoutRole: "container",
24
+ density: "md"
25
+ });
26
+ const style = [
27
+ `--flowpress-grid-base:${values.base}`,
28
+ `--flowpress-grid-sm:${values.sm}`,
29
+ `--flowpress-grid-md:${values.md}`,
30
+ `--flowpress-grid-lg:${values.lg}`,
31
+ `--flowpress-grid-xl:${values.xl}`,
32
+ `--flowpress-grid-2xl:${values["2xl"]}`
33
+ ].join(";");
34
+ ---
35
+
36
+ <S as="div" {...semantic} class="flowpress-grid" data-align={align} style={style}>
37
+ <slot />
38
+ </S>
39
+
40
+ <style>
41
+ .flowpress-grid {
42
+ display: grid;
43
+ grid-template-columns: repeat(var(--flowpress-grid-base), minmax(0, 1fr));
44
+ align-items: var(--flowpress-grid-align, stretch);
45
+ }
46
+ .flowpress-grid[data-align="start"] { --flowpress-grid-align: start; }
47
+ .flowpress-grid[data-align="center"] { --flowpress-grid-align: center; }
48
+ .flowpress-grid[data-align="end"] { --flowpress-grid-align: end; }
49
+ .flowpress-grid[data-align="stretch"] { --flowpress-grid-align: stretch; }
50
+ @media (min-width: 40rem) {
51
+ .flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-sm), minmax(0, 1fr)); }
52
+ }
53
+ @media (min-width: 48rem) {
54
+ .flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-md), minmax(0, 1fr)); }
55
+ }
56
+ @media (min-width: 64rem) {
57
+ .flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-lg), minmax(0, 1fr)); }
58
+ }
59
+ @media (min-width: 80rem) {
60
+ .flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-xl), minmax(0, 1fr)); }
61
+ }
62
+ @media (min-width: 96rem) {
63
+ .flowpress-grid { grid-template-columns: repeat(var(--flowpress-grid-2xl), minmax(0, 1fr)); }
64
+ }
65
+ </style>
@@ -0,0 +1,108 @@
1
+ ---
2
+ import S from "@styleflow.app/astro/S.astro";
3
+ import type { FlowpressBlock } from "../../types.js";
4
+ import type { StyleflowContract } from "../../styleflow.js";
5
+ import { objectValue, semanticProps } from "./helpers.js";
6
+
7
+ interface Props {
8
+ block: FlowpressBlock;
9
+ contract?: StyleflowContract;
10
+ }
11
+
12
+ const { block, contract } = Astro.props;
13
+ const attributes = block.attributes;
14
+ const semantic = semanticProps(attributes, contract, {
15
+ surface: "default",
16
+ layoutRole: "region",
17
+ density: "md"
18
+ });
19
+ const backgroundType = ["color", "image", "video"].includes(
20
+ String(attributes.backgroundType)
21
+ ) ? String(attributes.backgroundType) : "color";
22
+ const image = objectValue(block, "backgroundImage");
23
+ const video = objectValue(block, "backgroundVideo");
24
+ const imageSrc = String(image.url ?? image.sourceUrl ?? "");
25
+ const videoSrc = String(video.url ?? video.sourceUrl ?? video.mediaItemUrl ?? "");
26
+ const scrimEnabled = Boolean(attributes.scrimEnabled) && backgroundType !== "color";
27
+ const scrimIntensity = Math.min(10, Math.max(1, Number(attributes.scrimIntensity) || 6));
28
+ ---
29
+
30
+ <S
31
+ as="section"
32
+ {...semantic}
33
+ class="flowpress-hero"
34
+ style={`--flowpress-scrim-opacity:${scrimIntensity * 10}%`}
35
+ >
36
+ {backgroundType === "image" && imageSrc ? (
37
+ <img
38
+ class="flowpress-hero__media"
39
+ src={imageSrc}
40
+ alt=""
41
+ aria-hidden="true"
42
+ loading="eager"
43
+ decoding="async"
44
+ />
45
+ ) : null}
46
+ {backgroundType === "video" && videoSrc ? (
47
+ <video
48
+ class="flowpress-hero__media"
49
+ src={videoSrc}
50
+ poster={imageSrc || undefined}
51
+ autoplay
52
+ muted
53
+ loop
54
+ playsinline
55
+ preload="metadata"
56
+ aria-hidden="true"
57
+ tabindex="-1"
58
+ />
59
+ ) : null}
60
+ {scrimEnabled ? <span class="flowpress-hero__scrim" aria-hidden="true"></span> : null}
61
+ <div class="flowpress-hero__content">
62
+ <slot />
63
+ </div>
64
+ </S>
65
+
66
+ <style>
67
+ .flowpress-hero {
68
+ position: relative;
69
+ display: grid;
70
+ overflow: hidden;
71
+ isolation: isolate;
72
+ }
73
+
74
+ .flowpress-hero__media,
75
+ .flowpress-hero__scrim {
76
+ position: absolute;
77
+ inset: 0;
78
+ width: 100%;
79
+ height: 100%;
80
+ }
81
+
82
+ .flowpress-hero__media {
83
+ z-index: -2;
84
+ object-fit: cover;
85
+ }
86
+
87
+ .flowpress-hero__scrim {
88
+ z-index: -1;
89
+ background: color-mix(
90
+ in srgb,
91
+ var(--sf-background) var(--flowpress-scrim-opacity),
92
+ transparent
93
+ );
94
+ pointer-events: none;
95
+ }
96
+
97
+ .flowpress-hero__content {
98
+ position: relative;
99
+ display: grid;
100
+ gap: inherit;
101
+ }
102
+
103
+ @media (prefers-reduced-motion: reduce) {
104
+ .flowpress-hero__media:is(video) {
105
+ display: none;
106
+ }
107
+ }
108
+ </style>
@@ -0,0 +1,28 @@
1
+ ---
2
+ import S from "@styleflow.app/astro/S.astro";
3
+
4
+ interface Props {
5
+ item: Record<string, unknown>;
6
+ }
7
+
8
+ const { item } = Astro.props;
9
+ const image = item.image && typeof item.image === "object" && !Array.isArray(item.image)
10
+ ? item.image as Record<string, unknown>
11
+ : {};
12
+ ---
13
+
14
+ {image.url ? (
15
+ <img
16
+ src={String(image.url)}
17
+ alt={String(image.alt ?? "")}
18
+ loading="lazy"
19
+ decoding="async"
20
+ />
21
+ ) : null}
22
+ {item.uri ? (
23
+ <S as="a" href={String(item.uri)}>{String(item.title ?? "")}</S>
24
+ ) : (
25
+ <S as="span">{String(item.title ?? "")}</S>
26
+ )}
27
+ {item.excerpt ? <S as="p">{String(item.excerpt)}</S> : null}
28
+ {item.priceHtml ? <S as="div" set:html={String(item.priceHtml)} /> : null}
@@ -0,0 +1,61 @@
1
+ ---
2
+ import S from "@styleflow.app/astro/S.astro";
3
+ import type { FlowpressBlock } from "../../types.js";
4
+ import type { StyleflowContract } from "../../styleflow.js";
5
+ import { semanticProps, textValue, unwrapCoreHtml } from "./helpers.js";
6
+
7
+ interface Props {
8
+ block: FlowpressBlock;
9
+ contract?: StyleflowContract;
10
+ hasChildren?: boolean;
11
+ }
12
+
13
+ const { block, contract, hasChildren = false } = Astro.props;
14
+ const name = block.name.replace(/^sf\//, "");
15
+ const attributes = block.attributes;
16
+ const heading = name === "heading" || name === "core/heading";
17
+ const level = Math.min(6, Math.max(1, Number(attributes.level ?? 2)));
18
+ const headingTag = `h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
19
+ const defaults = heading
20
+ ? {
21
+ ty: "heading",
22
+ v: String(attributes.v ?? attributes.variant ?? level),
23
+ w: String(attributes.w ?? attributes.weight ?? "strong"),
24
+ foreground: "primary"
25
+ }
26
+ : {
27
+ ty: "body",
28
+ v: String(attributes.v ?? (name.includes("quote") ? "lg" : "md")),
29
+ w: String(attributes.w ?? "default"),
30
+ foreground: "primary"
31
+ };
32
+ const semantic = semanticProps(attributes, contract, defaults);
33
+ const html = name === "heading"
34
+ ? textValue(block)
35
+ : name === "rich-text"
36
+ ? String(attributes.content ?? "")
37
+ : name === "quote"
38
+ ? textValue(block, "quote")
39
+ : unwrapCoreHtml(String(attributes.content ?? attributes.value ?? block.innerHtml ?? ""));
40
+ const listTag = Boolean(attributes.ordered) ? "ol" : "ul";
41
+ ---
42
+
43
+ {heading ? (
44
+ <S as={headingTag} {...semantic} set:html={html} />
45
+ ) : name === "core/list" ? (
46
+ <S as={listTag} {...semantic}>
47
+ {hasChildren ? <slot /> : <Fragment set:html={html} />}
48
+ </S>
49
+ ) : name === "core/list-item" ? (
50
+ <S as="li" {...semantic}>
51
+ <span set:html={html}></span>
52
+ {hasChildren ? <slot /> : null}
53
+ </S>
54
+ ) : name === "quote" || name === "core/quote" ? (
55
+ <S as="blockquote" {...semantic}>
56
+ {hasChildren ? <slot /> : <div set:html={html}></div>}
57
+ {attributes.citation ? <cite>{String(attributes.citation)}</cite> : null}
58
+ </S>
59
+ ) : (
60
+ <S as={name === "core/paragraph" ? "p" : "div"} {...semantic} set:html={html} />
61
+ )}
@@ -0,0 +1,14 @@
1
+ import { type StyleflowContract } from "../../styleflow.js";
2
+ import type { FlowpressBlock } from "../../types.js";
3
+ export declare const DEFAULT_SEMANTIC_PROPS: {
4
+ surface: string;
5
+ layoutRole: string;
6
+ density: string;
7
+ };
8
+ export declare function semanticProps(attributes: Record<string, unknown>, contract?: StyleflowContract, defaults?: Record<string, unknown>): Record<string, unknown>;
9
+ export declare function objectValue(block: FlowpressBlock, key: string): Record<string, unknown>;
10
+ export declare function textValue(block: FlowpressBlock, key?: string): string;
11
+ export declare function linkValue(block: FlowpressBlock, key?: string): string;
12
+ export declare function responsiveValues(attributes: Record<string, unknown>, prefix: "columns" | "span", fallback: number): Record<"base" | "sm" | "md" | "lg" | "xl" | "2xl", number>;
13
+ export declare function unwrapCoreHtml(html: string): string;
14
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +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;AAqBF,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,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"}
@@ -0,0 +1,69 @@
1
+ import { resolveDynamicStyleflowProps } from "../../styleflow.js";
2
+ export const DEFAULT_SEMANTIC_PROPS = {
3
+ surface: "default",
4
+ layoutRole: "stack",
5
+ density: "md"
6
+ };
7
+ const SEMANTIC_KEYS = [
8
+ "tone",
9
+ "intensity",
10
+ "surface",
11
+ "layoutRole",
12
+ "density",
13
+ "ty",
14
+ "v",
15
+ "w",
16
+ "foreground",
17
+ "interactivePriority",
18
+ "noBackground",
19
+ "noBorder",
20
+ "noPadding",
21
+ "noGap",
22
+ "noRadius",
23
+ "noForeground"
24
+ ];
25
+ export function semanticProps(attributes, contract, defaults = DEFAULT_SEMANTIC_PROPS) {
26
+ const input = Object.fromEntries(SEMANTIC_KEYS
27
+ .filter((key) => attributes[key] !== undefined)
28
+ .map((key) => [key, attributes[key]]));
29
+ return contract
30
+ ? resolveDynamicStyleflowProps(input, defaults, contract)
31
+ : { ...defaults, ...input };
32
+ }
33
+ export function objectValue(block, key) {
34
+ const value = block.attributes[key];
35
+ return value && typeof value === "object" && !Array.isArray(value)
36
+ ? value
37
+ : {};
38
+ }
39
+ export function textValue(block, key = "text") {
40
+ return String(block.attributes[key] ?? "");
41
+ }
42
+ export function linkValue(block, key = "link") {
43
+ const value = block.attributes[key];
44
+ const href = typeof value === "string"
45
+ ? value
46
+ : String(objectValue(block, key).url ?? "");
47
+ return /^(?:\/(?!\/)|#|https?:\/\/|mailto:|tel:)/i.test(href) ? href : "";
48
+ }
49
+ export function responsiveValues(attributes, prefix, fallback) {
50
+ const keys = ["base", "sm", "md", "lg", "xl", "2xl"];
51
+ const result = {};
52
+ let inherited = fallback;
53
+ for (const key of keys) {
54
+ const suffix = key === "base"
55
+ ? "Base"
56
+ : key === "2xl" ? "2xl" : key.charAt(0).toUpperCase() + key.slice(1);
57
+ const candidate = Number(attributes[`${prefix}${suffix}`]);
58
+ if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {
59
+ inherited = candidate;
60
+ }
61
+ result[key] = inherited;
62
+ }
63
+ return result;
64
+ }
65
+ export function unwrapCoreHtml(html) {
66
+ const match = html.trim().match(/^<(p|h[1-6]|ul|ol|li|blockquote)(?:\s[^>]*)?>([\s\S]*)<\/\1>$/);
67
+ return match?.[2] ?? html;
68
+ }
69
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +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\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\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"]}
@@ -0,0 +1,95 @@
1
+ import {
2
+ resolveDynamicStyleflowProps,
3
+ type StyleflowContract
4
+ } from "../../styleflow.js";
5
+ import type { FlowpressBlock } from "../../types.js";
6
+
7
+ export const DEFAULT_SEMANTIC_PROPS = {
8
+ surface: "default",
9
+ layoutRole: "stack",
10
+ density: "md"
11
+ };
12
+
13
+ const SEMANTIC_KEYS = [
14
+ "tone",
15
+ "intensity",
16
+ "surface",
17
+ "layoutRole",
18
+ "density",
19
+ "ty",
20
+ "v",
21
+ "w",
22
+ "foreground",
23
+ "interactivePriority",
24
+ "noBackground",
25
+ "noBorder",
26
+ "noPadding",
27
+ "noGap",
28
+ "noRadius",
29
+ "noForeground"
30
+ ] as const;
31
+
32
+ export function semanticProps(
33
+ attributes: Record<string, unknown>,
34
+ contract?: StyleflowContract,
35
+ defaults: Record<string, unknown> = DEFAULT_SEMANTIC_PROPS
36
+ ): Record<string, unknown> {
37
+ const input = Object.fromEntries(
38
+ SEMANTIC_KEYS
39
+ .filter((key) => attributes[key] !== undefined)
40
+ .map((key) => [key, attributes[key]])
41
+ );
42
+ return contract
43
+ ? resolveDynamicStyleflowProps(input, defaults, contract)
44
+ : { ...defaults, ...input };
45
+ }
46
+
47
+ export function objectValue(
48
+ block: FlowpressBlock,
49
+ key: string
50
+ ): Record<string, unknown> {
51
+ const value = block.attributes[key];
52
+ return value && typeof value === "object" && !Array.isArray(value)
53
+ ? value as Record<string, unknown>
54
+ : {};
55
+ }
56
+
57
+ export function textValue(block: FlowpressBlock, key = "text"): string {
58
+ return String(block.attributes[key] ?? "");
59
+ }
60
+
61
+ export function linkValue(block: FlowpressBlock, key = "link"): string {
62
+ const value = block.attributes[key];
63
+ const href = typeof value === "string"
64
+ ? value
65
+ : String(objectValue(block, key).url ?? "");
66
+ return /^(?:\/(?!\/)|#|https?:\/\/|mailto:|tel:)/i.test(href) ? href : "";
67
+ }
68
+
69
+ export function responsiveValues(
70
+ attributes: Record<string, unknown>,
71
+ prefix: "columns" | "span",
72
+ fallback: number
73
+ ): Record<"base" | "sm" | "md" | "lg" | "xl" | "2xl", number> {
74
+ const keys = ["base", "sm", "md", "lg", "xl", "2xl"] as const;
75
+ const result = {} as Record<(typeof keys)[number], number>;
76
+ let inherited = fallback;
77
+ for (const key of keys) {
78
+ const suffix = key === "base"
79
+ ? "Base"
80
+ : key === "2xl" ? "2xl" : key.charAt(0).toUpperCase() + key.slice(1);
81
+ const candidate = Number(attributes[`${prefix}${suffix}`]);
82
+ if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {
83
+ inherited = candidate;
84
+ }
85
+ result[key] = inherited;
86
+ }
87
+ return result;
88
+ }
89
+
90
+ export function unwrapCoreHtml(html: string): string {
91
+ const match = html.trim().match(
92
+ /^<(p|h[1-6]|ul|ol|li|blockquote)(?:\s[^>]*)?>([\s\S]*)<\/\1>$/
93
+ );
94
+ return match?.[2] ?? html;
95
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"styleflow.d.ts","sourceRoot":"","sources":["../src/styleflow.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,WAAW,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;QACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,WAAW,EAAE;QACX,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAoB5D;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,qBAAqB,EAC5B,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,iBAAiB,GAC1B,qBAAqB,CAwCvB"}
1
+ {"version":3,"file":"styleflow.d.ts","sourceRoot":"","sources":["../src/styleflow.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,WAAW,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;QACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,SAAS,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;IACF,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,WAAW,EAAE;QACX,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,EAAE,MAAM,EAAE,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAqB5D;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,qBAAqB,EAC5B,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,iBAAiB,GAC1B,qBAAqB,CA+CvB"}
package/dist/styleflow.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const INHERITED_COLOR_PROPS = new Set(["tone", "intensity"]);
2
+ const FOREGROUND_ROLES = ["primary", "secondary", "tertiary", "muted", "accent"];
2
3
  const AXIS_BY_PROP = {
3
4
  tone: ["color", "tones"],
4
5
  intensity: ["color", "intensities"],
@@ -35,6 +36,12 @@ export function resolveDynamicStyleflowProps(input, defaults, contract) {
35
36
  : defaults.v;
36
37
  continue;
37
38
  }
39
+ if (key === "foreground") {
40
+ resolved.foreground = FOREGROUND_ROLES.includes(String(value))
41
+ ? value
42
+ : defaults.foreground;
43
+ continue;
44
+ }
38
45
  const axisPath = AXIS_BY_PROP[key];
39
46
  if (!axisPath) {
40
47
  resolved[key] = value;
@@ -1 +1 @@
1
- {"version":3,"file":"styleflow.js","sourceRoot":"","sources":["../src/styleflow.ts"],"names":[],"mappings":"AA2BA,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;AAE7D,MAAM,YAAY,GAAG;IACnB,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC;IACnC,OAAO,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9B,UAAU,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC;IAChC,EAAE,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC;IAC3B,CAAC,EAAE,CAAC,YAAY,EAAE,SAAS,CAAC;IAC5B,mBAAmB,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC;IAClD,gBAAgB,EAAE,CAAC,aAAa,EAAE,QAAQ,CAAC;CACnC,CAAC;AAEX,SAAS,aAAa,CAAC,SAAkB,EAAE,OAAgB;IACzD,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,KAA4B,EAC5B,QAA+B,EAC/B,QAA2B;IAE3B,MAAM,QAAQ,GAA0B,EAAE,GAAG,QAAQ,EAAE,CAAC;IAExD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YAC1D,SAAS;QACX,CAAC;QAED,IAAI,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC1D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;YACrB,SAAS;QACX,CAAC;QAED,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;YAChB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/D,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACpE,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAgC,CAAC,CAAC;QAChE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACtB,SAAS;QACX,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC;QAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAGvB,CAAC;QACF,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5C,QAAQ,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAChE,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["export interface StyleflowContractAxes {\n color: {\n tones: string[];\n intensities: Array<string | number>;\n surfaces: string[];\n };\n layout: {\n roles: string[];\n densities: string[];\n };\n typography: {\n types: string[];\n variants: Record<string, string[]>;\n weights: string[];\n };\n interaction: {\n priorities: string[];\n states: string[];\n };\n}\n\nexport interface StyleflowContract {\n axes: StyleflowContractAxes;\n}\n\nexport type DynamicStyleflowProps = Record<string, unknown>;\n\nconst INHERITED_COLOR_PROPS = new Set([\"tone\", \"intensity\"]);\n\nconst AXIS_BY_PROP = {\n tone: [\"color\", \"tones\"],\n intensity: [\"color\", \"intensities\"],\n surface: [\"color\", \"surfaces\"],\n layoutRole: [\"layout\", \"roles\"],\n density: [\"layout\", \"densities\"],\n ty: [\"typography\", \"types\"],\n w: [\"typography\", \"weights\"],\n interactivePriority: [\"interaction\", \"priorities\"],\n interactionState: [\"interaction\", \"states\"]\n} as const;\n\nfunction sameAxisValue(candidate: unknown, allowed: unknown): boolean {\n return String(candidate) === String(allowed);\n}\n\n/**\n * CMS values can outlive a contract revision. Invalid values fail closed to\n * component defaults before reaching Styleflow's Astro runtime.\n */\nexport function resolveDynamicStyleflowProps(\n input: DynamicStyleflowProps,\n defaults: DynamicStyleflowProps,\n contract: StyleflowContract\n): DynamicStyleflowProps {\n const resolved: DynamicStyleflowProps = { ...defaults };\n\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined || value === null || value === \"\") {\n continue;\n }\n\n if (INHERITED_COLOR_PROPS.has(key) && value === \"inherit\") {\n delete resolved[key];\n continue;\n }\n\n if (key === \"v\") {\n const type = String(input.ty ?? defaults.ty ?? \"\");\n const variants = contract.axes.typography.variants[type] ?? [];\n resolved.v = variants.some((variant) => sameAxisValue(value, variant))\n ? value\n : defaults.v;\n continue;\n }\n\n const axisPath = AXIS_BY_PROP[key as keyof typeof AXIS_BY_PROP];\n if (!axisPath) {\n resolved[key] = value;\n continue;\n }\n\n const [group, axis] = axisPath;\n const groups = contract.axes as unknown as Record<\n string,\n Record<string, unknown[]>\n >;\n const allowed = groups[group]?.[axis] ?? [];\n resolved[key] = allowed.some((item) => sameAxisValue(value, item))\n ? value\n : defaults[key];\n }\n\n return resolved;\n}\n"]}
1
+ {"version":3,"file":"styleflow.js","sourceRoot":"","sources":["../src/styleflow.ts"],"names":[],"mappings":"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","sourcesContent":["export interface StyleflowContractAxes {\n color: {\n tones: string[];\n intensities: Array<string | number>;\n surfaces: string[];\n };\n layout: {\n roles: string[];\n densities: string[];\n };\n typography: {\n types: string[];\n variants: Record<string, string[]>;\n weights: string[];\n };\n interaction: {\n priorities: string[];\n states: string[];\n };\n}\n\nexport interface StyleflowContract {\n axes: StyleflowContractAxes;\n}\n\nexport type DynamicStyleflowProps = Record<string, unknown>;\n\nconst INHERITED_COLOR_PROPS = new Set([\"tone\", \"intensity\"]);\nconst FOREGROUND_ROLES = [\"primary\", \"secondary\", \"tertiary\", \"muted\", \"accent\"];\n\nconst AXIS_BY_PROP = {\n tone: [\"color\", \"tones\"],\n intensity: [\"color\", \"intensities\"],\n surface: [\"color\", \"surfaces\"],\n layoutRole: [\"layout\", \"roles\"],\n density: [\"layout\", \"densities\"],\n ty: [\"typography\", \"types\"],\n w: [\"typography\", \"weights\"],\n interactivePriority: [\"interaction\", \"priorities\"],\n interactionState: [\"interaction\", \"states\"]\n} as const;\n\nfunction sameAxisValue(candidate: unknown, allowed: unknown): boolean {\n return String(candidate) === String(allowed);\n}\n\n/**\n * CMS values can outlive a contract revision. Invalid values fail closed to\n * component defaults before reaching Styleflow's Astro runtime.\n */\nexport function resolveDynamicStyleflowProps(\n input: DynamicStyleflowProps,\n defaults: DynamicStyleflowProps,\n contract: StyleflowContract\n): DynamicStyleflowProps {\n const resolved: DynamicStyleflowProps = { ...defaults };\n\n for (const [key, value] of Object.entries(input)) {\n if (value === undefined || value === null || value === \"\") {\n continue;\n }\n\n if (INHERITED_COLOR_PROPS.has(key) && value === \"inherit\") {\n delete resolved[key];\n continue;\n }\n\n if (key === \"v\") {\n const type = String(input.ty ?? defaults.ty ?? \"\");\n const variants = contract.axes.typography.variants[type] ?? [];\n resolved.v = variants.some((variant) => sameAxisValue(value, variant))\n ? value\n : defaults.v;\n continue;\n }\n\n if (key === \"foreground\") {\n resolved.foreground = FOREGROUND_ROLES.includes(String(value))\n ? value\n : defaults.foreground;\n continue;\n }\n\n const axisPath = AXIS_BY_PROP[key as keyof typeof AXIS_BY_PROP];\n if (!axisPath) {\n resolved[key] = value;\n continue;\n }\n\n const [group, axis] = axisPath;\n const groups = contract.axes as unknown as Record<\n string,\n Record<string, unknown[]>\n >;\n const allowed = groups[group]?.[axis] ?? [];\n resolved[key] = allowed.some((item) => sameAxisValue(value, item))\n ? value\n : defaults[key];\n }\n\n return resolved;\n}\n"]}
@@ -1 +1 @@
1
- {"root":["../src/astro.d.ts","../src/client.ts","../src/env.ts","../src/index.ts","../src/styleflow.ts","../src/template-hierarchy.ts","../src/templates.ts","../src/types.ts"],"version":"5.9.3"}
1
+ {"root":["../src/astro.d.ts","../src/client.ts","../src/env.ts","../src/index.ts","../src/styleflow.ts","../src/template-hierarchy.ts","../src/templates.ts","../src/types.ts","../src/astro/blocks/helpers.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@styleflow.app/flowpress-astro-core",
3
- "version": "0.2.0-beta.4",
3
+ "version": "0.2.0-beta.7",
4
4
  "description": "Project-agnostic WordPress routing, templates, SEO and GraphQL runtime for FlowPress Astro apps.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "build": "tsc -b && node ./scripts/copy-astro.mjs",
38
- "typecheck": "tsc -b --pretty false && node ./scripts/copy-astro.mjs",
38
+ "typecheck": "tsc -b --pretty false && node ./scripts/copy-astro.mjs && astro check",
39
39
  "test": "vitest run",
40
40
  "release:check": "npm run typecheck && npm test && npm pack --dry-run",
41
41
  "release:publish:next": "npm publish --access public --tag next",
@@ -43,12 +43,13 @@
43
43
  "prepublishOnly": "npm run typecheck && npm test && npm run build"
44
44
  },
45
45
  "dependencies": {
46
- "@styleflow.app/astro": "0.3.0-beta.2"
46
+ "@styleflow.app/astro": "0.3.0-beta.3"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "astro": ">=7.0.0"
50
50
  },
51
51
  "devDependencies": {
52
+ "@astrojs/check": "^0.9.9",
52
53
  "@types/node": "^22.19.3",
53
54
  "astro": "^7.0.4",
54
55
  "typescript": "^5.9.3",