Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support calculation in outline-* #1219

Merged
merged 6 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 19 additions & 20 deletions docs/review/api/alfa-css.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ export class Calculation<out D extends Calculation.Dimension = Calculation.Dimen
static of(expression: Calculation.Expression): Calculation;
// (undocumented)
reduce(resolver: Calculation.Resolver): Calculation;
resolve(this: Calculation<"length">, resolver: Calculation.LengthResolver): Option<Length<"px">>;
// (undocumented)
resolve(this: Calculation<"length-percentage">, resolver: Calculation.Resolver<"px", Length<"px">>): Option<Length<"px">>;
// (undocumented)
resolve(this: Calculation<"number">, resolver: Calculation.Resolver<"px", Length<"px">>): Option<Number_2>;
resolve(this: Calculation<"number">, resolver: Calculation.PercentageResolver): Option<Number_2>;
// (undocumented)
toJSON(): Calculation.JSON;
// (undocumented)
Expand All @@ -156,8 +158,6 @@ export namespace Calculation {
abstract equals(value: unknown): value is this;
// (undocumented)
abstract get kind(): Kind;
// Warning: (ae-incompatible-release-tags) The symbol "reduce" is marked as @public, but its signature references "Resolver" which is marked as @internal
//
// (undocumented)
abstract reduce<L extends Unit.Length = "px", P extends Numeric = Numeric>(resolver: Resolver<L, P>): Expression;
// (undocumented)
Expand Down Expand Up @@ -189,8 +189,6 @@ export namespace Calculation {
get kind(): Kind;
// (undocumented)
static of(operand: Expression): Invert;
// Warning: (ae-incompatible-release-tags) The symbol "reduce" is marked as @public, but its signature references "Resolver" which is marked as @internal
//
// (undocumented)
reduce<L extends Unit.Length = "px", P extends Numeric = Numeric>(resolver: Resolver<L, P>): Expression;
// (undocumented)
Expand Down Expand Up @@ -268,12 +266,15 @@ export namespace Calculation {
[K in Base]: number;
}>;
}
// @internal
export interface LengthResolver<L extends Unit.Length = "px"> {
// (undocumented)
length(value: Length<Unit.Length.Relative>): Length<L>;
}
// (undocumented)
export class Negate extends Operation.Unary {
// (undocumented)
static of(operand: Expression): Negate;
// Warning: (ae-incompatible-release-tags) The symbol "reduce" is marked as @public, but its signature references "Resolver" which is marked as @internal
//
// (undocumented)
reduce<L extends Unit.Length = "px", P extends Numeric = Numeric>(resolver: Resolver<L, P>): Expression;
// (undocumented)
Expand Down Expand Up @@ -316,31 +317,29 @@ export namespace Calculation {
}
}
// (undocumented)
export interface PercentageResolver<P extends Numeric = Numeric> {
// (undocumented)
percentage(value: Percentage): P;
}
// (undocumented)
export class Product extends Operation.Binary {
// (undocumented)
static of(...operands: [Expression, Expression]): Result<Product, string>;
// Warning: (ae-incompatible-release-tags) The symbol "reduce" is marked as @public, but its signature references "Resolver" which is marked as @internal
//
// (undocumented)
reduce<L extends Unit.Length = "px", P extends Numeric = Numeric>(resolver: Resolver<L, P>): Expression;
// (undocumented)
toString(): string;
// (undocumented)
get type(): "product";
}
// @internal
export interface Resolver<L extends Unit.Length = "px", P extends Numeric = Numeric> {
// (undocumented)
length(value: Length<Unit.Length.Relative>): Length<L>;
// (undocumented)
percentage(value: Percentage): P;
}
// Warning: (ae-incompatible-release-tags) The symbol "Resolver" is marked as @public, but its signature references "LengthResolver" which is marked as @internal
//
// (undocumented)
export type Resolver<L extends Unit.Length = "px", P extends Numeric = Numeric> = LengthResolver<L> & PercentageResolver<P>;
// (undocumented)
export class Sum extends Operation.Binary {
// (undocumented)
static of(...operands: [Expression, Expression]): Result<Sum, string>;
// Warning: (ae-incompatible-release-tags) The symbol "reduce" is marked as @public, but its signature references "Resolver" which is marked as @internal
//
// (undocumented)
reduce<L extends Unit.Length = "px", P extends Numeric = Numeric>(resolver: Resolver<L, P>): Expression;
// (undocumented)
Expand All @@ -356,8 +355,6 @@ export namespace Calculation {
get kind(): Kind;
// (undocumented)
static of(value: Numeric): Value;
// Warning: (ae-incompatible-release-tags) The symbol "reduce" is marked as @public, but its signature references "Resolver" which is marked as @internal
//
// (undocumented)
reduce<L extends Unit.Length = "px", P extends Numeric = Numeric>(resolver: Resolver<L, P>): Value;
// (undocumented)
Expand All @@ -384,6 +381,8 @@ export namespace Calculation {
// (undocumented)
parse: Parser<Slice<Token>, Calculation<Dimension>, string, []>;
const // (undocumented)
parseLength: Parser<Slice<Token>, Calculation<"length">, string, []>;
const // (undocumented)
parseLengthPercentage: Parser<Slice<Token>, Calculation<"length-percentage">, string, []>;
const // (undocumented)
parseLengthNumberPercentage: Parser<Slice<Token>, Calculation<"number"> | Calculation<"length-percentage">, string, []>;
Expand Down
32 changes: 25 additions & 7 deletions packages/alfa-css/src/value/calculation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,22 @@ export class Calculation<

// Other resolvers should be added when needed.
/**
* Resolves a calculation typed as a length-percentage or number.
* Resolves a calculation typed as a length, length-percentage or number.
* Needs a resolver to handle relative lengths and percentages.
*/
public resolve(
this: Calculation<"length">,
resolver: Calculation.LengthResolver
): Option<Length<"px">>;

public resolve(
this: Calculation<"length-percentage">,
resolver: Calculation.Resolver<"px", Length<"px">>
): Option<Length<"px">>;

public resolve(
this: Calculation<"number">,
resolver: Calculation.Resolver<"px", Length<"px">>
resolver: Calculation.PercentageResolver
): Option<Number>;

public resolve(
Expand All @@ -134,7 +139,8 @@ export class Calculation<
const expression = this._expression.reduce(resolver);

return this.isDimensionPercentage("length")
? expression.toLength()
? // length are also length-percentage, so this catches both.
expression.toLength()
: this.isNumber()
? expression.toNumber()
: None;
Expand Down Expand Up @@ -188,14 +194,19 @@ export namespace Calculation {
*
* @internal
*/
export interface Resolver<
L extends Unit.Length = "px",
P extends Numeric = Numeric
> {
export interface LengthResolver<L extends Unit.Length = "px"> {
length(value: Length<Unit.Length.Relative>): Length<L>;
}

export interface PercentageResolver<P extends Numeric = Numeric> {
percentage(value: Percentage): P;
}

export type Resolver<
L extends Unit.Length = "px",
P extends Numeric = Numeric
> = LengthResolver<L> & PercentageResolver<P>;

function angleResolver(angle: Angle): Angle<"deg"> {
return angle.withUnit("deg");
}
Expand Down Expand Up @@ -971,6 +982,13 @@ export namespace Calculation {
export const parse = map(parseCalc, Calculation.of);

// other parsers + filters can be added when needed
export const parseLength = filter(
parse,
(calculation): calculation is Calculation<"length"> =>
calculation.isDimension("length"),
() => `calc() expression must be of type "length"`
);

export const parseLengthPercentage = filter(
parse,
(calculation): calculation is Calculation<"length-percentage"> =>
Expand Down
15 changes: 5 additions & 10 deletions packages/alfa-style/src/property/font-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,25 +111,20 @@ export default Property.register(
parse,
(fontSize, style) =>
fontSize.map((fontSize) => {
const percentageResolver = Resolver.percentage(
const percentage = Resolver.percentage(
style.parent.computed("font-size").value
);
const lengthResolver = Resolver.length(style.parent);
const length = Resolver.length(style.parent);

switch (fontSize.type) {
case "calculation":
return fontSize
.resolve({
length: lengthResolver,
percentage: percentageResolver,
})
.get();
return fontSize.resolve({ length, percentage }).get();

case "length":
return lengthResolver(fontSize);
return length(fontSize);

case "percentage": {
return percentageResolver(fontSize);
return percentage(fontSize);
}

case "keyword": {
Expand Down
20 changes: 6 additions & 14 deletions packages/alfa-style/src/property/line-height.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,35 +58,27 @@ export default Property.register(
parse,
(value, style) =>
value.map((height) => {
const percentageResolver = Resolver.percentage(
const percentage = Resolver.percentage(
style.parent.computed("font-size").value
);
const lengthResolver = Resolver.length(style);
const length = Resolver.length(style);

switch (height.type) {
case "keyword":
case "number":
return height;

case "length":
return lengthResolver(height);
return length(height);

case "percentage":
return percentageResolver(height);
return percentage(height);

case "calculation":
// TS can't see that the union is exactly covered by the overloads
// so we have to do this ugly split :-/
return (
height.isNumber()
? height.resolve({
length: lengthResolver,
percentage: percentageResolver,
})
: height.resolve({
length: lengthResolver,
percentage: percentageResolver,
})
? height.resolve({ percentage })
: height.resolve({ length, percentage })
).get();
}
}),
Expand Down
25 changes: 17 additions & 8 deletions packages/alfa-style/src/property/outline-offset.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Length } from "@siteimprove/alfa-css";
import { Calculation, Length } from "@siteimprove/alfa-css";
import { Parser } from "@siteimprove/alfa-parser";

import { Property } from "../property";
import { Resolver } from "../resolver";

const { either } = Parser;

declare module "../property" {
interface Longhands {
"outline-offset": Property<Specified, Computed>;
Expand All @@ -12,7 +15,7 @@ declare module "../property" {
/**
* @internal
*/
export type Specified = Length;
export type Specified = Length | Calculation<"length">;

/**
* @internal
Expand All @@ -22,17 +25,23 @@ export type Computed = Length<"px">;
/**
* @internal
*/
export const parse = Length.parse;
export const parse = either(Length.parse, Calculation.parseLength);

/**
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/outline-offset}
*/
export default Property.register(
"outline-offset",
Property.of<Specified, Computed>(
Length.of(0, "px"),
parse,
(outlineOffset, style) =>
outlineOffset.map((offset) => Resolver.length(offset, style))
Property.of<Specified, Computed>(Length.of(0, "px"), parse, (value, style) =>
value.map((offset) => {
const length = Resolver.length(style);

switch (offset.type) {
case "length":
return length(offset);
case "calculation":
return offset.resolve({ length }).get();
}
})
)
);
28 changes: 18 additions & 10 deletions packages/alfa-style/src/property/outline-width.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Keyword, Length } from "@siteimprove/alfa-css";
import { Calculation, Keyword, Length, Token } from "@siteimprove/alfa-css";
import { Parser } from "@siteimprove/alfa-parser";
import { Slice } from "@siteimprove/alfa-slice";

import { Property } from "../property";
import { Resolver } from "../resolver";
Expand All @@ -18,6 +19,7 @@ declare module "../property" {
*/
export type Specified =
| Length
| Calculation<"length">
| Keyword<"thin">
| Keyword<"medium">
| Keyword<"thick">;
Expand All @@ -30,9 +32,10 @@ export type Computed = Length<"px">;
/**
* @internal
*/
export const parse = either(
export const parse = either<Slice<Token>, Specified, string>(
Keyword.parse("thin", "medium", "thick"),
Length.parse
Length.parse,
Calculation.parseLength
);

/**
Expand All @@ -44,17 +47,25 @@ export default Property.register(
Property.of<Specified, Computed>(
Length.of(3, "px"),
parse,
(outlineWidth, style) => {
(value, style) => {
if (
style.computed("outline-style").some(({ value }) => value === "none")
) {
return Value.of(Length.of(0, "px"));
}

return outlineWidth.map((value) => {
switch (value.type) {
return value.map((width) => {
const length = Resolver.length(style);

switch (width.type) {
case "length":
return length(width);

case "calculation":
return width.resolve({ length }).get();

case "keyword":
switch (value.value) {
switch (width.value) {
case "thin":
return Length.of(1, "px");

Expand All @@ -64,9 +75,6 @@ export default Property.register(
case "thick":
return Length.of(5, "px");
}

case "length":
return Resolver.length(value, style);
}
});
}
Expand Down
Loading