@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.
- package/README.md +117 -28
- package/dist/config.d.ts +13 -1
- package/dist/config.js +5 -1
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +5 -3
- package/dist/core/index.js +4 -3
- package/dist/debug.d.ts +26 -141
- package/dist/debug.js +356 -635
- package/dist/debug.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -3
- package/dist/parser/classify.js +2 -1
- package/dist/parser/classify.js.map +1 -1
- package/dist/parser/parser.js +1 -1
- package/dist/plugins/okhsl-plugin.js +2 -275
- package/dist/plugins/okhsl-plugin.js.map +1 -1
- package/dist/plugins/types.d.ts +1 -1
- package/dist/properties/index.js +2 -15
- package/dist/properties/index.js.map +1 -1
- package/dist/ssr/format-property.js +9 -7
- package/dist/ssr/format-property.js.map +1 -1
- package/dist/styles/color.js +9 -5
- package/dist/styles/color.js.map +1 -1
- package/dist/styles/createStyle.js +24 -21
- package/dist/styles/createStyle.js.map +1 -1
- package/dist/styles/index.js +1 -1
- package/dist/styles/predefined.js +1 -1
- package/dist/styles/predefined.js.map +1 -1
- package/dist/styles/types.d.ts +1 -1
- package/dist/tasty.d.ts +6 -6
- package/dist/tasty.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils/color-math.d.ts +46 -0
- package/dist/utils/color-math.js +749 -0
- package/dist/utils/color-math.js.map +1 -0
- package/dist/utils/color-space.d.ts +5 -0
- package/dist/utils/color-space.js +229 -0
- package/dist/utils/color-space.js.map +1 -0
- package/dist/utils/colors.js +3 -1
- package/dist/utils/colors.js.map +1 -1
- package/dist/utils/mod-attrs.js +1 -1
- package/dist/utils/mod-attrs.js.map +1 -1
- package/dist/utils/process-tokens.d.ts +3 -13
- package/dist/utils/process-tokens.js +18 -98
- package/dist/utils/process-tokens.js.map +1 -1
- package/dist/utils/styles.d.ts +2 -15
- package/dist/utils/styles.js +22 -217
- package/dist/utils/styles.js.map +1 -1
- package/docs/PIPELINE.md +519 -0
- package/docs/README.md +30 -0
- package/docs/adoption.md +10 -2
- package/docs/comparison.md +11 -6
- package/docs/configuration.md +26 -3
- package/docs/debug.md +152 -339
- package/docs/dsl.md +3 -1
- package/docs/getting-started.md +21 -7
- package/docs/injector.md +2 -2
- package/docs/runtime.md +59 -9
- package/docs/ssr.md +2 -2
- package/docs/styles.md +1 -1
- package/docs/tasty-static.md +13 -2
- package/package.json +4 -3
- package/dist/utils/hsl-to-rgb.js +0 -38
- package/dist/utils/hsl-to-rgb.js.map +0 -1
- package/dist/utils/okhsl-to-rgb.js +0 -296
- package/dist/utils/okhsl-to-rgb.js.map +0 -1
package/docs/getting-started.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting Started
|
|
2
2
|
|
|
3
|
-
This guide walks you from zero to a working Tasty
|
|
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
|
-
##
|
|
50
|
+
## Optional: add shared configuration
|
|
51
51
|
|
|
52
|
-
|
|
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
|
|
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`
|
|
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
|
|
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.
|
|
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(
|
|
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
|
-
###
|
|
265
|
+
### useKeyframes
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
Inject `@keyframes` rules and return the generated animation name:
|
|
268
268
|
|
|
269
269
|
```tsx
|
|
270
|
-
import {
|
|
270
|
+
import { useKeyframes } from '@tenphi/tasty';
|
|
271
271
|
|
|
272
|
-
function
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
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 <
|
|
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-
|
|
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
|
|
package/docs/tasty-static.md
CHANGED
|
@@ -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(
|
|
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.
|
|
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": "
|
|
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": "
|
|
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",
|
package/dist/utils/hsl-to-rgb.js
DELETED
|
@@ -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
|