@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.
- package/README.md +1 -0
- package/dist/{collector-DCWdN88E.js → collector-AHZaBSv8.js} +3 -3
- package/dist/{collector-DCWdN88E.js.map → collector-AHZaBSv8.js.map} +1 -1
- package/dist/{collector-CddJZDOr.d.ts → collector-DYA5AOwr.d.ts} +2 -2
- package/dist/{config-C3SKYuxY.d.ts → config-BtK9fUaz.d.ts} +2 -2
- package/dist/{config-DA5FBnzm.js → config-YDAcLaVf.js} +33 -30
- package/dist/config-YDAcLaVf.js.map +1 -0
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +5 -5
- package/dist/{core-BhlL-3k-.js → core-CS4bzqGu.js} +10 -10
- package/dist/core-CS4bzqGu.js.map +1 -0
- package/dist/{css-writer-Cnj-KrI4.js → css-writer-BXSANVrq.js} +3 -3
- package/dist/{css-writer-Cnj-KrI4.js.map → css-writer-BXSANVrq.js.map} +1 -1
- package/dist/{format-rules-COR773Tn.js → format-rules-CYriCDwq.js} +2 -2
- package/dist/{format-rules-COR773Tn.js.map → format-rules-CYriCDwq.js.map} +1 -1
- package/dist/{hydrate-F9olJTHt.js → hydrate-GVTorHpU.js} +2 -2
- package/dist/{hydrate-F9olJTHt.js.map → hydrate-GVTorHpU.js.map} +1 -1
- package/dist/{index-C5owZ15q.d.ts → index-B9ih23uv.d.ts} +4 -2
- package/dist/{index-ByBYTH9l.d.ts → index-BYtnj_gA.d.ts} +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +6 -6
- package/dist/{keyframes-DZbGV4H4.js → keyframes-CtcSlw4k.js} +2 -2
- package/dist/{keyframes-DZbGV4H4.js.map → keyframes-CtcSlw4k.js.map} +1 -1
- package/dist/{merge-styles-CvUi7SOD.d.ts → merge-styles-BZV-XAwX.d.ts} +2 -2
- package/dist/{merge-styles-DRUFVZH4.js → merge-styles-D4ITH4bc.js} +2 -2
- package/dist/{merge-styles-DRUFVZH4.js.map → merge-styles-D4ITH4bc.js.map} +1 -1
- package/dist/{resolve-recipes-DUfJKJef.js → resolve-recipes-llr0COs8.js} +3 -3
- package/dist/{resolve-recipes-DUfJKJef.js.map → resolve-recipes-llr0COs8.js.map} +1 -1
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro.js +3 -3
- package/dist/ssr/index.d.ts +1 -1
- package/dist/ssr/index.js +3 -3
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +4 -4
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +4 -4
- package/dist/static/index.js.map +1 -1
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +4 -4
- package/dist/zero/index.d.ts +1 -1
- package/dist/zero/index.js +1 -1
- package/docs/README.md +1 -0
- package/docs/ai-agents.md +206 -0
- package/docs/styles.md +5 -0
- package/package.json +2 -1
- package/dist/config-DA5FBnzm.js.map +0 -1
- 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-
|
|
3
|
-
import { i as registerSSRCollectorGetter } from "../format-rules-
|
|
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-
|
|
6
|
-
import { n as hydrateTastyClasses } from "../hydrate-
|
|
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
|
package/dist/static/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as Styles } from "../index-
|
|
2
|
-
import { t as mergeStyles } from "../merge-styles-
|
|
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
|
/**
|
package/dist/static/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as mergeStyles } from "../merge-styles-
|
|
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(`[
|
|
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("[
|
|
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("[
|
|
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
|
package/dist/static/index.js.map
CHANGED
|
@@ -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 `[
|
|
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"}
|
package/dist/zero/babel.d.ts
CHANGED
package/dist/zero/babel.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { i as getGlobalConfigTokens, t as configure, u as getGlobalStyles, v as resetConfig } from "../config-
|
|
2
|
-
import { t as mergeStyles } from "../merge-styles-
|
|
3
|
-
import { t as resolveRecipes } from "../resolve-recipes-
|
|
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-
|
|
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";
|
package/dist/zero/index.d.ts
CHANGED
package/dist/zero/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { o as extractStylesForSelector, s as extractStylesWithChunks, t as CSSWriter } from "../css-writer-
|
|
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.
|
|
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",
|