@acusti/styling 2.0.1 → 2.1.1

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
@@ -5,26 +5,640 @@
5
5
  [![downloads per month](https://img.shields.io/npm/dm/@acusti/styling?style=for-the-badge)](https://www.npmjs.com/package/@acusti/styling)
6
6
  [![bundle size](https://deno.bundlejs.com/badge?q=@acusti/styling)](https://bundlejs.com/?q=%40acusti%2Fstyling)
7
7
 
8
- This package exports `Style`, which is a React component that takes a CSS
9
- string as its children, minifies it, and renders it using the react v19+
10
- `<style>` element’s
8
+ `@acusti/styling` provides React 19+ optimized styling utilities, including
9
+ the `Style` component that leverages React’s new `<style>` element special
10
+ rendering behavior and a collection of CSS utilities. It’s designed for
11
+ modern React applications that need efficient, SSR-friendly styling with
12
+ automatic optimization.
13
+
14
+ ## Key Features
15
+
16
+ - **React 19+ optimized** - Uses React’s new `<style>` element special
17
+ rendering behavior
18
+ - **Automatic minification** - CSS is minified and cached for optimal
19
+ performance
20
+ - **SSR-friendly** - No hydration errors with server-side rendering
21
+ - **Style deduplication** - Identical styles are automatically deduplicated
22
+ - **Suspense integration** - Assets in CSS suspend properly during loading
23
+ - **Global cache** - Minified styles are cached to avoid re-computation
24
+ - **CSS utilities** - Includes useful CSS constants like system fonts
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ npm install @acusti/styling
30
+ # or
31
+ yarn add @acusti/styling
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```tsx
37
+ import { Style } from '@acusti/styling';
38
+
39
+ function MyComponent() {
40
+ return (
41
+ <>
42
+ <Style>
43
+ {`
44
+ .my-button {
45
+ background: #007bff;
46
+ color: white;
47
+ border: none;
48
+ padding: 8px 16px;
49
+ border-radius: 4px;
50
+ cursor: pointer;
51
+ }
52
+ .my-button:hover {
53
+ background: #0056b3;
54
+ }
55
+ `}
56
+ </Style>
57
+
58
+ <button className="my-button">Click me</button>
59
+ </>
60
+ );
61
+ }
62
+ ```
63
+
64
+ ## React 19+ `<style>` Special Behavior
65
+
66
+ This package leverages React 19’s new `<style>` element
11
67
  [special rendering behavior](https://react.dev/reference/react-dom/components/style#special-rendering-behavior):
12
68
 
13
69
  > React will move `<style>` components to the document’s `<head>`,
14
70
  > de-duplicate identical stylesheets, and suspend while the stylesheet is
15
71
  > loading.
16
72
 
17
- This behavior is SSR-friendly (no server hydration errors), and the
18
- suspense behavior ensures any assets used by the CSS styles that must be
19
- fetched and parsed (e.g. fonts or images) can do so with a loading behavior
20
- as-good or better than the way regular CSS stylesheets or inline styles are
21
- handled by the browser.
73
+ **Benefits:**
74
+
75
+ - **SSR-friendly** - No hydration errors between server and client
76
+ - **Automatic deduplication** - Identical styles are merged automatically
77
+ - **Suspense integration** - CSS assets (fonts, images) suspend properly
78
+ during loading
79
+ - **Performance optimized** - Styles are hoisted to `<head>` for optimal
80
+ loading
81
+
82
+ ## API Reference
83
+
84
+ ### `Style` Component
85
+
86
+ ```tsx
87
+ type Props = {
88
+ children: string;
89
+ href?: string;
90
+ precedence?: string;
91
+ };
92
+ ```
93
+
94
+ **Props:**
95
+
96
+ - **`children`** (string, required): The CSS string to render
97
+ - **`href`** (string, optional): Custom identifier for the stylesheet.
98
+ Defaults to minified CSS content
99
+ - **`precedence`** (string, optional): Controls loading priority. Defaults
100
+ to `'medium'`
101
+
102
+ ```tsx
103
+ import { Style } from '@acusti/styling';
104
+
105
+ function MyComponent() {
106
+ return (
107
+ <Style precedence="high" href="critical-styles">
108
+ {`.critical { display: block !important; }`}
109
+ </Style>
110
+ );
111
+ }
112
+ ```
113
+
114
+ ### CSS Minification Function
115
+
116
+ ```tsx
117
+ import { minifyStyles } from '@acusti/styling';
118
+
119
+ const minified = minifyStyles(`
120
+ .button {
121
+ background-color: #ffffff;
122
+ padding: 10px 20px;
123
+ border: 1px solid #cccccc;
124
+ }
125
+ `);
126
+ // Result: ".button{background-color:#fff;padding:10px 20px;border:1px solid #ccc}"
127
+ ```
128
+
129
+ ### CSS Constants
130
+
131
+ - **`SYSTEM_UI_FONT`**: Cross-platform system UI font stack
132
+
133
+ ```tsx
134
+ import { Style, SYSTEM_UI_FONT } from '@acusti/styling';
135
+
136
+ function SystemStyles() {
137
+ return <Style>{':root { font-family: ${SYSTEM_UI_FONT}; }'}</Style>;
138
+ }
139
+ ```
140
+
141
+ ## Usage Examples
142
+
143
+ ### Component-Scoped Styles
144
+
145
+ ```tsx
146
+ import { Style } from '@acusti/styling';
147
+
148
+ function Card({ title, children }) {
149
+ return (
150
+ <>
151
+ <Style>
152
+ {`
153
+ .card {
154
+ border: 1px solid #e0e0e0;
155
+ border-radius: 8px;
156
+ padding: 16px;
157
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
158
+ background: white;
159
+ }
160
+ .card-title {
161
+ margin: 0 0 12px 0;
162
+ font-size: 18px;
163
+ font-weight: 600;
164
+ color: #333;
165
+ }
166
+ .card-content {
167
+ color: #666;
168
+ line-height: 1.5;
169
+ }
170
+ `}
171
+ </Style>
172
+
173
+ <div className="card">
174
+ <h3 className="card-title">{title}</h3>
175
+ <div className="card-content">{children}</div>
176
+ </div>
177
+ </>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ### Global Styles with System Fonts
183
+
184
+ ```tsx
185
+ import { Style, SYSTEM_UI_FONT } from '@acusti/styling';
186
+
187
+ function GlobalStyles() {
188
+ return (
189
+ <Style precedence="low" href="global-styles">
190
+ {`
191
+ * {
192
+ box-sizing: border-box;
193
+ }
194
+
195
+ body {
196
+ margin: 0;
197
+ font-family: ${SYSTEM_UI_FONT};
198
+ line-height: 1.5;
199
+ color: #333;
200
+ background: #fff;
201
+ }
202
+
203
+ button {
204
+ font-family: inherit;
205
+ cursor: pointer;
206
+ }
207
+
208
+ input, textarea {
209
+ font-family: inherit;
210
+ }
211
+ `}
212
+ </Style>
213
+ );
214
+ }
215
+ ```
216
+
217
+ ### Theme-Based Styling
218
+
219
+ ```tsx
220
+ import { Style } from '@acusti/styling';
221
+
222
+ function ThemedButton({ theme = 'primary', children }) {
223
+ const themes = {
224
+ primary: {
225
+ bg: '#007bff',
226
+ hoverBg: '#0056b3',
227
+ color: 'white',
228
+ },
229
+ secondary: {
230
+ bg: '#6c757d',
231
+ hoverBg: '#545b62',
232
+ color: 'white',
233
+ },
234
+ success: {
235
+ bg: '#28a745',
236
+ hoverBg: '#1e7e34',
237
+ color: 'white',
238
+ },
239
+ };
240
+
241
+ const currentTheme = themes[theme] || themes.primary;
242
+
243
+ return (
244
+ <>
245
+ <Style href={`button-${theme}`}>
246
+ {`
247
+ .btn-${theme} {
248
+ background: ${currentTheme.bg};
249
+ color: ${currentTheme.color};
250
+ border: none;
251
+ padding: 8px 16px;
252
+ border-radius: 4px;
253
+ font-size: 14px;
254
+ font-weight: 500;
255
+ cursor: pointer;
256
+ transition: background-color 0.2s;
257
+ }
258
+ .btn-${theme}:hover {
259
+ background: ${currentTheme.hoverBg};
260
+ }
261
+ .btn-${theme}:focus {
262
+ outline: 2px solid ${currentTheme.bg};
263
+ outline-offset: 2px;
264
+ }
265
+ `}
266
+ </Style>
267
+
268
+ <button className={`btn-${theme}`}>{children}</button>
269
+ </>
270
+ );
271
+ }
272
+ ```
273
+
274
+ ### Dynamic Styles
275
+
276
+ ```tsx
277
+ import { Style } from '@acusti/styling';
278
+ import { useState } from 'react';
279
+
280
+ function DynamicCard() {
281
+ const [isExpanded, setIsExpanded] = useState(false);
282
+ const [accentColor, setAccentColor] = useState('#007bff');
283
+
284
+ return (
285
+ <>
286
+ <Style href={`dynamic-card-${accentColor}`}>
287
+ {`
288
+ .dynamic-card {
289
+ border: 2px solid ${accentColor};
290
+ border-radius: 8px;
291
+ padding: 16px;
292
+ transition: all 0.3s ease;
293
+ cursor: pointer;
294
+ background: white;
295
+ }
296
+
297
+ .dynamic-card:hover {
298
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
299
+ transform: translateY(-2px);
300
+ }
301
+
302
+ .dynamic-card.expanded {
303
+ background: ${accentColor}10;
304
+ }
305
+
306
+ .card-header {
307
+ color: ${accentColor};
308
+ font-weight: 600;
309
+ margin-bottom: 8px;
310
+ }
311
+
312
+ .card-controls {
313
+ margin-top: 16px;
314
+ display: flex;
315
+ gap: 8px;
316
+ align-items: center;
317
+ }
318
+ `}
319
+ </Style>
320
+
321
+ <div
322
+ className={`dynamic-card ${isExpanded ? 'expanded' : ''}`}
323
+ onClick={() => setIsExpanded(!isExpanded)}
324
+ >
325
+ <div className="card-header">
326
+ Dynamic Card{' '}
327
+ {isExpanded ? '(Expanded)' : '(Collapsed)'}
328
+ </div>
329
+
330
+ <p>Click to toggle expansion state</p>
331
+
332
+ {isExpanded && (
333
+ <div className="card-controls">
334
+ <label>Accent Color:</label>
335
+ <input
336
+ type="color"
337
+ value={accentColor}
338
+ onChange={(e) =>
339
+ setAccentColor(e.target.value)
340
+ }
341
+ onClick={(e) => e.stopPropagation()}
342
+ />
343
+ </div>
344
+ )}
345
+ </div>
346
+ </>
347
+ );
348
+ }
349
+ ```
350
+
351
+ ### Critical CSS with High Precedence
352
+
353
+ ```tsx
354
+ import { Style } from '@acusti/styling';
355
+
356
+ function CriticalStyles() {
357
+ return (
358
+ <Style precedence="high" href="critical">
359
+ {`
360
+ /* Critical above-the-fold styles */
361
+ .hero {
362
+ height: 100vh;
363
+ display: flex;
364
+ align-items: center;
365
+ justify-content: center;
366
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
367
+ color: white;
368
+ text-align: center;
369
+ }
370
+
371
+ .hero h1 {
372
+ font-size: clamp(2rem, 5vw, 4rem);
373
+ margin: 0;
374
+ font-weight: 700;
375
+ }
376
+
377
+ .hero p {
378
+ font-size: clamp(1rem, 3vw, 1.5rem);
379
+ margin: 1rem 0 2rem 0;
380
+ opacity: 0.9;
381
+ }
382
+
383
+ .cta-button {
384
+ background: rgba(255,255,255,0.2);
385
+ border: 2px solid rgba(255,255,255,0.3);
386
+ color: white;
387
+ padding: 12px 24px;
388
+ border-radius: 8px;
389
+ font-size: 16px;
390
+ font-weight: 600;
391
+ cursor: pointer;
392
+ transition: all 0.3s;
393
+ backdrop-filter: blur(10px);
394
+ }
395
+
396
+ .cta-button:hover {
397
+ background: rgba(255,255,255,0.3);
398
+ border-color: rgba(255,255,255,0.5);
399
+ transform: translateY(-2px);
400
+ }
401
+ `}
402
+ </Style>
403
+ );
404
+ }
405
+ ```
406
+
407
+ ### Animation Styles
408
+
409
+ ```tsx
410
+ import { Style } from '@acusti/styling';
411
+
412
+ function AnimatedModal({ isOpen, onClose, children }) {
413
+ return (
414
+ <>
415
+ <Style href="modal-animations">
416
+ {`
417
+ @keyframes fadeIn {
418
+ from { opacity: 0; }
419
+ to { opacity: 1; }
420
+ }
421
+
422
+ @keyframes slideUp {
423
+ from {
424
+ opacity: 0;
425
+ transform: translateY(20px);
426
+ }
427
+ to {
428
+ opacity: 1;
429
+ transform: translateY(0);
430
+ }
431
+ }
432
+
433
+ .modal-overlay {
434
+ position: fixed;
435
+ top: 0;
436
+ left: 0;
437
+ right: 0;
438
+ bottom: 0;
439
+ background: rgba(0,0,0,0.5);
440
+ display: flex;
441
+ align-items: center;
442
+ justify-content: center;
443
+ animation: fadeIn 0.2s ease-out;
444
+ z-index: 1000;
445
+ }
446
+
447
+ .modal-content {
448
+ background: white;
449
+ border-radius: 8px;
450
+ padding: 24px;
451
+ max-width: 500px;
452
+ width: 90%;
453
+ max-height: 80vh;
454
+ overflow: auto;
455
+ animation: slideUp 0.3s ease-out;
456
+ }
457
+
458
+ .modal-close {
459
+ position: absolute;
460
+ top: 16px;
461
+ right: 16px;
462
+ background: none;
463
+ border: none;
464
+ font-size: 24px;
465
+ cursor: pointer;
466
+ color: #666;
467
+ }
468
+ `}
469
+ </Style>
470
+
471
+ {isOpen && (
472
+ <div className="modal-overlay" onClick={onClose}>
473
+ <div
474
+ className="modal-content"
475
+ onClick={(e) => e.stopPropagation()}
476
+ >
477
+ <button className="modal-close" onClick={onClose}>
478
+ ×
479
+ </button>
480
+ {children}
481
+ </div>
482
+ </div>
483
+ )}
484
+ </>
485
+ );
486
+ }
487
+ ```
488
+
489
+ ## Performance Considerations
490
+
491
+ ### Style Caching
492
+
493
+ The component automatically caches minified styles to avoid recomputation:
494
+
495
+ ```tsx
496
+ // These will use the same cached minified styles
497
+ function Button1() {
498
+ return <Style>{buttonStyles}</Style>;
499
+ }
500
+
501
+ function Button2() {
502
+ return <Style>{buttonStyles}</Style>; // Reuses cache
503
+ }
504
+ ```
505
+
506
+ ### Custom href for Better Caching
507
+
508
+ Use custom `href` values for better caching control:
509
+
510
+ ```tsx
511
+ // Good: Uses custom href for consistent caching
512
+ <Style href="button-primary">
513
+ {generateButtonStyles('primary')}
514
+ </Style>
515
+
516
+ // Less optimal: href changes with dynamic content
517
+ <Style>
518
+ {generateButtonStyles(dynamicTheme)}
519
+ </Style>
520
+ ```
521
+
522
+ ### Precedence for DOM Order
523
+
524
+ Control CSS parsing priority with precedence:
525
+
526
+ ```tsx
527
+ // Critical styles load first
528
+ <Style precedence="high" href="critical">
529
+ {criticalStyles}
530
+ </Style>
531
+
532
+ // Component styles load normally
533
+ <Style precedence="medium" href="components">
534
+ {componentStyles}
535
+ </Style>
536
+
537
+ // Non-critical styles load last
538
+ <Style precedence="low" href="animations">
539
+ {animationStyles}
540
+ </Style>
541
+ ```
542
+
543
+ ## CSS Minification
544
+
545
+ The package includes a powerful CSS minifier that:
546
+
547
+ - Removes comments (except `/*!` important comments)
548
+ - Normalizes whitespace
549
+ - Removes unnecessary semicolons
550
+ - Optimizes values (e.g., `0px` → `0`, `#ffffff` → `#fff`)
551
+ - Preserves `calc()` expressions
552
+ - Handles string literals safely
553
+
554
+ ```tsx
555
+ import { minifyStyles } from '@acusti/styling';
556
+
557
+ const original = `
558
+ /* Comment */
559
+ .button {
560
+ background-color: #ffffff;
561
+ padding: 10px 20px;
562
+ border: 1px solid #cccccc;
563
+ margin: 0px 0px 0px 0px;
564
+ }
565
+ `;
566
+
567
+ const minified = minifyStyles(original);
568
+ // ".button{background-color:#fff;padding:10px 20px;border:1px solid #ccc;margin:0}"
569
+ ```
570
+
571
+ ## Best Practices
572
+
573
+ ### Component-Level Styles
574
+
575
+ Keep styles close to their components:
576
+
577
+ ```tsx
578
+ // ✅ Good: Styles are colocated with component
579
+ function Newsletter() {
580
+ return (
581
+ <>
582
+ <Style href="newsletter">{newsletterStyles}</Style>
583
+ <div className="newsletter">...</div>
584
+ </>
585
+ );
586
+ }
587
+ ```
588
+
589
+ ### Global vs Component Styles
590
+
591
+ Use precedence to control loading order:
592
+
593
+ ```tsx
594
+ // ✅ Global styles with low precedence
595
+ function GlobalStyles() {
596
+ return (
597
+ <Style precedence="low" href="global">
598
+ {globalStyles}
599
+ </Style>
600
+ );
601
+ }
602
+
603
+ // ✅ Critical component styles with high precedence
604
+ function Hero() {
605
+ return (
606
+ <Style precedence="high" href="hero">
607
+ {heroStyles}
608
+ </Style>
609
+ );
610
+ }
611
+ ```
612
+
613
+ ### Custom Properties for Theming
614
+
615
+ Use CSS custom properties for dynamic theming:
616
+
617
+ ```tsx
618
+ <Style href="theme">
619
+ {`
620
+ :root {
621
+ --primary-color: ${theme.primary};
622
+ --secondary-color: ${theme.secondary};
623
+ }
624
+
625
+ .button {
626
+ background: var(--primary-color);
627
+ }
628
+ `}
629
+ </Style>
630
+ ```
631
+
632
+ ## Common Use Cases
22
633
 
23
- The CSS minification means that insignifant differences between styles
24
- (e.g. varying whitespace or empty declarations) won’t result in sthyle
25
- duplication. Also, the component maintains an internal global cache of the
26
- minified styles to avoid unnecessary re-calculations.
634
+ - **Component libraries** - Style components without external CSS files
635
+ - **Dynamic themes** - Generate styles based on user preferences
636
+ - **SSR applications** - Avoid hydration issues with server-rendered styles
637
+ - **Critical CSS** - Inline above-the-fold styles for faster rendering
638
+ - **CSS-in-JS alternative** - Use regular CSS with React 19+ benefits
639
+ - **Animation libraries** - Define keyframes and animations inline
640
+ - **Responsive components** - Include media queries with components
27
641
 
28
- Lastly, this package exports useful CSS string literals, such as
29
- `SYSTEM_UI_FONT` which can be used as `font-family: ${SYSTEM_UI_FONT};` to
30
- specify the appropriate UI font for the current OS and browser.
642
+ This package provides a modern, efficient way to handle CSS in React 19+
643
+ applications while maintaining the familiarity and power of traditional
644
+ CSS.
package/dist/Style.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { default as React } from 'react';
2
1
  type Props = {
3
2
  children: string;
4
3
  href?: string;
5
4
  precedence?: string;
6
5
  };
7
- declare const Style: ({ children, href: _href, precedence }: Props) => React.JSX.Element;
6
+ declare const Style: ({ children, href: _href, precedence }: Props) => import("react/jsx-runtime").JSX.Element;
8
7
  export default Style;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { c } from "react/compiler-runtime";
3
- import { useState, useEffect } from "react";
3
+ import { useState, useEffectEvent, useEffect } from "react";
4
4
  function minifyStyles(css) {
5
5
  const preservedTokens = [];
6
6
  const comments = [];
@@ -78,7 +78,7 @@ const EMPTY_STYLES_ITEM = {
78
78
  styles: ""
79
79
  };
80
80
  function useStyles(styles, initialHref) {
81
- const $ = c(7);
81
+ const $ = c(13);
82
82
  let t0;
83
83
  if ($[0] !== initialHref || $[1] !== styles) {
84
84
  t0 = () => {
@@ -108,23 +108,34 @@ function useStyles(styles, initialHref) {
108
108
  }
109
109
  const [stylesItem, setStylesItem] = useState(t0);
110
110
  let t1;
111
- let t2;
112
111
  if ($[3] !== initialHref || $[4] !== styles) {
113
- t1 = () => {
114
- if (!styles) {
112
+ t1 = (key_0) => {
113
+ if (styleCache.get(key_0)) {
115
114
  return;
116
115
  }
117
- const key_0 = initialHref ?? styles;
118
- if (!styleCache.get(key_0)) {
119
- const minified_0 = minifyStyles(styles);
120
- const item_0 = {
121
- href: sanitizeHref(initialHref ?? minified_0),
122
- referenceCount: 1,
123
- styles: minified_0
124
- };
125
- styleCache.set(key_0, item_0);
126
- setStylesItem(item_0);
116
+ const minified_0 = minifyStyles(styles);
117
+ const item_0 = {
118
+ href: sanitizeHref(initialHref ?? minified_0),
119
+ referenceCount: 1,
120
+ styles: minified_0
121
+ };
122
+ styleCache.set(key_0, item_0);
123
+ setStylesItem(item_0);
124
+ };
125
+ $[3] = initialHref;
126
+ $[4] = styles;
127
+ $[5] = t1;
128
+ } else {
129
+ t1 = $[5];
130
+ }
131
+ const handleKeyUpdate = useEffectEvent(t1);
132
+ let t2;
133
+ if ($[6] !== handleKeyUpdate || $[7] !== initialHref || $[8] !== styles) {
134
+ t2 = () => {
135
+ if (!styles) {
136
+ return;
127
137
  }
138
+ handleKeyUpdate(initialHref ?? styles);
128
139
  return () => {
129
140
  const existingItem = styleCache.get(styles);
130
141
  if (existingItem) {
@@ -135,16 +146,23 @@ function useStyles(styles, initialHref) {
135
146
  }
136
147
  };
137
148
  };
138
- t2 = [initialHref, styles];
139
- $[3] = initialHref;
140
- $[4] = styles;
141
- $[5] = t1;
142
- $[6] = t2;
149
+ $[6] = handleKeyUpdate;
150
+ $[7] = initialHref;
151
+ $[8] = styles;
152
+ $[9] = t2;
143
153
  } else {
144
- t1 = $[5];
145
- t2 = $[6];
154
+ t2 = $[9];
155
+ }
156
+ let t3;
157
+ if ($[10] !== initialHref || $[11] !== styles) {
158
+ t3 = [initialHref, styles];
159
+ $[10] = initialHref;
160
+ $[11] = styles;
161
+ $[12] = t3;
162
+ } else {
163
+ t3 = $[12];
146
164
  }
147
- useEffect(t1, t2);
165
+ useEffect(t2, t3);
148
166
  return stylesItem;
149
167
  }
150
168
  function sanitizeHref(text) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/minifyStyles.ts","../src/useStyles.ts","../src/Style.tsx","../src/index.ts"],"sourcesContent":["/**\n * Adapted from:\n * https://github.com/jbleuzen/node-cssmin/blob/master/cssmin.js\n * node-cssmin\n * A simple module for Node.js that minify CSS\n * Author : Johan Bleuzen\n */\n\n/**\n * cssmin.js\n * Author: Stoyan Stefanov - http://phpied.com/\n * This is a JavaScript port of the CSS minification tool\n * distributed with YUICompressor, itself a port\n * of the cssmin utility by Isaac Schlueter - http://foohack.com/\n * Permission is hereby granted to use the JavaScript version under the same\n * conditions as the YUICompressor (original YUICompressor note below).\n */\n\n/*\n * YUI Compressor\n * http://developer.yahoo.com/yui/compressor/\n * Author: Julien Lecomte - http://www.julienlecomte.net/\n * Copyright (c) 2011 Yahoo! Inc. All rights reserved.\n * The copyrights embodied in the content of this file are licensed\n * by Yahoo! Inc. under the BSD (revised) open source license.\n */\n\nexport function minifyStyles(css: string) {\n const preservedTokens: Array<string> = [];\n const comments: Array<string> = [];\n const totalLength = css.length;\n let endIndex = 0,\n placeholder = '',\n startIndex = 0,\n token = '';\n\n // collect all comment blocks...\n while ((startIndex = css.indexOf('/*', startIndex)) >= 0) {\n endIndex = css.indexOf('*/', startIndex + 2);\n if (endIndex < 0) {\n endIndex = totalLength;\n }\n token = css.slice(startIndex + 2, endIndex);\n comments.push(token);\n css =\n css.slice(0, startIndex + 2) +\n '___PRESERVE_CANDIDATE_COMMENT_' +\n (comments.length - 1) +\n '___' +\n css.slice(endIndex);\n startIndex += 2;\n }\n\n // preserve strings so their content doesn't get accidentally minified\n css = css.replace(/(\"([^\\\\\"]|\\\\.|\\\\)*\")|('([^\\\\']|\\\\.|\\\\)*')/g, function (match) {\n const quote = match.substring(0, 1);\n\n match = match.slice(1, -1);\n\n // maybe the string contains a comment-like substring?\n // one, maybe more? put'em back then\n if (match.indexOf('___PRESERVE_CANDIDATE_COMMENT_') >= 0) {\n for (let i = 0, max = comments.length; i < max; i++) {\n match = match.replace(\n '___PRESERVE_CANDIDATE_COMMENT_' + i + '___',\n comments[i],\n );\n }\n }\n\n preservedTokens.push(match);\n return (\n quote + '___PRESERVED_TOKEN_' + (preservedTokens.length - 1) + '___' + quote\n );\n });\n\n // strings are safe, now wrestle the comments\n for (let i = 0, max = comments.length; i < max; i = i + 1) {\n token = comments[i];\n placeholder = '___PRESERVE_CANDIDATE_COMMENT_' + i + '___';\n\n // ! in the first position of the comment means preserve\n // so push to the preserved tokens keeping the !\n if (token.charAt(0) === '!') {\n preservedTokens.push(token);\n css = css.replace(\n placeholder,\n '___PRESERVED_TOKEN_' + (preservedTokens.length - 1) + '___',\n );\n continue;\n }\n\n // otherwise, kill the comment\n css = css.replace('/*' + placeholder + '*/', '');\n }\n\n // Normalize all whitespace strings to single spaces. Easier to work with that way.\n css = css.replace(/\\s+/g, ' ');\n\n // Remove the spaces before the things that should not have spaces before them.\n // But, be careful not to turn \"p :link {...}\" into \"p:link{...}\"\n // Swap out any pseudo-class colons with the token, and then swap back.\n css = css.replace(/(^|\\})(([^{:])+:)+([^{]*\\{)/g, function (m) {\n return m.replace(/:/g, '___PSEUDOCLASSCOLON___');\n });\n\n // Preserve spaces in calc expressions\n css = css.replace(/calc\\s*\\(\\s*(.*?)\\s*\\)/g, function (m, c: string) {\n return m.replace(c, c.replace(/\\s+/g, '___SPACE_IN_CALC___'));\n });\n\n css = css.replace(/\\s+([!{};:>+()\\],])/g, '$1');\n css = css.replace(/___PSEUDOCLASSCOLON___/g, ':');\n\n // no space after the end of a preserved comment\n css = css.replace(/\\*\\/ /g, '*/');\n\n // If there is a @charset, then only allow one, and push to the top of the file.\n css = css.replace(/^(.*)(@charset \"[^\"]*\";)/gi, '$2$1');\n css = css.replace(/^(\\s*@charset [^;]+;\\s*)+/gi, '$1');\n\n // Put the space back in some cases, to support stuff like\n // @media screen and (-webkit-min-device-pixel-ratio:0){\n css = css.replace(/\\band\\(/gi, 'and (');\n\n // Remove the spaces after the things that should not have spaces after them.\n css = css.replace(/([!{}:;>+([,])\\s+/g, '$1');\n\n // Restore preserved spaces in calc expressions\n css = css.replace(/___SPACE_IN_CALC___/g, ' ');\n\n // remove unnecessary semicolons\n css = css.replace(/;+\\}/g, '}');\n\n // Replace 0(px,em,%) with 0.\n css = css.replace(/([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)/gi, '$1$2');\n\n // Replace 0 0 0 0; with 0.\n css = css.replace(/:0 0 0 0(;|\\})/g, ':0$1');\n css = css.replace(/:0 0 0(;|\\})/g, ':0$1');\n css = css.replace(/:0 0(;|\\})/g, ':0$1');\n\n // Replace background-position:0; with background-position:0 0;\n // same for transform-origin\n css = css.replace(\n /(background-position|transform-origin):0(;|\\})/gi,\n function (_all, prop: string, tail: string) {\n return prop.toLowerCase() + ':0 0' + tail;\n },\n );\n\n // Replace 0.6 to .6, but only when preceded by : or a white-space\n css = css.replace(/(:|\\s)0+\\.(\\d+)/g, '$1.$2');\n\n // border: none -> border:0\n css = css.replace(\n /(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|\\})/gi,\n function (_all, prop: string, tail: string) {\n return prop.toLowerCase() + ':0' + tail;\n },\n );\n\n // Remove empty rules.\n css = css.replace(/[^};{/]+\\{\\}/g, '');\n\n // Replace multiple semi-colons in a row by a single one\n // See SF bug #1980989\n css = css.replace(/;;+/g, ';');\n\n // restore preserved comments and strings\n for (let i = 0, max = preservedTokens.length; i < max; i = i + 1) {\n css = css.replace('___PRESERVED_TOKEN_' + i + '___', preservedTokens[i]);\n }\n\n return css.trim();\n}\n","import { useEffect, useState } from 'react';\n\nimport { minifyStyles } from './minifyStyles.js';\n\ntype StyleCache = Map<string, { href: string; referenceCount: number; styles: string }>;\n\nconst styleCache: StyleCache = new Map();\n\nconst EMPTY_STYLES_ITEM = { href: '', referenceCount: 0, styles: '' };\n\nexport function useStyles(styles: string, initialHref?: string) {\n const [stylesItem, setStylesItem] = useState(() => {\n if (!styles) return EMPTY_STYLES_ITEM;\n\n const key = initialHref ?? styles;\n let item = styleCache.get(key);\n\n if (item) {\n item.referenceCount++;\n } else {\n const minified = minifyStyles(styles);\n item = {\n href: sanitizeHref(initialHref ?? minified),\n referenceCount: 1,\n styles: minified,\n };\n styleCache.set(key, item);\n }\n\n return item;\n });\n\n useEffect(() => {\n if (!styles) return;\n\n const key = initialHref ?? styles;\n\n if (!styleCache.get(key)) {\n const minified = minifyStyles(styles);\n const item = {\n href: sanitizeHref(initialHref ?? minified),\n referenceCount: 1,\n styles: minified,\n };\n styleCache.set(key, item);\n setStylesItem(item);\n }\n\n return () => {\n const existingItem = styleCache.get(styles);\n if (existingItem) {\n existingItem.referenceCount--;\n if (!existingItem.referenceCount) {\n // TODO try scheduling this via setTimeout\n // and add another referenceCount check\n // to deal with instance where existing <Style>\n // component is moved in the tree or re-keyed\n styleCache.delete(styles);\n }\n }\n };\n }, [initialHref, styles]);\n\n return stylesItem;\n}\n\nexport default useStyles;\n\n// Dashes in selectors in href prop create happy-dom / jsdom test errors:\n// Invalid regular expression (“Range out of order in character class”)\n// Spaces create react errors: React expected the `href` prop for a <style> tag\n// opting into hoisting semantics using the `precedence` prop to not have any\n// spaces but ecountered spaces instead. using spaces in this prop will cause\n// hydration of this style to fail on the client.\nfunction sanitizeHref(text: string) {\n return text.replace(/[- ]/g, '');\n}\n\n// The following export is for test usage only\nexport const getStyleCache = () => styleCache;\n","import React from 'react';\n\nimport { useStyles } from './useStyles.js';\n\ntype Props = {\n children: string;\n href?: string;\n precedence?: string;\n};\n\nconst Style = ({ children, href: _href, precedence = 'medium' }: Props) => {\n // Minify CSS styles to prevent unnecessary cache misses\n const { href, styles } = useStyles(children, _href);\n\n return (\n <style href={href} precedence={precedence}>\n {styles}\n </style>\n );\n};\n\nexport default Style;\n","export { minifyStyles } from './minifyStyles.js';\nexport { default as Style } from './Style.js';\n\nexport const SYSTEM_UI_FONT =\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif';\n"],"names":["minifyStyles","css","preservedTokens","comments","totalLength","length","endIndex","placeholder","startIndex","token","indexOf","slice","push","replace","match","quote","substring","i","max","charAt","m","c","_all","prop","tail","toLowerCase","trim","styleCache","Map","EMPTY_STYLES_ITEM","href","referenceCount","styles","useStyles","initialHref","$","_c","t0","key","item","get","minified","set","stylesItem","setStylesItem","useState","t1","t2","key_0","minified_0","item_0","sanitizeHref","existingItem","delete","useEffect","text","Style","children","_href","precedence","undefined","SYSTEM_UI_FONT"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/minifyStyles.ts","../src/useStyles.ts","../src/Style.tsx","../src/index.ts"],"sourcesContent":["/**\n * Adapted from:\n * https://github.com/jbleuzen/node-cssmin/blob/master/cssmin.js\n * node-cssmin\n * A simple module for Node.js that minify CSS\n * Author : Johan Bleuzen\n */\n\n/**\n * cssmin.js\n * Author: Stoyan Stefanov - http://phpied.com/\n * This is a JavaScript port of the CSS minification tool\n * distributed with YUICompressor, itself a port\n * of the cssmin utility by Isaac Schlueter - http://foohack.com/\n * Permission is hereby granted to use the JavaScript version under the same\n * conditions as the YUICompressor (original YUICompressor note below).\n */\n\n/*\n * YUI Compressor\n * http://developer.yahoo.com/yui/compressor/\n * Author: Julien Lecomte - http://www.julienlecomte.net/\n * Copyright (c) 2011 Yahoo! Inc. All rights reserved.\n * The copyrights embodied in the content of this file are licensed\n * by Yahoo! Inc. under the BSD (revised) open source license.\n */\n\nexport function minifyStyles(css: string) {\n const preservedTokens: Array<string> = [];\n const comments: Array<string> = [];\n const totalLength = css.length;\n let endIndex = 0,\n placeholder = '',\n startIndex = 0,\n token = '';\n\n // collect all comment blocks...\n while ((startIndex = css.indexOf('/*', startIndex)) >= 0) {\n endIndex = css.indexOf('*/', startIndex + 2);\n if (endIndex < 0) {\n endIndex = totalLength;\n }\n token = css.slice(startIndex + 2, endIndex);\n comments.push(token);\n css =\n css.slice(0, startIndex + 2) +\n '___PRESERVE_CANDIDATE_COMMENT_' +\n (comments.length - 1) +\n '___' +\n css.slice(endIndex);\n startIndex += 2;\n }\n\n // preserve strings so their content doesn't get accidentally minified\n css = css.replace(/(\"([^\\\\\"]|\\\\.|\\\\)*\")|('([^\\\\']|\\\\.|\\\\)*')/g, function (match) {\n const quote = match.substring(0, 1);\n\n match = match.slice(1, -1);\n\n // maybe the string contains a comment-like substring?\n // one, maybe more? put'em back then\n if (match.indexOf('___PRESERVE_CANDIDATE_COMMENT_') >= 0) {\n for (let i = 0, max = comments.length; i < max; i++) {\n match = match.replace(\n '___PRESERVE_CANDIDATE_COMMENT_' + i + '___',\n comments[i],\n );\n }\n }\n\n preservedTokens.push(match);\n return (\n quote + '___PRESERVED_TOKEN_' + (preservedTokens.length - 1) + '___' + quote\n );\n });\n\n // strings are safe, now wrestle the comments\n for (let i = 0, max = comments.length; i < max; i = i + 1) {\n token = comments[i];\n placeholder = '___PRESERVE_CANDIDATE_COMMENT_' + i + '___';\n\n // ! in the first position of the comment means preserve\n // so push to the preserved tokens keeping the !\n if (token.charAt(0) === '!') {\n preservedTokens.push(token);\n css = css.replace(\n placeholder,\n '___PRESERVED_TOKEN_' + (preservedTokens.length - 1) + '___',\n );\n continue;\n }\n\n // otherwise, kill the comment\n css = css.replace('/*' + placeholder + '*/', '');\n }\n\n // Normalize all whitespace strings to single spaces. Easier to work with that way.\n css = css.replace(/\\s+/g, ' ');\n\n // Remove the spaces before the things that should not have spaces before them.\n // But, be careful not to turn \"p :link {...}\" into \"p:link{...}\"\n // Swap out any pseudo-class colons with the token, and then swap back.\n css = css.replace(/(^|\\})(([^{:])+:)+([^{]*\\{)/g, function (m) {\n return m.replace(/:/g, '___PSEUDOCLASSCOLON___');\n });\n\n // Preserve spaces in calc expressions\n css = css.replace(/calc\\s*\\(\\s*(.*?)\\s*\\)/g, function (m, c: string) {\n return m.replace(c, c.replace(/\\s+/g, '___SPACE_IN_CALC___'));\n });\n\n css = css.replace(/\\s+([!{};:>+()\\],])/g, '$1');\n css = css.replace(/___PSEUDOCLASSCOLON___/g, ':');\n\n // no space after the end of a preserved comment\n css = css.replace(/\\*\\/ /g, '*/');\n\n // If there is a @charset, then only allow one, and push to the top of the file.\n css = css.replace(/^(.*)(@charset \"[^\"]*\";)/gi, '$2$1');\n css = css.replace(/^(\\s*@charset [^;]+;\\s*)+/gi, '$1');\n\n // Put the space back in some cases, to support stuff like\n // @media screen and (-webkit-min-device-pixel-ratio:0){\n css = css.replace(/\\band\\(/gi, 'and (');\n\n // Remove the spaces after the things that should not have spaces after them.\n css = css.replace(/([!{}:;>+([,])\\s+/g, '$1');\n\n // Restore preserved spaces in calc expressions\n css = css.replace(/___SPACE_IN_CALC___/g, ' ');\n\n // remove unnecessary semicolons\n css = css.replace(/;+\\}/g, '}');\n\n // Replace 0(px,em,%) with 0.\n css = css.replace(/([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)/gi, '$1$2');\n\n // Replace 0 0 0 0; with 0.\n css = css.replace(/:0 0 0 0(;|\\})/g, ':0$1');\n css = css.replace(/:0 0 0(;|\\})/g, ':0$1');\n css = css.replace(/:0 0(;|\\})/g, ':0$1');\n\n // Replace background-position:0; with background-position:0 0;\n // same for transform-origin\n css = css.replace(\n /(background-position|transform-origin):0(;|\\})/gi,\n function (_all, prop: string, tail: string) {\n return prop.toLowerCase() + ':0 0' + tail;\n },\n );\n\n // Replace 0.6 to .6, but only when preceded by : or a white-space\n css = css.replace(/(:|\\s)0+\\.(\\d+)/g, '$1.$2');\n\n // border: none -> border:0\n css = css.replace(\n /(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|\\})/gi,\n function (_all, prop: string, tail: string) {\n return prop.toLowerCase() + ':0' + tail;\n },\n );\n\n // Remove empty rules.\n css = css.replace(/[^};{/]+\\{\\}/g, '');\n\n // Replace multiple semi-colons in a row by a single one\n // See SF bug #1980989\n css = css.replace(/;;+/g, ';');\n\n // restore preserved comments and strings\n for (let i = 0, max = preservedTokens.length; i < max; i = i + 1) {\n css = css.replace('___PRESERVED_TOKEN_' + i + '___', preservedTokens[i]);\n }\n\n return css.trim();\n}\n","import { useEffect, useEffectEvent, useState } from 'react';\n\nimport { minifyStyles } from './minifyStyles.js';\n\ntype StyleCache = Map<string, { href: string; referenceCount: number; styles: string }>;\n\nconst styleCache: StyleCache = new Map();\n\nconst EMPTY_STYLES_ITEM = { href: '', referenceCount: 0, styles: '' };\n\nexport function useStyles(styles: string, initialHref?: string) {\n const [stylesItem, setStylesItem] = useState(() => {\n if (!styles) return EMPTY_STYLES_ITEM;\n\n const key = initialHref ?? styles;\n let item = styleCache.get(key);\n\n if (item) {\n item.referenceCount++;\n } else {\n const minified = minifyStyles(styles);\n item = {\n href: sanitizeHref(initialHref ?? minified),\n referenceCount: 1,\n styles: minified,\n };\n styleCache.set(key, item);\n }\n\n return item;\n });\n\n const handleKeyUpdate = useEffectEvent((key: string) => {\n if (styleCache.get(key)) return;\n const minified = minifyStyles(styles);\n const item = {\n href: sanitizeHref(initialHref ?? minified),\n referenceCount: 1,\n styles: minified,\n };\n styleCache.set(key, item);\n setStylesItem(item);\n });\n\n useEffect(() => {\n if (!styles) return;\n\n handleKeyUpdate(initialHref ?? styles);\n return () => {\n const existingItem = styleCache.get(styles);\n if (existingItem) {\n existingItem.referenceCount--;\n if (!existingItem.referenceCount) {\n // TODO try scheduling this via setTimeout\n // and add another referenceCount check\n // to deal with instance where existing <Style>\n // component is moved in the tree or re-keyed\n styleCache.delete(styles);\n }\n }\n };\n }, [initialHref, styles]);\n\n return stylesItem;\n}\n\nexport default useStyles;\n\n// Dashes in selectors in href prop create happy-dom / jsdom test errors:\n// Invalid regular expression (“Range out of order in character class”)\n// Spaces create react errors: React expected the `href` prop for a <style> tag\n// opting into hoisting semantics using the `precedence` prop to not have any\n// spaces but ecountered spaces instead. using spaces in this prop will cause\n// hydration of this style to fail on the client.\nfunction sanitizeHref(text: string) {\n return text.replace(/[- ]/g, '');\n}\n\n// The following export is for test usage only\nexport const getStyleCache = () => styleCache;\n","import { useStyles } from './useStyles.js';\n\ntype Props = {\n children: string;\n href?: string;\n precedence?: string;\n};\n\nconst Style = ({ children, href: _href, precedence = 'medium' }: Props) => {\n // Minify CSS styles to prevent unnecessary cache misses\n const { href, styles } = useStyles(children, _href);\n\n return (\n <style href={href} precedence={precedence}>\n {styles}\n </style>\n );\n};\n\nexport default Style;\n","export { minifyStyles } from './minifyStyles.js';\nexport { default as Style } from './Style.js';\n\nexport const SYSTEM_UI_FONT =\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif';\n"],"names":["minifyStyles","css","preservedTokens","comments","totalLength","length","endIndex","placeholder","startIndex","token","indexOf","slice","push","replace","match","quote","substring","i","max","charAt","m","c","_all","prop","tail","toLowerCase","trim","styleCache","Map","EMPTY_STYLES_ITEM","href","referenceCount","styles","useStyles","initialHref","$","_c","t0","key","item","get","minified","sanitizeHref","set","stylesItem","setStylesItem","useState","t1","key_0","minified_0","item_0","handleKeyUpdate","useEffectEvent","t2","existingItem","delete","t3","useEffect","text","Style","children","_href","precedence","undefined","SYSTEM_UI_FONT"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acusti/styling",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": "./dist/index.js",
@@ -36,19 +36,19 @@
36
36
  },
37
37
  "homepage": "https://github.com/acusti/uikit/tree/main/packages/styling#readme",
38
38
  "devDependencies": {
39
- "@testing-library/dom": "^10.4.0",
40
- "@testing-library/react": "^16.3.0",
39
+ "@testing-library/dom": "^10.4.1",
40
+ "@testing-library/react": "^16.3.2",
41
41
  "@testing-library/user-event": "^14.6.1",
42
- "@types/react": "^19.1.6",
43
- "@vitejs/plugin-react": "^4.5.0",
44
- "babel-plugin-react-compiler": "rc",
45
- "happy-dom": "^17.5.6",
42
+ "@types/react": "^19.2.10",
43
+ "@vitejs/plugin-react": "^5.0.4",
44
+ "babel-plugin-react-compiler": "^1",
45
+ "happy-dom": "^20.4.0",
46
46
  "react": "^19",
47
47
  "react-dom": "^19",
48
- "typescript": "5.8.3",
49
- "unplugin-dts": "^1.0.0-beta.0",
50
- "vite": "^6.3.5",
51
- "vitest": "^3.1.4"
48
+ "typescript": "5.9.2",
49
+ "unplugin-dts": "^1.0.0-beta.6",
50
+ "vite": "^7.1.10",
51
+ "vitest": "^3.2.4"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "react": "^19 || ~0.0.0-experimental < 0.0.0-f",