@seyuna/postcss 1.0.0-canary.13 → 1.0.0-canary.15

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/.github/workflows/release.yml +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +179 -0
  4. package/dist/at-rules/color-scheme.d.ts +4 -0
  5. package/dist/at-rules/color-scheme.js +43 -0
  6. package/dist/at-rules/color.d.ts +5 -4
  7. package/dist/at-rules/color.js +22 -87
  8. package/dist/at-rules/conditional.d.ts +6 -0
  9. package/dist/at-rules/conditional.js +29 -0
  10. package/dist/at-rules/container.d.ts +2 -1
  11. package/dist/at-rules/container.js +12 -8
  12. package/dist/at-rules/custom-media.d.ts +15 -0
  13. package/dist/at-rules/custom-media.js +40 -0
  14. package/dist/at-rules/index.d.ts +3 -2
  15. package/dist/at-rules/index.js +22 -22
  16. package/dist/at-rules/mixin.d.ts +10 -0
  17. package/dist/at-rules/mixin.js +37 -0
  18. package/dist/config.d.ts +20 -0
  19. package/dist/config.js +47 -0
  20. package/dist/errors.d.ts +7 -0
  21. package/dist/errors.js +14 -0
  22. package/dist/functions/color.d.ts +7 -2
  23. package/dist/functions/color.js +103 -27
  24. package/dist/functions/index.d.ts +2 -1
  25. package/dist/functions/index.js +10 -12
  26. package/dist/functions/theme.d.ts +6 -0
  27. package/dist/functions/theme.js +17 -0
  28. package/dist/helpers.d.ts +11 -0
  29. package/dist/helpers.js +54 -0
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +6 -5
  32. package/dist/parser.d.ts +4 -0
  33. package/dist/parser.js +59 -0
  34. package/dist/plugin.d.ts +2 -4
  35. package/dist/plugin.js +17 -22
  36. package/dist/types.d.ts +1 -19
  37. package/dist/types.js +1 -2
  38. package/package.json +14 -5
  39. package/src/at-rules/color-scheme.ts +52 -0
  40. package/src/at-rules/color.ts +20 -87
  41. package/src/at-rules/conditional.ts +34 -0
  42. package/src/at-rules/container.ts +13 -3
  43. package/src/at-rules/custom-media.ts +50 -0
  44. package/src/at-rules/index.ts +13 -6
  45. package/src/at-rules/mixin.ts +46 -0
  46. package/src/config.ts +74 -0
  47. package/src/errors.ts +23 -0
  48. package/src/functions/color.ts +120 -26
  49. package/src/functions/index.ts +9 -4
  50. package/src/functions/theme.ts +20 -0
  51. package/src/helpers.ts +74 -0
  52. package/src/index.ts +3 -1
  53. package/src/parser.ts +80 -0
  54. package/src/plugin.ts +13 -21
  55. package/src/types.ts +1 -19
  56. package/tests/plugin.test.ts +251 -0
  57. package/tsconfig.json +2 -2
  58. package/dist/at-rules/dark.d.ts +0 -23
  59. package/dist/at-rules/dark.js +0 -65
  60. package/dist/at-rules/light.d.ts +0 -23
  61. package/dist/at-rules/light.js +0 -65
  62. package/dist/functions/spacing.d.ts +0 -1
  63. package/dist/functions/spacing.js +0 -6
  64. package/src/at-rules/dark.ts +0 -69
  65. package/src/at-rules/light.ts +0 -69
  66. package/src/functions/spacing.ts +0 -3
@@ -24,7 +24,7 @@ jobs:
24
24
  - name: Setup Node.js with npm auth
25
25
  uses: actions/setup-node@v4
26
26
  with:
27
- node-version: 20
27
+ node-version: 22
28
28
  registry-url: https://registry.npmjs.org/
29
29
  scope: "@seyuna"
30
30
  always-auth: true
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # [1.0.0-canary.15](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.14...v1.0.0-canary.15) (2026-01-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * workflow node version upgrade. ([e98349a](https://github.com/seyuna-corp/seyuna-postcss/commit/e98349af69aab446f0ac4d0790112d5c702c1b36))
7
+
8
+ # [1.0.0-canary.14](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.13...v1.0.0-canary.14) (2025-10-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * bug in generateRules ([d5b66d3](https://github.com/seyuna-corp/seyuna-postcss/commit/d5b66d32786f554c0e4f7ea98ae526e7f7ac82c5))
14
+ * removed pnpm lock file that ([6d87d61](https://github.com/seyuna-corp/seyuna-postcss/commit/6d87d616a79a2a539b09dea26bc907dee1d437e2))
15
+ * updated lock file ([d52dcfb](https://github.com/seyuna-corp/seyuna-postcss/commit/d52dcfb2bccbd502d744ceb09aa83394df4ac057))
16
+
1
17
  # [1.0.0-canary.13](https://github.com/seyuna-corp/seyuna-postcss/compare/v1.0.0-canary.12...v1.0.0-canary.13) (2025-10-07)
2
18
 
3
19
 
package/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # @seyuna/postcss
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/@seyuna/postcss.svg)](https://www.npmjs.com/package/@seyuna/postcss)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ > The official PostCSS plugin for the Seyuna UI framework.
7
+
8
+ ---
9
+
10
+ ## 🚀 Overview
11
+
12
+ `@seyuna/postcss` is a powerful PostCSS plugin that brings the Seyuna design system to your CSS. It leverages the modern **OKLCH** color space, CSS Container Queries, and configuration-driven styling to provide a premium developer experience.
13
+
14
+ ---
15
+
16
+ ## 📦 Installation
17
+
18
+ ```bash
19
+ npm install @seyuna/postcss postcss-import postcss-advanced-variables postcss-preset-env --save-dev
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ Add the plugin to your `postcss.config.js` (ESM):
25
+
26
+ ```javascript
27
+ import seyunaPostcss from '@seyuna/postcss';
28
+ import postcssImport from 'postcss-import';
29
+ import postcssAdvancedVariables from 'postcss-advanced-variables';
30
+ import postcssPresetEnv from 'postcss-preset-env';
31
+
32
+ export default {
33
+ plugins: [
34
+ seyunaPostcss({
35
+ configPath: 'seyuna.json', // Path to your Seyuna config
36
+ modeAttribute: 'data-mode', // Attribute used for theme switching
37
+ }),
38
+ postcssImport,
39
+ postcssAdvancedVariables,
40
+ postcssPresetEnv({
41
+ stage: 3, // Enables features that are in the CSS spec pipeline
42
+ features: {
43
+ "nesting-rules": true, // Uses postcss-nesting internally
44
+ },
45
+ }),
46
+ ],
47
+ };
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 🎨 The Color System
53
+
54
+ Seyuna distinguishes between two types of colors to maximize flexibility and maintain consistency.
55
+
56
+ ### 1. Standard Colors (`sc`)
57
+ Standard colors are "hue-only" tokens. They automatically inherit the **lightness** and **chroma** variables assigned to the current mode (light or dark).
58
+
59
+ - **Usage**: `sc(primary)`
60
+ - **Compiled**: `oklch(var(--lightness) var(--chroma) var(--primary-hue) / 1)`
61
+
62
+ ### 2. Fixed Colors (`fc`)
63
+ Fixed colors ignore the global lightness/chroma context and use their own pre-defined values.
64
+
65
+ - **Usage**: `fc(white)`
66
+ - **Compiled**: `oklch(var(--white-lightness) var(--white-chroma) var(--white-hue) / 1)`
67
+
68
+ ---
69
+
70
+ ## 🛠️ Custom Functions
71
+
72
+ ### Color Manipulation
73
+ All color functions support both raw `oklch()` strings and color names from your `seyuna.json`.
74
+
75
+ | Function | Description | Example |
76
+ | :--- | :--- | :--- |
77
+ | `alpha(color, value)` | Sets the opacity of a color. | `alpha(primary, 0.5)` |
78
+ | `lighten(color, amt)` | Increases lightness. | `lighten(primary, 0.1)` |
79
+ | `darken(color, amt)` | Decreases lightness. | `darken(primary, 0.1)` |
80
+ | `contrast(color)` | Returns `black` or `white`. | `color: contrast(primary)` |
81
+
82
+ ### Utilities
83
+ - **`theme(path)`**: Access any value in `seyuna.json` using dot notation.
84
+ ```css
85
+ padding: theme(ui.spacing.medium);
86
+ ```
87
+
88
+ ---
89
+
90
+ ## 🏗️ Powerful At-Rules
91
+
92
+ ### Palette Iterators
93
+ Generate utility classes by iterating over your configuration.
94
+
95
+ ```css
96
+ @each-standard-color {
97
+ .bg-{name} { background-color: sc({name}); }
98
+ }
99
+
100
+ @each-fixed-color {
101
+ .text-{name} { color: fc({name}); }
102
+ }
103
+ ```
104
+
105
+ ### Theme Wrappers
106
+ Simplified syntax for light and dark mode overrides.
107
+
108
+ ```css
109
+ .card {
110
+ background: white;
111
+ @dark {
112
+ background: black;
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Breakpoint Shortcuts
118
+ These generate modern CSS Container Queries. Ensure your parent element has `container-type: inline-size`.
119
+
120
+ ```css
121
+ .grid {
122
+ grid-template-columns: 1fr;
123
+ @md {
124
+ grid-template-columns: 1fr 1fr;
125
+ }
126
+ }
127
+ ```
128
+ *Supported: `@xs`, `@sm`, `@md`, `@lg`, `@xl`, `@xxl`.*
129
+
130
+ ### Mixins & Conditionals
131
+ ```css
132
+ @define-mixin flex-center {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ }
137
+
138
+ .button {
139
+ @apply flex-center;
140
+
141
+ @if (theme(ui.features.glass)) {
142
+ backdrop-filter: blur(10px);
143
+ }
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## ⚙️ Configuration (`seyuna.json`)
150
+
151
+ ```json
152
+ {
153
+ "ui": {
154
+ "theme": {
155
+ "hues": {
156
+ "primary": 240,
157
+ "secondary": 30
158
+ },
159
+ "colors": {
160
+ "white": { "lightness": 1, "chroma": 0, "hue": 0 }
161
+ },
162
+ "light": {
163
+ "chroma": 0.26,
164
+ "lightness": 0.66
165
+ },
166
+ "dark": {
167
+ "chroma": 0.26,
168
+ "lightness": 0.66
169
+ }
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## 📜 License
178
+
179
+ MIT © [Seyuna](https://seyuna.com)
@@ -0,0 +1,4 @@
1
+ import { AtRule } from "postcss";
2
+ import { PluginContext } from "../config";
3
+ export declare const light: (atRule: AtRule, context: PluginContext) => void;
4
+ export declare const dark: (atRule: AtRule, context: PluginContext) => void;
@@ -0,0 +1,43 @@
1
+ import { Rule, AtRule } from "postcss";
2
+ /**
3
+ * Custom PostCSS plugin handler factory for `@light` and `@dark` at-rules.
4
+ */
5
+ function createColorSchemeHandler(scheme) {
6
+ return (atRule, context) => {
7
+ const { options } = context;
8
+ const modeAttribute = options.modeAttribute;
9
+ const clonedNodes = [];
10
+ // Clone all child nodes inside the block
11
+ atRule.each((node) => {
12
+ clonedNodes.push(node.clone());
13
+ });
14
+ /**
15
+ * Rule 1: [data-mode="scheme"] & { ... } (Explicit mode)
16
+ */
17
+ const explicitRule = new Rule({
18
+ selector: `[${modeAttribute}="${scheme}"] &`,
19
+ source: atRule.source,
20
+ });
21
+ clonedNodes.forEach((node) => explicitRule.append(node.clone()));
22
+ /**
23
+ * Rule 2: @media (prefers-color-scheme: scheme) { [data-mode="system"] & { ... } } (System preference)
24
+ */
25
+ const mediaAtRule = new AtRule({
26
+ name: "media",
27
+ params: `(prefers-color-scheme: ${scheme})`,
28
+ source: atRule.source,
29
+ });
30
+ const systemRule = new Rule({
31
+ selector: `[${modeAttribute}="system"] &`,
32
+ source: atRule.source,
33
+ });
34
+ clonedNodes.forEach((node) => systemRule.append(node.clone()));
35
+ mediaAtRule.append(systemRule);
36
+ /**
37
+ * Replace the original at-rule with the generated rules.
38
+ */
39
+ atRule.replaceWith(mediaAtRule, explicitRule);
40
+ };
41
+ }
42
+ export const light = createColorSchemeHandler('light');
43
+ export const dark = createColorSchemeHandler('dark');
@@ -1,9 +1,10 @@
1
1
  import { AtRule } from "postcss";
2
+ import { PluginContext } from "../config";
2
3
  /**
3
- * Custom PostCSS handler for @each-standard-color
4
+ * Handler for @each-standard-color
4
5
  */
5
- export declare function eachStandardColor(atRule: AtRule): void;
6
+ export declare function eachStandardColor(atRule: AtRule, context: PluginContext): void;
6
7
  /**
7
- * Custom PostCSS handler for @each-fixed-color
8
+ * Handler for @each-fixed-color
8
9
  */
9
- export declare function eachFixedColor(atRule: AtRule): void;
10
+ export declare function eachFixedColor(atRule: AtRule, context: PluginContext): void;
@@ -1,95 +1,30 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.eachStandardColor = eachStandardColor;
7
- exports.eachFixedColor = eachFixedColor;
8
- const postcss_1 = require("postcss");
9
- const fs_1 = __importDefault(require("fs"));
10
- const path_1 = __importDefault(require("path"));
11
- const color_1 = require("../functions/color");
1
+ import { generateRules } from "../helpers";
12
2
  /**
13
- * Helper: safely clone nodes and replace {name} placeholders,
14
- * evaluate fc() and sc() calls.
3
+ * Handler for @each-standard-color
15
4
  */
16
- function cloneNodesWithName(nodeList, name) {
17
- return nodeList.flatMap((node) => {
18
- const cloned = node.clone();
19
- if (cloned.type === "decl") {
20
- const decl = cloned;
21
- let value = decl.value.replace(/\{name\}/g, name);
22
- if (/sc\(/.test(value)) {
23
- const args = value
24
- .match(/sc\(([^)]*)\)/)?.[1]
25
- .split(",")
26
- .map((s) => s.trim());
27
- if (args)
28
- value = (0, color_1.sc)(...args);
29
- }
30
- if (/fc\(/.test(value)) {
31
- const args = value
32
- .match(/fc\(([^)]*)\)/)?.[1]
33
- .split(",")
34
- .map((s) => s.trim());
35
- if (args)
36
- value = (0, color_1.fc)(...args);
37
- }
38
- decl.value = value;
39
- return decl;
40
- }
41
- if (cloned.type === "rule") {
42
- const rule = cloned;
43
- if (!rule.selector)
44
- return [];
45
- rule.selector = rule.selector.replace(/\{name\}/g, name);
46
- rule.nodes = cloneNodesWithName(rule.nodes || [], name);
47
- return rule;
48
- }
49
- if (cloned.type === "atrule") {
50
- cloned.params = cloned.params.replace(/\{name\}/g, name);
51
- cloned.nodes = cloneNodesWithName(cloned.nodes || [], name);
52
- return cloned.nodes.length ? cloned : [];
53
- }
54
- return [];
55
- });
5
+ export function eachStandardColor(atRule, context) {
6
+ const { config } = context;
7
+ const hueNames = config.ui ? Object.keys(config.ui.theme.hues) : [];
8
+ const rules = generateRules(hueNames, atRule, context);
9
+ if (rules.length)
10
+ atRule.replaceWith(...rules);
11
+ else
12
+ atRule.remove();
56
13
  }
57
14
  /**
58
- * Custom PostCSS handler for @each-standard-color
15
+ * Handler for @each-fixed-color
59
16
  */
60
- function eachStandardColor(atRule) {
61
- const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
62
- const data = JSON.parse(fs_1.default.readFileSync(jsonPath, "utf-8"));
63
- const hues = data.ui.theme.hues;
64
- const hueNames = Object.keys(hues);
65
- const nodes = atRule.nodes ?? [];
66
- const generatedRules = [];
67
- for (const hueName of hueNames) {
68
- const rule = new postcss_1.Rule({ selector: `.${hueName}` });
69
- cloneNodesWithName(nodes, hueName).forEach((n) => rule.append(n));
70
- if (rule.nodes.length)
71
- generatedRules.push(rule);
72
- }
73
- atRule.replaceWith(...generatedRules);
74
- }
75
- /**
76
- * Custom PostCSS handler for @each-fixed-color
77
- */
78
- function eachFixedColor(atRule) {
79
- const jsonPath = path_1.default.resolve(process.cwd(), "seyuna.json");
80
- const data = JSON.parse(fs_1.default.readFileSync(jsonPath, "utf-8"));
81
- const light_colors = data.ui.theme.light.colors;
82
- const dark_colors = data.ui.theme.dark.colors;
17
+ export function eachFixedColor(atRule, context) {
18
+ const { config } = context;
83
19
  const mergedNames = [
84
- ...new Set([...Object.keys(light_colors), ...Object.keys(dark_colors)]),
20
+ ...new Set([
21
+ ...(config.ui ? Object.keys(config.ui.theme.light.colors) : []),
22
+ ...(config.ui ? Object.keys(config.ui.theme.dark.colors) : []),
23
+ ]),
85
24
  ];
86
- const nodes = atRule.nodes ?? [];
87
- const generatedRules = [];
88
- for (const colorName of mergedNames) {
89
- const rule = new postcss_1.Rule({ selector: `.${colorName}` });
90
- cloneNodesWithName(nodes, colorName).forEach((n) => rule.append(n));
91
- if (rule.nodes.length)
92
- generatedRules.push(rule);
93
- }
94
- atRule.replaceWith(...generatedRules);
25
+ const rules = generateRules(mergedNames, atRule, context);
26
+ if (rules.length)
27
+ atRule.replaceWith(...rules);
28
+ else
29
+ atRule.remove();
95
30
  }
@@ -0,0 +1,6 @@
1
+ import { AtRule } from "postcss";
2
+ import { PluginContext } from "../config";
3
+ /**
4
+ * Handler for @if (condition) { ... }
5
+ */
6
+ export declare function conditional(atRule: AtRule, context: PluginContext): void;
@@ -0,0 +1,29 @@
1
+ import { processFunctions } from "../parser";
2
+ /**
3
+ * Handler for @if (condition) { ... }
4
+ */
5
+ export function conditional(atRule, context) {
6
+ const { functions: fnMap } = context;
7
+ // Clean up params (remove parentheses if present)
8
+ let condition = atRule.params.trim().replace(/^\(|\)$/g, '').trim();
9
+ // Process functions in the condition (e.g., theme access)
10
+ const evaluated = processFunctions(condition, fnMap, atRule, context).trim();
11
+ // Simple truthy evaluation
12
+ const isFalsy = !evaluated ||
13
+ evaluated === 'false' ||
14
+ evaluated === '0' ||
15
+ evaluated === 'null' ||
16
+ evaluated === 'undefined';
17
+ if (!isFalsy) {
18
+ // Ungroup the nodes
19
+ if (atRule.nodes && atRule.nodes.length > 0) {
20
+ atRule.replaceWith(...atRule.nodes.map(n => n.clone()));
21
+ }
22
+ else {
23
+ atRule.remove();
24
+ }
25
+ }
26
+ else {
27
+ atRule.remove();
28
+ }
29
+ }
@@ -1,4 +1,5 @@
1
1
  import { AtRule } from "postcss";
2
+ import { PluginContext } from "../config";
2
3
  /**
3
4
  * Custom PostCSS plugin handler for responsive at-rules.
4
5
  *
@@ -15,4 +16,4 @@ import { AtRule } from "postcss";
15
16
  * }
16
17
  *
17
18
  */
18
- export default function container(atRule: AtRule): void;
19
+ export default function container(atRule: AtRule, context: PluginContext): void;
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = container;
4
- const postcss_1 = require("postcss");
1
+ import { AtRule } from "postcss";
5
2
  /**
6
3
  * Custom PostCSS plugin handler for responsive at-rules.
7
4
  *
@@ -18,9 +15,10 @@ const postcss_1 = require("postcss");
18
15
  * }
19
16
  *
20
17
  */
21
- function container(atRule) {
22
- // Map of shortcuts container widths
23
- const breakpoints = {
18
+ export default function container(atRule, context) {
19
+ const { config } = context;
20
+ // Default breakpoints
21
+ const defaultBreakpoints = {
24
22
  xs: "20rem",
25
23
  sm: "40rem",
26
24
  md: "48rem",
@@ -28,15 +26,21 @@ function container(atRule) {
28
26
  xl: "80rem",
29
27
  "2xl": "96rem",
30
28
  };
29
+ // Merge with config if available (assuming config.ui.breakpoints exists)
30
+ const breakpoints = {
31
+ ...defaultBreakpoints,
32
+ ...(config.ui?.theme?.breakpoints || {}),
33
+ };
31
34
  if (Object.keys(breakpoints).includes(atRule.name)) {
32
35
  const minWidth = breakpoints[atRule.name];
33
36
  const clonedNodes = [];
34
37
  atRule.each((node) => {
35
38
  clonedNodes.push(node.clone());
36
39
  });
37
- const containerAtRule = new postcss_1.AtRule({
40
+ const containerAtRule = new AtRule({
38
41
  name: "container",
39
42
  params: `(min-width: ${minWidth})`,
43
+ source: atRule.source,
40
44
  });
41
45
  clonedNodes.forEach((node) => containerAtRule.append(node));
42
46
  atRule.replaceWith(containerAtRule);
@@ -0,0 +1,15 @@
1
+ import { AtRule } from "postcss";
2
+ import { PluginContext } from "../config";
3
+ /**
4
+ * Global store for CSS-defined custom media (if needed)
5
+ * However, we primarily use the config.
6
+ */
7
+ /**
8
+ * Handler for @media at-rules to resolve custom media tokens
9
+ */
10
+ export declare function resolveCustomMedia(atRule: AtRule, context: PluginContext): void;
11
+ /**
12
+ * [Future-proofing] Handler for @custom-media --name query
13
+ * Adds to the config-like store for the current run
14
+ */
15
+ export declare function defineCustomMedia(atRule: AtRule, context: PluginContext): void;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Global store for CSS-defined custom media (if needed)
3
+ * However, we primarily use the config.
4
+ */
5
+ /**
6
+ * Handler for @media at-rules to resolve custom media tokens
7
+ */
8
+ export function resolveCustomMedia(atRule, context) {
9
+ const { config } = context;
10
+ const mediaTokens = config.media || {};
11
+ // Regex to find --tokens anywhere
12
+ let params = atRule.params;
13
+ const tokenRegex = /--[a-zA-Z0-9\-_]+/g;
14
+ params = params.replace(tokenRegex, (match) => {
15
+ const tokenName = match.slice(2);
16
+ if (mediaTokens[tokenName]) {
17
+ return mediaTokens[tokenName];
18
+ }
19
+ return match;
20
+ });
21
+ // Clean up double parentheses like ((...)) if any
22
+ params = params.replace(/\(\((.+)\)\)/g, '($1)');
23
+ atRule.params = params;
24
+ }
25
+ /**
26
+ * [Future-proofing] Handler for @custom-media --name query
27
+ * Adds to the config-like store for the current run
28
+ */
29
+ export function defineCustomMedia(atRule, context) {
30
+ const match = atRule.params.match(/^(--[a-zA-Z0-9\-_]+)\s+(.+)$/);
31
+ if (match) {
32
+ const name = match[1].slice(2);
33
+ const query = match[2];
34
+ if (!context.config.media) {
35
+ context.config.media = {};
36
+ }
37
+ context.config.media[name] = query;
38
+ }
39
+ atRule.remove();
40
+ }
@@ -1,6 +1,7 @@
1
- import type { AtRule } from "postcss";
1
+ import { AtRule } from "postcss";
2
+ import { PluginContext } from "../config";
2
3
  export interface AtRuleHandler {
3
4
  name: string;
4
- handler: (atRule: AtRule) => void;
5
+ handler: (atRule: AtRule, context: PluginContext) => void;
5
6
  }
6
7
  export declare const atRuleHandlers: AtRuleHandler[];
@@ -1,25 +1,25 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.atRuleHandlers = void 0;
7
- // atRuleHandlers.ts
8
- const dark_1 = __importDefault(require("./dark"));
9
- const light_1 = __importDefault(require("./light"));
10
- const container_1 = __importDefault(require("./container"));
11
- const color_1 = require("./color");
1
+ import { eachStandardColor, eachFixedColor } from "./color";
2
+ import container from "./container";
3
+ import { light, dark } from "./color-scheme";
4
+ import { defineMixin, applyMixin } from "./mixin";
5
+ import { conditional } from "./conditional";
6
+ import { resolveCustomMedia, defineCustomMedia } from "./custom-media";
12
7
  // Ordered array ensures execution order
13
- exports.atRuleHandlers = [
14
- { name: "each-standard-color", handler: color_1.eachStandardColor }, // first
15
- { name: "each-fixed-color", handler: color_1.eachFixedColor },
16
- { name: "light", handler: light_1.default },
17
- { name: "dark", handler: dark_1.default },
18
- { name: "xs", handler: container_1.default },
19
- { name: "sm", handler: container_1.default },
20
- { name: "md", handler: container_1.default },
21
- { name: "lg", handler: container_1.default },
22
- { name: "xl", handler: container_1.default },
23
- { name: "2xl", handler: container_1.default },
8
+ export const atRuleHandlers = [
9
+ { name: "define-mixin", handler: defineMixin },
10
+ { name: "custom-media", handler: defineCustomMedia },
11
+ { name: "apply", handler: applyMixin },
12
+ { name: "if", handler: conditional },
13
+ { name: "media", handler: resolveCustomMedia },
14
+ { name: "each-standard-color", handler: eachStandardColor }, // first
15
+ { name: "each-fixed-color", handler: eachFixedColor },
16
+ { name: "light", handler: light },
17
+ { name: "dark", handler: dark },
18
+ { name: "xs", handler: container },
19
+ { name: "sm", handler: container },
20
+ { name: "md", handler: container },
21
+ { name: "lg", handler: container },
22
+ { name: "xl", handler: container },
23
+ { name: "2xl", handler: container },
24
24
  // add more handlers here as needed
25
25
  ];
@@ -0,0 +1,10 @@
1
+ import { AtRule } from "postcss";
2
+ import { PluginContext } from "../config";
3
+ /**
4
+ * Handler for @define-mixin [name] { ... }
5
+ */
6
+ export declare function defineMixin(atRule: AtRule, context: PluginContext): void;
7
+ /**
8
+ * Handler for @apply [name]
9
+ */
10
+ export declare function applyMixin(atRule: AtRule, context: PluginContext): void;
@@ -0,0 +1,37 @@
1
+ import { reportError } from "../errors";
2
+ /**
3
+ * Handler for @define-mixin [name] { ... }
4
+ */
5
+ export function defineMixin(atRule, context) {
6
+ const name = atRule.params.trim();
7
+ if (!name) {
8
+ reportError("Mixin name is required", atRule, context);
9
+ return;
10
+ }
11
+ // Store the nodes (cloned)
12
+ context.mixins[name] = atRule.nodes?.map(n => n.clone()) || [];
13
+ // Remove the at-rule from the output
14
+ atRule.remove();
15
+ }
16
+ /**
17
+ * Handler for @apply [name]
18
+ */
19
+ export function applyMixin(atRule, context) {
20
+ const name = atRule.params.trim();
21
+ if (!name) {
22
+ reportError("Mixin name is required for @apply", atRule, context);
23
+ return;
24
+ }
25
+ const mixinNodes = context.mixins[name];
26
+ if (!mixinNodes) {
27
+ reportError(`Mixin "${name}" not found`, atRule, context);
28
+ return;
29
+ }
30
+ // Inject the nodes, ensuring they have the correct source for mapping
31
+ const nodesToInject = mixinNodes.map(n => {
32
+ const cloned = n.clone();
33
+ cloned.source = atRule.source;
34
+ return cloned;
35
+ });
36
+ atRule.replaceWith(...nodesToInject);
37
+ }