tailwind-to-style 4.0.0 → 4.0.2

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 CHANGED
@@ -64,6 +64,30 @@ const classes = cx('base', isActive && 'ring-2', { 'opacity-50': disabled });
64
64
 
65
65
  ---
66
66
 
67
+ ## Examples
68
+
69
+ Want to try the library with real demos?
70
+
71
+ - `examples/basic/` — runtime `tws()` examples for inline conversion and custom values.
72
+ - `examples/react-demo/` — full React showcase with components, variants, tokens, and theme switching.
73
+ - `examples/twsx-classname-app/` — Vite-based runtime `tw()` v4 demo with variant and slots components.
74
+
75
+ Run the demos by opening `examples/README.md` or using the commands below:
76
+
77
+ ```bash
78
+ cd examples/react-demo
79
+ npm install
80
+ npm run dev
81
+ ```
82
+
83
+ ```bash
84
+ cd examples/twsx-classname-app
85
+ npm install
86
+ npm run dev
87
+ ```
88
+
89
+ ---
90
+
67
91
  ## API Reference
68
92
 
69
93
  ### `tw()` — The Main Function
@@ -259,7 +283,7 @@ createTheme({
259
283
  },
260
284
  spacing: { sm: '0.5rem', md: '1rem', lg: '1.5rem' },
261
285
  radius: { sm: '0.25rem', md: '0.5rem', lg: '1rem' },
262
- });
286
+ }, { selector: ':root' });
263
287
  // Injects CSS variables on :root:
264
288
  // --tws-colors-primary: #3b82f6;
265
289
  // --tws-colors-secondary: #8b5cf6;
@@ -361,11 +385,10 @@ Import only what you need for minimal bundle size:
361
385
 
362
386
  Works with any framework or vanilla JS:
363
387
 
364
- - **React** — Full bindings via `tailwind-to-style/react`
365
- - **Vue** — Use `tw()` in computed properties or `tws()` in `:style`
366
- - **Svelte** — Use `tw()` in `class:` or `tws()` in `style:`
367
- - **Vanilla JS** — Direct DOM manipulation
368
- - **Node.js / SSR** — `tws()` for inline + `createSSRCollector()` for classes
388
+ - **React** — Full bindings via `tailwind-to-style/react` (example available in `examples/react-demo`)
389
+ - **Vanilla JS** — Direct DOM usage with `tw()` and `tws()`
390
+ - **Node.js / SSR** — `tws()` for inline styles + `createSSRCollector()` for CSS extraction
391
+ - **Vue / Svelte** — supported in runtime with `tw()` / `tws()`, examples can be added in future releases
369
392
 
370
393
  ---
371
394
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v4.0.0
2
+ * tailwind-to-style v4.0.2
3
3
  * Zero-build runtime Tailwind CSS engine
4
4
  *
5
5
  * @author Bigetion
@@ -2049,6 +2049,106 @@ function generateCssString$1() {
2049
2049
  return cssString;
2050
2050
  }
2051
2051
 
2052
+ /**
2053
+ * SSR (Server-Side Rendering) Collector
2054
+ *
2055
+ * Simplified API for collecting CSS during server-side rendering.
2056
+ * Replaces the imperative startSSR/stopSSR/getSSRStyles with a context-based approach.
2057
+ *
2058
+ * Features:
2059
+ * - Instance-based state (safe for concurrent requests)
2060
+ * - CSS deduplication
2061
+ * - Critical CSS extraction
2062
+ * - Minification support
2063
+ * - Nonce support for CSP
2064
+ *
2065
+ * @module ssr
2066
+ */
2067
+
2068
+ let _globalIsCollecting = false;
2069
+
2070
+ // Fallback "active collector" used when AsyncLocalStorage isn't available
2071
+ // (browsers, edge runtimes without async_hooks) or before it has finished
2072
+ // loading. Safe for the common single-request-at-a-time case, but multiple
2073
+ // concurrent requests on the same Node process can interleave and see each
2074
+ // other's CSS — use `collector.run(fn)` below to avoid that.
2075
+ let _activeCollector = null;
2076
+
2077
+ // ============================================================================
2078
+ // Async-context isolation (Node only)
2079
+ // ============================================================================
2080
+ // AsyncLocalStorage lets each concurrent request keep its own "active
2081
+ // collector" even though CSS is collected deep inside tw()/twsx() calls that
2082
+ // have no direct reference to the collector. It only exists in Node, and
2083
+ // this module is also bundled for browsers (via styled()/index.js), so the
2084
+ // Node builtin must never be statically imported — that would break the
2085
+ // browser/CDN build. It's loaded lazily via a dynamic import instead, and
2086
+ // every call site below falls back gracefully when it isn't available.
2087
+ const IS_NODE = typeof process !== "undefined" && !!process.versions && !!process.versions.node;
2088
+ let _als = null;
2089
+ let _alsLoadPromise = null;
2090
+ function loadAsyncLocalStorage() {
2091
+ if (!IS_NODE) return Promise.resolve(null);
2092
+ if (!_alsLoadPromise) {
2093
+ const specifier = "node:async_hooks";
2094
+ _alsLoadPromise = import(/* @vite-ignore */specifier).then(mod => new mod.AsyncLocalStorage()).catch(() => null);
2095
+ }
2096
+ return _alsLoadPromise;
2097
+ }
2098
+ if (IS_NODE) {
2099
+ // Kick off loading immediately so it's ready before the first request in
2100
+ // the common case. There's an unavoidable brief window right at process
2101
+ // startup where this hasn't resolved yet; the singleton fallback above
2102
+ // covers that window for single-request-at-a-time usage.
2103
+ loadAsyncLocalStorage().then(als => {
2104
+ _als = als;
2105
+ });
2106
+ }
2107
+
2108
+ // ============================================================================
2109
+ // Internal Helpers (used by twsx/tws)
2110
+ // ============================================================================
2111
+
2112
+ /**
2113
+ * Check if SSR collecting is active
2114
+ * @internal
2115
+ */
2116
+ function isSSRCollecting() {
2117
+ const active = getActiveCollector();
2118
+ return !!(active && active.isCollecting) || _globalIsCollecting;
2119
+ }
2120
+
2121
+ /**
2122
+ * Add CSS to SSR collection (prefers active collector if available)
2123
+ * @internal
2124
+ */
2125
+ function collectSSRCSS(css) {
2126
+ if (!css) return;
2127
+
2128
+ // Use active collector if available (modern API) — prefers the
2129
+ // AsyncLocalStorage-scoped collector for the current request when present.
2130
+ const active = getActiveCollector();
2131
+ if (active && active.isCollecting) {
2132
+ active._collect(css);
2133
+ return;
2134
+ }
2135
+ }
2136
+
2137
+ /**
2138
+ * Get active collector (for advanced usage)
2139
+ * Prefers the AsyncLocalStorage-scoped collector for the current async
2140
+ * context (set via `collector.run(fn)`) and falls back to the shared
2141
+ * singleton otherwise.
2142
+ * @internal
2143
+ */
2144
+ function getActiveCollector() {
2145
+ if (_als) {
2146
+ const store = _als.getStore();
2147
+ if (store) return store;
2148
+ }
2149
+ return _activeCollector;
2150
+ }
2151
+
2052
2152
  /**
2053
2153
  * Proper LRU (Least Recently Used) Cache implementation
2054
2154
  * Efficiently manages memory by removing least recently used items
@@ -5926,10 +6026,6 @@ cx.with = function () {
5926
6026
  // Detect environment once at module load for zero-cost runtime checks
5927
6027
  // ============================================================================
5928
6028
  const IS_BROWSER$1 = typeof window !== "undefined" && typeof document !== "undefined";
5929
-
5930
- // SSR CSS collector - accumulates CSS strings during server rendering
5931
- let _ssrCollectedCss = [];
5932
- let _ssrCollecting = false;
5933
6029
  const MAX_CACHE_SIZE$1 = 5000;
5934
6030
  const MAX_SET_SIZE = 10000;
5935
6031
 
@@ -7304,7 +7400,7 @@ function twsx(obj) {
7304
7400
  const cached = _twsxInputCache.get(cacheKey);
7305
7401
 
7306
7402
  // Re-inject with registryKey so the slot stays registered (no-op when unchanged).
7307
- if (inject && (IS_BROWSER$1 || _ssrCollecting)) {
7403
+ if (inject && (IS_BROWSER$1 || isSSRCollecting())) {
7308
7404
  autoInjectCss(cached, registryKey);
7309
7405
  }
7310
7406
  return cached;
@@ -7318,7 +7414,7 @@ function twsx(obj) {
7318
7414
  _twsxInputCache.set(cacheKey, result);
7319
7415
  // LRUCache handles eviction automatically
7320
7416
 
7321
- if (inject && (IS_BROWSER$1 || _ssrCollecting)) {
7417
+ if (inject && (IS_BROWSER$1 || isSSRCollecting())) {
7322
7418
  autoInjectCss(result, registryKey);
7323
7419
  }
7324
7420
  return result;
@@ -7363,8 +7459,15 @@ function autoInjectCss(cssString) {
7363
7459
  let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
7364
7460
  const marker = performanceMonitor.start("css:inject");
7365
7461
  try {
7366
- // SSR mode: collect CSS strings instead of DOM injection
7367
- if (_ssrCollecting) ;
7462
+ // SSR mode: collect CSS strings instead of DOM injection. Delegates to
7463
+ // the shared collector in ./utils/ssr.js so both the legacy
7464
+ // startSSR()/stopSSR() pair and the modern createSSRCollector() actually
7465
+ // receive the CSS generated by tw()/twsx().
7466
+ if (isSSRCollecting()) {
7467
+ collectSSRCSS(cssString);
7468
+ performanceMonitor.end(marker);
7469
+ return;
7470
+ }
7368
7471
  if (IS_BROWSER$1) {
7369
7472
  if (sourceKey) {
7370
7473
  // Slot-based update: each unique twsx(obj) call owns its own CSS block.
@@ -7834,6 +7937,11 @@ function injectCSS(className, css) {
7834
7937
  // Batch CSS injection with requestAnimationFrame
7835
7938
  pendingCSS.push(css);
7836
7939
  scheduleStyleUpdate();
7940
+ } else if (isSSRCollecting()) {
7941
+ // Server-side rendering: forward to the shared SSR collector. Previously
7942
+ // this branch didn't exist, so tw()/styled() produced no usable CSS
7943
+ // output at all during SSR (only the browser path injected anything).
7944
+ collectSSRCSS(css);
7837
7945
  }
7838
7946
  }
7839
7947
  function scheduleStyleUpdate() {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tailwind-to-style v4.0.0
2
+ * tailwind-to-style v4.0.2
3
3
  * Zero-build runtime Tailwind CSS engine
4
4
  *
5
5
  * @author Bigetion
@@ -2047,6 +2047,106 @@ function generateCssString$1() {
2047
2047
  return cssString;
2048
2048
  }
2049
2049
 
2050
+ /**
2051
+ * SSR (Server-Side Rendering) Collector
2052
+ *
2053
+ * Simplified API for collecting CSS during server-side rendering.
2054
+ * Replaces the imperative startSSR/stopSSR/getSSRStyles with a context-based approach.
2055
+ *
2056
+ * Features:
2057
+ * - Instance-based state (safe for concurrent requests)
2058
+ * - CSS deduplication
2059
+ * - Critical CSS extraction
2060
+ * - Minification support
2061
+ * - Nonce support for CSP
2062
+ *
2063
+ * @module ssr
2064
+ */
2065
+
2066
+ let _globalIsCollecting = false;
2067
+
2068
+ // Fallback "active collector" used when AsyncLocalStorage isn't available
2069
+ // (browsers, edge runtimes without async_hooks) or before it has finished
2070
+ // loading. Safe for the common single-request-at-a-time case, but multiple
2071
+ // concurrent requests on the same Node process can interleave and see each
2072
+ // other's CSS — use `collector.run(fn)` below to avoid that.
2073
+ let _activeCollector = null;
2074
+
2075
+ // ============================================================================
2076
+ // Async-context isolation (Node only)
2077
+ // ============================================================================
2078
+ // AsyncLocalStorage lets each concurrent request keep its own "active
2079
+ // collector" even though CSS is collected deep inside tw()/twsx() calls that
2080
+ // have no direct reference to the collector. It only exists in Node, and
2081
+ // this module is also bundled for browsers (via styled()/index.js), so the
2082
+ // Node builtin must never be statically imported — that would break the
2083
+ // browser/CDN build. It's loaded lazily via a dynamic import instead, and
2084
+ // every call site below falls back gracefully when it isn't available.
2085
+ const IS_NODE = typeof process !== "undefined" && !!process.versions && !!process.versions.node;
2086
+ let _als = null;
2087
+ let _alsLoadPromise = null;
2088
+ function loadAsyncLocalStorage() {
2089
+ if (!IS_NODE) return Promise.resolve(null);
2090
+ if (!_alsLoadPromise) {
2091
+ const specifier = "node:async_hooks";
2092
+ _alsLoadPromise = import(/* @vite-ignore */specifier).then(mod => new mod.AsyncLocalStorage()).catch(() => null);
2093
+ }
2094
+ return _alsLoadPromise;
2095
+ }
2096
+ if (IS_NODE) {
2097
+ // Kick off loading immediately so it's ready before the first request in
2098
+ // the common case. There's an unavoidable brief window right at process
2099
+ // startup where this hasn't resolved yet; the singleton fallback above
2100
+ // covers that window for single-request-at-a-time usage.
2101
+ loadAsyncLocalStorage().then(als => {
2102
+ _als = als;
2103
+ });
2104
+ }
2105
+
2106
+ // ============================================================================
2107
+ // Internal Helpers (used by twsx/tws)
2108
+ // ============================================================================
2109
+
2110
+ /**
2111
+ * Check if SSR collecting is active
2112
+ * @internal
2113
+ */
2114
+ function isSSRCollecting() {
2115
+ const active = getActiveCollector();
2116
+ return !!(active && active.isCollecting) || _globalIsCollecting;
2117
+ }
2118
+
2119
+ /**
2120
+ * Add CSS to SSR collection (prefers active collector if available)
2121
+ * @internal
2122
+ */
2123
+ function collectSSRCSS(css) {
2124
+ if (!css) return;
2125
+
2126
+ // Use active collector if available (modern API) — prefers the
2127
+ // AsyncLocalStorage-scoped collector for the current request when present.
2128
+ const active = getActiveCollector();
2129
+ if (active && active.isCollecting) {
2130
+ active._collect(css);
2131
+ return;
2132
+ }
2133
+ }
2134
+
2135
+ /**
2136
+ * Get active collector (for advanced usage)
2137
+ * Prefers the AsyncLocalStorage-scoped collector for the current async
2138
+ * context (set via `collector.run(fn)`) and falls back to the shared
2139
+ * singleton otherwise.
2140
+ * @internal
2141
+ */
2142
+ function getActiveCollector() {
2143
+ if (_als) {
2144
+ const store = _als.getStore();
2145
+ if (store) return store;
2146
+ }
2147
+ return _activeCollector;
2148
+ }
2149
+
2050
2150
  /**
2051
2151
  * Proper LRU (Least Recently Used) Cache implementation
2052
2152
  * Efficiently manages memory by removing least recently used items
@@ -5924,10 +6024,6 @@ cx.with = function () {
5924
6024
  // Detect environment once at module load for zero-cost runtime checks
5925
6025
  // ============================================================================
5926
6026
  const IS_BROWSER$1 = typeof window !== "undefined" && typeof document !== "undefined";
5927
-
5928
- // SSR CSS collector - accumulates CSS strings during server rendering
5929
- let _ssrCollectedCss = [];
5930
- let _ssrCollecting = false;
5931
6027
  const MAX_CACHE_SIZE$1 = 5000;
5932
6028
  const MAX_SET_SIZE = 10000;
5933
6029
 
@@ -7302,7 +7398,7 @@ function twsx(obj) {
7302
7398
  const cached = _twsxInputCache.get(cacheKey);
7303
7399
 
7304
7400
  // Re-inject with registryKey so the slot stays registered (no-op when unchanged).
7305
- if (inject && (IS_BROWSER$1 || _ssrCollecting)) {
7401
+ if (inject && (IS_BROWSER$1 || isSSRCollecting())) {
7306
7402
  autoInjectCss(cached, registryKey);
7307
7403
  }
7308
7404
  return cached;
@@ -7316,7 +7412,7 @@ function twsx(obj) {
7316
7412
  _twsxInputCache.set(cacheKey, result);
7317
7413
  // LRUCache handles eviction automatically
7318
7414
 
7319
- if (inject && (IS_BROWSER$1 || _ssrCollecting)) {
7415
+ if (inject && (IS_BROWSER$1 || isSSRCollecting())) {
7320
7416
  autoInjectCss(result, registryKey);
7321
7417
  }
7322
7418
  return result;
@@ -7361,8 +7457,15 @@ function autoInjectCss(cssString) {
7361
7457
  let sourceKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
7362
7458
  const marker = performanceMonitor.start("css:inject");
7363
7459
  try {
7364
- // SSR mode: collect CSS strings instead of DOM injection
7365
- if (_ssrCollecting) ;
7460
+ // SSR mode: collect CSS strings instead of DOM injection. Delegates to
7461
+ // the shared collector in ./utils/ssr.js so both the legacy
7462
+ // startSSR()/stopSSR() pair and the modern createSSRCollector() actually
7463
+ // receive the CSS generated by tw()/twsx().
7464
+ if (isSSRCollecting()) {
7465
+ collectSSRCSS(cssString);
7466
+ performanceMonitor.end(marker);
7467
+ return;
7468
+ }
7366
7469
  if (IS_BROWSER$1) {
7367
7470
  if (sourceKey) {
7368
7471
  // Slot-based update: each unique twsx(obj) call owns its own CSS block.
@@ -7832,6 +7935,11 @@ function injectCSS(className, css) {
7832
7935
  // Batch CSS injection with requestAnimationFrame
7833
7936
  pendingCSS.push(css);
7834
7937
  scheduleStyleUpdate();
7938
+ } else if (isSSRCollecting()) {
7939
+ // Server-side rendering: forward to the shared SSR collector. Previously
7940
+ // this branch didn't exist, so tw()/styled() produced no usable CSS
7941
+ // output at all during SSR (only the browser path injected anything).
7942
+ collectSSRCSS(css);
7835
7943
  }
7836
7944
  }
7837
7945
  function scheduleStyleUpdate() {