@udixio/tailwind 1.6.0 → 1.7.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 1.7.0 (2025-08-31)
2
+
3
+ ### 🚀 Features
4
+
5
+ - **tailwind:** enhance dynamic theming with flexible CSS selectors ([64bef44](https://github.com/Udixio/UI/commit/64bef44))
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - **tailwind:** correct indentation in dynamic CSS generation and adjust imports ([701599f](https://github.com/Udixio/UI/commit/701599f))
10
+
11
+ ### ❤️ Thank You
12
+
13
+ - Joël VIGREUX
14
+
1
15
  ## 1.6.0 (2025-08-29)
2
16
 
3
17
  ### 🚀 Features
@@ -1,7 +1,11 @@
1
1
  import { FontPlugin, PluginAbstract, PluginImplAbstract } from '@udixio/theme';
2
2
  export interface TailwindPluginOptions {
3
+ darkMode?: 'class' | 'media';
4
+ dynamicSelector?: string;
5
+ darkSelector?: string;
3
6
  responsiveBreakPoints?: Record<string, number>;
4
7
  styleFilePath?: string;
8
+ subThemes?: Record<string, string>;
5
9
  }
6
10
  export declare class TailwindPlugin extends PluginAbstract<TailwindImplPluginBrowser, TailwindPluginOptions> {
7
11
  dependencies: (typeof FontPlugin)[];
@@ -10,12 +14,14 @@ export declare class TailwindPlugin extends PluginAbstract<TailwindImplPluginBro
10
14
  }
11
15
  export declare class TailwindImplPluginBrowser extends PluginImplAbstract<TailwindPluginOptions> {
12
16
  outputCss: string;
13
- protected colors: Record<string, {
17
+ onInit(): void;
18
+ loadColor({ isDynamic }: {
19
+ isDynamic: boolean;
20
+ }): void;
21
+ getColors(): Record<string, {
14
22
  light: string;
15
23
  dark: string;
16
24
  }>;
17
- onInit(): void;
18
- loadColor(): void;
19
25
  onLoad(): Promise<void>;
20
26
  }
21
27
  //# sourceMappingURL=tailwind.plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind.plugin.d.ts","sourceRoot":"","sources":["../../src/browser/tailwind.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE/E,MAAM,WAAW,qBAAqB;IAEpC,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;CAExB;AAED,qBAAa,cAAe,SAAQ,cAAc,CAChD,yBAAyB,EACzB,qBAAqB,CACtB;IACQ,YAAY,wBAAgB;IAC5B,IAAI,SAAc;IACzB,WAAW,mCAA6B;CACzC;AAED,qBAAa,yBAA0B,SAAQ,kBAAkB,CAAC,qBAAqB,CAAC;IAC/E,SAAS,SAAM;IACtB,SAAS,CAAC,MAAM,EAAE,MAAM,CACtB,MAAM,EACN;QACE,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CACF,CAAM;IAEP,MAAM;IAMN,SAAS;IAqBH,MAAM;CAsBb"}
1
+ {"version":3,"file":"tailwind.plugin.d.ts","sourceRoot":"","sources":["../../src/browser/tailwind.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE/E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAgFD,qBAAa,cAAe,SAAQ,cAAc,CAChD,yBAAyB,EACzB,qBAAqB,CACtB;IACQ,YAAY,wBAAgB;IAC5B,IAAI,SAAc;IACzB,WAAW,mCAA6B;CACzC;AAED,qBAAa,yBAA0B,SAAQ,kBAAkB,CAAC,qBAAqB,CAAC;IAC/E,SAAS,SAAM;IAEtB,MAAM;IAeN,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE;IAsE/C,SAAS;eAII,MAAM;cACP,MAAM;;IAiBZ,MAAM;CAUb"}
package/dist/browser.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const tailwind_plugin = require("./tailwind.plugin-DYs0pbWt.cjs");
3
+ const tailwind_plugin = require("./tailwind.plugin-CUI_jxzw.cjs");
4
4
  exports.TailwindImplPluginBrowser = tailwind_plugin.TailwindImplPluginBrowser;
5
5
  exports.TailwindPlugin = tailwind_plugin.TailwindPlugin;
6
6
  exports.default = tailwind_plugin.main;
package/dist/browser.js CHANGED
@@ -1,5 +1,5 @@
1
- import { m as main } from "./tailwind.plugin-DJ3FRnT8.js";
2
- import { T, a, f, s } from "./tailwind.plugin-DJ3FRnT8.js";
1
+ import { m as main } from "./tailwind.plugin-q0sIRBpo.js";
2
+ import { T, a, f, s } from "./tailwind.plugin-q0sIRBpo.js";
3
3
  export {
4
4
  T as TailwindImplPluginBrowser,
5
5
  a as TailwindPlugin,
@@ -7,7 +7,6 @@ export declare class TailwindPlugin extends PluginAbstract<TailwindImplPlugin, T
7
7
  }
8
8
  declare class TailwindImplPlugin extends TailwindImplPluginBrowser {
9
9
  private isNodeJs;
10
- loadColor(): void;
11
10
  onLoad(): Promise<void>;
12
11
  }
13
12
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind.plugin.d.ts","sourceRoot":"","sources":["../../src/node/tailwind.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAI9F,qBAAa,cAAe,SAAQ,cAAc,CAChD,kBAAkB,EAClB,qBAAqB,CACtB;IACQ,YAAY,wBAAgB;IAC5B,IAAI,SAAc;IACzB,WAAW,4BAAsB;CAClC;AAED,cAAM,kBAAmB,SAAQ,yBAAyB;IACxD,OAAO,CAAC,QAAQ;IAQP,SAAS;IAuBH,MAAM;CA2FtB"}
1
+ {"version":3,"file":"tailwind.plugin.d.ts","sourceRoot":"","sources":["../../src/node/tailwind.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACtB,MAAM,4BAA4B,CAAC;AAIpC,qBAAa,cAAe,SAAQ,cAAc,CAChD,kBAAkB,EAClB,qBAAqB,CACtB;IACQ,YAAY,wBAAgB;IAC5B,IAAI,SAAc;IACzB,WAAW,4BAAsB;CAClC;AAED,cAAM,kBAAmB,SAAQ,yBAAyB;IACxD,OAAO,CAAC,QAAQ;IAQD,MAAM;CAkFtB"}
package/dist/node.cjs CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
26
26
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
27
- const tailwind_plugin = require("./tailwind.plugin-DYs0pbWt.cjs");
27
+ const tailwind_plugin = require("./tailwind.plugin-CUI_jxzw.cjs");
28
28
  const theme = require("@udixio/theme");
29
29
  const fs = require("fs");
30
30
  const console = require("node:console");
@@ -60,26 +60,7 @@ class TailwindImplPlugin extends tailwind_plugin.TailwindImplPluginBrowser {
60
60
  isNodeJs() {
61
61
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
62
62
  }
63
- loadColor() {
64
- if (!this.isNodeJs()) {
65
- super.loadColor();
66
- return;
67
- }
68
- this.outputCss += `
69
- @custom-variant dark (&:where(.dark, .dark *));
70
- @theme {
71
- --color-*: initial;
72
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
73
- }
74
- @layer theme {
75
- .dark {
76
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.dark};`).join("\n ")}
77
- }
78
- }
79
- `;
80
- }
81
63
  async onLoad() {
82
- var _a;
83
64
  if (!this.isNodeJs()) {
84
65
  await super.onLoad();
85
66
  return;
@@ -92,15 +73,7 @@ class TailwindImplPlugin extends tailwind_plugin.TailwindImplPluginBrowser {
92
73
  getFileContent: getFileContent2,
93
74
  replaceFileContent: replaceFileContent2
94
75
  } = await Promise.resolve().then(() => file);
95
- this.colors = {};
96
- for (const isDark of [false, true]) {
97
- this.api.themes.update({ isDark });
98
- for (const [key, value] of this.api.colors.getColors().entries()) {
99
- const newKey = key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
100
- (_a = this.colors)[newKey] ?? (_a[newKey] = { light: "", dark: "" });
101
- this.colors[newKey][isDark ? "dark" : "light"] = value.getHex();
102
- }
103
- }
76
+ const colors = this.getColors();
104
77
  let udixioCssPath = this.options.styleFilePath;
105
78
  const projectRoot = await findProjectRoot2(resolve());
106
79
  if (!udixioCssPath) {
@@ -118,7 +91,7 @@ class TailwindImplPlugin extends tailwind_plugin.TailwindImplPluginBrowser {
118
91
  }
119
92
  const { fontStyles, fontFamily } = this.api.plugins.getPlugin(theme.FontPlugin).getInstance().getFonts();
120
93
  const configCss = {
121
- colorKeys: Object.keys(this.colors).join(", "),
94
+ colorKeys: Object.keys(colors).join(", "),
122
95
  fontStyles: Object.entries(fontStyles).map(
123
96
  ([fontRole, fontStyle]) => Object.entries(fontStyle).map(
124
97
  ([fontSize, fontStyle2]) => `${fontRole}-${fontSize} ${Object.entries(fontStyle2).map(([name, value]) => `${name}[${value}]`).join(" ")}`
@@ -133,7 +106,7 @@ class TailwindImplPlugin extends tailwind_plugin.TailwindImplPluginBrowser {
133
106
  fontStyles: ${configCss.fontStyles};
134
107
  responsiveBreakPoints: ${configCss.responsiveBreakPoints};
135
108
  }`;
136
- this.loadColor();
109
+ this.loadColor({ isDynamic: false });
137
110
  this.outputCss += `
138
111
  @theme {
139
112
  ${Object.entries(fontFamily).map(
package/dist/node.js CHANGED
@@ -1,8 +1,8 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { T as TailwindImplPluginBrowser, m as main } from "./tailwind.plugin-DJ3FRnT8.js";
5
- import { f, s } from "./tailwind.plugin-DJ3FRnT8.js";
4
+ import { T as TailwindImplPluginBrowser, m as main } from "./tailwind.plugin-q0sIRBpo.js";
5
+ import { f, s } from "./tailwind.plugin-q0sIRBpo.js";
6
6
  import { PluginAbstract, FontPlugin } from "@udixio/theme";
7
7
  import * as fs from "fs";
8
8
  import * as console from "node:console";
@@ -20,26 +20,7 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
20
20
  isNodeJs() {
21
21
  return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
22
22
  }
23
- loadColor() {
24
- if (!this.isNodeJs()) {
25
- super.loadColor();
26
- return;
27
- }
28
- this.outputCss += `
29
- @custom-variant dark (&:where(.dark, .dark *));
30
- @theme {
31
- --color-*: initial;
32
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
33
- }
34
- @layer theme {
35
- .dark {
36
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.dark};`).join("\n ")}
37
- }
38
- }
39
- `;
40
- }
41
23
  async onLoad() {
42
- var _a;
43
24
  if (!this.isNodeJs()) {
44
25
  await super.onLoad();
45
26
  return;
@@ -52,15 +33,7 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
52
33
  getFileContent: getFileContent2,
53
34
  replaceFileContent: replaceFileContent2
54
35
  } = await Promise.resolve().then(() => file);
55
- this.colors = {};
56
- for (const isDark of [false, true]) {
57
- this.api.themes.update({ isDark });
58
- for (const [key, value] of this.api.colors.getColors().entries()) {
59
- const newKey = key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
60
- (_a = this.colors)[newKey] ?? (_a[newKey] = { light: "", dark: "" });
61
- this.colors[newKey][isDark ? "dark" : "light"] = value.getHex();
62
- }
63
- }
36
+ const colors = this.getColors();
64
37
  let udixioCssPath = this.options.styleFilePath;
65
38
  const projectRoot = await findProjectRoot2(resolve2());
66
39
  if (!udixioCssPath) {
@@ -78,7 +51,7 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
78
51
  }
79
52
  const { fontStyles, fontFamily } = this.api.plugins.getPlugin(FontPlugin).getInstance().getFonts();
80
53
  const configCss = {
81
- colorKeys: Object.keys(this.colors).join(", "),
54
+ colorKeys: Object.keys(colors).join(", "),
82
55
  fontStyles: Object.entries(fontStyles).map(
83
56
  ([fontRole, fontStyle]) => Object.entries(fontStyle).map(
84
57
  ([fontSize, fontStyle2]) => `${fontRole}-${fontSize} ${Object.entries(fontStyle2).map(([name, value]) => `${name}[${value}]`).join(" ")}`
@@ -93,7 +66,7 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
93
66
  fontStyles: ${configCss.fontStyles};
94
67
  responsiveBreakPoints: ${configCss.responsiveBreakPoints};
95
68
  }`;
96
- this.loadColor();
69
+ this.loadColor({ isDynamic: false });
97
70
  this.outputCss += `
98
71
  @theme {
99
72
  ${Object.entries(fontFamily).map(
@@ -162,6 +162,63 @@ const main = plugin.withOptions((args) => {
162
162
  shadow.handler(api);
163
163
  };
164
164
  });
165
+ function createFlexibleSelector(...classes) {
166
+ classes = classes.filter((classeName) => !!classeName);
167
+ if (classes.length === 0) return "";
168
+ if (classes.length === 1 && classes[0]) return classes[0];
169
+ const selectors = [];
170
+ selectors.push(classes.join(""));
171
+ for (let i = 0; i < classes.length; i++) {
172
+ const ancestor = classes[i];
173
+ const descendants = classes.filter(
174
+ (className, index) => index !== i && !!className
175
+ );
176
+ if (descendants.length === 1) {
177
+ selectors.push(`${ancestor} ${descendants[0]}`);
178
+ } else if (descendants.length > 1) {
179
+ selectors.push(`${ancestor} ${descendants.join("")}`);
180
+ for (const desc of descendants) {
181
+ selectors.push(`${ancestor} ${desc}`);
182
+ }
183
+ }
184
+ }
185
+ for (let i = 0; i < classes.length; i++) {
186
+ for (let j = i + 1; j < classes.length; j++) {
187
+ selectors.push(`${classes[i]} ${classes[j]}`);
188
+ selectors.push(`${classes[j]} ${classes[i]}`);
189
+ }
190
+ }
191
+ const uniqueSelectors = [...new Set(selectors)];
192
+ return `:is(${uniqueSelectors.join(", ")})`;
193
+ }
194
+ function darkStyle({
195
+ selectors,
196
+ mode,
197
+ darkSelector,
198
+ styles
199
+ }) {
200
+ selectors = selectors.filter((classeName) => !!classeName);
201
+ if (mode === "media") {
202
+ if (selectors.length !== 0) {
203
+ return `@media (prefers-color-scheme: dark) {
204
+ ${createFlexibleSelector(...selectors)} {
205
+ ${styles}
206
+ }
207
+ }
208
+ `;
209
+ } else {
210
+ return `@media (prefers-color-scheme: dark) {
211
+ ${styles}
212
+ }
213
+ `;
214
+ }
215
+ } else {
216
+ return `${createFlexibleSelector(...selectors, darkSelector)} {
217
+ ${styles}
218
+ }
219
+ `;
220
+ }
221
+ }
165
222
  class TailwindPlugin extends theme.PluginAbstract {
166
223
  constructor() {
167
224
  super(...arguments);
@@ -174,47 +231,95 @@ class TailwindImplPluginBrowser extends theme.PluginImplAbstract {
174
231
  constructor() {
175
232
  super(...arguments);
176
233
  __publicField(this, "outputCss", "");
177
- __publicField(this, "colors", {});
178
234
  }
179
235
  onInit() {
180
236
  var _a;
181
237
  (_a = this.options).responsiveBreakPoints ?? (_a.responsiveBreakPoints = {
182
238
  lg: 1.125
183
239
  });
240
+ this.options = {
241
+ responsiveBreakPoints: {
242
+ lg: 1.125
243
+ },
244
+ darkMode: "class",
245
+ darkSelector: ".dark",
246
+ dynamicSelector: ".dynamic",
247
+ ...this.options
248
+ };
184
249
  }
185
- loadColor() {
186
- this.outputCss += `
187
- @variant dynamic-default (&:where(.dynamic, .dynamic *));
188
- @variant dynamic-dark (&:where(.dynamic:where(.dark, .dark *), .dark:where(.dynamic, .dynamic *)));
250
+ loadColor({ isDynamic }) {
251
+ let { dynamicSelector, darkSelector } = this.options;
252
+ if (!isDynamic) {
253
+ dynamicSelector = void 0;
254
+ }
255
+ const darkMode = this.options.darkMode ?? "class";
256
+ if (darkMode == "media") {
257
+ darkSelector = void 0;
258
+ }
259
+ const colors = this.getColors();
260
+ if (isDynamic) {
261
+ this.outputCss += `
189
262
  @layer theme {
190
263
  .dynamic {
191
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
264
+ ${Object.entries(colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
192
265
  }
193
- }
266
+ }`;
267
+ } else {
268
+ this.outputCss += `
269
+ @theme {
270
+ --color-*: initial;
271
+ ${Object.entries(colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
272
+ }`;
273
+ }
274
+ this.outputCss += `
194
275
  @layer theme {
195
- :is(.dynamic.dark, .dynamic .dark, .dark .dynamic) {
196
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.dark};`).join("\n ")}
276
+ ${darkStyle({
277
+ selectors: [dynamicSelector],
278
+ mode: darkMode,
279
+ darkSelector: darkSelector ?? "",
280
+ styles: Object.entries(colors).map(([key, value]) => `--color-${key}: ${value.dark};`).join("\n ")
281
+ })}
282
+ }`;
283
+ for (const [key, value] of Object.entries(this.options.subThemes ?? {})) {
284
+ this.api.themes.update({ sourceColorHex: value });
285
+ const colors2 = this.getColors();
286
+ this.outputCss += `
287
+ @layer theme {
288
+ ${createFlexibleSelector(dynamicSelector, ".theme-" + key)} {
289
+ ${Object.entries(colors2).map(([key2, value2]) => `--color-${key2}: ${value2.dark};`).join("\n ")}
197
290
  }
198
291
  }
199
292
  `;
293
+ this.outputCss += `
294
+ @layer theme {
295
+ ${darkStyle({
296
+ selectors: [dynamicSelector, ".theme-" + key],
297
+ mode: darkMode,
298
+ darkSelector: darkSelector ?? "",
299
+ styles: Object.entries(colors2).map(([key2, value2]) => `--color-${key2}: ${value2.dark};`).join("\n ")
300
+ })}
301
+ }`;
302
+ }
200
303
  }
201
- async onLoad() {
202
- var _a;
203
- console.log("onLoad");
204
- this.colors = {};
304
+ getColors() {
305
+ const colors = {};
205
306
  for (const isDark of [false, true]) {
206
307
  this.api.themes.update({ isDark });
207
308
  for (const [key, value] of this.api.colors.getColors().entries()) {
208
309
  const newKey = key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
209
- (_a = this.colors)[newKey] ?? (_a[newKey] = { light: "", dark: "" });
210
- this.colors[newKey][isDark ? "dark" : "light"] = value.getHex();
310
+ colors[newKey] ?? (colors[newKey] = { light: "", dark: "" });
311
+ colors[newKey][isDark ? "dark" : "light"] = value.getHex();
211
312
  }
212
313
  }
314
+ return colors;
315
+ }
316
+ async onLoad() {
317
+ this.getColors();
213
318
  if (typeof window !== "undefined") {
214
319
  const { tailwindBrowserInit } = await Promise.resolve().then(() => require("./tailwind-browser-COFzjMN4.cjs"));
215
320
  this.outputCss = await tailwindBrowserInit(this.outputCss);
216
321
  }
217
- this.loadColor();
322
+ this.loadColor({ isDynamic: true });
218
323
  }
219
324
  }
220
325
  exports.TailwindImplPluginBrowser = TailwindImplPluginBrowser;
@@ -161,6 +161,63 @@ const main = plugin.withOptions((args) => {
161
161
  shadow.handler(api);
162
162
  };
163
163
  });
164
+ function createFlexibleSelector(...classes) {
165
+ classes = classes.filter((classeName) => !!classeName);
166
+ if (classes.length === 0) return "";
167
+ if (classes.length === 1 && classes[0]) return classes[0];
168
+ const selectors = [];
169
+ selectors.push(classes.join(""));
170
+ for (let i = 0; i < classes.length; i++) {
171
+ const ancestor = classes[i];
172
+ const descendants = classes.filter(
173
+ (className, index) => index !== i && !!className
174
+ );
175
+ if (descendants.length === 1) {
176
+ selectors.push(`${ancestor} ${descendants[0]}`);
177
+ } else if (descendants.length > 1) {
178
+ selectors.push(`${ancestor} ${descendants.join("")}`);
179
+ for (const desc of descendants) {
180
+ selectors.push(`${ancestor} ${desc}`);
181
+ }
182
+ }
183
+ }
184
+ for (let i = 0; i < classes.length; i++) {
185
+ for (let j = i + 1; j < classes.length; j++) {
186
+ selectors.push(`${classes[i]} ${classes[j]}`);
187
+ selectors.push(`${classes[j]} ${classes[i]}`);
188
+ }
189
+ }
190
+ const uniqueSelectors = [...new Set(selectors)];
191
+ return `:is(${uniqueSelectors.join(", ")})`;
192
+ }
193
+ function darkStyle({
194
+ selectors,
195
+ mode,
196
+ darkSelector,
197
+ styles
198
+ }) {
199
+ selectors = selectors.filter((classeName) => !!classeName);
200
+ if (mode === "media") {
201
+ if (selectors.length !== 0) {
202
+ return `@media (prefers-color-scheme: dark) {
203
+ ${createFlexibleSelector(...selectors)} {
204
+ ${styles}
205
+ }
206
+ }
207
+ `;
208
+ } else {
209
+ return `@media (prefers-color-scheme: dark) {
210
+ ${styles}
211
+ }
212
+ `;
213
+ }
214
+ } else {
215
+ return `${createFlexibleSelector(...selectors, darkSelector)} {
216
+ ${styles}
217
+ }
218
+ `;
219
+ }
220
+ }
164
221
  class TailwindPlugin extends PluginAbstract {
165
222
  constructor() {
166
223
  super(...arguments);
@@ -173,47 +230,95 @@ class TailwindImplPluginBrowser extends PluginImplAbstract {
173
230
  constructor() {
174
231
  super(...arguments);
175
232
  __publicField(this, "outputCss", "");
176
- __publicField(this, "colors", {});
177
233
  }
178
234
  onInit() {
179
235
  var _a;
180
236
  (_a = this.options).responsiveBreakPoints ?? (_a.responsiveBreakPoints = {
181
237
  lg: 1.125
182
238
  });
239
+ this.options = {
240
+ responsiveBreakPoints: {
241
+ lg: 1.125
242
+ },
243
+ darkMode: "class",
244
+ darkSelector: ".dark",
245
+ dynamicSelector: ".dynamic",
246
+ ...this.options
247
+ };
183
248
  }
184
- loadColor() {
185
- this.outputCss += `
186
- @variant dynamic-default (&:where(.dynamic, .dynamic *));
187
- @variant dynamic-dark (&:where(.dynamic:where(.dark, .dark *), .dark:where(.dynamic, .dynamic *)));
249
+ loadColor({ isDynamic }) {
250
+ let { dynamicSelector, darkSelector } = this.options;
251
+ if (!isDynamic) {
252
+ dynamicSelector = void 0;
253
+ }
254
+ const darkMode = this.options.darkMode ?? "class";
255
+ if (darkMode == "media") {
256
+ darkSelector = void 0;
257
+ }
258
+ const colors = this.getColors();
259
+ if (isDynamic) {
260
+ this.outputCss += `
188
261
  @layer theme {
189
262
  .dynamic {
190
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
263
+ ${Object.entries(colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
191
264
  }
192
- }
265
+ }`;
266
+ } else {
267
+ this.outputCss += `
268
+ @theme {
269
+ --color-*: initial;
270
+ ${Object.entries(colors).map(([key, value]) => `--color-${key}: ${value.light};`).join("\n ")}
271
+ }`;
272
+ }
273
+ this.outputCss += `
193
274
  @layer theme {
194
- :is(.dynamic.dark, .dynamic .dark, .dark .dynamic) {
195
- ${Object.entries(this.colors).map(([key, value]) => `--color-${key}: ${value.dark};`).join("\n ")}
275
+ ${darkStyle({
276
+ selectors: [dynamicSelector],
277
+ mode: darkMode,
278
+ darkSelector: darkSelector ?? "",
279
+ styles: Object.entries(colors).map(([key, value]) => `--color-${key}: ${value.dark};`).join("\n ")
280
+ })}
281
+ }`;
282
+ for (const [key, value] of Object.entries(this.options.subThemes ?? {})) {
283
+ this.api.themes.update({ sourceColorHex: value });
284
+ const colors2 = this.getColors();
285
+ this.outputCss += `
286
+ @layer theme {
287
+ ${createFlexibleSelector(dynamicSelector, ".theme-" + key)} {
288
+ ${Object.entries(colors2).map(([key2, value2]) => `--color-${key2}: ${value2.dark};`).join("\n ")}
196
289
  }
197
290
  }
198
291
  `;
292
+ this.outputCss += `
293
+ @layer theme {
294
+ ${darkStyle({
295
+ selectors: [dynamicSelector, ".theme-" + key],
296
+ mode: darkMode,
297
+ darkSelector: darkSelector ?? "",
298
+ styles: Object.entries(colors2).map(([key2, value2]) => `--color-${key2}: ${value2.dark};`).join("\n ")
299
+ })}
300
+ }`;
301
+ }
199
302
  }
200
- async onLoad() {
201
- var _a;
202
- console.log("onLoad");
203
- this.colors = {};
303
+ getColors() {
304
+ const colors = {};
204
305
  for (const isDark of [false, true]) {
205
306
  this.api.themes.update({ isDark });
206
307
  for (const [key, value] of this.api.colors.getColors().entries()) {
207
308
  const newKey = key.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
208
- (_a = this.colors)[newKey] ?? (_a[newKey] = { light: "", dark: "" });
209
- this.colors[newKey][isDark ? "dark" : "light"] = value.getHex();
309
+ colors[newKey] ?? (colors[newKey] = { light: "", dark: "" });
310
+ colors[newKey][isDark ? "dark" : "light"] = value.getHex();
210
311
  }
211
312
  }
313
+ return colors;
314
+ }
315
+ async onLoad() {
316
+ this.getColors();
212
317
  if (typeof window !== "undefined") {
213
318
  const { tailwindBrowserInit } = await import("./tailwind-browser-CTGKNrKy.js");
214
319
  this.outputCss = await tailwindBrowserInit(this.outputCss);
215
320
  }
216
- this.loadColor();
321
+ this.loadColor({ isDynamic: true });
217
322
  }
218
323
  }
219
324
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@udixio/tailwind",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "main": "./dist/node.js",
6
6
  "module": "./dist/node.js",
@@ -1,10 +1,90 @@
1
1
  import { FontPlugin, PluginAbstract, PluginImplAbstract } from '@udixio/theme';
2
2
 
3
3
  export interface TailwindPluginOptions {
4
- // darkMode?: 'class' | 'media';
4
+ darkMode?: 'class' | 'media';
5
+ dynamicSelector?: string;
6
+ darkSelector?: string;
5
7
  responsiveBreakPoints?: Record<string, number>;
6
8
  styleFilePath?: string;
7
- // subThemes?: Record<string, string>;
9
+ subThemes?: Record<string, string>;
10
+ }
11
+
12
+ function createFlexibleSelector(...classes: (string | undefined)[]): string {
13
+ classes = classes.filter((classeName) => !!classeName);
14
+ if (classes.length === 0) return '';
15
+ if (classes.length === 1 && classes[0]) return classes[0];
16
+
17
+ // Approche plus simple : générer les cas les plus courants
18
+ const selectors: string[] = [];
19
+
20
+ // 1. Toutes les classes sur le même élément
21
+ selectors.push(classes.join(''));
22
+
23
+ // 2. Chaque classe comme ancêtre des autres
24
+ for (let i = 0; i < classes.length; i++) {
25
+ const ancestor = classes[i];
26
+ const descendants = classes.filter(
27
+ (className, index) => index !== i && !!className,
28
+ );
29
+
30
+ if (descendants.length === 1) {
31
+ selectors.push(`${ancestor} ${descendants[0]}`);
32
+ } else if (descendants.length > 1) {
33
+ selectors.push(`${ancestor} ${descendants.join('')}`);
34
+ // Aussi les descendants séparés
35
+ for (const desc of descendants) {
36
+ selectors.push(`${ancestor} ${desc}`);
37
+ }
38
+ }
39
+ }
40
+
41
+ // 3. Permutations adjacentes (A B, B A)
42
+ for (let i = 0; i < classes.length; i++) {
43
+ for (let j = i + 1; j < classes.length; j++) {
44
+ selectors.push(`${classes[i]} ${classes[j]}`);
45
+ selectors.push(`${classes[j]} ${classes[i]}`);
46
+ }
47
+ }
48
+
49
+ // Supprimer les doublons
50
+ const uniqueSelectors = [...new Set(selectors)];
51
+
52
+ return `:is(${uniqueSelectors.join(', ')})`;
53
+ }
54
+
55
+ function darkStyle({
56
+ selectors,
57
+ mode,
58
+ darkSelector,
59
+ styles,
60
+ }: {
61
+ selectors: (string | undefined)[];
62
+ darkSelector: string;
63
+ styles: string;
64
+ mode: 'class' | 'media';
65
+ }): string {
66
+ selectors = selectors.filter((classeName) => !!classeName);
67
+
68
+ if (mode === 'media') {
69
+ if (selectors.length !== 0) {
70
+ return `@media (prefers-color-scheme: dark) {
71
+ ${createFlexibleSelector(...selectors)} {
72
+ ${styles}
73
+ }
74
+ }
75
+ `;
76
+ } else {
77
+ return `@media (prefers-color-scheme: dark) {
78
+ ${styles}
79
+ }
80
+ `;
81
+ }
82
+ } else {
83
+ return `${createFlexibleSelector(...selectors, darkSelector)} {
84
+ ${styles}
85
+ }
86
+ `;
87
+ }
8
88
  }
9
89
 
10
90
  export class TailwindPlugin extends PluginAbstract<
@@ -18,61 +98,122 @@ export class TailwindPlugin extends PluginAbstract<
18
98
 
19
99
  export class TailwindImplPluginBrowser extends PluginImplAbstract<TailwindPluginOptions> {
20
100
  public outputCss = '';
21
- protected colors: Record<
22
- string,
23
- {
24
- light: string;
25
- dark: string;
26
- }
27
- > = {};
28
101
 
29
102
  onInit() {
30
103
  this.options.responsiveBreakPoints ??= {
31
104
  lg: 1.125,
32
105
  };
106
+ this.options = {
107
+ responsiveBreakPoints: {
108
+ lg: 1.125,
109
+ },
110
+ darkMode: 'class',
111
+ darkSelector: '.dark',
112
+ dynamicSelector: '.dynamic',
113
+ ...this.options,
114
+ };
33
115
  }
34
116
 
35
- loadColor() {
36
- this.outputCss += `
37
- @variant dynamic-default (&:where(.dynamic, .dynamic *));
38
- @variant dynamic-dark (&:where(.dynamic:where(.dark, .dark *), .dark:where(.dynamic, .dynamic *)));
117
+ loadColor({ isDynamic }: { isDynamic: boolean }) {
118
+ let { dynamicSelector, darkSelector } = this.options;
119
+ if (!isDynamic) {
120
+ dynamicSelector = undefined;
121
+ }
122
+ const darkMode = this.options.darkMode ?? 'class';
123
+ if (darkMode == 'media') {
124
+ darkSelector = undefined;
125
+ }
126
+
127
+ const colors = this.getColors();
128
+
129
+ if (isDynamic) {
130
+ this.outputCss += `
39
131
  @layer theme {
40
132
  .dynamic {
41
- ${Object.entries(this.colors)
133
+ ${Object.entries(colors)
42
134
  .map(([key, value]) => `--color-${key}: ${value.light};`)
43
135
  .join('\n ')}
44
136
  }
45
- }
137
+ }`;
138
+ } else {
139
+ this.outputCss += `
140
+ @theme {
141
+ --color-*: initial;
142
+ ${Object.entries(colors)
143
+ .map(([key, value]) => `--color-${key}: ${value.light};`)
144
+ .join('\n ')}
145
+ }`;
146
+ }
147
+
148
+ this.outputCss += `
46
149
  @layer theme {
47
- :is(.dynamic.dark, .dynamic .dark, .dark .dynamic) {
48
- ${Object.entries(this.colors)
150
+ ${darkStyle({
151
+ selectors: [dynamicSelector],
152
+ mode: darkMode,
153
+ darkSelector: darkSelector ?? '',
154
+ styles: Object.entries(colors)
49
155
  .map(([key, value]) => `--color-${key}: ${value.dark};`)
50
- .join('\n ')}
156
+ .join('\n '),
157
+ })}
158
+ }`;
159
+
160
+ for (const [key, value] of Object.entries(this.options.subThemes ?? {})) {
161
+ this.api.themes.update({ sourceColorHex: value });
162
+ const colors = this.getColors();
163
+ this.outputCss += `
164
+ @layer theme {
165
+ ${createFlexibleSelector(dynamicSelector, '.theme-' + key)} {
166
+ ${Object.entries(colors)
167
+ .map(([key, value]) => `--color-${key}: ${value.dark};`)
168
+ .join('\n ')}
51
169
  }
52
170
  }
53
171
  `;
172
+
173
+ this.outputCss += `
174
+ @layer theme {
175
+ ${darkStyle({
176
+ selectors: [dynamicSelector, '.theme-' + key],
177
+ mode: darkMode,
178
+ darkSelector: darkSelector ?? '',
179
+ styles: Object.entries(colors)
180
+ .map(([key, value]) => `--color-${key}: ${value.dark};`)
181
+ .join('\n '),
182
+ })}
183
+ }`;
184
+ }
54
185
  }
55
186
 
56
- async onLoad() {
57
- console.log('onLoad');
58
- this.colors = {};
187
+ getColors() {
188
+ const colors: Record<
189
+ string,
190
+ {
191
+ light: string;
192
+ dark: string;
193
+ }
194
+ > = {};
59
195
  for (const isDark of [false, true]) {
60
196
  this.api.themes.update({ isDark: isDark });
61
197
  for (const [key, value] of this.api.colors.getColors().entries()) {
62
198
  const newKey = key
63
199
  .replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
64
200
  .toLowerCase();
65
- this.colors[newKey] ??= { light: '', dark: '' };
66
- this.colors[newKey][isDark ? 'dark' : 'light'] = value.getHex();
201
+ colors[newKey] ??= { light: '', dark: '' };
202
+ colors[newKey][isDark ? 'dark' : 'light'] = value.getHex();
67
203
  }
68
204
  }
69
205
 
206
+ return colors;
207
+ }
208
+
209
+ async onLoad() {
210
+ this.getColors();
211
+
70
212
  if (typeof window !== 'undefined') {
71
213
  const { tailwindBrowserInit } = await import('./tailwind-browser');
72
214
 
73
215
  this.outputCss = await tailwindBrowserInit(this.outputCss);
74
216
  }
75
-
76
- this.loadColor();
217
+ this.loadColor({ isDynamic: true });
77
218
  }
78
219
  }
@@ -1,6 +1,9 @@
1
1
  import { FontPlugin, PluginAbstract } from '@udixio/theme';
2
2
 
3
- import { TailwindImplPluginBrowser, TailwindPluginOptions } from '../browser/tailwind.plugin';
3
+ import {
4
+ TailwindImplPluginBrowser,
5
+ TailwindPluginOptions,
6
+ } from '../browser/tailwind.plugin';
4
7
 
5
8
  import { ConfigCss } from '../main';
6
9
 
@@ -22,29 +25,6 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
22
25
  );
23
26
  }
24
27
 
25
- override loadColor() {
26
- if (!this.isNodeJs()) {
27
- super.loadColor();
28
- return;
29
- }
30
- this.outputCss += `
31
- @custom-variant dark (&:where(.dark, .dark *));
32
- @theme {
33
- --color-*: initial;
34
- ${Object.entries(this.colors)
35
- .map(([key, value]) => `--color-${key}: ${value.light};`)
36
- .join('\n ')}
37
- }
38
- @layer theme {
39
- .dark {
40
- ${Object.entries(this.colors)
41
- .map(([key, value]) => `--color-${key}: ${value.dark};`)
42
- .join('\n ')}
43
- }
44
- }
45
- `;
46
- }
47
-
48
28
  override async onLoad() {
49
29
  if (!this.isNodeJs()) {
50
30
  await super.onLoad();
@@ -59,17 +39,8 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
59
39
  getFileContent,
60
40
  replaceFileContent,
61
41
  } = await import('./file');
62
- this.colors = {};
63
- for (const isDark of [false, true]) {
64
- this.api.themes.update({ isDark: isDark });
65
- for (const [key, value] of this.api.colors.getColors().entries()) {
66
- const newKey = key
67
- .replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
68
- .toLowerCase();
69
- this.colors[newKey] ??= { light: '', dark: '' };
70
- this.colors[newKey][isDark ? 'dark' : 'light'] = value.getHex();
71
- }
72
- }
42
+
43
+ const colors = this.getColors();
73
44
 
74
45
  let udixioCssPath = this.options.styleFilePath;
75
46
 
@@ -98,7 +69,7 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
98
69
  .getFonts();
99
70
 
100
71
  const configCss: ConfigCss = {
101
- colorKeys: Object.keys(this.colors).join(', ') as any,
72
+ colorKeys: Object.keys(colors).join(', ') as any,
102
73
  fontStyles: Object.entries(fontStyles)
103
74
  .map(([fontRole, fontStyle]) =>
104
75
  Object.entries(fontStyle)
@@ -123,7 +94,7 @@ class TailwindImplPlugin extends TailwindImplPluginBrowser {
123
94
  fontStyles: ${configCss.fontStyles};
124
95
  responsiveBreakPoints: ${configCss.responsiveBreakPoints};
125
96
  }`;
126
- this.loadColor();
97
+ this.loadColor({ isDynamic: false });
127
98
  this.outputCss += `
128
99
  @theme {
129
100
  ${Object.entries(fontFamily)