@k9kbdev/roblox-css 0.1.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/LICENSE +15 -0
- package/README.md +245 -0
- package/default.project.json +6 -0
- package/out/index.d.ts +35 -0
- package/out/init.luau +57 -0
- package/out/logger.d.ts +23 -0
- package/out/logger.luau +73 -0
- package/out/primitives/Box.d.ts +23 -0
- package/out/primitives/Box.luau +103 -0
- package/out/primitives/Button.d.ts +62 -0
- package/out/primitives/Button.luau +170 -0
- package/out/primitives/Image.d.ts +37 -0
- package/out/primitives/Image.luau +79 -0
- package/out/primitives/InlineText.d.ts +25 -0
- package/out/primitives/InlineText.luau +273 -0
- package/out/primitives/Input.d.ts +59 -0
- package/out/primitives/Input.luau +126 -0
- package/out/primitives/MotionBox.d.ts +15 -0
- package/out/primitives/MotionBox.luau +69 -0
- package/out/primitives/MotionButton.d.ts +15 -0
- package/out/primitives/MotionButton.luau +146 -0
- package/out/primitives/MotionImage.d.ts +13 -0
- package/out/primitives/MotionImage.luau +70 -0
- package/out/primitives/MotionText.d.ts +12 -0
- package/out/primitives/MotionText.luau +116 -0
- package/out/primitives/MotionUIScale.d.ts +9 -0
- package/out/primitives/MotionUIScale.luau +48 -0
- package/out/primitives/ScrollBox.d.ts +25 -0
- package/out/primitives/ScrollBox.luau +69 -0
- package/out/primitives/Text.d.ts +50 -0
- package/out/primitives/Text.luau +139 -0
- package/out/primitives/usePercentageConstraints.d.ts +3 -0
- package/out/primitives/usePercentageConstraints.luau +112 -0
- package/out/primitives/useVariantResolver.d.ts +13 -0
- package/out/primitives/useVariantResolver.luau +260 -0
- package/out/styles/CSSTypes.d.ts +96 -0
- package/out/styles/ParentSizeContext.d.ts +6 -0
- package/out/styles/ParentSizeContext.luau +13 -0
- package/out/styles/colorParser.d.ts +28 -0
- package/out/styles/colorParser.luau +229 -0
- package/out/styles/dimensionParser.d.ts +49 -0
- package/out/styles/dimensionParser.luau +205 -0
- package/out/styles/gradientParser.d.ts +9 -0
- package/out/styles/gradientParser.luau +434 -0
- package/out/styles/namedColors.d.ts +7 -0
- package/out/styles/namedColors.luau +162 -0
- package/out/styles/transitions.d.ts +18 -0
- package/out/styles/transitions.luau +19 -0
- package/out/styles/webStyle.d.ts +74 -0
- package/out/styles/webStyle.luau +973 -0
- package/out/types.d.ts +4 -0
- package/out/types.luau +3 -0
- package/out/utils/parseInlineImages.d.ts +20 -0
- package/out/utils/parseInlineImages.luau +93 -0
- package/package.json +56 -0
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
--[[
|
|
4
|
+
*
|
|
5
|
+
* webStyle.ts — Core CSS → Roblox translation engine.
|
|
6
|
+
*
|
|
7
|
+
* Takes a CSSProperties object and produces:
|
|
8
|
+
* 1. Roblox instance properties (Size, BackgroundColor3, etc.)
|
|
9
|
+
* 2. Child instances to inject (UICorner, UIPadding, UIListLayout, UIStroke)
|
|
10
|
+
*
|
|
11
|
+
* This is the heart of the middleware — the function that makes
|
|
12
|
+
* <Box style={{ borderRadius: 10 }}> automatically inject a <uicorner>.
|
|
13
|
+
*
|
|
14
|
+
* Translation mapping:
|
|
15
|
+
* CSS Property → Roblox Output
|
|
16
|
+
* ─────────────────────────────────────────────────────────
|
|
17
|
+
* width / height → Size (UDim2)
|
|
18
|
+
* backgroundColor → BackgroundColor3 + BackgroundTransparency
|
|
19
|
+
* opacity → BackgroundTransparency (inverted: 1 - opacity)
|
|
20
|
+
* borderRadius → <uicorner> child (CornerRadius)
|
|
21
|
+
* padding / padding* → <uipadding> child (PaddingTop/Right/Bottom/Left)
|
|
22
|
+
* display: "flex" → <uilistlayout> child (FillDirection, alignment, gap)
|
|
23
|
+
* border → <uistroke> child (Thickness, Color)
|
|
24
|
+
* position: "absolute"→ Position (UDim2) + AnchorPoint (Vector2)
|
|
25
|
+
|
|
26
|
+
]]
|
|
27
|
+
local _dimensionParser = TS.import(script, script.Parent, "dimensionParser")
|
|
28
|
+
local parseDimension = _dimensionParser.parseDimension
|
|
29
|
+
local parsePadding = _dimensionParser.parsePadding
|
|
30
|
+
local parseColor = TS.import(script, script.Parent, "colorParser").parseColor
|
|
31
|
+
local _gradientParser = TS.import(script, script.Parent, "gradientParser")
|
|
32
|
+
local parseGradient = _gradientParser.parseGradient
|
|
33
|
+
local isGradientString = _gradientParser.isGradientString
|
|
34
|
+
local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
|
|
35
|
+
-- Re-export for convenience
|
|
36
|
+
--[[
|
|
37
|
+
*
|
|
38
|
+
* Branded WebStyleResult — the `_parsed` brand ensures only values produced by
|
|
39
|
+
* webStyle() are assignable. A plain `{ props, children }` object will NOT
|
|
40
|
+
* satisfy this type, catching accidental bypasses at compile time.
|
|
41
|
+
*
|
|
42
|
+
* - `props`: Roblox instance properties to spread onto the host element
|
|
43
|
+
* - `children`: UI constraint elements to inject (<uicorner>, <uipadding>, etc.)
|
|
44
|
+
|
|
45
|
+
]]
|
|
46
|
+
--* Helper to construct a branded WebStyleResult inside this module.
|
|
47
|
+
local function makeWebStyleResult(props, children)
|
|
48
|
+
return {
|
|
49
|
+
props = props,
|
|
50
|
+
children = children,
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
--[[
|
|
54
|
+
*
|
|
55
|
+
* Alignment lookup tables — hoisted to module scope to avoid
|
|
56
|
+
* re-creation on every webStyle() call.
|
|
57
|
+
*
|
|
58
|
+
* JUSTIFY_MAP: CSS justifyContent → Roblox HorizontalAlignment
|
|
59
|
+
* "flex-start" → Left
|
|
60
|
+
* "center" → Center
|
|
61
|
+
* "flex-end" → Right
|
|
62
|
+
*
|
|
63
|
+
* ALIGN_MAP: CSS alignItems → Roblox VerticalAlignment
|
|
64
|
+
* "flex-start" → Top
|
|
65
|
+
* "center" → Center
|
|
66
|
+
* "flex-end" → Bottom
|
|
67
|
+
|
|
68
|
+
]]
|
|
69
|
+
local JUSTIFY_MAP = {
|
|
70
|
+
["flex-start"] = Enum.HorizontalAlignment.Left,
|
|
71
|
+
center = Enum.HorizontalAlignment.Center,
|
|
72
|
+
["flex-end"] = Enum.HorizontalAlignment.Right,
|
|
73
|
+
}
|
|
74
|
+
local ALIGN_MAP = {
|
|
75
|
+
["flex-start"] = Enum.VerticalAlignment.Top,
|
|
76
|
+
center = Enum.VerticalAlignment.Center,
|
|
77
|
+
["flex-end"] = Enum.VerticalAlignment.Bottom,
|
|
78
|
+
}
|
|
79
|
+
local FONT_WEIGHT_MAP = {
|
|
80
|
+
normal = Enum.FontWeight.Regular,
|
|
81
|
+
bold = Enum.FontWeight.Bold,
|
|
82
|
+
black = Enum.FontWeight.Heavy,
|
|
83
|
+
}
|
|
84
|
+
local FONT_FAMILY_MAP = {
|
|
85
|
+
BuilderSans = "rbxasset://fonts/families/BuilderSans.json",
|
|
86
|
+
Montserrat = "rbxasset://fonts/families/Montserrat.json",
|
|
87
|
+
}
|
|
88
|
+
local DEFAULT_FONT_FAMILY = "rbxasset://fonts/families/BuilderSans.json"
|
|
89
|
+
local TEXT_Y_ALIGN_MAP = {
|
|
90
|
+
top = Enum.TextYAlignment.Top,
|
|
91
|
+
center = Enum.TextYAlignment.Center,
|
|
92
|
+
bottom = Enum.TextYAlignment.Bottom,
|
|
93
|
+
}
|
|
94
|
+
local AUTO_SIZE_MAP = {
|
|
95
|
+
none = Enum.AutomaticSize.None,
|
|
96
|
+
x = Enum.AutomaticSize.X,
|
|
97
|
+
y = Enum.AutomaticSize.Y,
|
|
98
|
+
xy = Enum.AutomaticSize.XY,
|
|
99
|
+
}
|
|
100
|
+
local SHADOW_SIZE_MAP = {
|
|
101
|
+
sm = UDim2.new(1, 10, 1, 10),
|
|
102
|
+
md = UDim2.new(1, 15, 1, 15),
|
|
103
|
+
lg = UDim2.new(1, 20, 1, 20),
|
|
104
|
+
xl = UDim2.new(1, 30, 1, 30),
|
|
105
|
+
["2xl"] = UDim2.new(1, 40, 1, 40),
|
|
106
|
+
}
|
|
107
|
+
local SHADOW_OFFSET_MAP = {
|
|
108
|
+
sm = 2,
|
|
109
|
+
md = 4,
|
|
110
|
+
lg = 6,
|
|
111
|
+
xl = 10,
|
|
112
|
+
["2xl"] = 15,
|
|
113
|
+
}
|
|
114
|
+
local SHADOW_SLICE_SCALE_MAP = {
|
|
115
|
+
sm = 0.2,
|
|
116
|
+
md = 0.3,
|
|
117
|
+
lg = 0.4,
|
|
118
|
+
xl = 0.6,
|
|
119
|
+
["2xl"] = 0.8,
|
|
120
|
+
}
|
|
121
|
+
local SHADOW_OPACITY_MAP = {
|
|
122
|
+
sm = 0.7,
|
|
123
|
+
md = 0.6,
|
|
124
|
+
lg = 0.5,
|
|
125
|
+
xl = 0.45,
|
|
126
|
+
["2xl"] = 0.4,
|
|
127
|
+
}
|
|
128
|
+
--[[
|
|
129
|
+
*
|
|
130
|
+
* Default shadow asset for boxShadow emulation.
|
|
131
|
+
*
|
|
132
|
+
* This is a 9-slice shadow sprite from the Roblox Creator Marketplace:
|
|
133
|
+
* https://create.roblox.com/store/asset/6015897843
|
|
134
|
+
*
|
|
135
|
+
* SliceCenter is hardcoded to Rect(47, 47, 450, 450) to match this specific
|
|
136
|
+
* asset's padding region. If you change the asset, update SHADOW_SLICE_CENTER
|
|
137
|
+
* accordingly.
|
|
138
|
+
*
|
|
139
|
+
* To use a custom shadow asset project-wide, set `SHADOW_ASSET_ID` and
|
|
140
|
+
* `SHADOW_SLICE_CENTER` before any UI renders.
|
|
141
|
+
|
|
142
|
+
]]
|
|
143
|
+
local SHADOW_ASSET_ID = "rbxassetid://6015897843"
|
|
144
|
+
local SHADOW_SLICE_CENTER = Rect.new(47, 47, 450, 450)
|
|
145
|
+
--[[
|
|
146
|
+
*
|
|
147
|
+
* Builds a <uilistlayout> element from CSS flex properties.
|
|
148
|
+
*
|
|
149
|
+
* CSS → Roblox mapping:
|
|
150
|
+
* flexDirection → FillDirection (Horizontal | Vertical)
|
|
151
|
+
* justifyContent → main-axis alignment (Horizontal for row, Vertical for column)
|
|
152
|
+
* alignItems → cross-axis alignment (opposite axis from justify)
|
|
153
|
+
* gap → Padding (UDim spacing between items)
|
|
154
|
+
*
|
|
155
|
+
* Always sets SortOrder = LayoutOrder so children render in insertion order.
|
|
156
|
+
* Defaults to column (Vertical) when flexDirection is omitted.
|
|
157
|
+
|
|
158
|
+
]]
|
|
159
|
+
local function buildListLayout(style)
|
|
160
|
+
local fillDirection = if style.flexDirection == "row" then Enum.FillDirection.Horizontal else Enum.FillDirection.Vertical
|
|
161
|
+
local layoutProps = {
|
|
162
|
+
FillDirection = fillDirection,
|
|
163
|
+
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
164
|
+
}
|
|
165
|
+
if style.flexWrap == "wrap" then
|
|
166
|
+
layoutProps.Wraps = true
|
|
167
|
+
end
|
|
168
|
+
-- justifyContent → main-axis alignment
|
|
169
|
+
if style.justifyContent ~= nil then
|
|
170
|
+
if style.justifyContent == "space-between" or style.justifyContent == "space-around" then
|
|
171
|
+
local flexAlign = if style.justifyContent == "space-between" then Enum.UIFlexAlignment.SpaceBetween else Enum.UIFlexAlignment.SpaceAround
|
|
172
|
+
if style.flexDirection == "row" then
|
|
173
|
+
layoutProps.HorizontalFlex = flexAlign
|
|
174
|
+
else
|
|
175
|
+
layoutProps.VerticalFlex = flexAlign
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
if style.flexDirection == "row" then
|
|
179
|
+
layoutProps.HorizontalAlignment = JUSTIFY_MAP[style.justifyContent] or Enum.HorizontalAlignment.Left
|
|
180
|
+
else
|
|
181
|
+
layoutProps.VerticalAlignment = ALIGN_MAP[style.justifyContent] or Enum.VerticalAlignment.Top
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
-- alignItems → cross-axis alignment
|
|
186
|
+
if style.alignItems ~= nil then
|
|
187
|
+
if style.alignItems == "stretch" then
|
|
188
|
+
layoutProps.ItemLineAlignment = Enum.ItemLineAlignment.Stretch
|
|
189
|
+
else
|
|
190
|
+
if style.flexDirection == "row" then
|
|
191
|
+
layoutProps.VerticalAlignment = ALIGN_MAP[style.alignItems] or Enum.VerticalAlignment.Top
|
|
192
|
+
else
|
|
193
|
+
layoutProps.HorizontalAlignment = JUSTIFY_MAP[style.alignItems] or Enum.HorizontalAlignment.Left
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
-- gap → Padding (space between items)
|
|
198
|
+
if style.gap ~= nil then
|
|
199
|
+
layoutProps.Padding = parseDimension(style.gap) or UDim.new(0, 0)
|
|
200
|
+
end
|
|
201
|
+
layoutProps.key = "uilistlayout"
|
|
202
|
+
return React.createElement("uilistlayout", layoutProps)
|
|
203
|
+
end
|
|
204
|
+
--[[
|
|
205
|
+
*
|
|
206
|
+
* Builds a <uigridlayout> element from CSS grid properties.
|
|
207
|
+
*
|
|
208
|
+
* CSS → Roblox mapping:
|
|
209
|
+
* gridTemplateColumns → CellSize.X
|
|
210
|
+
* gridTemplateRows → CellSize.Y
|
|
211
|
+
* gap → CellPadding
|
|
212
|
+
* justifyContent → HorizontalAlignment
|
|
213
|
+
* alignItems → VerticalAlignment
|
|
214
|
+
|
|
215
|
+
]]
|
|
216
|
+
local function buildGridLayout(style)
|
|
217
|
+
local layoutProps = {
|
|
218
|
+
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
219
|
+
}
|
|
220
|
+
local fillDirection = if style.flexDirection == "column" then Enum.FillDirection.Vertical else Enum.FillDirection.Horizontal
|
|
221
|
+
layoutProps.FillDirection = fillDirection
|
|
222
|
+
-- gap, rowGap, columnGap -> CellPadding
|
|
223
|
+
local gapX = UDim.new(0, 0)
|
|
224
|
+
local gapY = UDim.new(0, 0)
|
|
225
|
+
local hasGap = false
|
|
226
|
+
if style.gap ~= nil then
|
|
227
|
+
local _gap = style.gap
|
|
228
|
+
local gapParts = if type(_gap) == "string" then string.split(style.gap, " ") else { style.gap }
|
|
229
|
+
gapX = parseDimension(gapParts[1]) or UDim.new(0, 0)
|
|
230
|
+
gapY = if #gapParts > 1 then parseDimension(gapParts[2]) or gapX else gapX
|
|
231
|
+
hasGap = true
|
|
232
|
+
end
|
|
233
|
+
if style.columnGap ~= nil then
|
|
234
|
+
gapX = parseDimension(style.columnGap) or UDim.new(0, 0)
|
|
235
|
+
hasGap = true
|
|
236
|
+
end
|
|
237
|
+
if style.rowGap ~= nil then
|
|
238
|
+
gapY = parseDimension(style.rowGap) or UDim.new(0, 0)
|
|
239
|
+
hasGap = true
|
|
240
|
+
end
|
|
241
|
+
if hasGap then
|
|
242
|
+
layoutProps.CellPadding = UDim2.new(gapX, gapY)
|
|
243
|
+
end
|
|
244
|
+
-- CellSize
|
|
245
|
+
local cellX = if style.gridTemplateColumns ~= nil then parseDimension(style.gridTemplateColumns) or UDim.new(0, 100) else UDim.new(0, 100)
|
|
246
|
+
local cellY = if style.gridTemplateRows ~= nil then parseDimension(style.gridTemplateRows) or UDim.new(0, 100) else UDim.new(0, 100)
|
|
247
|
+
layoutProps.CellSize = UDim2.new(cellX, cellY)
|
|
248
|
+
if style.justifyContent ~= nil then
|
|
249
|
+
layoutProps.HorizontalAlignment = JUSTIFY_MAP[style.justifyContent] or Enum.HorizontalAlignment.Left
|
|
250
|
+
end
|
|
251
|
+
if style.alignItems ~= nil then
|
|
252
|
+
layoutProps.VerticalAlignment = ALIGN_MAP[style.alignItems] or Enum.VerticalAlignment.Top
|
|
253
|
+
end
|
|
254
|
+
layoutProps.key = "uigridlayout"
|
|
255
|
+
return React.createElement("uigridlayout", layoutProps)
|
|
256
|
+
end
|
|
257
|
+
--[[
|
|
258
|
+
*
|
|
259
|
+
* Builds a <uistroke> element from a CSS border shorthand string.
|
|
260
|
+
*
|
|
261
|
+
* Supported format: "<width> <style> <color>"
|
|
262
|
+
* - Width: parsed via parseDimension (e.g. "1px", "2") → Thickness (offset only)
|
|
263
|
+
* - Style: currently ignored (Roblox has no dashed/dotted stroke)
|
|
264
|
+
* - Color: parsed via parseColor (e.g. "#000", "red") → Color (Color3)
|
|
265
|
+
*
|
|
266
|
+
* Examples:
|
|
267
|
+
* "1px solid #ff0000" → Thickness: 1, Color: Color3(1, 0, 0)
|
|
268
|
+
* "2px solid red" → Thickness: 2, Color: Color3(1, 0, 0)
|
|
269
|
+
* "3px" → Thickness: 3, Color: default (no color set)
|
|
270
|
+
|
|
271
|
+
]]
|
|
272
|
+
local function buildStroke(border, isTextStroke)
|
|
273
|
+
if isTextStroke == nil then
|
|
274
|
+
isTextStroke = false
|
|
275
|
+
end
|
|
276
|
+
local hasSpecifyingWord = false
|
|
277
|
+
local specifyingWords = {
|
|
278
|
+
["solid"] = true,
|
|
279
|
+
["dashed"] = true,
|
|
280
|
+
["dotted"] = true,
|
|
281
|
+
["double"] = true,
|
|
282
|
+
["groove"] = true,
|
|
283
|
+
["ridge"] = true,
|
|
284
|
+
["inset"] = true,
|
|
285
|
+
["outset"] = true,
|
|
286
|
+
}
|
|
287
|
+
if not isTextStroke then
|
|
288
|
+
for word in string.gmatch(border, "%a+") do
|
|
289
|
+
local _arg0 = string.lower(word)
|
|
290
|
+
if specifyingWords[_arg0] ~= nil then
|
|
291
|
+
hasSpecifyingWord = true
|
|
292
|
+
break
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
if not hasSpecifyingWord then
|
|
296
|
+
return nil
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
local strokeProps = {}
|
|
300
|
+
-- Extract thickness: find first space-separated token (e.g., "1px", "2")
|
|
301
|
+
local thicknessStr, rest = string.match(border, "^(%S+)%s*(.*)$")
|
|
302
|
+
if thicknessStr ~= nil then
|
|
303
|
+
local dim = parseDimension(thicknessStr)
|
|
304
|
+
if dim ~= nil then
|
|
305
|
+
strokeProps.Thickness = dim.Offset
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
-- After thickness, optionally skip CSS border-style keyword (solid, dashed, etc.)
|
|
309
|
+
-- then treat everything remaining as the color string.
|
|
310
|
+
if rest ~= nil and rest ~= "" then
|
|
311
|
+
local remaining = rest
|
|
312
|
+
-- Try to strip a leading style keyword (solid, dashed, dotted, double, groove, ridge, inset, outset, none)
|
|
313
|
+
local afterStyle = string.match(remaining, "^%s*%a+%s+(.+)$")
|
|
314
|
+
local colorStr = if afterStyle ~= nil then afterStyle else remaining
|
|
315
|
+
local parsed = parseColor(colorStr)
|
|
316
|
+
strokeProps.Color = parsed.color
|
|
317
|
+
end
|
|
318
|
+
strokeProps.ApplyStrokeMode = if isTextStroke then Enum.ApplyStrokeMode.Contextual else Enum.ApplyStrokeMode.Border
|
|
319
|
+
strokeProps.key = if isTextStroke then "uitextstroke" else "uistroke"
|
|
320
|
+
return React.createElement("uistroke", strokeProps)
|
|
321
|
+
end
|
|
322
|
+
--[[
|
|
323
|
+
*
|
|
324
|
+
* Computes Roblox Position (UDim2) and AnchorPoint (Vector2) from CSS
|
|
325
|
+
* absolute positioning properties.
|
|
326
|
+
*
|
|
327
|
+
* CSS → Roblox mapping:
|
|
328
|
+
* left: "10px" → Position.X = UDim(0, 10), AnchorPoint.X = 0
|
|
329
|
+
* right: "10px" → Position.X = UDim(1, -10), AnchorPoint.X = 1
|
|
330
|
+
* top: "50%" → Position.Y = UDim(0.5, 0), AnchorPoint.Y = 0
|
|
331
|
+
* bottom: "20px" → Position.Y = UDim(1, -20), AnchorPoint.Y = 1
|
|
332
|
+
*
|
|
333
|
+
* Priority: left overrides right, top overrides bottom (matching CSS spec).
|
|
334
|
+
* Returns props (not a React element) since positioning modifies the host
|
|
335
|
+
* instance rather than injecting a child constraint.
|
|
336
|
+
|
|
337
|
+
]]
|
|
338
|
+
local function buildAbsolutePosition(style)
|
|
339
|
+
local anchorX = 0
|
|
340
|
+
local anchorY = 0
|
|
341
|
+
local posX = UDim.new(0, 0)
|
|
342
|
+
local posY = UDim.new(0, 0)
|
|
343
|
+
-- Horizontal: left takes priority over right
|
|
344
|
+
if style.left ~= nil then
|
|
345
|
+
posX = parseDimension(style.left) or UDim.new(0, 0)
|
|
346
|
+
anchorX = 0
|
|
347
|
+
elseif style.right ~= nil then
|
|
348
|
+
local dim = parseDimension(style.right) or UDim.new(0, 0)
|
|
349
|
+
posX = UDim.new(1 - dim.Scale, -dim.Offset)
|
|
350
|
+
anchorX = 1
|
|
351
|
+
end
|
|
352
|
+
-- Vertical: top takes priority over bottom
|
|
353
|
+
if style.top ~= nil then
|
|
354
|
+
posY = parseDimension(style.top) or UDim.new(0, 0)
|
|
355
|
+
anchorY = 0
|
|
356
|
+
elseif style.bottom ~= nil then
|
|
357
|
+
local dim = parseDimension(style.bottom) or UDim.new(0, 0)
|
|
358
|
+
posY = UDim.new(1 - dim.Scale, -dim.Offset)
|
|
359
|
+
anchorY = 1
|
|
360
|
+
end
|
|
361
|
+
return {
|
|
362
|
+
Position = UDim2.new(posX, posY),
|
|
363
|
+
AnchorPoint = Vector2.new(anchorX, anchorY),
|
|
364
|
+
}
|
|
365
|
+
end
|
|
366
|
+
--[[
|
|
367
|
+
*
|
|
368
|
+
* Computes Roblox AnchorPoint (Vector2) from CSS transform-origin.
|
|
369
|
+
*
|
|
370
|
+
* CSS → Roblox mapping:
|
|
371
|
+
* "center" → Vector2(0.5, 0.5)
|
|
372
|
+
* "top left" → Vector2(0, 0)
|
|
373
|
+
* "100% 50%" → Vector2(1, 0.5)
|
|
374
|
+
|
|
375
|
+
]]
|
|
376
|
+
local function parseTransformOrigin(origin)
|
|
377
|
+
local _exp = string.split(origin, " ")
|
|
378
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
379
|
+
local _newValue = {}
|
|
380
|
+
local _callback = function(p)
|
|
381
|
+
return p ~= ""
|
|
382
|
+
end
|
|
383
|
+
local _length = 0
|
|
384
|
+
for _k, _v in _exp do
|
|
385
|
+
if _callback(_v, _k - 1, _exp) == true then
|
|
386
|
+
_length += 1
|
|
387
|
+
_newValue[_length] = _v
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
391
|
+
local parts = _newValue
|
|
392
|
+
local x = 0.5
|
|
393
|
+
local y = 0.5
|
|
394
|
+
local parseVal = function(val)
|
|
395
|
+
if val == "left" then
|
|
396
|
+
return 0
|
|
397
|
+
end
|
|
398
|
+
if val == "right" then
|
|
399
|
+
return 1
|
|
400
|
+
end
|
|
401
|
+
if val == "top" then
|
|
402
|
+
return 0
|
|
403
|
+
end
|
|
404
|
+
if val == "bottom" then
|
|
405
|
+
return 1
|
|
406
|
+
end
|
|
407
|
+
if val == "center" then
|
|
408
|
+
return 0.5
|
|
409
|
+
end
|
|
410
|
+
local numStr = string.match(val, "^(%-?%d+%.?%d*)%%?$")
|
|
411
|
+
if numStr ~= nil then
|
|
412
|
+
local num = tonumber(numStr)
|
|
413
|
+
if num ~= nil then
|
|
414
|
+
local hasPercent = string.match(val, "%%$")
|
|
415
|
+
if hasPercent ~= nil then
|
|
416
|
+
return num / 100
|
|
417
|
+
end
|
|
418
|
+
return 0
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
return 0.5
|
|
422
|
+
end
|
|
423
|
+
if #parts == 2 then
|
|
424
|
+
local isYFirst = parts[1] == "top" or parts[1] == "bottom"
|
|
425
|
+
local isXSecond = parts[2] == "left" or parts[2] == "right"
|
|
426
|
+
if isYFirst or isXSecond then
|
|
427
|
+
y = parseVal(parts[1])
|
|
428
|
+
x = parseVal(parts[2])
|
|
429
|
+
else
|
|
430
|
+
x = parseVal(parts[1])
|
|
431
|
+
y = parseVal(parts[2])
|
|
432
|
+
end
|
|
433
|
+
elseif #parts == 1 then
|
|
434
|
+
local part = parts[1]
|
|
435
|
+
if part == "top" or part == "bottom" then
|
|
436
|
+
x = 0.5
|
|
437
|
+
y = parseVal(part)
|
|
438
|
+
else
|
|
439
|
+
x = parseVal(part)
|
|
440
|
+
y = 0.5
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
return Vector2.new(x, y)
|
|
444
|
+
end
|
|
445
|
+
--[[
|
|
446
|
+
*
|
|
447
|
+
* Checks if any constraint property (minWidth, maxWidth, minHeight, maxHeight)
|
|
448
|
+
* contains a percentage value.
|
|
449
|
+
|
|
450
|
+
]]
|
|
451
|
+
local function hasPercentageScale(style)
|
|
452
|
+
local _condition = style.minWidth ~= nil
|
|
453
|
+
if _condition then
|
|
454
|
+
local _result = parseDimension(style.minWidth)
|
|
455
|
+
if _result ~= nil then
|
|
456
|
+
_result = _result.Scale
|
|
457
|
+
end
|
|
458
|
+
local _condition_1 = _result
|
|
459
|
+
if _condition_1 == nil then
|
|
460
|
+
_condition_1 = 0
|
|
461
|
+
end
|
|
462
|
+
_condition = _condition_1 > 0
|
|
463
|
+
end
|
|
464
|
+
if _condition then
|
|
465
|
+
return true
|
|
466
|
+
end
|
|
467
|
+
local _condition_1 = style.maxWidth ~= nil
|
|
468
|
+
if _condition_1 then
|
|
469
|
+
local _result = parseDimension(style.maxWidth)
|
|
470
|
+
if _result ~= nil then
|
|
471
|
+
_result = _result.Scale
|
|
472
|
+
end
|
|
473
|
+
local _condition_2 = _result
|
|
474
|
+
if _condition_2 == nil then
|
|
475
|
+
_condition_2 = 0
|
|
476
|
+
end
|
|
477
|
+
_condition_1 = _condition_2 > 0
|
|
478
|
+
end
|
|
479
|
+
if _condition_1 then
|
|
480
|
+
return true
|
|
481
|
+
end
|
|
482
|
+
local _condition_2 = style.minHeight ~= nil
|
|
483
|
+
if _condition_2 then
|
|
484
|
+
local _result = parseDimension(style.minHeight)
|
|
485
|
+
if _result ~= nil then
|
|
486
|
+
_result = _result.Scale
|
|
487
|
+
end
|
|
488
|
+
local _condition_3 = _result
|
|
489
|
+
if _condition_3 == nil then
|
|
490
|
+
_condition_3 = 0
|
|
491
|
+
end
|
|
492
|
+
_condition_2 = _condition_3 > 0
|
|
493
|
+
end
|
|
494
|
+
if _condition_2 then
|
|
495
|
+
return true
|
|
496
|
+
end
|
|
497
|
+
local _condition_3 = style.maxHeight ~= nil
|
|
498
|
+
if _condition_3 then
|
|
499
|
+
local _result = parseDimension(style.maxHeight)
|
|
500
|
+
if _result ~= nil then
|
|
501
|
+
_result = _result.Scale
|
|
502
|
+
end
|
|
503
|
+
local _condition_4 = _result
|
|
504
|
+
if _condition_4 == nil then
|
|
505
|
+
_condition_4 = 0
|
|
506
|
+
end
|
|
507
|
+
_condition_3 = _condition_4 > 0
|
|
508
|
+
end
|
|
509
|
+
if _condition_3 then
|
|
510
|
+
return true
|
|
511
|
+
end
|
|
512
|
+
return false
|
|
513
|
+
end
|
|
514
|
+
--[[
|
|
515
|
+
*
|
|
516
|
+
* Translates a CSSProperties object into Roblox-compatible props and child elements.
|
|
517
|
+
*
|
|
518
|
+
* This is the primary entry point for the CSS → Roblox translation layer.
|
|
519
|
+
* Wrapper components (Box, Text, etc.) call this function with their `style` prop
|
|
520
|
+
* and spread the resulting `props` onto the host instance while injecting `children`
|
|
521
|
+
* as UI constraint siblings.
|
|
522
|
+
*
|
|
523
|
+
* @param style - A CSSProperties object from the component's style prop.
|
|
524
|
+
* @returns A branded WebStyleResult containing:
|
|
525
|
+
* - `props`: Record of Roblox instance properties (Size, BackgroundColor3, Position, etc.)
|
|
526
|
+
* - `children`: Array of React elements for UI constraints (<uicorner>, <uipadding>, etc.)
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* const result = webStyle({ width: "50%", backgroundColor: "#333", borderRadius: 8 });
|
|
530
|
+
* // result.props → { Size: UDim2(...), BackgroundColor3: Color3(...) }
|
|
531
|
+
* // result.children → [ <uicorner CornerRadius={UDim(0, 8)} /> ]
|
|
532
|
+
|
|
533
|
+
]]
|
|
534
|
+
local function webStyle(style)
|
|
535
|
+
local props = {}
|
|
536
|
+
local children = {}
|
|
537
|
+
-- Implementation order:
|
|
538
|
+
-- 1. width / height → Size (UDim2)
|
|
539
|
+
if style.width ~= nil or style.height ~= nil then
|
|
540
|
+
local widthIsAuto = style.width == "auto"
|
|
541
|
+
local heightIsAuto = style.height == "auto"
|
|
542
|
+
local _result
|
|
543
|
+
if widthIsAuto then
|
|
544
|
+
_result = UDim.new(1, 0)
|
|
545
|
+
else
|
|
546
|
+
local _condition = style.width
|
|
547
|
+
if _condition == nil then
|
|
548
|
+
_condition = "100%"
|
|
549
|
+
end
|
|
550
|
+
local _condition_1 = parseDimension(_condition)
|
|
551
|
+
if _condition_1 == nil then
|
|
552
|
+
_condition_1 = UDim.new(1, 0)
|
|
553
|
+
end
|
|
554
|
+
_result = _condition_1
|
|
555
|
+
end
|
|
556
|
+
local _result_1
|
|
557
|
+
if heightIsAuto then
|
|
558
|
+
_result_1 = UDim.new(1, 0)
|
|
559
|
+
else
|
|
560
|
+
local _condition = style.height
|
|
561
|
+
if _condition == nil then
|
|
562
|
+
_condition = "100%"
|
|
563
|
+
end
|
|
564
|
+
local _condition_1 = parseDimension(_condition)
|
|
565
|
+
if _condition_1 == nil then
|
|
566
|
+
_condition_1 = UDim.new(1, 0)
|
|
567
|
+
end
|
|
568
|
+
_result_1 = _condition_1
|
|
569
|
+
end
|
|
570
|
+
props.Size = UDim2.new(_result, _result_1)
|
|
571
|
+
-- Set AutomaticSize for auto dimensions (only if autoSize wasn't explicitly set)
|
|
572
|
+
if (widthIsAuto or heightIsAuto) and style.autoSize == nil then
|
|
573
|
+
if widthIsAuto and heightIsAuto then
|
|
574
|
+
props.AutomaticSize = Enum.AutomaticSize.XY
|
|
575
|
+
elseif widthIsAuto then
|
|
576
|
+
props.AutomaticSize = Enum.AutomaticSize.X
|
|
577
|
+
else
|
|
578
|
+
props.AutomaticSize = Enum.AutomaticSize.Y
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
-- 2. backgroundColor → BackgroundColor3 + BackgroundTransparency
|
|
583
|
+
if style.backgroundColor ~= nil then
|
|
584
|
+
local parsed = parseColor(style.backgroundColor)
|
|
585
|
+
props.BackgroundColor3 = parsed.color
|
|
586
|
+
props.BackgroundTransparency = parsed.transparency
|
|
587
|
+
end
|
|
588
|
+
-- 2.5. background: "linear-gradient(...)" → inject <uigradient>
|
|
589
|
+
if style.background ~= nil and isGradientString(style.background) then
|
|
590
|
+
-- Extract pixel dimensions from style for corner keyword resolution
|
|
591
|
+
local elemWidth
|
|
592
|
+
local elemHeight
|
|
593
|
+
local _condition = style.width ~= nil
|
|
594
|
+
if _condition then
|
|
595
|
+
local _width = style.width
|
|
596
|
+
_condition = not (type(_width) == "string")
|
|
597
|
+
end
|
|
598
|
+
if _condition then
|
|
599
|
+
elemWidth = style.width
|
|
600
|
+
else
|
|
601
|
+
local _width = style.width
|
|
602
|
+
if type(_width) == "string" then
|
|
603
|
+
local wDim = parseDimension(style.width)
|
|
604
|
+
if wDim ~= nil and wDim.Scale == 0 then
|
|
605
|
+
elemWidth = wDim.Offset
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
local _condition_1 = style.height ~= nil
|
|
610
|
+
if _condition_1 then
|
|
611
|
+
local _height = style.height
|
|
612
|
+
_condition_1 = not (type(_height) == "string")
|
|
613
|
+
end
|
|
614
|
+
if _condition_1 then
|
|
615
|
+
elemHeight = style.height
|
|
616
|
+
else
|
|
617
|
+
local _height = style.height
|
|
618
|
+
if type(_height) == "string" then
|
|
619
|
+
local hDim = parseDimension(style.height)
|
|
620
|
+
if hDim ~= nil and hDim.Scale == 0 then
|
|
621
|
+
elemHeight = hDim.Offset
|
|
622
|
+
end
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
local gradient = parseGradient(style.background, elemWidth, elemHeight)
|
|
626
|
+
if gradient then
|
|
627
|
+
-- UIGradient multiplies with BackgroundColor3, so default to white
|
|
628
|
+
-- (the multiplicative identity) for CSS parity unless the user
|
|
629
|
+
-- explicitly set backgroundColor.
|
|
630
|
+
if style.backgroundColor == nil then
|
|
631
|
+
props.BackgroundColor3 = Color3.new(1, 1, 1)
|
|
632
|
+
end
|
|
633
|
+
-- CSS gradients are opaque by default — per-pixel alpha from rgba()
|
|
634
|
+
-- colors is already handled by the UIGradient's Transparency sequence.
|
|
635
|
+
props.BackgroundTransparency = 0
|
|
636
|
+
local gradientProps = {
|
|
637
|
+
key = "uigradient",
|
|
638
|
+
Color = gradient.colorSequence,
|
|
639
|
+
Rotation = gradient.rotation,
|
|
640
|
+
}
|
|
641
|
+
if gradient.transparencySequence ~= nil then
|
|
642
|
+
gradientProps.Transparency = gradient.transparencySequence
|
|
643
|
+
end
|
|
644
|
+
local _arg0 = React.createElement("uigradient", gradientProps)
|
|
645
|
+
table.insert(children, _arg0)
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
local hasGradient = style.background ~= nil and isGradientString(style.background)
|
|
649
|
+
-- 3. opacity → BackgroundTransparency (INVERTED)
|
|
650
|
+
-- if both backgroundColor transparent and opacity specified, opacity takes precedence
|
|
651
|
+
if style.opacity ~= nil and not hasGradient then
|
|
652
|
+
props.BackgroundTransparency = 1 - style.opacity
|
|
653
|
+
end
|
|
654
|
+
-- 4. borderRadius → inject <uicorner>
|
|
655
|
+
if style.borderRadius ~= nil then
|
|
656
|
+
local radius = parseDimension(style.borderRadius)
|
|
657
|
+
local _arg0 = React.createElement("uicorner", {
|
|
658
|
+
key = "uicorner",
|
|
659
|
+
CornerRadius = radius,
|
|
660
|
+
})
|
|
661
|
+
table.insert(children, _arg0)
|
|
662
|
+
end
|
|
663
|
+
-- 5. padding → inject <uipadding>
|
|
664
|
+
if style.padding ~= nil or style.paddingTop ~= nil or style.paddingRight ~= nil or style.paddingBottom ~= nil or style.paddingLeft ~= nil then
|
|
665
|
+
-- Start from shorthand, then let individual sides override
|
|
666
|
+
local base = if style.padding ~= nil then parsePadding(style.padding) else parsePadding(0)
|
|
667
|
+
local _arg0 = React.createElement("uipadding", {
|
|
668
|
+
key = "uipadding",
|
|
669
|
+
PaddingTop = if style.paddingTop ~= nil then parseDimension(style.paddingTop) else base.top,
|
|
670
|
+
PaddingRight = if style.paddingRight ~= nil then parseDimension(style.paddingRight) else base.right,
|
|
671
|
+
PaddingBottom = if style.paddingBottom ~= nil then parseDimension(style.paddingBottom) else base.bottom,
|
|
672
|
+
PaddingLeft = if style.paddingLeft ~= nil then parseDimension(style.paddingLeft) else base.left,
|
|
673
|
+
})
|
|
674
|
+
table.insert(children, _arg0)
|
|
675
|
+
end
|
|
676
|
+
-- 6. display: "flex" | "grid" | "none" → inject <uilistlayout> / <uigridlayout> or set Visible
|
|
677
|
+
if style.display == "flex" then
|
|
678
|
+
local _arg0 = buildListLayout(style)
|
|
679
|
+
table.insert(children, _arg0)
|
|
680
|
+
elseif style.display == "grid" then
|
|
681
|
+
local _arg0 = buildGridLayout(style)
|
|
682
|
+
table.insert(children, _arg0)
|
|
683
|
+
elseif style.display == "none" then
|
|
684
|
+
props.Visible = false
|
|
685
|
+
end
|
|
686
|
+
-- visibility → Visible (convenience alias, coexists with display: none)
|
|
687
|
+
if style.visibility ~= nil then
|
|
688
|
+
props.Visible = style.visibility ~= "hidden"
|
|
689
|
+
end
|
|
690
|
+
-- 7. border → inject <uistroke>
|
|
691
|
+
if style.border ~= nil then
|
|
692
|
+
local stroke = buildStroke(style.border)
|
|
693
|
+
if stroke ~= nil then
|
|
694
|
+
table.insert(children, stroke)
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
-- textStroke → inject <uistroke> with Contextual mode
|
|
698
|
+
if style.textStroke ~= nil then
|
|
699
|
+
local stroke = buildStroke(style.textStroke, true)
|
|
700
|
+
if stroke ~= nil then
|
|
701
|
+
table.insert(children, stroke)
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
-- backgroundImage → inject <imagelabel> underlay
|
|
705
|
+
if style.backgroundImage ~= nil then
|
|
706
|
+
local url = string.match(style.backgroundImage, [[url%([\"']?(.-)[\"']?%)]])
|
|
707
|
+
if url ~= nil then
|
|
708
|
+
local cleanUrl = url
|
|
709
|
+
local _arg0 = React.createElement("imagelabel", {
|
|
710
|
+
key = "uibackgroundimage",
|
|
711
|
+
Image = cleanUrl,
|
|
712
|
+
ZIndex = -1,
|
|
713
|
+
Size = UDim2.fromScale(1, 1),
|
|
714
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
715
|
+
Position = UDim2.fromScale(0.5, 0.5),
|
|
716
|
+
BackgroundTransparency = 1,
|
|
717
|
+
})
|
|
718
|
+
table.insert(children, _arg0)
|
|
719
|
+
end
|
|
720
|
+
end
|
|
721
|
+
-- 8. aspectRatio → inject <uiaspectratioconstraint>
|
|
722
|
+
if style.aspectRatio ~= nil then
|
|
723
|
+
local _arg0 = React.createElement("uiaspectratioconstraint", {
|
|
724
|
+
key = "uiaspectratioconstraint",
|
|
725
|
+
AspectRatio = style.aspectRatio,
|
|
726
|
+
})
|
|
727
|
+
table.insert(children, _arg0)
|
|
728
|
+
end
|
|
729
|
+
-- 9. position: "absolute" → Position + AnchorPoint
|
|
730
|
+
if style.position == "absolute" then
|
|
731
|
+
local abs = buildAbsolutePosition(style)
|
|
732
|
+
props.Position = abs.Position
|
|
733
|
+
props.AnchorPoint = abs.AnchorPoint
|
|
734
|
+
end
|
|
735
|
+
-- transformOrigin → overrides AnchorPoint
|
|
736
|
+
if style.transformOrigin ~= nil then
|
|
737
|
+
props.AnchorPoint = parseTransformOrigin(style.transformOrigin)
|
|
738
|
+
end
|
|
739
|
+
-- 10. zIndex → ZIndex
|
|
740
|
+
if style.zIndex ~= nil then
|
|
741
|
+
props.ZIndex = style.zIndex
|
|
742
|
+
end
|
|
743
|
+
if style.layoutOrder ~= nil then
|
|
744
|
+
props.LayoutOrder = style.layoutOrder
|
|
745
|
+
end
|
|
746
|
+
if style.rotation ~= nil then
|
|
747
|
+
props.Rotation = style.rotation
|
|
748
|
+
end
|
|
749
|
+
-- autoSize (standalone) — explicit autoSize takes precedence over width/height "auto"
|
|
750
|
+
if style.autoSize ~= nil then
|
|
751
|
+
props.AutomaticSize = AUTO_SIZE_MAP[style.autoSize] or Enum.AutomaticSize.None
|
|
752
|
+
end
|
|
753
|
+
-- 11. Size Constraints → inject <uisizeconstraint>
|
|
754
|
+
if not hasPercentageScale(style) and (style.minWidth ~= nil or style.maxWidth ~= nil or style.minHeight ~= nil or style.maxHeight ~= nil) then
|
|
755
|
+
local minW = if style.minWidth ~= nil then (parseDimension(style.minWidth) or UDim.new(0, 0)).Offset else 0
|
|
756
|
+
local minH = if style.minHeight ~= nil then (parseDimension(style.minHeight) or UDim.new(0, 0)).Offset else 0
|
|
757
|
+
local maxW = if style.maxWidth ~= nil then (parseDimension(style.maxWidth) or UDim.new(0, 0)).Offset else math.huge
|
|
758
|
+
local maxH = if style.maxHeight ~= nil then (parseDimension(style.maxHeight) or UDim.new(0, 0)).Offset else math.huge
|
|
759
|
+
local _arg0 = React.createElement("uisizeconstraint", {
|
|
760
|
+
key = "uisizeconstraint",
|
|
761
|
+
MinSize = Vector2.new(minW, minH),
|
|
762
|
+
MaxSize = Vector2.new(maxW, maxH),
|
|
763
|
+
})
|
|
764
|
+
table.insert(children, _arg0)
|
|
765
|
+
end
|
|
766
|
+
-- 12. Typography (color, fontSize, textAlign, whiteSpace)
|
|
767
|
+
if style.color ~= nil then
|
|
768
|
+
local parsedColor = parseColor(style.color)
|
|
769
|
+
props.TextColor3 = parsedColor.color
|
|
770
|
+
if parsedColor.transparent then
|
|
771
|
+
props.TextTransparency = 1
|
|
772
|
+
end
|
|
773
|
+
end
|
|
774
|
+
if style.fontSize ~= nil then
|
|
775
|
+
props.TextSize = style.fontSize
|
|
776
|
+
end
|
|
777
|
+
if style.textAlign ~= nil then
|
|
778
|
+
if style.textAlign == "left" then
|
|
779
|
+
props.TextXAlignment = Enum.TextXAlignment.Left
|
|
780
|
+
elseif style.textAlign == "center" then
|
|
781
|
+
props.TextXAlignment = Enum.TextXAlignment.Center
|
|
782
|
+
elseif style.textAlign == "right" then
|
|
783
|
+
props.TextXAlignment = Enum.TextXAlignment.Right
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
if style.textVerticalAlign ~= nil then
|
|
787
|
+
if style.textVerticalAlign == "top" then
|
|
788
|
+
props.TextYAlignment = Enum.TextYAlignment.Top
|
|
789
|
+
elseif style.textVerticalAlign == "center" then
|
|
790
|
+
props.TextYAlignment = Enum.TextYAlignment.Center
|
|
791
|
+
elseif style.textVerticalAlign == "bottom" then
|
|
792
|
+
props.TextYAlignment = Enum.TextYAlignment.Bottom
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
if style.whiteSpace ~= nil then
|
|
796
|
+
if style.whiteSpace == "normal" or style.whiteSpace == "pre-wrap" or style.whiteSpace == "pre-line" then
|
|
797
|
+
props.TextWrapped = true
|
|
798
|
+
elseif style.whiteSpace == "nowrap" then
|
|
799
|
+
props.TextWrapped = false
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
if style.wordBreak ~= nil then
|
|
803
|
+
if style.wordBreak == "normal" or style.wordBreak == "break-word" or style.wordBreak == "break-all" then
|
|
804
|
+
props.TextWrapped = true
|
|
805
|
+
elseif style.wordBreak == "keep-all" then
|
|
806
|
+
props.TextWrapped = false
|
|
807
|
+
end
|
|
808
|
+
end
|
|
809
|
+
if style.lineHeight ~= nil then
|
|
810
|
+
props.LineHeight = style.lineHeight
|
|
811
|
+
end
|
|
812
|
+
if style.textOverflow == "ellipsis" then
|
|
813
|
+
props.TextTruncate = Enum.TextTruncate.AtEnd
|
|
814
|
+
end
|
|
815
|
+
if style.richText == true then
|
|
816
|
+
props.RichText = true
|
|
817
|
+
end
|
|
818
|
+
if style.fontFamily ~= nil or style.fontWeight ~= nil or style.fontStyle ~= nil then
|
|
819
|
+
local _condition = style.fontFamily
|
|
820
|
+
if _condition == nil then
|
|
821
|
+
_condition = "BuilderSans"
|
|
822
|
+
end
|
|
823
|
+
local familyStr = _condition
|
|
824
|
+
local _condition_1 = FONT_FAMILY_MAP[familyStr]
|
|
825
|
+
if _condition_1 == nil then
|
|
826
|
+
_condition_1 = DEFAULT_FONT_FAMILY
|
|
827
|
+
end
|
|
828
|
+
local familyUri = _condition_1
|
|
829
|
+
local weightStr = if style.fontWeight ~= nil then tostring(style.fontWeight) else "normal"
|
|
830
|
+
local weightEnum = FONT_WEIGHT_MAP[weightStr] or Enum.FontWeight.Regular
|
|
831
|
+
local fontStyleEnum = if style.fontStyle == "italic" then Enum.FontStyle.Italic else Enum.FontStyle.Normal
|
|
832
|
+
props.FontFace = Font.new(familyUri, weightEnum, fontStyleEnum)
|
|
833
|
+
end
|
|
834
|
+
if style.textDecoration ~= nil and style.textDecoration ~= "none" then
|
|
835
|
+
props.RichText = true
|
|
836
|
+
props._textDecoration = style.textDecoration
|
|
837
|
+
end
|
|
838
|
+
if style.textTransform ~= nil and style.textTransform ~= "none" then
|
|
839
|
+
props._textTransform = style.textTransform
|
|
840
|
+
end
|
|
841
|
+
-- 13. overflow → ClipsDescendants
|
|
842
|
+
if style.overflow ~= nil then
|
|
843
|
+
if style.overflow == "hidden" then
|
|
844
|
+
props.ClipsDescendants = true
|
|
845
|
+
elseif style.overflow == "visible" then
|
|
846
|
+
props.ClipsDescendants = false
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
if style.objectFit ~= nil then
|
|
850
|
+
if style.objectFit == "cover" then
|
|
851
|
+
props.ScaleType = Enum.ScaleType.Crop
|
|
852
|
+
elseif style.objectFit == "contain" then
|
|
853
|
+
props.ScaleType = Enum.ScaleType.Fit
|
|
854
|
+
elseif style.objectFit == "fill" then
|
|
855
|
+
props.ScaleType = Enum.ScaleType.Stretch
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
-- 14. pointerEvents → Interactable / Active
|
|
859
|
+
if style.pointerEvents == "none" then
|
|
860
|
+
props.Interactable = false
|
|
861
|
+
props.Active = false
|
|
862
|
+
end
|
|
863
|
+
-- userSelect → TextSelectable
|
|
864
|
+
if style.userSelect ~= nil then
|
|
865
|
+
if style.userSelect == "text" or style.userSelect == "auto" then
|
|
866
|
+
props.TextSelectable = true
|
|
867
|
+
elseif style.userSelect == "none" then
|
|
868
|
+
props.TextSelectable = false
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
-- 15. boxShadow → inject <imagelabel>
|
|
872
|
+
if style.boxShadow ~= nil and style.boxShadow ~= "none" then
|
|
873
|
+
local shadowSize = SHADOW_SIZE_MAP[style.boxShadow] or UDim2.new(1, 20, 1, 20)
|
|
874
|
+
local _condition = SHADOW_OPACITY_MAP[style.boxShadow]
|
|
875
|
+
if _condition == nil then
|
|
876
|
+
_condition = 0.5
|
|
877
|
+
end
|
|
878
|
+
local shadowTransparency = _condition
|
|
879
|
+
local _condition_1 = SHADOW_OFFSET_MAP[style.boxShadow]
|
|
880
|
+
if _condition_1 == nil then
|
|
881
|
+
_condition_1 = 6
|
|
882
|
+
end
|
|
883
|
+
local shadowOffset = _condition_1
|
|
884
|
+
local _condition_2 = SHADOW_SLICE_SCALE_MAP[style.boxShadow]
|
|
885
|
+
if _condition_2 == nil then
|
|
886
|
+
_condition_2 = 0.4
|
|
887
|
+
end
|
|
888
|
+
local sliceScale = _condition_2
|
|
889
|
+
local _arg0 = React.createElement("imagelabel", {
|
|
890
|
+
key = "box-shadow",
|
|
891
|
+
Image = SHADOW_ASSET_ID,
|
|
892
|
+
ScaleType = Enum.ScaleType.Slice,
|
|
893
|
+
SliceCenter = SHADOW_SLICE_CENTER,
|
|
894
|
+
SliceScale = sliceScale,
|
|
895
|
+
BackgroundTransparency = 1,
|
|
896
|
+
ImageColor3 = Color3.new(0, 0, 0),
|
|
897
|
+
ImageTransparency = shadowTransparency,
|
|
898
|
+
ZIndex = -1,
|
|
899
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
900
|
+
Position = UDim2.new(0.5, 0, 0.5, shadowOffset),
|
|
901
|
+
Size = shadowSize,
|
|
902
|
+
})
|
|
903
|
+
table.insert(children, _arg0)
|
|
904
|
+
end
|
|
905
|
+
-- 16. flexGrow / flexShrink / alignSelf → inject <uiflexitem>
|
|
906
|
+
local _condition = style.flexGrow
|
|
907
|
+
if _condition == nil then
|
|
908
|
+
_condition = 0
|
|
909
|
+
end
|
|
910
|
+
local grow = _condition
|
|
911
|
+
local _condition_1 = style.flexShrink
|
|
912
|
+
if _condition_1 == nil then
|
|
913
|
+
_condition_1 = 0
|
|
914
|
+
end
|
|
915
|
+
local shrink = _condition_1
|
|
916
|
+
if style.flex ~= nil then
|
|
917
|
+
if style.flex == "auto" then
|
|
918
|
+
grow = 1
|
|
919
|
+
shrink = 1
|
|
920
|
+
elseif style.flex == "none" then
|
|
921
|
+
grow = 0
|
|
922
|
+
shrink = 0
|
|
923
|
+
else
|
|
924
|
+
local _flex = style.flex
|
|
925
|
+
if type(_flex) == "number" then
|
|
926
|
+
grow = style.flex
|
|
927
|
+
shrink = style.flex
|
|
928
|
+
else
|
|
929
|
+
local _flex_1 = style.flex
|
|
930
|
+
if type(_flex_1) == "string" then
|
|
931
|
+
local parsed = tonumber(style.flex)
|
|
932
|
+
if parsed ~= nil then
|
|
933
|
+
grow = parsed
|
|
934
|
+
shrink = parsed
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
end
|
|
938
|
+
end
|
|
939
|
+
end
|
|
940
|
+
local hasFlexItem = grow > 0 or shrink > 0 or (style.alignSelf ~= nil and style.alignSelf ~= "auto")
|
|
941
|
+
if hasFlexItem then
|
|
942
|
+
if style.display == "grid" then
|
|
943
|
+
warn("[webStyle] flexGrow/flexShrink/alignSelf have no effect inside display: 'grid'. UIFlexItem only works with UIListLayout (display: 'flex').")
|
|
944
|
+
end
|
|
945
|
+
local flexProps = {
|
|
946
|
+
key = "uiflexitem",
|
|
947
|
+
}
|
|
948
|
+
if grow > 0 or shrink > 0 then
|
|
949
|
+
flexProps.FlexMode = Enum.UIFlexMode.Custom
|
|
950
|
+
flexProps.GrowRatio = grow
|
|
951
|
+
flexProps.ShrinkRatio = shrink
|
|
952
|
+
end
|
|
953
|
+
if style.alignSelf ~= nil and style.alignSelf ~= "auto" then
|
|
954
|
+
if style.alignSelf == "flex-start" then
|
|
955
|
+
flexProps.ItemLineAlignment = Enum.ItemLineAlignment.Start
|
|
956
|
+
elseif style.alignSelf == "flex-end" then
|
|
957
|
+
flexProps.ItemLineAlignment = Enum.ItemLineAlignment.End
|
|
958
|
+
elseif style.alignSelf == "center" then
|
|
959
|
+
flexProps.ItemLineAlignment = Enum.ItemLineAlignment.Center
|
|
960
|
+
elseif style.alignSelf == "stretch" then
|
|
961
|
+
flexProps.ItemLineAlignment = Enum.ItemLineAlignment.Stretch
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
local _arg0 = React.createElement("uiflexitem", flexProps)
|
|
965
|
+
table.insert(children, _arg0)
|
|
966
|
+
end
|
|
967
|
+
return makeWebStyleResult(props, children)
|
|
968
|
+
end
|
|
969
|
+
return {
|
|
970
|
+
webStyle = webStyle,
|
|
971
|
+
SHADOW_ASSET_ID = SHADOW_ASSET_ID,
|
|
972
|
+
SHADOW_SLICE_CENTER = SHADOW_SLICE_CENTER,
|
|
973
|
+
}
|