@tenphi/tasty 3.9.11 → 3.11.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/README.md +399 -380
- package/dist/babel-types-BKDTT4--.d.ts +18 -0
- package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
- package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
- package/dist/chunks/build-config-5d8WlQbq.js +60 -0
- package/dist/chunks/build-config-5d8WlQbq.js.map +1 -0
- package/dist/chunks/{collector-D2ARGT2L.js → collector-CjCak4iE.js} +8 -6
- package/dist/chunks/collector-CjCak4iE.js.map +1 -0
- package/dist/chunks/{config-engine-DEmmPPdp.js → config-engine-uOFYZidB.js} +4 -4
- package/dist/chunks/{config-engine-DEmmPPdp.js.map → config-engine-uOFYZidB.js.map} +1 -1
- package/dist/chunks/config-file-DlZXJMj8.js +25 -0
- package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
- package/dist/chunks/{css-definitions-DotDkIXt.js → css-definitions-ubrHP97m.js} +130 -96
- package/dist/chunks/css-definitions-ubrHP97m.js.map +1 -0
- package/dist/chunks/{debug-D8pg9RGI.js → debug-CJ31U_WE.js} +6 -6
- package/dist/chunks/{debug-D8pg9RGI.js.map → debug-CJ31U_WE.js.map} +1 -1
- package/dist/chunks/{dsl-DRhFWQzo.js → dsl-DYSJc1-T.js} +11 -6
- package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
- package/dist/chunks/{react-runtime-BpxlOTbO.js → react-runtime-CyyCI02N.js} +190 -63
- package/dist/chunks/react-runtime-CyyCI02N.js.map +1 -0
- package/dist/chunks/{runtime-engine-CKFXUH-A.js → runtime-engine-BQmOu5-W.js} +69 -53
- package/dist/chunks/runtime-engine-BQmOu5-W.js.map +1 -0
- package/dist/chunks/{runtime-state-BxaLhVdP.js → runtime-state-DEsyUMU-.js} +28 -3
- package/dist/chunks/runtime-state-DEsyUMU-.js.map +1 -0
- package/dist/chunks/{style-engine-BBbBuSkT.js → style-engine-BX2LshDZ.js} +309 -103
- package/dist/chunks/style-engine-BX2LshDZ.js.map +1 -0
- package/dist/chunks/{zero-engine-gJrKBRiw.js → zero-engine-Dw0P4yGM.js} +62 -27
- package/dist/chunks/zero-engine-Dw0P4yGM.js.map +1 -0
- package/dist/{collector-w4mBj5gd.d.ts → collector-CTU-nl0g.d.ts} +2 -2
- package/dist/{config-CZWCzBr6.d.ts → config-CcxqSZI2.d.ts} +35 -16
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +7 -7
- package/dist/hydrate-CRyjjAyl.d.ts +22 -0
- package/dist/{index-G9JuwM9p.d.ts → index-xtey-KDu.d.ts} +43 -13
- package/dist/index.d.ts +4 -4
- package/dist/index.js +8 -8
- package/dist/{merge-styles-VXwJ0Frr.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -2
- package/dist/ssr/astro.js +4 -4
- package/dist/ssr/client.d.ts +2 -0
- package/dist/ssr/client.js +2 -0
- package/dist/ssr/index.d.ts +3 -22
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +1 -1
- package/dist/ssr/next-config.js +13 -18
- package/dist/ssr/next-config.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +3 -3
- package/dist/static/index.d.ts +2 -2
- package/dist/{types-JEY8PVYP.d.ts → types-DJEM_BOJ.d.ts} +6 -6
- package/dist/zero/babel.d.ts +67 -2
- package/dist/zero/babel.js +47 -44
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +58 -3
- package/dist/zero/index.js +24 -2
- package/dist/zero/index.js.map +1 -0
- package/dist/zero/next.d.ts +2 -1
- package/dist/zero/next.js +3 -7
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +4 -1
- package/docs/adoption.md +25 -17
- package/docs/ai-agents.md +37 -34
- package/docs/comparison.md +9 -8
- package/docs/configuration.md +174 -59
- package/docs/debug.md +3 -2
- package/docs/design-system.md +117 -15
- package/docs/dsl.md +122 -81
- package/docs/getting-started.md +81 -24
- package/docs/injector.md +39 -40
- package/docs/methodology.md +45 -20
- package/docs/migration-v3.md +2 -2
- package/docs/pipeline.md +20 -17
- package/docs/plugins.md +20 -40
- package/docs/react-api.md +28 -8
- package/docs/runtime-benchmarks.md +4 -4
- package/docs/ssr.md +83 -63
- package/docs/styles.md +27 -17
- package/docs/tasty-static.md +142 -55
- package/package.json +24 -10
- package/dist/babel-rZcEwPLM.d.ts +0 -82
- package/dist/chunks/async-storage-CG5HnUT5.js +0 -20
- package/dist/chunks/async-storage-CG5HnUT5.js.map +0 -1
- package/dist/chunks/build-config-CL4eRu2z.js +0 -42
- package/dist/chunks/build-config-CL4eRu2z.js.map +0 -1
- package/dist/chunks/collector-D2ARGT2L.js.map +0 -1
- package/dist/chunks/css-definitions-DotDkIXt.js.map +0 -1
- package/dist/chunks/dsl-DRhFWQzo.js.map +0 -1
- package/dist/chunks/react-runtime-BpxlOTbO.js.map +0 -1
- package/dist/chunks/runtime-engine-CKFXUH-A.js.map +0 -1
- package/dist/chunks/runtime-state-BxaLhVdP.js.map +0 -1
- package/dist/chunks/style-engine-BBbBuSkT.js.map +0 -1
- package/dist/chunks/zero-engine-gJrKBRiw.js.map +0 -1
package/dist/zero/next.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { t as loadConfigFile } from "../chunks/config-file-DlZXJMj8.js";
|
|
1
2
|
import * as path from "path";
|
|
2
|
-
import { createJiti } from "jiti";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
//#region src/zero/next.ts
|
|
@@ -64,12 +64,8 @@ function withTastyZero(options = {}) {
|
|
|
64
64
|
injectImport: true,
|
|
65
65
|
...mode ? { mode } : {}
|
|
66
66
|
};
|
|
67
|
-
if (wpAbsoluteConfigFile)
|
|
68
|
-
|
|
69
|
-
babelPluginOptions.config = () => {
|
|
70
|
-
return jiti(wpAbsoluteConfigFile);
|
|
71
|
-
};
|
|
72
|
-
} else if (tastyConfig) babelPluginOptions.config = tastyConfig;
|
|
67
|
+
if (wpAbsoluteConfigFile) babelPluginOptions.config = () => loadConfigFile(wpAbsoluteConfigFile, wpProjectDir);
|
|
68
|
+
else if (tastyConfig) babelPluginOptions.config = tastyConfig;
|
|
73
69
|
if (wpAllDeps.length > 0) babelPluginOptions.configDeps = wpAllDeps;
|
|
74
70
|
const babelPluginConfig = [babelPluginPath, babelPluginOptions];
|
|
75
71
|
const existingRule = config.module?.rules?.find((rule) => rule.use?.loader === "babel-loader" || rule.use?.some?.((u) => u.loader === "babel-loader"));
|
package/dist/zero/next.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.js","names":[],"sources":["../../src/zero/next.ts"],"sourcesContent":["/**\n * Next.js configuration wrapper for tasty-zero.\n *\n * Supports both webpack and Turbopack bundlers:\n * - **webpack**: Injects a babel-loader rule with the tasty-zero Babel plugin\n * via `webpack()` config hook. Config is passed as a jiti factory function.\n * - **Turbopack**: Adds a `turbopack.rules` entry with babel-loader and\n * JSON-serializable options (`configFile` path instead of a function).\n * The Babel plugin loads the config internally via jiti.\n *\n * The generated CSS is injected automatically — `@tenphi/tasty/static`\n * imports are replaced with an import of the output CSS file at build time.\n * No manual CSS import in layout files is needed.\n *\n * @example\n * ```javascript\n * // next.config.js\n * const { withTastyZero } = require('@tenphi/tasty/zero/next');\n *\n * module.exports = withTastyZero({\n * output: 'public/tasty.css',\n * configFile: './app/tasty-zero.config.ts',\n * })({\n * // your Next.js config\n * });\n * ```\n */\n\nimport { createRequire } from 'module';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport { createJiti } from 'jiti';\n\nimport type { TastyZeroBabelOptions, TastyZeroConfig } from './babel';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Next.js types (inline to avoid requiring next as a dependency)\ninterface WebpackConfigContext {\n isServer: boolean;\n dev: boolean;\n buildId: string;\n dir: string;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- webpack/Next.js config types are complex */\ninterface TurbopackLoaderItem {\n loader: string;\n options?: Record<string, unknown>;\n}\n\ninterface TurbopackRuleConfig {\n loaders: (string | TurbopackLoaderItem)[];\n as?: string;\n condition?: unknown;\n}\n\ninterface TurbopackConfig {\n rules?: Record<string, TurbopackRuleConfig | TurbopackRuleConfig[]>;\n [key: string]: unknown;\n}\n\ninterface NextConfig {\n webpack?: (config: any, context: WebpackConfigContext) => any;\n turbopack?: TurbopackConfig;\n [key: string]: unknown;\n}\n\nexport interface TastyZeroNextOptions {\n /**\n * Output path for CSS relative to project root.\n * @default 'public/tasty.css'\n */\n output?: string;\n\n /**\n * Whether to enable the plugin.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Tasty configuration for build-time processing.\n * For static configs that don't change during dev.\n *\n * For configs that depend on theme files, use `configFile` instead.\n */\n config?: TastyZeroConfig;\n\n /**\n * Path to a TypeScript/JavaScript module that exports the tasty zero config\n * as its default export. The module is re-evaluated on each\n * compilation, enabling hot reload when the file (or its imports) change.\n *\n * @example './app/tasty-zero.config.ts'\n */\n configFile?: string;\n\n /**\n * Extra file paths (relative to project root) that the config depends on.\n * When any of these files change, the Babel cache is invalidated and\n * the config is re-evaluated.\n *\n * The `configFile` itself is always tracked automatically.\n * Use this for transitive dependencies that aren't directly imported\n * by the config file, or when using `config` instead of `configFile`.\n *\n * @example ['./app/theme.ts']\n */\n configDeps?: string[];\n\n /**\n * Output mode for extracted CSS.\n *\n * - `'file'` (default): CSS is written to a single output file.\n * - `'inject'`: CSS is embedded inline in JS and injected at runtime.\n * No CSS file is written. Best for reusable components and extensions.\n *\n * When `mode` is `'inject'`, `output` is ignored.\n *\n * @default 'file'\n */\n mode?: 'file' | 'inject';\n}\n\n/**\n * Next.js configuration wrapper for tasty-zero.\n * Configures both webpack and Turbopack bundlers automatically.\n */\nexport function withTastyZero(options: TastyZeroNextOptions = {}) {\n const {\n output = 'public/tasty.css',\n enabled = true,\n config: tastyConfig,\n configFile,\n configDeps = [],\n mode,\n } = options;\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n if (!enabled) {\n return nextConfig;\n }\n\n const projectDir = process.cwd();\n const absoluteOutput = path.resolve(projectDir, output);\n const babelPluginPath = path.resolve(__dirname, 'babel.js');\n\n const absoluteConfigFile = configFile\n ? path.resolve(projectDir, configFile)\n : undefined;\n\n const allDeps = [\n ...(absoluteConfigFile ? [absoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(projectDir, dep)),\n ];\n\n // --- Turbopack configuration ---\n // Turbopack loader options must be JSON-serializable (no functions).\n // The Babel plugin loads config internally via `configFile` path + jiti.\n const turbopackBabelOptions: Record<string, unknown> = {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [\n [\n babelPluginPath,\n {\n output: absoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n ...(absoluteConfigFile\n ? { configFile: absoluteConfigFile }\n : tastyConfig\n ? { config: tastyConfig }\n : {}),\n ...(allDeps.length > 0 ? { configDeps: allDeps } : {}),\n },\n ],\n ],\n };\n\n const existingTurbopack = nextConfig.turbopack || {};\n const existingRules = existingTurbopack.rules || {};\n\n const existingExperimental =\n (nextConfig.experimental as Record<string, unknown>) || {};\n\n return {\n ...nextConfig,\n\n experimental: {\n ...existingExperimental,\n turbopackUseBuiltinBabel: true,\n },\n\n turbopack: {\n ...existingTurbopack,\n rules: {\n ...existingRules,\n '*.{ts,tsx,js,jsx}': {\n condition: { not: 'foreign' },\n loaders: [\n {\n loader: 'babel-loader',\n options: turbopackBabelOptions,\n },\n ],\n },\n },\n },\n\n webpack(config: any, context: WebpackConfigContext) {\n const { dir } = context;\n\n const wpProjectDir = dir || projectDir;\n const wpAbsoluteOutput = path.resolve(wpProjectDir, output);\n const projectRequire = createRequire(\n path.resolve(wpProjectDir, 'package.json'),\n );\n\n const wpAbsoluteConfigFile = configFile\n ? path.resolve(wpProjectDir, configFile)\n : undefined;\n\n const wpAllDeps = [\n ...(wpAbsoluteConfigFile ? [wpAbsoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(wpProjectDir, dep)),\n ];\n\n const babelPluginOptions: TastyZeroBabelOptions = {\n output: wpAbsoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n };\n\n if (wpAbsoluteConfigFile) {\n const jiti = createJiti(wpProjectDir, {\n moduleCache: false,\n });\n\n babelPluginOptions.config = () => {\n return jiti(wpAbsoluteConfigFile) as TastyZeroConfig;\n };\n } else if (tastyConfig) {\n babelPluginOptions.config = tastyConfig;\n }\n\n if (wpAllDeps.length > 0) {\n babelPluginOptions.configDeps = wpAllDeps;\n }\n\n const babelPluginConfig = [babelPluginPath, babelPluginOptions];\n\n const existingRule = config.module?.rules?.find(\n (rule: any) =>\n rule.use?.loader === 'babel-loader' ||\n rule.use?.some?.((u: any) => u.loader === 'babel-loader'),\n );\n\n if (existingRule) {\n const babelUse = Array.isArray(existingRule.use)\n ? existingRule.use.find((u: any) => u.loader === 'babel-loader')\n : existingRule.use;\n\n if (babelUse?.options) {\n babelUse.options.plugins = babelUse.options.plugins || [];\n babelUse.options.plugins.push(babelPluginConfig);\n }\n } else {\n config.module = config.module || {};\n config.module.rules = config.module.rules || [];\n config.module.rules.push({\n test: /\\.(tsx?|jsx?)$/,\n exclude: /node_modules/,\n use: [\n {\n loader: projectRequire.resolve('babel-loader'),\n options: {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [babelPluginConfig],\n },\n },\n ],\n });\n }\n\n if (typeof nextConfig.webpack === 'function') {\n return nextConfig.webpack(config, context);\n }\n\n return config;\n },\n };\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"next.js","names":[],"sources":["../../src/zero/next.ts"],"sourcesContent":["/**\n * Next.js configuration wrapper for tasty-zero.\n *\n * Supports both webpack and Turbopack bundlers:\n * - **webpack**: Injects a babel-loader rule with the tasty-zero Babel plugin\n * via `webpack()` config hook. Config is passed as a jiti factory function.\n * - **Turbopack**: Adds a `turbopack.rules` entry with babel-loader and\n * JSON-serializable options (`configFile` path instead of a function).\n * The Babel plugin loads the config internally via jiti.\n *\n * The generated CSS is injected automatically — `@tenphi/tasty/static`\n * imports are replaced with an import of the output CSS file at build time.\n * No manual CSS import in layout files is needed.\n *\n * @example\n * ```javascript\n * // next.config.js\n * const { withTastyZero } = require('@tenphi/tasty/zero/next');\n *\n * module.exports = withTastyZero({\n * output: 'public/tasty.css',\n * configFile: './app/tasty-zero.config.ts',\n * })({\n * // your Next.js config\n * });\n * ```\n */\n\nimport { createRequire } from 'module';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport type { TastyZeroBabelOptions, TastyZeroConfig } from './babel';\nimport { loadConfigFile } from './config-file';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Next.js types (inline to avoid requiring next as a dependency)\ninterface WebpackConfigContext {\n isServer: boolean;\n dev: boolean;\n buildId: string;\n dir: string;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- webpack/Next.js config types are complex */\ninterface TurbopackLoaderItem {\n loader: string;\n options?: Record<string, unknown>;\n}\n\ninterface TurbopackRuleConfig {\n loaders: (string | TurbopackLoaderItem)[];\n as?: string;\n condition?: unknown;\n}\n\ninterface TurbopackConfig {\n rules?: Record<string, TurbopackRuleConfig | TurbopackRuleConfig[]>;\n [key: string]: unknown;\n}\n\ninterface NextConfig {\n webpack?: (config: any, context: WebpackConfigContext) => any;\n turbopack?: TurbopackConfig;\n [key: string]: unknown;\n}\n\nexport interface TastyZeroNextOptions {\n /**\n * Output path for CSS relative to project root.\n * @default 'public/tasty.css'\n */\n output?: string;\n\n /**\n * Whether to enable the plugin.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Tasty configuration for build-time processing.\n * For static configs that don't change during dev.\n *\n * For configs that depend on theme files, use `configFile` instead.\n */\n config?: TastyZeroConfig;\n\n /**\n * Path to a TypeScript/JavaScript module that exports the tasty zero config\n * as its default export. The module is re-evaluated on each\n * compilation, enabling hot reload when the file (or its imports) change.\n *\n * @example './app/tasty-zero.config.ts'\n */\n configFile?: string;\n\n /**\n * Extra file paths (relative to project root) that the config depends on.\n * When any of these files change, the Babel cache is invalidated and\n * the config is re-evaluated.\n *\n * The `configFile` itself is always tracked automatically.\n * Use this for transitive dependencies that aren't directly imported\n * by the config file, or when using `config` instead of `configFile`.\n *\n * @example ['./app/theme.ts']\n */\n configDeps?: string[];\n\n /**\n * Output mode for extracted CSS.\n *\n * - `'file'` (default): CSS is written to a single output file.\n * - `'inject'`: CSS is embedded inline in JS and injected at runtime.\n * No CSS file is written. Best for reusable components and extensions.\n *\n * When `mode` is `'inject'`, `output` is ignored.\n *\n * @default 'file'\n */\n mode?: 'file' | 'inject';\n}\n\n/**\n * Next.js configuration wrapper for tasty-zero.\n * Configures both webpack and Turbopack bundlers automatically.\n */\nexport function withTastyZero(options: TastyZeroNextOptions = {}) {\n const {\n output = 'public/tasty.css',\n enabled = true,\n config: tastyConfig,\n configFile,\n configDeps = [],\n mode,\n } = options;\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n if (!enabled) {\n return nextConfig;\n }\n\n const projectDir = process.cwd();\n const absoluteOutput = path.resolve(projectDir, output);\n const babelPluginPath = path.resolve(__dirname, 'babel.js');\n\n const absoluteConfigFile = configFile\n ? path.resolve(projectDir, configFile)\n : undefined;\n\n const allDeps = [\n ...(absoluteConfigFile ? [absoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(projectDir, dep)),\n ];\n\n // --- Turbopack configuration ---\n // Turbopack loader options must be JSON-serializable (no functions).\n // The Babel plugin loads config internally via `configFile` path + jiti.\n const turbopackBabelOptions: Record<string, unknown> = {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [\n [\n babelPluginPath,\n {\n output: absoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n ...(absoluteConfigFile\n ? { configFile: absoluteConfigFile }\n : tastyConfig\n ? { config: tastyConfig }\n : {}),\n ...(allDeps.length > 0 ? { configDeps: allDeps } : {}),\n },\n ],\n ],\n };\n\n const existingTurbopack = nextConfig.turbopack || {};\n const existingRules = existingTurbopack.rules || {};\n\n const existingExperimental =\n (nextConfig.experimental as Record<string, unknown>) || {};\n\n return {\n ...nextConfig,\n\n experimental: {\n ...existingExperimental,\n turbopackUseBuiltinBabel: true,\n },\n\n turbopack: {\n ...existingTurbopack,\n rules: {\n ...existingRules,\n '*.{ts,tsx,js,jsx}': {\n condition: { not: 'foreign' },\n loaders: [\n {\n loader: 'babel-loader',\n options: turbopackBabelOptions,\n },\n ],\n },\n },\n },\n\n webpack(config: any, context: WebpackConfigContext) {\n const { dir } = context;\n\n const wpProjectDir = dir || projectDir;\n const wpAbsoluteOutput = path.resolve(wpProjectDir, output);\n const projectRequire = createRequire(\n path.resolve(wpProjectDir, 'package.json'),\n );\n\n const wpAbsoluteConfigFile = configFile\n ? path.resolve(wpProjectDir, configFile)\n : undefined;\n\n const wpAllDeps = [\n ...(wpAbsoluteConfigFile ? [wpAbsoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(wpProjectDir, dep)),\n ];\n\n const babelPluginOptions: TastyZeroBabelOptions = {\n output: wpAbsoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n };\n\n if (wpAbsoluteConfigFile) {\n babelPluginOptions.config = () =>\n loadConfigFile<TastyZeroConfig>(wpAbsoluteConfigFile, wpProjectDir);\n } else if (tastyConfig) {\n babelPluginOptions.config = tastyConfig;\n }\n\n if (wpAllDeps.length > 0) {\n babelPluginOptions.configDeps = wpAllDeps;\n }\n\n const babelPluginConfig = [babelPluginPath, babelPluginOptions];\n\n const existingRule = config.module?.rules?.find(\n (rule: any) =>\n rule.use?.loader === 'babel-loader' ||\n rule.use?.some?.((u: any) => u.loader === 'babel-loader'),\n );\n\n if (existingRule) {\n const babelUse = Array.isArray(existingRule.use)\n ? existingRule.use.find((u: any) => u.loader === 'babel-loader')\n : existingRule.use;\n\n if (babelUse?.options) {\n babelUse.options.plugins = babelUse.options.plugins || [];\n babelUse.options.plugins.push(babelPluginConfig);\n }\n } else {\n config.module = config.module || {};\n config.module.rules = config.module.rules || [];\n config.module.rules.push({\n test: /\\.(tsx?|jsx?)$/,\n exclude: /node_modules/,\n use: [\n {\n loader: projectRequire.resolve('babel-loader'),\n options: {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [babelPluginConfig],\n },\n },\n ],\n });\n }\n\n if (typeof nextConfig.webpack === 'function') {\n return nextConfig.webpack(config, context);\n }\n\n return config;\n },\n };\n };\n}\n"],"mappings":"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"}
|
package/docs/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Use this hub to move from the core idea to a working component, then into design
|
|
|
16
16
|
|
|
17
17
|
- **Application developer using an existing design system**: [Getting Started](getting-started.md), then [React API](react-api.md).
|
|
18
18
|
- **Design-system author**: [Methodology](methodology.md), [Building a Design System](design-system.md), [Configuration](configuration.md), and [Adoption Guide](adoption.md).
|
|
19
|
-
- **Platform or tooling engineer**: [Configuration](configuration.md), [Build-Time Extraction (`tastyStatic`)](tasty-static.md), [Server-Side Rendering](ssr.md), and [Debug Utilities](debug.md).
|
|
19
|
+
- **Platform or tooling engineer**: [Configuration](configuration.md), [Plugins & Extension Points](plugins.md), [Build-Time Extraction (`tastyStatic`)](tasty-static.md), [Server-Side Rendering](ssr.md), and [Debug Utilities](debug.md).
|
|
20
20
|
|
|
21
21
|
## By Styling Approach
|
|
22
22
|
|
|
@@ -29,6 +29,9 @@ Use this hub to move from the core idea to a working component, then into design
|
|
|
29
29
|
## By Task
|
|
30
30
|
|
|
31
31
|
- **Learn the style language**: [Style DSL](dsl.md)
|
|
32
|
+
- **Set up linting and autocomplete**: [Getting Started — ESLint plugin](getting-started.md#eslint-plugin) and [TypeScript autocomplete](getting-started.md#typescript-autocomplete)
|
|
33
|
+
- **Design a component's public API** (`styleProps`, `modProps`, `tokenProps`, variants, sub-elements): [React API](react-api.md), then [Methodology](methodology.md)
|
|
34
|
+
- **Generate a color palette with contrast solving**: [Glaze](../README.md#glaze)
|
|
32
35
|
- **Brief an AI agent (or yourself) on writing correct styles**: [Style Rules for AI Agents](ai-agents.md)
|
|
33
36
|
- **Look up a property handler**: [Style Properties](styles.md)
|
|
34
37
|
- **Define tokens, units, recipes, keyframes, properties, functions, or polyfills globally**: [Configuration](configuration.md)
|
package/docs/adoption.md
CHANGED
|
@@ -24,7 +24,8 @@ Product code
|
|
|
24
24
|
- The DSL and value parser (tokens, custom units, auto-calc, color opacity)
|
|
25
25
|
- State compilation (mutually exclusive selectors from state maps)
|
|
26
26
|
- Style injection and deduplication (runtime or build-time)
|
|
27
|
-
- The `tasty()` component factory and
|
|
27
|
+
- The `tasty()` component factory and style functions (`useStyles`, `useGlobalStyles`, …)
|
|
28
|
+
- The lint rules and TypeScript extension interfaces that check usage against your vocabulary
|
|
28
29
|
|
|
29
30
|
**What the DS team owns:**
|
|
30
31
|
|
|
@@ -64,16 +65,18 @@ For a detailed comparison with Tailwind, Panda CSS, vanilla-extract, StyleX, Sti
|
|
|
64
65
|
|
|
65
66
|
Tasty provides the engine. The DS team defines the language that runs on it. Here is what that typically involves:
|
|
66
67
|
|
|
67
|
-
| Layer | What you define
|
|
68
|
-
| ------------------ |
|
|
69
|
-
| **Tokens** | Color names
|
|
70
|
-
| **Units** |
|
|
71
|
-
| **State aliases** | Responsive breakpoints, theme modes, feature flags
|
|
72
|
-
| **Recipes** | Reusable style bundles (card, elevated, input-reset)
|
|
73
|
-
| **Typography** | Preset definitions (h1-h6, t1-t4, etc.)
|
|
74
|
-
| **Style props** | Which CSS properties each component exposes as React props
|
|
75
|
-
| **Sub-elements** | Inner parts of compound components (Title, Icon, Content)
|
|
76
|
-
| **
|
|
68
|
+
| Layer | What you define | Where |
|
|
69
|
+
| ------------------ | ----------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
70
|
+
| **Tokens** | Color names and named values (`#surface`, `$sidebar-width`) | `configure({ tokens })` |
|
|
71
|
+
| **Units** | Spacing step, radius, border and outline widths (`x`, `r`, `bw`, `ow`, or your own) | `configure({ units })` |
|
|
72
|
+
| **State aliases** | Responsive breakpoints, theme modes, feature flags | `configure({ states })` |
|
|
73
|
+
| **Recipes** | Reusable style bundles (card, elevated, input-reset) | `configure({ recipes })` |
|
|
74
|
+
| **Typography** | Preset definitions (h1-h6, t1-t4, etc.) | `configure({ presets: { ... } })` |
|
|
75
|
+
| **Style props** | Which CSS properties each component exposes as React props | `styleProps` in each component |
|
|
76
|
+
| **Sub-elements** | Inner parts of compound components (Title, Icon, Content) | `elements` + capitalized keys in `styles` |
|
|
77
|
+
| **Component API** | Typed props for states, values, and looks | `modProps`, `tokenProps`, `variants` |
|
|
78
|
+
| **Override rules** | How product engineers extend or constrain components | Styled wrappers via `tasty(Base, { ... })` |
|
|
79
|
+
| **Guardrails** | The names the linter and TypeScript check against | `tasty.config.ts`, `declare module '@tenphi/tasty'` |
|
|
77
80
|
|
|
78
81
|
The same engine can power a minimal design system with a handful of tokens:
|
|
79
82
|
|
|
@@ -155,18 +158,23 @@ configure({
|
|
|
155
158
|
'#surface': '#white',
|
|
156
159
|
'#text': '#111',
|
|
157
160
|
'$card-padding': '4x',
|
|
161
|
+
$gap: '8px',
|
|
162
|
+
$radius: '6px',
|
|
163
|
+
'$border-width': '1px',
|
|
158
164
|
},
|
|
159
|
-
//
|
|
160
|
-
// A DS
|
|
161
|
-
//
|
|
165
|
+
// Built-in units (x, r, cr, bw, ow) compile to fixed values by default.
|
|
166
|
+
// A DS often points them at the custom properties above, so the scale
|
|
167
|
+
// is controlled by CSS at runtime rather than fixed at build time:
|
|
162
168
|
units: {
|
|
163
|
-
x: 'var(--gap)', // 2x → calc(var(--gap)
|
|
169
|
+
x: 'var(--gap)', // 2x → calc(2 * var(--gap))
|
|
164
170
|
r: 'var(--radius)',
|
|
165
171
|
bw: 'var(--border-width)',
|
|
166
172
|
},
|
|
167
173
|
});
|
|
168
174
|
```
|
|
169
175
|
|
|
176
|
+
Turn on the guardrails in the same step. Add [`@tenphi/eslint-plugin-tasty`](https://github.com/tenphi/eslint-plugin-tasty) with `tasty.configs.recommended` and a `tasty.config.ts` that lists the tokens, units, states, presets, and recipes as you define them, and register the same color and preset names for TypeScript autocomplete (`TastyNamedColors`, `TastyPresetNames`). Every component you migrate from here on is checked against the vocabulary. See [Getting Started — ESLint plugin](getting-started.md#eslint-plugin).
|
|
177
|
+
|
|
170
178
|
### Phase 3 -- State aliases and recipes
|
|
171
179
|
|
|
172
180
|
Define the state vocabulary your components will share. This is where you start encoding your team's conventions.
|
|
@@ -175,7 +183,7 @@ Define the state vocabulary your components will share. This is where you start
|
|
|
175
183
|
configure({
|
|
176
184
|
states: {
|
|
177
185
|
'@mobile': '@media(w < 768px)',
|
|
178
|
-
'@tablet': '@media(w < 1024px)',
|
|
186
|
+
'@tablet': '@media(768px <= w < 1024px)',
|
|
179
187
|
'@dark':
|
|
180
188
|
'@root(schema=dark) | (!@root(schema) & @media(prefers-color-scheme: dark))',
|
|
181
189
|
},
|
|
@@ -252,7 +260,7 @@ const Card = tasty({
|
|
|
252
260
|
|
|
253
261
|
### Phase 7 -- Expand to full DS coverage
|
|
254
262
|
|
|
255
|
-
Migrate the remaining components,
|
|
263
|
+
Migrate the remaining components, switch the [ESLint plugin](https://github.com/tenphi/eslint-plugin-tasty) to `tasty.configs.strict` to also catch unknown `$` tokens and state aliases, and choose between [server-only `tasty()`](ssr.md) and [`tastyStatic()` build-time extraction](tasty-static.md) when a page should ship no Tasty styling runtime.
|
|
256
264
|
|
|
257
265
|
---
|
|
258
266
|
|
package/docs/ai-agents.md
CHANGED
|
@@ -64,19 +64,20 @@ The last row goes the other way — longhands over the shorthand — because `fl
|
|
|
64
64
|
|
|
65
65
|
### Tokens
|
|
66
66
|
|
|
67
|
-
| Syntax | Meaning
|
|
68
|
-
| --------------------- |
|
|
69
|
-
| `#name` | color token → `var(--name-color)`
|
|
70
|
-
| `#name.50` | token at 50% opacity
|
|
71
|
-
| `#name.$opacity` | opacity from a custom property
|
|
72
|
-
| `#clear` / `#current` | `transparent` / `currentcolor`
|
|
73
|
-
| `$name` | custom property → `var(--name)`
|
|
74
|
-
| `(#a, #b)` | fallback chain
|
|
75
|
-
| `$$name` / `##name` | the custom property _name_ (not its value) — inside `transition`
|
|
76
|
-
| `$$name(…)` | call a CSS `@function` declared via `@function`, `useFunction`, or config
|
|
77
|
-
|
|
78
|
-
- ❌ `#f5f5f5`, `rgb(0 0 0)`, `oklch(…)`, `okhsl(…)`, `red` → ✅ `#surface` (add the token to the config if it doesn't exist).
|
|
67
|
+
| Syntax | Meaning |
|
|
68
|
+
| --------------------- | -------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| `#name` | color token → `var(--name-color)` |
|
|
70
|
+
| `#name.50` | token at 50% opacity — digits are a decimal fraction: `.5` = `.50` = 50%, `.05` = 5%, `.100` = 10% |
|
|
71
|
+
| `#name.$opacity` | opacity from a custom property |
|
|
72
|
+
| `#clear` / `#current` | `transparent` / `currentcolor` |
|
|
73
|
+
| `$name` | custom property → `var(--name)` |
|
|
74
|
+
| `(#a, #b)` | fallback chain |
|
|
75
|
+
| `$$name` / `##name` | the custom property _name_ (not its value) — inside `transition` |
|
|
76
|
+
| `$$name(…)` | call a CSS `@function` declared via `@function`, `useFunction`, or config |
|
|
77
|
+
|
|
78
|
+
- ❌ `#f5f5f5`, `rgb(0 0 0)`, `oklch(…)`, `okhsl(…)`, `okhst(…)`, `red` → ✅ `#surface` (add the token to the config if it doesn't exist).
|
|
79
79
|
- ❌ `var(--gap)` → ✅ `$gap` · ❌ `$accent-color` → ✅ `#accent` · ❌ `transparent` → ✅ `#clear` · ❌ `currentColor` → ✅ `#current`.
|
|
80
|
+
- `#clear` is a default replace token for `transparent`, so `#clear.5` and `#clear.$opacity` stay transparent. It does not read `--clear-color`; override it through `replaceTokens` if needed.
|
|
80
81
|
- A `$name` must be declared as a `'$name': value` key in the same styles object, or in the config.
|
|
81
82
|
- Names are case-sensitive and start lowercase — `$myVar` → `--myVar`; a leading capital folds (`$Foo` → `--foo`). Prefer kebab-case.
|
|
82
83
|
- A `$name` reference is untyped: it fills the first free slot in a shorthand (`border: '1bw $my-style'` → the line style). Write colors as `#name`; `$name-color` is a fallback for pointing at a raw CSS custom property that holds a color.
|
|
@@ -84,7 +85,7 @@ The last row goes the other way — longhands over the shorthand — because `fl
|
|
|
84
85
|
|
|
85
86
|
### Units
|
|
86
87
|
|
|
87
|
-
Tasty units: `x`
|
|
88
|
+
Tasty units and their defaults: `x` 8px spacing step, `r` 6px radius, `cr` 10px card radius, `bw` 1px border width, `ow` 3px outline width, `sf` → `minmax(0, Nfr)`. They compile to literal values; change them through `units` in the config, not with `$gap`/`$radius` tokens. All CSS units work too, plus anything in `units` in the config. Any other unit is an error. Units are not resolved inside `@media(…)` / `@(…)` conditions — write query lengths in `px`, `em`, or `rem`.
|
|
88
89
|
|
|
89
90
|
Prefer units over raw pixels: `8px` → `1x` … `64px` → `8x`; `radius: '6px'` → `'1r'`; `border: '1px …'` → `'1bw …'`.
|
|
90
91
|
|
|
@@ -94,7 +95,7 @@ Prefer units over raw pixels: `8px` → `1x` … `64px` → `8x`; `radius: '6px'
|
|
|
94
95
|
|
|
95
96
|
### `true`
|
|
96
97
|
|
|
97
|
-
`true` means "the design-system default" and is accepted **only** by: `border`, `radius`, `outline`, `shadow`, `padding`, `margin`, `gap`, `inset`, `width`, `height`, `
|
|
98
|
+
`true` means "the design-system default" and is accepted **only** by: `border`, `radius`, `outline`, `shadow`, `padding`, `margin`, `gap`, `inset`, `width`, `height`, `color`, `fade`, `preset`, `font`, `scrollbar`, `scrollMargin`, `scrollPadding`, and the enhanced logical category styles listed above, plus `hide`. Anywhere else it is an error.
|
|
98
99
|
|
|
99
100
|
### `!important`
|
|
100
101
|
|
|
@@ -104,21 +105,23 @@ Never use it. Tasty owns specificity through doubled selectors and state orderin
|
|
|
104
105
|
|
|
105
106
|
A value is `[values…] [modifiers…]`, and several groups can be comma-separated (later groups override earlier ones). A group that names **direction** modifiers takes a **single** value, applied to every direction it names — per-side values come from comma groups. A group naming no direction keeps plain CSS shorthand order (1–4 values). (`inset` + `dock` is the one exception: a second value insets the spanned sides.) Only the modifiers a property knows are valid:
|
|
106
107
|
|
|
107
|
-
| Property
|
|
108
|
-
|
|
|
109
|
-
| `padding`, `margin`, `
|
|
110
|
-
| `
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
|
|
|
108
|
+
| Property | Modifiers |
|
|
109
|
+
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
110
|
+
| `padding`, `margin`, `fade` | `top` `right` `bottom` `left` |
|
|
111
|
+
| `inset` | the four directions + `dock` |
|
|
112
|
+
| `border` | the four directions + `solid` `dashed` `dotted` `double` `groove` `ridge` `inset` `outset` `none` `hidden` |
|
|
113
|
+
| logical axis spacing/inset styles | `start` `end` |
|
|
114
|
+
| `blockBorder`, `inlineBorder` | `start` `end` + the border style keywords above |
|
|
115
|
+
| `outline` | the style keywords above |
|
|
116
|
+
| `radius` | `top` `right` `bottom` `left`, corners `top-left` `top-right` `bottom-right` `bottom-left`, `inherit` + shapes `round` `ellipse` `leaf` `backleaf` |
|
|
117
|
+
| `width`, `height`, `blockSize`, `inlineSize` | `min` `max` `fixed` |
|
|
118
|
+
| `flow` | `row` `column` `row-reverse` `column-reverse` `wrap` `nowrap` `dense` |
|
|
119
|
+
| `overflow` | `visible` `hidden` `scroll` `clip` `auto` `overlay` |
|
|
120
|
+
| `position` | `static` `relative` `absolute` `fixed` `sticky` |
|
|
121
|
+
| `shadow` | `inset` |
|
|
122
|
+
| `preset` | `name / strong` (or `bold`) `italic` `icon` `tight` `normal` |
|
|
123
|
+
| `scrollbar` | `thin` `auto` `none` `stable` `both-edges` `always` |
|
|
124
|
+
| box properties | `longhand` — emit CSS longhands instead of the shorthand |
|
|
122
125
|
|
|
123
126
|
Directional modifiers beat placeholder zeros:
|
|
124
127
|
|
|
@@ -128,11 +131,11 @@ Directional modifiers beat placeholder zeros:
|
|
|
128
131
|
- ❌ `padding: '2x 4x top right'` — a directional group takes one value, so `4x` is dropped → ✅ `padding: '2x top, 4x right'`
|
|
129
132
|
- ❌ `fade: '3x 1x top bottom'` → ✅ `fade: '3x top, 1x bottom'`
|
|
130
133
|
|
|
131
|
-
Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `rowGap`, `opacity`, `zIndex`, `order`, `flexGrow`, `flexShrink`, `flexBasis`, `aspectRatio`, `lineClamp`, and `tabSize`. Logical axis category styles accept one or two values in start/end order and the `start`, `end`, and `longhand` modifiers. `fill` and `
|
|
134
|
+
Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `rowGap`, `opacity`, `zIndex`, `order`, `flexGrow`, `flexShrink`, `flexBasis`, `aspectRatio`, `lineClamp`, and `tabSize`. Logical axis category styles accept one or two values in start/end order and the `start`, `end`, and `longhand` modifiers. `fill`, `color`, `caretColor`, and `accentColor` take a color only (use `#clear` for transparent).
|
|
132
135
|
|
|
133
136
|
### `transition`
|
|
134
137
|
|
|
135
|
-
Use semantic names, not CSS property names: `fade` `fill` `color` `theme` `border` `radius` `shadow` `outline` `preset` `text` `gap` `opacity` `translate` `rotate` `scale` `filter` `image` `background` `width` `height` `zIndex` `inset
|
|
138
|
+
Use semantic names, not CSS property names: `fade` `fill` `color` `theme` `border` `radius` `shadow` `outline` `preset` `text` `gap` `opacity` `translate` `rotate` `scale` `filter` `image` `background` `width` `height` `zIndex` `inset`.
|
|
136
139
|
|
|
137
140
|
❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill'`
|
|
138
141
|
|
|
@@ -168,13 +171,13 @@ Precedence `!` > `^` > `|` > `&`; use parentheses to override.
|
|
|
168
171
|
|
|
169
172
|
Rules:
|
|
170
173
|
|
|
171
|
-
1. **`''` comes first.** The bare default is the lowest-priority state
|
|
174
|
+
1. **`''` comes first.** The bare default is the lowest-priority state. Written later, it triggers a `MISPLACED_DEFAULT_STATE` warning and Tasty moves it to the front.
|
|
172
175
|
2. **Every state map needs `''` or `_`** — except when extending (`tasty(Parent, …)`), where omitting `''` merges into the parent's states and including `''` replaces them wholesale.
|
|
173
176
|
3. **`_` is standalone-only** and always first (with `''` right after it, if present). `_` is a never-negated fallback floor for cases where a higher-priority branch may be _unknown_ (`@supports`, container queries). If a map contains only `_` and `''`, drop the `''`.
|
|
174
177
|
4. **No nested maps:** ❌ `{ hovered: { pressed: 'x' } }` → ✅ `{ 'hovered & pressed': 'x' }`
|
|
175
178
|
5. **State keys never sit at the top level** of a styles object — `:hover`, `.active`, `[open]` belong inside a property value.
|
|
176
179
|
6. **Write keys Tasty can fully read.** Names start with a letter (a single letter is fine: `x` → `[data-x]`); join states with an operator (`hovered & focused`, not `hovered focused`); no space between `@media` and `(`. Tasty warns (`INVALID_STATE_KEY`) and skips what it cannot read.
|
|
177
|
-
7. **Prefer automatic native states:** use `disabled` and `
|
|
180
|
+
7. **Prefer automatic native states:** use `disabled`, `checked`, and `hidden` in Tasty component styles instead of `[disabled]`, `[checked]`, and `[hidden]`. Tasty sets `data-disabled`, `data-checked`, and `data-hidden` from the native props automatically.
|
|
178
181
|
|
|
179
182
|
```jsx
|
|
180
183
|
color: { '': '#text', hovered: '#accent', disabled: '#text.40' }
|
|
@@ -204,7 +207,7 @@ When extending a parent's state map: `'@inherit'` reuses the parent's value for
|
|
|
204
207
|
A **capitalized** key targets `[data-element="Name"]`, and its value must be a style object.
|
|
205
208
|
|
|
206
209
|
```jsx
|
|
207
|
-
styles: { Title: { preset: 'h3' }, Icon: { $: '
|
|
210
|
+
styles: { Title: { preset: 'h3' }, Icon: { $: '>', color: { '': '#text', '@own(:last-child)': '#accent' } } }
|
|
208
211
|
```
|
|
209
212
|
|
|
210
213
|
❌ nested-selector keys (`'& .title'`, `'&:hover'`) → ✅ sub-elements and state maps. Use the `$` affix property inside a sub-element to control how its selector attaches (`>` direct child, `@` placeholder for the element itself, `&::before` for a root pseudo-element).
|
package/docs/comparison.md
CHANGED
|
@@ -6,7 +6,7 @@ Tasty is CSS-in-JS for React design systems. It is built for the point where reu
|
|
|
6
6
|
|
|
7
7
|
Its distinctive guarantee is narrow and concrete: each property’s state map compiles into selectors that cannot compete, so one declared branch wins regardless of CSS source order.
|
|
8
8
|
|
|
9
|
-
That does not require a big upfront configuration step. Tasty's built-in units and normal CSS color values work out of the box, and `okhsl(...)`
|
|
9
|
+
That does not require a big upfront configuration step. Tasty's built-in units and normal CSS color values work out of the box, and the built-in `okhsl(...)` and `okhst(...)` color functions are available immediately as the recommended path for color authoring. The extra setup comes later if a team wants shared tokens, aliases, recipes, or stricter conventions.
|
|
10
10
|
|
|
11
11
|
Most styling tools focus on one of these layers:
|
|
12
12
|
|
|
@@ -95,11 +95,12 @@ That makes Tasty less of a "better way to write CSS objects" and more of a **sta
|
|
|
95
95
|
|
|
96
96
|
Beyond state resolution, Tasty also provides several structural capabilities that reinforce the design-system layer:
|
|
97
97
|
|
|
98
|
-
- **
|
|
99
|
-
- **Sub-element styling** — Compound components declare inner parts via capitalized keys in `styles` and `
|
|
98
|
+
- **Typed component APIs** — `styleProps` exposes selected style properties as normal props (`<Button placeSelf="end">`, with state maps for responsive values), `modProps` turns modifiers into typed props (`size: ['sm', 'md', 'lg'] as const`), `tokenProps` passes per-instance values without generating new CSS, and `variants` gives a typed `variant` prop. Layout and composition controls stay inside a governed component API instead of pushing teams back to ad hoc styling escapes.
|
|
99
|
+
- **Sub-element styling** — Compound components declare inner parts via capitalized keys in `styles` and the `elements` option, which produces typed sub-components (`<Card.Title>`) that share the root's state context. States, tokens, and recipes apply across the entire element tree from a single definition. See [React API — Sub-element Styling](react-api.md#sub-element-styling).
|
|
100
|
+
- **Modern CSS as state keys** — Container queries, `@supports`, `:has()`, root and parent states, and `@starting-style` live in the same state maps as `:hover`, and `@keyframes`, `@property`, `@font-face`, `@counter-style`, and `@function` are built in.
|
|
100
101
|
- **Auto-inferred `@property`** — When a custom property is assigned a concrete value, Tasty infers the CSS `@property` syntax and registers it automatically, enabling smooth transitions on custom properties without manual declarations.
|
|
101
102
|
- **AI-friendly style definitions** — Style definitions are declarative, self-contained, and structurally consistent. AI tools can read, refactor, and generate Tasty styles as confidently as a human — no hidden cascade logic or implicit ordering to second-guess.
|
|
102
|
-
- **Companion ecosystem** — An [ESLint plugin](https://github.com/tenphi/eslint-plugin-tasty) with
|
|
103
|
+
- **Companion ecosystem** — An [ESLint and oxlint plugin](https://github.com/tenphi/eslint-plugin-tasty) with 40+ rules that validate styles against your project's `tasty.config.ts`, TypeScript augmentation for autocompleting your own token and preset names, a [VS Code extension](https://github.com/tenphi/tasty-vscode-extension) for syntax highlighting and snippets, and [Glaze](https://github.com/tenphi/glaze) for OKHST palette generation with WCAG and APCA contrast solving.
|
|
103
104
|
|
|
104
105
|
---
|
|
105
106
|
|
|
@@ -184,7 +185,7 @@ Every new state doubles the selector count. Miss one intersection and you ship a
|
|
|
184
185
|
)}>
|
|
185
186
|
```
|
|
186
187
|
|
|
187
|
-
The `theme` branch is runtime JS, not CSS.
|
|
188
|
+
The `theme` branch is runtime JS, not CSS. Tailwind resolves `hover:` against `disabled:` by its global variant order, not per component; when your priority differs from that order, or two base utilities conflict (`bg-primary` vs `bg-danger`), you need `tailwind-merge`, `!` modifiers, or more conditional class logic.
|
|
188
189
|
|
|
189
190
|
**Tasty** — each property declares all its states in one map. The engine generates mutually exclusive selectors:
|
|
190
191
|
|
|
@@ -196,10 +197,10 @@ const Button = tasty({
|
|
|
196
197
|
'': '#primary',
|
|
197
198
|
':hover': '#primary-hover',
|
|
198
199
|
':active': '#primary-pressed',
|
|
199
|
-
disabled: '#surface',
|
|
200
200
|
'theme=danger': '#danger',
|
|
201
201
|
'theme=danger & :hover': '#danger-hover',
|
|
202
202
|
'theme=danger & :active': '#danger-pressed',
|
|
203
|
+
disabled: '#surface',
|
|
203
204
|
},
|
|
204
205
|
color: {
|
|
205
206
|
'': '#on-primary',
|
|
@@ -213,7 +214,7 @@ const Button = tasty({
|
|
|
213
214
|
});
|
|
214
215
|
```
|
|
215
216
|
|
|
216
|
-
`disabled`
|
|
217
|
+
`disabled` is declared last, so it wins over `:hover`, `:active`, and `theme=danger`: Tasty guards every earlier branch with `:not([data-disabled])` — no source-order dependence, no manual intersection management, no `!important`.
|
|
217
218
|
|
|
218
219
|
---
|
|
219
220
|
|
|
@@ -328,7 +329,7 @@ That makes it narrower in audience, but deeper in architectural ambition.
|
|
|
328
329
|
|
|
329
330
|
There is also a fundamental architectural difference: Emotion and styled-components rely on React context and hooks internally, which means they require `'use client'` in modern React and cannot run as React Server Components. Tasty's style functions and `tasty()` components are hook-free, so they work as server components by default and ship no Tasty styling runtime in server-only contexts. This is not a minor compatibility detail — it means Tasty-based components stay as server components until _your_ code needs interactivity, while Emotion and styled-components force the client boundary at the styling layer.
|
|
330
331
|
|
|
331
|
-
For teams evaluating runtime styling at scale, Tasty
|
|
332
|
+
For teams evaluating runtime styling at scale, Tasty publishes reproducible [Runtime Benchmarks](runtime-benchmarks.md) and summarizes its caching model in the [README](../README.md#performance). That matters, but it is still secondary to the core question of whether you want Tasty's deterministic selector model.
|
|
332
333
|
|
|
333
334
|
---
|
|
334
335
|
|