@tenphi/tasty 0.13.1 → 0.14.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.
Files changed (66) hide show
  1. package/README.md +117 -28
  2. package/dist/config.d.ts +13 -1
  3. package/dist/config.js +5 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/core/index.d.ts +5 -3
  6. package/dist/core/index.js +4 -3
  7. package/dist/debug.d.ts +26 -141
  8. package/dist/debug.js +356 -635
  9. package/dist/debug.js.map +1 -1
  10. package/dist/index.d.ts +5 -3
  11. package/dist/index.js +4 -3
  12. package/dist/parser/classify.js +2 -1
  13. package/dist/parser/classify.js.map +1 -1
  14. package/dist/parser/parser.js +1 -1
  15. package/dist/plugins/okhsl-plugin.js +2 -275
  16. package/dist/plugins/okhsl-plugin.js.map +1 -1
  17. package/dist/plugins/types.d.ts +1 -1
  18. package/dist/properties/index.js +2 -15
  19. package/dist/properties/index.js.map +1 -1
  20. package/dist/ssr/format-property.js +9 -7
  21. package/dist/ssr/format-property.js.map +1 -1
  22. package/dist/styles/color.js +9 -5
  23. package/dist/styles/color.js.map +1 -1
  24. package/dist/styles/createStyle.js +24 -21
  25. package/dist/styles/createStyle.js.map +1 -1
  26. package/dist/styles/index.js +1 -1
  27. package/dist/styles/predefined.js +1 -1
  28. package/dist/styles/predefined.js.map +1 -1
  29. package/dist/styles/types.d.ts +1 -1
  30. package/dist/tasty.d.ts +6 -6
  31. package/dist/tasty.js +1 -1
  32. package/dist/types.d.ts +1 -1
  33. package/dist/utils/color-math.d.ts +46 -0
  34. package/dist/utils/color-math.js +749 -0
  35. package/dist/utils/color-math.js.map +1 -0
  36. package/dist/utils/color-space.d.ts +5 -0
  37. package/dist/utils/color-space.js +229 -0
  38. package/dist/utils/color-space.js.map +1 -0
  39. package/dist/utils/colors.js +3 -1
  40. package/dist/utils/colors.js.map +1 -1
  41. package/dist/utils/mod-attrs.js +1 -1
  42. package/dist/utils/mod-attrs.js.map +1 -1
  43. package/dist/utils/process-tokens.d.ts +3 -13
  44. package/dist/utils/process-tokens.js +18 -98
  45. package/dist/utils/process-tokens.js.map +1 -1
  46. package/dist/utils/styles.d.ts +2 -15
  47. package/dist/utils/styles.js +22 -217
  48. package/dist/utils/styles.js.map +1 -1
  49. package/docs/PIPELINE.md +519 -0
  50. package/docs/README.md +30 -0
  51. package/docs/adoption.md +10 -2
  52. package/docs/comparison.md +11 -6
  53. package/docs/configuration.md +26 -3
  54. package/docs/debug.md +152 -339
  55. package/docs/dsl.md +3 -1
  56. package/docs/getting-started.md +21 -7
  57. package/docs/injector.md +2 -2
  58. package/docs/runtime.md +59 -9
  59. package/docs/ssr.md +2 -2
  60. package/docs/styles.md +1 -1
  61. package/docs/tasty-static.md +13 -2
  62. package/package.json +4 -3
  63. package/dist/utils/hsl-to-rgb.js +0 -38
  64. package/dist/utils/hsl-to-rgb.js.map +0 -1
  65. package/dist/utils/okhsl-to-rgb.js +0 -296
  66. package/dist/utils/okhsl-to-rgb.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  # Getting Started
2
2
 
3
- This guide walks you from zero to a working Tasty setup with tooling. For a feature overview, see the [README](../README.md). For the full style language reference, see the [Style DSL](dsl.md). For the React API, see the [Runtime API](runtime.md).
3
+ This guide walks you from zero to a working Tasty component, then through the optional shared configuration and tooling layers. For a feature overview, see the [README](../README.md). For the full style language reference, see the [Style DSL](dsl.md). For the React API, see the [Runtime API](runtime.md). For the rest of the docs by role or task, see the [Docs Hub](README.md).
4
4
 
5
5
  ---
6
6
 
@@ -47,9 +47,9 @@ export default function App() {
47
47
 
48
48
  ---
49
49
 
50
- ## Add configuration
50
+ ## Optional: add shared configuration
51
51
 
52
- Call `configure()` once, before your app renders, to define state aliases and other conventions your components share:
52
+ Use `configure()` once, before your app renders, when your app or design system needs shared state aliases, tokens, recipes, or parser extensions:
53
53
 
54
54
  ```tsx
55
55
  // src/tasty-config.ts
@@ -63,6 +63,8 @@ configure({
63
63
  });
64
64
  ```
65
65
 
66
+ These examples use `data-schema="dark"` as the root-state convention. If your app already uses a different attribute such as `data-theme="dark"`, keep the pattern and swap the attribute name consistently across your config and components.
67
+
66
68
  Import this file at the top of your app entry point so it runs before any component renders:
67
69
 
68
70
  ```tsx
@@ -74,9 +76,9 @@ import App from './App';
74
76
  createRoot(document.getElementById('root')!).render(<App />);
75
77
  ```
76
78
 
77
- ### Define design tokens and unit values
79
+ ### Define shared tokens and override default unit values
78
80
 
79
- Color tokens like `#primary` resolve to CSS custom properties at runtime (e.g. `var(--primary-color)`). Built-in units like `x`, `r`, and `bw` also multiply CSS custom properties. Define them via `configure({ tokens })`:
81
+ Color tokens like `#primary` resolve to CSS custom properties at runtime (e.g. `var(--primary-color)`). Built-in units like `x`, `r`, and `bw` already work without setup and multiply CSS custom properties by default. Use `configure({ tokens })` when you want to define shared token values or override the defaults your app uses:
80
82
 
81
83
  ```tsx
82
84
  // src/tasty-config.ts
@@ -108,7 +110,7 @@ configure({
108
110
  });
109
111
  ```
110
112
 
111
- Every component using `#primary`, `2x`, or `1r` adjusts automatically. Tokens are injected as `:root` CSS custom properties when the first style is rendered.
113
+ Every component using `#primary`, `2x`, or `1r` adjusts automatically. Tokens are injected as `:root` CSS custom properties when the first style is rendered. You can also use standard CSS color values such as `rgb(...)`, `hsl(...)`, and named colors directly; `okhsl(...)` is the recommended choice when you want authored colors that stay aligned with Tasty's design-system-oriented workflow.
112
114
 
113
115
  > **Note:** `configure({ replaceTokens })` is a separate mechanism — it replaces tokens with literal values at parse time (baked into CSS). Use it for value aliases like `$card-padding: '4x'` that should be resolved during style generation, not for defining color or unit values. See [Configuration — Replace Tokens](configuration.md#replace-tokens-parse-time-substitution) for details.
114
116
 
@@ -142,7 +144,7 @@ export default [
142
144
 
143
145
  ### What `recommended` catches
144
146
 
145
- The recommended config enables 18 rules covering the most common issues:
147
+ The `recommended` config enables 18 of the plugin's 27 total rules. It covers the most common issues without turning on the stricter governance rules:
146
148
 
147
149
  | Category | Rules | Examples |
148
150
  |----------|-------|---------|
@@ -193,9 +195,21 @@ All three share the same DSL, tokens, units, and state mappings.
193
195
 
194
196
  ## Next steps
195
197
 
198
+ - **[Docs Hub](README.md)** — Pick the next guide by role, rendering mode, or task
196
199
  - **[Methodology](methodology.md)** — The recommended patterns for structuring Tasty components: sub-elements, styleProps, tokens, extension
197
200
  - **[Style DSL](dsl.md)** — State maps, tokens, units, extending semantics, keyframes, @property
198
201
  - **[Runtime API](runtime.md)** — `tasty()` factory, component props, variants, sub-elements, hooks
199
202
  - **[Building a Design System](design-system.md)** — Practical guide to building a DS layer with Tasty: tokens, recipes, primitives, compound components
203
+ - **[Adoption Guide](adoption.md)** — Roll out Tasty inside an existing design system or platform team
204
+ - **[Comparison](comparison.md)** — Evaluate Tasty against other styling systems
200
205
  - **[Configuration](configuration.md)** — Full `configure()` API: tokens, recipes, custom units, style handlers, TypeScript extensions
201
206
  - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
207
+ - **[Debug Utilities](debug.md)** — Inspect generated CSS and debug runtime behavior when styles do not look right
208
+
209
+ ---
210
+
211
+ ## Common issues
212
+
213
+ - Styles are missing on first render: make sure the file that calls `configure()` is imported before any `tasty()` component renders.
214
+ - Token or unit values are not what you expect: check your `configure({ tokens, units })` setup, then inspect the generated CSS variables with [Debug Utilities](debug.md).
215
+ - You need zero-runtime or SSR instead of the default runtime path: use [Zero Runtime (tastyStatic)](tasty-static.md) or [Server-Side Rendering](ssr.md) rather than trying to retrofit the runtime setup later.
package/docs/injector.md CHANGED
@@ -199,7 +199,7 @@ slideIn.dispose(); // Immediate keyframes deletion from DOM
199
199
 
200
200
  ### `configure(config): void`
201
201
 
202
- Configures the Tasty style system. Must be called **before** any styles are generated (before first render).
202
+ Configures the Tasty style system. `configure()` is optional, but if you use it, it must be called **before** any styles are generated (before first render).
203
203
 
204
204
  ```typescript
205
205
  import { configure } from '@tenphi/tasty';
@@ -216,7 +216,7 @@ configure({
216
216
  nonce: 'csp-nonce', // CSP nonce for security
217
217
  states: { // Global predefined states for advanced state mapping
218
218
  '@mobile': '@media(w < 768px)',
219
- '@dark': '@root(theme=dark)',
219
+ '@dark': '@root(schema=dark)',
220
220
  },
221
221
  });
222
222
  ```
package/docs/runtime.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Runtime API
2
2
 
3
- The React-specific `tasty()` component factory, component props, and hooks. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md).
3
+ The React-specific `tasty()` component factory, component props, and hooks. For the shared style language (state maps, tokens, units, extending semantics), see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
4
4
 
5
5
  ---
6
6
 
@@ -262,23 +262,72 @@ function GlobalReset() {
262
262
  }
263
263
  ```
264
264
 
265
- ### useMergeStyles
265
+ ### useKeyframes
266
266
 
267
- Merge multiple style objects for compound component prop forwarding:
267
+ Inject `@keyframes` rules and return the generated animation name:
268
268
 
269
269
  ```tsx
270
- import { useMergeStyles } from '@tenphi/tasty';
270
+ import { useKeyframes } from '@tenphi/tasty';
271
271
 
272
- function MyTabs({ styles, tabListStyles, prefixStyles }) {
273
- const mergedStyles = useMergeStyles(styles, {
274
- TabList: tabListStyles,
275
- Prefix: prefixStyles,
272
+ function Spinner() {
273
+ const spin = useKeyframes(
274
+ {
275
+ from: { transform: 'rotate(0deg)' },
276
+ to: { transform: 'rotate(360deg)' },
277
+ },
278
+ { name: 'spin' }
279
+ );
280
+
281
+ return <div style={{ animation: `${spin} 1s linear infinite` }} />;
282
+ }
283
+ ```
284
+
285
+ `useKeyframes()` also supports a factory function with dependencies:
286
+
287
+ ```tsx
288
+ function Pulse({ scale }: { scale: number }) {
289
+ const pulse = useKeyframes(
290
+ () => ({
291
+ '0%': { transform: 'scale(1)' },
292
+ '100%': { transform: `scale(${scale})` },
293
+ }),
294
+ [scale]
295
+ );
296
+
297
+ return <div style={{ animation: `${pulse} 500ms ease-in-out alternate infinite` }} />;
298
+ }
299
+ ```
300
+
301
+ ### useProperty
302
+
303
+ Register a CSS `@property` rule so a custom property can animate smoothly:
304
+
305
+ ```tsx
306
+ import { useProperty } from '@tenphi/tasty';
307
+
308
+ function Spinner() {
309
+ useProperty('$rotation', {
310
+ syntax: '<angle>',
311
+ inherits: false,
312
+ initialValue: '0deg',
276
313
  });
277
314
 
278
- return <TabsElement styles={mergedStyles} />;
315
+ return <div style={{ transform: 'rotate(var(--rotation))' }} />;
279
316
  }
280
317
  ```
281
318
 
319
+ `useProperty()` accepts Tasty token syntax for the property name:
320
+
321
+ - `$name` defines `--name`
322
+ - `#name` defines `--name-color` and auto-infers `<color>`
323
+ - `--name` is also supported for existing CSS variables
324
+
325
+ ### Troubleshooting
326
+
327
+ - Styles are not updating: make sure `configure()` runs before first render, and verify the generated class name or global rule with [Debug Utilities](debug.md).
328
+ - SSR output looks wrong: check the [SSR guide](ssr.md) because the hooks integrate with SSR collectors differently than the client-only runtime path.
329
+ - Animation/custom property issues: prefer `useKeyframes()` and `useProperty()` over raw CSS when you want Tasty to manage injection and SSR collection for you.
330
+
282
331
  ---
283
332
 
284
333
  ## Learn more
@@ -289,3 +338,4 @@ function MyTabs({ styles, tabListStyles, prefixStyles }) {
289
338
  - **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
290
339
  - **[Zero Runtime (tastyStatic)](tasty-static.md)** — Build-time static styling with Babel plugin
291
340
  - **[Server-Side Rendering](ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks
341
+ - **[Debug Utilities](debug.md)** — Inspect injected CSS, cache state, and active styles at runtime
package/docs/ssr.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Server-Side Rendering (SSR)
2
2
 
3
- Tasty supports server-side rendering with zero-cost client hydration. Your existing `tasty()` components work unchanged -- SSR is opt-in and requires no per-component modifications.
3
+ Tasty supports server-side rendering with zero-cost client hydration. Your existing `tasty()` components work unchanged -- SSR is opt-in and requires no per-component modifications. For the broader docs map, see the [Docs Hub](README.md).
4
4
 
5
5
  ---
6
6
 
@@ -8,7 +8,7 @@ Tasty supports server-side rendering with zero-cost client hydration. Your exist
8
8
 
9
9
  | Dependency | Version | Required for |
10
10
  |---|---|---|
11
- | `react` | >= 18 | All SSR entry points (peer dependency of `@tenphi/tasty`) |
11
+ | `react` | >= 18 | All SSR entry points (matches the current peer dependency of `@tenphi/tasty`) |
12
12
  | `next` | >= 13 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
13
13
  | Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
14
14
 
package/docs/styles.md CHANGED
@@ -252,7 +252,7 @@ Text color with design token support.
252
252
  | `"(#primary, #secondary)"` | Fallback: use `#primary`, fall back to `#secondary` |
253
253
  | `true` | `currentColor` |
254
254
 
255
- When set to a named color token, also sets `$current-color` and `$current-color-rgb` custom properties for downstream use.
255
+ When set to a named color token, also sets `$current-color` and `$current-color-{colorSpace}` custom properties for downstream use (suffix depends on the configured `colorSpace`, default `oklch`).
256
256
 
257
257
  ### `svgFill`
258
258
 
@@ -1,6 +1,6 @@
1
1
  # Zero Runtime Mode (tastyStatic)
2
2
 
3
- `tastyStatic` is a build-time utility for generating CSS with zero runtime overhead. It's designed for static sites, no-JS websites, and performance-critical applications where you want to eliminate all runtime styling code.
3
+ `tastyStatic` is a build-time utility for generating CSS with zero runtime overhead. It's designed for static sites, no-JS websites, and performance-critical applications where you want to eliminate all runtime styling code. For the broader docs map, see the [Docs Hub](README.md).
4
4
 
5
5
  ---
6
6
 
@@ -130,6 +130,8 @@ module.exports = {
130
130
  };
131
131
  ```
132
132
 
133
+ These examples use `data-schema="dark"` as the root-state convention. If your app already uses a different root attribute such as `data-theme`, keep the same alias pattern and swap the attribute name consistently in your zero-runtime config.
134
+
133
135
  ### With Configuration
134
136
 
135
137
  ```javascript
@@ -141,7 +143,7 @@ module.exports = {
141
143
  states: {
142
144
  '@mobile': '@media(w < 768px)',
143
145
  '@tablet': '@media(w < 1024px)',
144
- '@dark': '@root(theme=dark)',
146
+ '@dark': '@root(schema=dark)',
145
147
  },
146
148
  devMode: true,
147
149
  },
@@ -413,8 +415,17 @@ const card = tastyStatic({
413
415
 
414
416
  ---
415
417
 
418
+ ## Common Issues
419
+
420
+ - No CSS file is generated: make sure the Babel plugin actually runs for files importing `@tenphi/tasty/static`, and verify the `output` path is writable.
421
+ - Styles stay dynamic by mistake: `tastyStatic()` only supports build-time-known values. Move runtime values to CSS variables or switch that component to runtime `tasty()`.
422
+ - Turbopack config behaves inconsistently: prefer `configFile` over inline functions so the setup stays JSON-serializable.
423
+
424
+ ---
425
+
416
426
  ## Related
417
427
 
428
+ - [Docs Hub](README.md) — Choose the right guide by task or rendering mode
418
429
  - [Style DSL](dsl.md) — State maps, tokens, units, extending semantics (shared by runtime and static)
419
430
  - [Runtime API](runtime.md) — Runtime styling: `tasty()` factory, component props, variants, sub-elements, hooks
420
431
  - [Configuration](configuration.md) — Global configuration: tokens, recipes, custom units, and style handlers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
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",
@@ -168,7 +168,7 @@
168
168
  "path",
169
169
  "crypto"
170
170
  ],
171
- "limit": "27 kB"
171
+ "limit": "28 kB"
172
172
  },
173
173
  {
174
174
  "name": "babel-plugin",
@@ -179,7 +179,7 @@
179
179
  "path",
180
180
  "crypto"
181
181
  ],
182
- "limit": "38 kB"
182
+ "limit": "39 kB"
183
183
  }
184
184
  ],
185
185
  "scripts": {
@@ -193,6 +193,7 @@
193
193
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
194
194
  "format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
195
195
  "size": "size-limit",
196
+ "bench": "vitest bench",
196
197
  "changeset": "changeset",
197
198
  "version": "changeset version",
198
199
  "release": "changeset publish",
@@ -1,38 +0,0 @@
1
- import { hslToRgbValues } from "./process-tokens.js";
2
-
3
- //#region src/utils/hsl-to-rgb.ts
4
- /**
5
- * Convert HSL color string to RGB.
6
- * Supports:
7
- * - Modern: hsl(h s% l%), hsl(h s% l% / a)
8
- * - Legacy: hsl(h, s%, l%), hsla(h, s%, l%, a)
9
- */
10
- function hslToRgb(hslStr) {
11
- const match = hslStr.match(/hsla?\(([^)]+)\)/i);
12
- if (!match) return null;
13
- const [colorPart, slashAlpha] = match[1].trim().split("/");
14
- const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
15
- if (parts.length < 3) return null;
16
- const alphaPart = slashAlpha?.trim() || (parts.length >= 4 ? parts[3] : null);
17
- let h = parseFloat(parts[0]);
18
- const hueStr = parts[0].toLowerCase();
19
- if (hueStr.endsWith("turn")) h = parseFloat(hueStr) * 360;
20
- else if (hueStr.endsWith("rad")) h = parseFloat(hueStr) * 180 / Math.PI;
21
- h = (h % 360 + 360) % 360;
22
- const parsePercent = (val) => {
23
- const num = parseFloat(val);
24
- return val.includes("%") ? num / 100 : num;
25
- };
26
- const s = Math.max(0, Math.min(1, parsePercent(parts[1])));
27
- const l = Math.max(0, Math.min(1, parsePercent(parts[2])));
28
- const [r, g, b] = hslToRgbValues(h, s, l);
29
- if (alphaPart) {
30
- const alpha = parseFloat(alphaPart.trim());
31
- return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${alpha})`;
32
- }
33
- return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
34
- }
35
-
36
- //#endregion
37
- export { hslToRgb };
38
- //# sourceMappingURL=hsl-to-rgb.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hsl-to-rgb.js","names":[],"sources":["../../src/utils/hsl-to-rgb.ts"],"sourcesContent":["import { hslToRgbValues } from './process-tokens';\n\n/**\n * Convert HSL color string to RGB.\n * Supports:\n * - Modern: hsl(h s% l%), hsl(h s% l% / a)\n * - Legacy: hsl(h, s%, l%), hsla(h, s%, l%, a)\n */\nexport function hslToRgb(hslStr: string): string | null {\n const match = hslStr.match(/hsla?\\(([^)]+)\\)/i);\n if (!match) return null;\n\n const inner = match[1].trim();\n const [colorPart, slashAlpha] = inner.split('/');\n const parts = colorPart\n .trim()\n .split(/[,\\s]+/)\n .filter(Boolean);\n\n if (parts.length < 3) return null;\n\n // Alpha can come from slash notation (modern) or 4th part (legacy comma syntax)\n const alphaPart = slashAlpha?.trim() || (parts.length >= 4 ? parts[3] : null);\n\n // Parse hue\n let h = parseFloat(parts[0]);\n const hueStr = parts[0].toLowerCase();\n if (hueStr.endsWith('turn')) h = parseFloat(hueStr) * 360;\n else if (hueStr.endsWith('rad')) h = (parseFloat(hueStr) * 180) / Math.PI;\n h = ((h % 360) + 360) % 360;\n\n // Parse saturation and lightness\n const parsePercent = (val: string): number => {\n const num = parseFloat(val);\n return val.includes('%') ? num / 100 : num;\n };\n const s = Math.max(0, Math.min(1, parsePercent(parts[1])));\n const l = Math.max(0, Math.min(1, parsePercent(parts[2])));\n\n // Use shared HSL to RGB conversion\n const [r, g, b] = hslToRgbValues(h, s, l);\n\n if (alphaPart) {\n const alpha = parseFloat(alphaPart.trim());\n return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${alpha})`;\n }\n\n return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,SAAS,QAA+B;CACtD,MAAM,QAAQ,OAAO,MAAM,oBAAoB;AAC/C,KAAI,CAAC,MAAO,QAAO;CAGnB,MAAM,CAAC,WAAW,cADJ,MAAM,GAAG,MAAM,CACS,MAAM,IAAI;CAChD,MAAM,QAAQ,UACX,MAAM,CACN,MAAM,SAAS,CACf,OAAO,QAAQ;AAElB,KAAI,MAAM,SAAS,EAAG,QAAO;CAG7B,MAAM,YAAY,YAAY,MAAM,KAAK,MAAM,UAAU,IAAI,MAAM,KAAK;CAGxE,IAAI,IAAI,WAAW,MAAM,GAAG;CAC5B,MAAM,SAAS,MAAM,GAAG,aAAa;AACrC,KAAI,OAAO,SAAS,OAAO,CAAE,KAAI,WAAW,OAAO,GAAG;UAC7C,OAAO,SAAS,MAAM,CAAE,KAAK,WAAW,OAAO,GAAG,MAAO,KAAK;AACvE,MAAM,IAAI,MAAO,OAAO;CAGxB,MAAM,gBAAgB,QAAwB;EAC5C,MAAM,MAAM,WAAW,IAAI;AAC3B,SAAO,IAAI,SAAS,IAAI,GAAG,MAAM,MAAM;;CAEzC,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,aAAa,MAAM,GAAG,CAAC,CAAC;CAC1D,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,aAAa,MAAM,GAAG,CAAC,CAAC;CAG1D,MAAM,CAAC,GAAG,GAAG,KAAK,eAAe,GAAG,GAAG,EAAE;AAEzC,KAAI,WAAW;EACb,MAAM,QAAQ,WAAW,UAAU,MAAM,CAAC;AAC1C,SAAO,QAAQ,KAAK,MAAM,EAAE,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC,IAAI,MAAM;;AAG7E,QAAO,OAAO,KAAK,MAAM,EAAE,CAAC,GAAG,KAAK,MAAM,EAAE,CAAC,GAAG,KAAK,MAAM,EAAE,CAAC"}
@@ -1,296 +0,0 @@
1
- //#region src/utils/okhsl-to-rgb.ts
2
- const OKLab_to_LMS_M = [
3
- [
4
- 1,
5
- .3963377773761749,
6
- .2158037573099136
7
- ],
8
- [
9
- 1,
10
- -.1055613458156586,
11
- -.0638541728258133
12
- ],
13
- [
14
- 1,
15
- -.0894841775298119,
16
- -1.2914855480194092
17
- ]
18
- ];
19
- const LMS_to_linear_sRGB_M = [
20
- [
21
- 4.076741636075959,
22
- -3.307711539258062,
23
- .2309699031821041
24
- ],
25
- [
26
- -1.2684379732850313,
27
- 2.6097573492876878,
28
- -.3413193760026569
29
- ],
30
- [
31
- -.004196076138675526,
32
- -.703418617935936,
33
- 1.7076146940746113
34
- ]
35
- ];
36
- const OKLab_to_linear_sRGB_coefficients = [
37
- [[-1.8817030993265873, -.8093650129914302], [
38
- 1.19086277,
39
- 1.76576728,
40
- .59662641,
41
- .75515197,
42
- .56771245
43
- ]],
44
- [[1.8144407988010998, -1.194452667805235], [
45
- .73956515,
46
- -.45954404,
47
- .08285427,
48
- .12541073,
49
- -.14503204
50
- ]],
51
- [[.13110757611180954, 1.813339709266608], [
52
- 1.35733652,
53
- -.00915799,
54
- -1.1513021,
55
- -.50559606,
56
- .00692167
57
- ]]
58
- ];
59
- const TAU = 2 * Math.PI;
60
- const K1 = .206;
61
- const K2 = .03;
62
- const K3 = (1 + K1) / (1 + K2);
63
- const constrainAngle = (angle) => (angle % 360 + 360) % 360;
64
- const toeInv = (x) => (x ** 2 + K1 * x) / (K3 * (x + K2));
65
- const dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
66
- const dotXY = (a, b) => a[0] * b[0] + a[1] * b[1];
67
- const transform = (input, matrix) => [
68
- dot3(input, matrix[0]),
69
- dot3(input, matrix[1]),
70
- dot3(input, matrix[2])
71
- ];
72
- const cubed3 = (lms) => [
73
- lms[0] ** 3,
74
- lms[1] ** 3,
75
- lms[2] ** 3
76
- ];
77
- const OKLabToLinearSRGB = (lab) => {
78
- return transform(cubed3(transform(lab, OKLab_to_LMS_M)), LMS_to_linear_sRGB_M);
79
- };
80
- const sRGBLinearToGamma = (val) => {
81
- const sign = val < 0 ? -1 : 1;
82
- const abs = Math.abs(val);
83
- return abs > .0031308 ? sign * (1.055 * Math.pow(abs, 1 / 2.4) - .055) : 12.92 * val;
84
- };
85
- const computeMaxSaturationOKLC = (a, b) => {
86
- const okCoeff = OKLab_to_linear_sRGB_coefficients;
87
- const lmsToRgb = LMS_to_linear_sRGB_M;
88
- const tmp2 = [a, b];
89
- const tmp3 = [
90
- 0,
91
- a,
92
- b
93
- ];
94
- let chnlCoeff;
95
- let chnlLMS;
96
- if (dotXY(okCoeff[0][0], tmp2) > 1) {
97
- chnlCoeff = okCoeff[0][1];
98
- chnlLMS = lmsToRgb[0];
99
- } else if (dotXY(okCoeff[1][0], tmp2) > 1) {
100
- chnlCoeff = okCoeff[1][1];
101
- chnlLMS = lmsToRgb[1];
102
- } else {
103
- chnlCoeff = okCoeff[2][1];
104
- chnlLMS = lmsToRgb[2];
105
- }
106
- const [k0, k1, k2, k3, k4] = chnlCoeff;
107
- const [wl, wm, ws] = chnlLMS;
108
- let sat = k0 + k1 * a + k2 * b + k3 * (a * a) + k4 * a * b;
109
- const dotYZ = (mat, vec) => mat[1] * vec[1] + mat[2] * vec[2];
110
- const kl = dotYZ(OKLab_to_LMS_M[0], tmp3);
111
- const km = dotYZ(OKLab_to_LMS_M[1], tmp3);
112
- const ks = dotYZ(OKLab_to_LMS_M[2], tmp3);
113
- const l_ = 1 + sat * kl;
114
- const m_ = 1 + sat * km;
115
- const s_ = 1 + sat * ks;
116
- const l = l_ ** 3;
117
- const m = m_ ** 3;
118
- const s = s_ ** 3;
119
- const lds = 3 * kl * l_ * l_;
120
- const mds = 3 * km * m_ * m_;
121
- const sds = 3 * ks * s_ * s_;
122
- const lds2 = 6 * kl * kl * l_;
123
- const mds2 = 6 * km * km * m_;
124
- const sds2 = 6 * ks * ks * s_;
125
- const f = wl * l + wm * m + ws * s;
126
- const f1 = wl * lds + wm * mds + ws * sds;
127
- const f2 = wl * lds2 + wm * mds2 + ws * sds2;
128
- sat = sat - f * f1 / (f1 * f1 - .5 * f * f2);
129
- return sat;
130
- };
131
- const findCuspOKLCH = (a, b) => {
132
- const S_cusp = computeMaxSaturationOKLC(a, b);
133
- const rgb_at_max = OKLabToLinearSRGB([
134
- 1,
135
- S_cusp * a,
136
- S_cusp * b
137
- ]);
138
- const L_cusp = Math.cbrt(1 / Math.max(Math.max(rgb_at_max[0], rgb_at_max[1]), Math.max(rgb_at_max[2], 0)));
139
- return [L_cusp, L_cusp * S_cusp];
140
- };
141
- const findGamutIntersectionOKLCH = (a, b, l1, c1, l0, cusp) => {
142
- const lmsToRgb = LMS_to_linear_sRGB_M;
143
- const tmp3 = [
144
- 0,
145
- a,
146
- b
147
- ];
148
- const floatMax = Number.MAX_VALUE;
149
- let t;
150
- const dotYZ = (mat, vec) => mat[1] * vec[1] + mat[2] * vec[2];
151
- const dotXYZ = (vec, x, y, z) => vec[0] * x + vec[1] * y + vec[2] * z;
152
- if ((l1 - l0) * cusp[1] - (cusp[0] - l0) * c1 <= 0) {
153
- const denom = c1 * cusp[0] + cusp[1] * (l0 - l1);
154
- t = denom === 0 ? 0 : cusp[1] * l0 / denom;
155
- } else {
156
- const denom = c1 * (cusp[0] - 1) + cusp[1] * (l0 - l1);
157
- t = denom === 0 ? 0 : cusp[1] * (l0 - 1) / denom;
158
- const dl = l1 - l0;
159
- const dc = c1;
160
- const kl = dotYZ(OKLab_to_LMS_M[0], tmp3);
161
- const km = dotYZ(OKLab_to_LMS_M[1], tmp3);
162
- const ks = dotYZ(OKLab_to_LMS_M[2], tmp3);
163
- const L = l0 * (1 - t) + t * l1;
164
- const C = t * c1;
165
- const l_ = L + C * kl;
166
- const m_ = L + C * km;
167
- const s_ = L + C * ks;
168
- const l = l_ ** 3;
169
- const m = m_ ** 3;
170
- const s = s_ ** 3;
171
- const ldt = 3 * (dl + dc * kl) * l_ * l_;
172
- const mdt = 3 * (dl + dc * km) * m_ * m_;
173
- const sdt = 3 * (dl + dc * ks) * s_ * s_;
174
- const ldt2 = 6 * (dl + dc * kl) ** 2 * l_;
175
- const mdt2 = 6 * (dl + dc * km) ** 2 * m_;
176
- const sdt2 = 6 * (dl + dc * ks) ** 2 * s_;
177
- const r_ = dotXYZ(lmsToRgb[0], l, m, s) - 1;
178
- const r1 = dotXYZ(lmsToRgb[0], ldt, mdt, sdt);
179
- const r2 = dotXYZ(lmsToRgb[0], ldt2, mdt2, sdt2);
180
- const ur = r1 / (r1 * r1 - .5 * r_ * r2);
181
- let tr = -r_ * ur;
182
- const g_ = dotXYZ(lmsToRgb[1], l, m, s) - 1;
183
- const g1 = dotXYZ(lmsToRgb[1], ldt, mdt, sdt);
184
- const g2 = dotXYZ(lmsToRgb[1], ldt2, mdt2, sdt2);
185
- const ug = g1 / (g1 * g1 - .5 * g_ * g2);
186
- let tg = -g_ * ug;
187
- const b_ = dotXYZ(lmsToRgb[2], l, m, s) - 1;
188
- const b1 = dotXYZ(lmsToRgb[2], ldt, mdt, sdt);
189
- const b2 = dotXYZ(lmsToRgb[2], ldt2, mdt2, sdt2);
190
- const ub = b1 / (b1 * b1 - .5 * b_ * b2);
191
- let tb = -b_ * ub;
192
- tr = ur >= 0 ? tr : floatMax;
193
- tg = ug >= 0 ? tg : floatMax;
194
- tb = ub >= 0 ? tb : floatMax;
195
- t += Math.min(tr, Math.min(tg, tb));
196
- }
197
- return t;
198
- };
199
- const computeSt = (cusp) => [cusp[1] / cusp[0], cusp[1] / (1 - cusp[0])];
200
- const computeStMid = (a, b) => [.11516993 + 1 / (7.4477897 + 4.1590124 * b + a * (-2.19557347 + 1.75198401 * b + a * (-2.13704948 - 10.02301043 * b + a * (-4.24894561 + 5.38770819 * b + 4.69891013 * a)))), .11239642 + 1 / (1.6132032 - .68124379 * b + a * (.40370612 + .90148123 * b + a * (-.27087943 + .6122399 * b + a * (.00299215 - .45399568 * b - .14661872 * a))))];
201
- const getCs = (L, a, b, cusp) => {
202
- const cMax = findGamutIntersectionOKLCH(a, b, L, 1, L, cusp);
203
- const stMax = computeSt(cusp);
204
- const k = cMax / Math.min(L * stMax[0], (1 - L) * stMax[1]);
205
- const stMid = computeStMid(a, b);
206
- let ca = L * stMid[0];
207
- let cb = (1 - L) * stMid[1];
208
- const cMid = .9 * k * Math.sqrt(Math.sqrt(1 / (1 / ca ** 4 + 1 / cb ** 4)));
209
- ca = L * .4;
210
- cb = (1 - L) * .8;
211
- return [
212
- Math.sqrt(1 / (1 / ca ** 2 + 1 / cb ** 2)),
213
- cMid,
214
- cMax
215
- ];
216
- };
217
- const OKHSLToOKLab = (hsl) => {
218
- let h = hsl[0];
219
- const s = hsl[1];
220
- const l = hsl[2];
221
- const L = toeInv(l);
222
- let a = 0;
223
- let b = 0;
224
- h = constrainAngle(h) / 360;
225
- if (L !== 0 && L !== 1 && s !== 0) {
226
- const a_ = Math.cos(TAU * h);
227
- const b_ = Math.sin(TAU * h);
228
- const [c0, cMid, cMax] = getCs(L, a_, b_, findCuspOKLCH(a_, b_));
229
- const mid = .8;
230
- const midInv = 1.25;
231
- let t, k0, k1, k2;
232
- if (s < mid) {
233
- t = midInv * s;
234
- k0 = 0;
235
- k1 = mid * c0;
236
- k2 = 1 - k1 / cMid;
237
- } else {
238
- t = 5 * (s - .8);
239
- k0 = cMid;
240
- k1 = .2 * cMid ** 2 * 1.25 ** 2 / c0;
241
- k2 = 1 - k1 / (cMax - cMid);
242
- }
243
- const c = k0 + t * k1 / (1 - k2 * t);
244
- a = c * a_;
245
- b = c * b_;
246
- }
247
- return [
248
- L,
249
- a,
250
- b
251
- ];
252
- };
253
- function okhslToSrgbInternal(h, s, l) {
254
- const linearRGB = OKLabToLinearSRGB(OKHSLToOKLab([
255
- h,
256
- s,
257
- l
258
- ]));
259
- return [
260
- sRGBLinearToGamma(linearRGB[0]),
261
- sRGBLinearToGamma(linearRGB[1]),
262
- sRGBLinearToGamma(linearRGB[2])
263
- ];
264
- }
265
- /**
266
- * Convert OKHSL color string to RGB.
267
- * Uses the same algorithm as the okhsl-plugin.
268
- */
269
- function okhslToRgb(okhslStr) {
270
- const match = okhslStr.match(/okhsl\(([^)]+)\)/i);
271
- if (!match) return null;
272
- const [colorPart, alphaPart] = match[1].trim().split("/");
273
- const parts = colorPart.trim().split(/[,\s]+/).filter(Boolean);
274
- if (parts.length < 3) return null;
275
- let h = parseFloat(parts[0]);
276
- const hueStr = parts[0].toLowerCase();
277
- if (hueStr.endsWith("turn")) h = parseFloat(hueStr) * 360;
278
- else if (hueStr.endsWith("rad")) h = parseFloat(hueStr) * 180 / Math.PI;
279
- else if (hueStr.endsWith("deg")) h = parseFloat(hueStr);
280
- const parsePercent = (val) => {
281
- const num = parseFloat(val);
282
- return val.includes("%") ? num / 100 : num;
283
- };
284
- const s = Math.max(0, Math.min(1, parsePercent(parts[1])));
285
- const l = Math.max(0, Math.min(1, parsePercent(parts[2])));
286
- const [r, g, b] = okhslToSrgbInternal(h, s, l);
287
- const r255 = Math.round(Math.max(0, Math.min(1, r)) * 255);
288
- const g255 = Math.round(Math.max(0, Math.min(1, g)) * 255);
289
- const b255 = Math.round(Math.max(0, Math.min(1, b)) * 255);
290
- if (alphaPart) return `rgba(${r255}, ${g255}, ${b255}, ${parseFloat(alphaPart.trim())})`;
291
- return `rgb(${r255} ${g255} ${b255})`;
292
- }
293
-
294
- //#endregion
295
- export { okhslToRgb };
296
- //# sourceMappingURL=okhsl-to-rgb.js.map