@vertz/ui 0.2.15 → 0.2.17
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/README.md +49 -0
- package/dist/shared/{chunk-dksg08fq.js → chunk-07bh4m1e.js} +1 -1
- package/dist/shared/chunk-14eqne2a.js +10 -0
- package/dist/shared/{chunk-nn9v1zmk.js → chunk-2wtb9x81.js} +83 -20
- package/dist/shared/{chunk-8hsz5y4a.js → chunk-4fwcwxn6.js} +14 -4
- package/dist/shared/{chunk-4txc67nd.js → chunk-6jyt4ycw.js} +67 -2
- package/dist/shared/{chunk-83g4h38e.js → chunk-6wd36w21.js} +1 -0
- package/dist/shared/{chunk-h89w580h.js → chunk-afawz764.js} +1 -1
- package/dist/shared/{chunk-1wby7nex.js → chunk-dhehvmj0.js} +161 -9
- package/dist/shared/{chunk-wymw818z.js → chunk-fkbgbf3n.js} +48 -9
- package/dist/shared/{chunk-hw67ckr3.js → chunk-fs3eec4b.js} +230 -19
- package/dist/shared/{chunk-5dbq8jp9.js → chunk-j09yyh34.js} +72 -6
- package/dist/shared/chunk-mtsvrj9e.js +23 -0
- package/dist/shared/{chunk-j6qyxfdc.js → chunk-vndfjfdy.js} +3 -3
- package/dist/src/auth/public.d.ts +40 -24
- package/dist/src/auth/public.js +110 -52
- package/dist/src/css/public.d.ts +110 -2
- package/dist/src/css/public.js +8 -4
- package/dist/src/form/public.d.ts +29 -6
- package/dist/src/form/public.js +2 -2
- package/dist/src/index.d.ts +284 -13
- package/dist/src/index.js +160 -14
- package/dist/src/internals.d.ts +168 -5
- package/dist/src/internals.js +14 -8
- package/dist/src/jsx-runtime/index.d.ts +5 -0
- package/dist/src/jsx-runtime/index.js +8 -1
- package/dist/src/query/public.js +4 -3
- package/dist/src/router/public.d.ts +17 -4
- package/dist/src/router/public.js +16 -11
- package/dist/src/test/index.d.ts +5 -0
- package/dist/src/test/index.js +4 -3
- package/package.json +3 -3
- package/reactivity.json +1 -11
package/README.md
CHANGED
|
@@ -255,6 +255,53 @@ const compiled = compileTheme(theme);
|
|
|
255
255
|
ThemeProvider({ theme: 'dark', children: [<App />] });
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
+
### Fonts
|
|
259
|
+
|
|
260
|
+
Declare font families with `font()` and compile them into `@font-face` CSS, custom properties, and preload tags with `compileFonts()`. Only woff2 format is supported.
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
import { font, compileFonts } from '@vertz/ui/css';
|
|
264
|
+
|
|
265
|
+
const sans = font('DM Sans', {
|
|
266
|
+
weight: '100..1000',
|
|
267
|
+
src: '/fonts/dm-sans.woff2',
|
|
268
|
+
fallback: ['system-ui', 'sans-serif'],
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const mono = font('JetBrains Mono', {
|
|
272
|
+
weight: '100..800',
|
|
273
|
+
src: '/fonts/jb-mono.woff2',
|
|
274
|
+
fallback: ['monospace'],
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const compiled = compileFonts({ sans, mono });
|
|
278
|
+
|
|
279
|
+
// compiled.fontFaceCss — @font-face declarations
|
|
280
|
+
// compiled.cssVarsCss — :root { --font-sans: ...; --font-mono: ...; }
|
|
281
|
+
// compiled.cssVarLines — individual lines for merging into an existing :root
|
|
282
|
+
// compiled.preloadTags — <link rel="preload" ...> HTML tags
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Multiple font files** (e.g., normal + italic):
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
const sans = font('DM Sans', {
|
|
289
|
+
weight: '100..1000',
|
|
290
|
+
src: [
|
|
291
|
+
{ path: '/fonts/dm-sans.woff2', weight: '100..1000', style: 'normal' },
|
|
292
|
+
{ path: '/fonts/dm-sans-italic.woff2', weight: '100..1000', style: 'italic' },
|
|
293
|
+
],
|
|
294
|
+
fallback: ['system-ui', 'sans-serif'],
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Font without `src`** (system font with CSS var only):
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
const system = font('system-ui', { weight: '400' });
|
|
302
|
+
// compileFonts({ system }) generates --font-system: 'system-ui'; but no @font-face
|
|
303
|
+
```
|
|
304
|
+
|
|
258
305
|
---
|
|
259
306
|
|
|
260
307
|
## Forms
|
|
@@ -624,6 +671,8 @@ onMount(() => {
|
|
|
624
671
|
| `compileTheme` | Compile a theme to CSS |
|
|
625
672
|
| `ThemeProvider` | Provide a theme to descendants |
|
|
626
673
|
| `globalCss` | Inject global CSS |
|
|
674
|
+
| `font` | Declare a font family descriptor |
|
|
675
|
+
| `compileFonts` | Compile font descriptors into CSS, vars, and preload tags |
|
|
627
676
|
|
|
628
677
|
### Forms
|
|
629
678
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__classList,
|
|
3
3
|
__on
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-07bh4m1e.js";
|
|
5
5
|
import {
|
|
6
6
|
__append,
|
|
7
7
|
__element,
|
|
@@ -9,18 +9,25 @@ import {
|
|
|
9
9
|
__exitChildren,
|
|
10
10
|
__staticText,
|
|
11
11
|
getIsHydrating
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-vndfjfdy.js";
|
|
13
|
+
import {
|
|
14
|
+
RouterContext
|
|
15
|
+
} from "./chunk-mtsvrj9e.js";
|
|
16
|
+
import {
|
|
17
|
+
isBrowser
|
|
18
|
+
} from "./chunk-14eqne2a.js";
|
|
13
19
|
import {
|
|
14
20
|
_tryOnCleanup,
|
|
15
21
|
createContext,
|
|
16
22
|
domEffect,
|
|
23
|
+
getSSRContext,
|
|
17
24
|
popScope,
|
|
18
25
|
pushScope,
|
|
19
26
|
runCleanups,
|
|
20
27
|
signal,
|
|
21
28
|
untrack,
|
|
22
29
|
useContext
|
|
23
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-4fwcwxn6.js";
|
|
24
31
|
|
|
25
32
|
// src/router/link.ts
|
|
26
33
|
var DANGEROUS_SCHEMES = ["javascript:", "data:", "vbscript:"];
|
|
@@ -65,8 +72,10 @@ function createLink(currentPath, navigate, factoryOptions) {
|
|
|
65
72
|
} else {
|
|
66
73
|
__append(el, result);
|
|
67
74
|
}
|
|
68
|
-
} else {
|
|
75
|
+
} else if (typeof children === "string") {
|
|
69
76
|
__append(el, __staticText(children));
|
|
77
|
+
} else {
|
|
78
|
+
__append(el, children);
|
|
70
79
|
}
|
|
71
80
|
__exitChildren();
|
|
72
81
|
if (activeClass) {
|
|
@@ -88,22 +97,49 @@ function createLink(currentPath, navigate, factoryOptions) {
|
|
|
88
97
|
return el;
|
|
89
98
|
};
|
|
90
99
|
}
|
|
91
|
-
|
|
92
|
-
// src/router/router-context.ts
|
|
93
|
-
var RouterContext = createContext(undefined, "@vertz/ui::RouterContext");
|
|
94
|
-
function useRouter() {
|
|
100
|
+
function Link({ href, children, activeClass, className }) {
|
|
95
101
|
const router = useContext(RouterContext);
|
|
96
102
|
if (!router) {
|
|
97
|
-
throw new Error("
|
|
103
|
+
throw new Error("Link must be used within a RouterContext.Provider (via createRouter)");
|
|
98
104
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
const safeHref = isSafeUrl(href) ? href : "#";
|
|
106
|
+
const handleClick = (event) => {
|
|
107
|
+
const mouseEvent = event;
|
|
108
|
+
if (mouseEvent.ctrlKey || mouseEvent.metaKey || mouseEvent.shiftKey || mouseEvent.altKey) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
mouseEvent.preventDefault();
|
|
112
|
+
router.navigate({ to: safeHref });
|
|
113
|
+
};
|
|
114
|
+
const props = { href: safeHref };
|
|
115
|
+
if (className) {
|
|
116
|
+
props.class = className;
|
|
117
|
+
}
|
|
118
|
+
const el = __element("a", props);
|
|
119
|
+
__on(el, "click", handleClick);
|
|
120
|
+
__enterChildren(el);
|
|
121
|
+
if (typeof children === "function") {
|
|
122
|
+
const result = children();
|
|
123
|
+
if (typeof result === "string") {
|
|
124
|
+
__append(el, __staticText(result));
|
|
125
|
+
} else {
|
|
126
|
+
__append(el, result);
|
|
127
|
+
}
|
|
128
|
+
} else if (typeof children === "string") {
|
|
129
|
+
__append(el, __staticText(children));
|
|
130
|
+
} else {
|
|
131
|
+
__append(el, children);
|
|
132
|
+
}
|
|
133
|
+
__exitChildren();
|
|
134
|
+
if (activeClass) {
|
|
135
|
+
__classList(el, {
|
|
136
|
+
[activeClass]: () => {
|
|
137
|
+
router.current;
|
|
138
|
+
return isBrowser() ? window.location.pathname === safeHref : false;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
105
141
|
}
|
|
106
|
-
return
|
|
142
|
+
return el;
|
|
107
143
|
}
|
|
108
144
|
|
|
109
145
|
// src/router/outlet.ts
|
|
@@ -249,17 +285,44 @@ function buildLevels(matched) {
|
|
|
249
285
|
}));
|
|
250
286
|
}
|
|
251
287
|
function buildInsideOutFactory(matched, levels, startAt, router) {
|
|
252
|
-
|
|
288
|
+
const ssrCtx = getSSRContext();
|
|
289
|
+
const wrapForSSR = (route, routeIndex) => {
|
|
290
|
+
if (!ssrCtx)
|
|
291
|
+
return route.component;
|
|
292
|
+
const resolved = ssrCtx.resolvedComponents?.get(route);
|
|
293
|
+
if (resolved)
|
|
294
|
+
return resolved;
|
|
295
|
+
return () => {
|
|
296
|
+
const result = route.component();
|
|
297
|
+
if (result instanceof Promise) {
|
|
298
|
+
if (!ssrCtx.pendingRouteComponents) {
|
|
299
|
+
ssrCtx.pendingRouteComponents = new Map;
|
|
300
|
+
}
|
|
301
|
+
ssrCtx.pendingRouteComponents.set(route, result);
|
|
302
|
+
for (let j = routeIndex + 1;j < matched.length; j++) {
|
|
303
|
+
const childRoute = matched[j].route;
|
|
304
|
+
if (!ssrCtx.pendingRouteComponents.has(childRoute)) {
|
|
305
|
+
const childResult = childRoute.component();
|
|
306
|
+
if (childResult instanceof Promise) {
|
|
307
|
+
ssrCtx.pendingRouteComponents.set(childRoute, childResult);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
let factory = wrapForSSR(matched[matched.length - 1].route, matched.length - 1);
|
|
253
316
|
for (let i = matched.length - 2;i >= startAt; i--) {
|
|
254
317
|
const level = levels[i];
|
|
255
318
|
const childFactory = factory;
|
|
256
319
|
level.childSignal.value = childFactory;
|
|
257
|
-
const
|
|
320
|
+
const parentComponent = wrapForSSR(level.route, i);
|
|
258
321
|
const cs = level.childSignal;
|
|
259
322
|
factory = () => {
|
|
260
323
|
let result;
|
|
261
324
|
OutletContext.Provider({ childComponent: cs, router }, () => {
|
|
262
|
-
result =
|
|
325
|
+
result = parentComponent();
|
|
263
326
|
});
|
|
264
327
|
return result;
|
|
265
328
|
};
|
|
@@ -285,4 +348,4 @@ function useSearchParams(searchSignal) {
|
|
|
285
348
|
return searchSignal.value;
|
|
286
349
|
}
|
|
287
350
|
|
|
288
|
-
export { createLink,
|
|
351
|
+
export { createLink, Link, OutletContext, Outlet, RouterView, parseSearchParams, useSearchParams };
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
// src/ssr/ssr-render-context.ts
|
|
2
|
-
var
|
|
2
|
+
var RESOLVER_KEY = "__VERTZ_SSR_RESOLVER__";
|
|
3
|
+
function getResolver() {
|
|
4
|
+
return globalThis[RESOLVER_KEY] ?? null;
|
|
5
|
+
}
|
|
3
6
|
function registerSSRResolver(resolver) {
|
|
4
|
-
|
|
7
|
+
if (resolver === null) {
|
|
8
|
+
delete globalThis[RESOLVER_KEY];
|
|
9
|
+
} else {
|
|
10
|
+
globalThis[RESOLVER_KEY] = resolver;
|
|
11
|
+
}
|
|
5
12
|
}
|
|
6
13
|
function getSSRContext() {
|
|
7
|
-
return
|
|
14
|
+
return getResolver()?.();
|
|
15
|
+
}
|
|
16
|
+
function hasSSRResolver() {
|
|
17
|
+
return getResolver() !== null;
|
|
8
18
|
}
|
|
9
19
|
|
|
10
20
|
// src/component/context.ts
|
|
@@ -472,4 +482,4 @@ function lifecycleEffect(fn) {
|
|
|
472
482
|
return dispose;
|
|
473
483
|
}
|
|
474
484
|
|
|
475
|
-
export { registerSSRResolver, getSSRContext, createContext, useContext, getContextScope, setContextScope, DisposalScopeError, onCleanup, _tryOnCleanup, pushScope, popScope, runCleanups, setReadValueCallback, untrack, batch, startSignalCollection, stopSignalCollection, signal, computed, domEffect, lifecycleEffect };
|
|
485
|
+
export { registerSSRResolver, getSSRContext, hasSSRResolver, createContext, useContext, getContextScope, setContextScope, DisposalScopeError, onCleanup, _tryOnCleanup, pushScope, popScope, runCleanups, setReadValueCallback, untrack, batch, startSignalCollection, stopSignalCollection, signal, computed, domEffect, lifecycleEffect };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
injectCSS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-dhehvmj0.js";
|
|
4
4
|
|
|
5
5
|
// src/dom/animation.ts
|
|
6
6
|
function onAnimationsComplete(el, callback) {
|
|
@@ -479,7 +479,72 @@ function hydrate(registry) {
|
|
|
479
479
|
autoStrategy(el, doHydrate);
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
|
+
|
|
483
|
+
// src/hydrate/island-hydrate.ts
|
|
484
|
+
var hydrationQueue = [];
|
|
485
|
+
var isProcessing = false;
|
|
486
|
+
async function processQueue() {
|
|
487
|
+
if (isProcessing)
|
|
488
|
+
return;
|
|
489
|
+
isProcessing = true;
|
|
490
|
+
while (hydrationQueue.length > 0) {
|
|
491
|
+
const task = hydrationQueue.shift();
|
|
492
|
+
await task();
|
|
493
|
+
}
|
|
494
|
+
isProcessing = false;
|
|
495
|
+
}
|
|
496
|
+
function deserializeIslandProps(container) {
|
|
497
|
+
const script = container.querySelector(":scope > script[data-v-island-props]");
|
|
498
|
+
if (!script || !script.textContent) {
|
|
499
|
+
return {};
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
return JSON.parse(script.textContent);
|
|
503
|
+
} catch {
|
|
504
|
+
return {};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function hydrateIslands(registry) {
|
|
508
|
+
const elements = document.querySelectorAll("[data-v-island]");
|
|
509
|
+
const registryKeys = Object.keys(registry);
|
|
510
|
+
for (const el of elements) {
|
|
511
|
+
if (el.hasAttribute("data-v-hydrated"))
|
|
512
|
+
continue;
|
|
513
|
+
const islandId = el.getAttribute("data-v-island");
|
|
514
|
+
if (!islandId)
|
|
515
|
+
continue;
|
|
516
|
+
const loader = registry[islandId];
|
|
517
|
+
if (!loader) {
|
|
518
|
+
console.error(`[vertz] Island "${islandId}" not found in registry. Available: [${registryKeys.join(", ")}]`);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
const props = deserializeIslandProps(el);
|
|
522
|
+
const doHydrate = () => {
|
|
523
|
+
hydrationQueue.push(async () => {
|
|
524
|
+
try {
|
|
525
|
+
const mod = await loader();
|
|
526
|
+
const result = mod.default(props, el);
|
|
527
|
+
if (result instanceof Node) {
|
|
528
|
+
const children = Array.from(el.childNodes);
|
|
529
|
+
for (const child of children) {
|
|
530
|
+
if (child instanceof Element && child.hasAttribute("data-v-island-props")) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
el.removeChild(child);
|
|
534
|
+
}
|
|
535
|
+
el.appendChild(result);
|
|
536
|
+
}
|
|
537
|
+
el.setAttribute("data-v-hydrated", "");
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error(`[vertz] Failed to hydrate island "${islandId}":`, error);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
processQueue();
|
|
543
|
+
};
|
|
544
|
+
autoStrategy(el, doHydrate);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
482
547
|
// src/query/index.ts
|
|
483
548
|
import { isQueryDescriptor } from "@vertz/fetch";
|
|
484
549
|
|
|
485
|
-
export { onAnimationsComplete, keyframes, ANIMATION_DURATION, ANIMATION_EASING, fadeIn, fadeOut, zoomIn, zoomOut, slideInFromTop, slideInFromBottom, slideOutToTop, slideOutToBottom, slideInFromLeft, slideInFromRight, slideOutToLeft, slideOutToRight, accordionDown, accordionUp, palettes, resolveComponent, deserializeProps, hydrate, isQueryDescriptor };
|
|
550
|
+
export { onAnimationsComplete, keyframes, ANIMATION_DURATION, ANIMATION_EASING, fadeIn, fadeOut, zoomIn, zoomOut, slideInFromTop, slideInFromBottom, slideOutToTop, slideOutToBottom, slideInFromLeft, slideInFromRight, slideOutToLeft, slideOutToRight, accordionDown, accordionUp, palettes, resolveComponent, deserializeProps, hydrate, hydrateIslands, isQueryDescriptor };
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
__element,
|
|
4
4
|
__enterChildren,
|
|
5
5
|
__exitChildren
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-vndfjfdy.js";
|
|
7
7
|
import {
|
|
8
8
|
getSSRContext
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4fwcwxn6.js";
|
|
10
10
|
|
|
11
11
|
// src/component/children.ts
|
|
12
12
|
var MAX_RESOLVE_DEPTH = 100;
|
|
@@ -757,6 +757,143 @@ ${props}
|
|
|
757
757
|
}`;
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
+
// src/css/sanitize.ts
|
|
761
|
+
function sanitizeCssValue(value) {
|
|
762
|
+
return value.replace(/[;{}<>']/g, "").replace(/url\s*\(/gi, "").replace(/expression\s*\(/gi, "").replace(/@import/gi, "");
|
|
763
|
+
}
|
|
764
|
+
function escapeHtmlAttr(value) {
|
|
765
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">").replace(/'/g, "'");
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/css/font.ts
|
|
769
|
+
function font(family, options) {
|
|
770
|
+
return {
|
|
771
|
+
__brand: "FontDescriptor",
|
|
772
|
+
family,
|
|
773
|
+
weight: String(options.weight),
|
|
774
|
+
style: options.style ?? "normal",
|
|
775
|
+
display: options.display ?? "swap",
|
|
776
|
+
src: options.src,
|
|
777
|
+
fallback: options.fallback ?? [],
|
|
778
|
+
subsets: options.subsets ?? ["latin"],
|
|
779
|
+
unicodeRange: options.unicodeRange,
|
|
780
|
+
adjustFontFallback: options.adjustFontFallback ?? true
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function toCssWeight(weight) {
|
|
784
|
+
return sanitizeCssValue(weight).replace("..", " ");
|
|
785
|
+
}
|
|
786
|
+
function buildFontFace(family, src, weight, style, display, unicodeRange) {
|
|
787
|
+
const safeFamily = sanitizeCssValue(family);
|
|
788
|
+
const safeSrc = sanitizeCssValue(src);
|
|
789
|
+
const safeStyle = sanitizeCssValue(style);
|
|
790
|
+
const safeDisplay = sanitizeCssValue(display);
|
|
791
|
+
const lines = [
|
|
792
|
+
"@font-face {",
|
|
793
|
+
` font-family: '${safeFamily}';`,
|
|
794
|
+
` font-style: ${safeStyle};`,
|
|
795
|
+
` font-weight: ${toCssWeight(weight)};`,
|
|
796
|
+
` font-display: ${safeDisplay};`,
|
|
797
|
+
` src: url(${safeSrc}) format('woff2');`
|
|
798
|
+
];
|
|
799
|
+
if (unicodeRange) {
|
|
800
|
+
lines.push(` unicode-range: ${sanitizeCssValue(unicodeRange)};`);
|
|
801
|
+
}
|
|
802
|
+
lines.push("}");
|
|
803
|
+
return lines.join(`
|
|
804
|
+
`);
|
|
805
|
+
}
|
|
806
|
+
var PERCENT_RE = /^\d+\.\d+%$/;
|
|
807
|
+
function sanitizeMetricValue(value) {
|
|
808
|
+
if (!PERCENT_RE.test(value)) {
|
|
809
|
+
throw new Error(`Invalid font metric override value: "${value}"`);
|
|
810
|
+
}
|
|
811
|
+
return value;
|
|
812
|
+
}
|
|
813
|
+
function buildFallbackFontFace(family, metrics) {
|
|
814
|
+
const safeFamily = sanitizeCssValue(family);
|
|
815
|
+
const lines = [
|
|
816
|
+
"@font-face {",
|
|
817
|
+
` font-family: '${safeFamily} Fallback';`,
|
|
818
|
+
` src: local('${metrics.fallbackFont}');`,
|
|
819
|
+
` ascent-override: ${sanitizeMetricValue(metrics.ascentOverride)};`,
|
|
820
|
+
` descent-override: ${sanitizeMetricValue(metrics.descentOverride)};`,
|
|
821
|
+
` line-gap-override: ${sanitizeMetricValue(metrics.lineGapOverride)};`,
|
|
822
|
+
` size-adjust: ${sanitizeMetricValue(metrics.sizeAdjust)};`,
|
|
823
|
+
"}"
|
|
824
|
+
];
|
|
825
|
+
return lines.join(`
|
|
826
|
+
`);
|
|
827
|
+
}
|
|
828
|
+
function validateWoff2Src(path) {
|
|
829
|
+
if (!path.toLowerCase().endsWith(".woff2")) {
|
|
830
|
+
throw new Error(`Font src "${path}" is not a .woff2 file. Only woff2 format is currently supported.`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function compileFonts(fonts, options) {
|
|
834
|
+
const fontFaces = [];
|
|
835
|
+
const cssVars = [];
|
|
836
|
+
const preloadPaths = [];
|
|
837
|
+
const fallbackMetrics = options?.fallbackMetrics;
|
|
838
|
+
for (const [key, descriptor] of Object.entries(fonts)) {
|
|
839
|
+
if (!/^[a-zA-Z0-9-]+$/.test(key)) {
|
|
840
|
+
throw new Error(`Font key "${key}" contains invalid CSS identifier characters. Use only [a-zA-Z0-9-].`);
|
|
841
|
+
}
|
|
842
|
+
const { family, weight, style, display, src, fallback, unicodeRange } = descriptor;
|
|
843
|
+
const adjustFontFallback = descriptor.adjustFontFallback ?? true;
|
|
844
|
+
const metrics = fallbackMetrics?.[key];
|
|
845
|
+
const shouldGenerateFallback = metrics && src && adjustFontFallback !== false;
|
|
846
|
+
const safeFamily = sanitizeCssValue(family);
|
|
847
|
+
const safeFallbacks = fallback.map(sanitizeCssValue);
|
|
848
|
+
const fallbackFontName = shouldGenerateFallback ? `'${safeFamily} Fallback'` : undefined;
|
|
849
|
+
const familyParts = [
|
|
850
|
+
`'${safeFamily}'`,
|
|
851
|
+
...fallbackFontName ? [fallbackFontName] : [],
|
|
852
|
+
...safeFallbacks
|
|
853
|
+
];
|
|
854
|
+
const familyValue = familyParts.join(", ");
|
|
855
|
+
cssVars.push(` --font-${sanitizeCssValue(key)}: ${familyValue};`);
|
|
856
|
+
if (!src)
|
|
857
|
+
continue;
|
|
858
|
+
if (typeof src === "string") {
|
|
859
|
+
validateWoff2Src(src);
|
|
860
|
+
fontFaces.push(buildFontFace(family, src, weight, style, display, unicodeRange));
|
|
861
|
+
preloadPaths.push(src);
|
|
862
|
+
} else {
|
|
863
|
+
for (const entry of src) {
|
|
864
|
+
validateWoff2Src(entry.path);
|
|
865
|
+
const entryWeight = entry.weight != null ? String(entry.weight) : weight;
|
|
866
|
+
const entryStyle = entry.style ?? style;
|
|
867
|
+
fontFaces.push(buildFontFace(family, entry.path, entryWeight, entryStyle, display, unicodeRange));
|
|
868
|
+
}
|
|
869
|
+
const first = src[0];
|
|
870
|
+
if (first) {
|
|
871
|
+
preloadPaths.push(first.path);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (shouldGenerateFallback) {
|
|
875
|
+
fontFaces.push(buildFallbackFontFace(family, metrics));
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
const fontFaceCss = fontFaces.join(`
|
|
879
|
+
|
|
880
|
+
`);
|
|
881
|
+
const cssVarLines = cssVars;
|
|
882
|
+
const cssVarsCss = cssVars.length > 0 ? `:root {
|
|
883
|
+
${cssVars.join(`
|
|
884
|
+
`)}
|
|
885
|
+
}` : "";
|
|
886
|
+
const preloadTags = preloadPaths.map((p) => `<link rel="preload" href="${escapeHtmlAttr(p)}" as="font" type="font/woff2" crossorigin>`).join(`
|
|
887
|
+
`);
|
|
888
|
+
const preloadItems = preloadPaths.map((href) => ({
|
|
889
|
+
href,
|
|
890
|
+
as: "font",
|
|
891
|
+
type: "font/woff2",
|
|
892
|
+
crossorigin: true
|
|
893
|
+
}));
|
|
894
|
+
return { fontFaceCss, cssVarsCss, cssVarLines, preloadTags, preloadItems };
|
|
895
|
+
}
|
|
896
|
+
|
|
760
897
|
// src/css/global-css.ts
|
|
761
898
|
function globalCss(input) {
|
|
762
899
|
const rules = [];
|
|
@@ -810,16 +947,14 @@ class InlineStyleError extends Error {
|
|
|
810
947
|
}
|
|
811
948
|
|
|
812
949
|
// src/css/theme.ts
|
|
813
|
-
function sanitizeCssValue(value) {
|
|
814
|
-
return value.replace(/[;{}]/g, "").replace(/url\s*\(/gi, "").replace(/expression\s*\(/gi, "").replace(/@import/gi, "");
|
|
815
|
-
}
|
|
816
950
|
function defineTheme(input) {
|
|
817
951
|
return {
|
|
818
952
|
colors: input.colors,
|
|
819
|
-
spacing: input.spacing
|
|
953
|
+
spacing: input.spacing,
|
|
954
|
+
fonts: input.fonts
|
|
820
955
|
};
|
|
821
956
|
}
|
|
822
|
-
function compileTheme(theme) {
|
|
957
|
+
function compileTheme(theme, options) {
|
|
823
958
|
const rootVars = [];
|
|
824
959
|
const darkVars = [];
|
|
825
960
|
const tokenPaths = [];
|
|
@@ -864,7 +999,22 @@ function compileTheme(theme) {
|
|
|
864
999
|
tokenPaths.push(`spacing.${name}`);
|
|
865
1000
|
}
|
|
866
1001
|
}
|
|
1002
|
+
let fontFaceCss = "";
|
|
1003
|
+
let preloadTags = "";
|
|
1004
|
+
let preloadItems = [];
|
|
1005
|
+
if (theme.fonts) {
|
|
1006
|
+
const compiled = compileFonts(theme.fonts, {
|
|
1007
|
+
fallbackMetrics: options?.fallbackMetrics
|
|
1008
|
+
});
|
|
1009
|
+
fontFaceCss = compiled.fontFaceCss;
|
|
1010
|
+
preloadTags = compiled.preloadTags;
|
|
1011
|
+
preloadItems = compiled.preloadItems;
|
|
1012
|
+
rootVars.push(...compiled.cssVarLines);
|
|
1013
|
+
}
|
|
867
1014
|
const blocks = [];
|
|
1015
|
+
if (fontFaceCss) {
|
|
1016
|
+
blocks.push(fontFaceCss);
|
|
1017
|
+
}
|
|
868
1018
|
if (rootVars.length > 0) {
|
|
869
1019
|
blocks.push(`:root {
|
|
870
1020
|
${rootVars.join(`
|
|
@@ -880,7 +1030,9 @@ ${darkVars.join(`
|
|
|
880
1030
|
return {
|
|
881
1031
|
css: blocks.join(`
|
|
882
1032
|
`),
|
|
883
|
-
tokens: tokenPaths
|
|
1033
|
+
tokens: tokenPaths,
|
|
1034
|
+
preloadTags,
|
|
1035
|
+
preloadItems
|
|
884
1036
|
};
|
|
885
1037
|
}
|
|
886
1038
|
|
|
@@ -1017,4 +1169,4 @@ function variants(config) {
|
|
|
1017
1169
|
return fn;
|
|
1018
1170
|
}
|
|
1019
1171
|
|
|
1020
|
-
export { resolveChildren, children, PROPERTY_MAP, KEYWORD_MAP, DISPLAY_MAP, SPACING_SCALE, RADIUS_SCALE, SHADOW_SCALE, FONT_SIZE_SCALE, FONT_WEIGHT_SCALE, LINE_HEIGHT_SCALE, ALIGNMENT_MAP, SIZE_KEYWORDS, HEIGHT_AXIS_PROPERTIES, COLOR_NAMESPACES, CSS_COLOR_KEYWORDS, CONTENT_MAP, PSEUDO_PREFIXES, PSEUDO_MAP, injectCSS, resetInjectedStyles, getInjectedCSS, css, globalCss, s, defineTheme, compileTheme, ThemeProvider, variants };
|
|
1172
|
+
export { resolveChildren, children, PROPERTY_MAP, KEYWORD_MAP, DISPLAY_MAP, SPACING_SCALE, RADIUS_SCALE, SHADOW_SCALE, FONT_SIZE_SCALE, FONT_WEIGHT_SCALE, LINE_HEIGHT_SCALE, ALIGNMENT_MAP, SIZE_KEYWORDS, HEIGHT_AXIS_PROPERTIES, COLOR_NAMESPACES, CSS_COLOR_KEYWORDS, CONTENT_MAP, PSEUDO_PREFIXES, PSEUDO_MAP, injectCSS, resetInjectedStyles, getInjectedCSS, css, font, compileFonts, globalCss, s, defineTheme, compileTheme, ThemeProvider, variants };
|