Skip to content

Commit

Permalink
Merge branch 'main' into fix-rule-uri
Browse files Browse the repository at this point in the history
  • Loading branch information
Jym77 authored Dec 13, 2024
2 parents 021ea7f + 3a3e6e5 commit 00e2dc2
Show file tree
Hide file tree
Showing 32 changed files with 1,913 additions and 163 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-clouds-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siteimprove/alfa-style": minor
---

**Added:** CSS shorthand property `mask` and corresponding longhand properties are now supported.
5 changes: 5 additions & 0 deletions .changeset/rare-lies-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siteimprove/alfa-css": minor
---

**Added:** `List#cutOrExtend` is now available.
5 changes: 5 additions & 0 deletions .changeset/tame-ants-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siteimprove/alfa-css": minor
---

**Added:** `List#size` is now available.
37 changes: 34 additions & 3 deletions docs/review/api/alfa-css.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,18 +201,36 @@ export type Box = Keyword<"border-box"> | Keyword<"padding-box"> | Keyword<"cont
// @public (undocumented)
export namespace Box {
// (undocumented)
export type Geometry = Shape | Keyword<"fill-box"> | Keyword<"stroke-box"> | Keyword<"view-box">;
export type CoordBox = PaintBox | Keyword<"view-box">;
const // (undocumented)
parse: Parser<Box>;
// (undocumented)
export namespace CoordBox {
// (undocumented)
export type JSON = PaintBox.JSON | Keyword.JSON<"view-box">;
}
// (undocumented)
export type Geometry = Shape | Keyword<"fill-box"> | Keyword<"stroke-box"> | Keyword<"view-box">;
const // (undocumented)
parseShape: Parser<Shape>;
// (undocumented)
export namespace Geometry {
// (undocumented)
export type JSON = Shape.JSON | Keyword.JSON<"fill-box"> | Keyword.JSON<"stroke-box"> | Keyword.JSON<"view-box">;
}
// (undocumented)
export type JSON = Keyword.JSON<"border-box"> | Keyword.JSON<"padding-box"> | Keyword.JSON<"content-box">;
const // (undocumented)
parseShape: Parser<Shape>;
parseGeometry: Parser<Geometry>;
// (undocumented)
export type PaintBox = VisualBox | Keyword<"fill-box"> | Keyword<"stroke-box">;
// (undocumented)
export namespace PaintBox {
// (undocumented)
export type JSON = VisualBox.JSON | Keyword.JSON<"fill-box"> | Keyword.JSON<"stroke-box">;
}
const // (undocumented)
parseVisualBox: Parser<VisualBox>;
// (undocumented)
export type Shape = Box | Keyword<"margin-box">;
// (undocumented)
Expand All @@ -221,7 +239,17 @@ export namespace Box {
export type JSON = Box.JSON | Keyword.JSON<"margin-box">;
}
const // (undocumented)
parseGeometry: Parser<Geometry>;
parsePaintBox: Parser<PaintBox>;
// (undocumented)
export type VisualBox = Keyword<"content-box"> | Keyword<"padding-box"> | Keyword<"border-box">;
// (undocumented)
export namespace VisualBox {
// (undocumented)
export type JSON = Keyword.JSON<"content-box"> | Keyword.JSON<"padding-box"> | Keyword.JSON<"border-box">;
}
const // (undocumented)
parseCoordBox: Parser<CoordBox>;
{};
}

// Warning: (ae-forgotten-export) The symbol "BasicShape" needs to be exported by the entry point index.d.ts
Expand Down Expand Up @@ -1230,6 +1258,7 @@ export namespace Lexer {
export class List<V extends Value> extends Value<"list", Value.HasCalculation<[V]>> implements Iterable_2<V>, Resolvable<List<Resolvable.Resolved<V>>, Resolvable.Resolver<V>>, PartiallyResolvable<List<Resolvable.PartiallyResolved<V>>, Resolvable.PartialResolver<V>> {
// (undocumented)
[Symbol.iterator](): Iterator<V>;
cutOrExtend(length: number): List<V>;
// (undocumented)
equals<T extends Value>(value: List<T>): boolean;
// (undocumented)
Expand All @@ -1245,6 +1274,8 @@ export class List<V extends Value> extends Value<"list", Value.HasCalculation<[V
// (undocumented)
resolve(resolver?: Resolvable.Resolver<V>): List<Resolvable.Resolved<V>>;
// (undocumented)
get size(): number;
// (undocumented)
toJSON(): List.JSON<V>;
// (undocumented)
toString(): string;
Expand Down
334 changes: 180 additions & 154 deletions docs/review/api/alfa-style.api.md

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions packages/alfa-css/src/value/box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,62 @@ export namespace Box {
parseShape,
Keyword.parse("fill-box", "stroke-box", "view-box"),
);

/**
* {@link https://www.w3.org/TR/css-box-4/#typedef-visual-box}
*/
export type VisualBox =
| Keyword<"content-box">
| Keyword<"padding-box">
| Keyword<"border-box">;

namespace VisualBox {
export type JSON =
| Keyword.JSON<"content-box">
| Keyword.JSON<"padding-box">
| Keyword.JSON<"border-box">;
}

export const parseVisualBox: CSSParser<VisualBox> = Keyword.parse(
"content-box",
"padding-box",
"border-box",
);

/**
* {@link https://www.w3.org/TR/css-box-4/#typedef-paint-box}
*/
export type PaintBox =
| VisualBox
| Keyword<"fill-box">
| Keyword<"stroke-box">;

namespace PaintBox {
export type JSON =
| VisualBox.JSON
| Keyword.JSON<"fill-box">
| Keyword.JSON<"stroke-box">;
}

export const parsePaintBox: CSSParser<PaintBox> = either(
parseVisualBox,
Keyword.parse("fill-box", "stroke-box"),
);

/**
* {@link https://www.w3.org/TR/css-box-4/#typedef-coord-box}
*
* @privateRemarks
* This is not the same type as `Geometry`. The only difference is that this type does not contain `margin-box`.
*/
export type CoordBox = PaintBox | Keyword<"view-box">;

export namespace CoordBox {
export type JSON = PaintBox.JSON | Keyword.JSON<"view-box">;
}

export const parseCoordBox: CSSParser<CoordBox> = either(
parsePaintBox,
Keyword.parse("view-box"),
);
}
31 changes: 31 additions & 0 deletions packages/alfa-css/src/value/collection/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export class List<V extends Value>
return this._values;
}

public get size(): number {
return this._values.length;
}

public resolve(
resolver?: Resolvable.Resolver<V>,
): List<Resolvable.Resolved<V>> {
Expand All @@ -65,6 +69,33 @@ export class List<V extends Value>
return new List(this._values.map(mapper), this._separator);
}

/**
* Returns a copy of the current instance cut off or extended with repeated values to match the given `length`.
*
* @example
* List.of([1, 2, 3]).cutOrExtend(2); // returns a new List with values [1, 2]
*
* @example
* List.of([1, 2, 3]).cutOrExtend(5); // returns a new List with values [1, 2, 3, 1, 2]
*/
public cutOrExtend(length: number): List<V> {
if (this.size === length) {
return new List(this._values, this._separator);
}

if (length < this.size) {
return new List(this._values.slice(0, length), this._separator);
}

const extended: Array<V> = [];
for (let i = 0; i < length; ++i) {
// Cyclically repeat the values until the result has the desired length.
extended.push(this._values[i % this.size]);
}

return new List(extended, this._separator);
}

public equals<T extends Value>(value: List<T>): boolean;

public equals(value: unknown): value is this;
Expand Down
43 changes: 39 additions & 4 deletions packages/alfa-css/test/value/collection/list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import type { Parser } from "@siteimprove/alfa-parser";
import type { Slice } from "@siteimprove/alfa-slice";
import { test } from "@siteimprove/alfa-test";

import type {
Token,
Value} from "../../../dist/index.js";
import type { Token, Value } from "../../../dist/index.js";
import {
Length,
LengthPercentage,
Lexer,
List,
Number
Number,
} from "../../../dist/index.js";

function parse<V extends Value>(
Expand Down Expand Up @@ -68,3 +66,40 @@ test("resolve() resolves all values in a list", (t) => {
separator: ", ",
});
});

test("#cutOrExtend returns identical copy if list has the same length as the given length", (t) => {
t.deepEqual(parse("1, 2, 3", Number.parse).cutOrExtend(3).toJSON(), {
type: "list",
values: [
{ type: "number", value: 1 },
{ type: "number", value: 2 },
{ type: "number", value: 3 },
],
separator: ", ",
});
});

test("#cutOrExtend cuts a list longer than the given length", (t) => {
t.deepEqual(parse("1, 2, 3", Number.parse).cutOrExtend(2).toJSON(), {
type: "list",
values: [
{ type: "number", value: 1 },
{ type: "number", value: 2 },
],
separator: ", ",
});
});

test("#cutOrExtend repeats a list shorter than the given length", (t) => {
t.deepEqual(parse("1, 2, 3", Number.parse).cutOrExtend(5).toJSON(), {
type: "list",
values: [
{ type: "number", value: 1 },
{ type: "number", value: 2 },
{ type: "number", value: 3 },
{ type: "number", value: 1 },
{ type: "number", value: 2 },
],
separator: ", ",
});
});
16 changes: 16 additions & 0 deletions packages/alfa-style/src/longhands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ import MarginBottom from "./property/margin-bottom.js";
import MarginLeft from "./property/margin-left.js";
import MarginRight from "./property/margin-right.js";
import MarginTop from "./property/margin-top.js";
import MaskClip from "./property/mask-clip.js";
import MaskComposite from "./property/mask-composite.js";
import MaskImage from "./property/mask-image.js";
import MaskMode from "./property/mask-mode.js";
import MaskOrigin from "./property/mask-origin.js";
import MaskPosition from "./property/mask-position.js";
import MaskRepeat from "./property/mask-repeat.js";
import MaskSize from "./property/mask-size.js";
import MinHeight from "./property/min-height.js";
import MinWidth from "./property/min-width.js";
import MixBlendMode from "./property/mix-blend-mode.js";
Expand Down Expand Up @@ -268,6 +276,14 @@ export namespace Longhands {
"margin-left": MarginLeft,
"margin-right": MarginRight,
"margin-top": MarginTop,
"mask-clip": MaskClip,
"mask-composite": MaskComposite,
"mask-image": MaskImage,
"mask-mode": MaskMode,
"mask-origin": MaskOrigin,
"mask-position": MaskPosition,
"mask-repeat": MaskRepeat,
"mask-size": MaskSize,
"min-height": MinHeight,
"min-width": MinWidth,
"mix-blend-mode": MixBlendMode,
Expand Down
41 changes: 41 additions & 0 deletions packages/alfa-style/src/property/mask-clip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Parser } from "@siteimprove/alfa-parser";
import { Box, Keyword, List } from "@siteimprove/alfa-css";

import { Longhand } from "../longhand.js";
import { Resolver } from "../resolver.js";

const { either } = Parser;

type Specified = List<Specified.Item>;

/**
* @internal
*/
export namespace Specified {
export type Item = Box.CoordBox | Keyword<"no-clip">;
}

type Computed = Specified;

/**
* @internal
*/
export const parse = either(Box.parseCoordBox, Keyword.parse("no-clip"));

const parseList = List.parseCommaSeparated(parse);

/**
* @internal
*/
export const initialItem = Keyword.of("border-box");

/**
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/mask-clip}
*
* @internal
*/
export default Longhand.of<Specified, Computed>(
List.of([initialItem]),
parseList,
(value, style) => value.map(Resolver.layers(style, "mask-image")),
);
42 changes: 42 additions & 0 deletions packages/alfa-style/src/property/mask-composite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Keyword, List } from "@siteimprove/alfa-css";

import { Longhand } from "../longhand.js";
import { Resolver } from "../resolver.js";

type Specified = List<Specified.Item>;

/**
* @internal
*/
export namespace Specified {
export type Item =
| Keyword<"add">
| Keyword<"subtract">
| Keyword<"intersect">
| Keyword<"exclude">;
}

type Computed = Specified;

/**
* @internal
*/
export const parse = Keyword.parse("add", "subtract", "intersect", "exclude");

const parseList = List.parseCommaSeparated(parse);

/**
* @internal
*/
export const initialItem = Keyword.of("add");

/**
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/mask-composite}
*
* @internal
*/
export default Longhand.of<Specified, Computed>(
List.of([initialItem], ", "),
parseList,
(value, style) => value.map(Resolver.layers(style, "mask-image")),
);
Loading

0 comments on commit 00e2dc2

Please sign in to comment.