create-prisma-php-app 4.0.0-alpha.19 → 4.0.0-alpha.20

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.
@@ -6,189 +6,340 @@ namespace Lib\PHPX;
6
6
 
7
7
  class TwMerge
8
8
  {
9
- private static $classGroupPatterns = [
10
- // **General Padding classes**
11
- "p" => "/^p-/",
12
- // **Specific Padding classes**
13
- "pt" => "/^pt-/",
14
- "pr" => "/^pr-/",
15
- "pb" => "/^pb-/",
16
- "pl" => "/^pl-/",
17
- "px" => "/^px-/",
18
- "py" => "/^py-/",
19
- // **Margin classes**
20
- "m" => "/^m-/",
21
- "mt" => "/^mt-/",
22
- "mr" => "/^mr-/",
23
- "mb" => "/^mb-/",
24
- "ml" => "/^ml-/",
25
- "mx" => "/^mx-/",
26
- "my" => "/^my-/",
27
- // **Background color classes**
28
- "bg" => "/^bg-/",
29
- // **Text size classes**
30
- "text-size" => '/^text-(xs|sm|base|lg|xl|[2-9]xl)$/',
31
- // **Text alignment classes**
32
- "text-alignment" => '/^text-(left|center|right|justify)$/',
33
- // **Text color classes**
34
- "text-color" => '/^text-(?!xs$|sm$|base$|lg$|xl$|[2-9]xl$).+$/',
35
- // **Text transform classes**
36
- "text-transform" => '/^text-(uppercase|lowercase|capitalize|normal-case)$/',
37
- // **Text decoration classes**
38
- "text-decoration" => '/^text-(underline|line-through|no-underline)$/',
39
- // **Border width classes**
40
- "border-width" => '/^border(-[0-9]+)?$/',
41
- // **Border color classes**
42
- "border-color" => "/^border-(?![0-9])/",
43
- // **Border radius classes**
44
- "rounded" => '/^rounded(-.*)?$/',
45
- // **Font weight classes**
46
- "font" => '/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/',
47
- // **Hover background color classes**
48
- "hover:bg" => "/^hover:bg-/",
49
- // **Hover text color classes**
50
- "hover:text" => "/^hover:text-/",
51
- // **Transition classes**
52
- "transition" => '/^transition(-[a-z]+)?$/',
53
- // **Opacity classes**
54
- "opacity" => '/^opacity(-[0-9]+)?$/',
55
- // **Flexbox alignment classes**
56
- "justify" => "/^justify-(start|end|center|between|around|evenly)$/",
57
- // **Flexbox alignment classes**
58
- "items" => "/^items-(start|end|center|baseline|stretch)$/",
59
- // **Width classes**
60
- "w" => "/^w-(full|[0-9]+|\\[.+\\])$/",
61
- // **Max-width classes**
62
- "max-w" => '/^max-w-(full|[0-9]+|\\[.+\\]|[a-zA-Z]+)$/',
63
- // **Other utility classes can be added here**
9
+ private static array $classGroups = [
10
+ // Layout
11
+ 'aspect' => ['aspect-auto', 'aspect-square', 'aspect-video', '/^aspect-\[.+\]$/'],
12
+ 'container' => ['container'],
13
+ 'columns' => ['/^columns-(\d+|auto|\[.+\])$/'],
14
+ 'break-after' => ['/^break-after-(auto|avoid|all|avoid-page|page|left|right|column)$/'],
15
+ 'break-before' => ['/^break-before-(auto|avoid|all|avoid-page|page|left|right|column)$/'],
16
+ 'break-inside' => ['/^break-inside-(auto|avoid|avoid-page|avoid-column)$/'],
17
+ 'box-decoration' => ['/^box-decoration-(clone|slice)$/'],
18
+ 'box' => ['/^box-(border|content)$/'],
19
+ 'display' => ['block', 'inline-block', 'inline', 'flex', 'inline-flex', 'table', 'inline-table', 'table-caption', 'table-cell', 'table-column', 'table-column-group', 'table-footer-group', 'table-header-group', 'table-row-group', 'table-row', 'flow-root', 'grid', 'inline-grid', 'contents', 'list-item', 'hidden'],
20
+ 'float' => ['/^float-(right|left|none)$/'],
21
+ 'clear' => ['/^clear-(left|right|both|none)$/'],
22
+ 'isolation' => ['isolate', 'isolation-auto'],
23
+ 'object-fit' => ['/^object-(contain|cover|fill|none|scale-down)$/'],
24
+ 'object-position' => ['/^object-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top|\[.+\])$/'],
25
+ 'overflow' => ['/^overflow-(auto|hidden|clip|visible|scroll)$/'],
26
+ 'overflow-x' => ['/^overflow-x-(auto|hidden|clip|visible|scroll)$/'],
27
+ 'overflow-y' => ['/^overflow-y-(auto|hidden|clip|visible|scroll)$/'],
28
+ 'overscroll' => ['/^overscroll-(auto|contain|none)$/'],
29
+ 'overscroll-x' => ['/^overscroll-x-(auto|contain|none)$/'],
30
+ 'overscroll-y' => ['/^overscroll-y-(auto|contain|none)$/'],
31
+ 'position' => ['static', 'fixed', 'absolute', 'relative', 'sticky'],
32
+ 'inset' => ['/^inset-(\d+(\.\d+)?|auto|\[.+\]|px|full)$/', '/^-inset-(\d+(\.\d+)?|\[.+\])$/'],
33
+ 'inset-x' => ['/^inset-x-(\d+(\.\d+)?|auto|\[.+\]|px|full)$/', '/^-inset-x-(\d+(\.\d+)?|\[.+\])$/'],
34
+ 'inset-y' => ['/^inset-y-(\d+(\.\d+)?|auto|\[.+\]|px|full)$/', '/^-inset-y-(\d+(\.\d+)?|\[.+\])$/'],
35
+ 'top' => ['/^top-(\d+(\.\d+)?|auto|\[.+\]|px|full)$/', '/^-top-(\d+(\.\d+)?|\[.+\])$/'],
36
+ 'right' => ['/^right-(\d+(\.\d+)?|auto|\[.+\]|px|full)$/', '/^-right-(\d+(\.\d+)?|\[.+\])$/'],
37
+ 'bottom' => ['/^bottom-(\d+(\.\d+)?|auto|\[.+\]|px|full)$/', '/^-bottom-(\d+(\.\d+)?|\[.+\])$/'],
38
+ 'left' => ['/^left-(\d+(\.\d+)?|auto|\[.+\]|px|full)$/', '/^-left-(\d+(\.\d+)?|\[.+\])$/'],
39
+ 'visibility' => ['visible', 'invisible', 'collapse'],
40
+ 'z' => ['/^z-(\d+|auto|\[.+\])$/', '/^-z-(\d+|\[.+\])$/'],
41
+
42
+ // Flexbox & Grid
43
+ 'flex-basis' => ['/^basis-(\d+(\.\d+)?\/\d+|\d+(\.\d+)?|auto|px|full|\[.+\])$/'],
44
+ 'flex-direction' => ['/^flex-(row|row-reverse|col|col-reverse)$/'],
45
+ 'flex-wrap' => ['/^flex-(wrap|wrap-reverse|nowrap)$/'],
46
+ 'flex' => ['/^flex-(1|auto|initial|none|\[.+\])$/'],
47
+ 'flex-grow' => ['/^grow(-0|\[.+\])?$/'],
48
+ 'flex-shrink' => ['/^shrink(-0|\[.+\])?$/'],
49
+ 'order' => ['/^order-(\d+|first|last|none|\[.+\])$/'],
50
+ 'grid-template-columns' => ['/^grid-cols-(\d+|none|subgrid|\[.+\])$/'],
51
+ 'grid-column' => ['/^col-(auto|span-(\d+|full)|\[.+\])$/'],
52
+ 'grid-column-start' => ['/^col-start-(\d+|auto|\[.+\])$/'],
53
+ 'grid-column-end' => ['/^col-end-(\d+|auto|\[.+\])$/'],
54
+ 'grid-template-rows' => ['/^grid-rows-(\d+|none|subgrid|\[.+\])$/'],
55
+ 'grid-row' => ['/^row-(auto|span-(\d+|full)|\[.+\])$/'],
56
+ 'grid-row-start' => ['/^row-start-(\d+|auto|\[.+\])$/'],
57
+ 'grid-row-end' => ['/^row-end-(\d+|auto|\[.+\])$/'],
58
+ 'grid-auto-flow' => ['/^grid-flow-(row|col|dense|row-dense|col-dense)$/'],
59
+ 'grid-auto-columns' => ['/^auto-cols-(auto|min|max|fr|\[.+\])$/'],
60
+ 'grid-auto-rows' => ['/^auto-rows-(auto|min|max|fr|\[.+\])$/'],
61
+ 'gap' => ['/^gap-(\d+(\.\d+)?|px|\[.+\])$/'],
62
+ 'gap-x' => ['/^gap-x-(\d+(\.\d+)?|px|\[.+\])$/'],
63
+ 'gap-y' => ['/^gap-y-(\d+(\.\d+)?|px|\[.+\])$/'],
64
+ 'justify-content' => ['/^justify-(start|end|center|between|around|evenly)$/'],
65
+ 'justify-items' => ['/^justify-items-(start|end|center|stretch)$/'],
66
+ 'justify-self' => ['/^justify-self-(auto|start|end|center|stretch)$/'],
67
+ 'align-content' => ['/^content-(center|start|end|between|around|evenly|baseline|stretch)$/'],
68
+ 'align-items' => ['/^items-(start|end|center|baseline|stretch)$/'],
69
+ 'align-self' => ['/^self-(auto|start|end|center|stretch|baseline)$/'],
70
+ 'place-content' => ['/^place-content-(center|start|end|between|around|evenly|baseline|stretch)$/'],
71
+ 'place-items' => ['/^place-items-(start|end|center|baseline|stretch)$/'],
72
+ 'place-self' => ['/^place-self-(auto|start|end|center|stretch)$/'],
73
+
74
+ // Spacing
75
+ 'p' => ['/^p-(\d+(\.\d+)?|px|\[.+\])$/'],
76
+ 'px' => ['/^px-(\d+(\.\d+)?|px|\[.+\])$/'],
77
+ 'py' => ['/^py-(\d+(\.\d+)?|px|\[.+\])$/'],
78
+ 'pt' => ['/^pt-(\d+(\.\d+)?|px|\[.+\])$/'],
79
+ 'pr' => ['/^pr-(\d+(\.\d+)?|px|\[.+\])$/'],
80
+ 'pb' => ['/^pb-(\d+(\.\d+)?|px|\[.+\])$/'],
81
+ 'pl' => ['/^pl-(\d+(\.\d+)?|px|\[.+\])$/'],
82
+ 'm' => ['/^-?m-(\d+(\.\d+)?|px|auto|\[.+\])$/'],
83
+ 'mx' => ['/^-?mx-(\d+(\.\d+)?|px|auto|\[.+\])$/'],
84
+ 'my' => ['/^-?my-(\d+(\.\d+)?|px|auto|\[.+\])$/'],
85
+ 'mt' => ['/^-?mt-(\d+(\.\d+)?|px|auto|\[.+\])$/'],
86
+ 'mr' => ['/^-?mr-(\d+(\.\d+)?|px|auto|\[.+\])$/'],
87
+ 'mb' => ['/^-?mb-(\d+(\.\d+)?|px|auto|\[.+\])$/'],
88
+ 'ml' => ['/^-?ml-(\d+(\.\d+)?|px|auto|\[.+\])$/'],
89
+ 'space-x' => ['/^-?space-x-(\d+(\.\d+)?|px|reverse|\[.+\])$/'],
90
+ 'space-y' => ['/^-?space-y-(\d+(\.\d+)?|px|reverse|\[.+\])$/'],
91
+
92
+ // Sizing
93
+ 'w' => ['/^w-(?!$).+$/'],
94
+ 'min-w' => ['/^min-w-(?!$).+$/'],
95
+ 'max-w' => ['/^max-w-(?!$).+$/'],
96
+ 'h' => ['/^h-(?!$).+$/'],
97
+ 'min-h' => ['/^min-h-(?!$).+$/'],
98
+ 'max-h' => ['/^max-h-(?!$).+$/'],
99
+ 'size' => ['/^size-(?!$).+$/'],
100
+
101
+ // Typography
102
+ 'font-family' => ['/^font-(sans|serif|mono|\[.+\])$/'],
103
+ 'font-size' => ['/^text-(xs|sm|base|lg|xl|[2-9]xl|\[.+\])$/'],
104
+ 'font-smoothing' => ['antialiased', 'subpixel-antialiased'],
105
+ 'font-style' => ['italic', 'not-italic'],
106
+ 'font-weight' => ['/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black|\[.+\])$/'],
107
+ 'font-variant-numeric' => ['/^(normal-nums|ordinal|slashed-zero|lining-nums|oldstyle-nums|proportional-nums|tabular-nums|diagonal-fractions|stacked-fractions)$/'],
108
+ 'letter-spacing' => ['/^tracking-(tighter|tight|normal|wide|wider|widest|\[.+\])$/'],
109
+ 'line-clamp' => ['/^line-clamp-(\d+|none)$/'],
110
+ 'line-height' => ['/^leading-(\d+(\.\d+)?|none|tight|snug|normal|relaxed|loose|\[.+\])$/'],
111
+ 'list-image' => ['/^list-image-(none|\[.+\])$/'],
112
+ 'list-style-position' => ['/^list-(inside|outside)$/'],
113
+ 'list-style-type' => ['/^list-(none|disc|decimal|\[.+\])$/'],
114
+ 'text-align' => ['/^text-(left|center|right|justify|start|end)$/'],
115
+ 'text-color' => ['/^text-(?!xs$|sm$|base$|lg$|xl$|[2-9]xl$).+$/'],
116
+ 'text-decoration' => ['underline', 'overline', 'line-through', 'no-underline'],
117
+ 'text-decoration-color' => ['/^decoration-(?!auto$|from-font$|\d+$|px$).+$/'],
118
+ 'text-decoration-style' => ['/^decoration-(solid|double|dotted|dashed|wavy)$/'],
119
+ 'text-decoration-thickness' => ['/^decoration-(auto|from-font|\d+|px|\[.+\])$/'],
120
+ 'text-underline-offset' => ['/^underline-offset-(auto|\d+|px|\[.+\])$/'],
121
+ 'text-transform' => ['uppercase', 'lowercase', 'capitalize', 'normal-case'],
122
+ 'text-overflow' => ['truncate', 'text-ellipsis', 'text-clip'],
123
+ 'text-wrap' => ['/^text-(wrap|nowrap|balance|pretty)$/'],
124
+ 'text-indent' => ['/^indent-(\d+(\.\d+)?|px|\[.+\])$/'],
125
+ 'vertical-align' => ['/^align-(baseline|top|middle|bottom|text-top|text-bottom|sub|super|\[.+\])$/'],
126
+ 'whitespace' => ['/^whitespace-(normal|nowrap|pre|pre-line|pre-wrap|break-spaces)$/'],
127
+ 'word-break' => ['/^break-(normal|words|all|keep)$/'],
128
+ 'hyphens' => ['/^hyphens-(none|manual|auto)$/'],
129
+
130
+ // Backgrounds
131
+ 'bg-attachment' => ['/^bg-(fixed|local|scroll)$/'],
132
+ 'bg-clip' => ['/^bg-clip-(border|padding|content|text)$/'],
133
+ 'bg-color' => ['/^bg-(?!fixed$|local$|scroll$|clip-|origin-|no-repeat$|repeat|auto$|cover$|contain$|none$|gradient-to-).+$/'],
134
+ 'bg-origin' => ['/^bg-origin-(border|padding|content)$/'],
135
+ 'bg-position' => ['/^bg-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top|\[.+\])$/'],
136
+ 'bg-repeat' => ['/^bg-(no-repeat|repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/'],
137
+ 'bg-size' => ['/^bg-(auto|cover|contain|\[.+\])$/'],
138
+ 'bg-image' => ['/^bg-(none|gradient-to-(t|tr|r|br|b|bl|l|tl)|\[.+\])$/'],
139
+ 'gradient-from' => ['/^from-.+$/'],
140
+ 'gradient-via' => ['/^via-.+$/'],
141
+ 'gradient-to' => ['/^to-.+$/'],
142
+
143
+ // Borders
144
+ 'rounded' => ['/^rounded(-(\w+))?(-(\d+(\.\d+)?|px|full|\[.+\]))?$/'],
145
+ 'border-w-all' => ['/^border(-(\d+|px|\[.+\]))?$/', '/^border-0$/'],
146
+ 'border-w-x' => ['/^border-x(-(\d+|px|\[.+\]))?$/'],
147
+ 'border-w-y' => ['/^border-y(-(\d+|px|\[.+\]))?$/'],
148
+ 'border-w-t' => ['/^border-t(-(\d+|px|\[.+\]))?$/'],
149
+ 'border-w-r' => ['/^border-r(-(\d+|px|\[.+\]))?$/'],
150
+ 'border-w-b' => ['/^border-b(-(\d+|px|\[.+\]))?$/'],
151
+ 'border-w-l' => ['/^border-l(-(\d+|px|\[.+\]))?$/'],
152
+ 'border-color' => ['/^border(-[trbl])?-.+$/'],
153
+ 'border-style' => ['/^border-(solid|dashed|dotted|double|hidden|none)$/'],
154
+ 'divide-x' => ['/^divide-x(-(\d+|px|reverse|\[.+\]))?$/'],
155
+ 'divide-y' => ['/^divide-y(-(\d+|px|reverse|\[.+\]))?$/'],
156
+ 'divide-color' => ['/^divide-.+$/'],
157
+ 'divide-style' => ['/^divide-(solid|dashed|dotted|double|none)$/'],
158
+ 'outline-w' => ['/^outline(-(\d+|px|\[.+\]))?$/'],
159
+ 'outline-color' => ['/^outline-.+$/'],
160
+ 'outline-style' => ['/^outline-(none|solid|dashed|dotted|double)$/'],
161
+ 'outline-offset' => ['/^outline-offset-(\d+|px|\[.+\])$/'],
162
+ 'ring-w' => ['/^ring(-(\d+|px|inset|\[.+\]))?$/'],
163
+ 'ring-color' => ['/^ring-.+$/'],
164
+ 'ring-offset-w' => ['/^ring-offset-(\d+|px|\[.+\])$/'],
165
+ 'ring-offset-color' => ['/^ring-offset-.+$/'],
166
+
167
+ // Effects
168
+ 'shadow' => ['/^shadow(-(\w+|\[.+\]))?$/'],
169
+ 'shadow-color' => ['/^shadow-.+$/'],
170
+ 'opacity' => ['/^opacity-(\d+|\[.+\])$/'],
171
+ 'mix-blend' => ['/^mix-blend-(normal|multiply|screen|overlay|darken|lighten|color-dodge|color-burn|hard-light|soft-light|difference|exclusion|hue|saturation|color|luminosity|plus-lighter)$/'],
172
+ 'bg-blend' => ['/^bg-blend-(normal|multiply|screen|overlay|darken|lighten|color-dodge|color-burn|hard-light|soft-light|difference|exclusion|hue|saturation|color|luminosity)$/'],
173
+
174
+ // Filters
175
+ 'blur' => ['/^blur(-(\w+|\[.+\]))?$/'],
176
+ 'brightness' => ['/^brightness-(\d+|\[.+\])$/'],
177
+ 'contrast' => ['/^contrast-(\d+|\[.+\])$/'],
178
+ 'drop-shadow' => ['/^drop-shadow(-(\w+|\[.+\]))?$/'],
179
+ 'grayscale' => ['/^grayscale(-(\d+|\[.+\]))?$/'],
180
+ 'hue-rotate' => ['/^hue-rotate-(\d+|\[.+\])$/'],
181
+ 'invert' => ['/^invert(-(\d+|\[.+\]))?$/'],
182
+ 'saturate' => ['/^saturate-(\d+|\[.+\])$/'],
183
+ 'sepia' => ['/^sepia(-(\d+|\[.+\]))?$/'],
184
+ 'backdrop-blur' => ['/^backdrop-blur(-(\w+|\[.+\]))?$/'],
185
+ 'backdrop-brightness' => ['/^backdrop-brightness-(\d+|\[.+\])$/'],
186
+ 'backdrop-contrast' => ['/^backdrop-contrast-(\d+|\[.+\])$/'],
187
+ 'backdrop-grayscale' => ['/^backdrop-grayscale(-(\d+|\[.+\]))?$/'],
188
+ 'backdrop-hue-rotate' => ['/^backdrop-hue-rotate-(\d+|\[.+\])$/'],
189
+ 'backdrop-invert' => ['/^backdrop-invert(-(\d+|\[.+\]))?$/'],
190
+ 'backdrop-opacity' => ['/^backdrop-opacity-(\d+|\[.+\])$/'],
191
+ 'backdrop-saturate' => ['/^backdrop-saturate-(\d+|\[.+\])$/'],
192
+ 'backdrop-sepia' => ['/^backdrop-sepia(-(\d+|\[.+\]))?$/'],
193
+
194
+ // Transitions & Animation
195
+ 'transition-property' => ['/^transition(-(\w+|\[.+\]))?$/'],
196
+ 'transition-duration' => ['/^duration-(\d+|\[.+\])$/'],
197
+ 'transition-timing' => ['/^ease-(linear|in|out|in-out|\[.+\])$/'],
198
+ 'transition-delay' => ['/^delay-(\d+|\[.+\])$/'],
199
+ 'animate' => ['/^animate-(none|spin|ping|pulse|bounce|\[.+\])$/'],
200
+
201
+ // Transforms
202
+ 'scale' => ['/^scale(-[xy])?-(\d+|\[.+\])$/'],
203
+ 'rotate' => ['/^-?rotate-(\d+|\[.+\])$/'],
204
+ 'translate-x' => ['/^-?translate-x-(\d+(\.\d+)?\/\d+|\d+(\.\d+)?|px|full|\[.+\])$/'],
205
+ 'translate-y' => ['/^-?translate-y-(\d+(\.\d+)?\/\d+|\d+(\.\d+)?|px|full|\[.+\])$/'],
206
+ 'skew-x' => ['/^-?skew-x-(\d+|\[.+\])$/'],
207
+ 'skew-y' => ['/^-?skew-y-(\d+|\[.+\])$/'],
208
+ 'transform-origin' => ['/^origin-(center|top|top-right|right|bottom-right|bottom|bottom-left|left|top-left|\[.+\])$/'],
209
+
210
+ // Interactivity
211
+ 'accent' => ['/^accent-.+$/'],
212
+ 'appearance' => ['/^appearance-(none|auto)$/'],
213
+ 'cursor' => ['/^cursor-(auto|default|pointer|wait|text|move|help|not-allowed|none|context-menu|progress|cell|crosshair|vertical-text|alias|copy|no-drop|grab|grabbing|all-scroll|col-resize|row-resize|n-resize|e-resize|s-resize|w-resize|ne-resize|nw-resize|se-resize|sw-resize|ew-resize|ns-resize|nesw-resize|nwse-resize|zoom-in|zoom-out|\[.+\])$/'],
214
+ 'caret-color' => ['/^caret-.+$/'],
215
+ 'pointer-events' => ['/^pointer-events-(none|auto)$/'],
216
+ 'resize' => ['/^resize(-none|-y|-x)?$/'],
217
+ 'scroll-behavior' => ['/^scroll-(auto|smooth)$/'],
218
+ 'scroll-m' => ['/^scroll-m[trbl]?x?y?-(\d+(\.\d+)?|px|\[.+\])$/'],
219
+ 'scroll-p' => ['/^scroll-p[trbl]?x?y?-(\d+(\.\d+)?|px|\[.+\])$/'],
220
+ 'scroll-snap-align' => ['/^snap-(start|end|center|align-none)$/'],
221
+ 'scroll-snap-stop' => ['/^snap-(normal|always)$/'],
222
+ 'scroll-snap-type' => ['/^snap-(none|x|y|both|mandatory|proximity)$/'],
223
+ 'touch' => ['/^touch-(auto|none|pan-x|pan-left|pan-right|pan-y|pan-up|pan-down|pinch-zoom|manipulation)$/'],
224
+ 'user-select' => ['/^select-(none|text|all|auto)$/'],
225
+ 'will-change' => ['/^will-change-(auto|scroll|contents|transform|\[.+\])$/'],
64
226
  ];
65
227
 
66
- private static $conflictGroups = [
67
- // **Padding conflict groups**
68
- "p" => ["p", "px", "py", "pt", "pr", "pb", "pl"],
69
- "px" => ["px", "pl", "pr"],
70
- "py" => ["py", "pt", "pb"],
71
- "pt" => ["pt"],
72
- "pr" => ["pr"],
73
- "pb" => ["pb"],
74
- "pl" => ["pl"],
75
- // **Margin conflict groups**
76
- "m" => ["m", "mx", "my", "mt", "mr", "mb", "ml"],
77
- "mx" => ["mx", "ml", "mr"],
78
- "my" => ["my", "mt", "mb"],
79
- "mt" => ["mt"],
80
- "mr" => ["mr"],
81
- "mb" => ["mb"],
82
- "ml" => ["ml"],
83
- // **Border width conflict group**
84
- "border-width" => ["border-width"],
85
- // **Border color conflict group**
86
- "border-color" => ["border-color"],
87
- // **Text size conflict group**
88
- "text-size" => ["text-size"],
89
- // **Text color conflict group**
90
- "text-color" => ["text-color"],
91
- // **Text alignment conflict group**
92
- "text-alignment" => ["text-alignment"],
93
- // **Text transform conflict group**
94
- "text-transform" => ["text-transform"],
95
- // **Text decoration conflict group**
96
- "text-decoration" => ["text-decoration"],
97
- // **Opacity conflict group**
98
- "opacity" => ["opacity"],
99
- // **Flexbox alignment conflict groups**
100
- "justify" => ["justify"],
101
- // **Flexbox alignment conflict group**
102
- "items" => ["items"],
103
- // **Width conflict group**
104
- "w" => ["w"],
105
- // **Max-width conflict group**
106
- "max-w" => ["max-w"],
107
- // **Add other conflict groups as needed**
228
+ private static array $conflictingClassGroups = [
229
+ 'overflow' => ['overflow-x', 'overflow-y'],
230
+ 'overscroll' => ['overscroll-x', 'overscroll-y'],
231
+ 'inset' => ['inset-x', 'inset-y', 'top', 'right', 'bottom', 'left'],
232
+ 'inset-x' => ['right', 'left'],
233
+ 'inset-y' => ['top', 'bottom'],
234
+ 'flex' => ['basis', 'grow', 'shrink'],
235
+ 'gap' => ['gap-x', 'gap-y'],
236
+ 'p' => ['px', 'py', 'pt', 'pr', 'pb', 'pl'],
237
+ 'px' => ['pr', 'pl'],
238
+ 'py' => ['pt', 'pb'],
239
+ 'm' => ['mx', 'my', 'mt', 'mr', 'mb', 'ml'],
240
+ 'mx' => ['mr', 'ml'],
241
+ 'my' => ['mt', 'mb'],
242
+ 'font-size' => ['line-height'],
243
+ 'bg-color' => ['bg-color'],
244
+ 'text-color' => ['text-color'],
245
+ 'fvn-normal' => ['fvn-ordinal', 'fvn-slashed-zero', 'fvn-figure', 'fvn-spacing', 'fvn-fraction'],
246
+ 'rounded' => ['rounded-s', 'rounded-e', 'rounded-t', 'rounded-r', 'rounded-b', 'rounded-l', 'rounded-ss', 'rounded-se', 'rounded-ee', 'rounded-es', 'rounded-tl', 'rounded-tr', 'rounded-br', 'rounded-bl'],
247
+ 'rounded-s' => ['rounded-ss', 'rounded-es'],
248
+ 'rounded-e' => ['rounded-se', 'rounded-ee'],
249
+ 'rounded-t' => ['rounded-tl', 'rounded-tr'],
250
+ 'rounded-r' => ['rounded-tr', 'rounded-br'],
251
+ 'rounded-b' => ['rounded-br', 'rounded-bl'],
252
+ 'rounded-l' => ['rounded-tl', 'rounded-bl'],
253
+ 'border-spacing' => ['border-spacing-x', 'border-spacing-y'],
254
+ 'border-w-all' => [],
255
+ 'border-w-x' => ['border-w-all'],
256
+ 'border-w-y' => ['border-w-all'],
257
+ 'border-w-t' => ['border-w-all', 'border-w-y'],
258
+ 'border-w-r' => ['border-w-all', 'border-w-x'],
259
+ 'border-w-b' => ['border-w-all', 'border-w-y'],
260
+ 'border-w-l' => ['border-w-all', 'border-w-x'],
261
+ 'border-color' => ['border-color-t', 'border-color-r', 'border-color-b', 'border-color-l'],
262
+ 'border-color-x' => ['border-color-r', 'border-color-l'],
263
+ 'border-color-y' => ['border-color-t', 'border-color-b'],
264
+ 'scroll-m' => ['scroll-mx', 'scroll-my', 'scroll-mt', 'scroll-mr', 'scroll-mb', 'scroll-ml'],
265
+ 'scroll-mx' => ['scroll-mr', 'scroll-ml'],
266
+ 'scroll-my' => ['scroll-mt', 'scroll-mb'],
267
+ 'scroll-p' => ['scroll-px', 'scroll-py', 'scroll-pt', 'scroll-pr', 'scroll-pb', 'scroll-pl'],
268
+ 'scroll-px' => ['scroll-pr', 'scroll-pl'],
269
+ 'scroll-py' => ['scroll-pt', 'scroll-pb'],
108
270
  ];
109
271
 
110
- /**
111
- * Merges multiple CSS class strings or arrays of CSS class strings into a single, optimized CSS class string.
112
- *
113
- * @param string|array ...$classes The CSS classes to be merged.
114
- * @return string A single CSS class string with duplicates and conflicts resolved.
115
- */
116
- public static function mergeClasses(string|array ...$classes): string
272
+ public static function merge(string|array ...$inputs): string
117
273
  {
118
- $classArray = [];
119
-
120
- foreach ($classes as $class) {
121
- // Handle arrays by flattening them into strings.
122
- $classList = is_array($class) ? $class : [$class];
123
- foreach ($classList as $item) {
124
- if (!empty(trim($item))) {
125
- // Split the classes by any whitespace characters.
126
- $splitClasses = preg_split("/\s+/", $item);
127
- foreach ($splitClasses as $individualClass) {
128
- $classKey = self::getClassGroup($individualClass);
129
-
130
- // If the class is non-responsive (no colon), remove any responsive variants for the same base.
131
- if (strpos($classKey, ':') === false) {
132
- $baseGroup = $classKey;
133
- foreach ($classArray as $existingKey => $existingClass) {
134
-
135
- if (
136
- is_string($existingKey) // make sure we have a string
137
- && $existingKey !== $baseGroup
138
- && substr($existingKey, -strlen($baseGroup)) === $baseGroup
139
- ) {
140
- unset($classArray[$existingKey]);
141
- }
142
- }
143
- }
274
+ $allClasses = [];
144
275
 
145
- // Remove conflicting classes based on the conflict groups.
146
- $conflictingKeys = self::getConflictingKeys($classKey);
147
- foreach ($conflictingKeys as $key) {
148
- unset($classArray[$key]);
149
- }
276
+ foreach ($inputs as $input) {
277
+ if (is_array($input)) {
278
+ $allClasses = array_merge($allClasses, $input);
279
+ } else {
280
+ $classes = preg_split('/\s+/', trim($input));
281
+ $allClasses = array_merge($allClasses, array_filter($classes));
282
+ }
283
+ }
150
284
 
151
- // Update the array, prioritizing the last occurrence.
152
- $classArray[$classKey] = $individualClass;
153
- }
154
- }
285
+ return self::mergeClassList($allClasses);
286
+ }
287
+
288
+ private static function mergeClassList(array $classes): string
289
+ {
290
+ $result = [];
291
+
292
+ foreach ($classes as $originalClass) {
293
+ if (empty(trim($originalClass))) {
294
+ continue;
155
295
  }
296
+
297
+ $classKey = self::getClassGroup($originalClass);
298
+
299
+ $conflictingKeys = self::getConflictingKeys($classKey);
300
+ foreach ($conflictingKeys as $key) {
301
+ unset($result[$key]);
302
+ }
303
+
304
+ $result[$classKey] = $originalClass;
156
305
  }
157
306
 
158
- // Combine the final classes into a single string.
159
- return implode(" ", array_values($classArray));
307
+ return implode(' ', array_values($result));
160
308
  }
161
309
 
162
- private static function getClassGroup($class)
310
+ private static function getClassGroup(string $class): string
163
311
  {
164
- // Match optional prefixes (responsive and variants).
165
312
  $pattern = '/^((?:[^:]+:)*)([^:]+)$/';
166
313
  if (preg_match($pattern, $class, $matches)) {
167
314
  $prefixes = $matches[1];
168
315
  $utilityClass = $matches[2];
169
316
 
170
- // Match the utilityClass against patterns.
171
- foreach (self::$classGroupPatterns as $groupKey => $regex) {
172
- if (preg_match($regex, $utilityClass)) {
173
- return $prefixes . $groupKey;
317
+ foreach (self::$classGroups as $groupKey => $patterns) {
318
+ foreach ($patterns as $pattern) {
319
+ if (is_string($pattern) && str_starts_with($pattern, '/')) {
320
+ if (preg_match($pattern, $utilityClass)) {
321
+ return $prefixes . $groupKey;
322
+ }
323
+ } else {
324
+ if ($pattern === $utilityClass) {
325
+ return $prefixes . $groupKey;
326
+ }
327
+ }
174
328
  }
175
329
  }
176
- // If no match, use the full class.
177
330
  return $prefixes . $utilityClass;
178
331
  }
179
- // For classes without a recognizable prefix, return the class itself.
180
332
  return $class;
181
333
  }
182
334
 
183
- private static function getConflictingKeys($classKey)
335
+ private static function getConflictingKeys(string $classKey): array
184
336
  {
185
- // Remove any responsive or variant prefixes.
186
337
  $baseClassKey = preg_replace("/^(?:[^:]+:)+/", "", $classKey);
187
- if (isset(self::$conflictGroups[$baseClassKey])) {
188
- $prefix = preg_replace("/" . preg_quote($baseClassKey, "/") . '$/', "", $classKey);
338
+ if (isset(self::$conflictingClassGroups[$baseClassKey])) {
339
+ $prefix = preg_replace("/" . preg_quote($baseClassKey, "/") . '$/i', "", $classKey);
189
340
  return array_map(function ($conflict) use ($prefix) {
190
341
  return $prefix . $conflict;
191
- }, self::$conflictGroups[$baseClassKey]);
342
+ }, self::$conflictingClassGroups[$baseClassKey]);
192
343
  }
193
344
  return [$classKey];
194
345
  }
@@ -245,10 +245,10 @@ class TypeCoercer
245
245
 
246
246
  private static function isArrayLike(string $value): bool
247
247
  {
248
- return Validator::json($value) ||
249
- str_contains($value, ',') ||
250
- str_contains($value, '[') ||
251
- str_contains($value, '{');
248
+ return Validator::json($value) === true
249
+ || str_contains($value, ',')
250
+ || str_contains($value, '[')
251
+ || str_contains($value, '{');
252
252
  }
253
253
 
254
254
  private static function getNormalizedType(mixed $value): string
@@ -328,7 +328,7 @@ class TypeCoercer
328
328
  return $value;
329
329
  }
330
330
  if (is_string($value)) {
331
- if (Validator::json($value)) {
331
+ if (Validator::json($value) === true) {
332
332
  $decoded = json_decode($value, true);
333
333
  if (is_array($decoded)) {
334
334
  return $decoded;
@@ -193,8 +193,10 @@ class Request
193
193
  public static string $requestedWith = '';
194
194
 
195
195
  /**
196
- * Initialize the request by setting all static properties.
196
+ * @var string $remoteAddr Holds the remote address of the request.
197
197
  */
198
+ public static string $remoteAddr = '';
199
+
198
200
  public static function init(): void
199
201
  {
200
202
  self::$params = new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);
@@ -222,6 +224,7 @@ class Request
222
224
  self::$localStorage = self::getLocalStorage();
223
225
  self::$protocol = self::getProtocol();
224
226
  self::$documentUrl = self::$protocol . self::$domainName . self::$scriptName;
227
+ self::$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'Unknown';
225
228
  }
226
229
 
227
230
  /**
@@ -0,0 +1,33 @@
1
+ <?php
2
+ namespace Lib\Security;
3
+
4
+ use Lib\Headers\Boom;
5
+
6
+ final class RateLimiter
7
+ {
8
+ /**
9
+ * Lanza HTTP 429 si se supera el máximo de intentos en la ventana dada.
10
+ *
11
+ * @param string $key Identificador único (p. ej. IP o user‑id).
12
+ * @param int $maxAttempts Nº de peticiones permitidas.
13
+ * @param int $seconds Ventana de tiempo en segundos.
14
+ */
15
+ public static function check(string $key, int $maxAttempts = 60, int $seconds = 60): void
16
+ {
17
+ if (!function_exists('apcu_fetch')) {
18
+ // APCu no instalado: conviene registrar un “fallback” o lanzar excepción.
19
+ Boom::internal("APCu extension missing for rate‑limit.")->toResponse();
20
+ }
21
+
22
+ $apcuKey = "ratelimit:{$key}";
23
+ $current = apcu_fetch($apcuKey) ?: 0;
24
+
25
+ if ($current >= $maxAttempts) {
26
+ // HTTP 429 Too Many Requests
27
+ Boom::tooManyRequests('Rate limit exceeded, try again later.')->toResponse();
28
+ }
29
+
30
+ // Incrementa contador y refresca TTL
31
+ apcu_store($apcuKey, $current + 1, $seconds);
32
+ }
33
+ }
@@ -284,15 +284,29 @@ final class Validator
284
284
  // Other Validation
285
285
 
286
286
  /**
287
- * Validate a JSON string.
287
+ * Validate a JSON string or convert an array to a JSON string.
288
288
  *
289
- * @param mixed $value The value to validate.
290
- * @return bool True if valid JSON, false otherwise.
289
+ * This function checks if the input is a valid JSON string. If it is, it returns the string.
290
+ * If the input is an array, it converts it to a JSON string with specific options.
291
+ * If the input is invalid, it returns an error message.
292
+ *
293
+ * @param mixed $value The value to validate or convert.
294
+ * @return string The valid JSON string or an error message if invalid.
291
295
  */
292
- public static function json($value): bool
296
+ public static function json(mixed $value): string
293
297
  {
294
- json_decode($value);
295
- return json_last_error() === JSON_ERROR_NONE;
298
+ if (is_string($value)) {
299
+ json_decode($value);
300
+ if (json_last_error() !== JSON_ERROR_NONE) {
301
+ return json_last_error_msg();
302
+ }
303
+ return $value;
304
+ }
305
+
306
+ return json_encode(
307
+ $value,
308
+ JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
309
+ );
296
310
  }
297
311
 
298
312
  /**