Files
bun.sh/src/css/rules/container.zig
Zack Radisic 6b8fd718c2 Various CSS stuff (#14499)
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2024-10-12 07:00:20 -07:00

356 lines
12 KiB
Zig

const std = @import("std");
pub const css = @import("../css_parser.zig");
const bun = @import("root").bun;
const Result = css.Result;
const ArrayList = std.ArrayListUnmanaged;
const MediaList = css.MediaList;
const CustomMedia = css.CustomMedia;
const Printer = css.Printer;
const Maybe = css.Maybe;
const PrinterError = css.PrinterError;
const PrintErr = css.PrintErr;
const Location = css.css_rules.Location;
const CustomIdent = css.css_values.ident.CustomIdent;
const CustomIdentFns = css.css_values.ident.CustomIdentFns;
const QueryFeature = css.media_query.QueryFeature;
const QueryConditionFlags = css.media_query.QueryConditionFlags;
const Operator = css.media_query.Operator;
pub const ContainerName = struct {
v: css.css_values.ident.CustomIdent,
pub fn parse(input: *css.Parser) Result(ContainerName) {
const ident = switch (CustomIdentFns.parse(input)) {
.result => |vv| vv,
.err => |e| return .{ .err = e },
};
// todo_stuff.match_ignore_ascii_case;
if (bun.strings.eqlCaseInsensitiveASCIIICheckLength("none", ident.v) or
bun.strings.eqlCaseInsensitiveASCIIICheckLength("and", ident.v) or
bun.strings.eqlCaseInsensitiveASCIIICheckLength("not", ident.v) or
bun.strings.eqlCaseInsensitiveASCIIICheckLength("or", ident.v))
return .{ .err = input.newUnexpectedTokenError(.{ .ident = ident.v }) };
return .{ .result = ContainerName{ .v = ident } };
}
const This = @This();
pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void {
return try CustomIdentFns.toCss(&this.v, W, dest);
}
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
return css.implementDeepClone(@This(), this, allocator);
}
};
pub const ContainerNameFns = ContainerName;
pub const ContainerSizeFeature = QueryFeature(ContainerSizeFeatureId);
pub const ContainerSizeFeatureId = enum {
/// The [width](https://w3c.github.io/csswg-drafts/css-contain-3/#width) size container feature.
width,
/// The [height](https://w3c.github.io/csswg-drafts/css-contain-3/#height) size container feature.
height,
/// The [inline-size](https://w3c.github.io/csswg-drafts/css-contain-3/#inline-size) size container feature.
@"inline-size",
/// The [block-size](https://w3c.github.io/csswg-drafts/css-contain-3/#block-size) size container feature.
@"block-size",
/// The [aspect-ratio](https://w3c.github.io/csswg-drafts/css-contain-3/#aspect-ratio) size container feature.
@"aspect-ratio",
/// The [orientation](https://w3c.github.io/csswg-drafts/css-contain-3/#orientation) size container feature.
orientation,
pub usingnamespace css.DeriveValueType(@This());
pub const ValueTypeMap = .{
.width = css.MediaFeatureType.length,
.height = css.MediaFeatureType.length,
.@"inline-size" = css.MediaFeatureType.length,
.@"block-size" = css.MediaFeatureType.length,
.@"aspect-ratio" = css.MediaFeatureType.ratio,
.orientation = css.MediaFeatureType.ident,
};
pub fn asStr(this: *const @This()) []const u8 {
return css.enum_property_util.asStr(@This(), this);
}
pub fn parse(input: *css.Parser) Result(@This()) {
return css.enum_property_util.parse(@This(), input);
}
pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void {
return css.enum_property_util.toCss(@This(), this, W, dest);
}
pub fn toCssWithPrefix(this: *const @This(), prefix: []const u8, comptime W: type, dest: *Printer(W)) PrintErr!void {
try dest.writeStr(prefix);
try this.toCss(W, dest);
}
};
/// Represents a style query within a container condition.
pub const StyleQuery = union(enum) {
/// A style feature, implicitly parenthesized.
feature: css.Property,
/// A negation of a condition.
not: *StyleQuery,
/// A set of joint operations.
operation: struct {
/// The operator for the conditions.
operator: css.media_query.Operator,
/// The conditions for the operator.
conditions: ArrayList(StyleQuery),
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
return css.implementDeepClone(@This(), this, allocator);
}
},
pub fn toCss(this: *const StyleQuery, comptime W: type, dest: *Printer(W)) PrintErr!void {
switch (this.*) {
.feature => |f| try f.toCss(W, dest, false),
.not => |c| {
try dest.writeStr("not ");
return try css.media_query.toCssWithParensIfNeeded(
c,
W,
dest,
c.needsParens(null, &dest.targets),
);
},
.operation => |op| return css.media_query.operationToCss(
StyleQuery,
op.operator,
&op.conditions,
W,
dest,
),
}
}
pub fn parseFeature(input: *css.Parser) Result(StyleQuery) {
const property_id = switch (css.PropertyId.parse(input)) {
.result => |vv| vv,
.err => |e| return .{ .err = e },
};
if (input.expectColon().asErr()) |e| return .{ .err = e };
input.skipWhitespace();
const opts = css.ParserOptions.default(input.allocator(), null);
const feature = .{
.feature = switch (css.Property.parse(
property_id,
input,
&opts,
)) {
.result => |vv| vv,
.err => |e| return .{ .err = e },
},
};
_ = input.tryParse(css.parseImportant, .{});
return .{ .result = feature };
}
pub fn createNegation(condition: *StyleQuery) StyleQuery {
return .{ .not = condition };
}
pub fn createOperation(operator: Operator, conditions: ArrayList(StyleQuery)) StyleQuery {
return .{
.operation = .{
.operator = operator,
.conditions = conditions,
},
};
}
pub fn needsParens(
this: *const StyleQuery,
parent_operator: ?Operator,
_: *const css.Targets,
) bool {
return switch (this.*) {
.not => true,
.operation => |op| op.operator == parent_operator,
.feature => true,
};
}
pub fn parseStyleQuery(input: *css.Parser) Result(@This()) {
return .{ .err = input.newErrorForNextToken() };
}
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
return css.implementDeepClone(@This(), this, allocator);
}
};
pub const ContainerCondition = union(enum) {
/// A size container feature, implicitly parenthesized.
feature: ContainerSizeFeature,
/// A negation of a condition.
not: *ContainerCondition,
/// A set of joint operations.
operation: struct {
/// The operator for the conditions.
operator: css.media_query.Operator,
/// The conditions for the operator.
conditions: ArrayList(ContainerCondition),
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
return css.implementDeepClone(@This(), this, allocator);
}
},
/// A style query.
style: StyleQuery,
const This = @This();
pub fn parse(input: *css.Parser) Result(ContainerCondition) {
return css.media_query.parseQueryCondition(
ContainerCondition,
input,
QueryConditionFlags{
.allow_or = true,
.allow_style = true,
},
);
}
pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void {
switch (this.*) {
.feature => |f| try f.toCss(W, dest),
.not => |c| {
try dest.writeStr("not ");
return try css.media_query.toCssWithParensIfNeeded(
c,
W,
dest,
c.needsParens(null, &dest.targets),
);
},
.operation => |op| try css.media_query.operationToCss(ContainerCondition, op.operator, &op.conditions, W, dest),
.style => |query| {
try dest.writeStr("style(");
try query.toCss(W, dest);
try dest.writeChar(')');
},
}
}
pub fn parseFeature(input: *css.Parser) Result(ContainerCondition) {
const feature = switch (QueryFeature(ContainerSizeFeatureId).parse(input)) {
.result => |vv| vv,
.err => |e| return .{ .err = e },
};
return .{ .result = .{ .feature = feature } };
}
pub fn createNegation(condition: *ContainerCondition) ContainerCondition {
return .{ .not = condition };
}
pub fn createOperation(operator: Operator, conditions: ArrayList(ContainerCondition)) ContainerCondition {
return .{
.operation = .{
.operator = operator,
.conditions = conditions,
},
};
}
pub fn parseStyleQuery(input: *css.Parser) Result(ContainerCondition) {
const Fns = struct {
pub inline fn adaptedParseQueryCondition(i: *css.Parser, flags: QueryConditionFlags) Result(StyleQuery) {
return css.media_query.parseQueryCondition(StyleQuery, i, flags);
}
pub fn parseNestedBlockFn(_: void, i: *css.Parser) Result(ContainerCondition) {
if (i.tryParse(
@This().adaptedParseQueryCondition,
.{
QueryConditionFlags{ .allow_or = true },
},
).asValue()) |res| {
return .{ .result = .{ .style = res } };
}
return .{ .result = .{
.style = switch (StyleQuery.parseFeature(i)) {
.result => |vv| vv,
.err => |e| return .{ .err = e },
},
} };
}
};
return input.parseNestedBlock(ContainerCondition, {}, Fns.parseNestedBlockFn);
}
pub fn needsParens(
this: *const ContainerCondition,
parent_operator: ?Operator,
targets: *const css.Targets,
) bool {
return switch (this.*) {
.not => true,
.operation => |op| op.operator == parent_operator,
.feature => |f| f.needsParens(parent_operator, targets),
.style => false,
};
}
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() {
return css.implementDeepClone(@This(), this, allocator);
}
};
/// A [@container](https://drafts.csswg.org/css-contain-3/#container-rule) rule.
pub fn ContainerRule(comptime R: type) type {
return struct {
/// The name of the container.
name: ?ContainerName,
/// The container condition.
condition: ContainerCondition,
/// The rules within the `@container` rule.
rules: css.CssRuleList(R),
/// The location of the rule in the source file.
loc: Location,
const This = @This();
pub fn toCss(this: *const This, comptime W: type, dest: *Printer(W)) PrintErr!void {
// #[cfg(feature = "sourcemap")]
// dest.add_mapping(self.loc);
try dest.writeStr("@container ");
if (this.name) |*name| {
try name.toCss(W, dest);
try dest.writeChar(' ');
}
// Don't downlevel range syntax in container queries.
const exclude = dest.targets.exclude;
dest.targets.exclude.insert(css.targets.Features.media_queries);
try this.condition.toCss(W, dest);
dest.targets.exclude = exclude;
try dest.whitespace();
try dest.writeChar('{');
dest.indent();
try dest.newline();
try this.rules.toCss(W, dest);
dest.dedent();
try dest.newline();
try dest.writeChar('}');
}
pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) This {
return css.implementDeepClone(@This(), this, allocator);
}
};
}