@tenphi/tasty 2.10.0 → 2.11.1

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.
Files changed (47) hide show
  1. package/README.md +1 -0
  2. package/dist/{collector-DCWdN88E.js → collector-AHZaBSv8.js} +3 -3
  3. package/dist/{collector-DCWdN88E.js.map → collector-AHZaBSv8.js.map} +1 -1
  4. package/dist/{collector-CddJZDOr.d.ts → collector-DYA5AOwr.d.ts} +2 -2
  5. package/dist/{config-C3SKYuxY.d.ts → config-BtK9fUaz.d.ts} +2 -2
  6. package/dist/{config-DA5FBnzm.js → config-YDAcLaVf.js} +33 -30
  7. package/dist/config-YDAcLaVf.js.map +1 -0
  8. package/dist/core/index.d.ts +4 -4
  9. package/dist/core/index.js +5 -5
  10. package/dist/{core-BhlL-3k-.js → core-CS4bzqGu.js} +10 -10
  11. package/dist/core-CS4bzqGu.js.map +1 -0
  12. package/dist/{css-writer-Cnj-KrI4.js → css-writer-BXSANVrq.js} +3 -3
  13. package/dist/{css-writer-Cnj-KrI4.js.map → css-writer-BXSANVrq.js.map} +1 -1
  14. package/dist/{format-rules-COR773Tn.js → format-rules-CYriCDwq.js} +2 -2
  15. package/dist/{format-rules-COR773Tn.js.map → format-rules-CYriCDwq.js.map} +1 -1
  16. package/dist/{hydrate-F9olJTHt.js → hydrate-GVTorHpU.js} +2 -2
  17. package/dist/{hydrate-F9olJTHt.js.map → hydrate-GVTorHpU.js.map} +1 -1
  18. package/dist/{index-C5owZ15q.d.ts → index-B9ih23uv.d.ts} +4 -2
  19. package/dist/{index-ByBYTH9l.d.ts → index-BYtnj_gA.d.ts} +4 -4
  20. package/dist/index.d.ts +4 -4
  21. package/dist/index.js +6 -6
  22. package/dist/{keyframes-DZbGV4H4.js → keyframes-CtcSlw4k.js} +2 -2
  23. package/dist/{keyframes-DZbGV4H4.js.map → keyframes-CtcSlw4k.js.map} +1 -1
  24. package/dist/{merge-styles-CvUi7SOD.d.ts → merge-styles-BZV-XAwX.d.ts} +2 -2
  25. package/dist/{merge-styles-DRUFVZH4.js → merge-styles-D4ITH4bc.js} +2 -2
  26. package/dist/{merge-styles-DRUFVZH4.js.map → merge-styles-D4ITH4bc.js.map} +1 -1
  27. package/dist/{resolve-recipes-DUfJKJef.js → resolve-recipes-llr0COs8.js} +3 -3
  28. package/dist/{resolve-recipes-DUfJKJef.js.map → resolve-recipes-llr0COs8.js.map} +1 -1
  29. package/dist/ssr/astro-client.js +1 -1
  30. package/dist/ssr/astro.js +3 -3
  31. package/dist/ssr/index.d.ts +1 -1
  32. package/dist/ssr/index.js +3 -3
  33. package/dist/ssr/next.d.ts +1 -1
  34. package/dist/ssr/next.js +4 -4
  35. package/dist/static/index.d.ts +2 -2
  36. package/dist/static/index.js +4 -4
  37. package/dist/static/index.js.map +1 -1
  38. package/dist/zero/babel.d.ts +1 -1
  39. package/dist/zero/babel.js +4 -4
  40. package/dist/zero/index.d.ts +1 -1
  41. package/dist/zero/index.js +1 -1
  42. package/docs/README.md +1 -0
  43. package/docs/ai-agents.md +206 -0
  44. package/docs/styles.md +5 -0
  45. package/package.json +2 -1
  46. package/dist/config-DA5FBnzm.js.map +0 -1
  47. package/dist/core-BhlL-3k-.js.map +0 -1
package/dist/ssr/next.js CHANGED
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { n as getConfig } from "../config-DA5FBnzm.js";
3
- import { i as registerSSRCollectorGetter } from "../format-rules-COR773Tn.js";
2
+ import { n as getConfig } from "../config-YDAcLaVf.js";
3
+ import { i as registerSSRCollectorGetter } from "../format-rules-CYriCDwq.js";
4
4
  import { t as getTastySSRContext } from "../context-CA8YKeMn.js";
5
- import { t as ServerStyleCollector } from "../collector-DCWdN88E.js";
6
- import { n as hydrateTastyClasses } from "../hydrate-F9olJTHt.js";
5
+ import { t as ServerStyleCollector } from "../collector-AHZaBSv8.js";
6
+ import { n as hydrateTastyClasses } from "../hydrate-GVTorHpU.js";
7
7
  import { Fragment, createElement, useState } from "react";
8
8
  import { useServerInsertedHTML } from "next/navigation";
9
9
  //#region src/ssr/next.ts
@@ -1,5 +1,5 @@
1
- import { b as Styles } from "../index-C5owZ15q.js";
2
- import { t as mergeStyles } from "../merge-styles-CvUi7SOD.js";
1
+ import { b as Styles } from "../index-B9ih23uv.js";
2
+ import { t as mergeStyles } from "../merge-styles-BZV-XAwX.js";
3
3
 
4
4
  //#region src/static/types.d.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { t as mergeStyles } from "../merge-styles-DRUFVZH4.js";
1
+ import { t as mergeStyles } from "../merge-styles-D4ITH4bc.js";
2
2
  //#region src/static/types.ts
3
3
  /**
4
4
  * Create a StaticStyle object.
@@ -34,14 +34,14 @@ function isStaticStyle(value) {
34
34
  */
35
35
  function tastyStatic(stylesOrBaseOrSelector, styles) {
36
36
  if (typeof stylesOrBaseOrSelector === "string") {
37
- console.warn(`[tasty] tastyStatic('${stylesOrBaseOrSelector}', styles) was called at runtime. This indicates the Babel plugin is not configured. Add @tenphi/tasty/babel-plugin to your Babel config.`);
37
+ console.warn(`[Tasty] tastyStatic('${stylesOrBaseOrSelector}', styles) was called at runtime. This indicates the Babel plugin is not configured. Add @tenphi/tasty/babel-plugin to your Babel config.`);
38
38
  return;
39
39
  }
40
40
  if (isStaticStyle(stylesOrBaseOrSelector)) {
41
- console.warn("[tasty] tastyStatic(base, styles) was called at runtime. This indicates the Babel plugin is not configured. Add @tenphi/tasty/babel-plugin to your Babel config.");
41
+ console.warn("[Tasty] tastyStatic(base, styles) was called at runtime. This indicates the Babel plugin is not configured. Add @tenphi/tasty/babel-plugin to your Babel config.");
42
42
  return createStaticStyle("__TASTY_STATIC_NOT_TRANSFORMED__", mergeStyles(stylesOrBaseOrSelector.styles, styles || {}));
43
43
  }
44
- console.warn("[tasty] tastyStatic(styles) was called at runtime. This indicates the Babel plugin is not configured. Add @tenphi/tasty/babel-plugin to your Babel config.");
44
+ console.warn("[Tasty] tastyStatic(styles) was called at runtime. This indicates the Babel plugin is not configured. Add @tenphi/tasty/babel-plugin to your Babel config.");
45
45
  return createStaticStyle("__TASTY_STATIC_NOT_TRANSFORMED__", stylesOrBaseOrSelector);
46
46
  }
47
47
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/static/types.ts","../../src/static/tastyStatic.ts"],"sourcesContent":["import type { Styles } from '../styles/types';\n\n/**\n * Static style definition returned by tastyStatic().\n *\n * Supports both explicit className access and implicit string coercion via toString().\n *\n * @example\n * ```typescript\n * const button = tastyStatic({ fill: '#blue' });\n *\n * // Both work in JSX:\n * <div className={button} /> // Uses toString()\n * <div className={button.className} /> // Explicit\n *\n * // Extension:\n * const primary = tastyStatic(button, { fill: '#purple' });\n * ```\n */\nexport interface StaticStyle {\n /**\n * Generated className(s) for use in JSX.\n * May contain multiple space-separated class names due to chunking.\n */\n className: string;\n\n /**\n * The original (or merged) styles object.\n * Available for extension via tastyStatic(base, overrides).\n */\n styles: Styles;\n\n /**\n * Returns className for implicit string coercion.\n * Enables `<div className={button} />` syntax.\n */\n toString(): string;\n}\n\n/**\n * Create a StaticStyle object.\n * Used internally by the Babel plugin to generate output.\n */\nexport function createStaticStyle(\n className: string,\n styles: Styles,\n): StaticStyle {\n return {\n className,\n styles,\n toString() {\n return this.className;\n },\n };\n}\n\n/**\n * Type guard to check if a value is a StaticStyle object.\n */\nexport function isStaticStyle(value: unknown): value is StaticStyle {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'className' in value &&\n 'styles' in value &&\n 'toString' in value &&\n typeof (value as StaticStyle).className === 'string' &&\n typeof (value as StaticStyle).styles === 'object'\n );\n}\n","import type { Styles } from '../styles/types';\nimport { mergeStyles } from '../utils/merge-styles';\n\nimport type { StaticStyle } from './types';\nimport { createStaticStyle, isStaticStyle } from './types';\n\n/**\n * Generate styles and return a StaticStyle object.\n * The object has `className`, `styles`, and `toString()`.\n *\n * @example\n * ```typescript\n * const button = tastyStatic({\n * fill: '#blue',\n * padding: '2x',\n * });\n * // After build: { className: 'ts3f2a1b', styles: {...}, toString() }\n *\n * <div className={button} /> // Works via toString()\n * ```\n */\nexport function tastyStatic(styles: Styles): StaticStyle;\n\n/**\n * Extend an existing StaticStyle with additional styles.\n * Uses mergeStyles() internally for proper nested selector handling.\n *\n * @example\n * ```typescript\n * const button = tastyStatic({ fill: '#blue' });\n * const primary = tastyStatic(button, { fill: '#purple' });\n * // After build: { className: 'ts8c4d2e', styles: {...merged...}, toString() }\n * ```\n */\nexport function tastyStatic(base: StaticStyle, styles: Styles): StaticStyle;\n\n/**\n * Generate styles for a specific CSS selector.\n * The call is completely removed after build transformation.\n *\n * @example\n * ```typescript\n * tastyStatic('.heading', { preset: 'h1', color: '#primary' });\n * // After build: (removed)\n * ```\n */\nexport function tastyStatic(selector: string, styles: Styles): void;\n\n/**\n * Build-time only function for zero-runtime static site generation.\n *\n * This function is transformed by the Babel plugin:\n * - `tastyStatic(styles)` → StaticStyle object with className\n * - `tastyStatic(base, styles)` → StaticStyle object with merged styles\n * - `tastyStatic(selector, styles)` → removed entirely\n *\n * At runtime (during development/build), this function returns a placeholder.\n * In production, all calls are replaced/removed by the build plugin.\n */\nexport function tastyStatic(\n stylesOrBaseOrSelector: Styles | StaticStyle | string,\n styles?: Styles,\n): StaticStyle | void {\n // This code only executes if the Babel plugin hasn't processed the file yet.\n // In a properly configured build, this function is never called at runtime.\n\n if (typeof stylesOrBaseOrSelector === 'string') {\n // Selector mode: tastyStatic(selector, styles)\n // The plugin will remove this call entirely\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[tasty] tastyStatic('${stylesOrBaseOrSelector}', styles) was called at runtime. ` +\n 'This indicates the Babel plugin is not configured. ' +\n 'Add @tenphi/tasty/babel-plugin to your Babel config.',\n );\n }\n return; // void\n }\n\n if (isStaticStyle(stylesOrBaseOrSelector)) {\n // Extension mode: tastyStatic(base, styles)\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[tasty] tastyStatic(base, styles) was called at runtime. ' +\n 'This indicates the Babel plugin is not configured. ' +\n 'Add @tenphi/tasty/babel-plugin to your Babel config.',\n );\n }\n // Merge styles for dev mode preview (won't have real classNames)\n const mergedStyles = mergeStyles(\n stylesOrBaseOrSelector.styles,\n styles || {},\n );\n return createStaticStyle('__TASTY_STATIC_NOT_TRANSFORMED__', mergedStyles);\n }\n\n // Styles mode: tastyStatic(styles)\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[tasty] tastyStatic(styles) was called at runtime. ' +\n 'This indicates the Babel plugin is not configured. ' +\n 'Add @tenphi/tasty/babel-plugin to your Babel config.',\n );\n }\n\n // Return placeholder - styles won't be applied without the plugin\n return createStaticStyle(\n '__TASTY_STATIC_NOT_TRANSFORMED__',\n stylesOrBaseOrSelector,\n );\n}\n"],"mappings":";;;;;;AA2CA,SAAgB,kBACd,WACA,QACa;CACb,OAAO;EACL;EACA;EACA,WAAW;GACT,OAAO,KAAK;EACd;CACF;AACF;;;;AAKA,SAAgB,cAAc,OAAsC;CAClE,OACE,OAAO,UAAU,YACjB,UAAU,QACV,eAAe,SACf,YAAY,SACZ,cAAc,SACd,OAAQ,MAAsB,cAAc,YAC5C,OAAQ,MAAsB,WAAW;AAE7C;;;;;;;;;;;;;;ACVA,SAAgB,YACd,wBACA,QACoB;CAIpB,IAAI,OAAO,2BAA2B,UAAU;EAI5C,QAAQ,KACN,wBAAwB,uBAAuB,0IAGjD;EAEF;CACF;CAEA,IAAI,cAAc,sBAAsB,GAAG;EAGvC,QAAQ,KACN,kKAGF;EAOF,OAAO,kBAAkB,oCAJJ,YACnB,uBAAuB,QACvB,UAAU,CAAC,CAE2D,CAAC;CAC3E;CAIE,QAAQ,KACN,4JAGF;CAIF,OAAO,kBACL,oCACA,sBACF;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/static/types.ts","../../src/static/tastyStatic.ts"],"sourcesContent":["import type { Styles } from '../styles/types';\n\n/**\n * Static style definition returned by tastyStatic().\n *\n * Supports both explicit className access and implicit string coercion via toString().\n *\n * @example\n * ```typescript\n * const button = tastyStatic({ fill: '#blue' });\n *\n * // Both work in JSX:\n * <div className={button} /> // Uses toString()\n * <div className={button.className} /> // Explicit\n *\n * // Extension:\n * const primary = tastyStatic(button, { fill: '#purple' });\n * ```\n */\nexport interface StaticStyle {\n /**\n * Generated className(s) for use in JSX.\n * May contain multiple space-separated class names due to chunking.\n */\n className: string;\n\n /**\n * The original (or merged) styles object.\n * Available for extension via tastyStatic(base, overrides).\n */\n styles: Styles;\n\n /**\n * Returns className for implicit string coercion.\n * Enables `<div className={button} />` syntax.\n */\n toString(): string;\n}\n\n/**\n * Create a StaticStyle object.\n * Used internally by the Babel plugin to generate output.\n */\nexport function createStaticStyle(\n className: string,\n styles: Styles,\n): StaticStyle {\n return {\n className,\n styles,\n toString() {\n return this.className;\n },\n };\n}\n\n/**\n * Type guard to check if a value is a StaticStyle object.\n */\nexport function isStaticStyle(value: unknown): value is StaticStyle {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'className' in value &&\n 'styles' in value &&\n 'toString' in value &&\n typeof (value as StaticStyle).className === 'string' &&\n typeof (value as StaticStyle).styles === 'object'\n );\n}\n","import type { Styles } from '../styles/types';\nimport { mergeStyles } from '../utils/merge-styles';\n\nimport type { StaticStyle } from './types';\nimport { createStaticStyle, isStaticStyle } from './types';\n\n/**\n * Generate styles and return a StaticStyle object.\n * The object has `className`, `styles`, and `toString()`.\n *\n * @example\n * ```typescript\n * const button = tastyStatic({\n * fill: '#blue',\n * padding: '2x',\n * });\n * // After build: { className: 'ts3f2a1b', styles: {...}, toString() }\n *\n * <div className={button} /> // Works via toString()\n * ```\n */\nexport function tastyStatic(styles: Styles): StaticStyle;\n\n/**\n * Extend an existing StaticStyle with additional styles.\n * Uses mergeStyles() internally for proper nested selector handling.\n *\n * @example\n * ```typescript\n * const button = tastyStatic({ fill: '#blue' });\n * const primary = tastyStatic(button, { fill: '#purple' });\n * // After build: { className: 'ts8c4d2e', styles: {...merged...}, toString() }\n * ```\n */\nexport function tastyStatic(base: StaticStyle, styles: Styles): StaticStyle;\n\n/**\n * Generate styles for a specific CSS selector.\n * The call is completely removed after build transformation.\n *\n * @example\n * ```typescript\n * tastyStatic('.heading', { preset: 'h1', color: '#primary' });\n * // After build: (removed)\n * ```\n */\nexport function tastyStatic(selector: string, styles: Styles): void;\n\n/**\n * Build-time only function for zero-runtime static site generation.\n *\n * This function is transformed by the Babel plugin:\n * - `tastyStatic(styles)` → StaticStyle object with className\n * - `tastyStatic(base, styles)` → StaticStyle object with merged styles\n * - `tastyStatic(selector, styles)` → removed entirely\n *\n * At runtime (during development/build), this function returns a placeholder.\n * In production, all calls are replaced/removed by the build plugin.\n */\nexport function tastyStatic(\n stylesOrBaseOrSelector: Styles | StaticStyle | string,\n styles?: Styles,\n): StaticStyle | void {\n // This code only executes if the Babel plugin hasn't processed the file yet.\n // In a properly configured build, this function is never called at runtime.\n\n if (typeof stylesOrBaseOrSelector === 'string') {\n // Selector mode: tastyStatic(selector, styles)\n // The plugin will remove this call entirely\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n `[Tasty] tastyStatic('${stylesOrBaseOrSelector}', styles) was called at runtime. ` +\n 'This indicates the Babel plugin is not configured. ' +\n 'Add @tenphi/tasty/babel-plugin to your Babel config.',\n );\n }\n return; // void\n }\n\n if (isStaticStyle(stylesOrBaseOrSelector)) {\n // Extension mode: tastyStatic(base, styles)\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Tasty] tastyStatic(base, styles) was called at runtime. ' +\n 'This indicates the Babel plugin is not configured. ' +\n 'Add @tenphi/tasty/babel-plugin to your Babel config.',\n );\n }\n // Merge styles for dev mode preview (won't have real classNames)\n const mergedStyles = mergeStyles(\n stylesOrBaseOrSelector.styles,\n styles || {},\n );\n return createStaticStyle('__TASTY_STATIC_NOT_TRANSFORMED__', mergedStyles);\n }\n\n // Styles mode: tastyStatic(styles)\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Tasty] tastyStatic(styles) was called at runtime. ' +\n 'This indicates the Babel plugin is not configured. ' +\n 'Add @tenphi/tasty/babel-plugin to your Babel config.',\n );\n }\n\n // Return placeholder - styles won't be applied without the plugin\n return createStaticStyle(\n '__TASTY_STATIC_NOT_TRANSFORMED__',\n stylesOrBaseOrSelector,\n );\n}\n"],"mappings":";;;;;;AA2CA,SAAgB,kBACd,WACA,QACa;CACb,OAAO;EACL;EACA;EACA,WAAW;GACT,OAAO,KAAK;EACd;CACF;AACF;;;;AAKA,SAAgB,cAAc,OAAsC;CAClE,OACE,OAAO,UAAU,YACjB,UAAU,QACV,eAAe,SACf,YAAY,SACZ,cAAc,SACd,OAAQ,MAAsB,cAAc,YAC5C,OAAQ,MAAsB,WAAW;AAE7C;;;;;;;;;;;;;;ACVA,SAAgB,YACd,wBACA,QACoB;CAIpB,IAAI,OAAO,2BAA2B,UAAU;EAI5C,QAAQ,KACN,wBAAwB,uBAAuB,0IAGjD;EAEF;CACF;CAEA,IAAI,cAAc,sBAAsB,GAAG;EAGvC,QAAQ,KACN,kKAGF;EAOF,OAAO,kBAAkB,oCAJJ,YACnB,uBAAuB,QACvB,UAAU,CAAC,CAE2D,CAAC;CAC3E;CAIE,QAAQ,KACN,4JAGF;CAIF,OAAO,kBACL,oCACA,sBACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { t as TastyConfig } from "../config-C3SKYuxY.js";
1
+ import { t as TastyConfig } from "../config-BtK9fUaz.js";
2
2
  import { PluginPass } from "@babel/core";
3
3
 
4
4
  //#region src/zero/babel.d.ts
@@ -1,7 +1,7 @@
1
- import { i as getGlobalConfigTokens, t as configure, u as getGlobalStyles, v as resetConfig } from "../config-DA5FBnzm.js";
2
- import { t as mergeStyles } from "../merge-styles-DRUFVZH4.js";
3
- import { t as resolveRecipes } from "../resolve-recipes-DUfJKJef.js";
4
- import { a as extractPropertiesFromStyles, c as setExtractorNamePrefix, i as extractKeyframesFromStyles, n as extractCounterStyleFromStyles, o as extractStylesForSelector, r as extractFontFaceFromStyles, s as extractStylesWithChunks, t as CSSWriter } from "../css-writer-Cnj-KrI4.js";
1
+ import { i as getGlobalConfigTokens, t as configure, u as getGlobalStyles, v as resetConfig } from "../config-YDAcLaVf.js";
2
+ import { t as mergeStyles } from "../merge-styles-D4ITH4bc.js";
3
+ import { t as resolveRecipes } from "../resolve-recipes-llr0COs8.js";
4
+ import { a as extractPropertiesFromStyles, c as setExtractorNamePrefix, i as extractKeyframesFromStyles, n as extractCounterStyleFromStyles, o as extractStylesForSelector, r as extractFontFaceFromStyles, s as extractStylesWithChunks, t as CSSWriter } from "../css-writer-BXSANVrq.js";
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import { declare } from "@babel/helper-plugin-utils";
@@ -1,4 +1,4 @@
1
- import { b as Styles } from "../index-C5owZ15q.js";
1
+ import { b as Styles } from "../index-B9ih23uv.js";
2
2
 
3
3
  //#region src/zero/extractor.d.ts
4
4
  interface ExtractedChunk {
@@ -1,2 +1,2 @@
1
- import { o as extractStylesForSelector, s as extractStylesWithChunks, t as CSSWriter } from "../css-writer-Cnj-KrI4.js";
1
+ import { o as extractStylesForSelector, s as extractStylesWithChunks, t as CSSWriter } from "../css-writer-BXSANVrq.js";
2
2
  export { CSSWriter, extractStylesForSelector, extractStylesWithChunks };
package/docs/README.md CHANGED
@@ -24,6 +24,7 @@ Tasty is a styling engine for design systems that turns component state into det
24
24
  ## By Task
25
25
 
26
26
  - **Learn the style language**: [Style DSL](dsl.md)
27
+ - **Brief an AI agent (or yourself) on writing correct styles**: [Style Rules for AI Agents](ai-agents.md)
27
28
  - **Look up a property handler**: [Style Properties](styles.md)
28
29
  - **Define tokens, units, recipes, keyframes, or properties globally**: [Configuration](configuration.md)
29
30
  - **Debug generated CSS or cache behavior**: [Debug Utilities](debug.md)
@@ -0,0 +1,206 @@
1
+ # Tasty Style Rules for AI Agents
2
+
3
+ A compact ruleset for **writing correct `@tenphi/tasty` styles**. It is not an API tour — every rule here is machine-checked by [`@tenphi/eslint-plugin-tasty`](https://www.npmjs.com/package/@tenphi/eslint-plugin-tasty), so following it means clean lint output. For explanations and the complete API, see [Style DSL](dsl.md), [Style Properties](styles.md), [React API](react-api.md).
4
+
5
+ Notation: ❌ wrong → ✅ correct.
6
+
7
+ ---
8
+
9
+ ## 0. Read the project config first
10
+
11
+ Color tokens (`#name`), custom properties (`$name`), `preset` names, `recipe` names, state aliases (`@mobile`) and extra units are **project-defined** in `tasty.config.ts` or a `configure({ … })` call. Look them up before writing styles. Never invent a name — reuse an existing one, or add it to the config.
12
+
13
+ ## 1. Where styles go
14
+
15
+ ```jsx
16
+ const Card = tasty({ as: 'div', styles: { … }, styleProps: ['padding'] }); // new component
17
+ const Hero = tasty(Card, { styles: { … } }); // extend a component
18
+ const cls = useStyles({ … }); // ad-hoc class name
19
+ tastyStatic('.card', { … }); // build-time, zero runtime
20
+ ```
21
+
22
+ - Wrap, don't pass styles per instance: ❌ `<Box styles={{ padding: '2x' }} />` → ✅ `const Box = tasty({ styles: { padding: '2x' } })`.
23
+ - Style values are **static**. Route anything conditional through a state map plus `mods` (or `tokens` / `styleProps`):
24
+ ❌ `fill: isOpen ? '#primary' : '#surface'` → ✅ `fill: { '': '#surface', open: '#primary' }` with `mods={{ open: isOpen }}`.
25
+
26
+ ## 2. Property names
27
+
28
+ Keys are camelCase — a Tasty property or a real CSS property. No kebab-case, no invented names.
29
+
30
+ Prefer the Tasty shorthand over its CSS equivalents:
31
+
32
+ | Use | Instead of |
33
+ |-----|------------|
34
+ | `fill` | `backgroundColor`, `background` |
35
+ | `image` | `backgroundImage` |
36
+ | `border` | `borderColor`, `borderWidth`, `borderStyle`, `borderTop`/`Right`/`Bottom`/`Left` |
37
+ | `radius` | `borderRadius` |
38
+ | `outline` | `outlineColor`, `outlineWidth`, `outlineStyle`, `outlineOffset` |
39
+ | `shadow` | `boxShadow` |
40
+ | `padding` | `paddingTop`/`Right`/`Bottom`/`Left` |
41
+ | `margin` | `marginTop`/`Right`/`Bottom`/`Left` |
42
+ | `inset` | `top`, `right`, `bottom`, `left` |
43
+ | `width` / `height` | `minWidth`/`maxWidth`, `minHeight`/`maxHeight` |
44
+ | `flow` | `flexDirection`, `flexWrap`, `flexFlow`, `gridAutoFlow` |
45
+ | `preset` | `fontSize`, `lineHeight`, `letterSpacing`, `fontWeight`, `fontStyle`, `textTransform` |
46
+ | `font` | `fontFamily` |
47
+ | `scrollbar` | `scrollbarWidth`, `scrollbarColor`, `scrollbarGutter` |
48
+ | `gridColumns` / `gridRows` / `gridAreas` | `gridTemplateColumns` / `Rows` / `Areas` |
49
+ | `textOverflow: 'ellipsis / 3'` | `lineClamp` |
50
+ | `hide: true` | `display: 'none'` |
51
+ | `flexGrow` / `flexShrink` / `flexBasis` | `flex` |
52
+
53
+ The last row goes the other way — longhands over the shorthand — because `flex` is lossy: it resets the components you omit to non-initial values (`flex: '0'` silently sets `flex-basis: 0%`) and cannot express `flexShrink: 0` at all. The longhands also carry separate state maps.
54
+
55
+ ## 3. Values
56
+
57
+ ### Tokens
58
+
59
+ | Syntax | Meaning |
60
+ |--------|---------|
61
+ | `#name` | color token → `var(--name-color)` |
62
+ | `#name.50` | token at 50% opacity (`0`–`100`) |
63
+ | `#name.$opacity` | opacity from a custom property |
64
+ | `#clear` / `#current` | `transparent` / `currentcolor` |
65
+ | `$name` | custom property → `var(--name)` |
66
+ | `(#a, #b)` | fallback chain |
67
+ | `$$name` / `##name` | the property *name* — only inside `transition` |
68
+
69
+ - ❌ `#f5f5f5`, `rgb(0 0 0)`, `oklch(…)`, `okhsl(…)`, `red` → ✅ `#surface` (add the token to the config if it doesn't exist).
70
+ - ❌ `var(--gap)` → ✅ `$gap` · ❌ `$accent-color` → ✅ `#accent` · ❌ `transparent` → ✅ `#clear` · ❌ `currentColor` → ✅ `#current`.
71
+ - A `$name` must be declared as a `'$name': value` key in the same styles object, or in the config.
72
+
73
+ ### Units
74
+
75
+ Tasty units: `x` (gap multiple), `r` (radius), `cr` (card radius), `bw` (border width), `ow` (outline width), `sf` (`minmax(0, Nfr)`). All CSS units work too, plus anything in `units` in the config. Any other unit is an error.
76
+
77
+ Prefer units over raw pixels: `8px` → `1x` … `64px` → `8x`; `radius: '6px'` → `'1r'`; `border: '1px …'` → `'1bw …'`.
78
+
79
+ ### Math
80
+
81
+ ❌ `calc(100% - 2x)` → ✅ `(100% - 2x)`. Parentheses are wrapped in `calc()` automatically — and must be balanced.
82
+
83
+ ### `true`
84
+
85
+ `true` means "the design-system default" and is accepted **only** by: `border`, `radius`, `outline`, `shadow`, `padding`, `margin`, `gap`, `inset`, `width`, `height`, `fill`, `color`, `preset`, `font`, `scrollbar`, `hide`. Anywhere else it is an error.
86
+
87
+ ### `!important`
88
+
89
+ Never use it. Tasty owns specificity through doubled selectors and state ordering; `!important` breaks that. Express the exception as a state instead.
90
+
91
+ ### Modifiers
92
+
93
+ A value is `[values…] [modifiers…]`, and several groups can be comma-separated (later groups override earlier ones). Only the modifiers a property knows are valid:
94
+
95
+ | Property | Modifiers |
96
+ |----------|-----------|
97
+ | `padding`, `margin`, `inset`, `fade` | `top` `right` `bottom` `left` |
98
+ | `border` | the four directions + `solid` `dashed` `dotted` `double` `groove` `ridge` `inset` `outset` `none` `hidden` |
99
+ | `outline` | the style keywords above |
100
+ | `radius` | `top` `right` `bottom` `left` + shapes `round` `ellipse` `leaf` `backleaf` |
101
+ | `width`, `height` | `min` `max` `fixed` |
102
+ | `flow` | `row` `column` `row-reverse` `column-reverse` `wrap` `nowrap` `dense` |
103
+ | `overflow` | `visible` `hidden` `scroll` `clip` `auto` `overlay` |
104
+ | `position` | `static` `relative` `absolute` `fixed` `sticky` |
105
+ | `shadow` | `inset` |
106
+ | `preset` | `name / strong` (or `bold`) `italic` `icon` `tight` |
107
+ | box properties | `longhand` — emit CSS longhands instead of the shorthand |
108
+
109
+ Directional modifiers beat placeholder zeros:
110
+
111
+ - ❌ `padding: '0 0 2x 0'` → ✅ `padding: '2x bottom'`
112
+ - ❌ `padding: '1x 1x 2x 1x'` → ✅ `padding: '1x, 2x bottom'`
113
+ - ❌ `border: '0 0 1bw 0'` — four tokens parse as *one* border value, so this renders no border at all → ✅ `border: '1bw bottom'`
114
+
115
+ Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `rowGap`, `opacity`, `zIndex`, `order`, `flexGrow`, `flexShrink`, `flexBasis`, `aspectRatio`, `lineClamp`, `tabSize`, `paddingInline`, `paddingBlock`. `fill` and `color` take a color (plus `none` / `transparent`); `caretColor` and `accentColor` take a color only.
116
+
117
+ ### `transition`
118
+
119
+ 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` `flow` `dimension`.
120
+
121
+ ❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill 0.2s'`
122
+
123
+ ## 4. State maps
124
+
125
+ A property value can be an object of `state: value`. Key order is priority — later keys win.
126
+
127
+ | Key | Generated selector |
128
+ |-----|--------------------|
129
+ | `hovered` | `[data-hovered]` (boolean modifier from `mods`) |
130
+ | `theme=danger` | `[data-theme="danger"]` (value modifier) |
131
+ | `:hover` | pseudo-class |
132
+ | `.active` | class selector |
133
+ | `[aria-expanded="true"]` | attribute selector |
134
+ | `hovered & .active` | AND |
135
+ | `hovered \| focused` | OR (`,` also means OR) |
136
+ | `!disabled` | NOT |
137
+ | `hovered ^ focused` | XOR — exactly one (keep chains ≤ 4 operands) |
138
+
139
+ Precedence `!` > `^` > `|` > `&`; use parentheses to override.
140
+
141
+ Rules:
142
+
143
+ 1. **`''` comes first.** The bare default is the lowest-priority state; placing it later would override everything above it.
144
+ 2. **Every state map needs `''` or `_`** — except when extending (`tasty(Parent, …)`), where omitting `''` merges into the parent's states and including `''` replaces them wholesale.
145
+ 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 `''`.
146
+ 4. **No nested maps:** ❌ `{ hovered: { pressed: 'x' } }` → ✅ `{ 'hovered & pressed': 'x' }`
147
+ 5. **State keys never sit at the top level** of a styles object — `:hover`, `.active`, `[open]` belong inside a property value.
148
+
149
+ ```jsx
150
+ color: { '': '#text', hovered: '#accent', disabled: '#text.40' }
151
+ ```
152
+
153
+ Advanced states:
154
+
155
+ | Prefix | Use | Example |
156
+ |--------|-----|---------|
157
+ | `@media(…)` | media query; dimensions `w` `h`; types `@media:print` `:screen` `:all` `:speech` | `@media(w < 768px)`, `@media(600px <= w < 1200px)` |
158
+ | `@(…)` | container query; dimensions `w` `h` `is` `bs` | `@(layout, w >= 600px)`, `@($variant=primary)` |
159
+ | `@supports(…)` | feature query; `$` first argument tests a selector | `@supports(display: grid)`, `@supports($, :has(*))` |
160
+ | `@root(…)` | condition on `:root` | `@root(schema=dark)` |
161
+ | `@parent(…)` | condition on an ancestor; `, >` for the direct parent | `@parent(hovered, >)` |
162
+ | `@own(…)` | a sub-element's own state — **only inside sub-element styles** | `@own(:hover)` |
163
+ | `@starting` | `@starting-style` entry animation | `@starting` |
164
+ | `@name` | project state alias | `@mobile` |
165
+
166
+ - At root level write the selector directly: ❌ `'@own(:hover)'` → ✅ `':hover'`.
167
+ - `@name` aliases must exist in `states` in the config or be declared locally as an `'@name': '<state expression>'` key; alias keys start with `@` and their value must be a valid state expression.
168
+ - `:is()` / `:has()` / `:not()` / `:where()` work in state keys but support at most 2 levels of nested parentheses, and `:has()` is expensive — prefer `@parent()`, `@own()` and modifiers.
169
+
170
+ When extending a parent's state map: `'@inherit'` reuses the parent's value for that state, `null` removes a state (or resets a property, letting recipes fill in), `false` is a tombstone that blocks it entirely.
171
+
172
+ ## 5. Sub-elements
173
+
174
+ A **capitalized** key targets `[data-element="Name"]`, and its value must be a style object.
175
+
176
+ ```jsx
177
+ styles: { Title: { preset: 'h3' }, Icon: { $: '>@:last-child', color: '#accent' } }
178
+ ```
179
+
180
+ ❌ 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).
181
+
182
+ ## 6. Special top-level keys
183
+
184
+ | Key | Shape |
185
+ |-----|-------|
186
+ | `@keyframes` | `{ name: { '0%': styles, … } }` |
187
+ | `@properties` | `{ '$name': { syntax, inherits, initialValue } }` |
188
+ | `@fontFace` | `{ 'Family Name': descriptors \| descriptors[] }` |
189
+ | `@counterStyle` | `{ name: descriptors }` |
190
+ | `recipe` | a **string** of configured recipe names: `'card elevated'`, `'reset input / autofill'`, `'none / disabled'` |
191
+
192
+ ## 7. `tastyStatic()`
193
+
194
+ The selector must be a string literal and valid CSS. Values must be static — strings, numbers, booleans, `null`, or objects/arrays of those. No variables, template literals, function calls, or spreads.
195
+
196
+ ## 8. Checklist
197
+
198
+ - Token, preset, recipe, unit and `@alias` names exist in the project config.
199
+ - Tasty shorthand chosen over CSS longhands; `flexGrow`/`flexShrink`/`flexBasis` over `flex`; `hide: true` over `display: 'none'`.
200
+ - Colors are `#tokens`, not hex/rgb/oklch/named; `$prop` not `var(--prop)`.
201
+ - Spacing uses `x`/`r`/`bw`/`ow` units; math uses `(…)`, not `calc(…)`.
202
+ - `true` only on the properties that accept it; no `!important`.
203
+ - Modifiers valid for the property; directional shorthand instead of placeholder zeros.
204
+ - Every state map starts with `''` (or `_`), is flat, and lives inside a property value.
205
+ - `@own()` only inside sub-elements; sub-element keys are capitalized and hold objects.
206
+ - Values are static; dynamic behavior comes from `mods` / `tokens` / `styleProps`.
package/docs/styles.md CHANGED
@@ -199,6 +199,10 @@ Positioning offsets with directional modifiers and multi-group support. Same dir
199
199
  to the two perpendicular sides. `inset: 'bottom dock'` gives `inset: auto 0 0 0` — anchored to
200
200
  the bottom, full width. Without a direction, `dock` fills every side.
201
201
 
202
+ A second value applies to the spanned sides: `inset: '2x 4x bottom dock'` pins the bottom at
203
+ `2x` and insets the sides by `4x`. Intended for a single edge — combining `dock` with several
204
+ directions has no well-defined meaning.
205
+
202
206
  **Output modifier:** `longhand` — forces output as individual CSS properties (`top`, `right`, `bottom`, `left`) instead of the `inset` shorthand.
203
207
 
204
208
  | Value | Effect |
@@ -212,6 +216,7 @@ the bottom, full width. Without a direction, `dock` fills every side.
212
216
  | `"bottom dock"` | Bottom/left/right `0`, top `auto` — bottom-anchored, full width |
213
217
  | `"right dock"` | Right/top/bottom `0`, left `auto` — right-anchored, full height |
214
218
  | `"2x bottom dock"` | Bottom/left/right `2x`, top `auto` |
219
+ | `"2x 4x bottom dock"` | Bottom `2x`, left/right `4x`, top `auto` |
215
220
  | `"dock"` | All sides `0` |
216
221
 
217
222
  Later comma-separated groups override earlier groups for conflicting directions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "2.10.0",
3
+ "version": "2.11.1",
4
4
  "description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -139,6 +139,7 @@
139
139
  "@eslint/js": "^10.0.1",
140
140
  "@size-limit/esbuild": "^12.0.0",
141
141
  "@size-limit/file": "^12.0.0",
142
+ "@tenphi/eslint-plugin-tasty": "^0.11.3",
142
143
  "@testing-library/jest-dom": "^6.9.1",
143
144
  "@testing-library/react": "^16.3.2",
144
145
  "@types/babel__core": "^7.20.5",