@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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSSTypes.d.ts — Strongly-typed style prop interface.
|
|
3
|
+
*
|
|
4
|
+
* This is the contract that every wrapper component's `style` prop uses.
|
|
5
|
+
* Define all supported CSS properties here with their accepted value types.
|
|
6
|
+
* Properties not listed here are intentionally unsupported.
|
|
7
|
+
*
|
|
8
|
+
* Reference: docs/FEATURE_MAPPING.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface CSSProperties {
|
|
12
|
+
// --- Box Model & Sizing (FEATURE_MAPPING §2) ---
|
|
13
|
+
width?: string | number;
|
|
14
|
+
height?: string | number;
|
|
15
|
+
minWidth?: string | number;
|
|
16
|
+
maxWidth?: string | number;
|
|
17
|
+
minHeight?: string | number;
|
|
18
|
+
maxHeight?: string | number;
|
|
19
|
+
|
|
20
|
+
// --- Spacing ---
|
|
21
|
+
padding?: string | number;
|
|
22
|
+
paddingTop?: string | number;
|
|
23
|
+
paddingRight?: string | number;
|
|
24
|
+
paddingBottom?: string | number;
|
|
25
|
+
paddingLeft?: string | number;
|
|
26
|
+
|
|
27
|
+
// --- Positioning ---
|
|
28
|
+
position?: "absolute" | "relative";
|
|
29
|
+
top?: string | number;
|
|
30
|
+
right?: string | number;
|
|
31
|
+
bottom?: string | number;
|
|
32
|
+
left?: string | number;
|
|
33
|
+
zIndex?: number;
|
|
34
|
+
transformOrigin?: string;
|
|
35
|
+
|
|
36
|
+
// --- Layout ---
|
|
37
|
+
layoutOrder?: number;
|
|
38
|
+
rotation?: number;
|
|
39
|
+
autoSize?: "none" | "x" | "y" | "xy";
|
|
40
|
+
|
|
41
|
+
// --- Flexbox Layout (FEATURE_MAPPING §3) ---
|
|
42
|
+
display?: "flex" | "grid" | "none";
|
|
43
|
+
flexDirection?: "row" | "column";
|
|
44
|
+
flexWrap?: "nowrap" | "wrap";
|
|
45
|
+
justifyContent?: "flex-start" | "center" | "flex-end" | "space-between" | "space-around";
|
|
46
|
+
alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
|
|
47
|
+
gap?: string | number;
|
|
48
|
+
rowGap?: string | number;
|
|
49
|
+
columnGap?: string | number;
|
|
50
|
+
|
|
51
|
+
// --- Flex Item (per-child, injects UIFlexItem) ---
|
|
52
|
+
flexGrow?: number;
|
|
53
|
+
flexShrink?: number;
|
|
54
|
+
flex?: number | "auto" | "none";
|
|
55
|
+
alignSelf?: "auto" | "flex-start" | "flex-end" | "center" | "stretch";
|
|
56
|
+
|
|
57
|
+
// --- Grid Layout (Emulating UIGridLayout) ---
|
|
58
|
+
gridTemplateColumns?: string | number;
|
|
59
|
+
gridTemplateRows?: string | number;
|
|
60
|
+
|
|
61
|
+
// --- Aesthetics (FEATURE_MAPPING §4) ---
|
|
62
|
+
backgroundColor?: string;
|
|
63
|
+
background?: string;
|
|
64
|
+
backgroundImage?: string;
|
|
65
|
+
color?: string;
|
|
66
|
+
opacity?: number;
|
|
67
|
+
borderRadius?: string | number;
|
|
68
|
+
border?: string;
|
|
69
|
+
boxShadow?: "none" | "sm" | "md" | "lg" | "xl" | "2xl";
|
|
70
|
+
overflow?: "hidden" | "visible";
|
|
71
|
+
objectFit?: "cover" | "contain" | "fill";
|
|
72
|
+
visibility?: "visible" | "hidden";
|
|
73
|
+
|
|
74
|
+
// --- Constraints ---
|
|
75
|
+
aspectRatio?: number;
|
|
76
|
+
|
|
77
|
+
// --- Typography (FEATURE_MAPPING §5) ---
|
|
78
|
+
fontSize?: number;
|
|
79
|
+
fontFamily?: string;
|
|
80
|
+
fontWeight?: "normal" | "bold" | "black";
|
|
81
|
+
fontStyle?: "normal" | "italic";
|
|
82
|
+
textAlign?: "left" | "center" | "right";
|
|
83
|
+
textVerticalAlign?: "top" | "center" | "bottom";
|
|
84
|
+
whiteSpace?: "normal" | "nowrap" | "pre-wrap" | "pre-line";
|
|
85
|
+
wordBreak?: "normal" | "break-word" | "break-all" | "keep-all";
|
|
86
|
+
lineHeight?: number;
|
|
87
|
+
textOverflow?: "ellipsis";
|
|
88
|
+
textDecoration?: "none" | "underline" | "line-through";
|
|
89
|
+
textTransform?: "none" | "uppercase" | "lowercase" | "capitalize";
|
|
90
|
+
richText?: boolean;
|
|
91
|
+
textStroke?: string;
|
|
92
|
+
userSelect?: "none" | "auto" | "text";
|
|
93
|
+
|
|
94
|
+
// --- Interactivity (FEATURE_MAPPING §6) ---
|
|
95
|
+
pointerEvents?: "none" | "auto";
|
|
96
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from "@rbxts/react";
|
|
2
|
+
/**
|
|
3
|
+
* Carries the nearest ancestor <Box>'s AbsoluteSize as a binding,
|
|
4
|
+
* enabling children to reactively resolve percentage-based size constraints.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ParentSizeContext: React.Context<React.Binding<Vector2> | undefined>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local React = TS.import(script, TS.getModule(script, "@rbxts", "react"))
|
|
4
|
+
--[[
|
|
5
|
+
*
|
|
6
|
+
* Carries the nearest ancestor <Box>'s AbsoluteSize as a binding,
|
|
7
|
+
* enabling children to reactively resolve percentage-based size constraints.
|
|
8
|
+
|
|
9
|
+
]]
|
|
10
|
+
local ParentSizeContext = React.createContext(nil)
|
|
11
|
+
return {
|
|
12
|
+
ParentSizeContext = ParentSizeContext,
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* colorParser.ts — Converts CSS color strings to Roblox Color3.
|
|
3
|
+
*
|
|
4
|
+
* Supported formats:
|
|
5
|
+
* - Hex: "#ff0000", "#f00"
|
|
6
|
+
* - RGB: "rgb(255, 0, 0)"
|
|
7
|
+
* - RGBA: "rgba(255, 0, 0, 0.5)"
|
|
8
|
+
* - HSL: "hsl(0, 100%, 50%)"
|
|
9
|
+
* - HSLA: "hsla(0, 100%, 50%, 0.5)"
|
|
10
|
+
* - Named: "red", "blue", "white", "transparent"
|
|
11
|
+
* - Pass-through: Color3 instances returned as-is
|
|
12
|
+
*
|
|
13
|
+
* Returns a ParsedColor with `transparent` flag for "transparent" keyword.
|
|
14
|
+
*/
|
|
15
|
+
import { DeepReadonly } from "../types";
|
|
16
|
+
/**
|
|
17
|
+
* Branded ParsedColor — the `_parsed` brand ensures only values produced by
|
|
18
|
+
* parseColor() are assignable. A plain `{ color: Color3; transparency: number }`
|
|
19
|
+
* will NOT satisfy this type, catching accidental bypasses at compile time.
|
|
20
|
+
*/
|
|
21
|
+
export type ParsedColor = {
|
|
22
|
+
readonly color: Color3;
|
|
23
|
+
readonly transparent: boolean;
|
|
24
|
+
readonly transparency: number;
|
|
25
|
+
} & {
|
|
26
|
+
readonly _parsed: unique symbol;
|
|
27
|
+
};
|
|
28
|
+
export declare function parseColor(_input: string | Color3): DeepReadonly<ParsedColor>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
--[[
|
|
4
|
+
*
|
|
5
|
+
* colorParser.ts — Converts CSS color strings to Roblox Color3.
|
|
6
|
+
*
|
|
7
|
+
* Supported formats:
|
|
8
|
+
* - Hex: "#ff0000", "#f00"
|
|
9
|
+
* - RGB: "rgb(255, 0, 0)"
|
|
10
|
+
* - RGBA: "rgba(255, 0, 0, 0.5)"
|
|
11
|
+
* - HSL: "hsl(0, 100%, 50%)"
|
|
12
|
+
* - HSLA: "hsla(0, 100%, 50%, 0.5)"
|
|
13
|
+
* - Named: "red", "blue", "white", "transparent"
|
|
14
|
+
* - Pass-through: Color3 instances returned as-is
|
|
15
|
+
*
|
|
16
|
+
* Returns a ParsedColor with `transparent` flag for "transparent" keyword.
|
|
17
|
+
|
|
18
|
+
]]
|
|
19
|
+
local NAMED_COLORS = TS.import(script, script.Parent, "namedColors").NAMED_COLORS
|
|
20
|
+
local createLogger = TS.import(script, script.Parent.Parent, "logger").createLogger
|
|
21
|
+
local log = createLogger("colorParser")
|
|
22
|
+
--[[
|
|
23
|
+
*
|
|
24
|
+
* Branded ParsedColor — the `_parsed` brand ensures only values produced by
|
|
25
|
+
* parseColor() are assignable. A plain `{ color: Color3; transparency: number }`
|
|
26
|
+
* will NOT satisfy this type, catching accidental bypasses at compile time.
|
|
27
|
+
|
|
28
|
+
]]
|
|
29
|
+
--* Helper to construct a branded ParsedColor inside this module.
|
|
30
|
+
local function makeParsedColor(color, transparency)
|
|
31
|
+
return {
|
|
32
|
+
color = color,
|
|
33
|
+
transparent = transparency == 1,
|
|
34
|
+
transparency = transparency,
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
local function parseColor(_input)
|
|
38
|
+
-- 1. If input is already Color3, return it
|
|
39
|
+
local __input = _input
|
|
40
|
+
if typeof(__input) == "Color3" then
|
|
41
|
+
return makeParsedColor(_input, 0)
|
|
42
|
+
end
|
|
43
|
+
-- if not Color3, it must be a string
|
|
44
|
+
local input = _input
|
|
45
|
+
-- 2. If string "transparent", return white + transparency: 1
|
|
46
|
+
if input == "transparent" then
|
|
47
|
+
return makeParsedColor(Color3.new(1, 1, 1), 1)
|
|
48
|
+
end
|
|
49
|
+
-- 3. If starts with "#" or is a valid hex without "#", parse hex (handle 3-char and 6-char)
|
|
50
|
+
local hasHash = string.sub(input, 1, 1) == "#"
|
|
51
|
+
local possibleHex = if hasHash then string.sub(input, 2) else input
|
|
52
|
+
if (#possibleHex == 3 or #possibleHex == 6) and tonumber(possibleHex, 16) ~= nil then
|
|
53
|
+
local r = 0
|
|
54
|
+
local g = 0
|
|
55
|
+
local b = 0
|
|
56
|
+
if #possibleHex == 3 then
|
|
57
|
+
-- RGB → double each digit: "f" → "ff"
|
|
58
|
+
local _condition = tonumber(string.rep(string.sub(possibleHex, 1, 1), 2), 16)
|
|
59
|
+
if _condition == nil then
|
|
60
|
+
_condition = 0
|
|
61
|
+
end
|
|
62
|
+
r = _condition
|
|
63
|
+
local _condition_1 = tonumber(string.rep(string.sub(possibleHex, 2, 2), 2), 16)
|
|
64
|
+
if _condition_1 == nil then
|
|
65
|
+
_condition_1 = 0
|
|
66
|
+
end
|
|
67
|
+
g = _condition_1
|
|
68
|
+
local _condition_2 = tonumber(string.rep(string.sub(possibleHex, 3, 3), 2), 16)
|
|
69
|
+
if _condition_2 == nil then
|
|
70
|
+
_condition_2 = 0
|
|
71
|
+
end
|
|
72
|
+
b = _condition_2
|
|
73
|
+
else
|
|
74
|
+
-- RRGGBB
|
|
75
|
+
local _condition = tonumber(string.sub(possibleHex, 1, 2), 16)
|
|
76
|
+
if _condition == nil then
|
|
77
|
+
_condition = 0
|
|
78
|
+
end
|
|
79
|
+
r = _condition
|
|
80
|
+
local _condition_1 = tonumber(string.sub(possibleHex, 3, 4), 16)
|
|
81
|
+
if _condition_1 == nil then
|
|
82
|
+
_condition_1 = 0
|
|
83
|
+
end
|
|
84
|
+
g = _condition_1
|
|
85
|
+
local _condition_2 = tonumber(string.sub(possibleHex, 5, 6), 16)
|
|
86
|
+
if _condition_2 == nil then
|
|
87
|
+
_condition_2 = 0
|
|
88
|
+
end
|
|
89
|
+
b = _condition_2
|
|
90
|
+
end
|
|
91
|
+
return makeParsedColor(Color3.fromRGB(r, g, b), 0)
|
|
92
|
+
end
|
|
93
|
+
-- 4. If starts with "rgb(" or "rgba(", extract numbers
|
|
94
|
+
if string.sub(input, 1, 4) == "rgb(" then
|
|
95
|
+
local parts = string.split(input, ",")
|
|
96
|
+
local _condition = tonumber(string.sub(parts[1], 5))
|
|
97
|
+
if _condition == nil then
|
|
98
|
+
_condition = 0
|
|
99
|
+
end
|
|
100
|
+
local r = _condition
|
|
101
|
+
local _condition_1 = tonumber(parts[2])
|
|
102
|
+
if _condition_1 == nil then
|
|
103
|
+
_condition_1 = 0
|
|
104
|
+
end
|
|
105
|
+
local g = _condition_1
|
|
106
|
+
local _condition_2 = tonumber(string.sub(parts[3], 1, -2))
|
|
107
|
+
if _condition_2 == nil then
|
|
108
|
+
_condition_2 = 0
|
|
109
|
+
end
|
|
110
|
+
local b = _condition_2
|
|
111
|
+
return makeParsedColor(Color3.fromRGB(r, g, b), 0)
|
|
112
|
+
elseif string.sub(input, 1, 5) == "rgba(" then
|
|
113
|
+
local parts = string.split(input, ",")
|
|
114
|
+
local _condition = tonumber(string.sub(parts[1], 6))
|
|
115
|
+
if _condition == nil then
|
|
116
|
+
_condition = 0
|
|
117
|
+
end
|
|
118
|
+
local r = _condition
|
|
119
|
+
local _condition_1 = tonumber(parts[2])
|
|
120
|
+
if _condition_1 == nil then
|
|
121
|
+
_condition_1 = 0
|
|
122
|
+
end
|
|
123
|
+
local g = _condition_1
|
|
124
|
+
local _condition_2 = tonumber(parts[3])
|
|
125
|
+
if _condition_2 == nil then
|
|
126
|
+
_condition_2 = 0
|
|
127
|
+
end
|
|
128
|
+
local b = _condition_2
|
|
129
|
+
local _condition_3 = tonumber(string.sub(parts[4], 1, -2))
|
|
130
|
+
if _condition_3 == nil then
|
|
131
|
+
_condition_3 = 1
|
|
132
|
+
end
|
|
133
|
+
local alpha = _condition_3
|
|
134
|
+
return makeParsedColor(Color3.fromRGB(r, g, b), 1 - alpha)
|
|
135
|
+
end
|
|
136
|
+
-- 4.5. HSL / HSLA support
|
|
137
|
+
if string.sub(input, 1, 4) == "hsl(" or string.sub(input, 1, 5) == "hsla(" then
|
|
138
|
+
local isHsla = string.sub(input, 1, 5) == "hsla("
|
|
139
|
+
local parts = string.split(input, ",")
|
|
140
|
+
-- Extract hue from first part: "hsl(120" or "hsla(120"
|
|
141
|
+
local hStr = if isHsla then string.sub(parts[1], 6) else string.sub(parts[1], 5)
|
|
142
|
+
local _condition = tonumber(hStr)
|
|
143
|
+
if _condition == nil then
|
|
144
|
+
_condition = 0
|
|
145
|
+
end
|
|
146
|
+
local h = (_condition % 360 + 360) % 360
|
|
147
|
+
-- Extract saturation — strip non-numeric chars (%, spaces) via Lua pattern
|
|
148
|
+
local _condition_1 = parts[2]
|
|
149
|
+
if _condition_1 == nil then
|
|
150
|
+
_condition_1 = ""
|
|
151
|
+
end
|
|
152
|
+
local sMatch = string.match(_condition_1, "([%d%.]+)")
|
|
153
|
+
local _condition_2 = tonumber(sMatch)
|
|
154
|
+
if _condition_2 == nil then
|
|
155
|
+
_condition_2 = 0
|
|
156
|
+
end
|
|
157
|
+
local s = math.clamp(_condition_2 / 100, 0, 1)
|
|
158
|
+
-- Extract lightness — strip non-numeric chars (%, ), spaces)
|
|
159
|
+
local _condition_3 = parts[3]
|
|
160
|
+
if _condition_3 == nil then
|
|
161
|
+
_condition_3 = ""
|
|
162
|
+
end
|
|
163
|
+
local lMatch = string.match(_condition_3, "([%d%.]+)")
|
|
164
|
+
local _condition_4 = tonumber(lMatch)
|
|
165
|
+
if _condition_4 == nil then
|
|
166
|
+
_condition_4 = 0
|
|
167
|
+
end
|
|
168
|
+
local l = math.clamp(_condition_4 / 100, 0, 1)
|
|
169
|
+
-- Extract alpha (hsla only) — strip closing paren
|
|
170
|
+
local _result
|
|
171
|
+
if isHsla and #parts >= 4 then
|
|
172
|
+
local _condition_5 = tonumber(string.sub(parts[4], 1, -2))
|
|
173
|
+
if _condition_5 == nil then
|
|
174
|
+
_condition_5 = 1
|
|
175
|
+
end
|
|
176
|
+
_result = _condition_5
|
|
177
|
+
else
|
|
178
|
+
_result = 1
|
|
179
|
+
end
|
|
180
|
+
local alpha = _result
|
|
181
|
+
-- HSL to RGB conversion (standard algorithm)
|
|
182
|
+
local c = (1 - math.abs(2 * l - 1)) * s
|
|
183
|
+
local x = c * (1 - math.abs(((h / 60) % 2) - 1))
|
|
184
|
+
local m = l - c / 2
|
|
185
|
+
local r1 = 0
|
|
186
|
+
local g1 = 0
|
|
187
|
+
local b1 = 0
|
|
188
|
+
if h < 60 then
|
|
189
|
+
r1 = c
|
|
190
|
+
g1 = x
|
|
191
|
+
b1 = 0
|
|
192
|
+
elseif h < 120 then
|
|
193
|
+
r1 = x
|
|
194
|
+
g1 = c
|
|
195
|
+
b1 = 0
|
|
196
|
+
elseif h < 180 then
|
|
197
|
+
r1 = 0
|
|
198
|
+
g1 = c
|
|
199
|
+
b1 = x
|
|
200
|
+
elseif h < 240 then
|
|
201
|
+
r1 = 0
|
|
202
|
+
g1 = x
|
|
203
|
+
b1 = c
|
|
204
|
+
elseif h < 300 then
|
|
205
|
+
r1 = x
|
|
206
|
+
g1 = 0
|
|
207
|
+
b1 = c
|
|
208
|
+
else
|
|
209
|
+
r1 = c
|
|
210
|
+
g1 = 0
|
|
211
|
+
b1 = x
|
|
212
|
+
end
|
|
213
|
+
return makeParsedColor(Color3.new(math.clamp(r1 + m, 0, 1), math.clamp(g1 + m, 0, 1), math.clamp(b1 + m, 0, 1)), 1 - alpha)
|
|
214
|
+
end
|
|
215
|
+
-- 5. Lookup in NAMED_COLORS table (case-insensitive)
|
|
216
|
+
local _arg0 = string.lower(input)
|
|
217
|
+
local rgb = NAMED_COLORS[_arg0]
|
|
218
|
+
if rgb then
|
|
219
|
+
return makeParsedColor(Color3.fromRGB(rgb[1], rgb[2], rgb[3]), 0)
|
|
220
|
+
end
|
|
221
|
+
-- 6. Fallback: warn + return white
|
|
222
|
+
log:warn("UNKNOWN_COLOR", "Unknown color string", {
|
|
223
|
+
input = input,
|
|
224
|
+
})
|
|
225
|
+
return makeParsedColor(Color3.new(1, 1, 1), 0)
|
|
226
|
+
end
|
|
227
|
+
return {
|
|
228
|
+
parseColor = parseColor,
|
|
229
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { DeepReadonly } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* dimensionParser.ts — Converts CSS dimension strings to Roblox UDim.
|
|
4
|
+
*
|
|
5
|
+
* Supported formats:
|
|
6
|
+
* - Pixels: "100px" or 100 (number) → UDim(0, 100)
|
|
7
|
+
* - Percent: "50%" → UDim(0.5, 0)
|
|
8
|
+
* - Viewport: "100vw" / "100vh" → UDim(1, 0)
|
|
9
|
+
* - Calc: "calc(100% - 10px)" → UDim(1.0, -10)
|
|
10
|
+
* - Auto: "auto" → undefined
|
|
11
|
+
*
|
|
12
|
+
* Also exports parsePadding() for CSS padding shorthand.
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Branded ParsedDimension — the `_parsed` brand ensures only values produced by
|
|
17
|
+
* parseDimension() are assignable. A raw UDim will NOT satisfy this type,
|
|
18
|
+
* catching accidental bypasses at compile time.
|
|
19
|
+
*/
|
|
20
|
+
export type ParsedDimension = UDim & {
|
|
21
|
+
readonly _parsed: unique symbol;
|
|
22
|
+
};
|
|
23
|
+
export declare function parseDimension(_input: string | number): ParsedDimension | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Branded PaddingValues — the `_parsed` brand ensures only values produced by
|
|
26
|
+
* parsePadding() are assignable. A plain object with top, right, bottom, left
|
|
27
|
+
* will NOT satisfy this type, catching accidental bypasses at compile time.
|
|
28
|
+
*/
|
|
29
|
+
export type PaddingValues = {
|
|
30
|
+
readonly top: UDim;
|
|
31
|
+
readonly right: UDim;
|
|
32
|
+
readonly bottom: UDim;
|
|
33
|
+
readonly left: UDim;
|
|
34
|
+
} & {
|
|
35
|
+
readonly _parsed: unique symbol;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Converts a CSS padding shorthand string or number into UDim values for all 4 sides.
|
|
39
|
+
*
|
|
40
|
+
* Supported formats (similar to CSS):
|
|
41
|
+
* - 1 value: "10px" → top/right/bottom/left
|
|
42
|
+
* - 2 values: "10px 20%" → top/bottom, right/left
|
|
43
|
+
* - 3 values: "10px 20% 10px" → top, right/left, bottom
|
|
44
|
+
* - 4 values: "10px 20% 10px 5%" → top, right, bottom, left
|
|
45
|
+
*
|
|
46
|
+
* Supports any dimension format supported by parseDimension (px, %, vw, vh).
|
|
47
|
+
* Number inputs are treated as pixels applied to all sides.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parsePadding(_input: string | number): DeepReadonly<PaddingValues>;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local createLogger = TS.import(script, script.Parent.Parent, "logger").createLogger
|
|
4
|
+
local log = createLogger("dimensionParser")
|
|
5
|
+
--[[
|
|
6
|
+
*
|
|
7
|
+
* dimensionParser.ts — Converts CSS dimension strings to Roblox UDim.
|
|
8
|
+
*
|
|
9
|
+
* Supported formats:
|
|
10
|
+
* - Pixels: "100px" or 100 (number) → UDim(0, 100)
|
|
11
|
+
* - Percent: "50%" → UDim(0.5, 0)
|
|
12
|
+
* - Viewport: "100vw" / "100vh" → UDim(1, 0)
|
|
13
|
+
* - Calc: "calc(100% - 10px)" → UDim(1.0, -10)
|
|
14
|
+
* - Auto: "auto" → undefined
|
|
15
|
+
*
|
|
16
|
+
* Also exports parsePadding() for CSS padding shorthand.
|
|
17
|
+
*
|
|
18
|
+
|
|
19
|
+
]]
|
|
20
|
+
--[[
|
|
21
|
+
*
|
|
22
|
+
* Branded ParsedDimension — the `_parsed` brand ensures only values produced by
|
|
23
|
+
* parseDimension() are assignable. A raw UDim will NOT satisfy this type,
|
|
24
|
+
* catching accidental bypasses at compile time.
|
|
25
|
+
|
|
26
|
+
]]
|
|
27
|
+
--* Helper to construct a branded ParsedDimension inside this module.
|
|
28
|
+
local function makeParsedDimension(scale, offset)
|
|
29
|
+
return UDim.new(scale, offset)
|
|
30
|
+
end
|
|
31
|
+
local parseDimension
|
|
32
|
+
local function parseCalc(inner)
|
|
33
|
+
local parts = string.split(inner, " - ")
|
|
34
|
+
local isSubtraction = true
|
|
35
|
+
if #parts ~= 2 then
|
|
36
|
+
parts = string.split(inner, " + ")
|
|
37
|
+
isSubtraction = false
|
|
38
|
+
end
|
|
39
|
+
if #parts ~= 2 then
|
|
40
|
+
log:warn("PARSE_CALC_FAILED", "Malformed calc expression", {
|
|
41
|
+
inner = inner,
|
|
42
|
+
})
|
|
43
|
+
return nil
|
|
44
|
+
end
|
|
45
|
+
local term1 = parseDimension(parts[1])
|
|
46
|
+
local term2 = parseDimension(parts[2])
|
|
47
|
+
if term1 == nil or term2 == nil then
|
|
48
|
+
log:warn("PARSE_CALC_FAILED", "Failed to parse calc terms", {
|
|
49
|
+
inner = inner,
|
|
50
|
+
})
|
|
51
|
+
return nil
|
|
52
|
+
end
|
|
53
|
+
local scale2 = term2.Scale
|
|
54
|
+
local offset2 = term2.Offset
|
|
55
|
+
if isSubtraction then
|
|
56
|
+
scale2 = -scale2
|
|
57
|
+
offset2 = -offset2
|
|
58
|
+
end
|
|
59
|
+
return makeParsedDimension(term1.Scale + scale2, term1.Offset + offset2)
|
|
60
|
+
end
|
|
61
|
+
function parseDimension(_input)
|
|
62
|
+
-- 1. If number, treat as pixels → UDim(0, input)
|
|
63
|
+
local __input = _input
|
|
64
|
+
if type(__input) == "number" then
|
|
65
|
+
return makeParsedDimension(0, _input)
|
|
66
|
+
end
|
|
67
|
+
-- 1.5. If calc(...) expression, delegate to parseCalc
|
|
68
|
+
local __input_1 = _input
|
|
69
|
+
local _condition = type(__input_1) == "string"
|
|
70
|
+
if _condition then
|
|
71
|
+
_condition = string.sub(_input, 1, 5) == "calc("
|
|
72
|
+
end
|
|
73
|
+
if _condition then
|
|
74
|
+
local inner = string.sub(_input, 6, -2)
|
|
75
|
+
local result = parseCalc(inner)
|
|
76
|
+
if result ~= nil then
|
|
77
|
+
return result
|
|
78
|
+
end
|
|
79
|
+
-- Malformed calc — fall through to the warn at the bottom
|
|
80
|
+
end
|
|
81
|
+
-- 2. If string ending in "px", extract number → UDim(0, n)
|
|
82
|
+
if string.sub(_input, -2) == "px" then
|
|
83
|
+
local n = tonumber(string.sub(_input, 1, -3))
|
|
84
|
+
if n ~= nil then
|
|
85
|
+
return makeParsedDimension(0, n)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
-- 3. If string ending in "%", extract number / 100 → UDim(n/100, 0)
|
|
89
|
+
if string.sub(_input, -1) == "%" then
|
|
90
|
+
local n = tonumber(string.sub(_input, 1, -2))
|
|
91
|
+
if n ~= nil then
|
|
92
|
+
return makeParsedDimension(n / 100, 0)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
-- 4. If "100vw" or "100vh", → UDim(1, 0)
|
|
96
|
+
local last2Chars = string.sub(_input, -2)
|
|
97
|
+
if last2Chars == "vh" or last2Chars == "vw" then
|
|
98
|
+
local n = tonumber(string.sub(_input, 1, -3))
|
|
99
|
+
if n ~= nil then
|
|
100
|
+
return makeParsedDimension(n / 100, 0)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
-- 5. If "auto" → undefined
|
|
104
|
+
if _input == "auto" then
|
|
105
|
+
return nil
|
|
106
|
+
end
|
|
107
|
+
-- 6. If raw number string (e.g. "0")
|
|
108
|
+
local __input_2 = _input
|
|
109
|
+
if type(__input_2) == "string" then
|
|
110
|
+
local rawNumber = tonumber(_input)
|
|
111
|
+
if rawNumber ~= nil then
|
|
112
|
+
return makeParsedDimension(0, rawNumber)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
-- Fallback: warn + return undefined (callers must handle this)
|
|
116
|
+
log:warn("PARSE_DIMENSION_FAILED", "Could not parse dimension", {
|
|
117
|
+
input = _input,
|
|
118
|
+
})
|
|
119
|
+
return nil
|
|
120
|
+
end
|
|
121
|
+
--[[
|
|
122
|
+
*
|
|
123
|
+
* Branded PaddingValues — the `_parsed` brand ensures only values produced by
|
|
124
|
+
* parsePadding() are assignable. A plain object with top, right, bottom, left
|
|
125
|
+
* will NOT satisfy this type, catching accidental bypasses at compile time.
|
|
126
|
+
|
|
127
|
+
]]
|
|
128
|
+
--* Helper to construct a branded PaddingValues inside this module.
|
|
129
|
+
local function makePaddingValues(top, right, bottom, left)
|
|
130
|
+
return {
|
|
131
|
+
top = top,
|
|
132
|
+
right = right,
|
|
133
|
+
bottom = bottom,
|
|
134
|
+
left = left,
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
local ZERO_UDIM = UDim.new(0, 0)
|
|
138
|
+
--[[
|
|
139
|
+
*
|
|
140
|
+
* Converts a CSS padding shorthand string or number into UDim values for all 4 sides.
|
|
141
|
+
*
|
|
142
|
+
* Supported formats (similar to CSS):
|
|
143
|
+
* - 1 value: "10px" → top/right/bottom/left
|
|
144
|
+
* - 2 values: "10px 20%" → top/bottom, right/left
|
|
145
|
+
* - 3 values: "10px 20% 10px" → top, right/left, bottom
|
|
146
|
+
* - 4 values: "10px 20% 10px 5%" → top, right, bottom, left
|
|
147
|
+
*
|
|
148
|
+
* Supports any dimension format supported by parseDimension (px, %, vw, vh).
|
|
149
|
+
* Number inputs are treated as pixels applied to all sides.
|
|
150
|
+
|
|
151
|
+
]]
|
|
152
|
+
local function parsePadding(_input)
|
|
153
|
+
-- 1. If number, treat as pixels on all sides
|
|
154
|
+
local __input = _input
|
|
155
|
+
if type(__input) == "number" then
|
|
156
|
+
local dim = parseDimension(_input)
|
|
157
|
+
return makePaddingValues(dim, dim, dim, dim)
|
|
158
|
+
end
|
|
159
|
+
-- 2. If string, split on whitespace and parse each token through parseDimension
|
|
160
|
+
local splitInput = string.split(_input, " ")
|
|
161
|
+
local size = #splitInput
|
|
162
|
+
if size == 1 then
|
|
163
|
+
-- - 1 value: all sides
|
|
164
|
+
local a = parseDimension(splitInput[1])
|
|
165
|
+
if a ~= nil then
|
|
166
|
+
return makePaddingValues(a, a, a, a)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
if size == 2 then
|
|
170
|
+
-- - 2 values: vertical | horizontal
|
|
171
|
+
local a = parseDimension(splitInput[1])
|
|
172
|
+
local b = parseDimension(splitInput[2])
|
|
173
|
+
if a ~= nil and b ~= nil then
|
|
174
|
+
return makePaddingValues(a, b, a, b)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
-- - 3 values: top | horizontal | bottom
|
|
178
|
+
if size == 3 then
|
|
179
|
+
local a = parseDimension(splitInput[1])
|
|
180
|
+
local b = parseDimension(splitInput[2])
|
|
181
|
+
local c = parseDimension(splitInput[3])
|
|
182
|
+
if a ~= nil and b ~= nil and c ~= nil then
|
|
183
|
+
return makePaddingValues(a, b, c, b)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
-- - 4 values: top | right | bottom | left
|
|
187
|
+
if size == 4 then
|
|
188
|
+
local a = parseDimension(splitInput[1])
|
|
189
|
+
local b = parseDimension(splitInput[2])
|
|
190
|
+
local c = parseDimension(splitInput[3])
|
|
191
|
+
local d = parseDimension(splitInput[4])
|
|
192
|
+
if a ~= nil and b ~= nil and c ~= nil and d ~= nil then
|
|
193
|
+
return makePaddingValues(a, b, c, d)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
-- Fallback warn + return zero UDim padding
|
|
197
|
+
log:warn("PARSE_PADDING_FAILED", "Could not parse padding values", {
|
|
198
|
+
input = _input,
|
|
199
|
+
})
|
|
200
|
+
return makePaddingValues(ZERO_UDIM, ZERO_UDIM, ZERO_UDIM, ZERO_UDIM)
|
|
201
|
+
end
|
|
202
|
+
return {
|
|
203
|
+
parseDimension = parseDimension,
|
|
204
|
+
parsePadding = parsePadding,
|
|
205
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type ParsedGradient = {
|
|
2
|
+
readonly colorSequence: ColorSequence;
|
|
3
|
+
readonly transparencySequence: NumberSequence | undefined;
|
|
4
|
+
readonly rotation: number;
|
|
5
|
+
} & {
|
|
6
|
+
readonly _parsed: unique symbol;
|
|
7
|
+
};
|
|
8
|
+
export declare function isGradientString(input: string): boolean;
|
|
9
|
+
export declare function parseGradient(input: string, elementWidth?: number, elementHeight?: number): ParsedGradient | undefined;
|