Skip to content

Commit

Permalink
Calculatable Position (#1454)
Browse files Browse the repository at this point in the history
* Extract position to its own folder

* Move Offset to its own file

* Move Keywords and Side to their own files

* Move Component to its own file

* Remove Offset and use LengthPercentage directly

* Make Side somewhat calculatable

* Switch parameter order

* Prepare partial resolving

* Make Component always take a Side

* Add missing changeset

* Make offest optional in Side.of

* Make offset optional or Option in Side.of

* Add partial resolver, rework type parameters

* Make Position calculatable

* Add some parsing examples with calculations

* Add some resolving examples with calculations

* Resolve percentages separatly in each dimension

* Add changeset

* Clean up

* Extract API

* Clean up

* Typo

* Extract API

* Add Resolvable implementation

* Extract API

* Calculatable `Shadow` (#1455)

* Typo

* Add some package implementation details

* Streamline code

* Streamline parser

* Clean up test indentation

* Add some shadow teest

* Eport Option interface

* Add more shadow parsing tests

* Add a resolve test

* Make Shadow calculatable

* Add changesets

* Extract API

* Typos

* Clean up

* Actually accept calculations

* Add Value.hasCalculation helper

* Rename parameter

* Extract API

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>

* Calculatable `transform` functions (#1457)

* Streamline parsers

* Clean up

* Share parsers in tests

* Add Matrix parsing tests

* Simplify Matrix parser

* Improve parseIf

* Improve parser

* Add tests for Rotate prasing

* Add tests for Rotate prasing

* Simplify parser

* Simplify parser

* Add Scale parsing tests

* Simplify Scale parser

* Simplify parsers

* Add Skew parsing tests

* Simplify Skew parsers

* Add Translate tests

* Simplify parsers

* Clean up

* Extract API

* Typos

* Make translate Function calculatable

* Make matrix calculatable

* Make matrix calculatable

* Streamline Perspective

* Make Perspective calculatable

* Make Rotate calculatable

* Add calculated rotate tests

* Add calculated rotate tests

* Make Scale calculatable

* Accept calculation in Skew

* Make Translate calculatable

* Accept calculation in all transform function

* Clean up

* Extract API

* Clean up

* Extract API

* Fix type inferrence

* Extract API

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>

* SIA-R19: fix regression (#1459)

* Add breaking test

* Improve documentation

* Correctly search for id in the full tree

* Update documentation

* Add changeset

* v0.66.0

* Remove duplicate variable

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: siteimprove-builduser <[email protected]>
  • Loading branch information
3 people authored Oct 3, 2023
1 parent 0337ca8 commit 5f0f911
Show file tree
Hide file tree
Showing 25 changed files with 1,134 additions and 845 deletions.
8 changes: 8 additions & 0 deletions .changeset/clever-falcons-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@siteimprove/alfa-css": minor
---

**Added:** `Position` now accept calculations in any of their components.

To fully resolve a `Position`, the resolver needs both a length resolver, and two percentage bases, one for each dimension.
To partially resolve a `Position`, only a length resolver is needed.
7 changes: 7 additions & 0 deletions .changeset/cuddly-items-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@siteimprove/alfa-css": minor
---

**Breaking:** `Position.Component` cannot be raw `LengthPercentage` anymore.

Instead, they must always be a full `Position.Side` (or the "center" keyword) i.e. include an explicit side to count from. This side is automatically added when parsing raw `LengthPercentage`.
9 changes: 9 additions & 0 deletions .changeset/shy-schools-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@siteimprove/alfa-css": minor
---

**Changed:** The `Position` type requires more type paramters.

Instead of just accepting the horizontal and vertical components, the type now also requires the horizontal and vertical keywords list (as first and second paramter). The components parameter default to `Position.Component<H>` (reps. `V`) for keywords `H` (resp. `V`).

The type also accepts a `CALC` paramter indicating whether it may have calculations.
5 changes: 5 additions & 0 deletions .changeset/silver-papayas-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siteimprove/alfa-css": minor
---

**Removed:** The unused `Side.isCenter()` predicate is no longer available.
5 changes: 5 additions & 0 deletions .changeset/smart-mayflies-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siteimprove/alfa-css": patch
---

**Added:** `Position.Side.of` now also accepts an optional offset, as well as an `Option<offset>`.
5 changes: 5 additions & 0 deletions .changeset/thick-ways-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@siteimprove/alfa-css": patch
---

**Added:** The `LengthPercentage` type now accepts an optional `CALC` boolean parameter to indicate whether it contains calculations.
136 changes: 36 additions & 100 deletions docs/review/api/alfa-css.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export namespace Box {
// Warning: (ae-forgotten-export) The symbol "BasicShape" needs to be exported by the entry point index.d.ts
//
// @public (undocumented)
export class Circle<R extends Radius = Radius, P extends Position = Position> extends BasicShape<"circle"> {
export class Circle<R extends Radius = Radius, P extends Position.Fixed = Position.Fixed> extends BasicShape<"circle"> {
// (undocumented)
get center(): P;
// (undocumented)
Expand All @@ -269,7 +269,7 @@ export class Circle<R extends Radius = Radius, P extends Position = Position> ex
// (undocumented)
hash(hash: Hash): void;
// (undocumented)
static of<R extends Radius, P extends Position>(radius: R, center: P): Circle<R, P>;
static of<R extends Radius, P extends Position.Fixed>(radius: R, center: P): Circle<R, P>;
// (undocumented)
get radius(): R;
// (undocumented)
Expand Down Expand Up @@ -526,7 +526,7 @@ namespace Dimension_2 {
}

// @public (undocumented)
export class Ellipse<R extends Radius = Radius, P extends Position = Position> extends BasicShape<"ellipse"> {
export class Ellipse<R extends Radius = Radius, P extends Position.Fixed = Position.Fixed> extends BasicShape<"ellipse"> {
// (undocumented)
get center(): P;
// (undocumented)
Expand All @@ -536,7 +536,7 @@ export class Ellipse<R extends Radius = Radius, P extends Position = Position> e
// (undocumented)
hash(hash: Hash): void;
// (undocumented)
static of<R extends Radius = Radius, P extends Position = Position>(rx: R, ry: R, center: P): Ellipse<R, P>;
static of<R extends Radius = Radius, P extends Position.Fixed = Position.Fixed>(rx: R, ry: R, center: P): Ellipse<R, P>;
// (undocumented)
resolve(): Ellipse<R, P>;
// (undocumented)
Expand Down Expand Up @@ -1128,7 +1128,7 @@ namespace Length_2 {
}

// @public (undocumented)
export type LengthPercentage<U extends Unit.Length = Unit.Length> = LengthPercentage.Calculated | Length.Calculated | Length.Fixed<U> | Percentage.Calculated | Percentage.Fixed;
export type LengthPercentage<U extends Unit.Length = Unit.Length, CALC extends boolean = boolean> = CALC extends true ? LengthPercentage.Calculated | Length.Calculated | Percentage.Calculated : CALC extends false ? Length.Fixed<U> | Percentage.Fixed : LengthPercentage.Calculated | Length.Calculated | Percentage.Calculated | Length.Fixed<U> | Percentage.Fixed;

// @public (undocumented)
export namespace LengthPercentage {
Expand Down Expand Up @@ -1189,7 +1189,9 @@ export namespace LengthPercentage {
// (undocumented)
export type Resolver = Length.Resolver & Percentage.Resolver<"length", Canonical>;
const // (undocumented)
parse: Parser_2<Slice<Token>, LengthPercentage<Unit.Length>, string, []>;
parse: Parser_2<Slice<Token>, Length.Calculated | Length.Fixed<Unit.Length> | Calculated | Percentage.Calculated<Base.Numeric.Type> | Percentage.Fixed<Base.Numeric.Type>, string, []>;
const // @internal (undocumented)
parseBase: Parser<LengthPercentage<Unit.Length, false>>;
{};
}

Expand Down Expand Up @@ -2016,129 +2018,63 @@ export namespace Polygon {
}

// @public (undocumented)
export class Position<H extends Position.Component<Position.Keywords.Horizontal> = Position.Component<Position.Keywords.Horizontal>, V extends Position.Component<Position.Keywords.Vertical> = Position.Component<Position.Keywords.Vertical>> extends Value<"position", false> {
export class Position<H extends Position.Keywords.Horizontal = Position.Keywords.Horizontal, V extends Position.Keywords.Vertical = Position.Keywords.Vertical, HC extends Position.Component<H> = Position.Component<H>, VC extends Position.Component<V> = Position.Component<V>, CALC extends boolean = boolean> extends Value<"position", CALC> implements Resolvable<Position.Canonical<H, V>, Position.Resolver> {
// (undocumented)
equals(value: unknown): value is this;
// (undocumented)
hash(hash: Hash): void;
// (undocumented)
get horizontal(): H;
get horizontal(): HC;
// (undocumented)
static of<H extends Position.Component<Position.Keywords.Horizontal>, V extends Position.Component<Position.Keywords.Vertical>>(horizontal: H, vertical: V): Position<H, V>;
static of<H extends Position.Keywords.Horizontal = Position.Keywords.Horizontal, V extends Position.Keywords.Vertical = Position.Keywords.Vertical, HC extends Position.Component<H> = Position.Component<H>, VC extends Position.Component<V> = Position.Component<V>>(horizontal: HC, vertical: VC): Position<H, V, HC, VC, Value.HasCalculation<[HC, VC]>>;
// (undocumented)
resolve(): Position<H, V>;
resolve(resolver: Position.Resolver): Position.Canonical<H, V>;
// (undocumented)
toJSON(): Position.JSON;
// (undocumented)
toString(): string;
// (undocumented)
get vertical(): V;
get vertical(): VC;
}

// @public (undocumented)
export namespace Position {
// Warning: (ae-forgotten-export) The symbol "Keywords" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "Component" needs to be exported by the entry point index.d.ts
//
// (undocumented)
export type Canonical = Position<Component.Canonical<Keywords.Horizontal>, Component.Canonical<Keywords.Vertical>>;
// (undocumented)
export type Component<S extends Keywords.Horizontal | Keywords.Vertical = Keywords.Horizontal | Keywords.Vertical, U extends Unit.Length = Unit.Length> = Keywords.Center | Offset<U> | Side<S, Offset<U>>;
// (undocumented)
export namespace Component {
// (undocumented)
export type Canonical<S extends Keywords.Horizontal | Keywords.Vertical> = Percentage.Canonical | Keywords.Center | Length.Canonical | Side.Canonical<S>;
// (undocumented)
export type JSON = Keyword.JSON | Length.Fixed.JSON | Percentage.Fixed.JSON | Side.JSON;
const // Warning: (ae-incompatible-release-tags) The symbol "parseHorizontal" is marked as @public, but its signature references "Type" which is marked as @internal
//
// (undocumented)
parseHorizontal: Parser_2<Slice<Token>, Percentage.Fixed<import("../calculation/numeric").Numeric.Type> | Length.Fixed<Unit.Length> | Keyword<"center"> | Side<Keyword.ToKeywords<"right" | "left">, Offset<Unit.Length>>, string, []>;
const // Warning: (ae-incompatible-release-tags) The symbol "parseVertical" is marked as @public, but its signature references "Type" which is marked as @internal
//
// (undocumented)
parseVertical: Parser_2<Slice<Token>, Percentage.Fixed<import("../calculation/numeric").Numeric.Type> | Length.Fixed<Unit.Length> | Keyword<"center"> | Side<Keyword.ToKeywords<"top" | "bottom">, Offset<Unit.Length>>, string, []>;
}
export type Canonical<H extends Keywords.Horizontal = Keywords.Horizontal, V extends Keywords.Vertical = Keywords.Vertical> = Position<H, V, Component_2.Canonical<H>, Component_2.Canonical<V>, false>;
// @internal (undocumented)
export type Fixed<H extends Keywords.Horizontal = Keywords.Horizontal, V extends Keywords.Vertical = Keywords.Vertical> = Position<H, V, Component_2.Fixed<H>, Component_2.Fixed<V>, false>;
// (undocumented)
export interface JSON extends Value.JSON<"position"> {
// (undocumented)
horizontal: Component.JSON;
horizontal: Component_2.JSON;
// (undocumented)
vertical: Component.JSON;
vertical: Component_2.JSON;
}
// (undocumented)
export namespace Keywords {
// (undocumented)
export type Center = Keyword<"center">;
const // @internal (undocumented)
parseCenter: Parser<Keyword<"center">>;
// (undocumented)
export type Horizontal = Keyword<"left"> | Keyword<"right">;
const // @internal (undocumented)
parseVertical: Parser<Keyword.ToKeywords<"top" | "bottom">>;
// (undocumented)
export type Vertical = Keyword<"top"> | Keyword<"bottom">;
const // @internal (undocumented)
parseHorizontal: Parser<Keyword.ToKeywords<"right" | "left">>;
}
// (undocumented)
export type Offset<U extends Unit.Length = Unit.Length> = Length.Fixed<U> | Percentage.Fixed;
// (undocumented)
export namespace Offset {
const // Warning: (ae-incompatible-release-tags) The symbol "parse" is marked as @public, but its signature references "Type" which is marked as @internal
//
// (undocumented)
parse: Parser_2<Slice<Token>, Percentage.Fixed<import("../calculation/numeric").Numeric.Type> | Length.Fixed<Unit.Length>, string, []>;
}
// (undocumented)
export function parse(legacySyntax?: boolean): Parser<Position>;
import Keywords = keywords.Keywords;
import Side = side.Side;
import Component = component.Component;
// @internal (undocumented)
export function parseBase(legacySyntax?: boolean): Parser<Fixed>;
// (undocumented)
export class Side<S extends Keywords.Vertical | Keywords.Horizontal = Keywords.Vertical | Keywords.Horizontal, O extends Offset = Offset> extends Value<"side", false> {
// (undocumented)
equals(value: unknown): value is this;
// (undocumented)
hash(hash: Hash): void;
// (undocumented)
isCenter(): boolean;
// (undocumented)
static of<S extends Keywords.Vertical | Keywords.Horizontal, O extends Offset>(side: S, offset?: Option<O>): Side<S, O>;
// (undocumented)
get offset(): Option<O>;
// (undocumented)
resolve(): Side<S, O>;
// (undocumented)
get side(): S;
// (undocumented)
toJSON(): Side.JSON;
// (undocumented)
toString(): string;
}
export function partiallyResolve<H extends Keywords.Horizontal, V extends Keywords.Vertical>(resolver: PartialResolver): (value: Position<H, V>) => PartiallyResolved<H, V>;
// (undocumented)
export namespace Side {
export type PartiallyResolved<H extends Keywords.Horizontal = Keywords.Horizontal, V extends Keywords.Vertical = Keywords.Vertical> = Position<H, V, Component_2.PartiallyResolved<H>, Component_2.PartiallyResolved<V>>;
// (undocumented)
export type PartialResolver = Component_2.PartialResolver;
export interface Resolver extends Length.Resolver {
// (undocumented)
export type Canonical<S extends Keywords.Vertical | Keywords.Horizontal> = Side<S, Percentage.Canonical | Length.Canonical>;
percentageHBase: Length.Canonical;
// (undocumented)
export interface JSON extends Value.JSON<"side"> {
// (undocumented)
offset: Length.Fixed.JSON | Percentage.Fixed.JSON | null;
// (undocumented)
side: Keyword.JSON;
}
const // (undocumented)
parseHorizontalKeywordValue: Parser<Side<Keyword.ToKeywords<"right" | "left">, Offset<Unit.Length>>>;
const // (undocumented)
parseHorizontalKeyword: Parser<Keyword<"center"> | Side<Keyword.ToKeywords<"right" | "left">, Offset<Unit.Length>>>;
const // (undocumented)
parseVerticalKeywordValue: Parser<Side<Keyword.ToKeywords<"top" | "bottom">, Offset<Unit.Length>>>;
const // (undocumented)
parseVerticalKeyword: Parser<Keyword<"center"> | Side<Keyword.ToKeywords<"top" | "bottom">, Offset<Unit.Length>>>;
const // (undocumented)
parseHorizontal: Parser_2<Slice<Token>, Keyword<"center"> | Side<Keyword.ToKeywords<"right" | "left">, Offset<Unit.Length>>, string, []>;
const // (undocumented)
parseVertical: Parser_2<Slice<Token>, Keyword<"center"> | Side<Keyword.ToKeywords<"top" | "bottom">, Offset<Unit.Length>>, string, []>;
percentageVBase: Length.Canonical;
}
{};
}

// @public (undocumented)
export class Radial<I extends Gradient.Item = Gradient.Item, S extends Radial.Shape = Radial.Shape, P extends Position = Position> extends Value<"gradient", false> {
export class Radial<I extends Gradient.Item = Gradient.Item, S extends Radial.Shape = Radial.Shape, P extends Position.Fixed = Position.Fixed> extends Value<"gradient", false> {
// (undocumented)
equals(value: Radial): boolean;
// (undocumented)
Expand All @@ -2150,7 +2086,7 @@ export class Radial<I extends Gradient.Item = Gradient.Item, S extends Radial.Sh
// (undocumented)
get kind(): "radial";
// (undocumented)
static of<I extends Gradient.Item = Gradient.Item, S extends Radial.Shape = Radial.Shape, P extends Position = Position>(shape: S, position: P, items: Iterable<I>, repeats: boolean): Radial<I, S, P>;
static of<I extends Gradient.Item = Gradient.Item, S extends Radial.Shape = Radial.Shape, P extends Position.Fixed = Position.Fixed>(shape: S, position: P, items: Iterable<I>, repeats: boolean): Radial<I, S, P>;
// (undocumented)
get position(): P;
// (undocumented)
Expand All @@ -2168,7 +2104,7 @@ export class Radial<I extends Gradient.Item = Gradient.Item, S extends Radial.Sh
// @public (undocumented)
export namespace Radial {
// (undocumented)
export type Canonical = Radial<Gradient.Hint.Canonical | Gradient.Stop.Canonical, Radial.Circle.Canonical | Radial.Ellipse.Canonical | Radial.Extent, Position.Canonical>;
export type Canonical = Radial<Gradient.Hint.Canonical | Gradient.Stop.Canonical, Radial.Circle.Canonical | Radial.Ellipse.Canonical | Radial.Extent, Position.Fixed>;
// (undocumented)
export class Circle<R extends Length.Fixed = Length.Fixed> implements Equatable, Hashable, Serializable<Circle.JSON> {
// (undocumented)
Expand Down
10 changes: 6 additions & 4 deletions docs/review/api/alfa-style.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Image } from '@siteimprove/alfa-css';
import { Iterable as Iterable_2 } from '@siteimprove/alfa-iterable';
import * as json from '@siteimprove/alfa-json';
import { Keyword } from '@siteimprove/alfa-css';
import { Keywords } from '@siteimprove/alfa-css/src/value/position/keywords';
import { Length } from '@siteimprove/alfa-css';
import { LengthPercentage } from '@siteimprove/alfa-css';
import { List } from '@siteimprove/alfa-css';
Expand All @@ -40,6 +41,7 @@ import { Rotate } from '@siteimprove/alfa-css';
import { Serializable } from '@siteimprove/alfa-json';
import { Shadow } from '@siteimprove/alfa-css';
import { Shape } from '@siteimprove/alfa-css';
import { Side } from '@siteimprove/alfa-css/src/value/position/side';
import { Slice } from '@siteimprove/alfa-slice';
import { String as String_2 } from '@siteimprove/alfa-css';
import { Text } from '@siteimprove/alfa-dom';
Expand Down Expand Up @@ -180,8 +182,8 @@ export namespace Longhands {
readonly "background-color": Longhand<Color, Color.Canonical>;
readonly "background-image": Longhand<List<import("./property/background-image").Specified.Item, boolean>, List<Image | Keyword<"none">, boolean>>;
readonly "background-origin": Longhand<List<Box, boolean>, List<Box, boolean>>;
readonly "background-position-x": Longhand<List<import("./property/background-position-x").Specified.Item, boolean>, List<Position | (Length<"px"> | Percentage.Fixed<Numeric.Type>) | Position<Position, Length<"px"> | Percentage.Fixed<Numeric.Type>>, boolean>>;
readonly "background-position-y": Longhand<List<import("./property/background-position-y").Specified.Item, boolean>, List<Position | (Length<"px"> | Percentage.Fixed<Numeric.Type>) | Position<Position, Length<"px"> | Percentage.Fixed<Numeric.Type>>, boolean>>;
readonly "background-position-x": Longhand<List<import("./property/background-position-x").Specified.Item, boolean>, List<Keywords.Center | Side.PartiallyResolved<Keywords.Horizontal>, boolean>>;
readonly "background-position-y": Longhand<List<import("./property/background-position-y").Specified.Item, boolean>, List<Keywords.Center | Side.PartiallyResolved<Keywords.Vertical>, boolean>>;
readonly "background-repeat-x": Longhand<List<import("./property/background-repeat-x").Specified.Item, boolean>, List<import("./property/background-repeat-x").Specified.Item, boolean>>;
readonly "background-repeat-y": Longhand<List<import("./property/background-repeat-x").Specified.Item, boolean>, List<import("./property/background-repeat-x").Specified.Item, boolean>>;
readonly "background-size": Longhand<List<import("./property/background-size").Specified.Item, boolean>, List<Tuple<[LengthPercentage | Keyword<"auto">, LengthPercentage | Keyword<"auto">], boolean> | Keyword<"cover"> | Keyword<"contain">, boolean>>;
Expand Down Expand Up @@ -301,9 +303,9 @@ export namespace Resolver {
// (undocumented)
export function lengthPercentage(base: Length.Canonical, style: Style): LengthPercentage.Resolver;
// (undocumented)
export function position(position: Position, style: Style): Position.Canonical;
export function position(position: Position.Fixed, style: Style): Position.Fixed;
// (undocumented)
export function positionComponent<S extends Position.Keywords.Horizontal | Position.Keywords.Vertical>(position: Position.Component<S>, style: Style): Position.Component.Canonical<S>;
export function positionComponent<S extends Position.Keywords.Horizontal | Position.Keywords.Vertical>(position: Position.Component.Fixed<S>, style: Style): Position.Component.Fixed<S>;
}

// Warning: (ae-forgotten-export) The symbol "Name" needs to be exported by the entry point index.d.ts
Expand Down
8 changes: 4 additions & 4 deletions packages/alfa-css/src/value/image/gradient-radial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ const { map, either, pair, option, left, right, delimited, take } = Parser;
export class Radial<
I extends Gradient.Item = Gradient.Item,
S extends Radial.Shape = Radial.Shape,
P extends Position = Position
P extends Position.Fixed = Position.Fixed
> extends Value<"gradient", false> {
public static of<
I extends Gradient.Item = Gradient.Item,
S extends Radial.Shape = Radial.Shape,
P extends Position = Position
P extends Position.Fixed = Position.Fixed
>(
shape: S,
position: P,
Expand Down Expand Up @@ -135,7 +135,7 @@ export namespace Radial {
export type Canonical = Radial<
Gradient.Hint.Canonical | Gradient.Stop.Canonical,
Radial.Circle.Canonical | Radial.Ellipse.Canonical | Radial.Extent,
Position.Canonical
Position.Fixed
>;

export interface JSON extends Value.JSON<"gradient"> {
Expand Down Expand Up @@ -365,7 +365,7 @@ export namespace Radial {

const parsePosition = right(
delimited(option(Token.parseWhitespace), Keyword.parse("at")),
Position.parse(false /* legacySyntax */)
Position.parseBase(false /* legacySyntax */)
);

const parseCircleShape = Keyword.parse("circle");
Expand Down
Loading

0 comments on commit 5f0f911

Please sign in to comment.