Files
bun.sh/src/css/properties/transition.zig
Claude Bot 60388f3625 css: Fix transition.zig to remove remaining comptime/anytype
Replaced generic `property()` and `maybeFlush()` functions that used
`comptime prop: []const u8` and `val: anytype` with specific typed
functions for each transition property.

**Changes:**

- Created 4 specific property functions:
  - propertyProperties() for SmallList(PropertyId, 1)
  - propertyDurations() for SmallList(Time, 1)
  - propertyDelays() for SmallList(Time, 1)
  - propertyTimingFunctions() for SmallList(EasingFunction, 1)

- Created 4 specific maybeFlush functions:
  - maybeFlushProperties()
  - maybeFlushDurations()
  - maybeFlushDelays()
  - maybeFlushTimingFunctions()

- Updated all 8 call sites to use the specific functions

This eliminates the last remaining comptime parameter bloat in
transition property handlers, reducing instantiations from ~16 to 4.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 06:52:19 +00:00

598 lines
30 KiB
Zig

pub const css = @import("../css_parser.zig");
const SmallList = css.SmallList;
const Printer = css.Printer;
const PrintErr = css.PrintErr;
const Property = css.css_properties.Property;
const PropertyId = css.css_properties.PropertyId;
const Time = css.css_values.time.Time;
const EasingFunction = css.css_values.easing.EasingFunction;
const VendorPrefix = css.VendorPrefix;
const Feature = css.prefixes.Feature;
/// A value for the [transition](https://www.w3.org/TR/2018/WD-css-transitions-1-20181011/#transition-shorthand-property) property.
pub const Transition = struct {
/// The property to transition.
property: PropertyId,
/// The duration of the transition.
duration: Time,
/// The delay before the transition starts.
delay: Time,
/// The easing function for the transition.
timing_function: EasingFunction,
pub const PropertyFieldMap = .{
.property = css.PropertyIdTag.@"transition-property",
.duration = css.PropertyIdTag.@"transition-duration",
.delay = css.PropertyIdTag.@"transition-delay",
.timing_function = css.PropertyIdTag.@"transition-timing-function",
};
pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
return css.implementEql(@This(), lhs, rhs);
}
pub fn deepClone(this: *const @This(), allocator: Allocator) @This() {
return css.implementDeepClone(comptime @This(), this, allocator);
}
pub fn parse(parser: *css.Parser) css.Result(@This()) {
var property: ?PropertyId = null;
var duration: ?Time = null;
var delay: ?Time = null;
var timing_function: ?EasingFunction = null;
while (true) {
if (duration == null) {
if (parser.tryParse(Time.parse, .{}).asValue()) |value| {
duration = value;
continue;
}
}
if (timing_function == null) {
if (parser.tryParse(EasingFunction.parse, .{}).asValue()) |value| {
timing_function = value;
continue;
}
}
if (delay == null) {
if (parser.tryParse(Time.parse, .{}).asValue()) |value| {
delay = value;
continue;
}
}
if (property == null) {
if (parser.tryParse(PropertyId.parse, .{}).asValue()) |value| {
property = value;
continue;
}
}
break;
}
return .{ .result = .{
.property = property orelse .all,
.duration = duration orelse .{ .seconds = 0.0 },
.delay = delay orelse .{ .seconds = 0.0 },
.timing_function = timing_function orelse .ease,
} };
}
pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void {
try this.property.toCss(W, dest);
if (!this.duration.isZero() or !this.delay.isZero()) {
try dest.writeChar(' ');
try this.duration.toCss(W, dest);
}
if (!this.timing_function.isEase()) {
try dest.writeChar(' ');
try this.timing_function.toCss(W, dest);
}
if (!this.delay.isZero()) {
try dest.writeChar(' ');
try this.delay.toCss(W, dest);
}
}
};
pub const TransitionHandler = struct {
properties: ?struct { SmallList(PropertyId, 1), VendorPrefix } = null,
durations: ?struct { SmallList(Time, 1), VendorPrefix } = null,
delays: ?struct { SmallList(Time, 1), VendorPrefix } = null,
timing_functions: ?struct { SmallList(EasingFunction, 1), VendorPrefix } = null,
has_any: bool = false,
pub fn handleProperty(this: *@This(), prop: *const Property, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) bool {
switch (prop.*) {
.@"transition-property" => |*x| this.propertyProperties(dest, context, Feature.transition_property, &x.*[0], x.*[1]),
.@"transition-duration" => |*x| this.propertyDurations(dest, context, Feature.transition_duration, &x.*[0], x.*[1]),
.@"transition-delay" => |*x| this.propertyDelays(dest, context, Feature.transition_delay, &x.*[0], x.*[1]),
.@"transition-timing-function" => |*x| this.propertyTimingFunctions(dest, context, Feature.transition_timing_function, &x.*[0], x.*[1]),
.transition => |*x| {
const val: *const SmallList(Transition, 1) = &x.*[0];
const vp: VendorPrefix = x.*[1];
var properties = SmallList(PropertyId, 1).initCapacity(context.allocator, val.len());
var durations = SmallList(Time, 1).initCapacity(context.allocator, val.len());
var delays = SmallList(Time, 1).initCapacity(context.allocator, val.len());
var timing_functions = SmallList(EasingFunction, 1).initCapacity(context.allocator, val.len());
properties.setLen(val.len());
durations.setLen(val.len());
delays.setLen(val.len());
timing_functions.setLen(val.len());
for (val.slice(), properties.slice_mut()) |*item, *out_prop| {
out_prop.* = item.property.deepClone(context.allocator);
}
this.maybeFlushProperties(dest, context, &properties, vp);
for (val.slice(), durations.slice_mut()) |*item, *out_dur| {
out_dur.* = item.duration.deepClone(context.allocator);
}
this.maybeFlushDurations(dest, context, &durations, vp);
for (val.slice(), delays.slice_mut()) |*item, *out_delay| {
out_delay.* = item.delay.deepClone(context.allocator);
}
this.maybeFlushDelays(dest, context, &delays, vp);
for (val.slice(), timing_functions.slice_mut()) |*item, *out_timing| {
out_timing.* = item.timing_function.deepClone(context.allocator);
}
this.maybeFlushTimingFunctions(dest, context, &timing_functions, vp);
this.propertyProperties(dest, context, Feature.transition_property, &properties, vp);
this.propertyDurations(dest, context, Feature.transition_duration, &durations, vp);
this.propertyDelays(dest, context, Feature.transition_delay, &delays, vp);
this.propertyTimingFunctions(dest, context, Feature.transition_timing_function, &timing_functions, vp);
},
.unparsed => |*x| if (isTransitionProperty(&x.property_id)) {
this.flush(dest, context);
dest.append(
context.allocator,
.{ .unparsed = x.getPrefixed(context.allocator, context.targets, Feature.transition) },
) catch |err| bun.handleOom(err);
} else return false,
else => return false,
}
return true;
}
pub fn finalize(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void {
this.flush(dest, context);
}
fn propertyProperties(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, feature: Feature, val: *const SmallList(PropertyId, 1), vp: VendorPrefix) void {
this.maybeFlushProperties(dest, context, val, vp);
if (this.properties) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
v.* = val.deepClone(context.allocator);
bun.bits.insert(VendorPrefix, prefixes, vp);
prefixes.* = context.targets.prefixes(prefixes.*, feature);
} else {
const prefixes = context.targets.prefixes(vp, feature);
const cloned_val = val.deepClone(context.allocator);
this.properties = .{ cloned_val, prefixes };
this.has_any = true;
}
}
fn propertyDurations(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, feature: Feature, val: *const SmallList(Time, 1), vp: VendorPrefix) void {
this.maybeFlushDurations(dest, context, val, vp);
if (this.durations) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
v.* = val.deepClone(context.allocator);
bun.bits.insert(VendorPrefix, prefixes, vp);
prefixes.* = context.targets.prefixes(prefixes.*, feature);
} else {
const prefixes = context.targets.prefixes(vp, feature);
const cloned_val = val.deepClone(context.allocator);
this.durations = .{ cloned_val, prefixes };
this.has_any = true;
}
}
fn propertyDelays(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, feature: Feature, val: *const SmallList(Time, 1), vp: VendorPrefix) void {
this.maybeFlushDelays(dest, context, val, vp);
if (this.delays) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
v.* = val.deepClone(context.allocator);
bun.bits.insert(VendorPrefix, prefixes, vp);
prefixes.* = context.targets.prefixes(prefixes.*, feature);
} else {
const prefixes = context.targets.prefixes(vp, feature);
const cloned_val = val.deepClone(context.allocator);
this.delays = .{ cloned_val, prefixes };
this.has_any = true;
}
}
fn propertyTimingFunctions(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, feature: Feature, val: *const SmallList(EasingFunction, 1), vp: VendorPrefix) void {
this.maybeFlushTimingFunctions(dest, context, val, vp);
if (this.timing_functions) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
v.* = val.deepClone(context.allocator);
bun.bits.insert(VendorPrefix, prefixes, vp);
prefixes.* = context.targets.prefixes(prefixes.*, feature);
} else {
const prefixes = context.targets.prefixes(vp, feature);
const cloned_val = val.deepClone(context.allocator);
this.timing_functions = .{ cloned_val, prefixes };
this.has_any = true;
}
}
fn maybeFlushProperties(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, val: *const SmallList(PropertyId, 1), vp: VendorPrefix) void {
if (this.properties) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
if (!val.eql(v) and !bun.bits.contains(VendorPrefix, prefixes.*, vp)) {
this.flush(dest, context);
}
}
}
fn maybeFlushDurations(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, val: *const SmallList(Time, 1), vp: VendorPrefix) void {
if (this.durations) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
if (!val.eql(v) and !bun.bits.contains(VendorPrefix, prefixes.*, vp)) {
this.flush(dest, context);
}
}
}
fn maybeFlushDelays(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, val: *const SmallList(Time, 1), vp: VendorPrefix) void {
if (this.delays) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
if (!val.eql(v) and !bun.bits.contains(VendorPrefix, prefixes.*, vp)) {
this.flush(dest, context);
}
}
}
fn maybeFlushTimingFunctions(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, val: *const SmallList(EasingFunction, 1), vp: VendorPrefix) void {
if (this.timing_functions) |*p| {
const v = &p.*[0];
const prefixes = &p.*[1];
if (!val.eql(v) and !bun.bits.contains(VendorPrefix, prefixes.*, vp)) {
this.flush(dest, context);
}
}
}
fn flush(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void {
if (!this.has_any) return;
this.has_any = false;
var _properties: ?struct { SmallList(PropertyId, 1), VendorPrefix } = bun.take(&this.properties);
var _durations: ?struct { SmallList(Time, 1), VendorPrefix } = bun.take(&this.durations);
var _delays: ?struct { SmallList(Time, 1), VendorPrefix } = bun.take(&this.delays);
var _timing_functions: ?struct { SmallList(EasingFunction, 1), VendorPrefix } = bun.take(&this.timing_functions);
var rtl_properties: ?SmallList(PropertyId, 1) = if (_properties) |*p| expandProperties(&p.*[0], context) else null;
if (_properties != null and _durations != null and _delays != null and _timing_functions != null) {
const properties: *SmallList(PropertyId, 1) = &_properties.?[0];
const property_prefixes: *VendorPrefix = &_properties.?[1];
const durations: *SmallList(Time, 1) = &_durations.?[0];
const duration_prefixes: *VendorPrefix = &_durations.?[1];
const delays: *SmallList(Time, 1) = &_delays.?[0];
const delay_prefixes: *VendorPrefix = &_delays.?[1];
const timing_functions: *SmallList(EasingFunction, 1) = &_timing_functions.?[0];
const timing_prefixes: *VendorPrefix = &_timing_functions.?[1];
// Find the intersection of prefixes with the same value.
// Remove that from the prefixes of each of the properties. The remaining
// prefixes will be handled by outputting individual properties below.
const intersection = property_prefixes.bitwiseAnd(duration_prefixes.*).bitwiseAnd(delay_prefixes.*).bitwiseAnd(timing_prefixes.*);
if (!intersection.isEmpty()) {
const transitions = getTransitions(context, properties, durations, delays, timing_functions);
if (rtl_properties) |*rtl_properties2| {
const rtl_transitions = getTransitions(context, rtl_properties2, durations, delays, timing_functions);
context.addLogicalRule(
context.allocator,
Property{
.transition = .{ transitions, intersection },
},
Property{
.transition = .{ rtl_transitions, intersection },
},
);
} else {
dest.append(
context.allocator,
Property{ .transition = .{ transitions.deepClone(context.allocator), intersection } },
) catch |err| bun.handleOom(err);
}
bun.bits.remove(VendorPrefix, property_prefixes, intersection);
bun.bits.remove(VendorPrefix, duration_prefixes, intersection);
bun.bits.remove(VendorPrefix, timing_prefixes, intersection);
bun.bits.remove(VendorPrefix, delay_prefixes, intersection);
}
}
if (_properties != null) {
const properties: SmallList(PropertyId, 1) = _properties.?[0];
const prefix: VendorPrefix = _properties.?[1];
_properties = null;
if (!prefix.isEmpty()) {
if (rtl_properties) |rtl_properties2| {
context.addLogicalRule(
context.allocator,
Property{ .@"transition-property" = .{ properties, prefix } },
Property{ .@"transition-property" = .{ rtl_properties2, prefix } },
);
rtl_properties = null;
} else {
bun.handleOom(dest.append(context.allocator, Property{ .@"transition-property" = .{ properties, prefix } }));
}
}
}
if (_durations != null) {
const durations: SmallList(Time, 1) = _durations.?[0];
const prefix: VendorPrefix = _durations.?[1];
_durations = null;
if (!prefix.isEmpty()) {
bun.handleOom(dest.append(context.allocator, Property{ .@"transition-duration" = .{ durations, prefix } }));
}
}
if (_delays != null) {
const delays: SmallList(Time, 1) = _delays.?[0];
const prefix: VendorPrefix = _delays.?[1];
_delays = null;
if (!prefix.isEmpty()) {
bun.handleOom(dest.append(context.allocator, Property{ .@"transition-delay" = .{ delays, prefix } }));
}
}
if (_timing_functions != null) {
const timing_functions: SmallList(EasingFunction, 1) = _timing_functions.?[0];
const prefix: VendorPrefix = _timing_functions.?[1];
_timing_functions = null;
if (!prefix.isEmpty()) {
bun.handleOom(dest.append(context.allocator, Property{ .@"transition-timing-function" = .{ timing_functions, prefix } }));
}
}
this.reset();
}
inline fn getTransitions(
context: *const css.PropertyHandlerContext,
properties: *SmallList(PropertyId, 1),
durations: *SmallList(Time, 1),
delays: *SmallList(Time, 1),
timing_functions: *SmallList(EasingFunction, 1),
) SmallList(Transition, 1) {
const cycleBump = struct {
inline fn cycleBump(idx: *u32, len: u32) void {
idx.* = (idx.* + 1) % len;
}
}.cycleBump;
// transition-property determines the number of transitions. The values of other
// properties are repeated to match this length.
var transitions = SmallList(Transition, 1).initCapacity(context.allocator, 1);
var durations_idx: u32 = 0;
var delays_idx: u32 = 0;
var timing_idx: u32 = 0;
for (properties.slice()) |*property_id| {
const duration = if (durations.len() > durations_idx) durations.at(durations_idx).deepClone(context.allocator) else Time{ .seconds = 0.0 };
const delay = if (delays.len() > delays_idx) delays.at(delays_idx).deepClone(context.allocator) else Time{ .seconds = 0.0 };
const timing_function = if (timing_functions.len() > timing_idx) timing_functions.at(timing_idx).deepClone(context.allocator) else EasingFunction.ease;
cycleBump(&durations_idx, durations.len());
cycleBump(&delays_idx, delays.len());
cycleBump(&timing_idx, timing_functions.len());
const transition = Transition{
.property = property_id.deepClone(context.allocator),
.duration = duration,
.delay = delay,
.timing_function = timing_function,
};
var cloned = false;
const prefix_to_iter = property_id.prefix().orNone();
// Expand vendor prefixes into multiple transitions.
inline for (VendorPrefix.FIELDS) |prefix_field| {
if (@field(prefix_to_iter, prefix_field)) {
var t = if (cloned) transition.deepClone(context.allocator) else transition;
cloned = true;
var new_prefix = VendorPrefix{};
@field(new_prefix, prefix_field) = true;
t.property = property_id.withPrefix(new_prefix);
transitions.append(context.allocator, t);
}
}
}
return transitions;
}
pub fn reset(this: *@This()) void {
this.properties = null;
this.durations = null;
this.delays = null;
this.timing_functions = null;
this.has_any = false;
}
};
fn expandProperties(properties: *css.SmallList(PropertyId, 1), context: *css.PropertyHandlerContext) ?SmallList(PropertyId, 1) {
const replace = struct {
inline fn replace(allocator: Allocator, propertiez: anytype, props: []const PropertyId, i: u32) void {
propertiez.mut(i).* = props[0].deepClone(allocator);
if (props.len > 1) {
propertiez.insertSlice(allocator, i + 1, props[1..]);
}
}
}.replace;
var rtl_properties: ?SmallList(PropertyId, 1) = null;
var i: u32 = 0;
// Expand logical properties in place.
while (i < properties.len()) {
const result = getLogicalProperties(properties.at(i));
if (result == .block and context.shouldCompileLogical(result.block[0])) {
replace(context.allocator, properties, result.block[1], i);
if (rtl_properties) |*rtl| {
replace(context.allocator, rtl, result.block[1], i);
}
i += 1;
} else if (result == .@"inline" and context.shouldCompileLogical(result.@"inline"[0])) {
const ltr = result.@"inline"[1];
const rtl = result.@"inline"[2];
// Clone properties to create RTL version only when needed.
if (rtl_properties == null) {
rtl_properties = properties.deepClone(context.allocator);
}
replace(context.allocator, properties, ltr, i);
if (rtl_properties) |*rtl_props| {
replace(context.allocator, rtl_props, rtl, i);
}
i += @intCast(ltr.len);
} else {
// Expand vendor prefixes for targets.
properties.mut(i).setPrefixesForTargets(context.targets);
// Expand mask properties, which use different vendor-prefixed names.
if (css.css_properties.masking.getWebkitMaskProperty(properties.at(i))) |property_id| {
if (context.targets.prefixes(VendorPrefix.NONE, Feature.mask_border).webkit) {
properties.insert(context.allocator, i, property_id);
i += 1;
}
}
if (rtl_properties) |*rtl_props| {
rtl_props.mut(i).setPrefixesForTargets(context.targets);
if (css.css_properties.masking.getWebkitMaskProperty(rtl_props.at(i))) |property_id| {
if (context.targets.prefixes(VendorPrefix.NONE, Feature.mask_border).webkit) {
rtl_props.insert(context.allocator, i, property_id);
i += 1;
}
}
}
i += 1;
}
}
return rtl_properties;
}
const LogicalPropertyId = union(enum) {
none,
block: struct { css.compat.Feature, []const PropertyId },
@"inline": struct { css.compat.Feature, []const PropertyId, []const PropertyId },
};
fn getLogicalProperties(property_id: *const PropertyId) LogicalPropertyId {
return switch (property_id.*) {
.@"block-size" => .{ .block = .{ .logical_size, &[_]PropertyId{.height} } },
.@"inline-size" => .{ .@"inline" = .{ .logical_size, &[_]PropertyId{.width}, &[_]PropertyId{.height} } },
.@"min-block-size" => .{ .block = .{ .logical_size, &[_]PropertyId{.@"min-height"} } },
.@"max-block-size" => .{ .block = .{ .logical_size, &[_]PropertyId{.@"max-height"} } },
.@"min-inline-size" => .{ .@"inline" = .{ .logical_size, &[_]PropertyId{.@"min-width"}, &[_]PropertyId{.@"min-height"} } },
.@"max-inline-size" => .{ .@"inline" = .{ .logical_size, &[_]PropertyId{.@"max-width"}, &[_]PropertyId{.@"max-height"} } },
.@"inset-block-start" => .{ .block = .{ .logical_inset, &[_]PropertyId{.top} } },
.@"inset-block-end" => .{ .block = .{ .logical_inset, &[_]PropertyId{.bottom} } },
.@"inset-inline-start" => .{ .@"inline" = .{ .logical_inset, &[_]PropertyId{.left}, &[_]PropertyId{.right} } },
.@"inset-inline-end" => .{ .@"inline" = .{ .logical_inset, &[_]PropertyId{.right}, &[_]PropertyId{.left} } },
.@"inset-block" => .{ .block = .{ .logical_inset, &[_]PropertyId{ .top, .bottom } } },
.@"inset-inline" => .{ .block = .{ .logical_inset, &[_]PropertyId{ .left, .right } } },
.inset => .{ .block = .{ .logical_inset, &[_]PropertyId{ .top, .bottom, .left, .right } } },
.@"margin-block-start" => .{ .block = .{ .logical_margin, &[_]PropertyId{.@"margin-top"} } },
.@"margin-block-end" => .{ .block = .{ .logical_margin, &[_]PropertyId{.@"margin-bottom"} } },
.@"margin-inline-start" => .{ .@"inline" = .{ .logical_margin, &[_]PropertyId{.@"margin-left"}, &[_]PropertyId{.@"margin-right"} } },
.@"margin-inline-end" => .{ .@"inline" = .{ .logical_margin, &[_]PropertyId{.@"margin-right"}, &[_]PropertyId{.@"margin-left"} } },
.@"margin-block" => .{ .block = .{ .logical_margin, &[_]PropertyId{ .@"margin-top", .@"margin-bottom" } } },
.@"margin-inline" => .{ .block = .{ .logical_margin, &[_]PropertyId{ .@"margin-left", .@"margin-right" } } },
.@"padding-block-start" => .{ .block = .{ .logical_padding, &[_]PropertyId{.@"padding-top"} } },
.@"padding-block-end" => .{ .block = .{ .logical_padding, &[_]PropertyId{.@"padding-bottom"} } },
.@"padding-inline-start" => .{ .@"inline" = .{ .logical_padding, &[_]PropertyId{.@"padding-left"}, &[_]PropertyId{.@"padding-right"} } },
.@"padding-inline-end" => .{ .@"inline" = .{ .logical_padding, &[_]PropertyId{.@"padding-right"}, &[_]PropertyId{.@"padding-left"} } },
.@"padding-block" => .{ .block = .{ .logical_padding, &[_]PropertyId{ .@"padding-top", .@"padding-bottom" } } },
.@"padding-inline" => .{ .block = .{ .logical_padding, &[_]PropertyId{ .@"padding-left", .@"padding-right" } } },
.@"border-block-start" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top"} } },
.@"border-block-start-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top-width"} } },
.@"border-block-start-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top-color"} } },
.@"border-block-start-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top-style"} } },
.@"border-block-end" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom"} } },
.@"border-block-end-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom-width"} } },
.@"border-block-end-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom-color"} } },
.@"border-block-end-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom-style"} } },
.@"border-inline-start" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left"}, &[_]PropertyId{.@"border-right"} } },
.@"border-inline-start-width" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left-width"}, &[_]PropertyId{.@"border-right-width"} } },
.@"border-inline-start-color" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left-color"}, &[_]PropertyId{.@"border-right-color"} } },
.@"border-inline-start-style" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left-style"}, &[_]PropertyId{.@"border-right-style"} } },
.@"border-inline-end" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right"}, &[_]PropertyId{.@"border-left"} } },
.@"border-inline-end-width" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right-width"}, &[_]PropertyId{.@"border-left-width"} } },
.@"border-inline-end-color" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right-color"}, &[_]PropertyId{.@"border-left-color"} } },
.@"border-inline-end-style" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right-style"}, &[_]PropertyId{.@"border-left-style"} } },
.@"border-block" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top", .@"border-bottom" } } },
.@"border-block-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top-color", .@"border-bottom-color" } } },
.@"border-block-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top-width", .@"border-bottom-width" } } },
.@"border-block-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top-style", .@"border-bottom-style" } } },
.@"border-inline" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left", .@"border-right" } } },
.@"border-inline-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left-color", .@"border-right-color" } } },
.@"border-inline-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left-width", .@"border-right-width" } } },
.@"border-inline-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left-style", .@"border-right-style" } } },
.@"border-start-start-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-top-left-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-top-right-radius" = VendorPrefix.NONE }} } },
.@"border-start-end-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-top-right-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-top-left-radius" = VendorPrefix.NONE }} } },
.@"border-end-start-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-bottom-left-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-bottom-right-radius" = VendorPrefix.NONE }} } },
.@"border-end-end-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-bottom-right-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-bottom-left-radius" = VendorPrefix.NONE }} } },
else => .none,
};
}
fn isTransitionProperty(property_id: *const PropertyId) bool {
return switch (property_id.*) {
.@"transition-property",
.@"transition-duration",
.@"transition-delay",
.@"transition-timing-function",
.transition,
=> true,
else => false,
};
}
const bun = @import("bun");
const std = @import("std");
const Allocator = std.mem.Allocator;