@styleflow.app/flowpress-astro-core 0.2.0-beta.8 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -3
  4. package/dist/astro/BaseLayout.astro +7 -2
  5. package/dist/astro/DefaultTemplate.astro +5 -7
  6. package/dist/astro/GutenbergRenderer.astro +132 -28
  7. package/dist/astro/blocks/GridBlock.astro +1 -1
  8. package/dist/astro/blocks/HeroBlock.astro +8 -2
  9. package/dist/astro/blocks/ImageBlock.astro +90 -0
  10. package/dist/astro/blocks/NumberSpinnerBlock.astro +130 -0
  11. package/dist/astro/blocks/TextBlock.astro +21 -7
  12. package/dist/astro/blocks/helpers.d.ts +16 -0
  13. package/dist/astro/blocks/helpers.d.ts.map +1 -1
  14. package/dist/astro/blocks/helpers.js +104 -4
  15. package/dist/astro/blocks/helpers.js.map +1 -1
  16. package/dist/astro/blocks/helpers.ts +137 -4
  17. package/dist/block-renderers.d.ts +22 -0
  18. package/dist/block-renderers.d.ts.map +1 -0
  19. package/dist/block-renderers.js +2 -0
  20. package/dist/block-renderers.js.map +1 -0
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +26 -1
  23. package/dist/client.js.map +1 -1
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/number-spinner.d.ts +6 -0
  29. package/dist/number-spinner.d.ts.map +1 -0
  30. package/dist/number-spinner.js +136 -0
  31. package/dist/number-spinner.js.map +1 -0
  32. package/dist/style-classes.d.ts +9 -0
  33. package/dist/style-classes.d.ts.map +1 -0
  34. package/dist/style-classes.js +49 -0
  35. package/dist/style-classes.js.map +1 -0
  36. package/dist/styleflow.d.ts +37 -8
  37. package/dist/styleflow.d.ts.map +1 -1
  38. package/dist/styleflow.js +85 -42
  39. package/dist/styleflow.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/dist/types.d.ts +8 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js.map +1 -1
  44. package/package.json +6 -5
@@ -7,9 +7,32 @@ import type { FlowpressBlock } from "../../types.js";
7
7
  export const DEFAULT_SEMANTIC_PROPS = {
8
8
  surface: "default",
9
9
  layoutRole: "stack",
10
- density: "md"
10
+ density: "regular"
11
11
  };
12
12
 
13
+ export const DEFAULT_HERO_OBJECT_POSITION = "50% 50%";
14
+
15
+ export function heroObjectPosition(value: unknown): string {
16
+ if (typeof value !== "string") {
17
+ return DEFAULT_HERO_OBJECT_POSITION;
18
+ }
19
+
20
+ const match = value.trim().match(
21
+ /^(\d+(?:\.\d+)?|\.\d+)%\s+(\d+(?:\.\d+)?|\.\d+)%$/
22
+ );
23
+ if (
24
+ !match
25
+ || [match[1], match[2]].some((coordinate) => {
26
+ const numeric = Number(coordinate);
27
+ return !Number.isFinite(numeric) || numeric < 0 || numeric > 100;
28
+ })
29
+ ) {
30
+ return DEFAULT_HERO_OBJECT_POSITION;
31
+ }
32
+
33
+ return `${match[1]}% ${match[2]}%`;
34
+ }
35
+
13
36
  export function blockHtmlAttributes(
14
37
  attributes: Record<string, unknown>,
15
38
  baseClass = ""
@@ -32,6 +55,7 @@ export function blockHtmlAttributes(
32
55
  }
33
56
 
34
57
  const SEMANTIC_KEYS = [
58
+ "theme",
35
59
  "tone",
36
60
  "intensity",
37
61
  "surface",
@@ -50,6 +74,30 @@ const SEMANTIC_KEYS = [
50
74
  "noForeground"
51
75
  ] as const;
52
76
 
77
+ export const TEXT_HTML_TAGS = [
78
+ "span",
79
+ "p",
80
+ "h1",
81
+ "h2",
82
+ "h3",
83
+ "h4",
84
+ "h5",
85
+ "h6",
86
+ "strong",
87
+ "em",
88
+ "small",
89
+ "mark"
90
+ ] as const;
91
+
92
+ export type TextHtmlTag = (typeof TEXT_HTML_TAGS)[number];
93
+
94
+ export function textHtmlTag(value: unknown): TextHtmlTag {
95
+ const candidate = String(value ?? "span");
96
+ return TEXT_HTML_TAGS.includes(candidate as TextHtmlTag)
97
+ ? candidate as TextHtmlTag
98
+ : "span";
99
+ }
100
+
53
101
  export function semanticProps(
54
102
  attributes: Record<string, unknown>,
55
103
  contract?: StyleflowContract,
@@ -60,9 +108,62 @@ export function semanticProps(
60
108
  .filter((key) => attributes[key] !== undefined)
61
109
  .map((key) => [key, attributes[key]])
62
110
  );
63
- return contract
64
- ? resolveDynamicStyleflowProps(input, defaults, contract)
65
- : { ...defaults, ...input };
111
+ if (contract) {
112
+ return resolveDynamicStyleflowProps(input, defaults, contract, {
113
+ strict: process.env.NODE_ENV === "production" || process.env.STYLEFLOW_STRICT === "1",
114
+ onDiagnostic: (diagnostic) => console.warn(`[${diagnostic.code}] ${diagnostic.message}`)
115
+ });
116
+ }
117
+
118
+ const resolved = { ...defaults, ...input };
119
+ for (const key of ["tone", "intensity", "foreground"]) {
120
+ if (input[key] === "inherit") {
121
+ delete resolved[key];
122
+ }
123
+ }
124
+ return resolved;
125
+ }
126
+
127
+ const TEXT_ALIGNMENTS = new Set([
128
+ "left",
129
+ "center",
130
+ "right",
131
+ "justify",
132
+ "start",
133
+ "end"
134
+ ]);
135
+
136
+ /** Resolve only Gutenberg's supported text-alignment values to inline CSS. */
137
+ export function textAlignmentStyle(
138
+ attributes: Record<string, unknown>
139
+ ): string | undefined {
140
+ const style = attributes.style && typeof attributes.style === "object"
141
+ && !Array.isArray(attributes.style)
142
+ ? attributes.style as Record<string, unknown>
143
+ : {};
144
+ const typography = style.typography && typeof style.typography === "object"
145
+ && !Array.isArray(style.typography)
146
+ ? style.typography as Record<string, unknown>
147
+ : {};
148
+ const candidate = typography.textAlign ?? attributes.textAlign;
149
+ const alignment = String(candidate ?? "");
150
+ return TEXT_ALIGNMENTS.has(alignment) ? `text-align: ${alignment};` : undefined;
151
+ }
152
+
153
+ export function prefixedTextSemanticProps(
154
+ attributes: Record<string, unknown>,
155
+ prefix: string,
156
+ contract?: StyleflowContract,
157
+ defaults: Record<string, unknown> = {}
158
+ ): Record<string, unknown> {
159
+ const attributeName = (name: string) =>
160
+ `${prefix}${name.charAt(0).toUpperCase()}${name.slice(1)}`;
161
+ return semanticProps({
162
+ ty: attributes[attributeName("ty")],
163
+ v: attributes[attributeName("v")],
164
+ w: attributes[attributeName("w")],
165
+ foreground: attributes[attributeName("foreground")]
166
+ }, contract, defaults);
66
167
  }
67
168
 
68
169
  export function objectValue(
@@ -79,6 +180,38 @@ export function textValue(block: FlowpressBlock, key = "text"): string {
79
180
  return String(block.attributes[key] ?? "");
80
181
  }
81
182
 
183
+ /**
184
+ * Keep intentional blank lines visible across current and legacy RichText
185
+ * serializations. Gutenberg can represent them either as repeated `<br>` tags,
186
+ * empty paragraph elements, or literal newlines in an attribute value.
187
+ */
188
+ export function preserveRichTextSpacing(html: string): string {
189
+ const visibleEmptyBlocks = html
190
+ .replace(/\r\n?/g, "\n")
191
+ .replace(
192
+ /<(p|div)(\s[^>]*)?>(?:\s|&nbsp;|&#160;)*<\/\1>/gi,
193
+ (_match, tag: string, attributes = "") =>
194
+ `<${tag}${attributes}>&nbsp;</${tag}>`
195
+ );
196
+
197
+ return visibleEmptyBlocks
198
+ .split(/(<[^>]+>)/g)
199
+ .map((part) => {
200
+ if (part.startsWith("<") || part.trim() === "") {
201
+ return part;
202
+ }
203
+ return part.replace(/\n(?:[ \t]*\n)+/g, (newlines) =>
204
+ "<br>".repeat((newlines.match(/\n/g) || []).length)
205
+ );
206
+ })
207
+ .join("");
208
+ }
209
+
210
+ /** Keep an empty text element at its typographic line height without visible text. */
211
+ export function preserveEmptyTextBlock(html: string): string {
212
+ return html.trim() === "" ? "&nbsp;" : html;
213
+ }
214
+
82
215
  export function linkValue(block: FlowpressBlock, key = "link"): string {
83
216
  const value = block.attributes[key];
84
217
  const href = typeof value === "string"
@@ -0,0 +1,22 @@
1
+ import type { AstroComponentFactory } from "astro/runtime/server/index.js";
2
+ import type { StyleflowContract } from "./styleflow.js";
3
+ import type { FlowpressBlock } from "./types.js";
4
+ export interface FlowpressBlockRendererProps {
5
+ block: FlowpressBlock;
6
+ contract?: StyleflowContract;
7
+ }
8
+ export interface FlowpressIconRendererProps {
9
+ name: string;
10
+ display?: "inline" | "block";
11
+ foreground?: string;
12
+ tone?: string;
13
+ intensity?: string | number;
14
+ size?: number;
15
+ label?: string;
16
+ id?: string;
17
+ class?: string;
18
+ }
19
+ export type FlowpressBlockRenderer = AstroComponentFactory;
20
+ export type FlowpressIconRenderer = AstroComponentFactory;
21
+ export type FlowpressBlockRenderers = Readonly<Record<string, FlowpressBlockRenderer>>;
22
+ //# sourceMappingURL=block-renderers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-renderers.d.ts","sourceRoot":"","sources":["../src/block-renderers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,cAAc,CAAC;IACtB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,sBAAsB,GAAG,qBAAqB,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAE1D,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAC5C,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CACvC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=block-renderers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-renderers.js","sourceRoot":"","sources":["../src/block-renderers.ts"],"names":[],"mappings":"","sourcesContent":["import type { AstroComponentFactory } from \"astro/runtime/server/index.js\";\nimport type { StyleflowContract } from \"./styleflow.js\";\nimport type { FlowpressBlock } from \"./types.js\";\n\nexport interface FlowpressBlockRendererProps {\n block: FlowpressBlock;\n contract?: StyleflowContract;\n}\n\nexport interface FlowpressIconRendererProps {\n name: string;\n display?: \"inline\" | \"block\";\n foreground?: string;\n tone?: string;\n intensity?: string | number;\n size?: number;\n label?: string;\n id?: string;\n class?: string;\n}\n\nexport type FlowpressBlockRenderer = AstroComponentFactory;\n\nexport type FlowpressIconRenderer = AstroComponentFactory;\n\nexport type FlowpressBlockRenderers = Readonly<\n Record<string, FlowpressBlockRenderer>\n>;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE;QACX,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAOD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAG5C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,EAAE;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM;gBAJ1B,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,MAAM,CAAC,EAAE,qBAAqB,EAAE,YAAA,EAChC,aAAa,CAAC,EAAE,MAAM,YAAA,EACtB,QAAQ,CAAC,EAAE,MAAM,YAAA,EAC1B,KAAK,CAAC,EAAE,OAAO;CAKlB;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBAEZ,OAAO,EAAE,sBAAsB;IAarC,OAAO,CAAC,CAAC,EACb,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACtC,OAAO,CAAC,CAAC,CAAC;IAmFP,eAAe,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAoBjD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAgCtE,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IA6BrD,UAAU,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;CA2CpC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE;QACX,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;CACH;AAOD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAG5C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,EAAE;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM;gBAJ1B,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,MAAM,CAAC,EAAE,qBAAqB,EAAE,YAAA,EAChC,aAAa,CAAC,EAAE,MAAM,YAAA,EACtB,QAAQ,CAAC,EAAE,MAAM,YAAA,EAC1B,KAAK,CAAC,EAAE,OAAO;CAKlB;AAED,qBAAa,eAAe;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBAEZ,OAAO,EAAE,sBAAsB;IAarC,OAAO,CAAC,CAAC,EACb,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACtC,OAAO,CAAC,CAAC,CAAC;IAmFP,eAAe,IAAI,OAAO,CAAC,qBAAqB,CAAC;IA4CjD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAgCtE,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IA6BrD,UAAU,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;CA2CpC"}
package/dist/client.js CHANGED
@@ -100,9 +100,34 @@ export class FlowpressClient {
100
100
  contactForm7
101
101
  defaultLocale
102
102
  locales { code slug homeUrl }
103
+ features { id version enabled capabilities configJson }
103
104
  }
104
105
  }`);
105
- return data.styleflowCapabilities;
106
+ return {
107
+ ...data.styleflowCapabilities,
108
+ features: (data.styleflowCapabilities.features ?? []).map((feature) => {
109
+ const wire = feature;
110
+ let config = {};
111
+ if (wire.configJson) {
112
+ try {
113
+ const parsed = JSON.parse(wire.configJson);
114
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
115
+ config = parsed;
116
+ }
117
+ }
118
+ catch {
119
+ config = {};
120
+ }
121
+ }
122
+ return {
123
+ id: feature.id,
124
+ version: feature.version ?? null,
125
+ enabled: feature.enabled,
126
+ capabilities: feature.capabilities ?? [],
127
+ config
128
+ };
129
+ })
130
+ };
106
131
  }
107
132
  async getRoute(uri, locale) {
108
133
  const data = await this.request(`query FlowpressRoute($uri: String!, $locale: String) {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n FlowpressCapabilities,\n FlowpressContent,\n FlowpressRoute\n} from \"./types.js\";\n\nexport interface FlowpressGraphqlIssue {\n message?: string;\n extensions?: {\n debugMessage?: string;\n category?: string;\n [key: string]: unknown;\n };\n}\n\ninterface GraphqlResponse<T> {\n data?: T;\n errors?: FlowpressGraphqlIssue[];\n}\n\nexport interface FlowpressClientOptions {\n endpoint: string;\n timeoutMs?: number;\n headers?: Record<string, string>;\n debug?: boolean;\n}\n\nexport class FlowpressGraphqlError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly errors?: FlowpressGraphqlIssue[],\n readonly operationName?: string,\n readonly endpoint?: string,\n cause?: unknown\n ) {\n super(message, cause === undefined ? undefined : { cause });\n this.name = \"FlowpressGraphqlError\";\n }\n}\n\nexport class FlowpressClient {\n readonly endpoint: string;\n readonly timeoutMs: number;\n readonly headers: Record<string, string>;\n readonly debug: boolean;\n\n constructor(options: FlowpressClientOptions) {\n const endpoint = options.endpoint.trim();\n if (!endpoint) {\n throw new Error(\"FlowPress GraphQL endpoint is required.\");\n }\n\n new URL(endpoint);\n this.endpoint = endpoint;\n this.timeoutMs = options.timeoutMs ?? 15_000;\n this.headers = { ...options.headers };\n this.debug = options.debug ?? false;\n }\n\n async request<T>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n const operationName = getOperationName(query);\n const safeEndpoint = sanitizeEndpoint(this.endpoint);\n\n try {\n let response: Response;\n try {\n response = await fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...this.headers\n },\n body: JSON.stringify({ query, variables }),\n signal: controller.signal\n });\n } catch (error) {\n const timedOut = controller.signal.aborted;\n const detail = error instanceof Error && error.message\n ? ` ${error.message}`\n : \"\";\n throw new FlowpressGraphqlError(\n timedOut\n ? `FlowPress GraphQL ${operationName} timed out after ${this.timeoutMs}ms at ${safeEndpoint}.`\n : `FlowPress GraphQL ${operationName} could not reach ${safeEndpoint}.${detail}`,\n undefined,\n undefined,\n operationName,\n safeEndpoint,\n error\n );\n }\n\n const body = await response.text();\n let payload: GraphqlResponse<T>;\n try {\n payload = JSON.parse(body) as GraphqlResponse<T>;\n } catch (error) {\n const contentType = response.headers.get(\"content-type\") || \"unknown content type\";\n throw new FlowpressGraphqlError(\n `FlowPress GraphQL ${operationName} returned ${response.status} `\n + `with ${contentType}, not JSON, from ${safeEndpoint}.`,\n response.status,\n undefined,\n operationName,\n safeEndpoint,\n error\n );\n }\n\n if (!response.ok || payload.errors?.length || payload.data === undefined) {\n const errors: FlowpressGraphqlIssue[] | undefined = payload.errors?.map(\n (error) => {\n if (this.debug) {\n return error;\n }\n return error.message ? { message: error.message } : {};\n }\n );\n const messages = errors\n ?.flatMap((error) => [\n error.message,\n this.debug ? error.extensions?.debugMessage : undefined\n ])\n .filter((message): message is string => Boolean(message));\n throw new FlowpressGraphqlError(\n messages?.join(\"; \")\n || `FlowPress GraphQL ${operationName} failed with ${response.status} at ${safeEndpoint}.`,\n response.status,\n errors,\n operationName,\n safeEndpoint\n );\n }\n\n return payload.data;\n } finally {\n clearTimeout(timer);\n }\n }\n\n async getCapabilities(): Promise<FlowpressCapabilities> {\n const data = await this.request<{ styleflowCapabilities: FlowpressCapabilities }>(\n `query FlowpressCapabilities {\n styleflowCapabilities {\n schemaVersion\n wpGraphql\n acfPro\n wpGraphqlAcf\n rankMath\n wpml\n woocommerce\n contactForm7\n defaultLocale\n locales { code slug homeUrl }\n }\n }`\n );\n return data.styleflowCapabilities;\n }\n\n async getRoute(uri: string, locale?: string): Promise<FlowpressRoute | null> {\n const data = await this.request<{\n sfRouteByUri: (Omit<FlowpressRoute, \"seo\"> & {\n seo?: GraphqlSeo | null;\n }) | null;\n }>(\n `query FlowpressRoute($uri: String!, $locale: String) {\n sfRouteByUri(uri: $uri, locale: $locale) {\n uri status queryType locale isFrontPage isPostsPage frontPageMode\n postType taxonomy slug term databaseId termId authorNicename authorId\n pageTemplate mimeType postFormat templateCandidates\n content { loader databaseId termId }\n seo {\n title description canonicalUrl robots headHtml\n hreflang { locale url }\n jsonLd { raw }\n openGraph { title description image { url altText } }\n twitterMeta { title description image { url altText } }\n }\n }\n }`,\n { uri, locale: locale ?? null }\n );\n if (!data.sfRouteByUri) {\n return null;\n }\n return {\n ...data.sfRouteByUri,\n seo: normalizeGraphqlSeo(data.sfRouteByUri.seo)\n };\n }\n\n async getRoutes(locale?: string): Promise<FlowpressRoute[]> {\n const data = await this.request<{\n sfRoutes: Array<Omit<FlowpressRoute, \"seo\"> & {\n seo?: GraphqlSeo | null;\n }>;\n }>(\n `query FlowpressRoutes($locale: String) {\n sfRoutes(locale: $locale) {\n uri status queryType locale isFrontPage isPostsPage frontPageMode\n postType taxonomy slug term databaseId termId authorNicename authorId\n pageTemplate mimeType postFormat templateCandidates\n content { loader databaseId termId }\n seo {\n title description canonicalUrl robots headHtml\n hreflang { locale url }\n jsonLd { raw }\n openGraph { title description image { url altText } }\n twitterMeta { title description image { url altText } }\n }\n }\n }`,\n { locale: locale ?? null }\n );\n return data.sfRoutes.map((route) => ({\n ...route,\n seo: normalizeGraphqlSeo(route.seo)\n }));\n }\n\n async getContent(\n loader: string,\n variables: Record<string, unknown>\n ): Promise<FlowpressContent | null> {\n const data = await this.request<{\n sfContentByLoader: (Omit<FlowpressContent, \"contentBlocks\"> & {\n contentBlocksJson?: string | null;\n projectFieldsJson?: string | null;\n }) | null;\n }>(\n `query FlowpressContent(\n $loader: String!,\n $databaseId: Int,\n $termId: Int,\n $uri: String,\n $locale: String\n ) {\n sfContentByLoader(\n loader: $loader,\n databaseId: $databaseId,\n termId: $termId,\n uri: $uri,\n locale: $locale\n ) {\n title excerpt content contentBlocksJson projectFieldsJson\n }\n }`,\n {\n loader,\n databaseId: variables.databaseId ?? null,\n termId: variables.termId ?? null,\n uri: variables.uri ?? null,\n locale: variables.locale ?? null\n }\n );\n const content = data.sfContentByLoader;\n if (!content) {\n return null;\n }\n const { contentBlocksJson, projectFieldsJson, ...rest } = content;\n return {\n ...rest,\n contentBlocks: parseJson(contentBlocksJson, []),\n projectFields: parseJson(projectFieldsJson, {})\n };\n }\n}\n\nfunction getOperationName(query: string): string {\n return query.match(\n /\\b(?:query|mutation|subscription)\\s+([_A-Za-z][_0-9A-Za-z]*)/\n )?.[1] || \"AnonymousOperation\";\n}\n\nfunction sanitizeEndpoint(endpoint: string): string {\n const url = new URL(endpoint);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n}\n\ninterface GraphqlSeo {\n title?: string | null;\n description?: string | null;\n canonicalUrl?: string | null;\n robots?: string[] | null;\n headHtml?: string | null;\n jsonLd?: { raw?: string | null } | null;\n openGraph?: GraphqlSocial | null;\n twitterMeta?: GraphqlSocial | null;\n hreflang?: Array<{ locale: string; url: string }> | null;\n}\n\ninterface GraphqlSocial {\n title?: string | null;\n description?: string | null;\n image?: { url: string; altText?: string | null } | null;\n}\n\nfunction normalizeGraphqlSeo(seo?: GraphqlSeo | null) {\n if (!seo) return null;\n const social = (value?: GraphqlSocial | null) => value\n ? {\n title: value.title ?? null,\n description: value.description ?? null,\n image: value.image\n ? { url: value.image.url, alt: value.image.altText ?? null }\n : null\n }\n : null;\n\n return {\n title: seo.title ?? null,\n description: seo.description ?? null,\n canonicalUrl: seo.canonicalUrl ?? null,\n robots: seo.robots ?? null,\n headHtml: seo.headHtml ?? null,\n jsonLd: parseJson(seo.jsonLd?.raw, null),\n hreflang: seo.hreflang ?? null,\n openGraph: social(seo.openGraph),\n twitter: social(seo.twitterMeta)\n };\n}\n\nfunction parseJson<T>(value: string | null | undefined, fallback: T): T {\n if (!value) return fallback;\n try {\n return JSON.parse(value) as T;\n } catch {\n return fallback;\n }\n}\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n FlowpressCapabilities,\n FlowpressContent,\n FlowpressRoute\n} from \"./types.js\";\n\nexport interface FlowpressGraphqlIssue {\n message?: string;\n extensions?: {\n debugMessage?: string;\n category?: string;\n [key: string]: unknown;\n };\n}\n\ninterface GraphqlResponse<T> {\n data?: T;\n errors?: FlowpressGraphqlIssue[];\n}\n\nexport interface FlowpressClientOptions {\n endpoint: string;\n timeoutMs?: number;\n headers?: Record<string, string>;\n debug?: boolean;\n}\n\nexport class FlowpressGraphqlError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n readonly errors?: FlowpressGraphqlIssue[],\n readonly operationName?: string,\n readonly endpoint?: string,\n cause?: unknown\n ) {\n super(message, cause === undefined ? undefined : { cause });\n this.name = \"FlowpressGraphqlError\";\n }\n}\n\nexport class FlowpressClient {\n readonly endpoint: string;\n readonly timeoutMs: number;\n readonly headers: Record<string, string>;\n readonly debug: boolean;\n\n constructor(options: FlowpressClientOptions) {\n const endpoint = options.endpoint.trim();\n if (!endpoint) {\n throw new Error(\"FlowPress GraphQL endpoint is required.\");\n }\n\n new URL(endpoint);\n this.endpoint = endpoint;\n this.timeoutMs = options.timeoutMs ?? 15_000;\n this.headers = { ...options.headers };\n this.debug = options.debug ?? false;\n }\n\n async request<T>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n const operationName = getOperationName(query);\n const safeEndpoint = sanitizeEndpoint(this.endpoint);\n\n try {\n let response: Response;\n try {\n response = await fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...this.headers\n },\n body: JSON.stringify({ query, variables }),\n signal: controller.signal\n });\n } catch (error) {\n const timedOut = controller.signal.aborted;\n const detail = error instanceof Error && error.message\n ? ` ${error.message}`\n : \"\";\n throw new FlowpressGraphqlError(\n timedOut\n ? `FlowPress GraphQL ${operationName} timed out after ${this.timeoutMs}ms at ${safeEndpoint}.`\n : `FlowPress GraphQL ${operationName} could not reach ${safeEndpoint}.${detail}`,\n undefined,\n undefined,\n operationName,\n safeEndpoint,\n error\n );\n }\n\n const body = await response.text();\n let payload: GraphqlResponse<T>;\n try {\n payload = JSON.parse(body) as GraphqlResponse<T>;\n } catch (error) {\n const contentType = response.headers.get(\"content-type\") || \"unknown content type\";\n throw new FlowpressGraphqlError(\n `FlowPress GraphQL ${operationName} returned ${response.status} `\n + `with ${contentType}, not JSON, from ${safeEndpoint}.`,\n response.status,\n undefined,\n operationName,\n safeEndpoint,\n error\n );\n }\n\n if (!response.ok || payload.errors?.length || payload.data === undefined) {\n const errors: FlowpressGraphqlIssue[] | undefined = payload.errors?.map(\n (error) => {\n if (this.debug) {\n return error;\n }\n return error.message ? { message: error.message } : {};\n }\n );\n const messages = errors\n ?.flatMap((error) => [\n error.message,\n this.debug ? error.extensions?.debugMessage : undefined\n ])\n .filter((message): message is string => Boolean(message));\n throw new FlowpressGraphqlError(\n messages?.join(\"; \")\n || `FlowPress GraphQL ${operationName} failed with ${response.status} at ${safeEndpoint}.`,\n response.status,\n errors,\n operationName,\n safeEndpoint\n );\n }\n\n return payload.data;\n } finally {\n clearTimeout(timer);\n }\n }\n\n async getCapabilities(): Promise<FlowpressCapabilities> {\n const data = await this.request<{ styleflowCapabilities: FlowpressCapabilities }>(\n `query FlowpressCapabilities {\n styleflowCapabilities {\n schemaVersion\n wpGraphql\n acfPro\n wpGraphqlAcf\n rankMath\n wpml\n woocommerce\n contactForm7\n defaultLocale\n locales { code slug homeUrl }\n features { id version enabled capabilities configJson }\n }\n }`\n );\n return {\n ...data.styleflowCapabilities,\n features: (data.styleflowCapabilities.features ?? []).map((feature) => {\n const wire = feature as typeof feature & { configJson?: string | null };\n let config: Record<string, unknown> = {};\n if (wire.configJson) {\n try {\n const parsed = JSON.parse(wire.configJson);\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n config = parsed as Record<string, unknown>;\n }\n } catch {\n config = {};\n }\n }\n return {\n id: feature.id,\n version: feature.version ?? null,\n enabled: feature.enabled,\n capabilities: feature.capabilities ?? [],\n config\n };\n })\n };\n }\n\n async getRoute(uri: string, locale?: string): Promise<FlowpressRoute | null> {\n const data = await this.request<{\n sfRouteByUri: (Omit<FlowpressRoute, \"seo\"> & {\n seo?: GraphqlSeo | null;\n }) | null;\n }>(\n `query FlowpressRoute($uri: String!, $locale: String) {\n sfRouteByUri(uri: $uri, locale: $locale) {\n uri status queryType locale isFrontPage isPostsPage frontPageMode\n postType taxonomy slug term databaseId termId authorNicename authorId\n pageTemplate mimeType postFormat templateCandidates\n content { loader databaseId termId }\n seo {\n title description canonicalUrl robots headHtml\n hreflang { locale url }\n jsonLd { raw }\n openGraph { title description image { url altText } }\n twitterMeta { title description image { url altText } }\n }\n }\n }`,\n { uri, locale: locale ?? null }\n );\n if (!data.sfRouteByUri) {\n return null;\n }\n return {\n ...data.sfRouteByUri,\n seo: normalizeGraphqlSeo(data.sfRouteByUri.seo)\n };\n }\n\n async getRoutes(locale?: string): Promise<FlowpressRoute[]> {\n const data = await this.request<{\n sfRoutes: Array<Omit<FlowpressRoute, \"seo\"> & {\n seo?: GraphqlSeo | null;\n }>;\n }>(\n `query FlowpressRoutes($locale: String) {\n sfRoutes(locale: $locale) {\n uri status queryType locale isFrontPage isPostsPage frontPageMode\n postType taxonomy slug term databaseId termId authorNicename authorId\n pageTemplate mimeType postFormat templateCandidates\n content { loader databaseId termId }\n seo {\n title description canonicalUrl robots headHtml\n hreflang { locale url }\n jsonLd { raw }\n openGraph { title description image { url altText } }\n twitterMeta { title description image { url altText } }\n }\n }\n }`,\n { locale: locale ?? null }\n );\n return data.sfRoutes.map((route) => ({\n ...route,\n seo: normalizeGraphqlSeo(route.seo)\n }));\n }\n\n async getContent(\n loader: string,\n variables: Record<string, unknown>\n ): Promise<FlowpressContent | null> {\n const data = await this.request<{\n sfContentByLoader: (Omit<FlowpressContent, \"contentBlocks\"> & {\n contentBlocksJson?: string | null;\n projectFieldsJson?: string | null;\n }) | null;\n }>(\n `query FlowpressContent(\n $loader: String!,\n $databaseId: Int,\n $termId: Int,\n $uri: String,\n $locale: String\n ) {\n sfContentByLoader(\n loader: $loader,\n databaseId: $databaseId,\n termId: $termId,\n uri: $uri,\n locale: $locale\n ) {\n title excerpt content contentBlocksJson projectFieldsJson\n }\n }`,\n {\n loader,\n databaseId: variables.databaseId ?? null,\n termId: variables.termId ?? null,\n uri: variables.uri ?? null,\n locale: variables.locale ?? null\n }\n );\n const content = data.sfContentByLoader;\n if (!content) {\n return null;\n }\n const { contentBlocksJson, projectFieldsJson, ...rest } = content;\n return {\n ...rest,\n contentBlocks: parseJson(contentBlocksJson, []),\n projectFields: parseJson(projectFieldsJson, {})\n };\n }\n}\n\nfunction getOperationName(query: string): string {\n return query.match(\n /\\b(?:query|mutation|subscription)\\s+([_A-Za-z][_0-9A-Za-z]*)/\n )?.[1] || \"AnonymousOperation\";\n}\n\nfunction sanitizeEndpoint(endpoint: string): string {\n const url = new URL(endpoint);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n}\n\ninterface GraphqlSeo {\n title?: string | null;\n description?: string | null;\n canonicalUrl?: string | null;\n robots?: string[] | null;\n headHtml?: string | null;\n jsonLd?: { raw?: string | null } | null;\n openGraph?: GraphqlSocial | null;\n twitterMeta?: GraphqlSocial | null;\n hreflang?: Array<{ locale: string; url: string }> | null;\n}\n\ninterface GraphqlSocial {\n title?: string | null;\n description?: string | null;\n image?: { url: string; altText?: string | null } | null;\n}\n\nfunction normalizeGraphqlSeo(seo?: GraphqlSeo | null) {\n if (!seo) return null;\n const social = (value?: GraphqlSocial | null) => value\n ? {\n title: value.title ?? null,\n description: value.description ?? null,\n image: value.image\n ? { url: value.image.url, alt: value.image.altText ?? null }\n : null\n }\n : null;\n\n return {\n title: seo.title ?? null,\n description: seo.description ?? null,\n canonicalUrl: seo.canonicalUrl ?? null,\n robots: seo.robots ?? null,\n headHtml: seo.headHtml ?? null,\n jsonLd: parseJson(seo.jsonLd?.raw, null),\n hreflang: seo.hreflang ?? null,\n openGraph: social(seo.openGraph),\n twitter: social(seo.twitterMeta)\n };\n}\n\nfunction parseJson<T>(value: string | null | undefined, fallback: T): T {\n if (!value) return fallback;\n try {\n return JSON.parse(value) as T;\n } catch {\n return fallback;\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export * from "./client.js";
2
+ export * from "./block-renderers.js";
2
3
  export * from "./env.js";
4
+ export * from "./number-spinner.js";
3
5
  export * from "./styleflow.js";
6
+ export * from "./style-classes.js";
4
7
  export * from "./template-hierarchy.js";
5
8
  export * from "./types.js";
6
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,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,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,6 +1,9 @@
1
1
  export * from "./client.js";
2
+ export * from "./block-renderers.js";
2
3
  export * from "./env.js";
4
+ export * from "./number-spinner.js";
3
5
  export * from "./styleflow.js";
6
+ export * from "./style-classes.js";
4
7
  export * from "./template-hierarchy.js";
5
8
  export * from "./types.js";
6
9
  //# sourceMappingURL=index.js.map
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,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./client.js\";\nexport * from \"./env.js\";\nexport * from \"./styleflow.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,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"]}
@@ -0,0 +1,6 @@
1
+ export declare function spinnerFractionDigits(value: number): number;
2
+ export declare function clampSpinnerDuration(value: unknown): number;
3
+ export declare function spinnerFrameValue(target: number, progress: number): number;
4
+ export declare function formatSpinnerValue(value: number, locale: string, fractionDigits: number): string;
5
+ export declare function initializeNumberSpinners(root?: ParentNode): void;
6
+ //# sourceMappingURL=number-spinner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number-spinner.d.ts","sourceRoot":"","sources":["../src/number-spinner.ts"],"names":[],"mappings":"AAIA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS3D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAK3D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAI1E;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,MAAM,CAOR;AA2ED,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,UAAqB,GAAG,IAAI,CA0D1E"}
@@ -0,0 +1,136 @@
1
+ const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
2
+ const initializedSpinners = new WeakSet();
3
+ const runningFrames = new Map();
4
+ export function spinnerFractionDigits(value) {
5
+ if (!Number.isFinite(value)) {
6
+ return 0;
7
+ }
8
+ const [coefficient = "", exponentText] = String(value).toLowerCase().split("e");
9
+ const coefficientDecimals = coefficient.split(".")[1]?.length ?? 0;
10
+ const exponent = Number(exponentText ?? 0);
11
+ return Math.min(20, Math.max(0, coefficientDecimals - exponent));
12
+ }
13
+ export function clampSpinnerDuration(value) {
14
+ const duration = Number(value);
15
+ return Number.isFinite(duration)
16
+ ? Math.min(10_000, Math.max(0, duration))
17
+ : 1200;
18
+ }
19
+ export function spinnerFrameValue(target, progress) {
20
+ const normalized = Math.min(1, Math.max(0, progress));
21
+ const eased = 1 - Math.pow(1 - normalized, 3);
22
+ return target * eased;
23
+ }
24
+ export function formatSpinnerValue(value, locale, fractionDigits) {
25
+ const digits = Math.min(20, Math.max(0, Math.trunc(fractionDigits)));
26
+ return new Intl.NumberFormat(locale || undefined, {
27
+ minimumFractionDigits: digits,
28
+ maximumFractionDigits: digits,
29
+ useGrouping: true
30
+ }).format(value);
31
+ }
32
+ function spinnerDisplay(element) {
33
+ return element.querySelector("[data-flowpress-number-spinner-value]");
34
+ }
35
+ function spinnerTarget(element) {
36
+ const target = Number(element.dataset.spinnerTarget);
37
+ return Number.isFinite(target) ? target : 0;
38
+ }
39
+ function spinnerLocale(element) {
40
+ return element.dataset.spinnerLocale
41
+ || document.documentElement.lang
42
+ || navigator.language
43
+ || "en";
44
+ }
45
+ function renderSpinnerValue(element, value, final = false) {
46
+ const display = spinnerDisplay(element);
47
+ if (!display) {
48
+ return;
49
+ }
50
+ const target = spinnerTarget(element);
51
+ const digits = Number(element.dataset.spinnerFractionDigits) || 0;
52
+ display.textContent = formatSpinnerValue(final ? target : value, spinnerLocale(element), digits);
53
+ if (final) {
54
+ element.dataset.spinnerState = "complete";
55
+ }
56
+ }
57
+ function finishSpinner(element) {
58
+ const frame = runningFrames.get(element);
59
+ if (frame !== undefined) {
60
+ cancelAnimationFrame(frame);
61
+ runningFrames.delete(element);
62
+ }
63
+ renderSpinnerValue(element, spinnerTarget(element), true);
64
+ }
65
+ function animateSpinner(element) {
66
+ const duration = clampSpinnerDuration(element.dataset.spinnerDuration);
67
+ const target = spinnerTarget(element);
68
+ if (duration === 0 || target === 0) {
69
+ finishSpinner(element);
70
+ return;
71
+ }
72
+ element.dataset.spinnerState = "running";
73
+ renderSpinnerValue(element, 0);
74
+ const startedAt = performance.now();
75
+ const step = (now) => {
76
+ const progress = Math.min(1, (now - startedAt) / duration);
77
+ renderSpinnerValue(element, spinnerFrameValue(target, progress));
78
+ if (progress >= 1) {
79
+ finishSpinner(element);
80
+ return;
81
+ }
82
+ runningFrames.set(element, requestAnimationFrame(step));
83
+ };
84
+ runningFrames.set(element, requestAnimationFrame(step));
85
+ }
86
+ export function initializeNumberSpinners(root = document) {
87
+ if (typeof window === "undefined") {
88
+ return;
89
+ }
90
+ const elements = Array.from(root.querySelectorAll("[data-flowpress-number-spinner]")).filter((element) => {
91
+ if (initializedSpinners.has(element)) {
92
+ return false;
93
+ }
94
+ initializedSpinners.add(element);
95
+ return true;
96
+ });
97
+ if (!elements.length) {
98
+ return;
99
+ }
100
+ if (typeof window.matchMedia !== "function") {
101
+ elements.forEach(finishSpinner);
102
+ return;
103
+ }
104
+ const reducedMotion = window.matchMedia(REDUCED_MOTION_QUERY);
105
+ if (reducedMotion.matches) {
106
+ elements.forEach(finishSpinner);
107
+ return;
108
+ }
109
+ if (!("IntersectionObserver" in window)) {
110
+ elements.forEach(finishSpinner);
111
+ return;
112
+ }
113
+ const observer = new IntersectionObserver((entries) => {
114
+ for (const entry of entries) {
115
+ if (!entry.isIntersecting) {
116
+ continue;
117
+ }
118
+ const element = entry.target;
119
+ observer.unobserve(element);
120
+ animateSpinner(element);
121
+ }
122
+ }, {
123
+ threshold: 0.15
124
+ });
125
+ elements.forEach((element) => observer.observe(element));
126
+ const stopForReducedMotion = (event) => {
127
+ if (!event.matches) {
128
+ return;
129
+ }
130
+ observer.disconnect();
131
+ elements.forEach(finishSpinner);
132
+ reducedMotion.removeEventListener("change", stopForReducedMotion);
133
+ };
134
+ reducedMotion.addEventListener("change", stopForReducedMotion);
135
+ }
136
+ //# sourceMappingURL=number-spinner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number-spinner.js","sourceRoot":"","sources":["../src/number-spinner.ts"],"names":[],"mappings":"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","sourcesContent":["const REDUCED_MOTION_QUERY = \"(prefers-reduced-motion: reduce)\";\nconst initializedSpinners = new WeakSet<HTMLElement>();\nconst runningFrames = new Map<HTMLElement, number>();\n\nexport function spinnerFractionDigits(value: number): number {\n if (!Number.isFinite(value)) {\n return 0;\n }\n\n const [coefficient = \"\", exponentText] = String(value).toLowerCase().split(\"e\");\n const coefficientDecimals = coefficient.split(\".\")[1]?.length ?? 0;\n const exponent = Number(exponentText ?? 0);\n return Math.min(20, Math.max(0, coefficientDecimals - exponent));\n}\n\nexport function clampSpinnerDuration(value: unknown): number {\n const duration = Number(value);\n return Number.isFinite(duration)\n ? Math.min(10_000, Math.max(0, duration))\n : 1200;\n}\n\nexport function spinnerFrameValue(target: number, progress: number): number {\n const normalized = Math.min(1, Math.max(0, progress));\n const eased = 1 - Math.pow(1 - normalized, 3);\n return target * eased;\n}\n\nexport function formatSpinnerValue(\n value: number,\n locale: string,\n fractionDigits: number\n): string {\n const digits = Math.min(20, Math.max(0, Math.trunc(fractionDigits)));\n return new Intl.NumberFormat(locale || undefined, {\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n useGrouping: true\n }).format(value);\n}\n\nfunction spinnerDisplay(element: HTMLElement): HTMLElement | null {\n return element.querySelector<HTMLElement>(\n \"[data-flowpress-number-spinner-value]\"\n );\n}\n\nfunction spinnerTarget(element: HTMLElement): number {\n const target = Number(element.dataset.spinnerTarget);\n return Number.isFinite(target) ? target : 0;\n}\n\nfunction spinnerLocale(element: HTMLElement): string {\n return element.dataset.spinnerLocale\n || document.documentElement.lang\n || navigator.language\n || \"en\";\n}\n\nfunction renderSpinnerValue(\n element: HTMLElement,\n value: number,\n final = false\n): void {\n const display = spinnerDisplay(element);\n if (!display) {\n return;\n }\n const target = spinnerTarget(element);\n const digits = Number(element.dataset.spinnerFractionDigits) || 0;\n display.textContent = formatSpinnerValue(\n final ? target : value,\n spinnerLocale(element),\n digits\n );\n if (final) {\n element.dataset.spinnerState = \"complete\";\n }\n}\n\nfunction finishSpinner(element: HTMLElement): void {\n const frame = runningFrames.get(element);\n if (frame !== undefined) {\n cancelAnimationFrame(frame);\n runningFrames.delete(element);\n }\n renderSpinnerValue(element, spinnerTarget(element), true);\n}\n\nfunction animateSpinner(element: HTMLElement): void {\n const duration = clampSpinnerDuration(element.dataset.spinnerDuration);\n const target = spinnerTarget(element);\n if (duration === 0 || target === 0) {\n finishSpinner(element);\n return;\n }\n\n element.dataset.spinnerState = \"running\";\n renderSpinnerValue(element, 0);\n const startedAt = performance.now();\n\n const step = (now: number) => {\n const progress = Math.min(1, (now - startedAt) / duration);\n renderSpinnerValue(element, spinnerFrameValue(target, progress));\n if (progress >= 1) {\n finishSpinner(element);\n return;\n }\n runningFrames.set(element, requestAnimationFrame(step));\n };\n\n runningFrames.set(element, requestAnimationFrame(step));\n}\n\nexport function initializeNumberSpinners(root: ParentNode = document): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const elements = Array.from(\n root.querySelectorAll<HTMLElement>(\"[data-flowpress-number-spinner]\")\n ).filter((element) => {\n if (initializedSpinners.has(element)) {\n return false;\n }\n initializedSpinners.add(element);\n return true;\n });\n if (!elements.length) {\n return;\n }\n\n if (typeof window.matchMedia !== \"function\") {\n elements.forEach(finishSpinner);\n return;\n }\n\n const reducedMotion = window.matchMedia(REDUCED_MOTION_QUERY);\n if (reducedMotion.matches) {\n elements.forEach(finishSpinner);\n return;\n }\n\n if (!(\"IntersectionObserver\" in window)) {\n elements.forEach(finishSpinner);\n return;\n }\n\n const observer = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) {\n continue;\n }\n const element = entry.target as HTMLElement;\n observer.unobserve(element);\n animateSpinner(element);\n }\n }, {\n threshold: 0.15\n });\n\n elements.forEach((element) => observer.observe(element));\n\n const stopForReducedMotion = (event: MediaQueryListEvent) => {\n if (!event.matches) {\n return;\n }\n observer.disconnect();\n elements.forEach(finishSpinner);\n reducedMotion.removeEventListener(\"change\", stopForReducedMotion);\n };\n reducedMotion.addEventListener(\"change\", stopForReducedMotion);\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Collect class names authored in Gutenberg from serialized FlowPress blocks.
3
+ *
4
+ * The traversal includes resolved template/list blocks stored inside
5
+ * attributes as well as ordinary inner blocks. Tokens that could terminate a
6
+ * generated Tailwind `@source inline()` directive are discarded.
7
+ */
8
+ export declare function collectFlowpressClassNames(blocks: unknown): string[];
9
+ //# sourceMappingURL=style-classes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style-classes.d.ts","sourceRoot":"","sources":["../src/style-classes.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,CAgCpE"}
@@ -0,0 +1,49 @@
1
+ const MAX_CLASS_LENGTH = 256;
2
+ const UNSAFE_CLASS_CHARACTERS = /[\s\\{};\u0000-\u001f\u007f]/;
3
+ function classTokens(value) {
4
+ if (typeof value !== "string") {
5
+ return [];
6
+ }
7
+ return value
8
+ .split(/\s+/)
9
+ .map((token) => token.trim())
10
+ .filter((token) => (token.length > 0
11
+ && token.length <= MAX_CLASS_LENGTH
12
+ && !UNSAFE_CLASS_CHARACTERS.test(token)));
13
+ }
14
+ /**
15
+ * Collect class names authored in Gutenberg from serialized FlowPress blocks.
16
+ *
17
+ * The traversal includes resolved template/list blocks stored inside
18
+ * attributes as well as ordinary inner blocks. Tokens that could terminate a
19
+ * generated Tailwind `@source inline()` directive are discarded.
20
+ */
21
+ export function collectFlowpressClassNames(blocks) {
22
+ const classes = new Set();
23
+ const visited = new WeakSet();
24
+ const visit = (value) => {
25
+ if (!value || typeof value !== "object") {
26
+ return;
27
+ }
28
+ if (visited.has(value)) {
29
+ return;
30
+ }
31
+ visited.add(value);
32
+ if (Array.isArray(value)) {
33
+ value.forEach(visit);
34
+ return;
35
+ }
36
+ const block = value;
37
+ if (block.attributes && typeof block.attributes === "object") {
38
+ for (const attribute of ["className", "imageClassName"]) {
39
+ for (const token of classTokens(block.attributes[attribute])) {
40
+ classes.add(token);
41
+ }
42
+ }
43
+ }
44
+ Object.values(value).forEach(visit);
45
+ };
46
+ visit(blocks);
47
+ return [...classes].sort((left, right) => left.localeCompare(right));
48
+ }
49
+ //# sourceMappingURL=style-classes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style-classes.js","sourceRoot":"","sources":["../src/style-classes.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,uBAAuB,GAAG,8BAA8B,CAAC;AAE/D,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,KAAK;SACT,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACjB,KAAK,CAAC,MAAM,GAAG,CAAC;WACb,KAAK,CAAC,MAAM,IAAI,gBAAgB;WAChC,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CACxC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAe;IACxD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAU,CAAC;IAEtC,MAAM,KAAK,GAAG,CAAC,KAAc,EAAQ,EAAE;QACrC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,OAAO;QACT,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEnB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,KAAgC,CAAC;QAC/C,IAAI,KAAK,CAAC,UAAU,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC7D,KAAK,MAAM,SAAS,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAU,EAAE,CAAC;gBACjE,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;oBAC7D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACrB,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC;IAEF,KAAK,CAAC,MAAM,CAAC,CAAC;IACd,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;AACvE,CAAC","sourcesContent":["import type { FlowpressBlock } from \"./types.js\";\n\nconst MAX_CLASS_LENGTH = 256;\nconst UNSAFE_CLASS_CHARACTERS = /[\\s\\\\{};\\u0000-\\u001f\\u007f]/;\n\nfunction classTokens(value: unknown): string[] {\n if (typeof value !== \"string\") {\n return [];\n }\n\n return value\n .split(/\\s+/)\n .map((token) => token.trim())\n .filter((token) => (\n token.length > 0\n && token.length <= MAX_CLASS_LENGTH\n && !UNSAFE_CLASS_CHARACTERS.test(token)\n ));\n}\n\n/**\n * Collect class names authored in Gutenberg from serialized FlowPress blocks.\n *\n * The traversal includes resolved template/list blocks stored inside\n * attributes as well as ordinary inner blocks. Tokens that could terminate a\n * generated Tailwind `@source inline()` directive are discarded.\n */\nexport function collectFlowpressClassNames(blocks: unknown): string[] {\n const classes = new Set<string>();\n const visited = new WeakSet<object>();\n\n const visit = (value: unknown): void => {\n if (!value || typeof value !== \"object\") {\n return;\n }\n if (visited.has(value)) {\n return;\n }\n visited.add(value);\n\n if (Array.isArray(value)) {\n value.forEach(visit);\n return;\n }\n\n const block = value as Partial<FlowpressBlock>;\n if (block.attributes && typeof block.attributes === \"object\") {\n for (const attribute of [\"className\", \"imageClassName\"] as const) {\n for (const token of classTokens(block.attributes[attribute])) {\n classes.add(token);\n }\n }\n }\n\n Object.values(value).forEach(visit);\n };\n\n visit(blocks);\n return [...classes].sort((left, right) => left.localeCompare(right));\n}\n"]}