@styleflow.app/flowpress-astro-core 1.0.0-beta.1 → 1.0.0-beta.3

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,25 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.0.0-beta.3
6
+
7
+ - Deferred generic Gutenberg semantic validation until its renderer branch is
8
+ selected, preventing valid numeric heading variants from being validated as
9
+ body typography and spuriously falling back to `md`.
10
+ - Added a strict production fixture for `core/heading` with `v="5"` and verified
11
+ that the emitted Styleflow type and variant remain `heading` and `5`.
12
+
13
+ ## 1.0.0-beta.2
14
+
15
+ - Added exact `@splidejs/splide@4.1.4` support and dedicated renderers for
16
+ `sf/carousel` and `sf/carousel-item` with responsive item counts, arrows,
17
+ pagination, drag, focused keyboard control, loop and autoplay.
18
+ - Added accessible live-region integration and mandatory Play/Pause controls
19
+ for autoplay, including hover/focus pauses and reduced-motion protection.
20
+ - Added defensive CMS clamps, per-instance custom-element lifecycle cleanup,
21
+ readable no-JavaScript layouts and a diagnostic static fallback that keeps
22
+ legacy content whose direct children are not carousel items.
23
+
5
24
  ## 1.0.0-beta.1
6
25
 
7
26
  - Updated the exact `@styleflow.app/astro` dependency to `1.0.0-beta.1`,
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@1.0.0-beta.1 \
12
+ pnpm add @styleflow.app/flowpress-astro-core@1.0.0-beta.3 \
13
13
  @styleflow.app/astro@1.0.0-beta.1
14
14
  ```
15
15
 
@@ -54,6 +54,22 @@ visible characters. The exported
54
54
  from rendered CMS blocks so consumers can generate Tailwind
55
55
  `@source inline()` directives before compiling their frontend CSS.
56
56
 
57
+ `sf/carousel` renders an accessible horizontal Splide carousel whose direct
58
+ children must be `sf/carousel-item`; each item can recursively contain any
59
+ renderable block. Item counts inherit across `base/sm/md/lg/xl/2xl`, with
60
+ defaults `1/1/2/3/3/3`. Arrows, pagination, drag/swipe, focused keyboard
61
+ navigation, loop and autoplay follow CMS attributes. Autoplay always includes
62
+ one Play/Pause control, pauses on configured hover/focus states and starts
63
+ paused under reduced-motion preferences. Every custom element owns and
64
+ destroys exactly one Splide instance.
65
+
66
+ Stored CMS values are clamped to the public contract: item counts and movement
67
+ `1–12`, gap `0–128px`, autoplay interval `1000–60000ms`, and transition speed
68
+ `0–5000ms`. Without JavaScript, all valid items remain a readable responsive
69
+ grid and dead controls are hidden. If a legacy carousel contains any direct
70
+ child other than `sf/carousel-item`, no interactive instance is initialized:
71
+ the complete content renders statically, with a development diagnostic.
72
+
57
73
  Heading, rich-text, quote and matching core blocks accept Gutenberg alignment
58
74
  from either `style.typography.textAlign` or the FlowPress `textAlign` field.
59
75
  Only `left`, `center`, `right`, `justify`, `start` and `end` reach CSS.
@@ -7,6 +7,8 @@ import type {
7
7
  import type { StyleflowContract } from "../styleflow.js";
8
8
  import type { FlowpressBlock } from "../types.js";
9
9
  import ColumnBlock from "./blocks/ColumnBlock.astro";
10
+ import CarouselBlock from "./blocks/CarouselBlock.astro";
11
+ import CarouselItemBlock from "./blocks/CarouselItemBlock.astro";
10
12
  import FormBlock from "./blocks/FormBlock.astro";
11
13
  import GridBlock from "./blocks/GridBlock.astro";
12
14
  import HeroBlock from "./blocks/HeroBlock.astro";
@@ -52,7 +54,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
52
54
  {blocks.map((block) => {
53
55
  const name = block.name.replace(/^sf\//, "");
54
56
  const children = block.innerBlocks ?? [];
55
- const semantic = semanticProps(block.attributes, contract);
57
+ const genericSemantic = () => semanticProps(block.attributes, contract);
56
58
  const htmlAttributes = blockHtmlAttributes(block.attributes);
57
59
  const CustomBlockRenderer = blockRenderers[block.name];
58
60
 
@@ -108,6 +110,40 @@ function youtubeEmbedUrl(value: unknown): string | null {
108
110
  );
109
111
  }
110
112
 
113
+ if (name === "carousel") {
114
+ const invalidChildNames = children
115
+ .filter((child) => child.name !== "sf/carousel-item")
116
+ .map((child) => child.name);
117
+ return (
118
+ <CarouselBlock
119
+ block={block}
120
+ contract={contract}
121
+ validChildren={invalidChildNames.length === 0}
122
+ invalidChildNames={invalidChildNames}
123
+ >
124
+ <Astro.self
125
+ blocks={children}
126
+ contract={contract}
127
+ blockRenderers={blockRenderers}
128
+ iconRenderer={IconRenderer}
129
+ />
130
+ </CarouselBlock>
131
+ );
132
+ }
133
+
134
+ if (name === "carousel-item") {
135
+ return (
136
+ <CarouselItemBlock block={block} contract={contract}>
137
+ <Astro.self
138
+ blocks={children}
139
+ contract={contract}
140
+ blockRenderers={blockRenderers}
141
+ iconRenderer={IconRenderer}
142
+ />
143
+ </CarouselItemBlock>
144
+ );
145
+ }
146
+
111
147
  if (name === "number-spinner") {
112
148
  return <NumberSpinnerBlock block={block} contract={contract} />;
113
149
  }
@@ -208,7 +244,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
208
244
  ? block.attributes.images as Array<Record<string, unknown>>
209
245
  : [];
210
246
  return (
211
- <S as="div" {...semantic} {...htmlAttributes}>
247
+ <S as="div" {...genericSemantic()} {...htmlAttributes}>
212
248
  {images.map((image) => image.url ? (
213
249
  <img
214
250
  src={String(image.url)}
@@ -225,7 +261,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
225
261
  const video = objectValue(block, "video");
226
262
  const poster = objectValue(block, "poster");
227
263
  return video.url ? (
228
- <S as="div" {...semantic} {...htmlAttributes}>
264
+ <S as="div" {...genericSemantic()} {...htmlAttributes}>
229
265
  <video
230
266
  src={String(video.url)}
231
267
  poster={poster.url ? String(poster.url) : undefined}
@@ -241,7 +277,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
241
277
  if (name === "video-youtube") {
242
278
  const embed = youtubeEmbedUrl(block.attributes.url);
243
279
  return embed ? (
244
- <S as="div" {...semantic} {...htmlAttributes}>
280
+ <S as="div" {...genericSemantic()} {...htmlAttributes}>
245
281
  <iframe
246
282
  src={embed}
247
283
  title={String(block.attributes.title ?? "YouTube video")}
@@ -255,7 +291,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
255
291
 
256
292
  if (name === "chip") {
257
293
  return (
258
- <S as="span" {...semantic} {...htmlAttributes}>
294
+ <S as="span" {...genericSemantic()} {...htmlAttributes}>
259
295
  {textValue(block, "label")}
260
296
  </S>
261
297
  );
@@ -265,7 +301,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
265
301
  const media = objectValue(block, "image");
266
302
  const href = linkValue(block);
267
303
  return (
268
- <S as="article" {...semantic} {...htmlAttributes}>
304
+ <S as="article" {...genericSemantic()} {...htmlAttributes}>
269
305
  {media.url && (
270
306
  <img src={String(media.url)} alt={String(media.alt ?? "")} loading="lazy" />
271
307
  )}
@@ -284,7 +320,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
284
320
  ? block.attributes.resolvedBlocks as FlowpressBlock[][]
285
321
  : [];
286
322
  return (
287
- <S as="ul" {...semantic} {...htmlAttributes}>
323
+ <S as="ul" {...genericSemantic()} {...htmlAttributes}>
288
324
  {items.map((item, index) => (
289
325
  <S as="li">
290
326
  {resolvedBlocks[index]?.length ? (
@@ -329,7 +365,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
329
365
 
330
366
  if (name === "accordion-item") {
331
367
  return (
332
- <S as="div" {...semantic} {...htmlAttributes}>
368
+ <S as="div" {...genericSemantic()} {...htmlAttributes}>
333
369
  <details>
334
370
  <summary>{String(block.attributes.title ?? "")}</summary>
335
371
  <Astro.self
@@ -360,7 +396,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
360
396
  + `?key=${encodeURIComponent(googleMapsApiKey)}`
361
397
  + `&q=${encodeURIComponent(query)}`;
362
398
  return (
363
- <S as="div" {...semantic} {...htmlAttributes}>
399
+ <S as="div" {...genericSemantic()} {...htmlAttributes}>
364
400
  <iframe
365
401
  src={source}
366
402
  title={address || "Map"}
@@ -371,7 +407,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
371
407
  );
372
408
  }
373
409
  return query ? (
374
- <S as="div" {...semantic} {...htmlAttributes}>
410
+ <S as="div" {...genericSemantic()} {...htmlAttributes}>
375
411
  <S
376
412
  as="a"
377
413
  href={`https://www.openstreetmap.org/search?query=${encodeURIComponent(query)}`}
@@ -385,7 +421,7 @@ function youtubeEmbedUrl(value: unknown): string | null {
385
421
 
386
422
  const layoutTag = name === "section" ? "section" : "div";
387
423
  return (
388
- <S as={layoutTag} {...semantic} {...htmlAttributes}>
424
+ <S as={layoutTag} {...genericSemantic()} {...htmlAttributes}>
389
425
  {block.innerHtml && children.length === 0
390
426
  ? <S as="div" set:html={block.innerHtml} />
391
427
  : (
@@ -0,0 +1,200 @@
1
+ ---
2
+ import "@splidejs/splide/css/core";
3
+ import S from "@styleflow.app/astro/S.astro";
4
+ import { carouselOptions, carouselSettings } from "../../carousel.js";
5
+ import type { FlowpressBlock } from "../../types.js";
6
+ import type { StyleflowContract } from "../../styleflow.js";
7
+ import { blockHtmlAttributes, semanticProps } from "./helpers.js";
8
+
9
+ interface Props {
10
+ block: FlowpressBlock;
11
+ contract?: StyleflowContract;
12
+ validChildren: boolean;
13
+ invalidChildNames?: string[];
14
+ }
15
+
16
+ const { block, contract, validChildren, invalidChildNames = [] } = Astro.props;
17
+ const settings = carouselSettings(block.attributes);
18
+ const options = carouselOptions(settings);
19
+ const semantic = semanticProps(block.attributes, contract, {
20
+ surface: "default",
21
+ layoutRole: "container",
22
+ density: "regular"
23
+ });
24
+ const htmlAttributes = blockHtmlAttributes(
25
+ block.attributes,
26
+ `flowpress-carousel${validChildren ? " splide" : " flowpress-carousel--static"}`
27
+ );
28
+ const style = [
29
+ `--flowpress-carousel-gap:${settings.gap}px`,
30
+ `--flowpress-carousel-base:${settings.itemsPerView.base}`,
31
+ `--flowpress-carousel-sm:${settings.itemsPerView.sm}`,
32
+ `--flowpress-carousel-md:${settings.itemsPerView.md}`,
33
+ `--flowpress-carousel-lg:${settings.itemsPerView.lg}`,
34
+ `--flowpress-carousel-xl:${settings.itemsPerView.xl}`,
35
+ `--flowpress-carousel-2xl:${settings.itemsPerView["2xl"]}`
36
+ ].join(";");
37
+
38
+ if (!validChildren && import.meta.env.DEV) {
39
+ console.warn(
40
+ `[flowpress-carousel-invalid-children] sf/carousel accepts only sf/carousel-item; received: ${invalidChildNames.join(", ") || "unknown"}. Rendering static content.`
41
+ );
42
+ }
43
+ ---
44
+
45
+ {validChildren ? (
46
+ <flowpress-carousel>
47
+ <S
48
+ as="section"
49
+ {...semantic}
50
+ {...htmlAttributes}
51
+ aria-label={settings.ariaLabel}
52
+ data-flowpress-carousel-root
53
+ data-carousel-options={JSON.stringify(options)}
54
+ style={style}
55
+ >
56
+ {settings.showArrows ? (
57
+ <div class="splide__arrows" data-flowpress-carousel-controls>
58
+ <button class="splide__arrow splide__arrow--prev" type="button">
59
+ <span aria-hidden="true">←</span>
60
+ </button>
61
+ <button class="splide__arrow splide__arrow--next" type="button">
62
+ <span aria-hidden="true">→</span>
63
+ </button>
64
+ </div>
65
+ ) : null}
66
+ <div class="splide__track">
67
+ <ul class="splide__list">
68
+ <slot />
69
+ </ul>
70
+ </div>
71
+ {settings.showPagination ? <ul class="splide__pagination"></ul> : null}
72
+ {settings.autoplay ? (
73
+ <button
74
+ class="splide__toggle"
75
+ type="button"
76
+ data-flowpress-carousel-controls
77
+ >
78
+ <span class="splide__toggle__play">Avvia</span>
79
+ <span class="splide__toggle__pause">Pausa</span>
80
+ </button>
81
+ ) : null}
82
+ </S>
83
+ </flowpress-carousel>
84
+ ) : (
85
+ <S
86
+ as="section"
87
+ {...semantic}
88
+ {...htmlAttributes}
89
+ aria-label={settings.ariaLabel}
90
+ data-flowpress-carousel-invalid
91
+ data-invalid-children={invalidChildNames.join(",")}
92
+ style={style}
93
+ >
94
+ <slot />
95
+ </S>
96
+ )}
97
+
98
+ <script>
99
+ import Splide from "@splidejs/splide";
100
+ import type { Options } from "@splidejs/splide";
101
+
102
+ if (!customElements.get("flowpress-carousel")) {
103
+ customElements.define("flowpress-carousel", class extends HTMLElement {
104
+ private instance?: Splide;
105
+
106
+ connectedCallback() {
107
+ if (this.instance) return;
108
+ const root = this.querySelector<HTMLElement>("[data-flowpress-carousel-root]");
109
+ if (!root) return;
110
+ try {
111
+ const options = JSON.parse(root.dataset.carouselOptions || "{}") as Options;
112
+ this.instance = new Splide(root, options);
113
+ this.instance.mount();
114
+ } catch (error) {
115
+ console.error("[flowpress-carousel-init] Unable to initialize carousel.", error);
116
+ }
117
+ }
118
+
119
+ disconnectedCallback() {
120
+ this.instance?.destroy(true);
121
+ this.instance = undefined;
122
+ }
123
+ });
124
+ }
125
+ </script>
126
+
127
+ <style>
128
+ flowpress-carousel { display: block; }
129
+ .flowpress-carousel { min-width: 0; }
130
+ .flowpress-carousel:not(.is-initialized) .splide__list {
131
+ display: grid;
132
+ grid-template-columns: repeat(var(--flowpress-carousel-base), minmax(0, 1fr));
133
+ gap: var(--flowpress-carousel-gap);
134
+ }
135
+ .flowpress-carousel:not(.is-initialized) [data-flowpress-carousel-controls],
136
+ .flowpress-carousel:not(.is-initialized) .splide__pagination {
137
+ display: none;
138
+ }
139
+ .flowpress-carousel--static {
140
+ display: grid;
141
+ gap: var(--flowpress-carousel-gap);
142
+ }
143
+ .splide__arrows {
144
+ display: flex;
145
+ justify-content: space-between;
146
+ gap: 0.5rem;
147
+ margin-block-end: 0.75rem;
148
+ }
149
+ .splide__arrow:disabled { opacity: 0.45; }
150
+ .splide__pagination {
151
+ display: flex;
152
+ justify-content: center;
153
+ gap: 0.5rem;
154
+ margin: 0.75rem 0 0;
155
+ padding: 0;
156
+ }
157
+ :global(.splide__pagination__page) {
158
+ width: 0.75rem;
159
+ height: 0.75rem;
160
+ border-radius: 999px;
161
+ }
162
+ .splide__toggle { margin-block-start: 0.75rem; }
163
+ .splide__toggle__pause { display: none; }
164
+ .splide__toggle.is-active .splide__toggle__play { display: none; }
165
+ .splide__toggle.is-active .splide__toggle__pause { display: inline; }
166
+ .splide__arrow:focus-visible,
167
+ :global(.splide__pagination__page:focus-visible),
168
+ .splide__toggle:focus-visible {
169
+ outline: 3px solid currentColor;
170
+ outline-offset: 3px;
171
+ }
172
+ @media (min-width: 40rem) {
173
+ .flowpress-carousel:not(.is-initialized) .splide__list {
174
+ grid-template-columns: repeat(var(--flowpress-carousel-sm), minmax(0, 1fr));
175
+ }
176
+ }
177
+ @media (min-width: 48rem) {
178
+ .flowpress-carousel:not(.is-initialized) .splide__list {
179
+ grid-template-columns: repeat(var(--flowpress-carousel-md), minmax(0, 1fr));
180
+ }
181
+ }
182
+ @media (min-width: 64rem) {
183
+ .flowpress-carousel:not(.is-initialized) .splide__list {
184
+ grid-template-columns: repeat(var(--flowpress-carousel-lg), minmax(0, 1fr));
185
+ }
186
+ }
187
+ @media (min-width: 80rem) {
188
+ .flowpress-carousel:not(.is-initialized) .splide__list {
189
+ grid-template-columns: repeat(var(--flowpress-carousel-xl), minmax(0, 1fr));
190
+ }
191
+ }
192
+ @media (min-width: 96rem) {
193
+ .flowpress-carousel:not(.is-initialized) .splide__list {
194
+ grid-template-columns: repeat(var(--flowpress-carousel-2xl), minmax(0, 1fr));
195
+ }
196
+ }
197
+ @media (prefers-reduced-motion: reduce) {
198
+ .flowpress-carousel * { scroll-behavior: auto !important; }
199
+ }
200
+ </style>
@@ -0,0 +1,26 @@
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 { blockHtmlAttributes, semanticProps } from "./helpers.js";
6
+
7
+ interface Props {
8
+ block: FlowpressBlock;
9
+ contract?: StyleflowContract;
10
+ }
11
+
12
+ const { block, contract } = Astro.props;
13
+ const semantic = semanticProps(block.attributes, contract);
14
+ const htmlAttributes = blockHtmlAttributes(
15
+ block.attributes,
16
+ "splide__slide flowpress-carousel-item"
17
+ );
18
+ ---
19
+
20
+ <S as="li" {...semantic} {...htmlAttributes}>
21
+ <slot />
22
+ </S>
23
+
24
+ <style>
25
+ .flowpress-carousel-item { min-width: 0; }
26
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { Options } from "@splidejs/splide";
2
+ declare const BREAKPOINTS: readonly ["base", "sm", "md", "lg", "xl", "2xl"];
3
+ export interface FlowpressCarouselSettings {
4
+ ariaLabel: string;
5
+ itemsPerView: Record<(typeof BREAKPOINTS)[number], number>;
6
+ itemsPerMove: number;
7
+ gap: number;
8
+ showArrows: boolean;
9
+ showPagination: boolean;
10
+ drag: boolean;
11
+ keyboard: boolean;
12
+ autoplay: boolean;
13
+ autoplayInterval: number;
14
+ pauseOnHover: boolean;
15
+ pauseOnFocus: boolean;
16
+ loop: boolean;
17
+ speed: number;
18
+ }
19
+ export declare function carouselSettings(attributes: Record<string, unknown>): FlowpressCarouselSettings;
20
+ export declare function carouselOptions(settings: FlowpressCarouselSettings): Options;
21
+ export {};
22
+ //# sourceMappingURL=carousel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["../src/carousel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,QAAA,MAAM,WAAW,kDAAmD,CAAC;AAErE,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAqBD,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,yBAAyB,CAgD3B;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,yBAAyB,GAAG,OAAO,CA6C5E"}
@@ -0,0 +1,102 @@
1
+ const BREAKPOINTS = ["base", "sm", "md", "lg", "xl", "2xl"];
2
+ function boundedNumber(value, fallback, min, max) {
3
+ if (value === "" || value === null || value === undefined || value === "inherit") {
4
+ return fallback;
5
+ }
6
+ const numeric = Number(value);
7
+ return Number.isFinite(numeric)
8
+ ? Math.min(max, Math.max(min, Math.round(numeric)))
9
+ : fallback;
10
+ }
11
+ function booleanValue(value, fallback) {
12
+ return typeof value === "boolean" ? value : fallback;
13
+ }
14
+ export function carouselSettings(attributes) {
15
+ const defaults = { base: 1, sm: 1, md: 2, lg: 3, xl: 3, "2xl": 3 };
16
+ const values = {};
17
+ let inherited = defaults.base;
18
+ for (const breakpoint of BREAKPOINTS) {
19
+ const suffix = breakpoint === "base"
20
+ ? "Base"
21
+ : breakpoint === "2xl"
22
+ ? "2xl"
23
+ : breakpoint.charAt(0).toUpperCase() + breakpoint.slice(1);
24
+ const raw = attributes[`itemsPerView${suffix}`];
25
+ const fallback = breakpoint !== "base" && (raw === "" || raw === "inherit")
26
+ ? inherited
27
+ : breakpoint === "base"
28
+ ? defaults.base
29
+ : breakpoint === "md" || breakpoint === "lg"
30
+ ? defaults[breakpoint]
31
+ : inherited;
32
+ const value = boundedNumber(raw, fallback, 1, 12);
33
+ values[breakpoint] = value;
34
+ inherited = value;
35
+ }
36
+ const ariaLabel = typeof attributes.ariaLabel === "string"
37
+ ? attributes.ariaLabel.trim()
38
+ : "";
39
+ return {
40
+ ariaLabel: ariaLabel || "Carosello",
41
+ itemsPerView: values,
42
+ itemsPerMove: boundedNumber(attributes.itemsPerMove, 1, 1, 12),
43
+ gap: boundedNumber(attributes.gap, 24, 0, 128),
44
+ showArrows: booleanValue(attributes.showArrows, true),
45
+ showPagination: booleanValue(attributes.showPagination, true),
46
+ drag: booleanValue(attributes.drag, true),
47
+ keyboard: booleanValue(attributes.keyboard, true),
48
+ autoplay: booleanValue(attributes.autoplay, false),
49
+ autoplayInterval: boundedNumber(attributes.autoplayInterval, 5000, 1000, 60000),
50
+ pauseOnHover: booleanValue(attributes.pauseOnHover, true),
51
+ pauseOnFocus: booleanValue(attributes.pauseOnFocus, true),
52
+ loop: booleanValue(attributes.loop, false),
53
+ speed: boundedNumber(attributes.speed, 400, 0, 5000)
54
+ };
55
+ }
56
+ export function carouselOptions(settings) {
57
+ return {
58
+ label: settings.ariaLabel,
59
+ type: settings.loop ? "loop" : "slide",
60
+ perPage: settings.itemsPerView.base,
61
+ perMove: settings.itemsPerMove,
62
+ gap: `${settings.gap}px`,
63
+ arrows: settings.showArrows,
64
+ pagination: settings.showPagination,
65
+ drag: settings.drag,
66
+ keyboard: settings.keyboard ? "focused" : false,
67
+ autoplay: settings.autoplay,
68
+ interval: settings.autoplayInterval,
69
+ pauseOnHover: settings.pauseOnHover,
70
+ pauseOnFocus: settings.pauseOnFocus,
71
+ speed: settings.speed,
72
+ live: true,
73
+ mediaQuery: "min",
74
+ breakpoints: {
75
+ 640: { perPage: settings.itemsPerView.sm },
76
+ 768: { perPage: settings.itemsPerView.md },
77
+ 1024: { perPage: settings.itemsPerView.lg },
78
+ 1280: { perPage: settings.itemsPerView.xl },
79
+ 1536: { perPage: settings.itemsPerView["2xl"] }
80
+ },
81
+ reducedMotion: {
82
+ speed: 0,
83
+ rewindSpeed: 0,
84
+ autoplay: "pause"
85
+ },
86
+ i18n: {
87
+ prev: "Slide precedente",
88
+ next: "Slide successiva",
89
+ first: "Vai alla prima slide",
90
+ last: "Vai all'ultima slide",
91
+ slideX: "Vai alla slide %s",
92
+ pageX: "Vai alla pagina %s",
93
+ play: "Avvia autoplay",
94
+ pause: "Pausa autoplay",
95
+ carousel: "carosello",
96
+ slide: "slide",
97
+ select: "Seleziona una slide da mostrare",
98
+ slideLabel: "%s di %s"
99
+ }
100
+ };
101
+ }
102
+ //# sourceMappingURL=carousel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carousel.js","sourceRoot":"","sources":["../src/carousel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Options } from \"@splidejs/splide\";\n\nconst BREAKPOINTS = [\"base\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n\nexport interface FlowpressCarouselSettings {\n ariaLabel: string;\n itemsPerView: Record<(typeof BREAKPOINTS)[number], number>;\n itemsPerMove: number;\n gap: number;\n showArrows: boolean;\n showPagination: boolean;\n drag: boolean;\n keyboard: boolean;\n autoplay: boolean;\n autoplayInterval: number;\n pauseOnHover: boolean;\n pauseOnFocus: boolean;\n loop: boolean;\n speed: number;\n}\n\nfunction boundedNumber(\n value: unknown,\n fallback: number,\n min: number,\n max: number\n): number {\n if (value === \"\" || value === null || value === undefined || value === \"inherit\") {\n return fallback;\n }\n const numeric = Number(value);\n return Number.isFinite(numeric)\n ? Math.min(max, Math.max(min, Math.round(numeric)))\n : fallback;\n}\n\nfunction booleanValue(value: unknown, fallback: boolean): boolean {\n return typeof value === \"boolean\" ? value : fallback;\n}\n\nexport function carouselSettings(\n attributes: Record<string, unknown>\n): FlowpressCarouselSettings {\n const defaults = { base: 1, sm: 1, md: 2, lg: 3, xl: 3, \"2xl\": 3 };\n const values = {} as FlowpressCarouselSettings[\"itemsPerView\"];\n let inherited = defaults.base;\n for (const breakpoint of BREAKPOINTS) {\n const suffix = breakpoint === \"base\"\n ? \"Base\"\n : breakpoint === \"2xl\"\n ? \"2xl\"\n : breakpoint.charAt(0).toUpperCase() + breakpoint.slice(1);\n const raw = attributes[`itemsPerView${suffix}`];\n const fallback = breakpoint !== \"base\" && (raw === \"\" || raw === \"inherit\")\n ? inherited\n : breakpoint === \"base\"\n ? defaults.base\n : breakpoint === \"md\" || breakpoint === \"lg\"\n ? defaults[breakpoint]\n : inherited;\n const value = boundedNumber(\n raw,\n fallback,\n 1,\n 12\n );\n values[breakpoint] = value;\n inherited = value;\n }\n\n const ariaLabel = typeof attributes.ariaLabel === \"string\"\n ? attributes.ariaLabel.trim()\n : \"\";\n\n return {\n ariaLabel: ariaLabel || \"Carosello\",\n itemsPerView: values,\n itemsPerMove: boundedNumber(attributes.itemsPerMove, 1, 1, 12),\n gap: boundedNumber(attributes.gap, 24, 0, 128),\n showArrows: booleanValue(attributes.showArrows, true),\n showPagination: booleanValue(attributes.showPagination, true),\n drag: booleanValue(attributes.drag, true),\n keyboard: booleanValue(attributes.keyboard, true),\n autoplay: booleanValue(attributes.autoplay, false),\n autoplayInterval: boundedNumber(attributes.autoplayInterval, 5000, 1000, 60000),\n pauseOnHover: booleanValue(attributes.pauseOnHover, true),\n pauseOnFocus: booleanValue(attributes.pauseOnFocus, true),\n loop: booleanValue(attributes.loop, false),\n speed: boundedNumber(attributes.speed, 400, 0, 5000)\n };\n}\n\nexport function carouselOptions(settings: FlowpressCarouselSettings): Options {\n return {\n label: settings.ariaLabel,\n type: settings.loop ? \"loop\" : \"slide\",\n perPage: settings.itemsPerView.base,\n perMove: settings.itemsPerMove,\n gap: `${settings.gap}px`,\n arrows: settings.showArrows,\n pagination: settings.showPagination,\n drag: settings.drag,\n keyboard: settings.keyboard ? \"focused\" : false,\n autoplay: settings.autoplay,\n interval: settings.autoplayInterval,\n pauseOnHover: settings.pauseOnHover,\n pauseOnFocus: settings.pauseOnFocus,\n speed: settings.speed,\n live: true,\n mediaQuery: \"min\",\n breakpoints: {\n 640: { perPage: settings.itemsPerView.sm },\n 768: { perPage: settings.itemsPerView.md },\n 1024: { perPage: settings.itemsPerView.lg },\n 1280: { perPage: settings.itemsPerView.xl },\n 1536: { perPage: settings.itemsPerView[\"2xl\"] }\n },\n reducedMotion: {\n speed: 0,\n rewindSpeed: 0,\n autoplay: \"pause\"\n },\n i18n: {\n prev: \"Slide precedente\",\n next: \"Slide successiva\",\n first: \"Vai alla prima slide\",\n last: \"Vai all'ultima slide\",\n slideX: \"Vai alla slide %s\",\n pageX: \"Vai alla pagina %s\",\n play: \"Avvia autoplay\",\n pause: \"Pausa autoplay\",\n carousel: \"carosello\",\n slide: \"slide\",\n select: \"Seleziona una slide da mostrare\",\n slideLabel: \"%s di %s\"\n }\n };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./client.js";
2
+ export * from "./carousel.js";
2
3
  export * from "./block-renderers.js";
3
4
  export * from "./env.js";
4
5
  export * from "./number-spinner.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./client.js";
2
+ export * from "./carousel.js";
2
3
  export * from "./block-renderers.js";
3
4
  export * from "./env.js";
4
5
  export * from "./number-spinner.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./client.js\";\nexport * from \"./block-renderers.js\";\nexport * from \"./env.js\";\nexport * from \"./number-spinner.js\";\nexport * from \"./styleflow.js\";\nexport * from \"./style-classes.js\";\nexport * from \"./template-hierarchy.js\";\nexport * from \"./types.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./client.js\";\nexport * from \"./carousel.js\";\nexport * from \"./block-renderers.js\";\nexport * from \"./env.js\";\nexport * from \"./number-spinner.js\";\nexport * from \"./styleflow.js\";\nexport * from \"./style-classes.js\";\nexport * from \"./template-hierarchy.js\";\nexport * from \"./types.js\";\n"]}
@@ -1 +1 @@
1
- {"root":["../src/astro.d.ts","../src/block-renderers.ts","../src/client.ts","../src/env.ts","../src/index.ts","../src/number-spinner.ts","../src/style-classes.ts","../src/styleflow.ts","../src/template-hierarchy.ts","../src/templates.ts","../src/types.ts","../src/astro/blocks/helpers.ts"],"version":"5.9.3"}
1
+ {"root":["../src/astro.d.ts","../src/block-renderers.ts","../src/carousel.ts","../src/client.ts","../src/env.ts","../src/index.ts","../src/number-spinner.ts","../src/style-classes.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": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "Project-agnostic WordPress routing, templates, SEO and GraphQL runtime for FlowPress Astro apps.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -44,6 +44,7 @@
44
44
  "prepublishOnly": "npm run typecheck && npm test && npm run build"
45
45
  },
46
46
  "dependencies": {
47
+ "@splidejs/splide": "4.1.4",
47
48
  "@styleflow.app/astro": "1.0.0-beta.1"
48
49
  },
49
50
  "peerDependencies": {