Skip to content

Commit 67d5bef

Browse files
committed
comments, simplifications
1 parent 5ea71db commit 67d5bef

File tree

1 file changed

+112
-61
lines changed

1 file changed

+112
-61
lines changed

apps/builder/app/builder/features/style-panel/sections/backgrounds/gradient-utils.ts

Lines changed: 112 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ const gradientFunctionNames: Record<
6060
},
6161
};
6262

63+
/**
64+
* Checks if a CSS value string starts with a gradient function of the specified type.
65+
* Handles both base and repeating gradient functions (e.g., "linear-gradient" and "repeating-linear-gradient").
66+
*/
6367
const startsWithGradientFunction = (value: string, type: GradientType) => {
6468
const normalized = value.trim().toLowerCase();
6569
const { base, repeating } = gradientFunctionNames[type];
@@ -87,23 +91,16 @@ export const percentUnitOptions: UnitOption[] = [
8791
},
8892
];
8993

90-
const createKeywordValue = (value: string): KeywordValue => ({
91-
type: "keyword",
92-
value,
93-
});
94-
95-
const createCenterKeyword = () => createKeywordValue("center");
96-
9794
export const gradientPositionXOptions: KeywordValue[] = [
98-
createCenterKeyword(),
99-
createKeywordValue("left"),
100-
createKeywordValue("right"),
95+
{ type: "keyword", value: "center" },
96+
{ type: "keyword", value: "left" },
97+
{ type: "keyword", value: "right" },
10198
];
10299

103100
export const gradientPositionYOptions: KeywordValue[] = [
104-
createCenterKeyword(),
105-
createKeywordValue("top"),
106-
createKeywordValue("bottom"),
101+
{ type: "keyword", value: "center" },
102+
{ type: "keyword", value: "top" },
103+
{ type: "keyword", value: "bottom" },
107104
];
108105

109106
const getAxisPositionValue = (
@@ -120,11 +117,15 @@ const getAxisPositionValue = (
120117
return parsed;
121118
};
122119

120+
/**
121+
* Parses a gradient position string into separate x and y StyleValues.
122+
* Defaults to "center center" if position is undefined or parsing fails.
123+
*/
123124
export const parseGradientPositionValues = (position?: string) => {
124125
if (position === undefined) {
125126
return {
126-
xValue: createCenterKeyword(),
127-
yValue: createCenterKeyword(),
127+
xValue: { type: "keyword" as const, value: "center" },
128+
yValue: { type: "keyword" as const, value: "center" },
128129
} as const;
129130
}
130131
try {
@@ -134,27 +135,33 @@ export const parseGradientPositionValues = (position?: string) => {
134135
// Use the real background-position longhand when parsing so we can reuse
135136
// its CSS syntax rules, but assign the result to the gradient-specific
136137
// custom property downstream.
137-
xValue:
138-
getAxisPositionValue(backgroundPositionXLonghand, xLonghand?.[1]) ??
139-
createCenterKeyword(),
140-
yValue:
141-
getAxisPositionValue(backgroundPositionYLonghand, yLonghand?.[1]) ??
142-
createCenterKeyword(),
138+
xValue: getAxisPositionValue(
139+
backgroundPositionXLonghand,
140+
xLonghand?.[1]
141+
) ?? { type: "keyword" as const, value: "center" },
142+
yValue: getAxisPositionValue(
143+
backgroundPositionYLonghand,
144+
yLonghand?.[1]
145+
) ?? { type: "keyword" as const, value: "center" },
143146
} as const;
144147
} catch {
145148
return {
146-
xValue: createCenterKeyword(),
147-
yValue: createCenterKeyword(),
149+
xValue: { type: "keyword" as const, value: "center" },
150+
yValue: { type: "keyword" as const, value: "center" },
148151
} as const;
149152
}
150153
};
151154

155+
/**
156+
* Formats x and y position values into a CSS position string.
157+
* Omits "center center" as it's the default. Returns just x if y is center.
158+
*/
152159
export const formatGradientPositionValues = (
153160
xValue?: StyleValue,
154161
yValue?: StyleValue
155162
) => {
156-
const x = toValue(xValue ?? createCenterKeyword());
157-
const y = toValue(yValue ?? createCenterKeyword());
163+
const x = toValue(xValue ?? { type: "keyword" as const, value: "center" });
164+
const y = toValue(yValue ?? { type: "keyword" as const, value: "center" });
158165
if (x === "center" && y === "center") {
159166
return;
160167
}
@@ -190,6 +197,11 @@ const angleUnitToDegrees = (value: UnitValue): number | undefined => {
190197
}
191198
};
192199

200+
/**
201+
* Converts a UnitValue to a PercentUnitValue.
202+
* For percent units, clamps to 0-100. For angle units, converts to percent (0-100 representing 0-360deg).
203+
* Returns undefined for unsupported unit types.
204+
*/
193205
const toPercentUnitValue = (value: UnitValue): PercentUnitValue | undefined => {
194206
if (value.unit === "%") {
195207
return {
@@ -286,7 +298,7 @@ export const isRadialGradient = (
286298
): gradient is ParsedRadialGradient => gradient.type === "radial";
287299

288300
/**
289-
* Get the default angle for a gradient according to CSS spec.
301+
* Returns the CSS spec default angle for each gradient type:
290302
* - linear-gradient: 180deg (to bottom)
291303
* - conic-gradient: 0deg (from top)
292304
* - radial-gradient: undefined (no angle)
@@ -330,6 +342,10 @@ export const getPercentUnit = (
330342
}
331343
};
332344

345+
/**
346+
* Converts repeating-*-gradient to *-gradient while preserving leading whitespace.
347+
* Returns both the normalized string and whether it was originally repeating.
348+
*/
333349
export const normalizeGradientInput = (
334350
gradientString: string,
335351
gradientType: GradientType
@@ -401,6 +417,10 @@ export const sideOrCornerToAngle = (
401417
}
402418
};
403419

420+
/**
421+
* Interpolates missing stop positions proportionally between defined positions.
422+
* Only works with percent units - returns original gradient for non-percent units.
423+
*/
404424
export const fillMissingStopPositions = <T extends ParsedGradient>(
405425
gradient: T
406426
): T => {
@@ -501,11 +521,9 @@ export const fillMissingStopPositions = <T extends ParsedGradient>(
501521
} as T;
502522
};
503523

504-
const cloneVarValue = (value: VarValue): VarValue => ({
505-
...value,
506-
fallback: value.fallback && { ...value.fallback },
507-
});
508-
524+
/**
525+
* Clones stop values, deep cloning var fallbacks to avoid shared references.
526+
*/
509527
const cloneGradientStopValue = <
510528
Value extends GradientStop["position"] | GradientStop["hint"],
511529
>(
@@ -525,50 +543,48 @@ const cloneGradientStopValue = <
525543
return { ...value };
526544
};
527545

546+
/**
547+
* Clones colors, deep cloning var fallbacks to avoid shared references.
548+
*/
528549
const cloneGradientStopColor = (
529550
color: GradientStop["color"] | undefined
530551
): GradientStop["color"] => {
531552
if (color === undefined) {
532-
return { ...fallbackStopColor } satisfies GradientStop["color"];
553+
return { ...fallbackStopColor };
533554
}
534555
if (color.type === "var") {
535556
return {
536557
...color,
537558
fallback: color.fallback && { ...color.fallback },
538-
} satisfies GradientStop["color"];
539-
}
540-
if (color.type === "rgb") {
541-
return { ...color } satisfies GradientStop["color"];
559+
};
542560
}
543-
return { ...color } satisfies GradientStop["color"];
544-
};
545-
546-
const createSolidGradientStops = (color: GradientStop["color"]) => {
547-
const firstColor = cloneGradientStopColor(color);
548-
const secondColor = cloneGradientStopColor(color);
549-
return [
550-
{
551-
color: firstColor,
552-
position: { type: "unit", unit: "%", value: 0 },
553-
},
554-
{
555-
color: secondColor,
556-
position: { type: "unit", unit: "%", value: 100 },
557-
},
558-
] satisfies GradientStop[];
561+
return { ...color };
559562
};
560563

564+
/**
565+
* Creates a solid color gradient (two identical stops at 0% and 100%).
566+
*/
561567
export const createSolidLinearGradient = (
562568
color: GradientStop["color"],
563569
base?: ParsedLinearGradient
564570
): ParsedLinearGradient => {
565-
const stops = createSolidGradientStops(color);
571+
const firstColor = cloneGradientStopColor(color);
572+
const secondColor = cloneGradientStopColor(color);
566573
return {
567574
type: "linear",
568575
angle: base?.angle,
569576
sideOrCorner: base?.sideOrCorner,
570-
stops,
571-
} satisfies ParsedLinearGradient;
577+
stops: [
578+
{
579+
color: firstColor,
580+
position: { type: "unit", unit: "%", value: 0 },
581+
},
582+
{
583+
color: secondColor,
584+
position: { type: "unit", unit: "%", value: 100 },
585+
},
586+
],
587+
};
572588
};
573589

574590
type AngleUnitValue = UnitValue & { unit: AngleUnit };
@@ -582,14 +598,17 @@ const resolveAnglePrimitive = (
582598
}
583599

584600
if (value.type === "var") {
585-
return cloneVarValue(value);
601+
return {
602+
...value,
603+
fallback: value.fallback && { ...value.fallback },
604+
};
586605
}
587606

588607
if (value.type === "unit" && isAngleUnit(value.unit)) {
589608
return {
590609
...value,
591610
unit: value.unit,
592-
} satisfies AngleUnitValue;
611+
};
593612
}
594613

595614
return;
@@ -689,6 +708,9 @@ const normalizeStopsForPicker = <T extends ParsedGradient>(gradient: T): T => {
689708
} as T;
690709
};
691710

711+
/**
712+
* Converts conic angle units (deg, turn, etc.) to percent units for picker UI.
713+
*/
692714
const convertConicStopsToPercent = <T extends ParsedGradient>(
693715
gradient: T
694716
): T => {
@@ -748,6 +770,9 @@ const convertConicStopsToPercent = <T extends ParsedGradient>(
748770
} as T;
749771
};
750772

773+
/**
774+
* Prepares gradient for picker UI: converts angles to percent, resolves vars, fills positions, applies hint overrides.
775+
*/
751776
export const resolveGradientForPicker = <T extends ParsedGradient>(
752777
gradient: T,
753778
hintOverrides: ReadonlyMap<number, PercentUnitValue>
@@ -782,6 +807,9 @@ export const resolveGradientForPicker = <T extends ParsedGradient>(
782807
} as T;
783808
};
784809

810+
/**
811+
* Returns same map reference if unchanged for referential equality.
812+
*/
785813
export const removeHintOverride = (
786814
overrides: Map<number, PercentUnitValue>,
787815
stopIndex: number
@@ -794,6 +822,9 @@ export const removeHintOverride = (
794822
return next;
795823
};
796824

825+
/**
826+
* Returns same map reference if unchanged for referential equality.
827+
*/
797828
export const setHintOverride = (
798829
overrides: Map<number, PercentUnitValue>,
799830
stopIndex: number,
@@ -817,6 +848,9 @@ export const setHintOverride = (
817848
return next;
818849
};
819850

851+
/**
852+
* Returns same map reference if unchanged for referential equality.
853+
*/
820854
export const pruneHintOverrides = (
821855
overrides: Map<number, PercentUnitValue>,
822856
stopCount: number
@@ -835,13 +869,11 @@ export const pruneHintOverrides = (
835869
return changed ? next : overrides;
836870
};
837871

838-
// Helper to get stop position as a number (0-100) for sorting and calculations
839872
export const getStopPosition = (stop: GradientStop): number =>
840873
stop.position?.type === "unit" && stop.position.unit === "%"
841874
? stop.position.value
842875
: 0;
843876

844-
// Reindex hint overrides after a stop is deleted
845877
export const reindexHintOverrides = (
846878
overrides: Map<number, PercentUnitValue>,
847879
deletedIndex: number
@@ -857,7 +889,6 @@ export const reindexHintOverrides = (
857889
return reindexed;
858890
};
859891

860-
// Sort gradient stops by position and reindex hint overrides to match
861892
export const sortGradientStops = (
862893
gradient: ParsedGradient,
863894
hintOverrides: Map<number, PercentUnitValue>
@@ -905,6 +936,9 @@ export type ReverseStopsResolution<T extends ParsedGradient> =
905936
}
906937
| { type: "none" };
907938

939+
/**
940+
* Reverses stops and mirrors percent positions (0% becomes 100%, etc.).
941+
*/
908942
export const resolveReverseStops = <T extends ParsedGradient>(
909943
gradient: T,
910944
selectedStopIndex: number
@@ -1208,7 +1242,9 @@ type GradientByType<T extends GradientType> = Extract<
12081242
{ type: T }
12091243
>;
12101244

1211-
// Cache for parsed gradients - avoids re-parsing the same gradient string
1245+
/**
1246+
* Cached to avoid re-parsing the same string.
1247+
*/
12121248
const parsedGradientCache = new Map<string, ParsedGradient | undefined>();
12131249

12141250
export const parseAnyGradient = (value: string): ParsedGradient | undefined => {
@@ -1252,6 +1288,11 @@ export const convertGradientToTarget = <Target extends GradientType>(
12521288
return converted as GradientByType<Target>;
12531289
};
12541290

1291+
/**
1292+
* Formats a gradient for a specific background type.
1293+
* Handles solid color conversion and gradient type conversions.
1294+
* Returns the formatted CSS gradient string.
1295+
*/
12551296
export const formatGradientForType = (
12561297
styleValue: StyleValue | undefined,
12571298
target: Exclude<BackgroundType, "image">
@@ -1273,6 +1314,11 @@ export const formatGradientForType = (
12731314
return formatRadialGradient(parsed);
12741315
};
12751316

1317+
/**
1318+
* Detects the background type from a StyleValue.
1319+
* Returns "solid" for uniform linear gradients, specific gradient types for gradients,
1320+
* and "image" for non-gradient values or unparseable gradients.
1321+
*/
12761322
export const detectBackgroundType = (
12771323
styleValue?: StyleValue
12781324
): BackgroundType => {
@@ -1323,6 +1369,11 @@ export const detectBackgroundType = (
13231369
return "image";
13241370
};
13251371

1372+
/**
1373+
* Gets a specific background layer item from a ComputedStyleDecl.
1374+
* For index 0, returns the first layer or the cascaded value itself.
1375+
* For index > 0, delegates to getRepeatedStyleItem.
1376+
*/
13261377
export const getBackgroundStyleItem = (
13271378
styleDecl: ComputedStyleDecl,
13281379
index: number

0 commit comments

Comments
 (0)