tailwind-to-style 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,8 @@
24
24
  - [`tws()` — Tailwind to Inline Styles](#tws--tailwind-to-inline-styles)
25
25
  - [`twsx()` — CSS-in-JS Engine](#twsx--css-in-js-engine)
26
26
  - [`twsxVariants()` — Component Variant System](#twsxvariants--component-variant-system)
27
+ - [`twsxClassName()` — Unified CSS-in-JS](#twsxclassname--unified-css-in-js)
28
+ - [`tw()` — Atomic CSS Classes](#tw--atomic-css-classes)
27
29
  - [`cx()` — Conditional Class Builder](#cx--conditional-class-builder)
28
30
  - [Configuration & Plugins](#configuration--plugins)
29
31
  - [`configure()` — Custom Theme](#configure--custom-theme)
@@ -52,6 +54,8 @@
52
54
  | **Full Tailwind Support** | All utilities, responsive, pseudo-states, arbitrary values |
53
55
  | **SCSS-like Nesting** | `twsx()` for complex nested selector-based styles |
54
56
  | **Variant System** | Type-safe component variants like CVA/tailwind-variants |
57
+ | **Unified CSS-in-JS** | `twsxClassName()` — one API for basic/variants/slots |
58
+ | **Atomic CSS Classes** | `tw()` — reusable classes with hover/responsive support |
55
59
  | **Conditional Classes** | Built-in `cx()` utility (like clsx/classnames) |
56
60
  | **SSR Support** | Server-side rendering with `startSSR()`/`stopSSR()` |
57
61
  | **@css Directive** | Inject raw CSS for vendor-specific or complex properties |
@@ -389,6 +393,303 @@ btnClass({ 'opacity-50': disabled })
389
393
 
390
394
  ---
391
395
 
396
+ ### `twsxClassName()` — Unified CSS-in-JS
397
+
398
+ The complete CSS-in-JS solution with automatic mode detection. Generates scoped class names with auto-injected CSS from Tailwind classes.
399
+
400
+ ```javascript
401
+ import { twsxClassName, tw } from 'tailwind-to-style'
402
+ ```
403
+
404
+ **Basic Mode** — Simple className generation:
405
+
406
+ ```javascript
407
+ // Returns: "btn-a1b2c3d4" (or "btn" if hash: false)
408
+ const button = twsxClassName({
409
+ name: 'btn',
410
+ _: 'px-4 py-2 bg-blue-500 text-white rounded-lg',
411
+ hover: 'bg-blue-600',
412
+ focus: 'ring-2 ring-blue-500',
413
+ active: 'bg-blue-700',
414
+ dark: 'bg-blue-400',
415
+ })
416
+
417
+ // Usage
418
+ <button class="${button}">Click me</button>
419
+ ```
420
+
421
+ **Variants Mode** — Component variants like CVA/tailwind-variants:
422
+
423
+ ```javascript
424
+ const button = twsxClassName({
425
+ name: 'btn',
426
+ base: 'px-4 py-2 font-medium rounded-lg transition-colors',
427
+ variants: {
428
+ variant: {
429
+ primary: 'bg-blue-500 text-white hover:bg-blue-600',
430
+ secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
431
+ outline: 'border-2 border-blue-500 text-blue-500 hover:bg-blue-50',
432
+ },
433
+ size: {
434
+ sm: 'text-sm px-3 py-1.5',
435
+ md: 'text-base px-4 py-2',
436
+ lg: 'text-lg px-6 py-3',
437
+ },
438
+ disabled: {
439
+ true: 'opacity-50 cursor-not-allowed',
440
+ false: 'cursor-pointer',
441
+ }
442
+ },
443
+ compoundVariants: [
444
+ { variant: 'primary', size: 'lg', class: 'shadow-lg' },
445
+ ],
446
+ defaultVariants: {
447
+ variant: 'primary',
448
+ size: 'md',
449
+ disabled: false,
450
+ },
451
+ })
452
+
453
+ // Usage
454
+ <button class="${button()}">Default</button>
455
+ <button class="${button({ variant: 'secondary', size: 'lg' })}">Large Secondary</button>
456
+ <button class="${button({ disabled: true })}">Disabled</button>
457
+ ```
458
+
459
+ **Slots Mode** — Multi-part components:
460
+
461
+ ```javascript
462
+ const card = twsxClassName({
463
+ name: 'card',
464
+ slots: {
465
+ root: 'bg-white rounded-xl shadow-lg overflow-hidden',
466
+ header: 'px-6 py-4 border-b border-gray-200',
467
+ title: 'text-xl font-bold text-gray-900',
468
+ body: 'px-6 py-4',
469
+ footer: 'px-6 py-4 border-t border-gray-200',
470
+ },
471
+ variants: {
472
+ variant: {
473
+ elevated: { root: 'shadow-2xl' },
474
+ outlined: { root: 'shadow-none border-2 border-gray-200' },
475
+ },
476
+ },
477
+ })
478
+
479
+ // Usage
480
+ const classes = card({ variant: 'elevated' })
481
+ <div class="${classes.root}">
482
+ <div class="${classes.header}">
483
+ <h2 class="${classes.title}">Card Title</h2>
484
+ </div>
485
+ <div class="${classes.body}">Content here</div>
486
+ <div class="${classes.footer}">Footer</div>
487
+ </div>
488
+ ```
489
+
490
+ **Namespace Methods:**
491
+
492
+ | Category | Method | Description |
493
+ |----------|--------|-------------|
494
+ | **Config** | `config(options)` | Set global options (prefix, hash, hashLength) |
495
+ | | `getConfig()` | Get current configuration |
496
+ | **Tokens** | `defineTokens(tokens)` | Define design tokens |
497
+ | | `getTokens()` | Get all defined tokens |
498
+ | | `setToken(path, value)` | Set a single token value |
499
+ | **Themes** | `createTheme(name, tokens)` | Create a named theme |
500
+ | | `setTheme(name)` | Activate a theme |
501
+ | | `getTheme()` | Get active theme name |
502
+ | | `getThemes()` | Get all defined themes |
503
+ | **Extend** | `extend(base, extension)` | Extend an existing config |
504
+ | | `compose(...configs)` | Compose multiple configs |
505
+ | | `merge(...classes)` | Merge class values (like cx) |
506
+ | **Animation** | `defineAnimation(name, config)` | Register custom animation |
507
+ | | `getAnimations()` | Get all registered animations |
508
+ | **SSR** | `getCSS(className)` | Get CSS for a className |
509
+ | | `getAllCSS()` | Get all generated CSS |
510
+ | | `extractCSS()` | Extract as `<style>` tag |
511
+ | **Cache** | `clearCache()` | Clear all caches |
512
+ | | `getCacheStats()` | Get cache statistics |
513
+ | **Utility** | `tw(classes)` | Atomic CSS class generator |
514
+
515
+ ```javascript
516
+ // ═══════════════════════════════════════════════════════════════
517
+ // Configuration
518
+ // ═══════════════════════════════════════════════════════════════
519
+
520
+ // Global configuration
521
+ twsxClassName.config({ prefix: 'my-app', hash: false, hashLength: 8 })
522
+ twsxClassName.getConfig() // → { prefix: 'my-app', hash: false, ... }
523
+
524
+ // ═══════════════════════════════════════════════════════════════
525
+ // Design Tokens
526
+ // ═══════════════════════════════════════════════════════════════
527
+
528
+ // Define tokens
529
+ twsxClassName.defineTokens({
530
+ colors: { primary: '#3b82f6', danger: '#ef4444' },
531
+ spacing: { sm: '0.5rem', md: '1rem' },
532
+ })
533
+
534
+ // Get all tokens
535
+ twsxClassName.getTokens() // → { colors: {...}, spacing: {...} }
536
+
537
+ // Set a single token
538
+ twsxClassName.setToken('colors.success', '#10b981')
539
+
540
+ // Use tokens in styles (use $path.to.token syntax)
541
+ const btn = twsxClassName({ _: 'bg-$colors.primary p-$spacing.md' })
542
+
543
+ // ═══════════════════════════════════════════════════════════════
544
+ // Theme System
545
+ // ═══════════════════════════════════════════════════════════════
546
+
547
+ // Create named themes
548
+ twsxClassName.createTheme('dark', { background: '#1f2937', text: '#f9fafb' })
549
+ twsxClassName.createTheme('light', { background: '#ffffff', text: '#111827' })
550
+
551
+ // Switch themes
552
+ twsxClassName.setTheme('dark')
553
+
554
+ // Get current/all themes
555
+ twsxClassName.getTheme() // → 'dark'
556
+ twsxClassName.getThemes() // → { dark: {...}, light: {...} }
557
+
558
+ // ═══════════════════════════════════════════════════════════════
559
+ // Extend & Compose
560
+ // ═══════════════════════════════════════════════════════════════
561
+
562
+ // Extend existing config
563
+ const iconButton = twsxClassName.extend(button, {
564
+ base: 'p-0 aspect-square',
565
+ variants: { size: { sm: 'w-8 h-8', md: 'w-10 h-10' } },
566
+ })
567
+
568
+ // Compose multiple configs
569
+ const combined = twsxClassName.compose(baseStyles, variants, overrides)
570
+
571
+ // Merge classes (like cx)
572
+ twsxClassName.merge('p-4', isActive && 'bg-blue-500', { 'opacity-50': disabled })
573
+
574
+ // ═══════════════════════════════════════════════════════════════
575
+ // Animations
576
+ // ═══════════════════════════════════════════════════════════════
577
+
578
+ // Define custom animation preset
579
+ twsxClassName.defineAnimation('myFade', {
580
+ keyframes: { '0%': { opacity: 0 }, '100%': { opacity: 1 } },
581
+ duration: '300ms',
582
+ timing: 'ease-out',
583
+ })
584
+
585
+ // Get all registered animations
586
+ twsxClassName.getAnimations() // → { myFade: {...}, ... }
587
+
588
+ // Use in config
589
+ const modal = twsxClassName({
590
+ _: 'fixed inset-0',
591
+ animation: 'myFade', // or built-in: 'fadeIn', 'slideUp', etc.
592
+ })
593
+
594
+ // ═══════════════════════════════════════════════════════════════
595
+ // SSR Support
596
+ // ═══════════════════════════════════════════════════════════════
597
+
598
+ // Get CSS for specific className
599
+ twsxClassName.getCSS('btn-a1b2c3d4')
600
+
601
+ // Get all generated CSS
602
+ twsxClassName.getAllCSS()
603
+
604
+ // Extract as style tag (for SSR)
605
+ const styleTag = twsxClassName.extractCSS()
606
+ // → '<style data-twsx-classname>...</style>'
607
+
608
+ // ═══════════════════════════════════════════════════════════════
609
+ // Cache Management
610
+ // ═══════════════════════════════════════════════════════════════
611
+
612
+ // Clear all caches
613
+ twsxClassName.clearCache()
614
+
615
+ // Get cache statistics
616
+ twsxClassName.getCacheStats()
617
+ // → { classNameCacheSize: 42, cssCacheSize: 38, styleRegistrySize: 15 }
618
+ ```
619
+
620
+ ---
621
+
622
+ ### `tw()` — Atomic CSS Classes
623
+
624
+ Generates reusable atomic CSS classes from Tailwind utilities. Unlike `tws()` which returns inline styles, `tw()` returns class names with auto-injected CSS — supporting pseudo-classes and responsive breakpoints.
625
+
626
+ ```javascript
627
+ import { tw } from 'tailwind-to-style'
628
+ // or: import { twsxClassName } from '...' → twsxClassName.tw(...)
629
+ ```
630
+
631
+ **Basic Usage:**
632
+
633
+ ```javascript
634
+ // Returns: "tw-flex tw-gap-3 tw-items-center"
635
+ tw('flex gap-3 items-center')
636
+
637
+ // Usage
638
+ <div class="${tw('flex gap-3 items-center')}">
639
+ <span>Item 1</span>
640
+ <span>Item 2</span>
641
+ </div>
642
+ ```
643
+
644
+ **With Pseudo-classes:**
645
+
646
+ ```javascript
647
+ // Returns: "tw-bg-gray-100 tw-hover-bg-gray-200 tw-focus-ring-2"
648
+ tw('bg-gray-100 hover:bg-gray-200 focus:ring-2')
649
+
650
+ // Unlike tws(), these actually work!
651
+ <div class="${tw('opacity-0 hover:opacity-100 transition-opacity')}">
652
+ Hover to reveal
653
+ </div>
654
+ ```
655
+
656
+ **With Responsive Breakpoints:**
657
+
658
+ ```javascript
659
+ // Returns: "tw-flex tw-flex-col tw-md-flex-row tw-lg-gap-8"
660
+ tw('flex flex-col md:flex-row lg:gap-8')
661
+
662
+ <div class="${tw('grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4')}">
663
+ <!-- Responsive grid -->
664
+ </div>
665
+ ```
666
+
667
+ **Combining with twsxClassName:**
668
+
669
+ ```javascript
670
+ const button = twsxClassName({
671
+ name: 'btn',
672
+ base: 'px-4 py-2 rounded-lg',
673
+ variants: { color: { primary: 'bg-blue-500', danger: 'bg-red-500' } },
674
+ })
675
+
676
+ // Use tw() for layout, twsxClassName for components
677
+ <div class="${tw('flex gap-3 flex-wrap')}">
678
+ <button class="${button({ color: 'primary' })}">Save</button>
679
+ <button class="${button({ color: 'danger' })}">Delete</button>
680
+ </div>
681
+ ```
682
+
683
+ **When to use which:**
684
+
685
+ | Function | Use Case | Example |
686
+ |----------|----------|---------|
687
+ | `tws()` | Quick inline styles, no pseudo | `style="${tws('p-4 bg-blue')}"` |
688
+ | `tw()` | Reusable layout utilities | `class="${tw('flex gap-3 hover:...')}"` |
689
+ | `twsxClassName` | Components with variants | `class="${button({ size: 'lg' })}"` |
690
+
691
+ ---
692
+
392
693
  ## Configuration & Plugins
393
694
 
394
695
  ### `configure()` — Custom Theme
@@ -506,12 +807,49 @@ const fullHtml = `
506
807
  | `IS_BROWSER` | `true` in browser environment |
507
808
  | `IS_SERVER` | `true` in Node.js/server environment |
508
809
 
810
+ ### Advanced SSR Collector
811
+
812
+ For more control over SSR CSS collection:
813
+
814
+ ```javascript
815
+ import { createSSRCollector } from 'tailwind-to-style'
816
+
817
+ // Create collector with options
818
+ const ssr = createSSRCollector({
819
+ dedupe: true, // Remove duplicate rules
820
+ minify: true, // Minify output CSS
821
+ sort: true, // Sort by specificity
822
+ })
823
+
824
+ // Render app
825
+ const html = renderToString(<App />)
826
+
827
+ // Extract CSS
828
+ const css = ssr.extractRaw() // Raw CSS string
829
+ const styleTag = ssr.extract({ id: 'ssr-css' }) // <style id="ssr-css">...</style>
830
+
831
+ // Or extract critical CSS (above-the-fold)
832
+ const { critical, rest, stats } = ssr.extractCritical({ maxSize: 14000 })
833
+ // critical: CSS under 14KB limit
834
+ // rest: remaining CSS to lazy-load
835
+ // stats: { criticalSize, criticalCount, totalCount }
836
+
837
+ // Utilities
838
+ ssr.peek() // Preview CSS without stopping
839
+ ssr.count // Number of rules collected
840
+ ssr.uniqueCount // Unique rules (after dedupe)
841
+ ssr.clear(true) // Clear and restart collecting
842
+ ssr.getStats() // { ruleCount, uniqueCount, totalSize, minifiedSize }
843
+ ```
844
+
509
845
  ---
510
846
 
511
847
  ## Animation System
512
848
 
513
849
  ### Built-in CSS Animations
514
850
 
851
+ Tailwind animation utilities work out of the box:
852
+
515
853
  ```javascript
516
854
  tws('animate-spin') // → animation: spin 1s linear infinite
517
855
  tws('animate-bounce') // → animation: bounce 1s infinite
@@ -519,32 +857,104 @@ tws('animate-pulse') // → animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) i
519
857
  tws('animate-ping') // → animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite
520
858
  ```
521
859
 
522
- ### Web Animations API
860
+ ### Programmatic Animations (Web Animations API)
861
+
862
+ For interactive animations with full control:
523
863
 
524
864
  ```javascript
525
- import { applyWebAnimation } from 'tailwind-to-style'
865
+ import { animate, chain, stagger, parallel, transition } from 'tailwind-to-style'
866
+
867
+ // Single element animation
868
+ const ctrl = animate(element, 'fadeIn', { duration: 300 })
869
+ ctrl.pause() // Pause
870
+ ctrl.play() // Resume
871
+ ctrl.reverse() // Reverse
872
+ ctrl.cancel() // Cancel
873
+ await ctrl.finished // Wait for completion
874
+
875
+ // Sequential animations
876
+ await chain(element, ['fadeIn', 'slideUp', 'pulse'])
877
+
878
+ // With custom options per step
879
+ await chain(element, [
880
+ 'fadeIn',
881
+ { name: 'slideUp', delay: 200 },
882
+ { name: 'pulse', options: { iterations: 2 } }
883
+ ])
884
+
885
+ // Staggered animations (lists, grids)
886
+ stagger('.card', 'fadeIn', {
887
+ delay: 50, // 50ms between each
888
+ from: 'start', // 'start' | 'end' | 'center' | 'random'
889
+ onAllComplete: () => console.log('Done!')
890
+ })
891
+
892
+ // Parallel animations on multiple elements
893
+ await parallel([el1, el2, el3], 'fadeIn')
526
894
 
527
- // Apply a named animation to a DOM element
528
- applyWebAnimation(element, 'fadeIn')
529
- applyWebAnimation(element, 'slideUp')
895
+ // Transition between states
896
+ transition(element,
897
+ { opacity: 0, transform: 'scale(0.9)' }, // from
898
+ { opacity: 1, transform: 'scale(1)' }, // to
899
+ { duration: 300, easing: 'ease-out' }
900
+ )
530
901
  ```
531
902
 
532
- ### Inline Animations
903
+ ### Animation Presets
533
904
 
534
905
  ```javascript
535
- import { applyInlineAnimation, animateElement, chainAnimations, staggerAnimations } from 'tailwind-to-style'
906
+ import { ANIMATION_PRESETS, EASING } from 'tailwind-to-style'
536
907
 
537
- // Single animation
538
- applyInlineAnimation(element, 'fadeIn')
908
+ // Available presets
909
+ // fadeIn, fadeOut, slideUp, slideDown, slideLeft, slideRight,
910
+ // zoomIn, zoomOut, bounce, shake, pulse, spin, flipX, flipY,
911
+ // enterScale, exitScale, wiggle, heartbeat
539
912
 
540
- // Programmatic animation
541
- animateElement(element, { opacity: [0, 1] }, { duration: 300 })
913
+ // Easing presets
914
+ // linear, ease, easeIn, easeOut, easeInOut,
915
+ // spring, springLight, springMedium, springHeavy,
916
+ // smooth, smoothIn, smoothOut, bounce, elastic
917
+ ```
542
918
 
543
- // Sequential chain
544
- chainAnimations(element, ['fadeIn', 'slideUp', 'bounceIn'])
919
+ ### Custom Keyframes
545
920
 
546
- // Staggered across multiple elements
547
- staggerAnimations('.card', 'fadeIn', { delay: 100 })
921
+ ```javascript
922
+ import { createKeyframes, clearKeyframes, registerPreset } from 'tailwind-to-style'
923
+
924
+ // Create custom keyframes (auto-injects CSS)
925
+ const animationValue = createKeyframes('myFade', {
926
+ '0%': { opacity: 0, transform: 'translateY(-10px)' },
927
+ '100%': { opacity: 1, transform: 'translateY(0)' }
928
+ }, { duration: 400, easing: 'ease-out' })
929
+ // → "myFade 400ms ease-out forwards"
930
+
931
+ // Register as reusable preset
932
+ registerPreset('myFade', {
933
+ keyframes: [{ opacity: 0 }, { opacity: 1 }],
934
+ options: { duration: 400 }
935
+ })
936
+
937
+ // Use it
938
+ animate(element, 'myFade')
939
+
940
+ // Cleanup
941
+ clearKeyframes()
942
+ ```
943
+
944
+ ### Animation Utilities
945
+
946
+ ```javascript
947
+ import {
948
+ cancelAllAnimations,
949
+ getActiveAnimationCount,
950
+ isAnimationSupported,
951
+ getPresetNames
952
+ } from 'tailwind-to-style'
953
+
954
+ cancelAllAnimations() // Stop all running animations
955
+ getActiveAnimationCount() // Number of active animations
956
+ isAnimationSupported() // Check Web Animations API support
957
+ getPresetNames() // List all available preset names
548
958
  ```
549
959
 
550
960
  ---
@@ -555,26 +965,81 @@ Import only what you need to reduce bundle size by **50-70%**:
555
965
 
556
966
  ```javascript
557
967
  // Individual imports (recommended for production)
558
- import { tws } from 'tailwind-to-style/tws' // ~3KB
559
- import { twsx } from 'tailwind-to-style/twsx' // ~6KB
560
- import { twsxVariants } from 'tailwind-to-style/twsx-variants' // ~6KB
561
- import { cx } from 'tailwind-to-style/cx' // <1KB
968
+ import { tws } from 'tailwind-to-style/tws' // ~3KB
969
+ import { twsx } from 'tailwind-to-style/twsx' // ~6KB
970
+ import { twsxVariants } from 'tailwind-to-style/twsx-variants' // ~6KB
971
+ import { twsxClassName, tw } from 'tailwind-to-style/classname' // ~8KB
972
+ import { cx } from 'tailwind-to-style/cx' // <1KB
562
973
 
563
974
  // Full import (everything)
564
- import { tws, twsx, twsxVariants, cx } from 'tailwind-to-style' // ~12KB
975
+ import { tws, twsx, twsxVariants, twsxClassName, cx } from 'tailwind-to-style' // ~15KB
565
976
  ```
566
977
 
567
978
  | Import Path | Includes | Size (minified) |
568
979
  |---|---|---|
569
- | `tailwind-to-style` | Everything | ~12KB |
980
+ | `tailwind-to-style` | Everything | ~15KB |
570
981
  | `tailwind-to-style/tws` | `tws()` only | ~3KB |
571
982
  | `tailwind-to-style/twsx` | `twsx()` | ~6KB |
572
983
  | `tailwind-to-style/twsx-variants` | `twsxVariants()` | ~6KB |
984
+ | `tailwind-to-style/classname` | `twsxClassName()`, `tw()` | ~8KB |
573
985
  | `tailwind-to-style/cx` | `cx()` | <1KB |
574
986
  | `tailwind-to-style/utils` | Logger, LRUCache, error handler | ~2KB |
575
987
 
576
988
  All sub-paths provide ESM + CJS bundles with TypeScript type definitions.
577
989
 
990
+ ### Utility Exports
991
+
992
+ ```javascript
993
+ import {
994
+ // Debounced versions (for rapid updates)
995
+ debouncedTws, // Debounced tws() - 50ms delay
996
+ debouncedTwsx, // Debounced twsx() - 100ms delay
997
+
998
+ // Performance utilities
999
+ performanceUtils,
1000
+
1001
+ // Error handling
1002
+ TwsError,
1003
+ onError,
1004
+ handleError,
1005
+
1006
+ // Cache utilities
1007
+ LRUCache,
1008
+ getTailwindCache,
1009
+ resetTailwindCache,
1010
+
1011
+ // Logging
1012
+ logger,
1013
+ Logger,
1014
+ } from 'tailwind-to-style'
1015
+
1016
+ // Debounced functions - useful for user input
1017
+ const handleInput = (value) => {
1018
+ debouncedTws(`w-[${value}px]`, true) // Won't spam during typing
1019
+ }
1020
+
1021
+ // Error handling
1022
+ const unsubscribe = onError((error) => {
1023
+ console.error('TWS Error:', error.message, error.context)
1024
+ sendToErrorTracking(error)
1025
+ })
1026
+
1027
+ // Custom error
1028
+ const error = handleError(new Error('Invalid class'), { className: 'invalid' })
1029
+ // → TwsError with context and timestamp
1030
+
1031
+ // LRU Cache (bounded Map)
1032
+ const cache = new LRUCache(1000) // Max 1000 items
1033
+ cache.set('key', 'value')
1034
+ cache.get('key') // → 'value'
1035
+ cache.has('key') // → true
1036
+ cache.size // → 1
1037
+
1038
+ // Logger
1039
+ logger.setLevel('debug') // 'debug' | 'info' | 'warn' | 'error' | 'silent'
1040
+ logger.debug('Processing class:', className)
1041
+ ```
1042
+
578
1043
  ---
579
1044
 
580
1045
  ## Preflight CSS