@tenphi/tasty 0.15.2 → 0.16.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.
- package/dist/chunks/definitions.js +1 -1
- package/dist/chunks/definitions.js.map +1 -1
- package/dist/config.d.ts +45 -2
- package/dist/config.js +63 -24
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/counter-style/index.js +51 -0
- package/dist/counter-style/index.js.map +1 -0
- package/dist/font-face/index.js +63 -0
- package/dist/font-face/index.js.map +1 -0
- package/dist/hooks/resolve-ssr-collector.js +15 -0
- package/dist/hooks/resolve-ssr-collector.js.map +1 -0
- package/dist/hooks/useCounterStyle.d.ts +50 -0
- package/dist/hooks/useCounterStyle.js +47 -0
- package/dist/hooks/useCounterStyle.js.map +1 -0
- package/dist/hooks/useFontFace.d.ts +43 -0
- package/dist/hooks/useFontFace.js +71 -0
- package/dist/hooks/useFontFace.js.map +1 -0
- package/dist/hooks/useGlobalStyles.js +1 -5
- package/dist/hooks/useGlobalStyles.js.map +1 -1
- package/dist/hooks/useKeyframes.js +1 -5
- package/dist/hooks/useKeyframes.js.map +1 -1
- package/dist/hooks/useProperty.js +1 -5
- package/dist/hooks/useProperty.js.map +1 -1
- package/dist/hooks/useRawCSS.js +1 -5
- package/dist/hooks/useRawCSS.js.map +1 -1
- package/dist/hooks/useStyles.js +33 -12
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/injector/index.d.ts +20 -2
- package/dist/injector/index.js +19 -1
- package/dist/injector/index.js.map +1 -1
- package/dist/injector/injector.d.ts +19 -1
- package/dist/injector/injector.js +35 -0
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.js +2 -0
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/injector/types.d.ts +61 -1
- package/dist/ssr/collector.d.ts +17 -0
- package/dist/ssr/collector.js +50 -6
- package/dist/ssr/collector.js.map +1 -1
- package/dist/ssr/index.js +1 -1
- package/dist/states/index.js +2 -0
- package/dist/states/index.js.map +1 -1
- package/dist/styles/border.js +2 -2
- package/dist/styles/border.js.map +1 -1
- package/dist/styles/preset.js +23 -37
- package/dist/styles/preset.js.map +1 -1
- package/dist/styles/types.d.ts +25 -3
- package/dist/tasty.d.ts +1 -1
- package/dist/utils/styles.d.ts +0 -1
- package/dist/utils/styles.js +0 -1
- package/dist/utils/styles.js.map +1 -1
- package/dist/zero/babel.d.ts +11 -1
- package/dist/zero/babel.js +13 -7
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/extractor.js +53 -1
- package/dist/zero/extractor.js.map +1 -1
- package/docs/configuration.md +61 -0
- package/docs/design-system.md +17 -0
- package/docs/dsl.md +91 -1
- package/docs/runtime.md +88 -0
- package/docs/ssr.md +2 -0
- package/docs/tasty-static.md +2 -0
- package/package.json +2 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/counter-style/index.ts
|
|
2
|
+
const COUNTER_STYLE_KEY = "@counterStyle";
|
|
3
|
+
/**
|
|
4
|
+
* Check if styles object has local @counterStyle definition.
|
|
5
|
+
*/
|
|
6
|
+
function hasLocalCounterStyle(styles) {
|
|
7
|
+
return COUNTER_STYLE_KEY in styles;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Extract local @counterStyle from styles object.
|
|
11
|
+
* Returns null if no local counter styles (fast path).
|
|
12
|
+
*/
|
|
13
|
+
function extractLocalCounterStyle(styles) {
|
|
14
|
+
const counterStyle = styles[COUNTER_STYLE_KEY];
|
|
15
|
+
if (!counterStyle || typeof counterStyle !== "object") return null;
|
|
16
|
+
return counterStyle;
|
|
17
|
+
}
|
|
18
|
+
const COUNTER_STYLE_DESCRIPTOR_MAP = {
|
|
19
|
+
system: "system",
|
|
20
|
+
symbols: "symbols",
|
|
21
|
+
additiveSymbols: "additive-symbols",
|
|
22
|
+
prefix: "prefix",
|
|
23
|
+
suffix: "suffix",
|
|
24
|
+
negative: "negative",
|
|
25
|
+
range: "range",
|
|
26
|
+
pad: "pad",
|
|
27
|
+
fallback: "fallback",
|
|
28
|
+
speakAs: "speak-as"
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Format the inner declarations of a @counter-style rule (no wrapper).
|
|
32
|
+
* Used by the injector which needs selector and declarations separately.
|
|
33
|
+
*/
|
|
34
|
+
function formatCounterStyleDeclarations(descriptors) {
|
|
35
|
+
const parts = [];
|
|
36
|
+
for (const [key, cssName] of Object.entries(COUNTER_STYLE_DESCRIPTOR_MAP)) {
|
|
37
|
+
const value = descriptors[key];
|
|
38
|
+
if (value !== void 0) parts.push(`${cssName}: ${value};`);
|
|
39
|
+
}
|
|
40
|
+
return parts.join(" ");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Format a @counter-style rule as CSS.
|
|
44
|
+
*/
|
|
45
|
+
function formatCounterStyleRule(name, descriptors) {
|
|
46
|
+
return `@counter-style ${name} { ${formatCounterStyleDeclarations(descriptors)} }`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { extractLocalCounterStyle, formatCounterStyleDeclarations, formatCounterStyleRule, hasLocalCounterStyle };
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/counter-style/index.ts"],"sourcesContent":["/**\n * Counter Style Utilities\n *\n * Utilities for extracting and processing CSS @counter-style definitions in styles.\n * Counter-style rules are permanent once injected and do not need cleanup.\n */\n\nimport type { CounterStyleDescriptors } from '../injector/types';\nimport type { Styles } from '../styles/types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst COUNTER_STYLE_KEY = '@counterStyle';\n\n// ============================================================================\n// Extraction Functions\n// ============================================================================\n\n/**\n * Check if styles object has local @counterStyle definition.\n */\nexport function hasLocalCounterStyle(styles: Styles): boolean {\n return COUNTER_STYLE_KEY in styles;\n}\n\n/**\n * Extract local @counterStyle from styles object.\n * Returns null if no local counter styles (fast path).\n */\nexport function extractLocalCounterStyle(\n styles: Styles,\n): Record<string, CounterStyleDescriptors> | null {\n const counterStyle = styles[COUNTER_STYLE_KEY];\n if (!counterStyle || typeof counterStyle !== 'object') {\n return null;\n }\n return counterStyle as Record<string, CounterStyleDescriptors>;\n}\n\n// ============================================================================\n// CSS Formatting\n// ============================================================================\n\nconst COUNTER_STYLE_DESCRIPTOR_MAP: Record<string, string> = {\n system: 'system',\n symbols: 'symbols',\n additiveSymbols: 'additive-symbols',\n prefix: 'prefix',\n suffix: 'suffix',\n negative: 'negative',\n range: 'range',\n pad: 'pad',\n fallback: 'fallback',\n speakAs: 'speak-as',\n};\n\n/**\n * Format the inner declarations of a @counter-style rule (no wrapper).\n * Used by the injector which needs selector and declarations separately.\n */\nexport function formatCounterStyleDeclarations(\n descriptors: CounterStyleDescriptors,\n): string {\n const parts: string[] = [];\n\n for (const [key, cssName] of Object.entries(COUNTER_STYLE_DESCRIPTOR_MAP)) {\n const value = descriptors[key as keyof CounterStyleDescriptors];\n if (value !== undefined) {\n parts.push(`${cssName}: ${value};`);\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Format a @counter-style rule as CSS.\n */\nexport function formatCounterStyleRule(\n name: string,\n descriptors: CounterStyleDescriptors,\n): string {\n return `@counter-style ${name} { ${formatCounterStyleDeclarations(descriptors)} }`;\n}\n"],"mappings":";AAcA,MAAM,oBAAoB;;;;AAS1B,SAAgB,qBAAqB,QAAyB;AAC5D,QAAO,qBAAqB;;;;;;AAO9B,SAAgB,yBACd,QACgD;CAChD,MAAM,eAAe,OAAO;AAC5B,KAAI,CAAC,gBAAgB,OAAO,iBAAiB,SAC3C,QAAO;AAET,QAAO;;AAOT,MAAM,+BAAuD;CAC3D,QAAQ;CACR,SAAS;CACT,iBAAiB;CACjB,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,OAAO;CACP,KAAK;CACL,UAAU;CACV,SAAS;CACV;;;;;AAMD,SAAgB,+BACd,aACQ;CACR,MAAM,QAAkB,EAAE;AAE1B,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,6BAA6B,EAAE;EACzE,MAAM,QAAQ,YAAY;AAC1B,MAAI,UAAU,OACZ,OAAM,KAAK,GAAG,QAAQ,IAAI,MAAM,GAAG;;AAIvC,QAAO,MAAM,KAAK,IAAI;;;;;AAMxB,SAAgB,uBACd,MACA,aACQ;AACR,QAAO,kBAAkB,KAAK,KAAK,+BAA+B,YAAY,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/font-face/index.ts
|
|
2
|
+
const FONT_FACE_KEY = "@fontFace";
|
|
3
|
+
/**
|
|
4
|
+
* Check if styles object has local @fontFace definition.
|
|
5
|
+
*/
|
|
6
|
+
function hasLocalFontFace(styles) {
|
|
7
|
+
return FONT_FACE_KEY in styles;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Extract local @fontFace from styles object.
|
|
11
|
+
* Returns null if no local font faces (fast path).
|
|
12
|
+
*/
|
|
13
|
+
function extractLocalFontFace(styles) {
|
|
14
|
+
const fontFace = styles[FONT_FACE_KEY];
|
|
15
|
+
if (!fontFace || typeof fontFace !== "object") return null;
|
|
16
|
+
return fontFace;
|
|
17
|
+
}
|
|
18
|
+
const FONT_FACE_DESCRIPTOR_MAP = {
|
|
19
|
+
fontWeight: "font-weight",
|
|
20
|
+
fontStyle: "font-style",
|
|
21
|
+
fontStretch: "font-stretch",
|
|
22
|
+
fontDisplay: "font-display",
|
|
23
|
+
unicodeRange: "unicode-range",
|
|
24
|
+
ascentOverride: "ascent-override",
|
|
25
|
+
descentOverride: "descent-override",
|
|
26
|
+
lineGapOverride: "line-gap-override",
|
|
27
|
+
sizeAdjust: "size-adjust",
|
|
28
|
+
fontFeatureSettings: "font-feature-settings",
|
|
29
|
+
fontVariationSettings: "font-variation-settings"
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Format the inner declarations of a @font-face rule (no wrapper).
|
|
33
|
+
* Used by the injector which needs selector and declarations separately.
|
|
34
|
+
*/
|
|
35
|
+
function formatFontFaceDeclarations(family, descriptors) {
|
|
36
|
+
const parts = [];
|
|
37
|
+
parts.push(`font-family: "${family}";`);
|
|
38
|
+
parts.push(`src: ${descriptors.src};`);
|
|
39
|
+
for (const [key, cssName] of Object.entries(FONT_FACE_DESCRIPTOR_MAP)) {
|
|
40
|
+
const value = descriptors[key];
|
|
41
|
+
if (value !== void 0) parts.push(`${cssName}: ${value};`);
|
|
42
|
+
}
|
|
43
|
+
return parts.join(" ");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Format a single @font-face rule as CSS.
|
|
47
|
+
*/
|
|
48
|
+
function formatFontFaceRule(family, descriptors) {
|
|
49
|
+
return `@font-face { ${formatFontFaceDeclarations(family, descriptors)} }`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate a content hash for deduplication of a single font-face rule.
|
|
53
|
+
*/
|
|
54
|
+
function fontFaceContentHash(family, descriptors) {
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
family,
|
|
57
|
+
...descriptors
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { extractLocalFontFace, fontFaceContentHash, formatFontFaceDeclarations, formatFontFaceRule, hasLocalFontFace };
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/font-face/index.ts"],"sourcesContent":["/**\n * Font Face Utilities\n *\n * Utilities for extracting and processing CSS @font-face definitions in styles.\n * Font-face rules are permanent once injected and do not need cleanup.\n */\n\nimport type { FontFaceDescriptors, FontFaceInput } from '../injector/types';\nimport type { Styles } from '../styles/types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst FONT_FACE_KEY = '@fontFace';\n\n// ============================================================================\n// Extraction Functions\n// ============================================================================\n\n/**\n * Check if styles object has local @fontFace definition.\n */\nexport function hasLocalFontFace(styles: Styles): boolean {\n return FONT_FACE_KEY in styles;\n}\n\n/**\n * Extract local @fontFace from styles object.\n * Returns null if no local font faces (fast path).\n */\nexport function extractLocalFontFace(\n styles: Styles,\n): Record<string, FontFaceInput> | null {\n const fontFace = styles[FONT_FACE_KEY];\n if (!fontFace || typeof fontFace !== 'object') {\n return null;\n }\n return fontFace as Record<string, FontFaceInput>;\n}\n\n// ============================================================================\n// CSS Formatting\n// ============================================================================\n\nconst FONT_FACE_DESCRIPTOR_MAP: Record<string, string> = {\n fontWeight: 'font-weight',\n fontStyle: 'font-style',\n fontStretch: 'font-stretch',\n fontDisplay: 'font-display',\n unicodeRange: 'unicode-range',\n ascentOverride: 'ascent-override',\n descentOverride: 'descent-override',\n lineGapOverride: 'line-gap-override',\n sizeAdjust: 'size-adjust',\n fontFeatureSettings: 'font-feature-settings',\n fontVariationSettings: 'font-variation-settings',\n};\n\n/**\n * Format the inner declarations of a @font-face rule (no wrapper).\n * Used by the injector which needs selector and declarations separately.\n */\nexport function formatFontFaceDeclarations(\n family: string,\n descriptors: FontFaceDescriptors,\n): string {\n const parts: string[] = [];\n\n parts.push(`font-family: \"${family}\";`);\n parts.push(`src: ${descriptors.src};`);\n\n for (const [key, cssName] of Object.entries(FONT_FACE_DESCRIPTOR_MAP)) {\n const value = descriptors[key as keyof FontFaceDescriptors];\n if (value !== undefined) {\n parts.push(`${cssName}: ${value};`);\n }\n }\n\n return parts.join(' ');\n}\n\n/**\n * Format a single @font-face rule as CSS.\n */\nexport function formatFontFaceRule(\n family: string,\n descriptors: FontFaceDescriptors,\n): string {\n return `@font-face { ${formatFontFaceDeclarations(family, descriptors)} }`;\n}\n\n/**\n * Format all @font-face rules for a family (handles single or array form).\n * Returns an array of CSS strings, one per rule.\n */\nexport function formatFontFaceRules(\n family: string,\n input: FontFaceInput,\n): string[] {\n const descriptors = Array.isArray(input) ? input : [input];\n return descriptors.map((desc) => formatFontFaceRule(family, desc));\n}\n\n/**\n * Generate a content hash for deduplication of a single font-face rule.\n */\nexport function fontFaceContentHash(\n family: string,\n descriptors: FontFaceDescriptors,\n): string {\n return JSON.stringify({ family, ...descriptors });\n}\n"],"mappings":";AAcA,MAAM,gBAAgB;;;;AAStB,SAAgB,iBAAiB,QAAyB;AACxD,QAAO,iBAAiB;;;;;;AAO1B,SAAgB,qBACd,QACsC;CACtC,MAAM,WAAW,OAAO;AACxB,KAAI,CAAC,YAAY,OAAO,aAAa,SACnC,QAAO;AAET,QAAO;;AAOT,MAAM,2BAAmD;CACvD,YAAY;CACZ,WAAW;CACX,aAAa;CACb,aAAa;CACb,cAAc;CACd,gBAAgB;CAChB,iBAAiB;CACjB,iBAAiB;CACjB,YAAY;CACZ,qBAAqB;CACrB,uBAAuB;CACxB;;;;;AAMD,SAAgB,2BACd,QACA,aACQ;CACR,MAAM,QAAkB,EAAE;AAE1B,OAAM,KAAK,iBAAiB,OAAO,IAAI;AACvC,OAAM,KAAK,QAAQ,YAAY,IAAI,GAAG;AAEtC,MAAK,MAAM,CAAC,KAAK,YAAY,OAAO,QAAQ,yBAAyB,EAAE;EACrE,MAAM,QAAQ,YAAY;AAC1B,MAAI,UAAU,OACZ,OAAM,KAAK,GAAG,QAAQ,IAAI,MAAM,GAAG;;AAIvC,QAAO,MAAM,KAAK,IAAI;;;;;AAMxB,SAAgB,mBACd,QACA,aACQ;AACR,QAAO,gBAAgB,2BAA2B,QAAQ,YAAY,CAAC;;;;;AAkBzE,SAAgB,oBACd,QACA,aACQ;AACR,QAAO,KAAK,UAAU;EAAE;EAAQ,GAAG;EAAa,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getRegisteredSSRCollector } from "../ssr/ssr-collector-ref.js";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/resolve-ssr-collector.ts
|
|
4
|
+
/**
|
|
5
|
+
* Resolve the SSR collector from React context or AsyncLocalStorage.
|
|
6
|
+
* Returns null on the client (no collector available).
|
|
7
|
+
*/
|
|
8
|
+
function resolveSSRCollector(reactContext) {
|
|
9
|
+
if (reactContext) return reactContext;
|
|
10
|
+
return getRegisteredSSRCollector();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { resolveSSRCollector };
|
|
15
|
+
//# sourceMappingURL=resolve-ssr-collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-ssr-collector.js","names":[],"sources":["../../src/hooks/resolve-ssr-collector.ts"],"sourcesContent":["import type { ServerStyleCollector } from '../ssr/collector';\nimport { getRegisteredSSRCollector } from '../ssr/ssr-collector-ref';\n\n/**\n * Resolve the SSR collector from React context or AsyncLocalStorage.\n * Returns null on the client (no collector available).\n */\nexport function resolveSSRCollector(\n reactContext: ServerStyleCollector | null,\n): ServerStyleCollector | null {\n if (reactContext) return reactContext;\n return getRegisteredSSRCollector();\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,oBACd,cAC6B;AAC7B,KAAI,aAAc,QAAO;AACzB,QAAO,2BAA2B"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CounterStyleDescriptors } from "../injector/types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useCounterStyle.d.ts
|
|
4
|
+
interface UseCounterStyleOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
root?: Document | ShadowRoot;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Hook to inject a CSS @counter-style rule and return the generated name.
|
|
10
|
+
* Permanent — no cleanup on unmount. Deduplicates by name.
|
|
11
|
+
*
|
|
12
|
+
* @example Basic usage
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function EmojiList() {
|
|
15
|
+
* const styleName = useCounterStyle({
|
|
16
|
+
* system: 'cyclic',
|
|
17
|
+
* symbols: '"👍"',
|
|
18
|
+
* suffix: '" "',
|
|
19
|
+
* }, { name: 'thumbs' });
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <ol style={{ listStyleType: styleName }}>
|
|
23
|
+
* <li>First</li>
|
|
24
|
+
* <li>Second</li>
|
|
25
|
+
* </ol>
|
|
26
|
+
* );
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Factory function with dependencies
|
|
31
|
+
* ```tsx
|
|
32
|
+
* function DynamicList({ marker }: { marker: string }) {
|
|
33
|
+
* const styleName = useCounterStyle(
|
|
34
|
+
* () => ({
|
|
35
|
+
* system: 'cyclic',
|
|
36
|
+
* symbols: `"${marker}"`,
|
|
37
|
+
* suffix: '" "',
|
|
38
|
+
* }),
|
|
39
|
+
* [marker],
|
|
40
|
+
* );
|
|
41
|
+
*
|
|
42
|
+
* return <ol style={{ listStyleType: styleName }}>...</ol>;
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare function useCounterStyle(descriptors: CounterStyleDescriptors, options?: UseCounterStyleOptions): string;
|
|
47
|
+
declare function useCounterStyle(factory: () => CounterStyleDescriptors, deps: readonly unknown[], options?: UseCounterStyleOptions): string;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { useCounterStyle };
|
|
50
|
+
//# sourceMappingURL=useCounterStyle.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { formatCounterStyleRule } from "../counter-style/index.js";
|
|
2
|
+
import { getGlobalInjector } from "../config.js";
|
|
3
|
+
import { resolveSSRCollector } from "./resolve-ssr-collector.js";
|
|
4
|
+
import { TastySSRContext } from "../ssr/context.js";
|
|
5
|
+
import { useContext, useInsertionEffect, useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
//#region src/hooks/useCounterStyle.ts
|
|
8
|
+
let clientCounterStyleCounter = 0;
|
|
9
|
+
function useCounterStyle(descriptorsOrFactory, depsOrOptions, options) {
|
|
10
|
+
const ssrCollector = resolveSSRCollector(useContext(TastySSRContext));
|
|
11
|
+
const isFactory = typeof descriptorsOrFactory === "function";
|
|
12
|
+
const deps = isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : void 0;
|
|
13
|
+
const opts = isFactory ? options : depsOrOptions;
|
|
14
|
+
const inputKey = useMemo(() => isFactory ? null : JSON.stringify(descriptorsOrFactory), [isFactory ? null : descriptorsOrFactory]);
|
|
15
|
+
const descriptorsData = useMemo(() => {
|
|
16
|
+
const descriptors = isFactory ? descriptorsOrFactory() : descriptorsOrFactory;
|
|
17
|
+
if (!descriptors || !descriptors.system) return null;
|
|
18
|
+
return descriptors;
|
|
19
|
+
}, isFactory ? deps ?? [] : [inputKey]);
|
|
20
|
+
const name = useMemo(() => {
|
|
21
|
+
if (!descriptorsData) return "";
|
|
22
|
+
if (ssrCollector) {
|
|
23
|
+
const actualName = ssrCollector.allocateCounterStyleName(opts?.name);
|
|
24
|
+
const css = formatCounterStyleRule(actualName, descriptorsData);
|
|
25
|
+
ssrCollector.collectCounterStyle(actualName, css);
|
|
26
|
+
return actualName;
|
|
27
|
+
}
|
|
28
|
+
return opts?.name ?? `cs${clientCounterStyleCounter++}`;
|
|
29
|
+
}, [
|
|
30
|
+
descriptorsData,
|
|
31
|
+
opts?.name,
|
|
32
|
+
ssrCollector
|
|
33
|
+
]);
|
|
34
|
+
useInsertionEffect(() => {
|
|
35
|
+
if (!descriptorsData || !name) return;
|
|
36
|
+
getGlobalInjector().counterStyle(name, descriptorsData, { root: opts?.root });
|
|
37
|
+
}, [
|
|
38
|
+
descriptorsData,
|
|
39
|
+
name,
|
|
40
|
+
opts?.root
|
|
41
|
+
]);
|
|
42
|
+
return name;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { useCounterStyle };
|
|
47
|
+
//# sourceMappingURL=useCounterStyle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCounterStyle.js","names":[],"sources":["../../src/hooks/useCounterStyle.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo } from 'react';\n\nimport { getGlobalInjector } from '../config';\nimport { formatCounterStyleRule } from '../counter-style';\nimport type { CounterStyleDescriptors } from '../injector/types';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\n\ninterface UseCounterStyleOptions {\n name?: string;\n root?: Document | ShadowRoot;\n}\n\nlet clientCounterStyleCounter = 0;\n\n/**\n * Hook to inject a CSS @counter-style rule and return the generated name.\n * Permanent — no cleanup on unmount. Deduplicates by name.\n *\n * @example Basic usage\n * ```tsx\n * function EmojiList() {\n * const styleName = useCounterStyle({\n * system: 'cyclic',\n * symbols: '\"👍\"',\n * suffix: '\" \"',\n * }, { name: 'thumbs' });\n *\n * return (\n * <ol style={{ listStyleType: styleName }}>\n * <li>First</li>\n * <li>Second</li>\n * </ol>\n * );\n * }\n * ```\n *\n * @example Factory function with dependencies\n * ```tsx\n * function DynamicList({ marker }: { marker: string }) {\n * const styleName = useCounterStyle(\n * () => ({\n * system: 'cyclic',\n * symbols: `\"${marker}\"`,\n * suffix: '\" \"',\n * }),\n * [marker],\n * );\n *\n * return <ol style={{ listStyleType: styleName }}>...</ol>;\n * }\n * ```\n */\n\n// Overload 1: Static descriptors\nexport function useCounterStyle(\n descriptors: CounterStyleDescriptors,\n options?: UseCounterStyleOptions,\n): string;\n\n// Overload 2: Factory function with dependencies\nexport function useCounterStyle(\n factory: () => CounterStyleDescriptors,\n deps: readonly unknown[],\n options?: UseCounterStyleOptions,\n): string;\n\n// Implementation\nexport function useCounterStyle(\n descriptorsOrFactory:\n | CounterStyleDescriptors\n | (() => CounterStyleDescriptors),\n depsOrOptions?: readonly unknown[] | UseCounterStyleOptions,\n options?: UseCounterStyleOptions,\n): string {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n const isFactory = typeof descriptorsOrFactory === 'function';\n\n const deps =\n isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : undefined;\n const opts = isFactory\n ? options\n : (depsOrOptions as UseCounterStyleOptions | undefined);\n\n // Stable key for the static path — avoids re-triggering when caller\n // passes an inline object literal with the same content.\n const inputKey = useMemo(\n () => (isFactory ? null : JSON.stringify(descriptorsOrFactory)),\n [isFactory ? null : descriptorsOrFactory],\n );\n\n const descriptorsData = useMemo(\n () => {\n const descriptors = isFactory\n ? (descriptorsOrFactory as () => CounterStyleDescriptors)()\n : (descriptorsOrFactory as CounterStyleDescriptors);\n\n if (!descriptors || !descriptors.system) {\n return null;\n }\n\n return descriptors;\n },\n\n isFactory ? (deps ?? []) : [inputKey],\n );\n\n const name = useMemo(() => {\n if (!descriptorsData) {\n return '';\n }\n\n // SSR path: format and collect, return name without DOM injection\n if (ssrCollector) {\n const actualName = ssrCollector.allocateCounterStyleName(opts?.name);\n const css = formatCounterStyleRule(actualName, descriptorsData);\n ssrCollector.collectCounterStyle(actualName, css);\n return actualName;\n }\n\n // Client path: return the name (injection happens in useInsertionEffect)\n return opts?.name ?? `cs${clientCounterStyleCounter++}`;\n }, [descriptorsData, opts?.name, ssrCollector]);\n\n // Client path: inject via DOM\n useInsertionEffect(() => {\n if (!descriptorsData || !name) return;\n\n const injector = getGlobalInjector();\n injector.counterStyle(name, descriptorsData, { root: opts?.root });\n }, [descriptorsData, name, opts?.root]);\n\n return name;\n}\n"],"mappings":";;;;;;;AAaA,IAAI,4BAA4B;AAuDhC,SAAgB,gBACd,sBAGA,eACA,SACQ;CAER,MAAM,eAAe,oBADG,WAAW,gBAAgB,CACM;CAEzD,MAAM,YAAY,OAAO,yBAAyB;CAElD,MAAM,OACJ,aAAa,MAAM,QAAQ,cAAc,GAAG,gBAAgB;CAC9D,MAAM,OAAO,YACT,UACC;CAIL,MAAM,WAAW,cACR,YAAY,OAAO,KAAK,UAAU,qBAAqB,EAC9D,CAAC,YAAY,OAAO,qBAAqB,CAC1C;CAED,MAAM,kBAAkB,cAChB;EACJ,MAAM,cAAc,YACf,sBAAwD,GACxD;AAEL,MAAI,CAAC,eAAe,CAAC,YAAY,OAC/B,QAAO;AAGT,SAAO;IAGT,YAAa,QAAQ,EAAE,GAAI,CAAC,SAAS,CACtC;CAED,MAAM,OAAO,cAAc;AACzB,MAAI,CAAC,gBACH,QAAO;AAIT,MAAI,cAAc;GAChB,MAAM,aAAa,aAAa,yBAAyB,MAAM,KAAK;GACpE,MAAM,MAAM,uBAAuB,YAAY,gBAAgB;AAC/D,gBAAa,oBAAoB,YAAY,IAAI;AACjD,UAAO;;AAIT,SAAO,MAAM,QAAQ,KAAK;IACzB;EAAC;EAAiB,MAAM;EAAM;EAAa,CAAC;AAG/C,0BAAyB;AACvB,MAAI,CAAC,mBAAmB,CAAC,KAAM;AAG/B,EADiB,mBAAmB,CAC3B,aAAa,MAAM,iBAAiB,EAAE,MAAM,MAAM,MAAM,CAAC;IACjE;EAAC;EAAiB;EAAM,MAAM;EAAK,CAAC;AAEvC,QAAO"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { FontFaceInput } from "../injector/types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/useFontFace.d.ts
|
|
4
|
+
interface UseFontFaceOptions {
|
|
5
|
+
root?: Document | ShadowRoot;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Hook to inject CSS @font-face rules.
|
|
9
|
+
* Permanent — no cleanup on unmount. Deduplicates by content hash.
|
|
10
|
+
*
|
|
11
|
+
* @param family - The font-family name
|
|
12
|
+
* @param input - Single descriptor object or array of descriptors (for multiple weights/styles)
|
|
13
|
+
* @param options - Optional settings (e.g. Shadow DOM root)
|
|
14
|
+
*
|
|
15
|
+
* @example Single weight
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function App() {
|
|
18
|
+
* useFontFace('Brand Sans', {
|
|
19
|
+
* src: 'url("/fonts/brand-sans.woff2") format("woff2")',
|
|
20
|
+
* fontWeight: '400 700',
|
|
21
|
+
* fontDisplay: 'swap',
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example Multiple weights
|
|
29
|
+
* ```tsx
|
|
30
|
+
* function App() {
|
|
31
|
+
* useFontFace('Brand Sans', [
|
|
32
|
+
* { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
|
|
33
|
+
* { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
|
|
34
|
+
* ]);
|
|
35
|
+
*
|
|
36
|
+
* return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function useFontFace(family: string, input: FontFaceInput, options?: UseFontFaceOptions): void;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { useFontFace };
|
|
43
|
+
//# sourceMappingURL=useFontFace.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { fontFaceContentHash, formatFontFaceRule } from "../font-face/index.js";
|
|
2
|
+
import { getGlobalInjector } from "../config.js";
|
|
3
|
+
import { resolveSSRCollector } from "./resolve-ssr-collector.js";
|
|
4
|
+
import { TastySSRContext } from "../ssr/context.js";
|
|
5
|
+
import { useContext, useInsertionEffect, useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
//#region src/hooks/useFontFace.ts
|
|
8
|
+
/**
|
|
9
|
+
* Hook to inject CSS @font-face rules.
|
|
10
|
+
* Permanent — no cleanup on unmount. Deduplicates by content hash.
|
|
11
|
+
*
|
|
12
|
+
* @param family - The font-family name
|
|
13
|
+
* @param input - Single descriptor object or array of descriptors (for multiple weights/styles)
|
|
14
|
+
* @param options - Optional settings (e.g. Shadow DOM root)
|
|
15
|
+
*
|
|
16
|
+
* @example Single weight
|
|
17
|
+
* ```tsx
|
|
18
|
+
* function App() {
|
|
19
|
+
* useFontFace('Brand Sans', {
|
|
20
|
+
* src: 'url("/fonts/brand-sans.woff2") format("woff2")',
|
|
21
|
+
* fontWeight: '400 700',
|
|
22
|
+
* fontDisplay: 'swap',
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example Multiple weights
|
|
30
|
+
* ```tsx
|
|
31
|
+
* function App() {
|
|
32
|
+
* useFontFace('Brand Sans', [
|
|
33
|
+
* { src: 'url("/fonts/brand-regular.woff2") format("woff2")', fontWeight: 400, fontDisplay: 'swap' },
|
|
34
|
+
* { src: 'url("/fonts/brand-bold.woff2") format("woff2")', fontWeight: 700, fontDisplay: 'swap' },
|
|
35
|
+
* ]);
|
|
36
|
+
*
|
|
37
|
+
* return <div style={{ fontFamily: '"Brand Sans", sans-serif' }}>Hello</div>;
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function useFontFace(family, input, options) {
|
|
42
|
+
const ssrCollector = resolveSSRCollector(useContext(TastySSRContext));
|
|
43
|
+
const inputKey = useMemo(() => JSON.stringify(input), [input]);
|
|
44
|
+
useMemo(() => {
|
|
45
|
+
if (!ssrCollector || !family) return;
|
|
46
|
+
const descriptors = Array.isArray(input) ? input : [input];
|
|
47
|
+
for (const desc of descriptors) {
|
|
48
|
+
const hash = fontFaceContentHash(family, desc);
|
|
49
|
+
const css = formatFontFaceRule(family, desc);
|
|
50
|
+
ssrCollector.collectFontFace(hash, css);
|
|
51
|
+
}
|
|
52
|
+
}, [
|
|
53
|
+
ssrCollector,
|
|
54
|
+
family,
|
|
55
|
+
inputKey
|
|
56
|
+
]);
|
|
57
|
+
useInsertionEffect(() => {
|
|
58
|
+
if (!family) return;
|
|
59
|
+
const injector = getGlobalInjector();
|
|
60
|
+
const descriptors = Array.isArray(input) ? input : [input];
|
|
61
|
+
for (const desc of descriptors) injector.fontFace(family, desc, { root: options?.root });
|
|
62
|
+
}, [
|
|
63
|
+
family,
|
|
64
|
+
inputKey,
|
|
65
|
+
options?.root
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { useFontFace };
|
|
71
|
+
//# sourceMappingURL=useFontFace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFontFace.js","names":[],"sources":["../../src/hooks/useFontFace.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo } from 'react';\n\nimport { getGlobalInjector } from '../config';\nimport { fontFaceContentHash, formatFontFaceRule } from '../font-face';\nimport type { FontFaceDescriptors, FontFaceInput } from '../injector/types';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\n\ninterface UseFontFaceOptions {\n root?: Document | ShadowRoot;\n}\n\n/**\n * Hook to inject CSS @font-face rules.\n * Permanent — no cleanup on unmount. Deduplicates by content hash.\n *\n * @param family - The font-family name\n * @param input - Single descriptor object or array of descriptors (for multiple weights/styles)\n * @param options - Optional settings (e.g. Shadow DOM root)\n *\n * @example Single weight\n * ```tsx\n * function App() {\n * useFontFace('Brand Sans', {\n * src: 'url(\"/fonts/brand-sans.woff2\") format(\"woff2\")',\n * fontWeight: '400 700',\n * fontDisplay: 'swap',\n * });\n *\n * return <div style={{ fontFamily: '\"Brand Sans\", sans-serif' }}>Hello</div>;\n * }\n * ```\n *\n * @example Multiple weights\n * ```tsx\n * function App() {\n * useFontFace('Brand Sans', [\n * { src: 'url(\"/fonts/brand-regular.woff2\") format(\"woff2\")', fontWeight: 400, fontDisplay: 'swap' },\n * { src: 'url(\"/fonts/brand-bold.woff2\") format(\"woff2\")', fontWeight: 700, fontDisplay: 'swap' },\n * ]);\n *\n * return <div style={{ fontFamily: '\"Brand Sans\", sans-serif' }}>Hello</div>;\n * }\n * ```\n */\nexport function useFontFace(\n family: string,\n input: FontFaceInput,\n options?: UseFontFaceOptions,\n): void {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n const inputKey = useMemo(() => JSON.stringify(input), [input]);\n\n // SSR path: collect @font-face CSS during render\n useMemo(() => {\n if (!ssrCollector || !family) return;\n\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n ssrCollector.collectFontFace(hash, css);\n }\n }, [ssrCollector, family, inputKey]);\n\n // Client path: inject via DOM\n useInsertionEffect(() => {\n if (!family) return;\n\n const injector = getGlobalInjector();\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n\n for (const desc of descriptors) {\n injector.fontFace(family, desc, { root: options?.root });\n }\n }, [family, inputKey, options?.root]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,YACd,QACA,OACA,SACM;CAEN,MAAM,eAAe,oBADG,WAAW,gBAAgB,CACM;CAEzD,MAAM,WAAW,cAAc,KAAK,UAAU,MAAM,EAAE,CAAC,MAAM,CAAC;AAG9D,eAAc;AACZ,MAAI,CAAC,gBAAgB,CAAC,OAAQ;EAE9B,MAAM,cAAqC,MAAM,QAAQ,MAAM,GAC3D,QACA,CAAC,MAAM;AAEX,OAAK,MAAM,QAAQ,aAAa;GAC9B,MAAM,OAAO,oBAAoB,QAAQ,KAAK;GAC9C,MAAM,MAAM,mBAAmB,QAAQ,KAAK;AAC5C,gBAAa,gBAAgB,MAAM,IAAI;;IAExC;EAAC;EAAc;EAAQ;EAAS,CAAC;AAGpC,0BAAyB;AACvB,MAAI,CAAC,OAAQ;EAEb,MAAM,WAAW,mBAAmB;EACpC,MAAM,cAAqC,MAAM,QAAQ,MAAM,GAC3D,QACA,CAAC,MAAM;AAEX,OAAK,MAAM,QAAQ,YACjB,UAAS,SAAS,QAAQ,MAAM,EAAE,MAAM,SAAS,MAAM,CAAC;IAEzD;EAAC;EAAQ;EAAU,SAAS;EAAK,CAAC"}
|
|
@@ -3,16 +3,12 @@ import { getConfig } from "../config.js";
|
|
|
3
3
|
import { injectGlobal } from "../injector/index.js";
|
|
4
4
|
import { resolveRecipes } from "../utils/resolve-recipes.js";
|
|
5
5
|
import { collectAutoInferredProperties } from "../ssr/collect-auto-properties.js";
|
|
6
|
+
import { resolveSSRCollector } from "./resolve-ssr-collector.js";
|
|
6
7
|
import { TastySSRContext } from "../ssr/context.js";
|
|
7
|
-
import { getRegisteredSSRCollector } from "../ssr/ssr-collector-ref.js";
|
|
8
8
|
import { formatGlobalRules } from "../ssr/format-global-rules.js";
|
|
9
9
|
import { useContext, useInsertionEffect, useMemo, useRef } from "react";
|
|
10
10
|
|
|
11
11
|
//#region src/hooks/useGlobalStyles.ts
|
|
12
|
-
function resolveSSRCollector(reactContext) {
|
|
13
|
-
if (reactContext) return reactContext;
|
|
14
|
-
return getRegisteredSSRCollector();
|
|
15
|
-
}
|
|
16
12
|
/**
|
|
17
13
|
* Hook to inject global styles for a given selector.
|
|
18
14
|
* Useful for styling elements by selector without generating classNames.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGlobalStyles.js","names":[],"sources":["../../src/hooks/useGlobalStyles.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { getConfig } from '../config';\nimport { injectGlobal } from '../injector';\nimport type { StyleResult } from '../pipeline';\nimport { renderStyles } from '../pipeline';\nimport { collectAutoInferredProperties } from '../ssr/collect-auto-properties';\nimport
|
|
1
|
+
{"version":3,"file":"useGlobalStyles.js","names":[],"sources":["../../src/hooks/useGlobalStyles.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { getConfig } from '../config';\nimport { injectGlobal } from '../injector';\nimport type { StyleResult } from '../pipeline';\nimport { renderStyles } from '../pipeline';\nimport { collectAutoInferredProperties } from '../ssr/collect-auto-properties';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\nimport { formatGlobalRules } from '../ssr/format-global-rules';\nimport type { Styles } from '../styles/types';\nimport { resolveRecipes } from '../utils/resolve-recipes';\n\n/**\n * Hook to inject global styles for a given selector.\n * Useful for styling elements by selector without generating classNames.\n *\n * SSR-aware: when a ServerStyleCollector is available, CSS is collected\n * during the render phase instead of being injected into the DOM.\n *\n * @param selector - CSS selector to apply styles to (e.g., '.my-class', ':root', 'body')\n * @param styles - Tasty styles object\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * useGlobalStyles('.card', {\n * padding: '2x',\n * radius: '1r',\n * fill: '#white',\n * });\n *\n * return <div className=\"card\">Content</div>;\n * }\n * ```\n */\nexport function useGlobalStyles(selector: string, styles?: Styles): void {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n const disposeRef = useRef<(() => void) | null>(null);\n\n // Resolve recipes before rendering (zero overhead if no recipes configured)\n const resolvedStyles = useMemo(() => {\n if (!styles) return styles;\n return resolveRecipes(styles);\n }, [styles]);\n\n // Render styles with the provided selector\n // Note: renderStyles overload with selector string returns StyleResult[] directly\n const styleResults = useMemo((): StyleResult[] => {\n if (!resolvedStyles) return [];\n\n if (!selector) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Tasty] useGlobalStyles: selector is required and cannot be empty. ' +\n 'Styles will not be injected.',\n );\n }\n return [];\n }\n\n const result = renderStyles(resolvedStyles, selector);\n return result as StyleResult[];\n }, [resolvedStyles, selector]);\n\n // SSR path: collect CSS during render\n useMemo(() => {\n if (!ssrCollector || styleResults.length === 0) return;\n\n ssrCollector.collectInternals();\n\n const css = formatGlobalRules(styleResults);\n if (css) {\n const key = `global:${selector}:${css.length}:${css.slice(0, 64)}`;\n ssrCollector.collectGlobalStyles(key, css);\n }\n\n if (getConfig().autoPropertyTypes !== false) {\n collectAutoInferredProperties(styleResults, ssrCollector, resolvedStyles);\n }\n }, [ssrCollector, styleResults, selector]);\n\n // Client path: inject via DOM\n useInsertionEffect(() => {\n disposeRef.current?.();\n\n if (styleResults.length > 0) {\n const { dispose } = injectGlobal(styleResults);\n disposeRef.current = dispose;\n } else {\n disposeRef.current = null;\n }\n\n return () => {\n disposeRef.current?.();\n disposeRef.current = null;\n };\n }, [styleResults]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,gBAAgB,UAAkB,QAAuB;CAEvE,MAAM,eAAe,oBADG,WAAW,gBAAgB,CACM;CAEzD,MAAM,aAAa,OAA4B,KAAK;CAGpD,MAAM,iBAAiB,cAAc;AACnC,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,eAAe,OAAO;IAC5B,CAAC,OAAO,CAAC;CAIZ,MAAM,eAAe,cAA6B;AAChD,MAAI,CAAC,eAAgB,QAAO,EAAE;AAE9B,MAAI,CAAC,UAAU;AAEX,WAAQ,KACN,kGAED;AAEH,UAAO,EAAE;;AAIX,SADe,aAAa,gBAAgB,SAAS;IAEpD,CAAC,gBAAgB,SAAS,CAAC;AAG9B,eAAc;AACZ,MAAI,CAAC,gBAAgB,aAAa,WAAW,EAAG;AAEhD,eAAa,kBAAkB;EAE/B,MAAM,MAAM,kBAAkB,aAAa;AAC3C,MAAI,KAAK;GACP,MAAM,MAAM,UAAU,SAAS,GAAG,IAAI,OAAO,GAAG,IAAI,MAAM,GAAG,GAAG;AAChE,gBAAa,oBAAoB,KAAK,IAAI;;AAG5C,MAAI,WAAW,CAAC,sBAAsB,MACpC,+BAA8B,cAAc,cAAc,eAAe;IAE1E;EAAC;EAAc;EAAc;EAAS,CAAC;AAG1C,0BAAyB;AACvB,aAAW,WAAW;AAEtB,MAAI,aAAa,SAAS,GAAG;GAC3B,MAAM,EAAE,YAAY,aAAa,aAAa;AAC9C,cAAW,UAAU;QAErB,YAAW,UAAU;AAGvB,eAAa;AACX,cAAW,WAAW;AACtB,cAAW,UAAU;;IAEtB,CAAC,aAAa,CAAC"}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { keyframes } from "../injector/index.js";
|
|
2
|
+
import { resolveSSRCollector } from "./resolve-ssr-collector.js";
|
|
2
3
|
import { TastySSRContext } from "../ssr/context.js";
|
|
3
4
|
import { formatKeyframesCSS } from "../ssr/format-keyframes.js";
|
|
4
|
-
import { getRegisteredSSRCollector } from "../ssr/ssr-collector-ref.js";
|
|
5
5
|
import { useContext, useInsertionEffect, useMemo, useRef } from "react";
|
|
6
6
|
|
|
7
7
|
//#region src/hooks/useKeyframes.ts
|
|
8
|
-
function resolveSSRCollector(reactContext) {
|
|
9
|
-
if (reactContext) return reactContext;
|
|
10
|
-
return getRegisteredSSRCollector();
|
|
11
|
-
}
|
|
12
8
|
function useKeyframes(stepsOrFactory, depsOrOptions, options) {
|
|
13
9
|
const ssrCollector = resolveSSRCollector(useContext(TastySSRContext));
|
|
14
10
|
const isFactory = typeof stepsOrFactory === "function";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useKeyframes.js","names":[],"sources":["../../src/hooks/useKeyframes.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { keyframes } from '../injector';\nimport type { KeyframesResult, KeyframesSteps } from '../injector/types';\nimport
|
|
1
|
+
{"version":3,"file":"useKeyframes.js","names":[],"sources":["../../src/hooks/useKeyframes.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { keyframes } from '../injector';\nimport type { KeyframesResult, KeyframesSteps } from '../injector/types';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\nimport { formatKeyframesCSS } from '../ssr/format-keyframes';\n\ninterface UseKeyframesOptions {\n name?: string;\n root?: Document | ShadowRoot;\n}\n\n/**\n * Hook to inject CSS @keyframes and return the generated animation name.\n * Handles keyframes injection with proper cleanup on unmount or dependency changes.\n *\n * @example Basic usage - steps object is the dependency\n * ```tsx\n * function MyComponent() {\n * const bounce = useKeyframes({\n * '0%': { transform: 'scale(1)' },\n * '50%': { transform: 'scale(1.1)' },\n * '100%': { transform: 'scale(1)' },\n * });\n *\n * return <div style={{ animation: `${bounce} 1s infinite` }}>Bouncing</div>;\n * }\n * ```\n *\n * @example With custom name\n * ```tsx\n * function MyComponent() {\n * const fadeIn = useKeyframes(\n * { from: { opacity: 0 }, to: { opacity: 1 } },\n * { name: 'fadeIn' }\n * );\n *\n * return <div style={{ animation: `${fadeIn} 0.3s ease-out` }}>Fading in</div>;\n * }\n * ```\n *\n * @example Factory function with dependencies\n * ```tsx\n * function MyComponent({ scale }: { scale: number }) {\n * const pulse = useKeyframes(\n * () => ({\n * '0%': { transform: 'scale(1)' },\n * '100%': { transform: `scale(${scale})` },\n * }),\n * [scale]\n * );\n *\n * return <div style={{ animation: `${pulse} 1s infinite` }}>Pulsing</div>;\n * }\n * ```\n */\n\n// Overload 1: Static steps object\nexport function useKeyframes(\n steps: KeyframesSteps,\n options?: UseKeyframesOptions,\n): string;\n\n// Overload 2: Factory function with dependencies\nexport function useKeyframes(\n factory: () => KeyframesSteps,\n deps: readonly unknown[],\n options?: UseKeyframesOptions,\n): string;\n\n// Implementation\nexport function useKeyframes(\n stepsOrFactory: KeyframesSteps | (() => KeyframesSteps),\n depsOrOptions?: readonly unknown[] | UseKeyframesOptions,\n options?: UseKeyframesOptions,\n): string {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n // Detect which overload is being used\n const isFactory = typeof stepsOrFactory === 'function';\n\n // Parse arguments based on overload\n const deps =\n isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : undefined;\n const opts = isFactory\n ? options\n : (depsOrOptions as UseKeyframesOptions | undefined);\n\n // Memoize the keyframes steps to get a stable reference\n const stepsData = useMemo(\n () => {\n const steps = isFactory\n ? (stepsOrFactory as () => KeyframesSteps)()\n : (stepsOrFactory as KeyframesSteps);\n\n if (!steps || Object.keys(steps).length === 0) {\n return null;\n }\n\n return steps;\n },\n\n isFactory ? (deps ?? []) : [stepsOrFactory],\n );\n\n // Store keyframes results for cleanup (client only)\n const renderResultRef = useRef<KeyframesResult | null>(null);\n const effectResultRef = useRef<KeyframesResult | null>(null);\n\n const name = useMemo(() => {\n if (!stepsData) {\n return '';\n }\n\n // SSR path: format and collect, return name without DOM injection\n if (ssrCollector) {\n const actualName = ssrCollector.allocateKeyframeName(opts?.name);\n const css = formatKeyframesCSS(actualName, stepsData);\n ssrCollector.collectKeyframes(actualName, css);\n return actualName;\n }\n\n // Client path: inject keyframes synchronously for immediate name availability\n renderResultRef.current?.dispose();\n renderResultRef.current = null;\n\n const result = keyframes(stepsData, {\n name: opts?.name,\n root: opts?.root,\n });\n\n renderResultRef.current = result;\n\n return result.toString();\n }, [stepsData, opts?.name, opts?.root, ssrCollector]);\n\n // Client path: handle Strict Mode double-invocation and cleanup\n useInsertionEffect(() => {\n effectResultRef.current?.dispose();\n effectResultRef.current = null;\n\n if (stepsData) {\n const result = keyframes(stepsData, {\n name: opts?.name,\n root: opts?.root,\n });\n effectResultRef.current = result;\n }\n\n return () => {\n effectResultRef.current?.dispose();\n effectResultRef.current = null;\n renderResultRef.current?.dispose();\n renderResultRef.current = null;\n };\n }, [stepsData, opts?.name, opts?.root]);\n\n return name;\n}\n"],"mappings":";;;;;;;AAwEA,SAAgB,aACd,gBACA,eACA,SACQ;CAER,MAAM,eAAe,oBADG,WAAW,gBAAgB,CACM;CAGzD,MAAM,YAAY,OAAO,mBAAmB;CAG5C,MAAM,OACJ,aAAa,MAAM,QAAQ,cAAc,GAAG,gBAAgB;CAC9D,MAAM,OAAO,YACT,UACC;CAGL,MAAM,YAAY,cACV;EACJ,MAAM,QAAQ,YACT,gBAAyC,GACzC;AAEL,MAAI,CAAC,SAAS,OAAO,KAAK,MAAM,CAAC,WAAW,EAC1C,QAAO;AAGT,SAAO;IAGT,YAAa,QAAQ,EAAE,GAAI,CAAC,eAAe,CAC5C;CAGD,MAAM,kBAAkB,OAA+B,KAAK;CAC5D,MAAM,kBAAkB,OAA+B,KAAK;CAE5D,MAAM,OAAO,cAAc;AACzB,MAAI,CAAC,UACH,QAAO;AAIT,MAAI,cAAc;GAChB,MAAM,aAAa,aAAa,qBAAqB,MAAM,KAAK;GAChE,MAAM,MAAM,mBAAmB,YAAY,UAAU;AACrD,gBAAa,iBAAiB,YAAY,IAAI;AAC9C,UAAO;;AAIT,kBAAgB,SAAS,SAAS;AAClC,kBAAgB,UAAU;EAE1B,MAAM,SAAS,UAAU,WAAW;GAClC,MAAM,MAAM;GACZ,MAAM,MAAM;GACb,CAAC;AAEF,kBAAgB,UAAU;AAE1B,SAAO,OAAO,UAAU;IACvB;EAAC;EAAW,MAAM;EAAM,MAAM;EAAM;EAAa,CAAC;AAGrD,0BAAyB;AACvB,kBAAgB,SAAS,SAAS;AAClC,kBAAgB,UAAU;AAE1B,MAAI,UAKF,iBAAgB,UAJD,UAAU,WAAW;GAClC,MAAM,MAAM;GACZ,MAAM,MAAM;GACb,CAAC;AAIJ,eAAa;AACX,mBAAgB,SAAS,SAAS;AAClC,mBAAgB,UAAU;AAC1B,mBAAgB,SAAS,SAAS;AAClC,mBAAgB,UAAU;;IAE3B;EAAC;EAAW,MAAM;EAAM,MAAM;EAAK,CAAC;AAEvC,QAAO"}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { getGlobalInjector } from "../config.js";
|
|
2
2
|
import { formatPropertyCSS } from "../ssr/format-property.js";
|
|
3
|
+
import { resolveSSRCollector } from "./resolve-ssr-collector.js";
|
|
3
4
|
import { TastySSRContext } from "../ssr/context.js";
|
|
4
|
-
import { getRegisteredSSRCollector } from "../ssr/ssr-collector-ref.js";
|
|
5
5
|
import { useContext, useInsertionEffect, useMemo } from "react";
|
|
6
6
|
|
|
7
7
|
//#region src/hooks/useProperty.ts
|
|
8
|
-
function resolveSSRCollector(reactContext) {
|
|
9
|
-
if (reactContext) return reactContext;
|
|
10
|
-
return getRegisteredSSRCollector();
|
|
11
|
-
}
|
|
12
8
|
/**
|
|
13
9
|
* Hook to register a CSS @property custom property.
|
|
14
10
|
* This enables advanced features like animating custom properties.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProperty.js","names":[],"sources":["../../src/hooks/useProperty.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo } from 'react';\n\nimport { getGlobalInjector } from '../config';\nimport
|
|
1
|
+
{"version":3,"file":"useProperty.js","names":[],"sources":["../../src/hooks/useProperty.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo } from 'react';\n\nimport { getGlobalInjector } from '../config';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\nimport { formatPropertyCSS } from '../ssr/format-property';\n\nexport interface UsePropertyOptions {\n /**\n * CSS syntax string for the property (e.g., '<color>', '<length>', '<angle>').\n * For color tokens (#name), this is auto-set to '<color>' and cannot be overridden.\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@property/syntax\n */\n syntax?: string;\n /**\n * Whether the property inherits from parent elements\n * @default true\n */\n inherits?: boolean;\n /**\n * Initial value for the property.\n * For color tokens (#name), this defaults to 'transparent' if not specified.\n */\n initialValue?: string | number;\n /**\n * Shadow root or document to inject into\n */\n root?: Document | ShadowRoot;\n}\n\n/**\n * Hook to register a CSS @property custom property.\n * This enables advanced features like animating custom properties.\n *\n * Note: @property rules are global and persistent once defined.\n * The hook ensures the property is only registered once per root.\n *\n * Accepts tasty token syntax for the property name:\n * - `$name` → defines `--name`\n * - `#name` → defines `--name-color` (auto-sets syntax: '<color>', defaults initialValue: 'transparent')\n * - `--name` → defines `--name` (legacy format)\n *\n * @param name - The property token ($name, #name) or CSS property name (--name)\n * @param options - Property configuration\n *\n * @example Basic property with token syntax\n * ```tsx\n * function Spinner() {\n * useProperty('$rotation', {\n * syntax: '<angle>',\n * inherits: false,\n * initialValue: '0deg',\n * });\n *\n * return <div className=\"spinner\" />;\n * }\n * ```\n *\n * @example Color property with token syntax (auto-sets syntax)\n * ```tsx\n * function MyComponent() {\n * useProperty('#theme', {\n * initialValue: 'red', // syntax: '<color>' is auto-set\n * });\n *\n * // Now --theme-color can be animated with CSS transitions\n * return <div style={{ '--theme-color': 'blue' } as React.CSSProperties}>Colored</div>;\n * }\n * ```\n *\n * @example Legacy format (still supported)\n * ```tsx\n * function ResizableBox() {\n * useProperty('--box-size', {\n * syntax: '<length>',\n * initialValue: '100px',\n * });\n *\n * return <div style={{ width: 'var(--box-size)' }} />;\n * }\n * ```\n */\nexport function useProperty(name: string, options?: UsePropertyOptions): void {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n // Memoize the options to create a stable dependency\n const optionsKey = useMemo(() => {\n if (!options) return '';\n return JSON.stringify({\n syntax: options.syntax,\n inherits: options.inherits,\n initialValue: options.initialValue,\n });\n }, [options?.syntax, options?.inherits, options?.initialValue]);\n\n // SSR path: collect @property CSS during render\n useMemo(() => {\n if (!ssrCollector || !name) return;\n\n ssrCollector.collectInternals();\n\n const css = formatPropertyCSS(name, {\n syntax: options?.syntax,\n inherits: options?.inherits,\n initialValue: options?.initialValue,\n });\n if (css) {\n ssrCollector.collectProperty(name, css);\n }\n }, [ssrCollector, name, optionsKey]);\n\n // Client path: inject via DOM\n useInsertionEffect(() => {\n if (!name) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(`[Tasty] useProperty: property name is required`);\n }\n return;\n }\n\n const injector = getGlobalInjector();\n\n if (injector.isPropertyDefined(name, { root: options?.root })) {\n return;\n }\n\n injector.property(name, {\n syntax: options?.syntax,\n inherits: options?.inherits,\n initialValue: options?.initialValue,\n root: options?.root,\n });\n }, [name, optionsKey, options?.root]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFA,SAAgB,YAAY,MAAc,SAAoC;CAE5E,MAAM,eAAe,oBADG,WAAW,gBAAgB,CACM;CAGzD,MAAM,aAAa,cAAc;AAC/B,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,KAAK,UAAU;GACpB,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,cAAc,QAAQ;GACvB,CAAC;IACD;EAAC,SAAS;EAAQ,SAAS;EAAU,SAAS;EAAa,CAAC;AAG/D,eAAc;AACZ,MAAI,CAAC,gBAAgB,CAAC,KAAM;AAE5B,eAAa,kBAAkB;EAE/B,MAAM,MAAM,kBAAkB,MAAM;GAClC,QAAQ,SAAS;GACjB,UAAU,SAAS;GACnB,cAAc,SAAS;GACxB,CAAC;AACF,MAAI,IACF,cAAa,gBAAgB,MAAM,IAAI;IAExC;EAAC;EAAc;EAAM;EAAW,CAAC;AAGpC,0BAAyB;AACvB,MAAI,CAAC,MAAM;AAEP,WAAQ,KAAK,iDAAiD;AAEhE;;EAGF,MAAM,WAAW,mBAAmB;AAEpC,MAAI,SAAS,kBAAkB,MAAM,EAAE,MAAM,SAAS,MAAM,CAAC,CAC3D;AAGF,WAAS,SAAS,MAAM;GACtB,QAAQ,SAAS;GACjB,UAAU,SAAS;GACnB,cAAc,SAAS;GACvB,MAAM,SAAS;GAChB,CAAC;IACD;EAAC;EAAM;EAAY,SAAS;EAAK,CAAC"}
|
package/dist/hooks/useRawCSS.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { injectRawCSS } from "../injector/index.js";
|
|
2
|
+
import { resolveSSRCollector } from "./resolve-ssr-collector.js";
|
|
2
3
|
import { TastySSRContext } from "../ssr/context.js";
|
|
3
|
-
import { getRegisteredSSRCollector } from "../ssr/ssr-collector-ref.js";
|
|
4
4
|
import { useContext, useInsertionEffect, useMemo, useRef } from "react";
|
|
5
5
|
|
|
6
6
|
//#region src/hooks/useRawCSS.ts
|
|
7
|
-
function resolveSSRCollector(reactContext) {
|
|
8
|
-
if (reactContext) return reactContext;
|
|
9
|
-
return getRegisteredSSRCollector();
|
|
10
|
-
}
|
|
11
7
|
function useRawCSS(cssOrFactory, depsOrOptions, options) {
|
|
12
8
|
const ssrCollector = resolveSSRCollector(useContext(TastySSRContext));
|
|
13
9
|
const isFactory = typeof cssOrFactory === "function";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRawCSS.js","names":[],"sources":["../../src/hooks/useRawCSS.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { injectRawCSS } from '../injector';\nimport
|
|
1
|
+
{"version":3,"file":"useRawCSS.js","names":[],"sources":["../../src/hooks/useRawCSS.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport { injectRawCSS } from '../injector';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\n\ninterface UseRawCSSOptions {\n root?: Document | ShadowRoot;\n}\n\n/**\n * Hook to inject raw CSS text directly without parsing.\n * This is a low-overhead alternative for injecting global CSS that doesn't need tasty processing.\n *\n * The CSS is inserted into a separate style element (data-tasty-raw) to avoid conflicts\n * with tasty's chunked style sheets.\n *\n * @example Static CSS string\n * ```tsx\n * function GlobalStyles() {\n * useRawCSS(`\n * body {\n * margin: 0;\n * padding: 0;\n * font-family: sans-serif;\n * }\n * `);\n *\n * return null;\n * }\n * ```\n *\n * @example Factory function with dependencies (like useMemo)\n * ```tsx\n * function ThemeStyles({ theme }: { theme: 'light' | 'dark' }) {\n * useRawCSS(() => `\n * :root {\n * --bg-color: ${theme === 'dark' ? '#1a1a1a' : '#ffffff'};\n * --text-color: ${theme === 'dark' ? '#ffffff' : '#1a1a1a'};\n * }\n * `, [theme]);\n *\n * return null;\n * }\n * ```\n *\n * @example With options\n * ```tsx\n * function ShadowStyles({ shadowRoot }) {\n * useRawCSS(() => `.scoped { color: red; }`, [], { root: shadowRoot });\n * return null;\n * }\n * ```\n */\n\n// Overload 1: Static CSS string\nexport function useRawCSS(css: string, options?: UseRawCSSOptions): void;\n\n// Overload 2: Factory function with dependencies\nexport function useRawCSS(\n factory: () => string,\n deps: readonly unknown[],\n options?: UseRawCSSOptions,\n): void;\n\n// Implementation\nexport function useRawCSS(\n cssOrFactory: string | (() => string),\n depsOrOptions?: readonly unknown[] | UseRawCSSOptions,\n options?: UseRawCSSOptions,\n): void {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n // Detect which overload is being used\n const isFactory = typeof cssOrFactory === 'function';\n\n // Parse arguments based on overload\n const deps =\n isFactory && Array.isArray(depsOrOptions) ? depsOrOptions : undefined;\n const opts = isFactory\n ? options\n : (depsOrOptions as UseRawCSSOptions | undefined);\n\n // Memoize CSS - for factory functions, use provided deps; for strings, use the string itself\n const css = useMemo(\n () =>\n isFactory ? (cssOrFactory as () => string)() : (cssOrFactory as string),\n\n isFactory ? (deps ?? []) : [cssOrFactory],\n );\n\n // SSR path: collect raw CSS during render\n useMemo(() => {\n if (!ssrCollector || !css.trim()) return;\n\n const key = `raw:${css.length}:${css.slice(0, 64)}`;\n ssrCollector.collectRawCSS(key, css);\n }, [ssrCollector, css]);\n\n const disposeRef = useRef<(() => void) | null>(null);\n\n // Client path: inject via DOM\n useInsertionEffect(() => {\n disposeRef.current?.();\n\n if (!css.trim()) {\n disposeRef.current = null;\n return;\n }\n\n const { dispose } = injectRawCSS(css, opts);\n disposeRef.current = dispose;\n\n return () => {\n disposeRef.current?.();\n disposeRef.current = null;\n };\n }, [css, opts?.root]);\n}\n"],"mappings":";;;;;;AAkEA,SAAgB,UACd,cACA,eACA,SACM;CAEN,MAAM,eAAe,oBADG,WAAW,gBAAgB,CACM;CAGzD,MAAM,YAAY,OAAO,iBAAiB;CAG1C,MAAM,OACJ,aAAa,MAAM,QAAQ,cAAc,GAAG,gBAAgB;CAC9D,MAAM,OAAO,YACT,UACC;CAGL,MAAM,MAAM,cAER,YAAa,cAA+B,GAAI,cAElD,YAAa,QAAQ,EAAE,GAAI,CAAC,aAAa,CAC1C;AAGD,eAAc;AACZ,MAAI,CAAC,gBAAgB,CAAC,IAAI,MAAM,CAAE;EAElC,MAAM,MAAM,OAAO,IAAI,OAAO,GAAG,IAAI,MAAM,GAAG,GAAG;AACjD,eAAa,cAAc,KAAK,IAAI;IACnC,CAAC,cAAc,IAAI,CAAC;CAEvB,MAAM,aAAa,OAA4B,KAAK;AAGpD,0BAAyB;AACvB,aAAW,WAAW;AAEtB,MAAI,CAAC,IAAI,MAAM,EAAE;AACf,cAAW,UAAU;AACrB;;EAGF,MAAM,EAAE,YAAY,aAAa,KAAK,KAAK;AAC3C,aAAW,UAAU;AAErB,eAAa;AACX,cAAW,WAAW;AACtB,cAAW,UAAU;;IAEtB,CAAC,KAAK,MAAM,KAAK,CAAC"}
|