@styleflow.app/flowpress-astro-core 0.2.0-beta.10 → 0.2.0-beta.12
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 +16 -0
- package/README.md +10 -2
- package/dist/astro/GutenbergRenderer.astro +5 -0
- package/dist/astro/blocks/NumberSpinnerBlock.astro +131 -0
- package/dist/astro/blocks/TextBlock.astro +5 -1
- package/dist/astro/blocks/helpers.d.ts +6 -0
- package/dist/astro/blocks/helpers.d.ts.map +1 -1
- package/dist/astro/blocks/helpers.js +34 -1
- package/dist/astro/blocks/helpers.js.map +1 -1
- package/dist/astro/blocks/helpers.ts +46 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/number-spinner.d.ts +6 -0
- package/dist/number-spinner.d.ts.map +1 -0
- package/dist/number-spinner.js +131 -0
- package/dist/number-spinner.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.0-beta.12
|
|
6
|
+
|
|
7
|
+
- Added the reusable `sf/number-spinner` Astro renderer with independently
|
|
8
|
+
styled prefix, number and suffix text segments.
|
|
9
|
+
- Started counting only on first viewport intersection, retained the final
|
|
10
|
+
target in no-JavaScript markup and disabled animation for reduced-motion
|
|
11
|
+
preferences, including preference changes during an active count.
|
|
12
|
+
- Kept the final numeric value permanently available to assistive technology
|
|
13
|
+
while visual frame updates remain hidden from accessibility APIs.
|
|
14
|
+
|
|
15
|
+
## 0.2.0-beta.11
|
|
16
|
+
|
|
17
|
+
- Rendered empty Gutenberg and FlowPress text blocks with an internal
|
|
18
|
+
` `, preserving their typographic line height without visible
|
|
19
|
+
characters or synthetic break elements.
|
|
20
|
+
|
|
5
21
|
## 0.2.0-beta.10
|
|
6
22
|
|
|
7
23
|
- Preserved intentionally empty core Gutenberg paragraphs as visible line
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ The package must not import from an application or project plugin.
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pnpm add @styleflow.app/flowpress-astro-core@0.2.0-beta.
|
|
12
|
+
pnpm add @styleflow.app/flowpress-astro-core@0.2.0-beta.12
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Prereleases are pinned exactly. The package expects Astro 7 or newer and the
|
|
@@ -37,11 +37,19 @@ to the protected FlowPress REST endpoint, so the same component works in
|
|
|
37
37
|
static and server output modes. Gutenberg `anchor` and `className` attributes
|
|
38
38
|
are applied to the rendered block root as HTML `id` and `class`. Rich Text
|
|
39
39
|
preserves intentional double, triple and longer line breaks, including content
|
|
40
|
-
saved by older editor versions as empty paragraphs.
|
|
40
|
+
saved by older editor versions as empty paragraphs. Empty textual blocks render
|
|
41
|
+
an internal ` `, preserving their typographic line height without adding
|
|
42
|
+
visible characters. The exported
|
|
41
43
|
`collectFlowpressClassNames()` helper recursively extracts safe class tokens
|
|
42
44
|
from rendered CMS blocks so consumers can generate Tailwind
|
|
43
45
|
`@source inline()` directives before compiling their frontend CSS.
|
|
44
46
|
|
|
47
|
+
`sf/number-spinner` renders prefix, target number and suffix with independent
|
|
48
|
+
semantic tags and StyleFlow text coordinates. Its numeric value counts from
|
|
49
|
+
zero only when it enters the viewport. The final target remains available to
|
|
50
|
+
assistive technology and without JavaScript; `prefers-reduced-motion: reduce`
|
|
51
|
+
skips the animation entirely.
|
|
52
|
+
|
|
45
53
|
## Release
|
|
46
54
|
|
|
47
55
|
```bash
|
|
@@ -7,6 +7,7 @@ import FormBlock from "./blocks/FormBlock.astro";
|
|
|
7
7
|
import GridBlock from "./blocks/GridBlock.astro";
|
|
8
8
|
import HeroBlock from "./blocks/HeroBlock.astro";
|
|
9
9
|
import ListItemFallback from "./blocks/ListItemFallback.astro";
|
|
10
|
+
import NumberSpinnerBlock from "./blocks/NumberSpinnerBlock.astro";
|
|
10
11
|
import TextBlock from "./blocks/TextBlock.astro";
|
|
11
12
|
import {
|
|
12
13
|
blockHtmlAttributes,
|
|
@@ -71,6 +72,10 @@ function youtubeEmbedUrl(value: unknown): string | null {
|
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
if (name === "number-spinner") {
|
|
76
|
+
return <NumberSpinnerBlock block={block} contract={contract} />;
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
if ([
|
|
75
80
|
"heading",
|
|
76
81
|
"rich-text",
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
import S from "@styleflow.app/astro/S.astro";
|
|
3
|
+
import {
|
|
4
|
+
clampSpinnerDuration,
|
|
5
|
+
spinnerFractionDigits
|
|
6
|
+
} from "../../number-spinner.js";
|
|
7
|
+
import type { StyleflowContract } from "../../styleflow.js";
|
|
8
|
+
import type { FlowpressBlock } from "../../types.js";
|
|
9
|
+
import {
|
|
10
|
+
blockHtmlAttributes,
|
|
11
|
+
prefixedTextSemanticProps,
|
|
12
|
+
semanticProps,
|
|
13
|
+
textHtmlTag
|
|
14
|
+
} from "./helpers.js";
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
block: FlowpressBlock;
|
|
18
|
+
contract?: StyleflowContract;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { block, contract } = Astro.props;
|
|
22
|
+
const attributes = block.attributes;
|
|
23
|
+
const prefix = String(attributes.prefix ?? "");
|
|
24
|
+
const suffix = String(attributes.suffix ?? "");
|
|
25
|
+
const candidateNumber = Number(attributes.number);
|
|
26
|
+
const number = Number.isFinite(candidateNumber) ? candidateNumber : 0;
|
|
27
|
+
const duration = clampSpinnerDuration(attributes.duration);
|
|
28
|
+
const fractionDigits = spinnerFractionDigits(number);
|
|
29
|
+
const locale = Astro.currentLocale ?? "";
|
|
30
|
+
const numberText = String(number);
|
|
31
|
+
const semantic = semanticProps(attributes, contract);
|
|
32
|
+
const htmlAttributes = blockHtmlAttributes(
|
|
33
|
+
attributes,
|
|
34
|
+
"flowpress-number-spinner"
|
|
35
|
+
);
|
|
36
|
+
const prefixSemantic = prefixedTextSemanticProps(
|
|
37
|
+
attributes,
|
|
38
|
+
"prefix",
|
|
39
|
+
contract,
|
|
40
|
+
{ ty: "body", v: "md", w: "default", foreground: "primary" }
|
|
41
|
+
);
|
|
42
|
+
const numberSemantic = prefixedTextSemanticProps(
|
|
43
|
+
attributes,
|
|
44
|
+
"number",
|
|
45
|
+
contract,
|
|
46
|
+
{ ty: "heading", v: "2", w: "strong", foreground: "primary" }
|
|
47
|
+
);
|
|
48
|
+
const suffixSemantic = prefixedTextSemanticProps(
|
|
49
|
+
attributes,
|
|
50
|
+
"suffix",
|
|
51
|
+
contract,
|
|
52
|
+
{ ty: "body", v: "md", w: "default", foreground: "primary" }
|
|
53
|
+
);
|
|
54
|
+
const prefixTag = textHtmlTag(attributes.prefixTag);
|
|
55
|
+
const numberTag = textHtmlTag(attributes.numberTag);
|
|
56
|
+
const suffixTag = textHtmlTag(attributes.suffixTag);
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<S
|
|
60
|
+
as="div"
|
|
61
|
+
{...semantic}
|
|
62
|
+
{...htmlAttributes}
|
|
63
|
+
data-flowpress-number-spinner
|
|
64
|
+
data-spinner-target={number}
|
|
65
|
+
data-spinner-duration={duration}
|
|
66
|
+
data-spinner-fraction-digits={fractionDigits}
|
|
67
|
+
data-spinner-locale={locale || undefined}
|
|
68
|
+
>
|
|
69
|
+
{prefix ? (
|
|
70
|
+
<S
|
|
71
|
+
as={prefixTag}
|
|
72
|
+
class="flowpress-number-spinner__segment"
|
|
73
|
+
{...prefixSemantic}
|
|
74
|
+
>
|
|
75
|
+
{prefix}
|
|
76
|
+
</S>
|
|
77
|
+
) : null}
|
|
78
|
+
<S
|
|
79
|
+
as={numberTag}
|
|
80
|
+
class="flowpress-number-spinner__segment flowpress-number-spinner__number"
|
|
81
|
+
{...numberSemantic}
|
|
82
|
+
>
|
|
83
|
+
<S as="span" class="flowpress-number-spinner__accessible-value">
|
|
84
|
+
{numberText}
|
|
85
|
+
</S>
|
|
86
|
+
<S
|
|
87
|
+
as="span"
|
|
88
|
+
aria-hidden="true"
|
|
89
|
+
data-flowpress-number-spinner-value
|
|
90
|
+
>
|
|
91
|
+
{numberText}
|
|
92
|
+
</S>
|
|
93
|
+
</S>
|
|
94
|
+
{suffix ? (
|
|
95
|
+
<S
|
|
96
|
+
as={suffixTag}
|
|
97
|
+
class="flowpress-number-spinner__segment"
|
|
98
|
+
{...suffixSemantic}
|
|
99
|
+
>
|
|
100
|
+
{suffix}
|
|
101
|
+
</S>
|
|
102
|
+
) : null}
|
|
103
|
+
</S>
|
|
104
|
+
|
|
105
|
+
<script>
|
|
106
|
+
import { initializeNumberSpinners } from "../../number-spinner.js";
|
|
107
|
+
|
|
108
|
+
initializeNumberSpinners();
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<style>
|
|
112
|
+
.flowpress-number-spinner {
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
align-items: baseline;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.flowpress-number-spinner__segment {
|
|
118
|
+
display: inline;
|
|
119
|
+
margin: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.flowpress-number-spinner__accessible-value {
|
|
123
|
+
position: absolute;
|
|
124
|
+
width: 1px;
|
|
125
|
+
height: 1px;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
clip: rect(0 0 0 0);
|
|
128
|
+
clip-path: inset(50%);
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
@@ -4,6 +4,7 @@ import type { FlowpressBlock } from "../../types.js";
|
|
|
4
4
|
import type { StyleflowContract } from "../../styleflow.js";
|
|
5
5
|
import {
|
|
6
6
|
blockHtmlAttributes,
|
|
7
|
+
preserveEmptyTextBlock,
|
|
7
8
|
preserveRichTextSpacing,
|
|
8
9
|
semanticProps,
|
|
9
10
|
textValue,
|
|
@@ -43,13 +44,16 @@ const coreHtml = String(
|
|
|
43
44
|
?? block.innerHtml
|
|
44
45
|
?? ""
|
|
45
46
|
);
|
|
46
|
-
const
|
|
47
|
+
const normalizedHtml = name === "heading"
|
|
47
48
|
? textValue(block)
|
|
48
49
|
: name === "rich-text"
|
|
49
50
|
? preserveRichTextSpacing(String(attributes.content ?? ""))
|
|
50
51
|
: name === "quote"
|
|
51
52
|
? textValue(block, "quote")
|
|
52
53
|
: unwrapCoreHtml(preserveRichTextSpacing(coreHtml));
|
|
54
|
+
const html = name === "core/list"
|
|
55
|
+
? normalizedHtml
|
|
56
|
+
: preserveEmptyTextBlock(normalizedHtml);
|
|
53
57
|
const listTag = Boolean(attributes.ordered) ? "ol" : "ul";
|
|
54
58
|
---
|
|
55
59
|
|
|
@@ -9,7 +9,11 @@ export declare function blockHtmlAttributes(attributes: Record<string, unknown>,
|
|
|
9
9
|
id?: string;
|
|
10
10
|
class?: string;
|
|
11
11
|
};
|
|
12
|
+
export declare const TEXT_HTML_TAGS: readonly ["span", "p", "h1", "h2", "h3", "h4", "h5", "h6", "strong", "em", "small", "mark"];
|
|
13
|
+
export type TextHtmlTag = (typeof TEXT_HTML_TAGS)[number];
|
|
14
|
+
export declare function textHtmlTag(value: unknown): TextHtmlTag;
|
|
12
15
|
export declare function semanticProps(attributes: Record<string, unknown>, contract?: StyleflowContract, defaults?: Record<string, unknown>): Record<string, unknown>;
|
|
16
|
+
export declare function prefixedTextSemanticProps(attributes: Record<string, unknown>, prefix: string, contract?: StyleflowContract, defaults?: Record<string, unknown>): Record<string, unknown>;
|
|
13
17
|
export declare function objectValue(block: FlowpressBlock, key: string): Record<string, unknown>;
|
|
14
18
|
export declare function textValue(block: FlowpressBlock, key?: string): string;
|
|
15
19
|
/**
|
|
@@ -18,6 +22,8 @@ export declare function textValue(block: FlowpressBlock, key?: string): string;
|
|
|
18
22
|
* empty paragraph elements, or literal newlines in an attribute value.
|
|
19
23
|
*/
|
|
20
24
|
export declare function preserveRichTextSpacing(html: string): string;
|
|
25
|
+
/** Keep an empty text element at its typographic line height without visible text. */
|
|
26
|
+
export declare function preserveEmptyTextBlock(html: string): string;
|
|
21
27
|
export declare function linkValue(block: FlowpressBlock, key?: string): string;
|
|
22
28
|
export declare function responsiveValues(attributes: Record<string, unknown>, prefix: "columns" | "span", fallback: number): Record<"base" | "sm" | "md" | "lg" | "xl" | "2xl", number>;
|
|
23
29
|
export declare function unwrapCoreHtml(html: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,sBAAsB;;;;CAIlC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,SAAS,SAAK,GACb;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBjC;AAqBD,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAA0B,GACzD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAErE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAMrE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,MAAM,CAAC,CAe5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD"}
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,sBAAsB;;;;CAIlC,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,SAAS,SAAK,GACb;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAgBjC;AAqBD,eAAO,MAAM,cAAc,6FAajB,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAKvD;AAED,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,yBAAyB,CACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,MAAM,GACV,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAErE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB5D;AAED,sFAAsF;AACtF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,GAAG,MAAM,CAMrE;AAED,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,MAAM,CAAC,CAe5D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD"}
|
|
@@ -38,6 +38,26 @@ const SEMANTIC_KEYS = [
|
|
|
38
38
|
"noRadius",
|
|
39
39
|
"noForeground"
|
|
40
40
|
];
|
|
41
|
+
export const TEXT_HTML_TAGS = [
|
|
42
|
+
"span",
|
|
43
|
+
"p",
|
|
44
|
+
"h1",
|
|
45
|
+
"h2",
|
|
46
|
+
"h3",
|
|
47
|
+
"h4",
|
|
48
|
+
"h5",
|
|
49
|
+
"h6",
|
|
50
|
+
"strong",
|
|
51
|
+
"em",
|
|
52
|
+
"small",
|
|
53
|
+
"mark"
|
|
54
|
+
];
|
|
55
|
+
export function textHtmlTag(value) {
|
|
56
|
+
const candidate = String(value ?? "span");
|
|
57
|
+
return TEXT_HTML_TAGS.includes(candidate)
|
|
58
|
+
? candidate
|
|
59
|
+
: "span";
|
|
60
|
+
}
|
|
41
61
|
export function semanticProps(attributes, contract, defaults = DEFAULT_SEMANTIC_PROPS) {
|
|
42
62
|
const input = Object.fromEntries(SEMANTIC_KEYS
|
|
43
63
|
.filter((key) => attributes[key] !== undefined)
|
|
@@ -46,6 +66,15 @@ export function semanticProps(attributes, contract, defaults = DEFAULT_SEMANTIC_
|
|
|
46
66
|
? resolveDynamicStyleflowProps(input, defaults, contract)
|
|
47
67
|
: { ...defaults, ...input };
|
|
48
68
|
}
|
|
69
|
+
export function prefixedTextSemanticProps(attributes, prefix, contract, defaults = {}) {
|
|
70
|
+
const attributeName = (name) => `${prefix}${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
71
|
+
return semanticProps({
|
|
72
|
+
ty: attributes[attributeName("ty")],
|
|
73
|
+
v: attributes[attributeName("v")],
|
|
74
|
+
w: attributes[attributeName("w")],
|
|
75
|
+
foreground: attributes[attributeName("foreground")]
|
|
76
|
+
}, contract, defaults);
|
|
77
|
+
}
|
|
49
78
|
export function objectValue(block, key) {
|
|
50
79
|
const value = block.attributes[key];
|
|
51
80
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
@@ -63,7 +92,7 @@ export function textValue(block, key = "text") {
|
|
|
63
92
|
export function preserveRichTextSpacing(html) {
|
|
64
93
|
const visibleEmptyBlocks = html
|
|
65
94
|
.replace(/\r\n?/g, "\n")
|
|
66
|
-
.replace(/<(p|div)(\s[^>]*)?>(?:\s| | )*<\/\1>/gi, (_match, tag, attributes = "") => `<${tag}${attributes}
|
|
95
|
+
.replace(/<(p|div)(\s[^>]*)?>(?:\s| | )*<\/\1>/gi, (_match, tag, attributes = "") => `<${tag}${attributes}> </${tag}>`);
|
|
67
96
|
return visibleEmptyBlocks
|
|
68
97
|
.split(/(<[^>]+>)/g)
|
|
69
98
|
.map((part) => {
|
|
@@ -74,6 +103,10 @@ export function preserveRichTextSpacing(html) {
|
|
|
74
103
|
})
|
|
75
104
|
.join("");
|
|
76
105
|
}
|
|
106
|
+
/** Keep an empty text element at its typographic line height without visible text. */
|
|
107
|
+
export function preserveEmptyTextBlock(html) {
|
|
108
|
+
return html.trim() === "" ? " " : html;
|
|
109
|
+
}
|
|
77
110
|
export function linkValue(block, key = "link") {
|
|
78
111
|
const value = block.attributes[key];
|
|
79
112
|
const href = typeof value === "string"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveDynamicStyleflowProps,\n type StyleflowContract\n} from \"../../styleflow.js\";\nimport type { FlowpressBlock } from \"../../types.js\";\n\nexport const DEFAULT_SEMANTIC_PROPS = {\n surface: \"default\",\n layoutRole: \"stack\",\n density: \"md\"\n};\n\nexport function blockHtmlAttributes(\n attributes: Record<string, unknown>,\n baseClass = \"\"\n): { id?: string; class?: string } {\n const anchor = typeof attributes.anchor === \"string\"\n ? attributes.anchor.trim()\n : \"\";\n const customClass = typeof attributes.className === \"string\"\n ? attributes.className.trim()\n : \"\";\n const className = [baseClass.trim(), customClass]\n .filter(Boolean)\n .join(\" \")\n .replace(/\\s+/g, \" \");\n\n return {\n ...(anchor ? { id: anchor } : {}),\n ...(className ? { class: className } : {})\n };\n}\n\nconst SEMANTIC_KEYS = [\n \"tone\",\n \"intensity\",\n \"surface\",\n \"layoutRole\",\n \"density\",\n \"ty\",\n \"v\",\n \"w\",\n \"foreground\",\n \"interactivePriority\",\n \"noBackground\",\n \"noBorder\",\n \"noPadding\",\n \"noGap\",\n \"noRadius\",\n \"noForeground\"\n] as const;\n\nexport function semanticProps(\n attributes: Record<string, unknown>,\n contract?: StyleflowContract,\n defaults: Record<string, unknown> = DEFAULT_SEMANTIC_PROPS\n): Record<string, unknown> {\n const input = Object.fromEntries(\n SEMANTIC_KEYS\n .filter((key) => attributes[key] !== undefined)\n .map((key) => [key, attributes[key]])\n );\n return contract\n ? resolveDynamicStyleflowProps(input, defaults, contract)\n : { ...defaults, ...input };\n}\n\nexport function objectValue(\n block: FlowpressBlock,\n key: string\n): Record<string, unknown> {\n const value = block.attributes[key];\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function textValue(block: FlowpressBlock, key = \"text\"): string {\n return String(block.attributes[key] ?? \"\");\n}\n\n/**\n * Keep intentional blank lines visible across current and legacy RichText\n * serializations. Gutenberg can represent them either as repeated `<br>` tags,\n * empty paragraph elements, or literal newlines in an attribute value.\n */\nexport function preserveRichTextSpacing(html: string): string {\n const visibleEmptyBlocks = html\n .replace(/\\r\\n?/g, \"\\n\")\n .replace(\n /<(p|div)(\\s[^>]*)?>(?:\\s| | )*<\\/\\1>/gi,\n (_match, tag: string, attributes = \"\") =>\n `<${tag}${attributes}><br aria-hidden=\"true\"></${tag}>`\n );\n\n return visibleEmptyBlocks\n .split(/(<[^>]+>)/g)\n .map((part) => {\n if (part.startsWith(\"<\") || part.trim() === \"\") {\n return part;\n }\n return part.replace(/\\n(?:[ \\t]*\\n)+/g, (newlines) =>\n \"<br>\".repeat((newlines.match(/\\n/g) || []).length)\n );\n })\n .join(\"\");\n}\n\nexport function linkValue(block: FlowpressBlock, key = \"link\"): string {\n const value = block.attributes[key];\n const href = typeof value === \"string\"\n ? value\n : String(objectValue(block, key).url ?? \"\");\n return /^(?:\\/(?!\\/)|#|https?:\\/\\/|mailto:|tel:)/i.test(href) ? href : \"\";\n}\n\nexport function responsiveValues(\n attributes: Record<string, unknown>,\n prefix: \"columns\" | \"span\",\n fallback: number\n): Record<\"base\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\", number> {\n const keys = [\"base\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n const result = {} as Record<(typeof keys)[number], number>;\n let inherited = fallback;\n for (const key of keys) {\n const suffix = key === \"base\"\n ? \"Base\"\n : key === \"2xl\" ? \"2xl\" : key.charAt(0).toUpperCase() + key.slice(1);\n const candidate = Number(attributes[`${prefix}${suffix}`]);\n if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {\n inherited = candidate;\n }\n result[key] = inherited;\n }\n return result;\n}\n\nexport function unwrapCoreHtml(html: string): string {\n const match = html.trim().match(\n /^<(p|h[1-6]|ul|ol|li|blockquote)(?:\\s[^>]*)?>([\\s\\S]*)<\\/\\1>$/\n );\n return match?.[2] ?? html;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../../src/astro/blocks/helpers.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveDynamicStyleflowProps,\n type StyleflowContract\n} from \"../../styleflow.js\";\nimport type { FlowpressBlock } from \"../../types.js\";\n\nexport const DEFAULT_SEMANTIC_PROPS = {\n surface: \"default\",\n layoutRole: \"stack\",\n density: \"md\"\n};\n\nexport function blockHtmlAttributes(\n attributes: Record<string, unknown>,\n baseClass = \"\"\n): { id?: string; class?: string } {\n const anchor = typeof attributes.anchor === \"string\"\n ? attributes.anchor.trim()\n : \"\";\n const customClass = typeof attributes.className === \"string\"\n ? attributes.className.trim()\n : \"\";\n const className = [baseClass.trim(), customClass]\n .filter(Boolean)\n .join(\" \")\n .replace(/\\s+/g, \" \");\n\n return {\n ...(anchor ? { id: anchor } : {}),\n ...(className ? { class: className } : {})\n };\n}\n\nconst SEMANTIC_KEYS = [\n \"tone\",\n \"intensity\",\n \"surface\",\n \"layoutRole\",\n \"density\",\n \"ty\",\n \"v\",\n \"w\",\n \"foreground\",\n \"interactivePriority\",\n \"noBackground\",\n \"noBorder\",\n \"noPadding\",\n \"noGap\",\n \"noRadius\",\n \"noForeground\"\n] as const;\n\nexport const TEXT_HTML_TAGS = [\n \"span\",\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"strong\",\n \"em\",\n \"small\",\n \"mark\"\n] as const;\n\nexport type TextHtmlTag = (typeof TEXT_HTML_TAGS)[number];\n\nexport function textHtmlTag(value: unknown): TextHtmlTag {\n const candidate = String(value ?? \"span\");\n return TEXT_HTML_TAGS.includes(candidate as TextHtmlTag)\n ? candidate as TextHtmlTag\n : \"span\";\n}\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 prefixedTextSemanticProps(\n attributes: Record<string, unknown>,\n prefix: string,\n contract?: StyleflowContract,\n defaults: Record<string, unknown> = {}\n): Record<string, unknown> {\n const attributeName = (name: string) =>\n `${prefix}${name.charAt(0).toUpperCase()}${name.slice(1)}`;\n return semanticProps({\n ty: attributes[attributeName(\"ty\")],\n v: attributes[attributeName(\"v\")],\n w: attributes[attributeName(\"w\")],\n foreground: attributes[attributeName(\"foreground\")]\n }, contract, defaults);\n}\n\nexport function objectValue(\n block: FlowpressBlock,\n key: string\n): Record<string, unknown> {\n const value = block.attributes[key];\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function textValue(block: FlowpressBlock, key = \"text\"): string {\n return String(block.attributes[key] ?? \"\");\n}\n\n/**\n * Keep intentional blank lines visible across current and legacy RichText\n * serializations. Gutenberg can represent them either as repeated `<br>` tags,\n * empty paragraph elements, or literal newlines in an attribute value.\n */\nexport function preserveRichTextSpacing(html: string): string {\n const visibleEmptyBlocks = html\n .replace(/\\r\\n?/g, \"\\n\")\n .replace(\n /<(p|div)(\\s[^>]*)?>(?:\\s| | )*<\\/\\1>/gi,\n (_match, tag: string, attributes = \"\") =>\n `<${tag}${attributes}> </${tag}>`\n );\n\n return visibleEmptyBlocks\n .split(/(<[^>]+>)/g)\n .map((part) => {\n if (part.startsWith(\"<\") || part.trim() === \"\") {\n return part;\n }\n return part.replace(/\\n(?:[ \\t]*\\n)+/g, (newlines) =>\n \"<br>\".repeat((newlines.match(/\\n/g) || []).length)\n );\n })\n .join(\"\");\n}\n\n/** Keep an empty text element at its typographic line height without visible text. */\nexport function preserveEmptyTextBlock(html: string): string {\n return html.trim() === \"\" ? \" \" : html;\n}\n\nexport function linkValue(block: FlowpressBlock, key = \"link\"): string {\n const value = block.attributes[key];\n const href = typeof value === \"string\"\n ? value\n : String(objectValue(block, key).url ?? \"\");\n return /^(?:\\/(?!\\/)|#|https?:\\/\\/|mailto:|tel:)/i.test(href) ? href : \"\";\n}\n\nexport function responsiveValues(\n attributes: Record<string, unknown>,\n prefix: \"columns\" | \"span\",\n fallback: number\n): Record<\"base\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\", number> {\n const keys = [\"base\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n const result = {} as Record<(typeof keys)[number], number>;\n let inherited = fallback;\n for (const key of keys) {\n const suffix = key === \"base\"\n ? \"Base\"\n : key === \"2xl\" ? \"2xl\" : key.charAt(0).toUpperCase() + key.slice(1);\n const candidate = Number(attributes[`${prefix}${suffix}`]);\n if (Number.isInteger(candidate) && candidate >= 1 && candidate <= 12) {\n inherited = candidate;\n }\n result[key] = inherited;\n }\n return result;\n}\n\nexport function unwrapCoreHtml(html: string): string {\n const match = html.trim().match(\n /^<(p|h[1-6]|ul|ol|li|blockquote)(?:\\s[^>]*)?>([\\s\\S]*)<\\/\\1>$/\n );\n return match?.[2] ?? html;\n}\n"]}
|
|
@@ -50,6 +50,30 @@ const SEMANTIC_KEYS = [
|
|
|
50
50
|
"noForeground"
|
|
51
51
|
] as const;
|
|
52
52
|
|
|
53
|
+
export const TEXT_HTML_TAGS = [
|
|
54
|
+
"span",
|
|
55
|
+
"p",
|
|
56
|
+
"h1",
|
|
57
|
+
"h2",
|
|
58
|
+
"h3",
|
|
59
|
+
"h4",
|
|
60
|
+
"h5",
|
|
61
|
+
"h6",
|
|
62
|
+
"strong",
|
|
63
|
+
"em",
|
|
64
|
+
"small",
|
|
65
|
+
"mark"
|
|
66
|
+
] as const;
|
|
67
|
+
|
|
68
|
+
export type TextHtmlTag = (typeof TEXT_HTML_TAGS)[number];
|
|
69
|
+
|
|
70
|
+
export function textHtmlTag(value: unknown): TextHtmlTag {
|
|
71
|
+
const candidate = String(value ?? "span");
|
|
72
|
+
return TEXT_HTML_TAGS.includes(candidate as TextHtmlTag)
|
|
73
|
+
? candidate as TextHtmlTag
|
|
74
|
+
: "span";
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
export function semanticProps(
|
|
54
78
|
attributes: Record<string, unknown>,
|
|
55
79
|
contract?: StyleflowContract,
|
|
@@ -65,6 +89,22 @@ export function semanticProps(
|
|
|
65
89
|
: { ...defaults, ...input };
|
|
66
90
|
}
|
|
67
91
|
|
|
92
|
+
export function prefixedTextSemanticProps(
|
|
93
|
+
attributes: Record<string, unknown>,
|
|
94
|
+
prefix: string,
|
|
95
|
+
contract?: StyleflowContract,
|
|
96
|
+
defaults: Record<string, unknown> = {}
|
|
97
|
+
): Record<string, unknown> {
|
|
98
|
+
const attributeName = (name: string) =>
|
|
99
|
+
`${prefix}${name.charAt(0).toUpperCase()}${name.slice(1)}`;
|
|
100
|
+
return semanticProps({
|
|
101
|
+
ty: attributes[attributeName("ty")],
|
|
102
|
+
v: attributes[attributeName("v")],
|
|
103
|
+
w: attributes[attributeName("w")],
|
|
104
|
+
foreground: attributes[attributeName("foreground")]
|
|
105
|
+
}, contract, defaults);
|
|
106
|
+
}
|
|
107
|
+
|
|
68
108
|
export function objectValue(
|
|
69
109
|
block: FlowpressBlock,
|
|
70
110
|
key: string
|
|
@@ -90,7 +130,7 @@ export function preserveRichTextSpacing(html: string): string {
|
|
|
90
130
|
.replace(
|
|
91
131
|
/<(p|div)(\s[^>]*)?>(?:\s| | )*<\/\1>/gi,
|
|
92
132
|
(_match, tag: string, attributes = "") =>
|
|
93
|
-
`<${tag}${attributes}
|
|
133
|
+
`<${tag}${attributes}> </${tag}>`
|
|
94
134
|
);
|
|
95
135
|
|
|
96
136
|
return visibleEmptyBlocks
|
|
@@ -106,6 +146,11 @@ export function preserveRichTextSpacing(html: string): string {
|
|
|
106
146
|
.join("");
|
|
107
147
|
}
|
|
108
148
|
|
|
149
|
+
/** Keep an empty text element at its typographic line height without visible text. */
|
|
150
|
+
export function preserveEmptyTextBlock(html: string): string {
|
|
151
|
+
return html.trim() === "" ? " " : html;
|
|
152
|
+
}
|
|
153
|
+
|
|
109
154
|
export function linkValue(block: FlowpressBlock, key = "link"): string {
|
|
110
155
|
const value = block.attributes[key];
|
|
111
156
|
const href = typeof value === "string"
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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,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
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,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC","sourcesContent":["export * from \"./client.js\";\nexport * from \"./env.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,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 \"./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,CAMR;AA2ED,wBAAgB,wBAAwB,CAAC,IAAI,GAAE,UAAqB,GAAG,IAAI,CAqD1E"}
|
|
@@ -0,0 +1,131 @@
|
|
|
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
|
+
}).format(value);
|
|
30
|
+
}
|
|
31
|
+
function spinnerDisplay(element) {
|
|
32
|
+
return element.querySelector("[data-flowpress-number-spinner-value]");
|
|
33
|
+
}
|
|
34
|
+
function spinnerTarget(element) {
|
|
35
|
+
const target = Number(element.dataset.spinnerTarget);
|
|
36
|
+
return Number.isFinite(target) ? target : 0;
|
|
37
|
+
}
|
|
38
|
+
function spinnerLocale(element) {
|
|
39
|
+
return element.dataset.spinnerLocale
|
|
40
|
+
|| document.documentElement.lang
|
|
41
|
+
|| navigator.language
|
|
42
|
+
|| "en";
|
|
43
|
+
}
|
|
44
|
+
function renderSpinnerValue(element, value, final = false) {
|
|
45
|
+
const display = spinnerDisplay(element);
|
|
46
|
+
if (!display) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const target = spinnerTarget(element);
|
|
50
|
+
const digits = Number(element.dataset.spinnerFractionDigits) || 0;
|
|
51
|
+
display.textContent = formatSpinnerValue(final ? target : value, spinnerLocale(element), digits);
|
|
52
|
+
if (final) {
|
|
53
|
+
element.dataset.spinnerState = "complete";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function finishSpinner(element) {
|
|
57
|
+
const frame = runningFrames.get(element);
|
|
58
|
+
if (frame !== undefined) {
|
|
59
|
+
cancelAnimationFrame(frame);
|
|
60
|
+
runningFrames.delete(element);
|
|
61
|
+
}
|
|
62
|
+
renderSpinnerValue(element, spinnerTarget(element), true);
|
|
63
|
+
}
|
|
64
|
+
function animateSpinner(element) {
|
|
65
|
+
const duration = clampSpinnerDuration(element.dataset.spinnerDuration);
|
|
66
|
+
const target = spinnerTarget(element);
|
|
67
|
+
if (duration === 0 || target === 0) {
|
|
68
|
+
finishSpinner(element);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
element.dataset.spinnerState = "running";
|
|
72
|
+
renderSpinnerValue(element, 0);
|
|
73
|
+
const startedAt = performance.now();
|
|
74
|
+
const step = (now) => {
|
|
75
|
+
const progress = Math.min(1, (now - startedAt) / duration);
|
|
76
|
+
renderSpinnerValue(element, spinnerFrameValue(target, progress));
|
|
77
|
+
if (progress >= 1) {
|
|
78
|
+
finishSpinner(element);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
runningFrames.set(element, requestAnimationFrame(step));
|
|
82
|
+
};
|
|
83
|
+
runningFrames.set(element, requestAnimationFrame(step));
|
|
84
|
+
}
|
|
85
|
+
export function initializeNumberSpinners(root = document) {
|
|
86
|
+
if (typeof window === "undefined") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const elements = Array.from(root.querySelectorAll("[data-flowpress-number-spinner]")).filter((element) => {
|
|
90
|
+
if (initializedSpinners.has(element)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
initializedSpinners.add(element);
|
|
94
|
+
return true;
|
|
95
|
+
});
|
|
96
|
+
if (!elements.length) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const reducedMotion = window.matchMedia(REDUCED_MOTION_QUERY);
|
|
100
|
+
if (reducedMotion.matches) {
|
|
101
|
+
elements.forEach(finishSpinner);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (!("IntersectionObserver" in window)) {
|
|
105
|
+
elements.forEach(animateSpinner);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const observer = new IntersectionObserver((entries) => {
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
if (!entry.isIntersecting) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const element = entry.target;
|
|
114
|
+
observer.unobserve(element);
|
|
115
|
+
animateSpinner(element);
|
|
116
|
+
}
|
|
117
|
+
}, {
|
|
118
|
+
threshold: 0.15
|
|
119
|
+
});
|
|
120
|
+
elements.forEach((element) => observer.observe(element));
|
|
121
|
+
const stopForReducedMotion = (event) => {
|
|
122
|
+
if (!event.matches) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
observer.disconnect();
|
|
126
|
+
elements.forEach(finishSpinner);
|
|
127
|
+
reducedMotion.removeEventListener("change", stopForReducedMotion);
|
|
128
|
+
};
|
|
129
|
+
reducedMotion.addEventListener("change", stopForReducedMotion);
|
|
130
|
+
}
|
|
131
|
+
//# 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 }).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 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(animateSpinner);\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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/astro.d.ts","../src/client.ts","../src/env.ts","../src/index.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/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