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 for mask CSS shorthand property #1711

Merged
merged 51 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
bce6125
Add `mask-*` longhands
rcj-siteimprove Nov 18, 2024
f69b7ef
Merge branch 'main' into mask-properties
rcj-siteimprove Nov 18, 2024
c183573
Add tests for mask-mode
rcj-siteimprove Nov 18, 2024
3eb3e54
Add tests for mask-origin
rcj-siteimprove Nov 18, 2024
bcd1696
Add tests for mask-repeat
rcj-siteimprove Nov 18, 2024
cbe12ee
Merge branch 'main' into mask-properties
rcj-siteimprove Nov 27, 2024
d8ece81
Move types from coord-box.ts to box.ts
rcj-siteimprove Nov 28, 2024
e49d057
Extract API
github-actions[bot] Nov 28, 2024
a4853eb
Fix knip warnings
rcj-siteimprove Nov 28, 2024
983ee01
Implement mask-repeat
rcj-siteimprove Dec 2, 2024
43fb56b
Extract common function
rcj-siteimprove Dec 2, 2024
f1f9d1e
Merge branch 'main' into mask-properties
rcj-siteimprove Dec 2, 2024
1b164b4
Remove empty test files
rcj-siteimprove Dec 2, 2024
24f2ea7
Handle layers in mask-composite
rcj-siteimprove Dec 3, 2024
22813a7
Update TODO and fix link
rcj-siteimprove Dec 3, 2024
cfd6875
Handle layers in mask-mode
rcj-siteimprove Dec 3, 2024
04761a9
Handle layers in mask-origin
rcj-siteimprove Dec 3, 2024
949c703
Add mask-size
rcj-siteimprove Dec 3, 2024
d6745df
Add mask-position
rcj-siteimprove Dec 3, 2024
e82fe0f
Remove unused imports
rcj-siteimprove Dec 4, 2024
9e11e9a
Extract API
github-actions[bot] Dec 4, 2024
63939b1
Merge branch 'main' into mask-properties
rcj-siteimprove Dec 4, 2024
f3d6f90
Implement mask-position
rcj-siteimprove Dec 4, 2024
873b22b
Extract API
github-actions[bot] Dec 4, 2024
2baf4f3
Implement shorthand `mask`
rcj-siteimprove Dec 6, 2024
58761ed
Merge branch 'mask-properties' of github.com:Siteimprove/alfa into ma…
rcj-siteimprove Dec 6, 2024
82d2af6
Merge branch 'main' into mask-properties
rcj-siteimprove Dec 6, 2024
758da49
Extract API
github-actions[bot] Dec 6, 2024
216928d
Add `mask` to shorthands
rcj-siteimprove Dec 6, 2024
f6ac94f
Merge branch 'mask-properties' of github.com:Siteimprove/alfa into ma…
rcj-siteimprove Dec 6, 2024
7e820c4
Extract API
github-actions[bot] Dec 6, 2024
071c468
Add test of shorthand as well as clean up and minor fixes
rcj-siteimprove Dec 9, 2024
123b392
Merge branch 'mask-properties' of github.com:Siteimprove/alfa into ma…
rcj-siteimprove Dec 9, 2024
029bbd8
Update few-clouds-play.md
rcj-siteimprove Dec 9, 2024
7cee5bf
Rename file
rcj-siteimprove Dec 9, 2024
6c92810
Update mask-layers.ts
rcj-siteimprove Dec 9, 2024
b7a779c
Update packages/alfa-style/src/property/helpers/mask-layers.ts
rcj-siteimprove Dec 10, 2024
8174e64
Update packages/alfa-style/src/property/mask.ts
rcj-siteimprove Dec 10, 2024
4f9c2d2
Add method for cutting or repeating an `alfa-css` `List`
rcj-siteimprove Dec 11, 2024
56f48ac
Move layering logic
rcj-siteimprove Dec 11, 2024
c51a4c0
Align with existing code structure
rcj-siteimprove Dec 11, 2024
9daa21c
Merge branch 'mask-properties' of github.com:Siteimprove/alfa into ma…
rcj-siteimprove Dec 11, 2024
e7bcbd1
Extract API
github-actions[bot] Dec 12, 2024
f4521be
Merge branch 'main' into mask-properties
rcj-siteimprove Dec 12, 2024
2430748
Make the returned layers resolver typed
rcj-siteimprove Dec 12, 2024
d8618a9
Extract API
github-actions[bot] Dec 12, 2024
beafecf
Use `Tuple` in `mask-repeat`
rcj-siteimprove Dec 12, 2024
978b09f
Use `Tuple` in `mask-size`
rcj-siteimprove Dec 12, 2024
1136b8b
Merge branch 'mask-properties' of github.com:Siteimprove/alfa into ma…
rcj-siteimprove Dec 12, 2024
671d552
Add note about discrepancy with spec in position computation
rcj-siteimprove Dec 12, 2024
dcaddde
Extract API
github-actions[bot] Dec 12, 2024
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
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
Loading