@salty-css/core 0.1.0-alpha.20 → 0.1.0-alpha.21

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 CHANGED
@@ -57,6 +57,7 @@ To get help with problems, [Join Salty CSS Discord server](https://discord.gg/R6
57
57
  - [defineVariables](#variables) - create CSS variables (tokens) that can be used in any styling function
58
58
  - [defineMediaQuery](#media-queries) - create CSS media queries and use them in any styling function
59
59
  - [defineTemplates](#templates) - create reusable templates that can be applied when same styles are used over and over again
60
+ - [defineFont](#custom-fonts) - register custom fonts via `@font-face` (or a remote stylesheet) and expose them as a CSS variable
60
61
  - [keyframes](#keyframes-animations) - create CSS keyframes animation that can be used and imported in any styling function
61
62
 
62
63
  ### Styling helpers & utility
@@ -319,6 +320,88 @@ Example usage:
319
320
  styled('div', { base: { textStyle: 'headline.large', card: '20px' } });
320
321
  ```
321
322
 
323
+ ## Custom fonts
324
+
325
+ Register custom fonts that will be emitted as `@font-face` declarations and exposed as a CSS variable. Mirrors the developer experience of Next.js / Astro font loaders, but generated at build time alongside the rest of your Salty CSS output.
326
+
327
+ The returned object stringifies to its `font-family` value and exposes helpers for explicit usage:
328
+
329
+ - `Font.variable` → CSS variable name (e.g. `--font-inter`)
330
+ - `Font.fontFamily` → final `font-family` string with fallbacks
331
+ - `Font.className` → class that sets the variable + applies the font on a subtree
332
+ - `Font.style` → object you can spread on a React `style` prop
333
+
334
+ ```ts
335
+ // /styles/fonts.css.ts
336
+ import { defineFont } from '@salty-css/core/factories';
337
+
338
+ // 1. Local or self-hosted @font-face sources.
339
+ // URLs are passed through as-is (use a public-folder path or a CDN URL).
340
+ export const Inter = defineFont({
341
+ name: 'Inter', // CSS font-family value
342
+ variable: '--font-inter', // Optional — accepts 'font-inter' too; if omitted we derive `--font-<name>-<hash>` from the inputs
343
+ display: 'swap', // Optional default applied to variants without their own `display`
344
+ fallback: ['system-ui', 'sans-serif'], // Optional family fallbacks appended after `name`
345
+ variants: [
346
+ {
347
+ weight: 400,
348
+ style: 'normal',
349
+ // Shorthand: pass a string and the `format()` descriptor is auto-detected
350
+ // from the file extension (woff2, woff, ttf, otf, eot, svg, ttc).
351
+ src: '/fonts/inter-400.woff2',
352
+ },
353
+ {
354
+ weight: 700,
355
+ style: 'normal',
356
+ // Multiple sources can be a string array — first entry is preferred;
357
+ // the browser picks the first format it supports.
358
+ src: ['/fonts/inter-700.woff2', '/fonts/inter-700.ttf'],
359
+ },
360
+ {
361
+ weight: 400,
362
+ style: 'italic',
363
+ // Use the `{ url, format }` object form when the URL has no recognisable
364
+ // extension (signed CDN URLs, query-only endpoints, etc.). You can also
365
+ // mix strings and objects in the same array.
366
+ src: ['/fonts/inter-400-italic.woff2', { url: 'https://cdn.example.com/inter-italic', format: 'woff' }],
367
+ },
368
+ ],
369
+ });
370
+
371
+ // 2. Remote stylesheet (Google Fonts, etc.). Emits `@import url(...)` and still
372
+ // registers the CSS variable so usage stays the same as the @font-face flow.
373
+ export const InterCdn = defineFont({
374
+ name: 'Inter',
375
+ variable: '--font-inter',
376
+ import: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap',
377
+ });
378
+ ```
379
+
380
+ Example usage:
381
+
382
+ ```tsx
383
+ import { Inter } from './fonts.css';
384
+ import { styled } from '@salty-css/react/styled';
385
+
386
+ // Apply the font globally by attaching its className high up in the tree.
387
+ // This sets `--font-inter` on the subtree and applies `font-family: var(--font-inter)`.
388
+ export const App = ({ children }) => <div className={Inter.className}>{children}</div>;
389
+
390
+ // `Inter` stringifies to its font-family value (with fallbacks), so it can be used directly.
391
+ export const Heading = styled('h1', {
392
+ base: {
393
+ fontFamily: `${Inter}`,
394
+ },
395
+ });
396
+
397
+ // Or reference the CSS variable explicitly.
398
+ export const Body = styled('p', {
399
+ base: {
400
+ fontFamily: `var(${Inter.variable})`,
401
+ },
402
+ });
403
+ ```
404
+
322
405
  ## Keyframes animations
323
406
 
324
407
  ```ts
@@ -2,7 +2,7 @@
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
- const parseStyles = require("./parse-styles-DWQ4UQCp.cjs");
5
+ const parseStyles = require("./parse-styles-CX1WjafO.cjs");
6
6
  const dashCase = require("./dash-case-DIwKaYgE.cjs");
7
7
  const toHash = require("./to-hash-C05Y906F.cjs");
8
8
  class StylesGenerator {
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { p as parseAndJoinStyles } from "./parse-styles-g4NPgqhh.js";
4
+ import { p as parseAndJoinStyles } from "./parse-styles-DIqJjXF3.js";
5
5
  import { d as dashCase } from "./dash-case-DblXvymC.js";
6
6
  import { t as toHash } from "./to-hash-DAN2LcHK.js";
7
7
  class StylesGenerator {
@@ -10,10 +10,10 @@ const promises = require("fs/promises");
10
10
  const fs = require("fs");
11
11
  const child_process = require("child_process");
12
12
  const compiler_helpers = require("./helpers.cjs");
13
- const defineTemplates = require("../define-templates-Deq1aCbN.cjs");
14
13
  const dashCase = require("../dash-case-DIwKaYgE.cjs");
15
14
  const toHash = require("../to-hash-C05Y906F.cjs");
16
- const parseStyles = require("../parse-styles-DWQ4UQCp.cjs");
15
+ const defineTemplates = require("../define-templates-Deq1aCbN.cjs");
16
+ const parseStyles = require("../parse-styles-CX1WjafO.cjs");
17
17
  const css_merge = require("../css/merge.cjs");
18
18
  const parsers_index = require("../parsers/index.cjs");
19
19
  const compiler_getFiles = require("./get-files.cjs");
@@ -348,7 +348,7 @@ ${currentFile}`;
348
348
  });
349
349
  }
350
350
  const otherGlobalCssFiles = globalCssFiles.map((file) => `@import url('./css/${file}');`).join("\n");
351
- const globalCssFilenames = ["_variables.css", "_reset.css", "_global.css", "_templates.css"];
351
+ const globalCssFilenames = ["_variables.css", "_reset.css", "_global.css", "_templates.css", "_fonts.css"];
352
352
  const importsWithData = globalCssFilenames.filter((file) => {
353
353
  try {
354
354
  const data = fs.readFileSync(path.join(destDir, "css", file), "utf8");
@@ -359,7 +359,7 @@ ${currentFile}`;
359
359
  });
360
360
  const globalImports = importsWithData.map((file) => `@import url('./css/${file}');`);
361
361
  const generatorText = "/*!\n * Generated with Salty CSS (https://salty-css.dev)\n * Do not edit this file directly\n */\n";
362
- let cssContent = `${generatorText}@layer reset, global, templates, l0, l1, l2, l3, l4, l5, l6, l7, l8;
362
+ let cssContent = `${generatorText}@layer reset, global, templates, fonts, l0, l1, l2, l3, l4, l5, l6, l7, l8;
363
363
 
364
364
  ${globalImports.join(
365
365
  "\n"
@@ -405,7 +405,8 @@ ${css}
405
405
  mediaQueries: [],
406
406
  globalStyles: [],
407
407
  variables: [],
408
- templates: []
408
+ templates: [],
409
+ fonts: []
409
410
  };
410
411
  await Promise.all(
411
412
  [...configFiles].map(async (src) => {
@@ -415,6 +416,7 @@ ${css}
415
416
  else if (value.isGlobalDefine) generationResults.globalStyles.push(value);
416
417
  else if (value.isDefineVariables) generationResults.variables.push(value);
417
418
  else if (value.isDefineTemplates) generationResults.templates.push(value._setPath(`${name};;${outputFilePath}`));
419
+ else if (value.isDefineFont) generationResults.fonts.push(value);
418
420
  });
419
421
  })
420
422
  );
@@ -516,6 +518,22 @@ ${css}
516
518
  const configTemplateFactories = config.templates ? [defineTemplates.defineTemplates(config.templates)._setPath(`config;;${configPath}`)] : [];
517
519
  const templateFactories = css_merge.mergeFactories(generationResults.templates, configTemplateFactories);
518
520
  configCacheContent.templatePaths = Object.fromEntries(Object.entries(templateFactories).map(([key, faktory]) => [key, faktory._path]));
521
+ const fontsStylesPath = path.join(destDir, "css/_fonts.css");
522
+ if (generationResults.fonts.length === 0) {
523
+ fs.writeFileSync(fontsStylesPath, "");
524
+ } else {
525
+ const fontImports = [];
526
+ const fontBodies = [];
527
+ for (const font of generationResults.fonts) {
528
+ const { imports, body } = font._toCss();
529
+ fontImports.push(...imports);
530
+ fontBodies.push(body);
531
+ }
532
+ const importBlock = fontImports.length ? `${fontImports.join("\n")}
533
+
534
+ ` : "";
535
+ fs.writeFileSync(fontsStylesPath, `${importBlock}@layer fonts { ${fontBodies.join(" ")} }`);
536
+ }
519
537
  const tsTokensPath = path.join(destDir, "types/css-tokens.d.ts");
520
538
  const tsVariableTokens = [...variableTokens].join("|");
521
539
  const tsTokensTypes = `
@@ -44,6 +44,7 @@ export declare class SaltyCompiler {
44
44
  isGlobalDefine?: boolean;
45
45
  isDefineVariables?: boolean;
46
46
  isDefineTemplates?: boolean;
47
+ isDefineFont?: boolean;
47
48
  isKeyframes?: boolean;
48
49
  animationName?: string;
49
50
  css?: Promise<string>;
@@ -8,10 +8,10 @@ import { readFile } from "fs/promises";
8
8
  import { readFileSync, existsSync, mkdirSync, statSync, readdirSync, writeFileSync } from "fs";
9
9
  import { execSync } from "child_process";
10
10
  import { isSaltyFile, resolveExportValue, getCorePackageRoot, saltyFileExtensions } from "./helpers.js";
11
- import { d as defineTemplates } from "../define-templates-CVhhgPnd.js";
12
11
  import { d as dashCase } from "../dash-case-DblXvymC.js";
13
12
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
14
- import { p as parseAndJoinStyles, b as parseVariableTokens } from "../parse-styles-g4NPgqhh.js";
13
+ import { d as defineTemplates } from "../define-templates-CVhhgPnd.js";
14
+ import { p as parseAndJoinStyles, b as parseVariableTokens } from "../parse-styles-DIqJjXF3.js";
15
15
  import { mergeObjects, mergeFactories } from "../css/merge.js";
16
16
  import { parseTemplates, getTemplateTypes } from "../parsers/index.js";
17
17
  import { getPackageJson } from "./get-files.js";
@@ -328,7 +328,7 @@ ${currentFile}`;
328
328
  });
329
329
  }
330
330
  const otherGlobalCssFiles = globalCssFiles.map((file) => `@import url('./css/${file}');`).join("\n");
331
- const globalCssFilenames = ["_variables.css", "_reset.css", "_global.css", "_templates.css"];
331
+ const globalCssFilenames = ["_variables.css", "_reset.css", "_global.css", "_templates.css", "_fonts.css"];
332
332
  const importsWithData = globalCssFilenames.filter((file) => {
333
333
  try {
334
334
  const data = readFileSync(join(destDir, "css", file), "utf8");
@@ -339,7 +339,7 @@ ${currentFile}`;
339
339
  });
340
340
  const globalImports = importsWithData.map((file) => `@import url('./css/${file}');`);
341
341
  const generatorText = "/*!\n * Generated with Salty CSS (https://salty-css.dev)\n * Do not edit this file directly\n */\n";
342
- let cssContent = `${generatorText}@layer reset, global, templates, l0, l1, l2, l3, l4, l5, l6, l7, l8;
342
+ let cssContent = `${generatorText}@layer reset, global, templates, fonts, l0, l1, l2, l3, l4, l5, l6, l7, l8;
343
343
 
344
344
  ${globalImports.join(
345
345
  "\n"
@@ -385,7 +385,8 @@ ${css}
385
385
  mediaQueries: [],
386
386
  globalStyles: [],
387
387
  variables: [],
388
- templates: []
388
+ templates: [],
389
+ fonts: []
389
390
  };
390
391
  await Promise.all(
391
392
  [...configFiles].map(async (src) => {
@@ -395,6 +396,7 @@ ${css}
395
396
  else if (value.isGlobalDefine) generationResults.globalStyles.push(value);
396
397
  else if (value.isDefineVariables) generationResults.variables.push(value);
397
398
  else if (value.isDefineTemplates) generationResults.templates.push(value._setPath(`${name};;${outputFilePath}`));
399
+ else if (value.isDefineFont) generationResults.fonts.push(value);
398
400
  });
399
401
  })
400
402
  );
@@ -496,6 +498,22 @@ ${css}
496
498
  const configTemplateFactories = config.templates ? [defineTemplates(config.templates)._setPath(`config;;${configPath}`)] : [];
497
499
  const templateFactories = mergeFactories(generationResults.templates, configTemplateFactories);
498
500
  configCacheContent.templatePaths = Object.fromEntries(Object.entries(templateFactories).map(([key, faktory]) => [key, faktory._path]));
501
+ const fontsStylesPath = join(destDir, "css/_fonts.css");
502
+ if (generationResults.fonts.length === 0) {
503
+ writeFileSync(fontsStylesPath, "");
504
+ } else {
505
+ const fontImports = [];
506
+ const fontBodies = [];
507
+ for (const font of generationResults.fonts) {
508
+ const { imports, body } = font._toCss();
509
+ fontImports.push(...imports);
510
+ fontBodies.push(body);
511
+ }
512
+ const importBlock = fontImports.length ? `${fontImports.join("\n")}
513
+
514
+ ` : "";
515
+ writeFileSync(fontsStylesPath, `${importBlock}@layer fonts { ${fontBodies.join(" ")} }`);
516
+ }
499
517
  const tsTokensPath = join(destDir, "types/css-tokens.d.ts");
500
518
  const tsVariableTokens = [...variableTokens].join("|");
501
519
  const tsTokensTypes = `
package/config/index.cjs CHANGED
@@ -5,8 +5,10 @@ const defineTemplates = require("../define-templates-Deq1aCbN.cjs");
5
5
  const defineConfig = (config) => {
6
6
  return config;
7
7
  };
8
+ exports.FontFactory = factories_index.FontFactory;
8
9
  exports.GlobalStylesFactory = factories_index.GlobalStylesFactory;
9
10
  exports.VariablesFactory = factories_index.VariablesFactory;
11
+ exports.defineFont = factories_index.defineFont;
10
12
  exports.defineGlobalStyles = factories_index.defineGlobalStyles;
11
13
  exports.defineMediaQuery = factories_index.defineMediaQuery;
12
14
  exports.defineVariables = factories_index.defineVariables;
package/config/index.js CHANGED
@@ -1,14 +1,16 @@
1
- import { GlobalStylesFactory, VariablesFactory, defineGlobalStyles, defineMediaQuery, defineVariables } from "../factories/index.js";
1
+ import { FontFactory, GlobalStylesFactory, VariablesFactory, defineFont, defineGlobalStyles, defineMediaQuery, defineVariables } from "../factories/index.js";
2
2
  import { T, a, d } from "../define-templates-CVhhgPnd.js";
3
3
  const defineConfig = (config) => {
4
4
  return config;
5
5
  };
6
6
  export {
7
+ FontFactory,
7
8
  GlobalStylesFactory,
8
9
  T as TemplateFactory,
9
10
  a as TemplatesFactory,
10
11
  VariablesFactory,
11
12
  defineConfig,
13
+ defineFont,
12
14
  defineGlobalStyles,
13
15
  defineMediaQuery,
14
16
  d as defineTemplates,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-DWQ4UQCp.cjs");
3
+ const parseStyles = require("../parse-styles-CX1WjafO.cjs");
4
4
  const toHash = require("../to-hash-C05Y906F.cjs");
5
5
  const cache_resolveDynamicConfigCache = require("../cache/resolve-dynamic-config-cache.cjs");
6
6
  const getDynamicStylesClassName = (styles) => {
@@ -1,4 +1,4 @@
1
- import { a as parseStyles } from "../parse-styles-g4NPgqhh.js";
1
+ import { a as parseStyles } from "../parse-styles-DIqJjXF3.js";
2
2
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
3
3
  import { resolveDynamicConfigCache } from "../cache/resolve-dynamic-config-cache.js";
4
4
  const getDynamicStylesClassName = (styles) => {
package/css/keyframes.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-DWQ4UQCp.cjs");
3
+ const parseStyles = require("../parse-styles-CX1WjafO.cjs");
4
4
  const toHash = require("../to-hash-C05Y906F.cjs");
5
5
  const keyframes = ({ animationName: _name, params: _params, appendInitialStyles, ...keyframes2 }) => {
6
6
  const modifyKeyframes = async (params = {}) => {
package/css/keyframes.js CHANGED
@@ -1,4 +1,4 @@
1
- import { p as parseAndJoinStyles } from "../parse-styles-g4NPgqhh.js";
1
+ import { p as parseAndJoinStyles } from "../parse-styles-DIqJjXF3.js";
2
2
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
3
3
  const keyframes = ({ animationName: _name, params: _params, appendInitialStyles, ...keyframes2 }) => {
4
4
  const modifyKeyframes = async (params = {}) => {
@@ -0,0 +1,28 @@
1
+ import { DefineFontOptions } from '../types/font-types';
2
+ export interface FontCss {
3
+ /** `@import url(...)` lines that must sit at the top of the stylesheet, before any `@layer`. */
4
+ imports: string[];
5
+ /** Body that goes inside the `@layer fonts { ... }` wrapper. */
6
+ body: string;
7
+ }
8
+ export declare class FontFactory {
9
+ readonly _options: DefineFontOptions;
10
+ readonly variable: string;
11
+ readonly fontFamily: string;
12
+ readonly className: string;
13
+ constructor(_options: DefineFontOptions);
14
+ get isDefineFont(): true;
15
+ /** Acts as a string equal to the resolved font-family value. */
16
+ toString(): string;
17
+ /** Inline-style helper: spread onto a React `style` prop. */
18
+ get style(): Record<string, string>;
19
+ /** Build the CSS pieces written to `_fonts.css`. */
20
+ _toCss(): FontCss;
21
+ }
22
+ /**
23
+ * Define a custom font that is registered globally as `@font-face` and exposed
24
+ * as a CSS variable. The returned object stringifies to its `font-family`
25
+ * value, and exposes `.variable`, `.fontFamily`, `.className`, and `.style`
26
+ * for use in styles and components.
27
+ */
28
+ export declare const defineFont: (options: DefineFontOptions) => FontFactory;
@@ -1,7 +1,133 @@
1
1
  "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
2
5
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
6
+ const dashCase = require("../dash-case-DIwKaYgE.cjs");
7
+ const toHash = require("../to-hash-C05Y906F.cjs");
3
8
  const css_media = require("../css/media.cjs");
4
9
  const defineTemplates = require("../define-templates-Deq1aCbN.cjs");
10
+ const FONT_FORMAT_BY_EXTENSION = {
11
+ woff2: "woff2",
12
+ woff: "woff",
13
+ ttf: "truetype",
14
+ otf: "opentype",
15
+ eot: "embedded-opentype",
16
+ svg: "svg",
17
+ ttc: "collection"
18
+ };
19
+ const detectFontFormat = (url) => {
20
+ const cleaned = url.split("?")[0].split("#")[0];
21
+ const dot = cleaned.lastIndexOf(".");
22
+ if (dot === -1) return void 0;
23
+ const ext = cleaned.slice(dot + 1).toLowerCase();
24
+ return FONT_FORMAT_BY_EXTENSION[ext];
25
+ };
26
+ const toFontSrc = (entry) => {
27
+ if (typeof entry === "string") return { url: entry, format: detectFontFormat(entry) };
28
+ return entry;
29
+ };
30
+ const normalizeSources = (src) => {
31
+ if (Array.isArray(src)) return src.map(toFontSrc);
32
+ return [toFontSrc(src)];
33
+ };
34
+ const normalizeVariable = (variable) => {
35
+ const trimmed = variable.trim();
36
+ const stripped = trimmed.replace(/^--/, "");
37
+ if (!stripped) throw new Error(`defineFont: invalid \`variable\` value "${variable}".`);
38
+ return `--${dashCase.dashCase(stripped)}`;
39
+ };
40
+ const deriveVariable = (options) => {
41
+ const hashSource = [options.name, options.fallback, "variants" in options ? options.variants : void 0, "import" in options ? options.import : void 0];
42
+ return `--font-${dashCase.dashCase(options.name)}-${toHash.toHash(hashSource, 6)}`;
43
+ };
44
+ const quoteFamily = (name) => {
45
+ if (/^["'].*["']$/.test(name)) return name;
46
+ if (/\s/.test(name)) return `"${name}"`;
47
+ return name;
48
+ };
49
+ const buildFontFamilyValue = (name, fallback) => {
50
+ const head = quoteFamily(name);
51
+ if (!fallback || fallback.length === 0) return head;
52
+ return [head, ...fallback].join(", ");
53
+ };
54
+ const formatSrc = (src) => {
55
+ const parts = [`url("${src.url}")`];
56
+ if (src.format) parts.push(`format("${src.format}")`);
57
+ if (src.tech) parts.push(`tech(${src.tech})`);
58
+ return parts.join(" ");
59
+ };
60
+ const variantToFontFace = (name, variant, defaultDisplay) => {
61
+ const sources = normalizeSources(variant.src);
62
+ if (sources.length === 0) {
63
+ throw new Error(`defineFont(${name}): variant must declare at least one \`src\`.`);
64
+ }
65
+ const lines = [`font-family: ${quoteFamily(name)};`, `src: ${sources.map(formatSrc).join(", ")};`, `font-display: ${variant.display ?? defaultDisplay};`];
66
+ if (variant.weight !== void 0) lines.push(`font-weight: ${variant.weight};`);
67
+ if (variant.style !== void 0) lines.push(`font-style: ${variant.style};`);
68
+ if (variant.stretch !== void 0) lines.push(`font-stretch: ${variant.stretch};`);
69
+ if (variant.unicodeRange !== void 0) lines.push(`unicode-range: ${variant.unicodeRange};`);
70
+ if (variant.ascentOverride !== void 0) lines.push(`ascent-override: ${variant.ascentOverride};`);
71
+ if (variant.descentOverride !== void 0) lines.push(`descent-override: ${variant.descentOverride};`);
72
+ if (variant.lineGapOverride !== void 0) lines.push(`line-gap-override: ${variant.lineGapOverride};`);
73
+ if (variant.sizeAdjust !== void 0) lines.push(`size-adjust: ${variant.sizeAdjust};`);
74
+ return `@font-face { ${lines.join(" ")} }`;
75
+ };
76
+ class FontFactory {
77
+ constructor(_options) {
78
+ __publicField(this, "variable");
79
+ __publicField(this, "fontFamily");
80
+ __publicField(this, "className");
81
+ this._options = _options;
82
+ if (!_options || !_options.name) {
83
+ throw new Error("defineFont: `name` is required.");
84
+ }
85
+ if ("variants" in _options && "import" in _options && _options.import !== void 0 && _options.variants !== void 0) {
86
+ throw new Error("defineFont: provide either `variants` or `import`, not both.");
87
+ }
88
+ if (!("variants" in _options && _options.variants) && !("import" in _options && _options.import)) {
89
+ throw new Error("defineFont: must provide either `variants` or `import`.");
90
+ }
91
+ this.variable = _options.variable ? normalizeVariable(_options.variable) : deriveVariable(_options);
92
+ this.fontFamily = buildFontFamilyValue(_options.name, _options.fallback);
93
+ this.className = `font-${dashCase.dashCase(_options.name)}`;
94
+ }
95
+ get isDefineFont() {
96
+ return true;
97
+ }
98
+ /** Acts as a string equal to the resolved font-family value. */
99
+ toString() {
100
+ return this.fontFamily;
101
+ }
102
+ /** Inline-style helper: spread onto a React `style` prop. */
103
+ get style() {
104
+ return {
105
+ fontFamily: this.fontFamily,
106
+ [this.variable]: this.fontFamily
107
+ };
108
+ }
109
+ /** Build the CSS pieces written to `_fonts.css`. */
110
+ _toCss() {
111
+ const imports = [];
112
+ const blocks = [];
113
+ if ("import" in this._options && this._options.import) {
114
+ imports.push(`@import url("${this._options.import}");`);
115
+ } else if ("variants" in this._options && this._options.variants) {
116
+ const display = this._options.display ?? "swap";
117
+ for (const variant of this._options.variants) {
118
+ blocks.push(variantToFontFace(this._options.name, variant, display));
119
+ }
120
+ }
121
+ blocks.push(
122
+ `:root { ${this.variable}: ${this.fontFamily}; }`,
123
+ `.${this.className} { ${this.variable}: ${this.fontFamily}; font-family: var(${this.variable}); }`
124
+ );
125
+ return { imports, body: blocks.join(" ") };
126
+ }
127
+ }
128
+ const defineFont = (options) => {
129
+ return new FontFactory(options);
130
+ };
5
131
  class GlobalStylesFactory {
6
132
  constructor(_current) {
7
133
  this._current = _current;
@@ -30,8 +156,10 @@ const defineVariables = (variables) => {
30
156
  exports.TemplateFactory = defineTemplates.TemplateFactory;
31
157
  exports.TemplatesFactory = defineTemplates.TemplatesFactory;
32
158
  exports.defineTemplates = defineTemplates.defineTemplates;
159
+ exports.FontFactory = FontFactory;
33
160
  exports.GlobalStylesFactory = GlobalStylesFactory;
34
161
  exports.VariablesFactory = VariablesFactory;
162
+ exports.defineFont = defineFont;
35
163
  exports.defineGlobalStyles = defineGlobalStyles;
36
164
  exports.defineMediaQuery = defineMediaQuery;
37
165
  exports.defineVariables = defineVariables;
@@ -1,3 +1,4 @@
1
+ export * from './define-font';
1
2
  export * from './define-global-styles';
2
3
  export * from './define-media-query';
3
4
  export * from './define-variables';
@@ -1,5 +1,131 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { d as dashCase } from "../dash-case-DblXvymC.js";
5
+ import { t as toHash } from "../to-hash-DAN2LcHK.js";
1
6
  import { media } from "../css/media.js";
2
7
  import { T, a, d } from "../define-templates-CVhhgPnd.js";
8
+ const FONT_FORMAT_BY_EXTENSION = {
9
+ woff2: "woff2",
10
+ woff: "woff",
11
+ ttf: "truetype",
12
+ otf: "opentype",
13
+ eot: "embedded-opentype",
14
+ svg: "svg",
15
+ ttc: "collection"
16
+ };
17
+ const detectFontFormat = (url) => {
18
+ const cleaned = url.split("?")[0].split("#")[0];
19
+ const dot = cleaned.lastIndexOf(".");
20
+ if (dot === -1) return void 0;
21
+ const ext = cleaned.slice(dot + 1).toLowerCase();
22
+ return FONT_FORMAT_BY_EXTENSION[ext];
23
+ };
24
+ const toFontSrc = (entry) => {
25
+ if (typeof entry === "string") return { url: entry, format: detectFontFormat(entry) };
26
+ return entry;
27
+ };
28
+ const normalizeSources = (src) => {
29
+ if (Array.isArray(src)) return src.map(toFontSrc);
30
+ return [toFontSrc(src)];
31
+ };
32
+ const normalizeVariable = (variable) => {
33
+ const trimmed = variable.trim();
34
+ const stripped = trimmed.replace(/^--/, "");
35
+ if (!stripped) throw new Error(`defineFont: invalid \`variable\` value "${variable}".`);
36
+ return `--${dashCase(stripped)}`;
37
+ };
38
+ const deriveVariable = (options) => {
39
+ const hashSource = [options.name, options.fallback, "variants" in options ? options.variants : void 0, "import" in options ? options.import : void 0];
40
+ return `--font-${dashCase(options.name)}-${toHash(hashSource, 6)}`;
41
+ };
42
+ const quoteFamily = (name) => {
43
+ if (/^["'].*["']$/.test(name)) return name;
44
+ if (/\s/.test(name)) return `"${name}"`;
45
+ return name;
46
+ };
47
+ const buildFontFamilyValue = (name, fallback) => {
48
+ const head = quoteFamily(name);
49
+ if (!fallback || fallback.length === 0) return head;
50
+ return [head, ...fallback].join(", ");
51
+ };
52
+ const formatSrc = (src) => {
53
+ const parts = [`url("${src.url}")`];
54
+ if (src.format) parts.push(`format("${src.format}")`);
55
+ if (src.tech) parts.push(`tech(${src.tech})`);
56
+ return parts.join(" ");
57
+ };
58
+ const variantToFontFace = (name, variant, defaultDisplay) => {
59
+ const sources = normalizeSources(variant.src);
60
+ if (sources.length === 0) {
61
+ throw new Error(`defineFont(${name}): variant must declare at least one \`src\`.`);
62
+ }
63
+ const lines = [`font-family: ${quoteFamily(name)};`, `src: ${sources.map(formatSrc).join(", ")};`, `font-display: ${variant.display ?? defaultDisplay};`];
64
+ if (variant.weight !== void 0) lines.push(`font-weight: ${variant.weight};`);
65
+ if (variant.style !== void 0) lines.push(`font-style: ${variant.style};`);
66
+ if (variant.stretch !== void 0) lines.push(`font-stretch: ${variant.stretch};`);
67
+ if (variant.unicodeRange !== void 0) lines.push(`unicode-range: ${variant.unicodeRange};`);
68
+ if (variant.ascentOverride !== void 0) lines.push(`ascent-override: ${variant.ascentOverride};`);
69
+ if (variant.descentOverride !== void 0) lines.push(`descent-override: ${variant.descentOverride};`);
70
+ if (variant.lineGapOverride !== void 0) lines.push(`line-gap-override: ${variant.lineGapOverride};`);
71
+ if (variant.sizeAdjust !== void 0) lines.push(`size-adjust: ${variant.sizeAdjust};`);
72
+ return `@font-face { ${lines.join(" ")} }`;
73
+ };
74
+ class FontFactory {
75
+ constructor(_options) {
76
+ __publicField(this, "variable");
77
+ __publicField(this, "fontFamily");
78
+ __publicField(this, "className");
79
+ this._options = _options;
80
+ if (!_options || !_options.name) {
81
+ throw new Error("defineFont: `name` is required.");
82
+ }
83
+ if ("variants" in _options && "import" in _options && _options.import !== void 0 && _options.variants !== void 0) {
84
+ throw new Error("defineFont: provide either `variants` or `import`, not both.");
85
+ }
86
+ if (!("variants" in _options && _options.variants) && !("import" in _options && _options.import)) {
87
+ throw new Error("defineFont: must provide either `variants` or `import`.");
88
+ }
89
+ this.variable = _options.variable ? normalizeVariable(_options.variable) : deriveVariable(_options);
90
+ this.fontFamily = buildFontFamilyValue(_options.name, _options.fallback);
91
+ this.className = `font-${dashCase(_options.name)}`;
92
+ }
93
+ get isDefineFont() {
94
+ return true;
95
+ }
96
+ /** Acts as a string equal to the resolved font-family value. */
97
+ toString() {
98
+ return this.fontFamily;
99
+ }
100
+ /** Inline-style helper: spread onto a React `style` prop. */
101
+ get style() {
102
+ return {
103
+ fontFamily: this.fontFamily,
104
+ [this.variable]: this.fontFamily
105
+ };
106
+ }
107
+ /** Build the CSS pieces written to `_fonts.css`. */
108
+ _toCss() {
109
+ const imports = [];
110
+ const blocks = [];
111
+ if ("import" in this._options && this._options.import) {
112
+ imports.push(`@import url("${this._options.import}");`);
113
+ } else if ("variants" in this._options && this._options.variants) {
114
+ const display = this._options.display ?? "swap";
115
+ for (const variant of this._options.variants) {
116
+ blocks.push(variantToFontFace(this._options.name, variant, display));
117
+ }
118
+ }
119
+ blocks.push(
120
+ `:root { ${this.variable}: ${this.fontFamily}; }`,
121
+ `.${this.className} { ${this.variable}: ${this.fontFamily}; font-family: var(${this.variable}); }`
122
+ );
123
+ return { imports, body: blocks.join(" ") };
124
+ }
125
+ }
126
+ const defineFont = (options) => {
127
+ return new FontFactory(options);
128
+ };
3
129
  class GlobalStylesFactory {
4
130
  constructor(_current) {
5
131
  this._current = _current;
@@ -26,10 +152,12 @@ const defineVariables = (variables) => {
26
152
  return new VariablesFactory(variables);
27
153
  };
28
154
  export {
155
+ FontFactory,
29
156
  GlobalStylesFactory,
30
157
  T as TemplateFactory,
31
158
  a as TemplatesFactory,
32
159
  VariablesFactory,
160
+ defineFont,
33
161
  defineGlobalStyles,
34
162
  defineMediaQuery,
35
163
  d as defineTemplates,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const classNameGenerator = require("../class-name-generator-CYgwnxnZ.cjs");
3
+ const classNameGenerator = require("../class-name-generator-DVuPkyrS.cjs");
4
4
  const dashCase = require("../dash-case-DIwKaYgE.cjs");
5
5
  class StyledGenerator extends classNameGenerator.StylesGenerator {
6
6
  constructor(tagName, _params) {
@@ -1,5 +1,5 @@
1
- import { S as StylesGenerator } from "../class-name-generator-BKMSRP7N.js";
2
- import { C } from "../class-name-generator-BKMSRP7N.js";
1
+ import { S as StylesGenerator } from "../class-name-generator-DkByUzHU.js";
2
+ import { C } from "../class-name-generator-DkByUzHU.js";
3
3
  import { d as dashCase } from "../dash-case-DblXvymC.js";
4
4
  class StyledGenerator extends StylesGenerator {
5
5
  constructor(tagName, _params) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const classNameGenerator = require("../class-name-generator-CYgwnxnZ.cjs");
3
+ const classNameGenerator = require("../class-name-generator-DVuPkyrS.cjs");
4
4
  const classNameInstance = (params) => {
5
5
  const generator = new classNameGenerator.ClassNameGenerator(params);
6
6
  const createClass = (classNameStr) => {
@@ -1,4 +1,4 @@
1
- import { C as ClassNameGenerator } from "../class-name-generator-BKMSRP7N.js";
1
+ import { C as ClassNameGenerator } from "../class-name-generator-DkByUzHU.js";
2
2
  const classNameInstance = (params) => {
3
3
  const generator = new ClassNameGenerator(params);
4
4
  const createClass = (classNameStr) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salty-css/core",
3
- "version": "0.1.0-alpha.20",
3
+ "version": "0.1.0-alpha.21",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "typings": "./dist/index.d.ts",
@@ -153,6 +153,7 @@ const parseStyles = async (styles, currentScope = "", config, omitTemplates = fa
153
153
  if (typeof value === "object") {
154
154
  if (!value) return void 0;
155
155
  if (value.isColor) return toString(value.toString());
156
+ if (value.isDefineFont) return toString(value.toString());
156
157
  if (_key === "defaultVariants") return void 0;
157
158
  if (_key === "variants") {
158
159
  const variantEntries = Object.entries(value);
@@ -152,6 +152,7 @@ const parseStyles = async (styles, currentScope = "", config, omitTemplates = fa
152
152
  if (typeof value === "object") {
153
153
  if (!value) return void 0;
154
154
  if (value.isColor) return toString(value.toString());
155
+ if (value.isDefineFont) return toString(value.toString());
155
156
  if (_key === "defaultVariants") return void 0;
156
157
  if (_key === "variants") {
157
158
  const variantEntries = Object.entries(value);
package/parsers/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-DWQ4UQCp.cjs");
3
+ const parseStyles = require("../parse-styles-CX1WjafO.cjs");
4
4
  const dashCase = require("../dash-case-DIwKaYgE.cjs");
5
5
  const toHash = require("../to-hash-C05Y906F.cjs");
6
6
  const parseTemplates = async (obj, path = []) => {
package/parsers/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { p as parseAndJoinStyles } from "../parse-styles-g4NPgqhh.js";
2
- import { a, c, d, b, r } from "../parse-styles-g4NPgqhh.js";
1
+ import { p as parseAndJoinStyles } from "../parse-styles-DIqJjXF3.js";
2
+ import { a, c, d, b, r } from "../parse-styles-DIqJjXF3.js";
3
3
  import { d as dashCase } from "../dash-case-DblXvymC.js";
4
4
  import { t as toHash } from "../to-hash-DAN2LcHK.js";
5
5
  const parseTemplates = async (obj, path = []) => {
package/runtime/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const parseStyles = require("../parse-styles-DWQ4UQCp.cjs");
3
+ const parseStyles = require("../parse-styles-CX1WjafO.cjs");
4
4
  const defineRuntime = (config) => {
5
5
  const getDynamicStylesCss = async (styles, scope) => {
6
6
  const parsed = await parseStyles.parseStyles(styles, scope, config);
package/runtime/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as parseStyles } from "../parse-styles-g4NPgqhh.js";
1
+ import { a as parseStyles } from "../parse-styles-DIqJjXF3.js";
2
2
  const defineRuntime = (config) => {
3
3
  const getDynamicStylesCss = async (styles, scope) => {
4
4
  const parsed = await parseStyles(styles, scope, config);
@@ -1,5 +1,6 @@
1
1
  import { BaseStyles, CssStyles, MediaQueryStyles } from '../types';
2
2
  import { OrString } from '../types/util-types';
3
+ export * from './font-types';
3
4
  export type GlobalStyles = Record<string, BaseStyles>;
4
5
  export type CssVariableTokensObject = Record<string, unknown>;
5
6
  export interface CssResponsiveVariables {
@@ -91,4 +92,3 @@ export interface CachedConfig {
91
92
  [key: string]: string;
92
93
  };
93
94
  }
94
- export {};
@@ -0,0 +1,53 @@
1
+ export type FontDisplay = 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
2
+ export type FontFormat = 'woff2' | 'woff' | 'truetype' | 'opentype' | 'embedded-opentype' | 'svg' | 'collection';
3
+ export type FontStyle = 'normal' | 'italic' | 'oblique' | (string & {});
4
+ export type FontWeight = number | 'normal' | 'bold' | 'lighter' | 'bolder' | (string & {});
5
+ export interface FontSrc {
6
+ url: string;
7
+ format?: FontFormat;
8
+ /** Optional `tech(...)` descriptor passed straight through to @font-face. */
9
+ tech?: string;
10
+ }
11
+ export interface FontVariant {
12
+ weight?: FontWeight;
13
+ style?: FontStyle;
14
+ stretch?: string;
15
+ display?: FontDisplay;
16
+ unicodeRange?: string;
17
+ ascentOverride?: string;
18
+ descentOverride?: string;
19
+ lineGapOverride?: string;
20
+ sizeAdjust?: string;
21
+ /**
22
+ * One or more font sources. Strings are treated as URLs and the `format()`
23
+ * descriptor is auto-detected from the file extension when possible. Use
24
+ * the `{ url, format }` object form for CDN/extensionless URLs where the
25
+ * format must be set explicitly.
26
+ */
27
+ src: string | FontSrc | (string | FontSrc)[];
28
+ }
29
+ interface DefineFontBase {
30
+ /** CSS `font-family` value users will see in styles. */
31
+ name: string;
32
+ /**
33
+ * CSS variable name. Accepts `--font-inter` or `font-inter`; we normalize.
34
+ * Optional — when omitted, a deterministic name is derived from the other
35
+ * inputs as `--font-<name>-<hash>`.
36
+ */
37
+ variable?: string;
38
+ /** Default `font-display` applied to variants that don't set their own. */
39
+ display?: FontDisplay;
40
+ /** Family fallbacks appended after `name` in the generated `font-family` string. */
41
+ fallback?: string[];
42
+ }
43
+ export interface DefineFontVariantsOptions extends DefineFontBase {
44
+ variants: FontVariant[];
45
+ import?: never;
46
+ }
47
+ export interface DefineFontImportOptions extends DefineFontBase {
48
+ /** Remote stylesheet URL (e.g. Google Fonts). Emitted as `@import url(...)`. */
49
+ import: string;
50
+ variants?: never;
51
+ }
52
+ export type DefineFontOptions = DefineFontVariantsOptions | DefineFontImportOptions;
53
+ export {};