mirror of
https://github.com/sass/sass.git
synced 2024-09-21 10:37:22 +00:00
Merge branch 'main' of github.com:sass/sass into feature.color-4
This commit is contained in:
commit
c52b517334
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@ -12,18 +12,22 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with: {node-version: '${{ env.NODE_VERSION }}'}
|
||||
- run: npm ci
|
||||
- run: npm run tangle && npx gts lint && npx tsc --noEmit
|
||||
- run: >
|
||||
npm run tangle &&
|
||||
npx gts lint &&
|
||||
npx tsc --noEmit &&
|
||||
npx markdownlint-cli2
|
||||
|
||||
toc:
|
||||
name: Tables of contents
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with: {node-version: '${{ env.NODE_VERSION }}'}
|
||||
- run: npm ci
|
||||
@ -34,7 +38,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with: {node-version: '${{ env.NODE_VERSION }}'}
|
||||
- run: npm ci
|
||||
@ -45,7 +49,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with: {node-version: '${{ env.NODE_VERSION }}'}
|
||||
- run: npm ci
|
||||
@ -56,7 +60,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with: {node-version: '${{ env.NODE_VERSION }}'}
|
||||
- run: npm ci
|
||||
@ -67,18 +71,18 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: arduino/setup-protoc@v1
|
||||
with: { version: "3.x", repo-token: "${{ github.token }}" }
|
||||
- uses: actions/checkout@v4
|
||||
- uses: bufbuild/buf-setup-action@v1.26.1
|
||||
with: {github_token: "${{ github.token }}"}
|
||||
- name: Generate protobuf code
|
||||
run: protoc --js_out=/tmp spec/embedded_sass.proto
|
||||
run: buf generate
|
||||
|
||||
embedded_protocol_versions:
|
||||
name: "Validate Embedded Protocol versions"
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check versions match
|
||||
run: |
|
||||
if ! (grep -q '\-dev$' spec/EMBEDDED_PROTOCOL_VERSION ||
|
||||
@ -96,7 +100,7 @@ jobs:
|
||||
github.ref == 'refs/heads/main'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@ -129,12 +133,12 @@ jobs:
|
||||
github.ref == 'refs/heads/main'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with: {fetch-depth: 0}
|
||||
|
||||
- name: Find changed files in js-api-doc
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@a96679dfee2a1e64b1db5a210c0ffaf1f2cb24ce
|
||||
uses: tj-actions/changed-files@8238a4103220c636f2dad328ead8a7c8dbe316a3
|
||||
with: {files: js-api-doc}
|
||||
|
||||
- name: Deploy
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -96,3 +96,4 @@ typings/
|
||||
# End of https://www.gitignore.io/api/node
|
||||
|
||||
/docs
|
||||
/gen
|
||||
|
27
.markdownlint-cli2.yaml
Normal file
27
.markdownlint-cli2.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
globs: ["**.md"]
|
||||
ignores: ["node_modules/**"]
|
||||
config:
|
||||
# BNF syntax examples can go over length, and there's no way to exempt them
|
||||
# specifically.
|
||||
line-length: false
|
||||
|
||||
# We use inline HTML to format BNF syntax definitions.
|
||||
no-inline-html: false
|
||||
|
||||
# We use untagged code blocks for function definitions.
|
||||
fenced-code-language: false
|
||||
|
||||
emphasis-style:
|
||||
style: asterisk
|
||||
|
||||
# We often have to have duplicate subheadings for different parents.
|
||||
no-duplicate-heading: false
|
||||
|
||||
# We use multiple sequential blockquotes to indicate unrelated non-normative
|
||||
# notes.
|
||||
no-blanks-blockquote: false
|
||||
|
||||
# We use spaces in code formatting because spaces are relevant in Sass.
|
||||
no-space-in-code: false
|
||||
|
||||
first-line-heading: false
|
@ -16,6 +16,7 @@ process][], very small features can follow the [fast-track process][] instead.
|
||||
* [Embedded Protocol](#embedded-protocol)
|
||||
* [Fast Track](#fast-track)
|
||||
* [Emergency Track](#emergency-track)
|
||||
* [Large Language Models](#large-language-models)
|
||||
|
||||
## Process
|
||||
|
||||
@ -172,7 +173,7 @@ accepted.
|
||||
|
||||
<x><pre>
|
||||
**MinMaxExpression** ::= CssMinMax | FunctionExpression
|
||||
**CssMinMax** ::= ('min(' | 'max(') CalcValue (',' CalcValue)* ')'
|
||||
**CssMinMax** ::= ('min(' | 'max(') CalcValue (',' CalcValue)\* ')'
|
||||
**CalcValue** ::= CalcValue (('+' | '-' | '*' | '/') CalcValue)+
|
||||
  | '(' CalcValue ')'
|
||||
  | ('calc(' | 'env(' | 'var(') InterpolatedDeclarationValue ')'
|
||||
@ -214,7 +215,7 @@ accepted.
|
||||
See [CSS Imports][css-imports deprecation] for a good example of a Deprecation
|
||||
Process section.
|
||||
|
||||
[css-imports deprecation]: https://github.com/sass/dart-sass#compatibility-policy
|
||||
[css-imports deprecation]: accepted/css-imports.md#deprecation-process
|
||||
|
||||
### JavaScript API Proposals
|
||||
|
||||
@ -371,3 +372,14 @@ as follows:
|
||||
2. These pull requests may be merged as soon as they're approved. If the issue
|
||||
appears outside of work hours, it may be merged without review, but a *post
|
||||
facto* review should be done as soon as possible.
|
||||
|
||||
## Large Language Models
|
||||
|
||||
Do not submit any code or prose written or modified by large language models or
|
||||
"artificial intelligence" such as GitHub Copilot or ChatGPT to this project.
|
||||
These tools produce code that looks plausible, which means that not only is it
|
||||
likely to contain bugs those bugs are likely to be difficult to notice on
|
||||
review. In addition, because these models were trained indiscriminately and
|
||||
non-consensually on open-source code with a variety of licenses, it's not
|
||||
obvious that we have the moral or legal right to redistribute code they
|
||||
generate.
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
* Clarify the definition of bogus selectors.
|
||||
|
||||
* Only omit style rules if _all_ of their complex selectors are bogus.
|
||||
* Only omit style rules if *all* of their complex selectors are bogus.
|
||||
|
||||
* Expand the set of selectors that are treated by the extend algorithm as
|
||||
matching nothing to include bogus pseudo selectors, since these can never be
|
||||
|
@ -119,7 +119,7 @@ combinator].
|
||||
combinators*) as well as a sequence of [complex selector components]. Either,
|
||||
but not both, of these sequences may be empty~~
|
||||
|
||||
[visible combinators]: #visible-combinator
|
||||
[visible combinators]: #visible-combinator
|
||||
[complex selector components]: #complex-selector-component
|
||||
|
||||
A *complex selector* is an optional [visible combinator] (its *leading
|
||||
@ -127,7 +127,7 @@ combinator*) as well as a sequence of [complex selector components]. The
|
||||
component sequence may be empty only for complex selectors with leading
|
||||
combinators.
|
||||
|
||||
[visible combinator]: #visible-combinator
|
||||
[visible combinator]: #visible-combinator
|
||||
|
||||
### Complex Selector Component
|
||||
|
||||
@ -169,9 +169,9 @@ This proposal modifies the existing `ComplexSelector` and
|
||||
`ComplexSelectorComponent` productions to drop support for multiple combinators:
|
||||
|
||||
<x><pre>
|
||||
~~**ComplexSelector** ::= [\<combinator>]* ComplexSelectorComponent+~~
|
||||
~~**ComplexSelector** ::= [\<combinator>]\* ComplexSelectorComponent+~~
|
||||
~~  | [\<combinator>]+~~
|
||||
~~**ComplexSelectorComponent** ::= CompoundSelector [\<combinator>]*~~
|
||||
~~**ComplexSelectorComponent** ::= CompoundSelector [\<combinator>]\*~~
|
||||
**ComplexSelector** ::= [\<combinator>]? ComplexSelectorComponent+
|
||||
  | [\<combinator>]
|
||||
**ComplexSelectorComponent** ::= CompoundSelector [\<combinator>]?
|
||||
@ -228,7 +228,7 @@ In particular:
|
||||
* The parsing of `ComplexSelector` and `ComplexSelectorComponent` is unchanged.
|
||||
|
||||
* A complex selector is instead considered [bogus] if it would be bogus in Phase
|
||||
2 _or_ if it can be parsed in Phase 1 but not in Phase 2.
|
||||
2 *or* if it can be parsed in Phase 1 but not in Phase 2.
|
||||
|
||||
* The newly-added errors produce deprecation warnings instead.
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
* Throw an error when serializing a degenerate number with complex units in a
|
||||
calculation.
|
||||
|
||||
* Clarify that we're checking for degenerate _values_ in "Converting a Number to
|
||||
* Clarify that we're checking for degenerate *values* in "Converting a Number to
|
||||
a Calculation".
|
||||
|
||||
* Use `UnquotedString`s to represent unknown calculation constants rather than
|
||||
|
@ -68,16 +68,16 @@ silence real errors without providing any actual value.
|
||||
|
||||
[Converting a Number to a Calculation]: #converting-a-number-to-a-calculation
|
||||
|
||||
Whether and how we want to support this once browsers _can_ parse it is a
|
||||
Whether and how we want to support this once browsers *can* parse it is a
|
||||
question for another time.
|
||||
|
||||
## Definitions
|
||||
|
||||
### Degenerate Number
|
||||
|
||||
The doubles `Infinity`, `-Infinity`, and `NaN` are _degenerate_.
|
||||
The doubles `Infinity`, `-Infinity`, and `NaN` are *degenerate*.
|
||||
|
||||
A number is _degenerate_ if its value is degenerate.
|
||||
A number is *degenerate* if its value is degenerate.
|
||||
|
||||
## Syntax
|
||||
|
||||
|
@ -1,3 +1,63 @@
|
||||
## Draft 3.2
|
||||
|
||||
* Support space-separated lists in calculation expressions without deprecation,
|
||||
since they're necessary to support the valid CSS construct `calc(1
|
||||
var(--plus-two))`.
|
||||
|
||||
* Always preserve parentheses around unquoted strings.
|
||||
|
||||
## Draft 3.1
|
||||
|
||||
* Update the definition of potentially slash-separated numbers to reflect the
|
||||
fact that calculations are no longer determinable syntactically.
|
||||
|
||||
* Add a section describing how to restructure slash-separated lists as division
|
||||
within calculations.
|
||||
|
||||
* Don't evaluate `min()`, `max()`, `round()`, or `abs()` as a calculation if it
|
||||
has keyword or rest arguments.
|
||||
|
||||
* During the deprecation period, only consider unbracketed
|
||||
`SpaceListExpressions` with multiple elements that actually contain
|
||||
interpolation to be calculation-safe.
|
||||
|
||||
* Handle `"*"` and `"/"` tokens when evaluating `SumExpresssion`s and
|
||||
`ProductExpression`s as calculations.
|
||||
|
||||
* Remove the `CalculationInterpolation` type, and deprecate the associated JS
|
||||
API and embedded protocol field. The problem this type existed to work around
|
||||
is no longer an issue in the new parsing structure.
|
||||
|
||||
* Preserve parentheses around all `var()` strings that get injected from
|
||||
anywhere, as well as any unquoted strings that appear in `calc()` and might
|
||||
need parentheses.
|
||||
|
||||
* Don't treat `abs()`, `min()`, `max()`, or `round()` as potentially
|
||||
slash-separated operands..
|
||||
|
||||
## Draft 3.0
|
||||
|
||||
* Refactor the way calculations are parsed to allow them to coexist with
|
||||
user-defined Sass functions of the same names.
|
||||
|
||||
* No longer forbid user-defined functions with the same names as CSS math
|
||||
functions and remove the associated deprecation process.
|
||||
|
||||
* Drop support for interpolation in calculations outside of identifier position
|
||||
and add a deprecation process for this.
|
||||
|
||||
## Draft 2.1
|
||||
|
||||
* Allow custom functions named like vendor-prefixed new CSS functions.
|
||||
|
||||
## Draft 2.0
|
||||
|
||||
* Explicitly forbid user-defined functions with the same names as CSS math
|
||||
functions.
|
||||
|
||||
* Add a deprecation process for gradually phasing out user-defined functions
|
||||
with name conflicts.
|
||||
|
||||
## Draft 1.5
|
||||
|
||||
* Fix the definition of `rem()` to use `result - modulus` rather than `result -
|
||||
@ -6,7 +66,7 @@
|
||||
## Draft 1.4
|
||||
|
||||
* Don't exempt percentages from most functions' simplification logic, since
|
||||
those functions don't allow _any_ units in CSS, including percents. Now only
|
||||
those functions don't allow *any* units in CSS, including percents. Now only
|
||||
`abs()`, `sign()`, `atan2()`, and `hypot()` check for known units because CSS
|
||||
allows percentages for them but they aren't linear so they can't be resolved
|
||||
in terms of percentages in Sass.
|
||||
|
@ -1,29 +1,55 @@
|
||||
# Calculation Functions: Draft 1.5
|
||||
# Calculation Functions: Draft 3.2
|
||||
|
||||
*([Issue](https://github.com/sass/sass/issues/3504))*
|
||||
*([Issue](https://github.com/sass/sass/issues/3504), [Changelog](calc-functions.changes.md))*
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Background](#background)
|
||||
* [Summary](#summary)
|
||||
* [Design Decisions](#design-decisions)
|
||||
* [Merged Syntax](#merged-syntax)
|
||||
* [Changing Mod Infinity Behavior](#changing-mod-infinity-behavior)
|
||||
* [Definitions](#definitions)
|
||||
* [Calculation-Safe Expression](#calculation-safe-expression)
|
||||
* [Exact Equality](#exact-equality)
|
||||
* [Known Units](#known-units)
|
||||
* [Potentially Slash-Separated Number](#potentially-slash-separated-number)
|
||||
* [Syntax](#syntax)
|
||||
* [`FunctionExpression`](#functionexpression)
|
||||
* [`CssMinMax`](#cssminmax)
|
||||
* [`CalculationExpression`](#calculationexpression)
|
||||
* [`CssRound`](#cssround)
|
||||
* [`CssAbs`](#cssabs)
|
||||
* [Types](#types)
|
||||
* [Calculation](#calculation)
|
||||
* [Operations](#operations)
|
||||
* [Modulo](#modulo)
|
||||
* [Procedures](#procedures)
|
||||
* [Evaluating a `FunctionCall` as a Calculation](#evaluating-a-functioncall-as-a-calculation)
|
||||
* [Evaluating an Expression as a Calculation Value](#evaluating-an-expression-as-a-calculation-value)
|
||||
* [Simplifying a Calculation](#simplifying-a-calculation)
|
||||
* [Simplifying a `CalculationValue`](#simplifying-a-calculationvalue)
|
||||
* [Semantics](#semantics)
|
||||
* [Calculation Expressions](#calculation-expressions)
|
||||
* [`FunctionCall`](#functioncall)
|
||||
* [Calculations](#calculations)
|
||||
* [`FunctionExpression` and `Variable`](#functionexpression-and-variable)
|
||||
* [`SumExpression` and `ProductExpression`](#sumexpression-and-productexpression)
|
||||
* [`SpaceListExpression`](#spacelistexpression)
|
||||
* [`ParenthesizedExpression`](#parenthesizedexpression)
|
||||
* [`InterpolatedIdentifier`](#interpolatedidentifier)
|
||||
* [Interaction with Forward Slash as a Separator](#interaction-with-forward-slash-as-a-separator)
|
||||
* [Adjusting Slash Precedence](#adjusting-slash-precedence)
|
||||
* [`SlashListExpression`](#slashlistexpression)
|
||||
* [API](#api)
|
||||
* [Types](#types-1)
|
||||
* [`CalculationInterpolation`](#calculationinterpolation)
|
||||
* [`internal`](#internal)
|
||||
* [Constructor](#constructor)
|
||||
* [`value`](#value)
|
||||
* [`equals`](#equals)
|
||||
* [`hashCode`](#hashcode)
|
||||
* [Embedded Protocol](#embedded-protocol)
|
||||
* [`CalculationValue.value.interpolation`](#calculationvaluevalueinterpolation)
|
||||
* [Deprecation Process](#deprecation-process)
|
||||
* [`abs-percent`](#abs-percent)
|
||||
|
||||
## Background
|
||||
|
||||
@ -57,12 +83,37 @@ equivalent of `-10%` since percentages are resolved before calculations. To
|
||||
handle this, we'll deprecate the global `abs()` function with a percentage and
|
||||
recommend users explicitly write `math.abs()` or `abs(#{})` instead.
|
||||
|
||||
This also expands calculation parsing to allow constructs like `calc(1
|
||||
var(--plus-two))` (where for example `--plus-two: + 2`) which are valid CSS but
|
||||
weren't supported by the old first-class calculation parsing.
|
||||
|
||||
### Design Decisions
|
||||
|
||||
#### Merged Syntax
|
||||
|
||||
This proposal substantially changes the way calculations are parsed, merging the
|
||||
syntax with the standard Sass expression syntax. Now the only difference between
|
||||
a calculation and a normal Sass function is how it's *evaluated*. This has the
|
||||
notable benefit of allowing calculations to coexist with user-defined Sass
|
||||
functions of the same name, preserving backwards-compatibility.
|
||||
|
||||
Because this overlap is always going to be somewhat confusing for readers, we
|
||||
considered simply disallowing Sass functions whose names matched CSS
|
||||
calculations after a suitable deprecation period. However, in addition to the
|
||||
intrinsic value of avoiding breaking changes, the function name `rem()` in
|
||||
particular is widely used in Sass libraries as a means of converting pixel
|
||||
widths to relative ems, so this is a fairly substantial breaking change in
|
||||
practice.
|
||||
|
||||
This does also require its own breaking change to the way interpolation is
|
||||
handled in calculations—`calc(#{"1px +"} 1%)` was formerly valid but is no
|
||||
longer. However, this is likely to break many fewer users in practice, and is
|
||||
relatively easy to continue supporting in a deprecated state in the short term.
|
||||
|
||||
#### Changing Mod Infinity Behavior
|
||||
|
||||
This proposal changes the behavior of the `%` operation when the right-hand side
|
||||
is infinite _and_ has a different sign than the left-hand side. Sass used to
|
||||
is infinite *and* has a different sign than the left-hand side. Sass used to
|
||||
return the right-hand side in accordance with the floating point specification,
|
||||
but it now returns NaN to match CSS's `mod()` function.
|
||||
|
||||
@ -72,6 +123,26 @@ process for it.
|
||||
|
||||
## Definitions
|
||||
|
||||
### Calculation-Safe Expression
|
||||
|
||||
An expression is "calculation-safe" if it is one of:
|
||||
|
||||
* A [`FunctionExpression`].
|
||||
* A `ParenthesizedExpression` whose contents is calculation-safe.
|
||||
* A `SumExpression` whose operands are calculation-safe.
|
||||
* A `ProductExpression` whose operator is `*` or `/` and whose operands are
|
||||
calculation-safe.
|
||||
* A `Number`.
|
||||
* A `Variable`.
|
||||
* An `InterpolatedIdentifier`.
|
||||
* An unbracketed `SpaceListExpression` with more than one element, whose
|
||||
elements are all calculation-safe.
|
||||
|
||||
[`FunctionExpression`]: ../spec/functions.md#syntax
|
||||
|
||||
> Because calculations have special syntax in CSS, only a subset of SassScript
|
||||
> expressions are valid (and these are interpreted differently than elsewhere).
|
||||
|
||||
### Exact Equality
|
||||
|
||||
Two [doubles] are said to be *exactly equal* if they are equal according to the
|
||||
@ -113,114 +184,55 @@ a `/` operator is evaluated and each operand is *syntactically* one of the
|
||||
following:
|
||||
|
||||
* a `Number`,
|
||||
* a [`Calculation`] **whose name is not `abs`, `max`, `min`, or `round`**, or
|
||||
* a [`FunctionCall`], or
|
||||
* a `ProductExpression` that can itself create potentially slash-separated
|
||||
numbers.
|
||||
|
||||
[`FunctionCall`]: ../spec/functions.md#functioncall
|
||||
|
||||
> We exclude these four calc functions from producing potentially
|
||||
> slash-separated numbers to ensure that existing code like `1 / round(1.5)`
|
||||
> continues to be evaluated as division when the function goes from being
|
||||
> evaluated as a Sass global function to being evaluated as a calc function.
|
||||
If the result of evaluating the `ProductExpression` is a number, that number is
|
||||
potentially slash-separated if all of the following are true:
|
||||
|
||||
[`Calculation`]: ../spec/types/calculation.md#syntax
|
||||
* the results of evaluating both operands were numbers, and
|
||||
* if either operand was a `FunctionCall`, it was [evaluated as a calculation]
|
||||
and its name was not `"abs"`, `"max"`, `"min"`, or `"round"`.
|
||||
|
||||
If both operands are evaluated as numbers, the resulting number is potentially
|
||||
slash-separated. The first operand is the original numerator of the potentially
|
||||
slash-separated number returned by the `/` operator, and the second is the
|
||||
original denominator.
|
||||
[evaluated as a calculation]: #evaluating-a-functioncall-as-a-calculation
|
||||
|
||||
If both of these are true, the first operand is the original numerator of the
|
||||
potentially slash-separated number returned by the `/` operator, and the second
|
||||
is the original denominator.
|
||||
|
||||
## Syntax
|
||||
|
||||
> Calculations are no longer parsed differently than other Sass productions.
|
||||
> Instead, they're *evaluated* differently at runtime. This allows them to
|
||||
> coexist with user-defined Sass functions even when their names overlap.
|
||||
|
||||
### `FunctionExpression`
|
||||
|
||||
Replace [the definition of `FunctionExpression`] with the following:
|
||||
Remove `CssMinMax` and `CalculationExpression` from [the definition of
|
||||
`FunctionExpression`].
|
||||
|
||||
[the definition of `FunctionExpression`]: ../spec/functions.md#syntax
|
||||
|
||||
<x><pre>
|
||||
**FunctionExpression**¹ ::= [CssMinMax]
|
||||
  | [CssRound]
|
||||
  | [CssAbs]
|
||||
  | [SpecialFunctionExpression]
|
||||
  | [CalculationExpression]
|
||||
  | EmptyFallbackVar
|
||||
  | FunctionCall
|
||||
**EmptyFallbackVar** ::= 'var('² Expression ',' ')'
|
||||
**FunctionCall**³ ::= [NamespacedIdentifier] ArgumentInvocation
|
||||
</pre></x>
|
||||
### `CssMinMax`
|
||||
|
||||
[CssMinMax]: ../spec/types/calculation.md#cssminmax
|
||||
[CssRound]: #cssround
|
||||
[CssAbs]: #cssabs
|
||||
[SpecialFunctionExpression]: ../spec/syntax.md#specialfunctionexpression
|
||||
[CalculationExpression]: ../spec/types/calculation.md#calculationexpression
|
||||
[NamespacedIdentifier]: ../spec/modules.md#syntax
|
||||
|
||||
1: `CssMinMax`, `CssRound`, `CssAbs`, and `EmptyFallbackVar` all take precedence
|
||||
over `FunctionCall` if either could be consumed.
|
||||
|
||||
2: `'var('` is matched case-insensitively.
|
||||
|
||||
3: `FunctionCall` may not have any whitespace between the `NamespacedIdentifier`
|
||||
and the `ArgumentInvocation`. It may not start with [`SpecialFunctionName`],
|
||||
[`UnaryCalcName`], [`BinaryCalcName`], `'hypot('`, or `'clamp('`
|
||||
(case-insensitively).
|
||||
|
||||
[`SpecialFunctionName`]: ../spec/syntax.md#specialfunctionexpression
|
||||
[`UnaryCalcName`]: #calculationexpression
|
||||
[`BinaryCalcName`]: #calculationexpression
|
||||
Remove the `CssMinMax` production.
|
||||
|
||||
### `CalculationExpression`
|
||||
|
||||
Replace [the definition of `CalculationExpression`] with:
|
||||
Remove the `CalculationExpression` production.
|
||||
|
||||
[the definition of `CalculationExpression`]: ../spec/types/calculation.md#calculationexpression
|
||||
## Types
|
||||
|
||||
<x><pre>
|
||||
**CalculationExpression** ::= UnaryCalcExpression
|
||||
  | BinaryCalcExpression
|
||||
  | ClampExpression
|
||||
  | HypotExpression
|
||||
**UnaryCalcExpression** ::= UnaryCalcName CalcArgument ')'
|
||||
**BinaryCalcExpression** ::= BinaryCalcName CalcArgument (',' CalcArgument)? ')'
|
||||
**HypotExpression** ::= 'hypot('¹ CalcArgument (',' CalcArgument)\* ')'
|
||||
**UnaryCalcName**¹ ::= 'calc(' | 'sin(' | 'cos(' | 'tan(' | 'asin('
|
||||
  | 'acos(' | 'atan(' | 'sqrt(' | 'exp(' | 'sign('
|
||||
**BinaryCalcName**¹ ::= 'mod(' | 'rem(' | 'atan2(' | 'pow(' | 'log('
|
||||
</pre></x>
|
||||
### Calculation
|
||||
|
||||
1: The strings `hypot(`, `clamp(`, and `var(` are matched case-insensitively, as
|
||||
are the productions `UnaryCalcName` and `BinaryCalcName`.
|
||||
Delete the `CalculationInterpolation` type and remove all references to it.
|
||||
|
||||
Remove the existing definition of `CalcExpression`.
|
||||
|
||||
> Note: we aren't adding `| CssRound | CssAbs` to the definition of `CalcValue`
|
||||
> because existing Sass releases already allow the global `round()` and `abs()`
|
||||
> functions in calculations.
|
||||
|
||||
### `CssRound`
|
||||
|
||||
Add the following production:
|
||||
|
||||
<x><pre>
|
||||
**CssRound** ::= 'round('¹ CalcArgument (',' CalcArgument){2} ')'
|
||||
</pre></x>
|
||||
|
||||
1: The string `round(` is matched case-insensitively.
|
||||
|
||||
> Although the three-argument `round()` function only allows a few values in its
|
||||
> first argument, for simplicity those are checked at evaluation time rather
|
||||
> than parse time.
|
||||
|
||||
### `CssAbs`
|
||||
|
||||
Add the following production:
|
||||
|
||||
<x><pre>
|
||||
**CssAbs** ::= 'abs('¹ CalcArgument ')'
|
||||
</pre></x>
|
||||
|
||||
1: The string `abs(` is matched case-insensitively.
|
||||
> This type only existed to track where we needed to defensively insert
|
||||
> parentheses. Now that we track parentheses as part of the calculation AST,
|
||||
> this is no longer necessary
|
||||
|
||||
## Operations
|
||||
|
||||
@ -265,6 +277,35 @@ Let `n1` and `n2` be two numbers. To determine `n1 % n2`:
|
||||
|
||||
## Procedures
|
||||
|
||||
### Evaluating a `FunctionCall` as a Calculation
|
||||
|
||||
This algorithm takes a [`FunctionCall`] `call` whose name is a plain identifier
|
||||
and returns a number or a calculation.
|
||||
|
||||
* If `call`'s `ArgumentInvocation` contains one or more `KeywordArgument`s or
|
||||
one or more `RestArgument`s, throw an error.
|
||||
|
||||
* Let `calc` be a calculation whose name is the lower-case value of `call`'s
|
||||
name and whose arguments are the result of evaluating each `Expression` in
|
||||
`call`'s `ArgumentInvocation` [as a calculation value].
|
||||
|
||||
[as a calculation value]: #evaluating-an-expression-as-a-calculation-value
|
||||
|
||||
* Return the result of [simplifying] `calc`.
|
||||
|
||||
### Evaluating an Expression as a Calculation Value
|
||||
|
||||
This algorithm takes an expression `expression` and returns a
|
||||
`CalculationValue`.
|
||||
|
||||
* If `expression` isn't [calculation-safe], throw an error.
|
||||
|
||||
* Otherwise, evaluate `expression` using the semantics defined in the
|
||||
[Calculations] specification if there are any, or the standard semantics
|
||||
otherwise.
|
||||
|
||||
[Calculations]: #calculations
|
||||
|
||||
### Simplifying a Calculation
|
||||
|
||||
Replace [the definition of "Simplifying a Calculation"] with the following:
|
||||
@ -287,8 +328,8 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
or calculation, return it.
|
||||
|
||||
* If `calc`'s name is `"mod"`, `"rem"`, `"atan2"`, or `"pow"`; `arguments` has
|
||||
fewer than two elements; and none of those are unquoted strings or
|
||||
`CalculationInterpolation`s, throw an error.
|
||||
fewer than two elements; and none of those are unquoted strings, throw an
|
||||
error.
|
||||
|
||||
> It's valid to write `pow(var(--two-args))` or `pow(#{"2, 3"})`, but
|
||||
> otherwise calculations' arguments must match the expected number.
|
||||
@ -323,7 +364,7 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
|
||||
> In this case, `number` is either `+0`, `-0`, or NaN.
|
||||
|
||||
> To match CSS's behavior, these computations _don't_ use fuzzy comparisons.
|
||||
> To match CSS's behavior, these computations *don't* use fuzzy comparisons.
|
||||
|
||||
* If `calc`'s name is `"log"`:
|
||||
|
||||
@ -356,8 +397,8 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
|
||||
* If `calc`'s name is `"mod"` or `"rem"`:
|
||||
|
||||
* If `arguments` has only one element and it's not an unquoted string or a
|
||||
`CalculationInterpolation`, throw an error.
|
||||
* If `arguments` has only one element and it's not an unquoted string, throw
|
||||
an error.
|
||||
|
||||
* Otherwise, if `arguments` contains exactly two numbers `dividend` and
|
||||
`modulus`:
|
||||
@ -377,6 +418,7 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
|
||||
* Otherwise, return `result`.
|
||||
|
||||
[compatible]: ../spec/types/number.md#compatible-units
|
||||
[definitely-incompatible]: ../spec/types/number.md#possibly-compatible-numbers
|
||||
[exactly equals]: #exact-equality
|
||||
|
||||
@ -389,7 +431,7 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
|
||||
* If the first element is an unquoted string or interpolation with value
|
||||
`"nearest"`, `"up"`, `"down"`, or `"to-zero"`, and the second argument
|
||||
isn't an unquoted string or `CalculationInterpolation`, throw an error.
|
||||
isn't an unquoted string, throw an error.
|
||||
|
||||
> Normally we allow unquoted strings anywhere in a calculation, but this
|
||||
> helps catch the likely error of a user accidentally writing `round(up,
|
||||
@ -398,8 +440,7 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
* Otherwise, set `number` and `step` to the two arguments respectively and
|
||||
`strategy` to an unquoted string with value `"nearest"`.
|
||||
|
||||
* Otherwise, if the single argument isn't an unquoted string or
|
||||
`CalculationInterpolation`, throw an error.
|
||||
* Otherwise, if the single argument isn't an unquoted string, throw an error.
|
||||
|
||||
* If `strategy`, `number`, and `step` are set:
|
||||
|
||||
@ -459,7 +500,7 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
* If `calc`'s name is `"clamp"`:
|
||||
|
||||
* If `arguments` has fewer than three elements, and none of those are unquoted
|
||||
strings or `CalculationInterpolation`s, throw an error.
|
||||
strings, throw an error.
|
||||
|
||||
* Otherwise, if any two elements of `arguments` are [definitely-incompatible]
|
||||
numbers, throw an error.
|
||||
@ -498,31 +539,327 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
* Otherwise, return a calculation with the same name as `calc` and `arguments`
|
||||
as its arguments.
|
||||
|
||||
### Simplifying a `CalculationValue`
|
||||
|
||||
Replace the block "If `value` is a calculation" in the procedure for
|
||||
[simplifying a `CalculationValue`] with the following:
|
||||
|
||||
[simplifying a `CalculationValue`]: ../spec/types/calculation.md#simplifying-a-calculationvalue
|
||||
|
||||
* If `value` is a calculation:
|
||||
|
||||
* Let `result` be the result of [simplifying] `value`.
|
||||
|
||||
* If `result` isn't a calculation whose name is `"calc"`, return `result`.
|
||||
|
||||
* If `result`'s argument isn't an unquoted string, return `result`.
|
||||
|
||||
* If `result`'s argument begins case-insensitively with `"var("`; or if it
|
||||
contains whitespace, `"/"`, or `"*"`; return `"(" +` result's argument `+
|
||||
")"` as an unquoted string.
|
||||
|
||||
> This is ensures that values that could resolve to operations end up
|
||||
> parenthesized if used in other operations. It's potentially a little
|
||||
> overzealous, but that's unlikely to be a major problem given that the
|
||||
> output is still smaller than including the full `calc()` and we don't want
|
||||
> to encourage users to inject calculations with interpolation anyway.
|
||||
|
||||
## Semantics
|
||||
|
||||
### Calculation Expressions
|
||||
### `FunctionCall`
|
||||
|
||||
Replace the semantics for `CalcExpression`, `ClampExpression`, and `CssMinMax`
|
||||
with the following:
|
||||
Add the following to [the semantics for `FunctionCall`] before checking for a
|
||||
global function:
|
||||
|
||||
To evaluate a `UnaryCalcExpression`, `BinaryCalcExpression`, `HypotExpression`,
|
||||
`CssMinMax`, `CssRound`, or `CssAbs`:
|
||||
[the semantics for `FunctionCall`]: ../spec/functions.md#functioncall
|
||||
|
||||
* Let `name` be the lower-case value of the expression's first token without the
|
||||
trailing parenthesis.
|
||||
* If `function` is null; `name` is case-insensitively equal to `"min"`, `"max"`,
|
||||
`"round"`, or `"abs"`; `call`'s `ArgumentInvocation` doesn't have any
|
||||
`KeywordArgument`s or `RestArgument`s; and all arguments in `call`'s
|
||||
`ArgumentInvocation` are [calculation-safe], return the result of evaluating
|
||||
`call` [as a calculation].
|
||||
|
||||
* Let `calc` be a calculation whose name is `name` and whose arguments are the
|
||||
results of [evaluating the expression's `CalcArgument`s].
|
||||
[calculation-safe]: #calculation-safe-expression
|
||||
[as a calculation]: #evaluating-a-functioncall-as-a-calculation
|
||||
|
||||
[evaluating the expression's `CalcArgument`s]: ../spec/types/calculation.md#calcargument
|
||||
> For calculation functions that overlap with global Sass function names, we
|
||||
> want anything Sass-specific like this to end up calling the Sass function.
|
||||
> For all other calculation functions, we want those constructs to throw an
|
||||
> error (which they do when evaluating `call` [as a calculation]).
|
||||
|
||||
* Return the result of [simplifying] `calc`.
|
||||
* If `function` is null and `name` is case-insensitively equal to `"calc"`,
|
||||
`"clamp"`, `"hypot"`, `"sin"`, `"cos"`, `"tan"`, `"asin"`, `"acos"`, `"atan"`,
|
||||
`"sqrt"`, `"exp"`, `"sign"`, `"mod"`, `"rem"`, `"atan2"`, `"pow"`, or `"log"`,
|
||||
return the result of evaluating `call` [as a calculation].
|
||||
|
||||
### Calculations
|
||||
|
||||
Remove all prior [semantics for Calculations]. The following semantics apply
|
||||
only when evaluating expressions [as calculation values].
|
||||
|
||||
[semantics for Calculations]: ../spec/types/calculation.md#semantics
|
||||
[as calculation values]: #evaluating-an-expression-as-a-calculation-value
|
||||
|
||||
#### `FunctionExpression` and `Variable`
|
||||
|
||||
To evaluate a `FunctionExpression` or a `Variable` as a calculation value,
|
||||
evaluate it using the standard semantics. If the result is a number, an unquoted
|
||||
string, or a calculation, return it. Otherwise, throw an error.
|
||||
|
||||
> Allowing variables to return unquoted strings here supports referential
|
||||
> transparency, so that `$var: fn(); calc($var)` works the same as `calc(fn())`.
|
||||
|
||||
#### `SumExpression` and `ProductExpression`
|
||||
|
||||
To evaluate a `SumExpresssion` or a `ProductExpression` as a calculation value:
|
||||
|
||||
* Let `left` be the result of evaluating the first operand as a calculation
|
||||
value.
|
||||
|
||||
* For each remaining `"+"`, `"-"`, `"*"`, or `"/"` token `operator` and operand
|
||||
`operand`:
|
||||
|
||||
* Let `right` be the result of evaluating `operand` as a calculation value.
|
||||
|
||||
* Set `left` to a `CalcOperation` with `operator`, `left`, and `right`.
|
||||
|
||||
* Return `left`.
|
||||
|
||||
### `SpaceListExpression`
|
||||
|
||||
To evaluate a `SpaceListExpresssion` as a calculation value:
|
||||
|
||||
* Let `elements` be the results of evaluating each element as a calculation
|
||||
value.
|
||||
|
||||
* If `elements` has two adjacent elements that aren't unquoted strings, throw an
|
||||
error.
|
||||
|
||||
> This ensures that valid CSS constructs like `calc(1 var(--plus-two))` and
|
||||
> similar Sass constructs like `calc(1 #{"+ 2"})` work while preventing clear
|
||||
> errors like `calc(1 2)`.
|
||||
>
|
||||
> This does allow errors like `calc(a b)`, but the complexity of verifying
|
||||
> that the unquoted strings could actually be a partial operation isn't worth
|
||||
> the benefit of eagerly producing an error in this edge case.
|
||||
|
||||
* Let `serialized` be an empty list.
|
||||
|
||||
* For each `element` of `elements`:
|
||||
|
||||
* Let `css` be the result of [serializing] `element`.
|
||||
|
||||
[serializing]: ../spec/types/calculation.md#serialization
|
||||
|
||||
* If `element` is a `CalcOperation` that was produced by evaluating a
|
||||
`ParenthesizedExpression`, set `css` to `"(" + css + ")"`.
|
||||
|
||||
* Append `css` to `serialized`.
|
||||
|
||||
* Return an unquoted strings whose contents are the elements of `serialized`
|
||||
separated by `" "`.
|
||||
|
||||
#### `ParenthesizedExpression`
|
||||
|
||||
> If a `var()` or an interpolation is written directly within parentheses, it's
|
||||
> necessary to preserve those parentheses. CSS resolves `var()` by literally
|
||||
> replacing the function with the value of the variable and *then* parsing the
|
||||
> surrounding context.
|
||||
>
|
||||
> For example, if `--ratio: 2/3`, `calc(1 / (var(--ratio)))` is parsed as
|
||||
> `calc(1 / (2/3)) = calc(3/2)` but `calc(1 / var(--ratio))` is parsed as
|
||||
> `calc(1 / 2/3) = calc(1/6)`.
|
||||
|
||||
To evaluate a `ParenthesizedExpression` with contents `expression` as a
|
||||
calculation value:
|
||||
|
||||
* Let `result` be the result of evaluating `expression` as a calculation value.
|
||||
|
||||
* If `result` is an unquoted string, return `"(" + result + ")"` as an unquoted
|
||||
string.
|
||||
|
||||
* Otherwise, return `result`.
|
||||
|
||||
#### `InterpolatedIdentifier`
|
||||
|
||||
To evaluate an `InterpolatedIdentifier` `ident` as a calculation value:
|
||||
|
||||
* If `ident` is case-insensitively equal to `pi`, return 3.141592653589793.
|
||||
|
||||
> This is the closest double approximation of the mathematical constant π.
|
||||
|
||||
* If `ident` is case-insensitively equal to `e`, return 2.718281828459045.
|
||||
|
||||
> This is the closest double approximation of the mathematical constant e.
|
||||
|
||||
* If `ident` is case-insensitively equal to `infinity`, return the double
|
||||
`Infinity`.
|
||||
|
||||
* If `ident` is case-insensitively equal to `-infinity`, return the double
|
||||
`-Infinity`.
|
||||
|
||||
* If `ident` is case-insensitively equal to `nan`, return the double `NaN`.
|
||||
|
||||
* Otherwise, return the result of evaluating `ident` using standard semantics.
|
||||
|
||||
> This will be an `UnquotedString`.
|
||||
|
||||
## Interaction with Forward Slash as a Separator
|
||||
|
||||
Although the [Forward Slash as a Separator proposal] has not yet been integrated
|
||||
into the canonical spec, it will affect some of the constructs modified by this
|
||||
proposal. This section defines additional modifications to the spec *as it will
|
||||
exist* when that proposal is integrated.
|
||||
|
||||
[Forward Slash as a Separator proposal]: slash-separator.md
|
||||
|
||||
Remove "or `/`" from the definition of a calculation-safe `ProductExpression`.
|
||||
Add "An unbracketed `SlashListExpression` with more than one element, all of
|
||||
which are calculation-safe" to the list of calculation-safe expressions.
|
||||
|
||||
Replace "evaluating each `Expression`" with "[adjusting slash precedence] in and
|
||||
then evaluating each `Expression`" in [evaluting a `FunctionCall` as a
|
||||
calculation].
|
||||
|
||||
[adjusting slash precedence]: #adjusting-slash-precedence
|
||||
[evaluting a `FunctionCall` as a calculation]: #evaluating-a-functioncall-as-a-calculation
|
||||
|
||||
### Adjusting Slash Precedence
|
||||
|
||||
This algorithm takes a calculation-safe expression `expression` and returns
|
||||
another calculation-safe expression with the precedence of
|
||||
`SlashListExpression`s adjusted to match division precedence.
|
||||
|
||||
* Return a copy of `expression` except, for each `SlashListExpression`:
|
||||
|
||||
* Let `left` be the first element of the list.
|
||||
|
||||
* For each remaining element `right`:
|
||||
|
||||
* If `left` and `right` are both `SumExpression`s:
|
||||
|
||||
* Let `last-left` be the last operand of `left` and `first-right` the
|
||||
first operand of `right`.
|
||||
|
||||
* Set `left` to a `SumExpression` that begins with all operands and
|
||||
operators of `left` except `last-left`, followed by a
|
||||
`SlashListExpression` with elements `last-left` and `first-right`,
|
||||
followed by all operators and operands of `right` except `first-right`.
|
||||
|
||||
> For example, `slash-list(1 + 2, 3 + 4)` becomes `1 + (2 / 3) + 4`.
|
||||
|
||||
* Otherwise, if `left` is a `SumExpression`:
|
||||
|
||||
* Let `last-left` be the last operand of `left`.
|
||||
|
||||
* Set `left` to a `SumExpression` that begins with all operands and
|
||||
operators of `left` except `last-left`, followed by a
|
||||
`SlashListExpression` with elements `last-left` and `right`.
|
||||
|
||||
> For example, `slash-list(1 + 2, 3)` becomes `1 + (2 / 3)`.
|
||||
|
||||
* Otherwise, if `right` is a `SumExpression` or a `ProductExpression`:
|
||||
|
||||
* Let `first-right` be the first operand of `right`.
|
||||
|
||||
* Set `left` to an expression of the same type as `right` that begins a
|
||||
`SlashListExpression` with elements `left` and `first-right`, followed
|
||||
by operators and operands of `right` except `first-right`.
|
||||
|
||||
> For example, `slash-list(1, 2 * 3)` becomes `(1 / 2) * 3`.
|
||||
|
||||
* Otherwise, if `left` is a slash-separated list, add `right` to the end.
|
||||
|
||||
* Otherwise, set `left` to a slash-separated list containing `left` and
|
||||
`right`.
|
||||
|
||||
* Replace each element in `left` with the result of adjusting slash precedence
|
||||
in that element.
|
||||
|
||||
* Replace the `SlashListExpression` with `left` in the returned expression.
|
||||
|
||||
### `SlashListExpression`
|
||||
|
||||
To evaluate a `SlashListExpression` as a calculation value:
|
||||
|
||||
* Let `left` be the result of evaluating the first element of the list as a
|
||||
calculation value.
|
||||
|
||||
* For each remaining element `element`:
|
||||
|
||||
* Let `right` be the result of evaluating `element` as a calculation value.
|
||||
|
||||
* Set `left` to a `CalcOperation` with operator `"/"`, `left`, and `right`.
|
||||
|
||||
* Return `left`.
|
||||
|
||||
## API
|
||||
|
||||
### Types
|
||||
|
||||
#### `CalculationInterpolation`
|
||||
|
||||
Replace the definition of this class, other than its TypeScript API, with the
|
||||
following:
|
||||
|
||||
A deprecated alternative JS API representation of an unquoted Sass string that's
|
||||
always surrounded by parentheses. It's never returned by the Sass compiler, but
|
||||
for backwards-compatibility users may still construct it and pass it to the Sass
|
||||
compiler.
|
||||
|
||||
> `CalculationInterpolation`s are no longer generated by the Sass compiler,
|
||||
> because it can now tell at evaluation time whether an interpolation was
|
||||
> originally surrounded by parentheses. However, until we make a breaking
|
||||
> revision of the JS API, users may continue to pass `CalculationInterpolation`s
|
||||
|
||||
##### `internal`
|
||||
|
||||
A private property like [`Value.internal`] that refers to a Sass string.
|
||||
|
||||
##### Constructor
|
||||
|
||||
Creates a `CalculationInterpolation` with its `internal` set to an unquoted Sass
|
||||
string with text `"(" + value + ")"` and returns it.
|
||||
|
||||
##### `value`
|
||||
|
||||
Returns [`internal`](#internal)'s `value` field's text, without the leading and
|
||||
trailing parentheses.
|
||||
|
||||
##### `equals`
|
||||
|
||||
Whether `other` is a `CalculationInterpolation` and [`internal`](#internal) is
|
||||
equal to `other.internal` in Sass.
|
||||
|
||||
##### `hashCode`
|
||||
|
||||
Returns the same number for any two `CalculationInterpolation`s that are equal
|
||||
according to [`equals`](#equals).
|
||||
|
||||
## Embedded Protocol
|
||||
|
||||
### `CalculationValue.value.interpolation`
|
||||
|
||||
Add the following to this field's documentation:
|
||||
|
||||
The compiler must treat this as identical to a `string` option whose value is
|
||||
`"(" + interpolation + ")"`.
|
||||
|
||||
This field is deprecated and hosts should avoid using it.
|
||||
|
||||
## Deprecation Process
|
||||
|
||||
Before this specification is applied in full force, it will be applied with the
|
||||
following modification:
|
||||
This proposal causes two breaking changes, each of which will be mitigated by
|
||||
supporting something very close to the old behavior with a deprecation warning
|
||||
until the next major version release.
|
||||
|
||||
* When simplifying a calculation named `"abs"` whose sole argument is a number
|
||||
_without_ [known units], return the result of calling `math.abs()` with that
|
||||
number and emit a deprecation warning named `abs-percent`.
|
||||
### `abs-percent`
|
||||
|
||||
> Under this proposal, if a number with unit `%` is passed to the global `abs()`
|
||||
> function, it will be emitted as a plain CSS `abs()` rather than returning the
|
||||
> absolute value of the percentage itself.
|
||||
|
||||
During the deprecation period, when simplifying a calculation named `"abs"`
|
||||
whose sole argument is a number *without* [known units], return the result of
|
||||
calling `math.abs()` with that number and emit a deprecation warning named
|
||||
`abs-percent`.
|
||||
|
@ -290,7 +290,7 @@ export type CalculationOperator = '+' | '-' | '*' | '/';
|
||||
|
||||
The JS API representation of a Sass [`CalculationOperation`].
|
||||
|
||||
[CalculationOperation]: ../spec/types/calculation.md#types
|
||||
[`CalculationOperation`]: ../spec/types/calculation.md#types
|
||||
|
||||
```ts
|
||||
export class CalculationOperation implements ValueObject {
|
||||
|
@ -106,9 +106,7 @@ The [`SpecialFunctionName`] production will be changed to the following:
|
||||
|
||||
### `CalcValue`
|
||||
|
||||
The [`CalcValue`] production will be changed to the following:
|
||||
|
||||
[`CalcValue`]: ../spec/types/calculation.md#calculationexpression
|
||||
The `CalcValue` production will be changed to the following:
|
||||
|
||||
<x><pre>
|
||||
**CalcValue** ::= CalcValue (('+' | '-' | '*' | '/') CalcValue)+
|
||||
|
@ -3,7 +3,7 @@
|
||||
*([Issue](https://github.com/sass/sass/issues/2834))*
|
||||
|
||||
This proposal adds a new `hwb()` color format to the `sass:color` module, along
|
||||
with inspection and adjustment options for _whiteness_ and _blackness_.
|
||||
with inspection and adjustment options for *whiteness* and *blackness*.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@ -42,12 +42,12 @@ module to avoid conflicts with the CSS syntax, and will be converted to more
|
||||
common color-name, hex, or `rgba()` syntax for output -- following the same
|
||||
logic as our current color functions.
|
||||
|
||||
- New `color.hwb()` function describes colors in the sRGB colorspace using
|
||||
* New `color.hwb()` function describes colors in the sRGB colorspace using
|
||||
`$hue` (defined identically to the `hsl()` "hue" value), along with
|
||||
`$whiteness`, `$blackness`, and optional `$alpha` transparency.
|
||||
- New `color.whiteness()` and `color.blackness()` functions return the respective
|
||||
* New `color.whiteness()` and `color.blackness()` functions return the respective
|
||||
values of `w` or `b` for a given color.
|
||||
- Existing `color.adjust()`, `color.scale()`, and `color.change()` functions will
|
||||
* Existing `color.adjust()`, `color.scale()`, and `color.change()` functions will
|
||||
accept additional `$whiteness` and `$blackness` parameters before the final
|
||||
`$alpha` parameter.
|
||||
|
||||
@ -137,7 +137,6 @@ All new functions are part of the `sass:color` built-in module.
|
||||
[percent-converting]: ../spec/built-in-modules/color.md#percent-converting-a-number
|
||||
[to RGB]: https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
||||
|
||||
|
||||
* ```
|
||||
hwb($channels)
|
||||
```
|
||||
|
@ -4,6 +4,7 @@
|
||||
[Changelog](deprecations-api.changes.md))*
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Background](#background)
|
||||
* [Summary](#summary)
|
||||
* [Design Decisions](#design-decisions)
|
||||
@ -110,7 +111,7 @@ that applies only to itself, it may still do so.
|
||||
Additionally, while a deprecation's status is part of the specification, we
|
||||
chose to leave the `deprecatedIn` and `obsoleteIn` versions of each
|
||||
deprecation out of the specification. As the two current implementers of this
|
||||
API are both based on Dart Sass, these versions are _currently_ consistent
|
||||
API are both based on Dart Sass, these versions are *currently* consistent
|
||||
across implementations in practice, potential future implementers should not
|
||||
need to be tied to Dart Sass's versioning.
|
||||
|
||||
@ -579,6 +580,7 @@ the compiler must respond with a `CompileFailure` instead of a `CompileSuccess`.
|
||||
|
||||
The compiler must emit an event of type `LogEventType.WARNING` if any of the
|
||||
following is true:
|
||||
|
||||
* an invalid deprecation ID is passed
|
||||
* an obsolete deprecation ID is passed
|
||||
* a future deprecation ID is passed that is not also passed to
|
||||
|
@ -62,7 +62,7 @@ This proposal makes three breaking changes to the embedded Sass protocol:
|
||||
|
||||
#### Length Before Compilation ID
|
||||
|
||||
This proposal places the compilation ID for each request _after_ the length. The
|
||||
This proposal places the compilation ID for each request *after* the length. The
|
||||
length is defined as the length of the protocol buffer plus the length of the
|
||||
compilation ID.
|
||||
|
||||
@ -101,7 +101,7 @@ length of the compilation ID, which is given by the following table:
|
||||
|
||||
#### Cross-Compilation State
|
||||
|
||||
We have a [future goal] to (optionally) share state _across_ compilations, to
|
||||
We have a [future goal] to (optionally) share state *across* compilations, to
|
||||
more efficiently compile projects with many small entrypoints where the bulk of
|
||||
the complexity is in static shared libraries. If/when we support this, there
|
||||
could be two broad implementation strategies for a compiler with worker-based
|
||||
@ -139,8 +139,8 @@ compilation ID will only have one request at a time, so we could just declare
|
||||
that any response with a given compilation ID is for the single outstanding
|
||||
request.
|
||||
|
||||
However, the _expectation_ that each compilation be single-threaded isn't a
|
||||
_requirement_. One could imagine a multithreaded Sass compiler that actually is
|
||||
However, the *expectation* that each compilation be single-threaded isn't a
|
||||
*requirement*. One could imagine a multithreaded Sass compiler that actually is
|
||||
capable of fielding multiple concurrent requests as it compiles independent
|
||||
chunks of a given stylesheet or resolves loads eagerly. We don't want to cut off
|
||||
this possibility, so we retain the outbound request IDs.
|
||||
@ -153,12 +153,12 @@ Replace the last paragraph of the [embedded protocol overview] with:
|
||||
|
||||
[embedded protocol overview]: ../spec/embedded-protocol.md#overview
|
||||
|
||||
Each message in the embedded protocol is sent as a _packet_ which contains two
|
||||
Each message in the embedded protocol is sent as a *packet* which contains two
|
||||
values: an unsigned [varint] up to 32 bits long known as the "compilation ID",
|
||||
and a protocol buffer that contains the protobuf message. For streams (like
|
||||
standard input and output) that don't have built-in message boundaries, every
|
||||
packet must begin with another unsigned varint indicating the length in bytes of
|
||||
the remaining message (_including the compilation ID_). This matches the best
|
||||
the remaining message (*including the compilation ID*). This matches the best
|
||||
practice described in [the protocol buffer documentation].
|
||||
|
||||
Because JavaScript can't easily represent integers larger than 2^53 - 1, the
|
||||
|
@ -18,7 +18,7 @@ extend(a.foo, a, b) = a.foo, b.foo
|
||||
extend(c, a, b) = c
|
||||
```
|
||||
|
||||
### Specificity of the Base Selector
|
||||
## Specificity of the Base Selector
|
||||
|
||||
Note that so far, it's always the case that `extend(S, A, B)[0] = S`. However,
|
||||
consider `extend(a.foo, .foo, a)`. One interpretation of this would give the
|
||||
@ -62,12 +62,12 @@ This new selector has higher specificity than the original. As such, we must
|
||||
allow the generated selector to have higher specificity than the original in
|
||||
some cases.
|
||||
|
||||
#### First Law of Extend: `spec(extend(S, A, B)[0]) >= spec(S)`
|
||||
### First Law of Extend: `spec(extend(S, A, B)[0]) >= spec(S)`
|
||||
|
||||
This is not always the behavior in Sass, either in master or in stable; this is
|
||||
clearly a bug that should be fixed.
|
||||
|
||||
### Specificity of Generated Selectors
|
||||
## Specificity of Generated Selectors
|
||||
|
||||
Now that we've established what `spec(extend(S, A, B)[0])` should look like,
|
||||
it's time to think about what `spec(extend(S, A, B)[1])` should look like as
|
||||
@ -97,9 +97,9 @@ There is one guarantee we can make, though:
|
||||
`spec(extend(S, A, B)[1]) >= spec(B)`, since everything in `S` is either merged
|
||||
with or added to `B`.
|
||||
|
||||
#### Second Law of Extend: `spec(extend(S, A, B)[1]) >= spec(B)`
|
||||
### Second Law of Extend: `spec(extend(S, A, B)[1]) >= spec(B)`
|
||||
|
||||
### Implications for Optimization
|
||||
## Implications for Optimization
|
||||
|
||||
The ultimate goal of this discussion is, of course, that we want to be able to
|
||||
perform certain optimizations on the generated selectors in order to reduce
|
||||
@ -120,7 +120,7 @@ However, many of the optimizations added in [8f4869e][] do still work. For
|
||||
example, `extend(.bar a, a, a.foo) = .bar a` works because
|
||||
`spec(.bar a) = spec(a.foo)`.
|
||||
|
||||
### Conclusion
|
||||
## Conclusion
|
||||
|
||||
As long as we make the `@extend` optimizer specificity-aware, we can retain a
|
||||
number of useful optimizations while still providing the same guarantees that
|
||||
|
@ -380,8 +380,6 @@ case-insensitively.
|
||||
|
||||
†: These productions are invalid in plain CSS syntax.
|
||||
|
||||
[`<function-token>`]: https://drafts.csswg.org/css-syntax-3/#ref-for-typedef-function-token%E2%91%A0
|
||||
|
||||
> The `CalcArgument` production provides backwards-compatibility with the
|
||||
> historical use of interpolation to inject SassScript values into `calc()`
|
||||
> expressions. Because interpolation could inject any part of a `calc()`
|
||||
@ -391,9 +389,7 @@ case-insensitively.
|
||||
### `CssMinMax`
|
||||
|
||||
This proposal replaces the reference to `CalcValue` in the definition of
|
||||
[`CssMinMax`] with `CalcArgument`.
|
||||
|
||||
[`CssMinMax`]: ../spec/types/calculation.md#cssminmax
|
||||
`CssMinMax` with `CalcArgument`.
|
||||
|
||||
> Note that this increases the number of cases where a `MinMaxExpression` will
|
||||
> be parsed as a `CssMinMax` rather than a `FunctionExpression` (for example,
|
||||
@ -544,12 +540,12 @@ This algorithm takes a `CalculationValue` `value` and returns a
|
||||
|
||||
* If `value` is a calculation:
|
||||
|
||||
* Let `result` be the result of [simplifying] `value`.
|
||||
* Let `result` be the result of [simplifying] `value`.
|
||||
|
||||
* If `result` is a calculation whose name is `"calc"`, return `result`'s
|
||||
* If `result` is a calculation whose name is `"calc"`, return `result`'s
|
||||
single argument.
|
||||
|
||||
* Otherwise, return `result`.
|
||||
* Otherwise, return `result`.
|
||||
|
||||
[simplifying]: #simplifying-a-calculation
|
||||
|
||||
|
@ -61,7 +61,6 @@ one would with function values:
|
||||
|
||||
* `meta.call()` => `meta.apply()`
|
||||
|
||||
|
||||
### JavaScript API Design Decisions
|
||||
|
||||
Mixins differ from functions in that the result of their execution is a Sass AST
|
||||
@ -73,7 +72,6 @@ For this reason, it is not meaningful -- or even possible -- to construct or
|
||||
execute a mixin through the JavaScript API. A mixin object shall be opaque, and
|
||||
the only operation available shall be to return the object as-is.
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
This proposal promotes the [mixin value] to a Sass value type.
|
||||
@ -290,4 +288,4 @@ message CompilerMixin {
|
||||
The protocol allows first-class mixins defined in the compiler to be passed
|
||||
to the host and vice-versa as `Value.CompilerMixin`s.
|
||||
|
||||
Two first-class mixins are equal if they have the same ID.
|
||||
Two first-class mixins are equal if they have the same ID.
|
@ -660,8 +660,6 @@ Replace this function's procedure with:
|
||||
* Let `double` be the value of [converting `$number` to `rad`] allowing
|
||||
unitless.
|
||||
|
||||
[converting `$number` to `rad`]: #converting-a-number-to-units
|
||||
|
||||
* Return a unitless number whose value is the result of `sin(double)` as defined
|
||||
by [IEEE 754 2019], §9.2.
|
||||
|
||||
@ -672,8 +670,6 @@ Replace this function's procedure with:
|
||||
* Let `double` be the value of [converting `$number` to `rad`] allowing
|
||||
unitless.
|
||||
|
||||
[converting `$number` to `rad`]: #converting-a-number-to-units
|
||||
|
||||
* Return a unitless number whose value is the result of `tan(double)` as defined
|
||||
by [IEEE 754 2019], §9.2.
|
||||
|
||||
|
@ -46,23 +46,23 @@ I propose that, as today, `#{}` be allowed either within strings (quoted or
|
||||
unquoted) or on its own. However, its effect will be limited to the strings that
|
||||
contain it or to its own value. Specifically:
|
||||
|
||||
- When parsing or evaluating a quoted string, treat interpolation the same way
|
||||
* When parsing or evaluating a quoted string, treat interpolation the same way
|
||||
it's treated today.
|
||||
- When parsing an identifier, treat interpolation as though it's an alphabetic
|
||||
* When parsing an identifier, treat interpolation as though it's an alphabetic
|
||||
character. When evaluating an interpolated unquoted string, concatenate the
|
||||
literal identifier characters with the values of the interpolated segments.
|
||||
- Otherwise, parse an interpolation as an individual expression. When evaluating
|
||||
* Otherwise, parse an interpolation as an individual expression. When evaluating
|
||||
it, return its value as an unquoted string.
|
||||
|
||||
Here are some examples (I'm including quotes for unquoted strings in the output
|
||||
to clarify their extents):
|
||||
|
||||
- `"a #{b} c"` would continue to produce `"a b c"`.
|
||||
- `a#{b}c` would continue to produce `"abc"`.
|
||||
- `a #{b}c` currently produces `"a bc"` but would produce `"a" "bc"`.
|
||||
- `a#{b} c` currently produces `"ab c"` but would produce `"ab" "c"`.
|
||||
- `a b#{c}d e` currently produces `"a bcd e"` but would produce `"a" "bcd" "e"`.
|
||||
- `a #{b} c` currently produces `"a b c"` but would produce `"a" "b" "c"`.
|
||||
* `"a #{b} c"` would continue to produce `"a b c"`.
|
||||
* `a#{b}c` would continue to produce `"abc"`.
|
||||
* `a #{b}c` currently produces `"a bc"` but would produce `"a" "bc"`.
|
||||
* `a#{b} c` currently produces `"ab c"` but would produce `"ab" "c"`.
|
||||
* `a b#{c}d e` currently produces `"a bcd e"` but would produce `"a" "bcd" "e"`.
|
||||
* `a #{b} c` currently produces `"a b c"` but would produce `"a" "b" "c"`.
|
||||
|
||||
## Design decisions
|
||||
|
||||
@ -77,24 +77,24 @@ various situations.
|
||||
|
||||
### Interpolation in unquoted strings
|
||||
|
||||
It was tempting to restrict interpolation for use _only_ in quoted strings.
|
||||
It was tempting to restrict interpolation for use *only* in quoted strings.
|
||||
Interpolation in unquoted strings can be mimicked using `+`, and allowing it in
|
||||
unquoted strings could produce the incorrect impression that interpolation is
|
||||
performed before any other SassScript resolution. However, we decided to allow
|
||||
this for several reasons:
|
||||
|
||||
- Backwards compatibility, as described above.
|
||||
- Similarity with quoted strings. It's not always obvious that unquoted strings
|
||||
* Backwards compatibility, as described above.
|
||||
* Similarity with quoted strings. It's not always obvious that unquoted strings
|
||||
and quoted strings are the same sorts of value under the hood, but sharing
|
||||
capabilities helps reinforce that idea.
|
||||
- Similarity with other identifiers. Interpolation can be used in almost all
|
||||
* Similarity with other identifiers. Interpolation can be used in almost all
|
||||
most non-SassScript contexts where identifiers appear, most notably property
|
||||
names, so it's natural that users would think that all Sass identifiers can be
|
||||
interpolated.
|
||||
- Vendor prefixes. It would be very difficult to dynamically choose vendor
|
||||
* Vendor prefixes. It would be very difficult to dynamically choose vendor
|
||||
prefixes for function names or other values, since `-` on its own is not an
|
||||
identifier.
|
||||
- Aesthetics. Although `font-stretch: $amount + -condensed` is legal, it's less
|
||||
* Aesthetics. Although `font-stretch: $amount + -condensed` is legal, it's less
|
||||
clear and less pleasant than `font-stretch: #{$amount}-condensed`.
|
||||
|
||||
### Interpolation outside of strings
|
||||
@ -128,13 +128,13 @@ rules, and E the value of the same expression under the new rules. Let S2 be the
|
||||
conversion of E to CSS. For example, suppose the expression in question is
|
||||
`a #{b} + c`. S1 is `"a b + c"`, E is `"a" "bc"`, and S2 is `"a bc"`.
|
||||
|
||||
- If S1 and S2 aren't semantically identical when interpreted as CSS, issue a
|
||||
* If S1 and S2 aren't semantically identical when interpreted as CSS, issue a
|
||||
warning. This means that `#{a} + b` would emit a warning since S1 is `"a + b"`
|
||||
but S2 is `"ab"`. However, `#{a} b c` would not emit a warning, since S1 and
|
||||
S2 are both `"a b c"`. Note that an expressions like `#{a} / b` _should not_
|
||||
S2 are both `"a b c"`. Note that an expressions like `#{a} / b` *should not*
|
||||
emit a warning here, since we know that it will produce `a/b` under the new
|
||||
semantics.
|
||||
- Otherwise, if E is not a string, set an "interpolated" flag on S1. If any
|
||||
* Otherwise, if E is not a string, set an "interpolated" flag on S1. If any
|
||||
operation is performed on S1 that wouldn't first convert it to a string, emit
|
||||
a warning.
|
||||
|
||||
@ -146,14 +146,14 @@ problem in the second case, which we'll get to below.
|
||||
|
||||
In service of determining how to go about deprecating the current semantics of
|
||||
SassScript interpolation, I want to precisely define them. For our purposes, we
|
||||
only care about _free interpolation_—that is, interpolation outside the context
|
||||
only care about *free interpolation*—that is, interpolation outside the context
|
||||
of a string or a special function (e.g. `calc()`) that's parsed like a string.
|
||||
|
||||
The grammar for interpolation is straightforward. Note that the representation
|
||||
below elides much of the unrelated complexity of the SassScript grammar. The
|
||||
`Operation` and `UnaryOperation` productions should be understood to encompass
|
||||
all binary and unary operations supported by SassScript, except for `,` which is
|
||||
handled by the `CommaList` production. Note that this _includes_ the implicit
|
||||
handled by the `CommaList` production. Note that this *includes* the implicit
|
||||
adjacency operator that normally creates space-separated lists. `Value` should
|
||||
be understood to encompass literals, parenthesized expressions, maps, and
|
||||
function calls.
|
||||
@ -192,8 +192,8 @@ If there was any whitespace in the source text between the operator and the
|
||||
| `-#{1}` | ``` `-#{1}` ``` | `-1` |
|
||||
| `- #{1}` | ``` `- #{1}` ``` | `- 1` |
|
||||
|
||||
For an `Operation` production, all _adjacent_ `UnaryOperation` sub-expressions
|
||||
that are _not_ `Interpolation`s are parsed as normal, and interpolated into the
|
||||
For an `Operation` production, all *adjacent* `UnaryOperation` sub-expressions
|
||||
that are *not* `Interpolation`s are parsed as normal, and interpolated into the
|
||||
ESI alongside the `Interpolation` subexpressions, separated by the operation in
|
||||
question. As with a `UnaryOperation`, a space will be included before or after
|
||||
the `Interpolation`s depending on whether whitespace appeared in the
|
||||
@ -212,7 +212,7 @@ have a space operator.
|
||||
| `a#{b}c` | ``` `#{a}#{b}#{c}` ``` | `abc` |
|
||||
|
||||
Finally, `CommaList` productions behave almost the same as `Operation`s. The
|
||||
only difference is that if _only_ the first `Operation` sub-expression is an
|
||||
only difference is that if *only* the first `Operation` sub-expression is an
|
||||
`Interpolation`, the rest of the list isn't included in the interpolation.
|
||||
|
||||
| SassScript | ESI | CSS |
|
||||
@ -228,8 +228,8 @@ Now that we (hopefully) have a clear idea of how free interpolation works right
|
||||
now, we can start figuring out the surface area that needs deprecation warnings
|
||||
when moving to the new semantics.
|
||||
|
||||
Ideally, we want to warn only when the new semantics will produce _semantically
|
||||
different_ CSS output. In practice determining this exactly isn't always
|
||||
Ideally, we want to warn only when the new semantics will produce *semantically
|
||||
different* CSS output. In practice determining this exactly isn't always
|
||||
feasible, since free interpolation produces values that can be used in many
|
||||
heterogeneous ways, so instead we'll warn if the values they produce are ever
|
||||
used in a way that will change behavior under the new semantics.
|
||||
@ -268,7 +268,7 @@ different meanings. This includes any operators that don't insert their own
|
||||
textual representation when operating on a string.
|
||||
|
||||
The following operators and their inverses should produce warnings immediately.
|
||||
Note that _any expression_ containing free interpolation whose new ESI contains
|
||||
Note that *any expression* containing free interpolation whose new ESI contains
|
||||
these operators should have an immediate warning, even if they also include
|
||||
other operators.
|
||||
|
||||
@ -328,7 +328,7 @@ the first and fourth should not, as their stringifications remain the same.
|
||||
There is one case where the new behavior differs from the old. It comes up when
|
||||
a dynamic value is included in an interpolated string without an explicit
|
||||
`#{}`—that is, for every location that doesn't have a `#{}` in the SassScript
|
||||
but does in the ESI. _If that value is a quoted string_, it will retain its
|
||||
but does in the ESI. *If that value is a quoted string*, it will retain its
|
||||
quotes, where if it were explicitly interpolated it would lose them. For
|
||||
example:
|
||||
|
||||
@ -375,30 +375,30 @@ as a list. When passing an interpolation value produced via a list operator to
|
||||
such a function, the implementation should emit a deprecation warning. Of the
|
||||
canonical Sass functions, this includes:
|
||||
|
||||
- `unquote()`
|
||||
- `quote()`
|
||||
- `str-length()`
|
||||
- The first or second argument of `str-insert()`
|
||||
- `str-index()`
|
||||
- The first argument of `str-slice()`
|
||||
- `to-upper-case()`
|
||||
- `to-lower-case()`
|
||||
- `length()`
|
||||
- The first argument of `nth()`
|
||||
- The first argument of `set-nth()`
|
||||
- `join()`
|
||||
- The first or last argument of `append()`
|
||||
- `zip()`
|
||||
- The first argument of `index()`
|
||||
- `list-separator()`
|
||||
- `feature-exists()`
|
||||
- `variable-exists()`
|
||||
- `global-variable-exists()`
|
||||
- `function-exists()`
|
||||
- `mixin-exists()`
|
||||
- `inspect()`
|
||||
- `type-of()`
|
||||
- The first argument of `call()`
|
||||
* `unquote()`
|
||||
* `quote()`
|
||||
* `str-length()`
|
||||
* The first or second argument of `str-insert()`
|
||||
* `str-index()`
|
||||
* The first argument of `str-slice()`
|
||||
* `to-upper-case()`
|
||||
* `to-lower-case()`
|
||||
* `length()`
|
||||
* The first argument of `nth()`
|
||||
* The first argument of `set-nth()`
|
||||
* `join()`
|
||||
* The first or last argument of `append()`
|
||||
* `zip()`
|
||||
* The first argument of `index()`
|
||||
* `list-separator()`
|
||||
* `feature-exists()`
|
||||
* `variable-exists()`
|
||||
* `global-variable-exists()`
|
||||
* `function-exists()`
|
||||
* `mixin-exists()`
|
||||
* `inspect()`
|
||||
* `type-of()`
|
||||
* The first argument of `call()`
|
||||
|
||||
It's up to each implementation to determine whether to emit warnings for which
|
||||
user-defined functions.
|
||||
|
@ -131,15 +131,12 @@ This production has the same grammar as [`<ident-token>`][].
|
||||
This algorithm consumes input from a stream of [code points][] and returns a
|
||||
sequence of strings and/or expressions.
|
||||
|
||||
[code points]: https://infra.spec.whatwg.org/#code-point
|
||||
|
||||
The grammar for this production is:
|
||||
|
||||
<x><pre>
|
||||
**InterpolatedIdentifier** ::= ([\<ident-token>][`<ident-token>`] | '-'? Interpolation) ([Name](#consuming-a-name) | Interpolation)*
|
||||
</pre></x>
|
||||
|
||||
[name-start code point]: https://drafts.csswg.org/css-syntax-3/#name-start-code-point
|
||||
[escape]: https://drafts.csswg.org/css-syntax-3/#escape-diagram
|
||||
|
||||
No whitespace is allowed between components of an `InterpolatedIdentifier`.
|
||||
@ -219,10 +216,9 @@ This production has the same grammar as [`escape`][escape] in CSS Syntax Level 3
|
||||
[non-printable code point]: https://drafts.csswg.org/css-syntax-3/#non-printable-code-point
|
||||
[digit]: https://drafts.csswg.org/css-syntax-3/#digit
|
||||
|
||||
* Let `code` be the lowercase hexadecimal representation of `codepoint`,
|
||||
* Let `code` be the lowercase hexadecimal representation of `codepoint`,
|
||||
with no leading `0`s.
|
||||
|
||||
* Return `"\"` + `code` + `" "`.
|
||||
* Return `"\"` + `code` + `" "`.
|
||||
|
||||
* Otherwise, return `"\"` + `character`.
|
||||
|
||||
|
@ -139,7 +139,6 @@ created, if the type is not specified, it is considered *explicit*.
|
||||
This proposal modifies the fourth bullet of the [Loading Modules][] procedure
|
||||
within the [module system spec][] to read as follows:
|
||||
|
||||
|
||||
* If `file` has already been [executed][]:
|
||||
|
||||
* If `config` is **explicit and** not empty, throw an error.
|
||||
|
@ -61,8 +61,8 @@ all identifiers matched case-insensitively):
|
||||
|
||||
<x><pre>
|
||||
**MediaQuery** ::= MediaNot
|
||||
  | MediaInParens (MediaAnd* | MediaOr*)
|
||||
  | MediaType ('and' MediaNot | MediaAnd*)
|
||||
  | MediaInParens (MediaAnd\* | MediaOr\*)
|
||||
  | MediaType ('and' MediaNot | MediaAnd\*)
|
||||
**MediaType** ::= [InterpolatedIdentifier] [InterpolatedIdentifier]¹?
|
||||
**MediaNot**² ::= 'not' MediaOrInterp
|
||||
**MediaAnd**² ::= 'and' MediaOrInterp
|
||||
@ -73,7 +73,7 @@ all identifiers matched case-insensitively):
|
||||
  | '(' Expression³ [\<mf-lt>] Expression³ [\<mf-lt>] Expression³ ')'
|
||||
  | '(' Expression³ [\<mf-gt>] Expression³ [\<mf-gt>] Expression³ ')'
|
||||
  | '(' MediaNot ')'
|
||||
  | '(' MediaInParens (MediaAnd* | MediaOr*) ')'
|
||||
  | '(' MediaInParens (MediaAnd\* | MediaOr\*) ')'
|
||||
</pre></x>
|
||||
|
||||
[InterpolatedIdentifier]: ../spec/syntax.md#interpolatedidentifier
|
||||
|
@ -69,7 +69,6 @@ intended to replace the existing syntax.
|
||||
  | '(' Expression² <mf-gt> Expression² <mf-gt> Expression² ')'
|
||||
</pre></x>
|
||||
|
||||
|
||||
1: This `InterpolatedIdentifier` may not be the identifier `"and"`.
|
||||
|
||||
2: These `Expression`s may not contain binary operator expressions with the
|
||||
|
@ -81,7 +81,7 @@ The grammar for this production is:
|
||||
|
||||
<x><pre>
|
||||
**MinMaxExpression** ::= CssMinMax | FunctionExpression
|
||||
**CssMinMax** ::= ('min(' | 'max(') CalcValue (',' CalcValue)* ')'
|
||||
**CssMinMax** ::= ('min(' | 'max(') CalcValue (',' CalcValue)\* ')'
|
||||
**CalcValue** ::= CalcValue (('+' | '-' | '*' | '/') CalcValue)+
|
||||
  | '(' CalcValue ')'
|
||||
  | ('calc(' | 'env(' | 'var(') InterpolatedDeclarationValue ')'
|
||||
|
@ -777,7 +777,7 @@ controlled as explicitly as members can.
|
||||
> ```
|
||||
>
|
||||
> isn't identical (from a downstream user's perspective) to
|
||||
>
|
||||
>
|
||||
> ```scss
|
||||
> .foo, .bar { /* ... */ }
|
||||
> ```
|
||||
@ -853,7 +853,6 @@ CSS for *all* modules transitively used or forwarded by `starting-module`.
|
||||
* Add a copy of `extension` with its extender replaced by `complex` to
|
||||
`new-extensions[domestic]`.
|
||||
|
||||
[the first law of extend]: ../spec/at-rules/extend.md#specificity
|
||||
[the specificity laws of extend]: ../spec/at-rules/extend.md#specificity
|
||||
|
||||
* Let `css` be an empty CSS tree.
|
||||
@ -867,7 +866,7 @@ CSS for *all* modules transitively used or forwarded by `starting-module`.
|
||||
|
||||
> Because this traverses modules depth-first, it emits CSS in reverse
|
||||
> topological order.
|
||||
|
||||
|
||||
* Let `initial-imports` be the longest initial subsequence of top-level
|
||||
statements in `domestic`'s CSS that contains only comments and `@import`
|
||||
rules *and* that ends with an `@import` rule.
|
||||
@ -888,7 +887,6 @@ CSS for *all* modules transitively used or forwarded by `starting-module`.
|
||||
|
||||
* Return `css`.
|
||||
|
||||
[queue]: https://en.wikipedia.org/wiki/Queue_(abstract_data_type)
|
||||
[topological]: https://en.wikipedia.org/wiki/Topological_sorting
|
||||
|
||||
### Resolving a `file:` URL
|
||||
@ -1096,7 +1094,7 @@ Given a source file `file`, a [configuration](#configuration) `config`, and an
|
||||
|
||||
* Remove any [complex selectors][] containing a placeholder selector that
|
||||
begins with `-` or `_` from `css`.
|
||||
|
||||
|
||||
* Remove any style rules that now have no selector from `css`.
|
||||
|
||||
* Append `css` to `module`'s CSS.
|
||||
|
@ -5,6 +5,7 @@
|
||||
This proposal adds the following members to the built-in `sass:math` module.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Background](#background)
|
||||
* [Summary](#summary)
|
||||
* [Semantics](#semantics)
|
||||
|
@ -23,13 +23,13 @@ setting, and getting elements from nested maps.
|
||||
|
||||
Variables have always been a key feature of the Sass language. But these days,
|
||||
design systems and component libraries form the basis of most CSS projects --
|
||||
with well organized _design tokens_ as the foundation. While Individual token
|
||||
with well organized *design tokens* as the foundation. While Individual token
|
||||
variables can be quite useful, the ability to group tokens into structured and
|
||||
meaningful relationships is essential for creating resilient systems.
|
||||
|
||||
There are many ways to group tokens. The popular [Style Dictionary] recommends a
|
||||
deep nesting of _category_, _type_, _item_, _sub-item_, and _state_. Other
|
||||
taxonomies also include concepts like _theme_, or even _operating system_. Most
|
||||
deep nesting of *category*, *type*, *item*, *sub-item*, and *state*. Other
|
||||
taxonomies also include concepts like *theme*, or even *operating system*. Most
|
||||
of the existing tools rely on YAML or JSON objects to achieve that nested
|
||||
structure, at the expense of other important information. YAML and JSON are not
|
||||
design languages, and do not understand fundamental CSS concepts like color or
|
||||
|
@ -97,9 +97,9 @@ a {
|
||||
```
|
||||
|
||||
Linked order makes sense when using comments to annotate information about
|
||||
dependencies, but it's counterproductive when a user wants to annotate the _end_
|
||||
dependencies, but it's counterproductive when a user wants to annotate the *end*
|
||||
of a module, since that comment would be considered linked to the next module
|
||||
load. Traversal order handles that case better _and_ matches the old `@import`
|
||||
load. Traversal order handles that case better *and* matches the old `@import`
|
||||
behavior, so we chose to use it instead.
|
||||
|
||||
## Procedures
|
||||
|
@ -41,8 +41,6 @@ to access the URL of the stylesheet that contained the load, known in the legacy
|
||||
API as the "previous URL". This was an intentional design choice which enforced
|
||||
the invariant that the same canonical URL always refers to the same file.
|
||||
|
||||
[new import API]: ../accepted/new-js-importer.d.ts
|
||||
|
||||
However, this restriction makes it difficult for importers to work as expected
|
||||
in certain contexts. For example, in the Node.js ecosystem JS loads depend on
|
||||
the structure of the `node_modules` directory closest to the containing file.
|
||||
@ -63,10 +61,10 @@ that provides the canonical URL of the containing file (the "containing URL").
|
||||
However, in order to preserve the desired invariants, this option is only
|
||||
provided when either:
|
||||
|
||||
- `Importer.canonicalize()` is being passed a relative URL (which means the URL
|
||||
* `Importer.canonicalize()` is being passed a relative URL (which means the URL
|
||||
has already been tried as a load relative to the current canonical URL), or
|
||||
|
||||
- `Importer.canonicalize()` is passed an absolute URL whose scheme the importer
|
||||
* `Importer.canonicalize()` is passed an absolute URL whose scheme the importer
|
||||
has declared as non-canonical.
|
||||
|
||||
A "non-canonical" scheme is a new concept introduced by this proposal.
|
||||
@ -110,7 +108,7 @@ Providing access to the containing URL puts these invariants at risk in two ways
|
||||
|
||||
#### Alternatives Considered
|
||||
|
||||
To mitigate these risks, we need to have _some_ restriction on when the
|
||||
To mitigate these risks, we need to have *some* restriction on when the
|
||||
containing URL is available to importers. We considered the following
|
||||
alternative restrictions before settling on the current one:
|
||||
|
||||
@ -155,7 +153,7 @@ URLs, this is a blocking limitation.
|
||||
[package imports]: https://github.com/sass/sass/issues/2739
|
||||
|
||||
Thus we arrive at the actual behavior, which makes the containing URL
|
||||
unavailable for absolute loads _unless_ they have a URL scheme declared
|
||||
unavailable for absolute loads *unless* they have a URL scheme declared
|
||||
explicitly non-canonical. This supports the `pkg:` use-case while still
|
||||
protecting against risk (1), since the containing URL is never available for
|
||||
canonical resolutions.
|
||||
@ -190,7 +188,7 @@ interface FileImporter<sync extends 'sync' | 'async' = 'sync' | 'async'> {
|
||||
```ts
|
||||
findFileUrl(
|
||||
url: string,
|
||||
options: {fromImport: boolean, containingUrl?: URL}
|
||||
options: {fromImport: boolean; containingUrl?: URL}
|
||||
): PromiseOr<URL | null, sync>;
|
||||
```
|
||||
|
||||
|
@ -33,7 +33,6 @@ However, a numeric integer can include units (e.g. `5px` or `8em`) and the
|
||||
current behavior [drops the units][issue], which is unexpected for most users.
|
||||
For example: `math.random(42px) => 28` (there is no `px`).
|
||||
|
||||
[random]: https://sass-lang.com/documentation/modules/math#random
|
||||
[issue]: https://github.com/sass/sass/issues/1890
|
||||
|
||||
## Summary
|
||||
@ -83,9 +82,10 @@ defaults to `null`.
|
||||
[units] as `$limit`.
|
||||
|
||||
> Examples:
|
||||
> - `math.random(123) => 87`
|
||||
> - `math.random(123px) => 43px`
|
||||
> - `math.random(500%) => 238%`
|
||||
>
|
||||
> * `math.random(123) => 87`
|
||||
> * `math.random(123px) => 43px`
|
||||
> * `math.random(500%) => 238%`
|
||||
|
||||
* Otherwise throw an error.
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
## Draft 3.1
|
||||
|
||||
* Update to accommodate new calculation parsing logic.
|
||||
|
||||
## Draft 3
|
||||
|
||||
* Make a potentially slash-separated number slash-free when passing it as an
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Forward Slash as a Separator: Draft 3
|
||||
# Forward Slash as a Separator: Draft 3.1
|
||||
|
||||
*([Issue](https://github.com/sass/sass/issues/2565), [Changelog](slash-separator.changes.md))*
|
||||
|
||||
@ -15,7 +15,13 @@ operator.
|
||||
* [First-Class `calc()`](#first-class-calc)
|
||||
* [`math()` Syntax](#math-syntax)
|
||||
* [Existing Behavior](#existing-behavior)
|
||||
* [Definitions](#definitions)
|
||||
* [Calculation-Safe Expression](#calculation-safe-expression)
|
||||
* [Syntax](#syntax)
|
||||
* [Procedures](#procedures)
|
||||
* [Evaluating a `FunctionCall` as a Calculation](#evaluating-a-functioncall-as-a-calculation)
|
||||
* [Adjusting Slash Precedence](#adjusting-slash-precedence)
|
||||
* [`SlashListExpression`](#slashlistexpression)
|
||||
* [Semantics](#semantics)
|
||||
* [Slash-Separated Lists](#slash-separated-lists)
|
||||
* [`math.div()` Function](#mathdiv-function)
|
||||
@ -199,6 +205,16 @@ value, it is written as the original numerator followed by `/` followed by the
|
||||
original denominator. If either the original numerator or denominator are
|
||||
themselves slash-separated, they're also written this way.
|
||||
|
||||
## Definitions
|
||||
|
||||
### Calculation-Safe Expression
|
||||
|
||||
Remove "or `/`" from the definition of a [calculation-safe] `ProductExpression`.
|
||||
Add "An unbracketed `SlashListExpression` with more than one element, all of
|
||||
which are calculation-safe" to the list of calculation-safe expressions.
|
||||
|
||||
[calculation-safe]: ../spec/types/calculation.md#calculation-safe-expression
|
||||
|
||||
## Syntax
|
||||
|
||||
> Note that the existing productions being modified have not been defined
|
||||
@ -237,6 +253,86 @@ When a `SlashListExpression` with one or more `/`s is evaluated, it produces a
|
||||
list object whose contents are the values of its constituent
|
||||
`SpaceListExpression`s and whose separator is "slash".
|
||||
|
||||
## Procedures
|
||||
|
||||
### Evaluating a `FunctionCall` as a Calculation
|
||||
|
||||
Replace "evaluating each `Expression`" with "[adjusting slash precedence] in and
|
||||
then evaluating each `Expression`" in [evaluting a `FunctionCall` as a
|
||||
calculation].
|
||||
|
||||
[adjusting slash precedence]: #adjusting-slash-precedence
|
||||
[evaluting a `FunctionCall` as a calculation]: ../spec/types/calculation.md#evaluating-a-functioncall-as-a-calculation
|
||||
|
||||
### Adjusting Slash Precedence
|
||||
|
||||
This algorithm takes a calculation-safe expression `expression` and returns
|
||||
another calculation-safe expression with the precedence of
|
||||
`SlashListExpression`s adjusted to match division precedence.
|
||||
|
||||
* Return a copy of `expression` except, for each `SlashListExpression`:
|
||||
|
||||
* Let `left` be the first element of the list.
|
||||
|
||||
* For each remaining element `right`:
|
||||
|
||||
* If `left` and `right` are both `SumExpression`s:
|
||||
|
||||
* Let `last-left` be the last operand of `left` and `first-right` the
|
||||
first operand of `right`.
|
||||
|
||||
* Set `left` to a `SumExpression` that begins with all operands and
|
||||
operators of `left` except `last-left`, followed by a
|
||||
`SlashListExpression` with elements `last-left` and `first-right`,
|
||||
followed by all operators and operands of `right` except `first-right`.
|
||||
|
||||
> For example, `slash-list(1 + 2, 3 + 4)` becomes `1 + (2 / 3) + 4`.
|
||||
|
||||
* Otherwise, if `left` is a `SumExpression`:
|
||||
|
||||
* Let `last-left` be the last operand of `left`.
|
||||
|
||||
* Set `left` to a `SumExpression` that begins with all operands and
|
||||
operators of `left` except `last-left`, followed by a
|
||||
`SlashListExpression` with elements `last-left` and `right`.
|
||||
|
||||
> For example, `slash-list(1 + 2, 3)` becomes `1 + (2 / 3)`.
|
||||
|
||||
* Otherwise, if `right` is a `SumExpression` or a `ProductExpression`:
|
||||
|
||||
* Let `first-right` be the first operand of `right`.
|
||||
|
||||
* Set `left` to an expression of the same type as `right` that begins a
|
||||
`SlashListExpression` with elements `left` and `first-right`, followed
|
||||
by operators and operands of `right` except `first-right`.
|
||||
|
||||
> For example, `slash-list(1, 2 * 3)` becomes `(1 / 2) * 3`.
|
||||
|
||||
* Otherwise, if `left` is a slash-separated list, add `right` to the end.
|
||||
|
||||
* Otherwise, set `left` to a slash-separated list containing `left` and
|
||||
`right`.
|
||||
|
||||
* Replace each element in `left` with the result of adjusting slash precedence
|
||||
in that element.
|
||||
|
||||
* Replace the `SlashListExpression` with `left` in the returned expression.
|
||||
|
||||
### `SlashListExpression`
|
||||
|
||||
To evaluate a `SlashListExpression` as a calculation value:
|
||||
|
||||
* Let `left` be the result of evaluating the first element of the list as a
|
||||
calculation value.
|
||||
|
||||
* For each remaining element `element`:
|
||||
|
||||
* Let `right` be the result of evaluating `element` as a calculation value.
|
||||
|
||||
* Set `left` to a `CalcOperation` with operator `"/"`, `left`, and `right`.
|
||||
|
||||
* Return `left`.
|
||||
|
||||
## Semantics
|
||||
|
||||
### Slash-Separated Lists
|
||||
|
@ -5,7 +5,7 @@
|
||||
## Draft 1.1
|
||||
|
||||
* Returns a bracketed list instead of an unbracketed one to be more clear
|
||||
about what type of value is being returned.
|
||||
about what type of value is being returned.
|
||||
|
||||
## Draft 1
|
||||
|
||||
|
@ -15,22 +15,22 @@ This proposal adds `string.split()` to the `sass:string` module.
|
||||
|
||||
> This section is non-normative.
|
||||
|
||||
The `sass:string` module contains several functions for manipulating and finding
|
||||
out information about strings. Currently, though, there is no built-in function
|
||||
that splits one string into a list of substrings, and authors have been creating
|
||||
The `sass:string` module contains several functions for manipulating and finding
|
||||
out information about strings. Currently, though, there is no built-in function
|
||||
that splits one string into a list of substrings, and authors have been creating
|
||||
their own versions of functions that achieve this functionality.
|
||||
|
||||
## Summary
|
||||
|
||||
> This section is non-normative.
|
||||
|
||||
This proposal adds the `string.split()` function to the `sass:string` module.
|
||||
The function takes a string, splits it based on a provided separator, and
|
||||
This proposal adds the `string.split()` function to the `sass:string` module.
|
||||
The function takes a string, splits it based on a provided separator, and
|
||||
returns a bracketed, comma-separated list of substrings.
|
||||
|
||||
This could be used to take a string and repurpose parts of it for some other
|
||||
use. For example, fonts contained in a font stack list could be split into
|
||||
segments and then used as keys in a new map.
|
||||
This could be used to take a string and repurpose parts of it for some other
|
||||
use. For example, fonts contained in a font stack list could be split into
|
||||
segments and then used as keys in a new map.
|
||||
|
||||
Examples:
|
||||
|
||||
@ -39,14 +39,13 @@ $fonts: "Helvetica Neue, Helvetica, Arial";
|
||||
string.split($fonts, ', '); // ["Helvetica Neue", "Helvetica", "Arial"]
|
||||
```
|
||||
|
||||
A third argument can set a limit to the the number of splits performed on the
|
||||
A third argument can set a limit to the the number of splits performed on the
|
||||
string:
|
||||
|
||||
```scss
|
||||
string.split($fonts, ', ', 1); // ["Helvetica Neue", "Helvetica, Arial"]
|
||||
```
|
||||
|
||||
|
||||
An empty `$separator` returns all Unicode code points in the original string:
|
||||
|
||||
```scss
|
||||
@ -54,7 +53,6 @@ $font: "Helvetica"
|
||||
string.split($font, ''); // ["H", "e", "l", "v", "e", "t", "i", "c", "a"]
|
||||
```
|
||||
|
||||
|
||||
## Semantics
|
||||
|
||||
### `split()`
|
||||
@ -71,12 +69,12 @@ split($string, $separator, $limit: null)
|
||||
|
||||
* If `$limit` is less than 1, throw an error.
|
||||
|
||||
* If `$string` is an empty string, return a list with `$string` as the only
|
||||
* If `$string` is an empty string, return a list with `$string` as the only
|
||||
item.
|
||||
|
||||
* Let `split-list` be an empty list.
|
||||
|
||||
* If `$limit` is `null`, set `$limit` to the value of calling
|
||||
* If `$limit` is `null`, set `$limit` to the value of calling
|
||||
`string.length($string)`.
|
||||
|
||||
* Let `split-counter` equal 0.
|
||||
@ -87,7 +85,7 @@ split($string, $separator, $limit: null)
|
||||
|
||||
* Append `$string` to `split-list`.
|
||||
|
||||
* Set `$string` to an empty string.
|
||||
* Set `$string` to an empty string.
|
||||
|
||||
* Otherwise:
|
||||
|
||||
@ -103,10 +101,10 @@ split($string, $separator, $limit: null)
|
||||
|
||||
* Otherwise:
|
||||
|
||||
* Let `index` be the result of calling
|
||||
* Let `index` be the result of calling
|
||||
`string.index($string, $separator)`.
|
||||
|
||||
* If `index` is `null`, append `$string` to `split-list` and set `$string`
|
||||
* If `index` is `null`, append `$string` to `split-list` and set `$string`
|
||||
to an empty string.
|
||||
|
||||
* Otherwise:
|
||||
@ -115,10 +113,10 @@ split($string, $separator, $limit: null)
|
||||
`string.slice($string, 1, index - 1)`.
|
||||
|
||||
* Append `current-substring` to `split-list`.
|
||||
|
||||
* Set `$string` to
|
||||
|
||||
* Set `$string` to
|
||||
`string.slice($string, index + string.length($separator))`.
|
||||
|
||||
* Increase `split-counter` by 1.
|
||||
|
||||
* Return `split-list` as a bracketed, comma-separated list.
|
||||
|
||||
* Return `split-list` as a bracketed, comma-separated list.
|
||||
|
4
buf.gen.yaml
Normal file
4
buf.gen.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
version: v1
|
||||
plugins:
|
||||
- plugin: buf.build/bufbuild/es
|
||||
out: gen
|
2
buf.work.yaml
Normal file
2
buf.work.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
version: v1
|
||||
directories: [spec]
|
62
js-api-doc/importer.d.ts
vendored
62
js-api-doc/importer.d.ts
vendored
@ -1,6 +1,38 @@
|
||||
import {Syntax} from './options';
|
||||
import {PromiseOr} from './util/promise_or';
|
||||
|
||||
/**
|
||||
* Contextual information passed to {@link Importer.canonicalize} and {@link
|
||||
* FileImporter.findFileUrl}. Not all importers will need this information to
|
||||
* resolve loads, but some may find it useful.
|
||||
*/
|
||||
export interface CanonicalizeContext {
|
||||
/**
|
||||
* Whether this is being invoked because of a Sass
|
||||
* `@import` rule, as opposed to a `@use` or `@forward` rule.
|
||||
*
|
||||
* This should *only* be used for determining whether or not to load
|
||||
* [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
|
||||
*/
|
||||
fromImport: boolean;
|
||||
|
||||
/**
|
||||
* The canonical URL of the file that contains the load, if that information
|
||||
* is available.
|
||||
*
|
||||
* For an {@link Importer}, this is only passed when the `url` parameter is a
|
||||
* relative URL _or_ when its [URL scheme] is included in {@link
|
||||
* Importer.nonCanonicalScheme}. This ensures that canonical URLs are always
|
||||
* resolved the same way regardless of context.
|
||||
*
|
||||
* [URL scheme]: https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL#scheme
|
||||
*
|
||||
* For a {@link FileImporter}, this is always available as long as Sass knows
|
||||
* the canonical URL of the containing file.
|
||||
*/
|
||||
containingUrl: URL | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A special type of importer that redirects all loads to existing files on
|
||||
* disk. Although this is less powerful than a full {@link Importer}, it
|
||||
@ -56,12 +88,6 @@ export interface FileImporter<
|
||||
* @param url - The loaded URL. Since this might be relative, it's represented
|
||||
* as a string rather than a {@link URL} object.
|
||||
*
|
||||
* @param options.fromImport - Whether this is being invoked because of a Sass
|
||||
* `@import` rule, as opposed to a `@use` or `@forward` rule.
|
||||
*
|
||||
* This should *only* be used for determining whether or not to load
|
||||
* [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
|
||||
*
|
||||
* @returns An absolute `file:` URL if this importer recognizes the `url`.
|
||||
* This may be only partially resolved: the compiler will automatically look
|
||||
* for [partials](https://sass-lang.com/documentation/at-rules/use#partials),
|
||||
@ -85,7 +111,7 @@ export interface FileImporter<
|
||||
*/
|
||||
findFileUrl(
|
||||
url: string,
|
||||
options: {fromImport: boolean}
|
||||
context: CanonicalizeContext
|
||||
): PromiseOr<URL | null, sync>;
|
||||
|
||||
/** @hidden */
|
||||
@ -220,12 +246,6 @@ export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
|
||||
* @param url - The loaded URL. Since this might be relative, it's represented
|
||||
* as a string rather than a {@link URL} object.
|
||||
*
|
||||
* @param options.fromImport - Whether this is being invoked because of a Sass
|
||||
* `@import` rule, as opposed to a `@use` or `@forward` rule.
|
||||
*
|
||||
* This should *only* be used for determining whether or not to load
|
||||
* [import-only files](https://sass-lang.com/documentation/at-rules/import#import-only-files).
|
||||
*
|
||||
* @returns An absolute URL if this importer recognizes the `url`, or `null`
|
||||
* if it doesn't. If this returns `null`, other importers or {@link
|
||||
* Options.loadPaths | load paths} may handle the load.
|
||||
@ -242,7 +262,7 @@ export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
|
||||
*/
|
||||
canonicalize(
|
||||
url: string,
|
||||
options: {fromImport: boolean}
|
||||
context: CanonicalizeContext
|
||||
): PromiseOr<URL | null, sync>;
|
||||
|
||||
/**
|
||||
@ -272,6 +292,20 @@ export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
|
||||
|
||||
/** @hidden */
|
||||
findFileUrl?: never;
|
||||
|
||||
/**
|
||||
* A URL scheme or set of schemes (without the `:`) that this importer
|
||||
* promises never to use for URLs returned by {@link canonicalize}. If it does
|
||||
* return a URL with one of these schemes, that's an error.
|
||||
*
|
||||
* If this is set, any call to canonicalize for a URL with a non-canonical
|
||||
* scheme will be passed {@link CanonicalizeContext.containingUrl} if it's
|
||||
* known.
|
||||
*
|
||||
* These schemes may only contain lowercase ASCII letters, ASCII numerals,
|
||||
* `+`, `-`, and `.`. They may not be empty.
|
||||
*/
|
||||
nonCanonicalScheme?: string | string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
8
js-api-doc/index.d.ts
vendored
8
js-api-doc/index.d.ts
vendored
@ -10,7 +10,12 @@ export {
|
||||
compileStringAsync,
|
||||
} from './compile';
|
||||
export {Exception} from './exception';
|
||||
export {FileImporter, Importer, ImporterResult} from './importer';
|
||||
export {
|
||||
CanonicalizeContext,
|
||||
FileImporter,
|
||||
Importer,
|
||||
ImporterResult,
|
||||
} from './importer';
|
||||
export {Logger, SourceSpan, SourceLocation} from './logger';
|
||||
export {
|
||||
CustomFunction,
|
||||
@ -35,6 +40,7 @@ export {
|
||||
SassFunction,
|
||||
SassList,
|
||||
SassMap,
|
||||
SassMixin,
|
||||
SassNumber,
|
||||
SassString,
|
||||
Value,
|
||||
|
21
js-api-doc/value/color.d.ts
vendored
21
js-api-doc/value/color.d.ts
vendored
@ -12,6 +12,13 @@ export class SassColor extends Value {
|
||||
/**
|
||||
* Creates an RGB color.
|
||||
*
|
||||
* **Only** `undefined` should be passed to indicate a missing `alpha`. If
|
||||
* `null` is passed instead, it will be treated as a [missing component] in
|
||||
* future versions of Dart Sass. See [breaking changes] for details.
|
||||
*
|
||||
* [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components
|
||||
* [breaking changes]: /documentation/breaking-changes/null-alpha
|
||||
*
|
||||
* @throws `Error` if `red`, `green`, and `blue` aren't between `0` and
|
||||
* `255`, or if `alpha` isn't between `0` and `1`.
|
||||
*/
|
||||
@ -25,6 +32,13 @@ export class SassColor extends Value {
|
||||
/**
|
||||
* Creates an HSL color.
|
||||
*
|
||||
* **Only** `undefined` should be passed to indicate a missing `alpha`. If
|
||||
* `null` is passed instead, it will be treated as a [missing component] in
|
||||
* future versions of Dart Sass. See [breaking changes] for details.
|
||||
*
|
||||
* [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components
|
||||
* [breaking changes]: /documentation/breaking-changes/null-alpha
|
||||
*
|
||||
* @throws `Error` if `saturation` or `lightness` aren't between `0` and
|
||||
* `100`, or if `alpha` isn't between `0` and `1`.
|
||||
*/
|
||||
@ -38,6 +52,13 @@ export class SassColor extends Value {
|
||||
/**
|
||||
* Creates an HWB color.
|
||||
*
|
||||
* **Only** `undefined` should be passed to indicate a missing `alpha`. If
|
||||
* `null` is passed instead, it will be treated as a [missing component] in
|
||||
* future versions of Dart Sass. See [breaking changes] for details.
|
||||
*
|
||||
* [missing component]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#missing_color_components
|
||||
* [breaking changes]: /documentation/breaking-changes/null-alpha
|
||||
*
|
||||
* @throws `Error` if `whiteness` or `blackness` aren't between `0` and `100`,
|
||||
* or if `alpha` isn't between `0` and `1`.
|
||||
*/
|
||||
|
10
js-api-doc/value/index.d.ts
vendored
10
js-api-doc/value/index.d.ts
vendored
@ -6,6 +6,7 @@ import {SassColor} from './color';
|
||||
import {SassFunction} from './function';
|
||||
import {ListSeparator} from './list';
|
||||
import {SassMap} from './map';
|
||||
import {SassMixin} from './mixin';
|
||||
import {SassNumber} from './number';
|
||||
import {SassString} from './string';
|
||||
|
||||
@ -22,6 +23,7 @@ export {SassColor} from './color';
|
||||
export {SassFunction} from './function';
|
||||
export {SassList, ListSeparator} from './list';
|
||||
export {SassMap} from './map';
|
||||
export {SassMixin} from './mixin';
|
||||
export {SassNumber} from './number';
|
||||
export {SassString} from './string';
|
||||
|
||||
@ -156,6 +158,14 @@ export abstract class Value implements ValueObject {
|
||||
*/
|
||||
assertMap(name?: string): SassMap;
|
||||
|
||||
/**
|
||||
* Throws if `this` isn't a {@link SassMixin}.
|
||||
*
|
||||
* @param name - The name of the function argument `this` came from (without
|
||||
* the `$`) if it came from an argument. Used for error reporting.
|
||||
*/
|
||||
assertMixin(name?: string): SassMixin;
|
||||
|
||||
/**
|
||||
* Throws if `this` isn't a {@link SassNumber}.
|
||||
*
|
||||
|
14
js-api-doc/value/mixin.d.ts
vendored
Normal file
14
js-api-doc/value/mixin.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import {Value} from './index';
|
||||
|
||||
/**
|
||||
* Sass's [mixin type](https://sass-lang.com/documentation/values/mixins).
|
||||
*
|
||||
* @category Custom Function
|
||||
*/
|
||||
export class SassMixin extends Value {
|
||||
/**
|
||||
* It is not possible to construct a Sass mixin outside of Sass. Attempting to
|
||||
* construct one will throw an exception.
|
||||
*/
|
||||
constructor();
|
||||
}
|
1581
package-lock.json
generated
1581
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -16,31 +16,31 @@
|
||||
"typedoc": "npm run tangle && npx typedoc --treatWarningsAsErrors js-api-doc/index.d.ts",
|
||||
"tangle": "npx ts-node tool/tangle.ts",
|
||||
"untangle": "npx ts-node tool/untangle.ts",
|
||||
"fix": "npm run update-toc && npm run tangle && gts fix && npm run untangle",
|
||||
"markdownlint": "npx markdownlint-cli2 '**/*.md' '*.md' --ignore 'node_modules/**'",
|
||||
"fix": "npm run update-toc && npm run markdownlint -- --fix && npm run tangle && gts fix && npm run untangle",
|
||||
"test": "npm run tangle && gts lint && tsc --noEmit && npm run toc-check && npm run link-check && npm run js-api-doc-check && npm run typedoc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/diff": "^5.0.1",
|
||||
"@types/glob": "^7.1.4",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/node": "^14.11.2",
|
||||
"@types/prettier": "^2.4.1",
|
||||
"colors": "^1.3.3",
|
||||
"diff": "^5.0.0",
|
||||
"glob": "^10.0.0",
|
||||
"gts": "^3.1.0",
|
||||
"immutable": "^4.0.0",
|
||||
"indent-string": "^4.0.0",
|
||||
"markdown-link-check": "3.11.1",
|
||||
"markdown-toc": "^1.2.0",
|
||||
"markdownlint-cli2": "^0.8.1",
|
||||
"marked": "^4.3.0",
|
||||
"prettier": "^2.4.1",
|
||||
"source-map-js": "^0.6.2",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"ts-node": "^10.2.1",
|
||||
"typedoc": "^0.24.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.11.2",
|
||||
"gts": "^3.1.0",
|
||||
"typedoc": "^0.24.7",
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
}
|
||||
|
21
proposal/color-4-new-spaces-js.changes.md
Normal file
21
proposal/color-4-new-spaces-js.changes.md
Normal file
@ -0,0 +1,21 @@
|
||||
## Draft 1.1
|
||||
|
||||
* Clarify values in `channels` and `channelsOrNull`.
|
||||
|
||||
* Throw an error if construction space can not be determined.
|
||||
|
||||
* Remove `alpha` from list of deprecated getters.
|
||||
|
||||
* Rename types: `ColorSpaceLAB` to `ColorSpaceLab`, `ChannelNameLAB` to
|
||||
`ChannelNameLab`.
|
||||
|
||||
* Use `Exclude<>` instead of `Omit<>` for union types.
|
||||
|
||||
* Make procedure for determining space backwards compatible when using `change`
|
||||
for legacy colors.
|
||||
|
||||
* Fix channel names for `change` with `oklch` and `lch`.
|
||||
|
||||
## Draft 1
|
||||
|
||||
* Initial draft
|
961
proposal/color-4-new-spaces-js.d.ts.md
Normal file
961
proposal/color-4-new-spaces-js.d.ts.md
Normal file
@ -0,0 +1,961 @@
|
||||
# CSS Color Level 4, New Color Spaces JavaScript API: Draft 1.1
|
||||
|
||||
*([Issue](https://github.com/sass/sass/issues/2831),
|
||||
[Changelog](color-4-new-spaces-js.changes.md))*
|
||||
|
||||
This proposal updates Sass's JavaScript (JS) API to match the [color spaces
|
||||
proposal].
|
||||
|
||||
[color spaces proposal]: ./color-4-new-spaces.md
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [API](#api)
|
||||
* [Types](#types)
|
||||
* [Color Space Definitions](#color-space-definitions)
|
||||
* [New Color Functions](#new-color-functions)
|
||||
* [`space`](#space)
|
||||
* [`toSpace`](#tospace)
|
||||
* [`isLegacy`](#islegacy)
|
||||
* [`isInGamut`](#isingamut)
|
||||
* [`toGamut`](#togamut)
|
||||
* [`channelsOrNull`](#channelsornull)
|
||||
* [`channels`](#channels)
|
||||
* [`channel`](#channel)
|
||||
* [`alpha`](#alpha)
|
||||
* [`isChannelMissing`](#ischannelmissing)
|
||||
* [`isAlphaMissing`](#isalphamissing)
|
||||
* [`isChannelPowerless`](#ischannelpowerless)
|
||||
* [`interpolate`](#interpolate)
|
||||
* [Updated Color Functions](#updated-color-functions)
|
||||
* [`change`](#change)
|
||||
* [New Constructors](#new-constructors)
|
||||
* [Lab Channel Constructor](#lab-channel-constructor)
|
||||
* [LCH Channel Constructor](#lch-channel-constructor)
|
||||
* [Predefined RGB Channel Constructor](#predefined-rgb-channel-constructor)
|
||||
* [XYZ Channel Constructor](#xyz-channel-constructor)
|
||||
* [Modified Legacy Color Constructors](#modified-legacy-color-constructors)
|
||||
* [HSL Constructor](#hsl-constructor)
|
||||
* [HWB Constructor](#hwb-constructor)
|
||||
* [RGB Constructor](#rgb-constructor)
|
||||
* [Deprecations](#deprecations)
|
||||
* [Procedures](#procedures)
|
||||
* [Parsing a Channel Value](#parsing-a-channel-value)
|
||||
* [Changing a Component Value](#changing-a-component-value)
|
||||
* [Determining Construction Space](#determining-construction-space)
|
||||
* [Embedded Protocol](#embedded-protocol)
|
||||
* [SassColor](#sasscolor)
|
||||
* [Removed SassScript values](#removed-sassscript-values)
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import {Value} from '../spec/js-api/value';
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
### Color Space Definitions
|
||||
|
||||
```ts
|
||||
export type ColorSpaceHSL = 'hsl';
|
||||
|
||||
export type ChannelNameHSL = 'hue' | 'saturation' | 'lightness';
|
||||
|
||||
export type ColorSpaceHWB = 'hwb';
|
||||
|
||||
export type ChannelNameHWB = 'hue' | 'whiteness' | 'blackness';
|
||||
|
||||
export type ColorSpaceLab = 'lab' | 'oklab';
|
||||
|
||||
export type ChannelNameLab = 'lightness' | 'a' | 'b';
|
||||
|
||||
export type ColorSpaceLCH = 'lch' | 'oklch';
|
||||
|
||||
export type ChannelNameLCH = 'lightness' | 'chroma' | 'hue';
|
||||
|
||||
export type ColorSpaceRGB =
|
||||
| 'a98-rgb'
|
||||
| 'display-p3'
|
||||
| 'prophoto-rgb'
|
||||
| 'rgb'
|
||||
| 'srgb'
|
||||
| 'srgb-linear';
|
||||
|
||||
export type ChannelNameRGB = 'red' | 'green' | 'blue';
|
||||
|
||||
export type ColorSpaceXYZ = 'xyz' | 'xyz-d50' | 'xyz-d65';
|
||||
|
||||
export type ChannelNameXYZ = 'x' | 'y' | 'z';
|
||||
|
||||
export type ChannelName =
|
||||
| ChannelNameHSL
|
||||
| ChannelNameHWB
|
||||
| ChannelNameLab
|
||||
| ChannelNameLCH
|
||||
| ChannelNameRGB
|
||||
| ChannelNameXYZ;
|
||||
|
||||
export type KnownColorSpace =
|
||||
| ColorSpaceHSL
|
||||
| ColorSpaceHWB
|
||||
| ColorSpaceLab
|
||||
| ColorSpaceLCH
|
||||
| ColorSpaceRGB
|
||||
| ColorSpaceXYZ;
|
||||
|
||||
export type PolarColorSpace = ColorSpaceHSL | ColorSpaceHWB | ColorSpaceLCH;
|
||||
|
||||
export type RectangularColorSpace = Exclude<KnownColorSpace, PolarColorSpace>;
|
||||
|
||||
export type HueInterpolationMethod =
|
||||
| 'decreasing'
|
||||
| 'increasing'
|
||||
| 'longer'
|
||||
| 'shorter';
|
||||
```
|
||||
|
||||
### New Color Functions
|
||||
|
||||
```ts
|
||||
export class SassColor extends Value {
|
||||
```
|
||||
|
||||
#### `space`
|
||||
|
||||
Returns the name of [`internal`]'s space.
|
||||
|
||||
[`internal`]: ../spec/js-api/value/color.d.ts.md#internal
|
||||
|
||||
```ts
|
||||
get space(): KnownColorSpace;
|
||||
```
|
||||
|
||||
#### `toSpace`
|
||||
|
||||
* If `this.space` is equal to `space`, return `this`.
|
||||
|
||||
* Otherwise, return the result of [`color.to-space(internal, space)`].
|
||||
|
||||
```ts
|
||||
toSpace(space: KnownColorSpace): SassColor;
|
||||
```
|
||||
|
||||
[`color.to-space(internal, space)`]: ./color-4-new-spaces.md#colorto-space
|
||||
|
||||
#### `isLegacy`
|
||||
|
||||
Returns whether [`internal`] is in a [legacy color space] (`rgb`, `hsl`, or
|
||||
`hwb`).
|
||||
|
||||
```ts
|
||||
get isLegacy(): boolean;
|
||||
```
|
||||
|
||||
[legacy color space]: ./color-4-new-spaces.md#legacy-color
|
||||
|
||||
#### `isInGamut`
|
||||
|
||||
Returns the result of [`color.is-in-gamut(internal, space)`] as a JavaScript
|
||||
boolean.
|
||||
|
||||
```ts
|
||||
isInGamut(space?: KnownColorSpace): boolean;
|
||||
```
|
||||
|
||||
[`color.is-in-gamut(internal, space)`]: ./color-4-new-spaces.md#coloris-in-gamut
|
||||
|
||||
#### `toGamut`
|
||||
|
||||
Returns the result of [`color.to-gamut(internal, space)`].
|
||||
|
||||
```ts
|
||||
toGamut(space?: KnownColorSpace): SassColor;
|
||||
```
|
||||
|
||||
[`color.to-gamut(internal, space)`]: ./color-4-new-spaces.md#colorto-gamut-1
|
||||
|
||||
#### `channelsOrNull`
|
||||
|
||||
Returns an array of channel values (excluding alpha) for [`internal`], with
|
||||
[missing channels][missing components] converted to `null`.
|
||||
|
||||
* Let `space` be the result of [`this.space`].
|
||||
|
||||
* Let `components` be the list of channels in `space`.
|
||||
|
||||
* Let `channels` be an empty array.
|
||||
|
||||
* For each `component` in `components`:
|
||||
|
||||
* Let `value` be the channel value in [`internal`] with name of `component`.
|
||||
|
||||
* If `value` is `none`, let `value` be `null`.
|
||||
|
||||
* Append `value` to `channels`.
|
||||
|
||||
* Return `channels`.
|
||||
|
||||
```ts
|
||||
get channelsOrNull(): [number | null, number | null, number | null];
|
||||
```
|
||||
|
||||
[missing components]: ./color-4-new-spaces.md#missing-components
|
||||
[`this.space`]: #space
|
||||
|
||||
#### `channels`
|
||||
|
||||
This algorithm returns an array of channel values (excluding alpha) for
|
||||
[`internal`], with [missing channels][missing components] converted to `0`.
|
||||
|
||||
* Let `channelsOrNull` be the result of [`this.channelsOrNull`].
|
||||
|
||||
* Let `channels` be an empty array.
|
||||
|
||||
* For each `channel` in `channelsOrNull`:
|
||||
|
||||
* If `channel` equals `null`, let `value` be 0.
|
||||
|
||||
* Append `value` to `channels`.
|
||||
|
||||
* Return `channels`.
|
||||
|
||||
[`this.channelsOrNull`]: #channelsornull
|
||||
|
||||
```ts
|
||||
get channels(): [number, number, number];
|
||||
```
|
||||
|
||||
#### `channel`
|
||||
|
||||
* Let `initialSpace` be the value of [`this.space()`].
|
||||
|
||||
* Let `space` be `options.space` if it is defined, and the value of
|
||||
`initialSpace` otherwise.
|
||||
|
||||
* If `channel` is not "alpha" or a channel in `space`, throw an error.
|
||||
|
||||
* Let `color` be the result of [`this.toSpace(space)`].
|
||||
|
||||
* Let `value` be the channel value in `color` with name of `component`.
|
||||
|
||||
* If `value` is null, return 0.
|
||||
|
||||
* Otherwise, return `value`.
|
||||
|
||||
```ts
|
||||
channel(channel: ChannelName): number;
|
||||
channel(
|
||||
channel: ChannelNameHSL | 'alpha',
|
||||
options: {space: ColorSpaceHSL}
|
||||
): number;
|
||||
channel(
|
||||
channel: ChannelNameHWB | 'alpha',
|
||||
options: {space: ColorSpaceHWB}
|
||||
): number;
|
||||
channel(
|
||||
channel: ChannelNameLab | 'alpha',
|
||||
options: {space: ColorSpaceLab}
|
||||
): number;
|
||||
channel(
|
||||
channel: ChannelNameLCH | 'alpha',
|
||||
options: {space: ColorSpaceLCH}
|
||||
): number;
|
||||
channel(
|
||||
channel: ChannelNameRGB | 'alpha',
|
||||
options: {space: ColorSpaceRGB}
|
||||
): number;
|
||||
channel(
|
||||
channel: ChannelNameXYZ | 'alpha',
|
||||
options: {space: ColorSpaceXYZ}
|
||||
): number;
|
||||
```
|
||||
|
||||
#### `alpha`
|
||||
|
||||
Returns the result of calling [`this.channel('alpha')`].
|
||||
|
||||
[`this.channel('alpha')`]: #channel
|
||||
|
||||
```ts
|
||||
get alpha(): number;
|
||||
```
|
||||
|
||||
#### `isChannelMissing`
|
||||
|
||||
Returns the result of [`color.is-missing(internal,
|
||||
channel)`][color.is-missing()] as a JavaScript boolean.
|
||||
|
||||
```ts
|
||||
isChannelMissing(channel: ChannelName | 'alpha'): boolean;
|
||||
```
|
||||
|
||||
[color.is-missing()]: ./color-4-new-spaces.md#coloris-missing-1
|
||||
|
||||
#### `isAlphaMissing`
|
||||
|
||||
Returns the result of [`color.is-missing(internal,
|
||||
'alpha')`][color.is-missing()] as a JavaScript boolean.
|
||||
|
||||
```ts
|
||||
get isAlphaMissing(): boolean;
|
||||
```
|
||||
|
||||
#### `isChannelPowerless`
|
||||
|
||||
Returns the result of [`color.is-powerless(internal, channel, space)`] as a
|
||||
JavaScript boolean.
|
||||
|
||||
[`color.is-powerless(internal, channel, space)`]: ./color-4-new-spaces.md#coloris-powerless-1
|
||||
|
||||
```ts
|
||||
isChannelPowerless(channel: ChannelName): boolean;
|
||||
isChannelPowerless(
|
||||
channel: ChannelNameHSL,
|
||||
options?: {space: ColorSpaceHSL}
|
||||
): boolean;
|
||||
isChannelPowerless(
|
||||
channel: ChannelNameHWB,
|
||||
options?: {space: ColorSpaceHWB}
|
||||
): boolean;
|
||||
isChannelPowerless(
|
||||
channel: ChannelNameLab,
|
||||
options?: {space: ColorSpaceLab}
|
||||
): boolean;
|
||||
isChannelPowerless(
|
||||
channel: ChannelNameLCH,
|
||||
options?: {space: ColorSpaceLCH}
|
||||
): boolean;
|
||||
isChannelPowerless(
|
||||
channel: ChannelNameRGB,
|
||||
options?: {space: ColorSpaceRGB}
|
||||
): boolean;
|
||||
isChannelPowerless(
|
||||
channel: ChannelNameXYZ,
|
||||
options?: {space: ColorSpaceXYZ}
|
||||
): boolean;
|
||||
```
|
||||
|
||||
#### `interpolate`
|
||||
|
||||
* Let `space` be the value of [`this.space()`].
|
||||
|
||||
* If `options.method` is set, let `interpolationMethod` be a space separated
|
||||
list containing the value of `space`, a space, and the value of
|
||||
`options.method`.
|
||||
|
||||
* Otherwise, if `space` is a rectangular color space, let `interpolationMethod`
|
||||
be `space`.
|
||||
|
||||
* Otherwise, let `interpolationMethod` be a space separated list containing the
|
||||
value of `space`, a space, and the string "shorter".
|
||||
|
||||
* Return the result of [`color.mix(internal, options.color2, options.weight, interpolationMethod)`][`color.mix()`].
|
||||
|
||||
```ts
|
||||
interpolate(options: {color2: SassColor; weight?: number}): SassColor;
|
||||
|
||||
interpolate(options: {
|
||||
color2: SassColor;
|
||||
weight?: number;
|
||||
method?: HueInterpolationMethod;
|
||||
}): SassColor;
|
||||
```
|
||||
|
||||
[`color.mix()`]: ./color-4-new-spaces.md#colormix-1
|
||||
|
||||
### Updated Color Functions
|
||||
|
||||
#### `change`
|
||||
|
||||
Replace the definition of [color.change] with the following:
|
||||
|
||||
[color.change]: ../spec/js-api/value/color.d.ts.md#change
|
||||
|
||||
This algorithm takes a JavaScript object `options` and returns a new SassColor
|
||||
as the result of changing some of [`internal`]'s components.
|
||||
|
||||
> The `space` value defaults to the `space` of [`internal`], and the caller may
|
||||
> specify any combination of channels and alpha in that space to be changed.
|
||||
>
|
||||
> If `space` is not a [legacy color space], a channel value of `null` will
|
||||
> result in a [missing component][missing components] value for that channel.
|
||||
|
||||
* Let `initialSpace` be the value of [`this.space()`].
|
||||
|
||||
* Let `spaceSetExplicitly` be `true` if `options.space` is defined, and `false`
|
||||
otherwise.
|
||||
|
||||
* Let `space` be `options.space` if `spaceSetExplicitly` is true, and the value
|
||||
of `initialSpace` otherwise.
|
||||
|
||||
* If `initialSpace` is a [legacy color space] and `spaceSetExplicitly` is false:
|
||||
|
||||
* If `options.whiteness` or `options.blackness` is set, let `space` be `hwb`.
|
||||
|
||||
* Otherwise, if `options.hue`, `options.saturation`, or `options.lightness` is
|
||||
set, let `space` be `hsl`.
|
||||
|
||||
* Otherwise, if `options.red`, `options.green`, or `options.blue` is set, let
|
||||
`space` be `rgb`.
|
||||
|
||||
* If `initialSpace` is not equal to `space`, emit a deprecation warning named
|
||||
`color-4-api`.
|
||||
|
||||
* Let `changes` be the object `options` without `space` and its value.
|
||||
|
||||
* Let `keys` be a list of the keys in `changes`.
|
||||
|
||||
* Let `components` be `"alpha"` and the names of the channels in `space`.
|
||||
|
||||
* If any key in `keys` is not the name of a channel in `components`, throw an
|
||||
error.
|
||||
|
||||
* Let `color` be the result of [`this.toSpace(space)`].
|
||||
|
||||
* Let `changedValue` be a function that takes a string argument for `channel`
|
||||
and calls the procedure [`Changing a Component Value`] with `changes` and
|
||||
`this` as `initial`.
|
||||
|
||||
* If `space` equals `hsl` and `spaceSetExplicitly` is `false`:
|
||||
|
||||
* If any of `options.hue`, `options.saturation`, `options.lightness` or
|
||||
`options.alpha` equals null, emit a deprecation warning named `null-alpha`.
|
||||
|
||||
* Let `changedColor` be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
hue: options.hue ?? color.channel('hue'),
|
||||
saturation: options.saturation ?? color.channel('saturation'),
|
||||
lightness: options.lightness ?? color.channel('lightness'),
|
||||
alpha: options.alpha ?? color.channel('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `hsl` and `spaceSetExplicitly` is `true`, let `changedColor`
|
||||
be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
hue: changedValue('hue'),
|
||||
saturation: changedValue('saturation'),
|
||||
lightness: changedValue('lightness'),
|
||||
alpha: changedValue('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `hwb` and `spaceSetExplicitly` is `false`:
|
||||
|
||||
* If any of `options.hue`, `options.whiteness`, `options.blackness` or
|
||||
`options.alpha` equals null, emit a deprecation warning named `null-alpha`.
|
||||
|
||||
* Let `changedColor` be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
hue: options.hue ?? color.channel('hue'),
|
||||
whiteness: options.whiteness ?? color.channel('whiteness'),
|
||||
blackness: options.blackness ?? color.channel('blackness'),
|
||||
alpha: options.alpha ?? color.channel('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `hwb` and `spaceSetExplicitly` is `true`, let `changedColor`
|
||||
be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
hue: changedValue('hue'),
|
||||
whiteness: changedValue('whiteness'),
|
||||
blackness: changedValue('blackness'),
|
||||
alpha: changedValue('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `rgb` and `spaceSetExplicitly` is `false`:
|
||||
|
||||
* If any of `options.red`, `options.green`, `options.blue` or `options.alpha`
|
||||
equals null, emit a deprecation warning named `null-alpha`.
|
||||
|
||||
* Let `changedColor` be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
red: options.red ?? color.channel('red'),
|
||||
green: options.green ?? color.channel('green'),
|
||||
blue: options.blue ?? color.channel('blue'),
|
||||
alpha: options.alpha ?? color.channel('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `rgb` and `spaceSetExplicitly` is `true`, let `changedColor`
|
||||
be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
red: changedValue('red'),
|
||||
green: changedValue('green'),
|
||||
blue: changedValue('blue'),
|
||||
alpha: changedValue('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `lab` or `oklab`, let `changedColor` be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
lightness: changedValue('lightness'),
|
||||
a: changedValue('a'),
|
||||
b: changedValue('b'),
|
||||
alpha: changedValue('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `lch` or `oklch`, let `changedColor` be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
lightness: changedValue('lightness'),
|
||||
chroma: changedValue('chroma'),
|
||||
hue: changedValue('hue'),
|
||||
alpha: changedValue('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `a98-rgb`, `display-p3`, `prophoto-rgb`, `srgb`, or
|
||||
`srgb-linear`, let `changedColor` be the result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
red: changedValue('red'),
|
||||
green: changedValue('green'),
|
||||
blue: changedValue('blue'),
|
||||
alpha: changedValue('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* If `space` equals `xyz`, `xyz-d50`, or `xyz-d65`, let `changedColor` be the
|
||||
result of:
|
||||
|
||||
```js
|
||||
new SassColor({
|
||||
y: changedValue('y'),
|
||||
x: changedValue('x'),
|
||||
z: changedValue('z'),
|
||||
alpha: changedValue('alpha'),
|
||||
space: space
|
||||
})
|
||||
```
|
||||
|
||||
* Return the result of [`changedColor.toSpace(initialSpace)`].
|
||||
|
||||
[`this.space()`]: #space
|
||||
[`this.toSpace(space)`]: #tospace
|
||||
[`changedColor.toSpace(initialSpace)`]: #tospace
|
||||
[`Changing a Component Value`]: #changing-a-component-value
|
||||
|
||||
```ts
|
||||
change(
|
||||
options: {
|
||||
[key in ChannelName]?: number | null;
|
||||
} & {alpha?: number}
|
||||
): SassColor;
|
||||
|
||||
change(
|
||||
options: {
|
||||
[key in ChannelNameHSL]?: number | null;
|
||||
} & {
|
||||
alpha?: number;
|
||||
space: ColorSpaceHSL;
|
||||
}
|
||||
): SassColor;
|
||||
|
||||
change(
|
||||
options: {
|
||||
[key in ChannelNameHWB]?: number | null;
|
||||
} & {
|
||||
alpha?: number;
|
||||
space: ColorSpaceHWB;
|
||||
}
|
||||
): SassColor;
|
||||
|
||||
change(
|
||||
options: {
|
||||
[key in ChannelNameLab]?: number | null;
|
||||
} & {
|
||||
alpha?: number | null;
|
||||
space: ColorSpaceLab;
|
||||
}
|
||||
): SassColor;
|
||||
|
||||
change(
|
||||
options: {
|
||||
[key in ChannelNameLCH]?: number | null;
|
||||
} & {
|
||||
alpha?: number | null;
|
||||
space: ColorSpaceLCH;
|
||||
}
|
||||
): SassColor;
|
||||
|
||||
change(
|
||||
options: {
|
||||
[key in ChannelNameRGB]?: number | null;
|
||||
} & {
|
||||
alpha?: number | null;
|
||||
space: ColorSpaceRGB;
|
||||
}
|
||||
): SassColor;
|
||||
|
||||
change(
|
||||
options: {
|
||||
[key in ChannelNameXYZ]?: number | null;
|
||||
} & {
|
||||
alpha?: number | null;
|
||||
space: ColorSpaceXYZ;
|
||||
}
|
||||
): SassColor;
|
||||
```
|
||||
|
||||
### New Constructors
|
||||
|
||||
* Let `constructionSpace` be the result of [Determining Construction Space] with
|
||||
the `options` object passed to the constructor.
|
||||
|
||||
* Use the constructor that matches `constructionSpace`.
|
||||
|
||||
[Determining Construction Space]: #determining-construction-space
|
||||
|
||||
#### Lab Channel Constructor
|
||||
|
||||
Create a new SassColor in a color space with Lab channels -- `lab` and `oklab`.
|
||||
|
||||
* Let `lightness` be the result of [parsing a channel value] with value
|
||||
`options.lightness`.
|
||||
|
||||
* Let `a` be the result of [parsing a channel value] with value `options.a`.
|
||||
|
||||
* Let `b` be the result of [parsing a channel value] with value `options.b`.
|
||||
|
||||
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
|
||||
the result of [parsing a channel value] with value `options.alpha`.
|
||||
|
||||
* If `options.space` equals `lab`, set [`internal`] to the result of
|
||||
[`lab(lightness a b / alpha)`].
|
||||
|
||||
* Otherwise, if `options.space` equals `oklab`, set [`internal`] to the result
|
||||
of [`oklab(lightness a b / alpha)`].
|
||||
|
||||
[`lab(lightness a b / alpha)`]: ./color-4-new-spaces.md#lab
|
||||
[`oklab(lightness a b / alpha)`]: ./color-4-new-spaces.md#oklab
|
||||
[parsing a channel value]: #parsing-a-channel-value
|
||||
|
||||
```ts
|
||||
constructor(options: {
|
||||
lightness: number | null;
|
||||
a: number | null;
|
||||
b: number | null;
|
||||
alpha?: number | null;
|
||||
space: ColorSpaceLab;
|
||||
});
|
||||
```
|
||||
|
||||
#### LCH Channel Constructor
|
||||
|
||||
Create a new SassColor in a color space with LCH channels -- `lch` and `oklch`.
|
||||
|
||||
* Let `lightness` be the result of [parsing a channel value] with value
|
||||
`options.lightness`.
|
||||
|
||||
* Let `c` be the result of [parsing a channel value] with value `options.c`.
|
||||
|
||||
* Let `h` be the result of [parsing a channel value] with value `options.h`.
|
||||
|
||||
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
|
||||
the result of [parsing a channel value] with value `options.alpha`.
|
||||
|
||||
* If `options.space` equals `lch`, set [`internal`] to the result of
|
||||
[`lch(lightness a b / alpha)`].
|
||||
|
||||
* Otherwise, if `options.space` equals `oklch`, set [`internal`] to the result
|
||||
of [`oklch(lightness a b / alpha)`].
|
||||
|
||||
[`lch(lightness a b / alpha)`]: ./color-4-new-spaces.md#lch
|
||||
[`oklch(lightness a b / alpha)`]: ./color-4-new-spaces.md#oklch
|
||||
|
||||
```ts
|
||||
constructor(options: {
|
||||
lightness: number | null;
|
||||
chroma: number | null;
|
||||
hue: number | null;
|
||||
alpha?: number | null;
|
||||
space: ColorSpaceLCH;
|
||||
});
|
||||
```
|
||||
|
||||
#### Predefined RGB Channel Constructor
|
||||
|
||||
Create a new SassColor in a color space with RGB channels -- `srgb`,
|
||||
`srgb-linear`, `display-p3`, `a98-rgb`, and `prophoto-rgb`. `rgb` is supported
|
||||
through the modified [RGB Constructor].
|
||||
|
||||
* Let `red` be the result of [parsing a channel value] with value `options.red`.
|
||||
|
||||
* Let `green` be the result of [parsing a channel value] with value
|
||||
`options.green`.
|
||||
|
||||
* Let `blue` be the result of [parsing a channel value] with value
|
||||
`options.blue`.
|
||||
|
||||
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
|
||||
the result of [parsing a channel value] with value `options.alpha`.
|
||||
|
||||
* Let `space` be the unquoted string value of `options.space`.
|
||||
|
||||
* Set [`internal`] to the result of [`color(space red green blue / alpha)`].
|
||||
|
||||
[`color(space red green blue / alpha)`]: ./color-4-new-spaces.md#color-1
|
||||
[RGB Constructor]: #rgb-constructor
|
||||
|
||||
```ts
|
||||
constructor(options: {
|
||||
red: number | null;
|
||||
green: number | null;
|
||||
blue: number | null;
|
||||
alpha?: number | null;
|
||||
space: Exclude<ColorSpaceRGB, 'rgb'>;
|
||||
});
|
||||
```
|
||||
|
||||
#### XYZ Channel Constructor
|
||||
|
||||
Create a new SassColor in a color space with XYZ channels -- `xyz`, `xyz-d50`,
|
||||
and `xyz-d65`.
|
||||
|
||||
* Let `x` be the result of [parsing a channel value] with value `options.x`.
|
||||
|
||||
* Let `y` be the result of [parsing a channel value] with value `options.y`.
|
||||
|
||||
* Let `z` be the result of [parsing a channel value] with value `options.z`.
|
||||
|
||||
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
|
||||
the result of [parsing a channel value] with value `options.alpha`.
|
||||
|
||||
* Let `space` be the unquoted string value of `options.space`.
|
||||
|
||||
* Set [`internal`] to the result of [`color(space x y z / alpha)`].
|
||||
|
||||
[`color(space x y z / alpha)`]: ./color-4-new-spaces.md#color-1
|
||||
|
||||
```ts
|
||||
constructor(options: {
|
||||
x: number | null;
|
||||
y: number | null;
|
||||
z: number | null;
|
||||
alpha?: number | null;
|
||||
space: ColorSpaceXYZ;
|
||||
});
|
||||
```
|
||||
|
||||
### Modified Legacy Color Constructors
|
||||
|
||||
These will replace the [existing constructors] for legacy colors.
|
||||
|
||||
[existing constructors]: ../spec/js-api/value/color.d.ts.md#constructor
|
||||
|
||||
#### HSL Constructor
|
||||
|
||||
Create a new SassColor in the `hsl` color space.
|
||||
|
||||
* If `options.alpha` is `null` and `options.space` is not set, emit a
|
||||
deprecation warning named `null-alpha`.
|
||||
|
||||
* Let `hue` be the result of [parsing a channel value] with value `options.hue`.
|
||||
|
||||
* Let `saturation` be the result of [parsing a channel value] with value
|
||||
`options.saturation`.
|
||||
|
||||
* Let `lightness` be the result of [parsing a channel value] with value
|
||||
`options.lightness`.
|
||||
|
||||
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
|
||||
the result of [parsing a channel value] with value `options.alpha`.
|
||||
|
||||
* Set [`internal`] to the result of [`hsl(hue saturation lightness / alpha)`].
|
||||
|
||||
[`hsl(hue saturation lightness / alpha)`]: ../spec/functions.md#hsl-and-hsla
|
||||
|
||||
```ts
|
||||
constructor(options: {
|
||||
hue: number | null;
|
||||
saturation: number | null;
|
||||
lightness: number | null;
|
||||
alpha?: number | null;
|
||||
space?: ColorSpaceHSL;
|
||||
});
|
||||
```
|
||||
|
||||
#### HWB Constructor
|
||||
|
||||
Create a new SassColor in the `hwb` color space.
|
||||
|
||||
* If `options.alpha` is `null` and `options.space` is not set, emit a
|
||||
deprecation warning named `null-alpha`.
|
||||
|
||||
* Let `hue` be the result of [parsing a channel value] with value `options.hue`.
|
||||
|
||||
* Let `whiteness` be the result of [parsing a channel value] with value
|
||||
`options.whiteness`.
|
||||
|
||||
* Let `blackness` be the result of [parsing a channel value] with value
|
||||
`options.blackness`.
|
||||
|
||||
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
|
||||
the result of [parsing a channel value] with value `options.alpha`.
|
||||
|
||||
* Set [`internal`] to the result of [`hwb(hue whiteness blackness / alpha)`].
|
||||
|
||||
[`hwb(hue whiteness blackness / alpha)`]: ./color-4-new-spaces.md#hwb-1
|
||||
|
||||
```ts
|
||||
constructor(options: {
|
||||
hue: number | null;
|
||||
whiteness: number | null;
|
||||
blackness: number | null;
|
||||
alpha?: number | null;
|
||||
space?: ColorSpaceHWB;
|
||||
});
|
||||
```
|
||||
|
||||
#### RGB Constructor
|
||||
|
||||
Create a new SassColor in the `rgb` color space.
|
||||
|
||||
* If `options.alpha` is `null` and `options.space` is not set, emit a
|
||||
deprecation warning named `null-alpha`.
|
||||
|
||||
* Let `red` be the result of [parsing a channel value] with value `options.red`.
|
||||
|
||||
* Let `green` be the result of [parsing a channel value] with value
|
||||
`options.green`.
|
||||
|
||||
* Let `blue` be the result of [parsing a channel value] with value
|
||||
`options.blue`.
|
||||
|
||||
* If `options.alpha` is not set, let `alpha` be `1`. Otherwise, let `alpha` be
|
||||
the result of [parsing a channel value] with value `options.alpha`.
|
||||
|
||||
* Return the result of [`rgb(red green blue / alpha)`].
|
||||
|
||||
[`rgb(red green blue / alpha)`]: ./color-4-new-spaces.md#rgb-and-rgba
|
||||
|
||||
```ts
|
||||
constructor(options: {
|
||||
red: number | null;
|
||||
green: number | null;
|
||||
blue: number | null;
|
||||
alpha?: number | null;
|
||||
space?: 'rgb';
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
}
|
||||
```
|
||||
|
||||
### Deprecations
|
||||
|
||||
A number of SassColor getters only make sense for [legacy color space], and so
|
||||
are being deprecated in favor of the new [`channel`] function. This deprecation
|
||||
is called `color-4-api`.
|
||||
|
||||
[`channel`]: #channel
|
||||
|
||||
* `red`
|
||||
* `green`
|
||||
* `blue`
|
||||
* `hue`
|
||||
* `saturation`
|
||||
* `lightness`
|
||||
* `whiteness`
|
||||
* `blackness`
|
||||
|
||||
## Procedures
|
||||
|
||||
### Parsing a Channel Value
|
||||
|
||||
This procedure takes a channel value `value`, and returns the special value
|
||||
`none` if the value is `null`.
|
||||
|
||||
* If `value` is a number, return a Sass number with a value of `value`.
|
||||
|
||||
* If `value` is the Javascript value `null`, return the unquoted Sass string
|
||||
`none`.
|
||||
|
||||
### Changing a Component Value
|
||||
|
||||
This procedure takes a `channel` name, an object `changes` and a SassColor
|
||||
`initial` and returns the result of applying the change for `channel` to
|
||||
`initial`.
|
||||
|
||||
* Let `initialValue` be the channel value in `initial` with name of `channel`.
|
||||
|
||||
* If `channel` is not a key in `changes`, return `initialValue`.
|
||||
|
||||
* Otherwise, return the value for `channel` in `changes`.
|
||||
|
||||
### Determining Construction Space
|
||||
|
||||
This procedure takes an object `options` with unknown keys and returns a color
|
||||
space for construction.
|
||||
|
||||
* If `options.space` is set, return `options.space`.
|
||||
|
||||
* If `options.red` is set, return "rgb".
|
||||
|
||||
* If `options.saturation` is set, return "hsl".
|
||||
|
||||
* If `options.whiteness` is set, return "hwb".
|
||||
|
||||
* Otherwise, throw an error.
|
||||
|
||||
## Embedded Protocol
|
||||
|
||||
This introduces a breaking change in the Embedded Protocol, as it removes the
|
||||
legacy SassScript values.
|
||||
|
||||
### SassColor
|
||||
|
||||
```proto
|
||||
message SassColor {
|
||||
// The name of a known color space.
|
||||
string space = 1;
|
||||
|
||||
// The value of the first channel associated with `space`.
|
||||
double channel1 = 2;
|
||||
|
||||
// The value of the second channel associated with `space`.
|
||||
double channel2 = 3;
|
||||
|
||||
// The value of the third channel associated with `space`.
|
||||
double channel3 = 4;
|
||||
|
||||
// The color's alpha channel. Mandatory. Must be between 0 and 1,
|
||||
// inclusive.
|
||||
double alpha = 5;
|
||||
}
|
||||
```
|
||||
|
||||
### Removed SassScript values
|
||||
|
||||
The `RgbColor`, `HslColor` and `HwbColor` SassScript values will be removed from
|
||||
the Embedded Protocol.
|
@ -1,3 +1,27 @@
|
||||
## Draft 1.11
|
||||
|
||||
* Add support for the relative color syntax in the algorithm parsing color
|
||||
arguments, for CSS compatibility.
|
||||
|
||||
## Draft 1.10
|
||||
|
||||
* Properly scale `%` return values for `color.channel()`.
|
||||
|
||||
* Clean up some language related to percent-conversion.
|
||||
|
||||
## Draft 1.9
|
||||
|
||||
* Explicitly define associated units for color space channels.
|
||||
|
||||
## Draft 1.8
|
||||
|
||||
* Require a quoted string for `color.is-missing()` for consistency with other
|
||||
color functions and ease of use with channels whose names overlap with colors.
|
||||
|
||||
## Draft 1.7
|
||||
|
||||
* Resolve missing `alpha` channels *after* premultiplying colors.
|
||||
|
||||
## Draft 1.6
|
||||
|
||||
* Clarify in the known color space definitions that lightness channels are
|
||||
@ -84,7 +108,7 @@
|
||||
function syntax is already explicit about which parameter is where.
|
||||
|
||||
* `color.invert()` throws an error when `$weight` would require mixing in an
|
||||
invalid `color.mix()` _interpolation color space_.
|
||||
invalid `color.mix()` *interpolation color space*.
|
||||
|
||||
* Allow scaling channels with a non-0 minimum value, such as the `a` and `b`
|
||||
channels in `lab()`/`oklab()`.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# CSS Color Level 4, New Color Spaces: Draft 1.6
|
||||
# CSS Color Level 4, New Color Spaces: Draft 1.11
|
||||
|
||||
*([Issue](https://github.com/sass/sass/issues/2831))*
|
||||
|
||||
@ -117,7 +117,7 @@ wider RGB gamuts.
|
||||
|
||||
* A *color model* is a mathematical approach to representing colors and their
|
||||
relationships. Historically, RGB has been the dominant color model for both
|
||||
computer monitors and web browsers. Lately, CIELab and OKLab models have
|
||||
computer monitors and web browsers. Lately, CIELab and Oklab models have
|
||||
shown significant benefits by providing a more *perceptually uniform*
|
||||
distribution of colors, so that similar mathematical adjustments achieve
|
||||
visually similar results.
|
||||
@ -128,7 +128,7 @@ wider RGB gamuts.
|
||||
project the RGB color model into cubic coordinate systems, while `hsl()`
|
||||
projects the same color model into a cylindrical (polar-angle) space.
|
||||
Similarly, `oklab()` and `oklch()` provide different coordinate projections
|
||||
of the OKLab model.
|
||||
of the Oklab model.
|
||||
|
||||
* A *color gamut* is the full range of colors that can be described in a color
|
||||
space. Historically, all CSS syntaxes have been limited to the sRGB gamut.
|
||||
@ -235,8 +235,8 @@ such, `oklch` is often the best space for consistent transforms.
|
||||
#### `lab()` and `lch()`
|
||||
|
||||
The `lab()` and `lch()` functions provide access to an unbounded gamut of colors
|
||||
in a space that's less perpetually-uniform but more widely-adopted than OKLab
|
||||
and OKLCH.
|
||||
in a space that's less perpetually-uniform but more widely-adopted than Oklab
|
||||
and Oklch.
|
||||
|
||||
#### `hwb()`
|
||||
|
||||
@ -530,7 +530,7 @@ Legacy colors that have [missing] components are
|
||||
|
||||
### Color Equality
|
||||
|
||||
For determining _equality_ between two colors:
|
||||
For determining *equality* between two colors:
|
||||
|
||||
* If both colors are [legacy colors](#legacy-color):
|
||||
|
||||
@ -552,13 +552,13 @@ Each channel has a name, and an associated unit where allowed. Space and
|
||||
channel names match unquoted strings, ignoring case. They are always emitted as
|
||||
unquoted lowercase strings by inspection functions.
|
||||
|
||||
Values outside a _bounded gamut_ range (including infinity or negative infinity)
|
||||
are valid but are considered _out of gamut_ for the given color space. They
|
||||
Values outside a *bounded gamut* range (including infinity or negative infinity)
|
||||
are valid but are considered *out of gamut* for the given color space. They
|
||||
remain un-clamped unless the gamut is specifically marked as "clamped". If the
|
||||
channel is bounded, or has a percentage mapping, then the channel is considered
|
||||
_scalable_.
|
||||
*scalable*.
|
||||
|
||||
Some color spaces use a _polar angle_ value for the `hue` channel. Polar-angle
|
||||
Some color spaces use a *polar angle* value for the `hue` channel. Polar-angle
|
||||
hues represent an angle position around a given hue wheel, using a CSS `<angle>`
|
||||
dimension or number (interpreted as a `deg` value), and are serialized with
|
||||
`deg` units.
|
||||
@ -577,19 +577,24 @@ The known color spaces and their channels are:
|
||||
|
||||
* `hwb` (RGB, legacy):
|
||||
* `hue`:
|
||||
* associated unit: `deg`
|
||||
* degrees: polar angle
|
||||
* `whiteness`, `blackness`:
|
||||
* associated unit: `%`
|
||||
* gamut: bounded
|
||||
* percentage: `[0%,100%]`
|
||||
|
||||
* `hsl` (RGB, legacy):
|
||||
* `hue`:
|
||||
* associated unit: `deg`
|
||||
* degrees: polar angle
|
||||
* `saturation`:
|
||||
* gamut: bounded
|
||||
* associated unit: `%`
|
||||
* percentage: `[0%,100%]`
|
||||
* `lightness`:
|
||||
* gamut: bounded, clamped
|
||||
* associated unit: `%`
|
||||
* percentage: `[0%,100%]`
|
||||
|
||||
* `srgb`, `srgb-linear`, `display-p3`, `a98-rgb`, `prophoto-rgb`,
|
||||
@ -610,6 +615,7 @@ The known color spaces and their channels are:
|
||||
* `lab`:
|
||||
* `lightness`:
|
||||
* gamut: un-bounded, clamped
|
||||
* associated unit: `%`
|
||||
* number: `[0,100]`
|
||||
|
||||
> Percentages `[0%,100%]` map to the `[0,100]` range.
|
||||
@ -623,6 +629,7 @@ The known color spaces and their channels are:
|
||||
* `lch`:
|
||||
* `lightness`:
|
||||
* gamut: un-bounded, clamped
|
||||
* associated unit: `%`
|
||||
* number: `[0,100]`
|
||||
|
||||
> Percentages `[0%,100%]` map to the `[0,100]` range.
|
||||
@ -634,11 +641,13 @@ The known color spaces and their channels are:
|
||||
> Percentages `[0%,100%]` map to the `[0,150]` range.
|
||||
|
||||
* `hue`:
|
||||
* associated unit: `deg`
|
||||
* degrees: polar angle
|
||||
|
||||
* `oklab`:
|
||||
* `lightness`:
|
||||
* gamut: un-bounded, clamped
|
||||
* associated unit: `%`
|
||||
* number: `[0,1]`
|
||||
|
||||
> Percentages `[0%,100%]` map to the `[0,1]` range.
|
||||
@ -652,6 +661,7 @@ The known color spaces and their channels are:
|
||||
* `oklch`:
|
||||
* `lightness`:
|
||||
* gamut: un-bounded, clamped
|
||||
* associated unit: `%`
|
||||
* number: `[0,1]`
|
||||
|
||||
> Percentages `[0%,100%]` map to the `[0,1]` range.
|
||||
@ -663,13 +673,14 @@ The known color spaces and their channels are:
|
||||
> Percentages `[0%,100%]` map to the `[0,0.4]` range.
|
||||
|
||||
* `hue`:
|
||||
* associated unit: `deg`
|
||||
* degrees: polar angle
|
||||
|
||||
### Predefined Color Spaces
|
||||
|
||||
> 'Predefined color spaces' can be described using the `color()` function.
|
||||
|
||||
The _predefined RGB spaces_ are:
|
||||
The *predefined RGB spaces* are:
|
||||
|
||||
* `srgb`
|
||||
* `srgb-linear`
|
||||
@ -678,7 +689,7 @@ The _predefined RGB spaces_ are:
|
||||
* `prophoto-rgb`
|
||||
* `rec2020`
|
||||
|
||||
The _predefined XYZ spaces_ are:
|
||||
The *predefined XYZ spaces* are:
|
||||
|
||||
* `xyz`
|
||||
* `xyz-d50`
|
||||
@ -693,7 +704,7 @@ value of that same component in the other color. In all other cases, the
|
||||
missing value is treated as `0`.
|
||||
|
||||
For the sake of [interpolating] between colors with missing components, the
|
||||
following _analogous components_ are defined by [CSS Color Level 4][color-4]:
|
||||
following *analogous components* are defined by [CSS Color Level 4][color-4]:
|
||||
|
||||
| Category | Components |
|
||||
| ------------- | ------------------- |
|
||||
@ -737,7 +748,7 @@ in certain circumstances.
|
||||
|
||||
### Color Interpolation Method
|
||||
|
||||
A _color interpolation method_ is a space-separated list of unquoted strings,
|
||||
A *color interpolation method* is a space-separated list of unquoted strings,
|
||||
parsed according to the following syntax definition:
|
||||
|
||||
<x><pre>
|
||||
@ -751,9 +762,9 @@ parsed according to the following syntax definition:
|
||||
  ) 'hue'
|
||||
</pre></x>
|
||||
|
||||
A valid _PolarColorSpace_ is the name of a [known color space] with a polar
|
||||
angle hue channel. A _RectangularColorSpace_ is the name of any other
|
||||
[known color space], without a polar-angle hue. The _interpolation color space_
|
||||
A valid *PolarColorSpace* is the name of a [known color space] with a polar
|
||||
angle hue channel. A *RectangularColorSpace* is the name of any other
|
||||
[known color space], without a polar-angle hue. The *interpolation color space*
|
||||
is the result of [looking up a known color space] named by either the
|
||||
`PolarColorSpace` or `RectangularColorSpace` productions.
|
||||
|
||||
@ -891,15 +902,15 @@ The individual conversion algorithms are:
|
||||
|
||||
* [sRGB to HWB](https://www.w3.org/TR/css-color-4/#rgb-to-hwb)
|
||||
|
||||
* [Lab to LCH, OKLab to OKLCH](https://www.w3.org/TR/css-color-4/#lab-to-lch)
|
||||
* [Lab to LCH, Oklab to Oklch](https://www.w3.org/TR/css-color-4/#lab-to-lch)
|
||||
|
||||
* [LCH to Lab, OKLCH to OKLab](https://www.w3.org/TR/css-color-4/#lch-to-lab)
|
||||
* [LCH to Lab, Oklch to Oklab](https://www.w3.org/TR/css-color-4/#lch-to-lab)
|
||||
|
||||
* [Between predefined RGB spaces](https://www.w3.org/TR/css-color-4/#predefined-to-predefined)
|
||||
|
||||
* [Any RGB to Lab/OKLab](https://www.w3.org/TR/css-color-4/#predefined-to-lab-oklab)
|
||||
* [Any RGB to Lab/Oklab](https://www.w3.org/TR/css-color-4/#predefined-to-lab-oklab)
|
||||
|
||||
* [Lab/OKLab to any RGB](https://www.w3.org/TR/css-color-4/#oklab-lab-to-predefined)
|
||||
* [Lab/Oklab to any RGB](https://www.w3.org/TR/css-color-4/#oklab-lab-to-predefined)
|
||||
|
||||
> For additional details, see the [Sample code for color conversions][convert].
|
||||
|
||||
@ -950,131 +961,135 @@ three values: a color space, a list of channel values, and an alpha value.
|
||||
|
||||
The procedure is:
|
||||
|
||||
* If `input` is a [special variable string], return an unquoted string with
|
||||
the value of `input`.
|
||||
* If `input` is a [special variable string], return an unquoted string with
|
||||
the value of `input`.
|
||||
|
||||
* If `input` is a bracketed list, or a list with a separator other than
|
||||
'slash' or 'space', throw an error.
|
||||
* If `input` is a bracketed list, or a list with a separator other than
|
||||
'slash' or 'space', throw an error.
|
||||
|
||||
* If `input` is a slash-separated list:
|
||||
* If `input` is a slash-separated list:
|
||||
|
||||
* If `input` doesn't have exactly two elements, throw an error.
|
||||
* If `input` doesn't have exactly two elements, throw an error.
|
||||
|
||||
* Otherwise, let `components` be the first element and `alpha` the second
|
||||
element of `input`.
|
||||
* Otherwise, let `components` be the first element and `alpha` the second
|
||||
element of `input`.
|
||||
|
||||
* Otherwise:
|
||||
* Otherwise:
|
||||
|
||||
* Let `components` be an unbracketed space separated list of all except the
|
||||
last element of `input`.
|
||||
* Let `components` be an unbracketed space separated list of all except the
|
||||
last element of `input`.
|
||||
|
||||
* If the last element of `input` is an unquoted string that contains `/`:
|
||||
* If the last element of `input` is an unquoted string that contains `/`:
|
||||
|
||||
* Let `split-last` be the result calling `string.split()` with the last
|
||||
element of `input` as the string to split, and `/` as the separator.
|
||||
* Let `split-last` be the result calling `string.split()` with the last
|
||||
element of `input` as the string to split, and `/` as the separator.
|
||||
|
||||
* If `split-last` has two items, and one or both items are an unquoted
|
||||
string that's case-insensitively equal to 'none':
|
||||
* If `split-last` has two items, and one or both items are an unquoted
|
||||
string that's case-insensitively equal to 'none':
|
||||
|
||||
> Special handling for `none/none`, `none/<number>`, and `<number>/none`.
|
||||
> Special handling for `none/none`, `none/<number>`, and `<number>/none`.
|
||||
|
||||
* If either item in `split-last` can be coerced to a number, replace
|
||||
the current value of the item with the resulting number value.
|
||||
* If either item in `split-last` can be coerced to a number, replace
|
||||
the current value of the item with the resulting number value.
|
||||
|
||||
* If any item in `split-last` is not a number or an unquoted string
|
||||
that's case-insensitively equal to 'none', return an unquoted string
|
||||
with the value of `input`.
|
||||
* If any item in `split-last` is not a number or an unquoted string
|
||||
that's case-insensitively equal to 'none', return an unquoted string
|
||||
with the value of `input`.
|
||||
|
||||
* Otherwise, let `alpha` be the second element in `split-last`, and
|
||||
append the first element of `split-last` to `components`.
|
||||
* Otherwise, let `alpha` be the second element in `split-last`, and
|
||||
append the first element of `split-last` to `components`.
|
||||
|
||||
* Otherwise, return an unquoted string with the value of `input`.
|
||||
* Otherwise, return an unquoted string with the value of `input`.
|
||||
|
||||
> This solves for a legacy handling of `/` in Sass that would produce an
|
||||
> unquoted string when the alpha value is a CSS function such as `var()`
|
||||
> or when either value is `none`.
|
||||
|
||||
* Otherwise, if the last element of `input` has preserved its status as two
|
||||
slash-separated numbers:
|
||||
* Otherwise, if the last element of `input` has preserved its status as two
|
||||
slash-separated numbers:
|
||||
|
||||
* Let `alpha` be the number after the slash, and append the number before
|
||||
the slash to `components`.
|
||||
* Let `alpha` be the number after the slash, and append the number before
|
||||
the slash to `components`.
|
||||
|
||||
* Otherwise, append the last element of `input` to `components`.
|
||||
* Otherwise, append the last element of `input` to `components`.
|
||||
|
||||
* If `components` is an empty list, throw an error.
|
||||
* If `components` is an empty list, throw an error.
|
||||
|
||||
* If `components` is a [special variable string]:
|
||||
* If `components` is a [special variable string]:
|
||||
|
||||
* Let `channels` be the value of `components`.
|
||||
* Let `channels` be the value of `components`.
|
||||
|
||||
* Otherwise:
|
||||
* Otherwise:
|
||||
|
||||
* If `components` is not an unbracketed space-separated list, throw an error.
|
||||
* If `components` is not an unbracketed space-separated list, throw an error.
|
||||
|
||||
* If `space` is null:
|
||||
* If the first element of `components` is an unquoted string which is
|
||||
case-insensitively equal to `from`, return an unquoted string with the
|
||||
value of `input`.
|
||||
|
||||
* Let `input-space` be the first element in `components`.
|
||||
* If `space` is null:
|
||||
|
||||
* If `input-space` is a [special variable string], return an unquoted
|
||||
string with the value of `input`.
|
||||
* Let `input-space` be the first element in `components`.
|
||||
|
||||
* Set `space` be the result of [looking up a known color space] with the
|
||||
name `input-space`.
|
||||
* If `input-space` is a [special variable string], return an unquoted
|
||||
string with the value of `input`.
|
||||
|
||||
* If `space` is not a [predefined color space], throw an error.
|
||||
* Set `space` be the result of [looking up a known color space] with the
|
||||
name `input-space`.
|
||||
|
||||
> Only predefined spaces can be passed in as color syntax components.
|
||||
> All other known color spaces use explicit functions.
|
||||
* If `space` is not a [predefined color space], throw an error.
|
||||
|
||||
* Let `channels` be an unbracketed space-separated list with the
|
||||
remaining elements from `components`.
|
||||
> Only predefined spaces can be passed in as color syntax components.
|
||||
> All other known color spaces use explicit functions.
|
||||
|
||||
* Otherwise, let `channels` be the value of `components`.
|
||||
* Let `channels` be an unbracketed space-separated list with the
|
||||
remaining elements from `components`.
|
||||
|
||||
* Let `expected` be the number of channels in `space`.
|
||||
* Otherwise, let `channels` be the value of `components`.
|
||||
|
||||
* If any element of `channels` is not either a number, a special variable
|
||||
string, a [special number], or an unquoted string that's
|
||||
case-insensitively equal to 'none', throw an error.
|
||||
* Let `expected` be the number of channels in `space`.
|
||||
|
||||
* If `alpha` is null, let `alpha` be `1`.
|
||||
* If any element of `channels` is not either a number, a special variable
|
||||
string, a [special number], or an unquoted string that's
|
||||
case-insensitively equal to 'none', throw an error.
|
||||
|
||||
* Otherwise, If `alpha` is not a [special number]:
|
||||
* If `alpha` is null, let `alpha` be `1`.
|
||||
|
||||
* If `alpha` is a number, set `alpha` to the result of
|
||||
[percent-converting] `alpha` with a max of 1, and then clamping the value
|
||||
between 0 and 1, inclusive.
|
||||
* Otherwise, If `alpha` is not a [special number]:
|
||||
|
||||
* Otherwise, throw an error.
|
||||
* If `alpha` is a number, set `alpha` to the result of
|
||||
[percent-converting] `alpha` with a max of 1, and then clamping the value
|
||||
between 0 and 1, inclusive.
|
||||
|
||||
* If `channels` is a [special variable string], or if `alpha` is a [special
|
||||
number], return an unquoted string with the value of `input`.
|
||||
* Otherwise, throw an error.
|
||||
|
||||
* If any element of `channels` is a [special number]:
|
||||
* If `channels` is a [special variable string], or if `alpha` is a [special
|
||||
number], return an unquoted string with the value of `input`.
|
||||
|
||||
* If `space` is a [legacy color] space:
|
||||
* If any element of `channels` is a [special number]:
|
||||
|
||||
* Let `comma-list` be the result of calling
|
||||
`list.append(channels, alpha, 'comma')`.
|
||||
* If `space` is a [legacy color] space:
|
||||
|
||||
* Return an unquoted string with the value of `comma-list`.
|
||||
* Let `comma-list` be the result of calling
|
||||
`list.append(channels, alpha, 'comma')`.
|
||||
|
||||
* Otherwise, return an unquoted string with the value of `input`.
|
||||
* Return an unquoted string with the value of `comma-list`.
|
||||
|
||||
* Otherwise, return an unquoted string with the value of `input`.
|
||||
|
||||
> Doing this late in the process allows us to throw any obvious syntax
|
||||
> errors, even for colors that can't be fully resolved during compilation.
|
||||
|
||||
* If the length of `channels` is not equal to `expected`, throw an error.
|
||||
* If the length of `channels` is not equal to `expected`, throw an error.
|
||||
|
||||
> Once special values have been handled, any colors remaining should have
|
||||
> exactly the expected number of channels.
|
||||
|
||||
* Set `channels` to the result of [normalizing] `channels` in `space`.
|
||||
* Set `channels` to the result of [normalizing] `channels` in `space`.
|
||||
|
||||
* Let `space-name` be a lowercase unquoted string of the `space` name.
|
||||
* Let `space-name` be a lowercase unquoted string of the `space` name.
|
||||
|
||||
* Return `space-name`, `channels` channels, and `alpha` alpha value.
|
||||
* Return `space-name`, `channels` channels, and `alpha` alpha value.
|
||||
|
||||
[special variable string]: ../spec/functions.md#special-variable-string
|
||||
[special number]: ../spec/functions.md#special-number
|
||||
@ -1265,29 +1280,35 @@ input colors.
|
||||
|
||||
* If `weight == 1`, return `color1`.
|
||||
|
||||
* Let `space` be the _interpolation color space_ specified by the `method`
|
||||
* Let `space` be the *interpolation color space* specified by the `method`
|
||||
[color interpolation method].
|
||||
|
||||
> Only known color spaces are allowed as part of a color interpolation method.
|
||||
|
||||
* If `space` is a [PolarColorSpace][color-method]:
|
||||
|
||||
* Let `hue-arc` be the `HueInterpolationMethod` specified in `method`, or
|
||||
`shorter` if no hue interpolation is specified.
|
||||
* Let `hue-arc` be the `HueInterpolationMethod` specified in `method`, or
|
||||
`shorter` if no hue interpolation is specified.
|
||||
|
||||
* Set `color1` and `color2` respectively to the results of [converting] `color1`
|
||||
and `color2` into `space`.
|
||||
|
||||
* For each `color` in `color1` and `color2`:
|
||||
|
||||
* If any `component` of `color` is `none`, set that `component` to the value
|
||||
of the corresponding component in the other color.
|
||||
* If any non-`alpha` `component` of `color` is `none`, set that `component` to
|
||||
the value of the corresponding component in the other color.
|
||||
|
||||
> If both values are `none`, the interpolation result for that component
|
||||
> will also be `none`.
|
||||
|
||||
* Set `color` to the result of [premultiplying] `color`.
|
||||
|
||||
* If `color`'s `alpha` component is `none`, set it to the value of the `alpha`
|
||||
component in the other color.
|
||||
|
||||
> This is resolved after premultiplying, because premultiplying has special
|
||||
> handling for a missing `alpha` component.
|
||||
|
||||
* Let `mix` be a new color in the color space `space`, with `none` for all
|
||||
channel and alpha values.
|
||||
|
||||
@ -1554,18 +1575,21 @@ channel($color, $channel, $space: null)
|
||||
* Let `color` be `$color` if `$space` is null, and the result of calling
|
||||
`color.to-space($color, $space)` otherwise.
|
||||
|
||||
* If `channel` is not the name of a channel in `color`, throw an error.
|
||||
* Let `channel` be the channel in `color`'s space named `$channel`. Throw an
|
||||
error if no such channel exists.
|
||||
|
||||
* Let `value` be the channel value in `color` with name of `channel`.
|
||||
* Let `value` be `channel`'s value in `color`, or `0` if the channel's value
|
||||
is missing.
|
||||
|
||||
* Let `unit` be the unit associated with `channel` in `color`'s space, if
|
||||
defined, and `null` otherwise.
|
||||
|
||||
* If `value` is `null`, return `0`.
|
||||
* If `unit` is `%`, return `value * 100` divided by the maximum of
|
||||
`channel`'s gamut range with unit `%`.
|
||||
|
||||
* If `unit` is not null, return the result of appending `unit` units to `value`.
|
||||
* Otherwise, if `unit` is not null, return `value` with unit `unit`.
|
||||
|
||||
* Return `value`.
|
||||
* Otherwise, return `value` as a unitless number.
|
||||
|
||||
### `color.is-missing()`
|
||||
|
||||
@ -1575,7 +1599,7 @@ is-missing($color, $channel)
|
||||
|
||||
* If `$color` is not a color, throw an error.
|
||||
|
||||
* If `$channel` is not an unquoted string, throw an error.
|
||||
* If `$channel` is not a quoted string, throw an error.
|
||||
|
||||
* If `$channel == alpha` (ignoring case), let `value` be the alpha value of
|
||||
`$color`.
|
||||
@ -1677,11 +1701,11 @@ This function is also available as a global function named `change-color()`.
|
||||
|
||||
* If the keyword argument `$space` is specified in `$args`:
|
||||
|
||||
* Let `known-space` be the result [looking up a known color space] named
|
||||
`$space`.
|
||||
* Let `known-space` be the result [looking up a known color space] named
|
||||
`$space`.
|
||||
|
||||
* If `space != origin-space`, set `color` to the result of calling
|
||||
`color.to-space(color, space)`.
|
||||
* If `space != origin-space`, set `color` to the result of calling
|
||||
`color.to-space(color, space)`.
|
||||
|
||||
* Otherwise, let `known-space` be `origin-space`.
|
||||
|
||||
@ -1689,8 +1713,8 @@ This function is also available as a global function named `change-color()`.
|
||||
|
||||
* If the keyword argument `$alpha` is specified in `$args`:
|
||||
|
||||
* Set `alpha` to the result of [percent-converting] `$alpha`, and clamping
|
||||
it between 0 and 1 (inclusive).
|
||||
* Set `alpha` to the result of [percent-converting] `$alpha` with a `max` of
|
||||
1, and clamping it between 0 and 1 (inclusive).
|
||||
|
||||
* Let `channel-args` be the remaining keyword arguments in `$args`, not
|
||||
including `$space` or `$alpha` arguments.
|
||||
@ -1754,11 +1778,11 @@ This function is also available as a global function named `adjust-color()`.
|
||||
|
||||
* If the keyword argument `$space` is specified in `$args`:
|
||||
|
||||
* Let `known-space` be the result [looking up a known color space] named
|
||||
`$space`.
|
||||
* Let `known-space` be the result [looking up a known color space] named
|
||||
`$space`.
|
||||
|
||||
* If `space != origin-space`, set `color` to the result of calling
|
||||
`color.to-space(color, space)`.
|
||||
* If `space != origin-space`, set `color` to the result of calling
|
||||
`color.to-space(color, space)`.
|
||||
|
||||
* Otherwise, let `known-space` be `origin-space`.
|
||||
|
||||
@ -1766,13 +1790,13 @@ This function is also available as a global function named `adjust-color()`.
|
||||
|
||||
* If the keyword argument `$alpha` is specified in `$args`:
|
||||
|
||||
* If `alpha == none`, throw an error.
|
||||
* If `alpha == none`, throw an error.
|
||||
|
||||
> This is not the ideal solution for handling `none`, but we want to
|
||||
> match CSS relative color syntax if possible. Throwing an error for now
|
||||
> means we can adjust to match the CSS behavior once it is defined.
|
||||
> This is not the ideal solution for handling `none`, but we want to
|
||||
> match CSS relative color syntax if possible. Throwing an error for now
|
||||
> means we can adjust to match the CSS behavior once it is defined.
|
||||
|
||||
* Let `new-alpha` be the result of [percent-converting] `$alpha` with a max
|
||||
* Let `new-alpha` be the result of [percent-converting] `$alpha` with a `max`
|
||||
of 1.
|
||||
|
||||
* Set `alpha` to the value of `new-alpha + alpha` clamped between 0 and 1.
|
||||
@ -1822,8 +1846,8 @@ This function is also available as a global function named `adjust-color()`.
|
||||
`%` units to `channel`.
|
||||
|
||||
* Otherwise, if `valid` allows percentage mapping, set `adjust` to the
|
||||
result of [percent-converting] `adjust` with a `min` and `max` defined
|
||||
by the `valid` channel range.
|
||||
result of [percent-converting] `adjust` with a `max` given by the maximum
|
||||
of `valid`'s gamut range.
|
||||
|
||||
* Otherwise, throw an error.
|
||||
|
||||
@ -1855,10 +1879,10 @@ This function is also available as a global function named `scale-color()`.
|
||||
|
||||
* If the keyword argument `$space` is specified in `$args`:
|
||||
|
||||
* Let `space` be the result of [looking up a known color space] named
|
||||
`$space`.
|
||||
* Let `space` be the result of [looking up a known color space] named
|
||||
`$space`.
|
||||
|
||||
* Let `color` be the result of [converting] `$color` to `space`.
|
||||
* Let `color` be the result of [converting] `$color` to `space`.
|
||||
|
||||
* Otherwise:
|
||||
|
||||
@ -1870,13 +1894,13 @@ This function is also available as a global function named `scale-color()`.
|
||||
|
||||
* If the keyword argument `$alpha` is specified in `$args`:
|
||||
|
||||
* If `alpha == none`, throw an error.
|
||||
* If `alpha == none`, throw an error.
|
||||
|
||||
> This is not the ideal solution for handling `none`, but we want to
|
||||
> match CSS relative color syntax if possible. Throwing an error for now
|
||||
> means we can adjust to match the CSS behavior once it is defined.
|
||||
> This is not the ideal solution for handling `none`, but we want to
|
||||
> match CSS relative color syntax if possible. Throwing an error for now
|
||||
> means we can adjust to match the CSS behavior once it is defined.
|
||||
|
||||
* Set `alpha` to the result of [scaling] `alpha` by `$alpha` with `max` 1.
|
||||
* Set `alpha` to the result of [scaling] `alpha` by `$alpha` with `max` 1.
|
||||
|
||||
* Let `channel-args` be the remaining keyword arguments in `$args`, not
|
||||
including `$space` or `$alpha` arguments.
|
||||
@ -1997,8 +2021,8 @@ This function is also available as a global function named `invert()`.
|
||||
|
||||
* If `$weight == 0%`, return the value of `$color`.
|
||||
|
||||
* If `space` is not a valid [color interpolation method] _interpolation color
|
||||
space_, and `$weight != 100%`, throw an error.
|
||||
* If `space` is not a valid [color interpolation method] *interpolation color
|
||||
space*, and `$weight != 100%`, throw an error.
|
||||
|
||||
* Let `color` be the result of [converting] `$color` into `space`.
|
||||
|
||||
@ -2093,7 +2117,6 @@ ie-hex-str($color)
|
||||
|
||||
* Return the result of concatenating `hex-list` into a string.
|
||||
|
||||
|
||||
## New Global Functions
|
||||
|
||||
These new CSS functions are provided globally.
|
||||
|
13
proposal/package-importer.changes.md
Normal file
13
proposal/package-importer.changes.md
Normal file
@ -0,0 +1,13 @@
|
||||
## Draft 1.1
|
||||
|
||||
* Throw an error if `nodePackageImporter` is used in the browser or other
|
||||
environment without filesystem access.
|
||||
|
||||
* Remove specified order in the global import list, as users can specify the
|
||||
order within the `importers` option.
|
||||
|
||||
* Specify importer ordering for the Legacy API.
|
||||
|
||||
## Draft 1
|
||||
|
||||
* Initial draft
|
559
proposal/package-importer.d.ts.md
Normal file
559
proposal/package-importer.d.ts.md
Normal file
@ -0,0 +1,559 @@
|
||||
# Package Importer
|
||||
|
||||
*([Issue](https://github.com/sass/sass/issues/2739))*
|
||||
|
||||
This proposal introduces the semantics for a Package Importer and defines the
|
||||
`pkg:` URL scheme to indicate Sass package imports in an implementation-agnostic
|
||||
format. It also defines the semantics for a new built-in Node Package
|
||||
Importer.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Background](#background)
|
||||
* [Summary](#summary)
|
||||
* [Node built-in importer](#node-built-in-importer)
|
||||
* [Design Decisions](#design-decisions)
|
||||
* [Using a `pkg:` URL scheme](#using-a-pkg-url-scheme)
|
||||
* [No built-in `pkg:` resolver for browsers](#no-built-in-pkg-resolver-for-browsers)
|
||||
* [Available as an opt-in importer](#available-as-an-opt-in-importer)
|
||||
* [Available in legacy API](#available-in-legacy-api)
|
||||
* [Node Resolution Decisions](#node-resolution-decisions)
|
||||
* [Types](#types)
|
||||
* [`nodePackageImporter`](#nodepackageimporter)
|
||||
* [Updated `importers` option](#updated-importers-option)
|
||||
* [Legacy API `pkgImporter`](#legacy-api-pkgimporter)
|
||||
* [Semantics](#semantics)
|
||||
* [Package Importers](#package-importers)
|
||||
* [Node Package Importer](#node-package-importer)
|
||||
* [Procedures](#procedures)
|
||||
* [Node Algorithm for Resolving a `pkg:` URL](#node-algorithm-for-resolving-a-pkg-url)
|
||||
* [Resolving a package name](#resolving-a-package-name)
|
||||
* [Resolving the root directory for a package](#resolving-the-root-directory-for-a-package)
|
||||
* [Resolving package exports](#resolving-package-exports)
|
||||
* [Resolving package root values](#resolving-package-root-values)
|
||||
* [Export Load Paths](#export-load-paths)
|
||||
* [Embedded Protocol](#embedded-protocol)
|
||||
* [Ecosystem Notes](#ecosystem-notes)
|
||||
|
||||
## Background
|
||||
|
||||
> This section is non-normative.
|
||||
|
||||
Historically, Sass has not specified a standard method for using packages from
|
||||
dependencies. A number of domain-specific solutions exist using custom importers
|
||||
or by specifying a load path. This can lead to Sass code being written in a way
|
||||
that is tied to a specific domain and make it difficult to rely on dependencies.
|
||||
|
||||
## Summary
|
||||
|
||||
> This section is non-normative.
|
||||
|
||||
Sass users often need to use styles from a dependency to customize an existing
|
||||
theme or access styling utilities.
|
||||
|
||||
This proposal defines a `pkg:` URL scheme for usage with `@use` that directs an
|
||||
implementation to resolve a URL within a dependency. Sass interfaces may provide
|
||||
one or more implementations that will resolve the dependency URL using the
|
||||
resolution standards and conventions for that environment. Once resolved, this
|
||||
URL will be loaded in the same way as any other `file:` URL.
|
||||
|
||||
This proposal also defines a built-in Node importer.
|
||||
|
||||
For example, `@use "pkg:bootstrap";` would resolve to the path of a
|
||||
library-defined export within the `bootstrap` dependency. In Node, that could be
|
||||
resolved within `node_modules`, using the [Node resolution algorithm].
|
||||
|
||||
[node resolution algorithm]: https://nodejs.org/api/packages.html
|
||||
|
||||
### Node built-in importer
|
||||
|
||||
The built-in Node importer resolves in the following order:
|
||||
|
||||
1. `sass`, `style`, or `default` condition in package.json `exports`.
|
||||
|
||||
2. If there is not a subpath, then find the root export:
|
||||
|
||||
1. `sass` key at package.json root.
|
||||
|
||||
2. `style` key at package.json root.
|
||||
|
||||
3. `index` file at package root, resolved for file extensions and partials.
|
||||
|
||||
3. If there is a subpath, resolve that path relative to the package root, and
|
||||
resolve for file extensions and partials.
|
||||
|
||||
For library creators, the recommended method is to add a `sass` conditional
|
||||
export to `package.json`. The `style` condition is an acceptable alternative,
|
||||
but relying on the `default` condition is discouraged. Notably, the key order
|
||||
matters, and the importer will resolve to the first value with a key that is
|
||||
`sass`, `style`, or `default`.
|
||||
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
".": {
|
||||
"sass": "./dist/scss/index.scss",
|
||||
"import": "./dist/js/index.mjs",
|
||||
"default": "./dist/js/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, library consumers can use the `pkg:` syntax to get the default export.
|
||||
|
||||
```scss
|
||||
@use 'pkg:library';
|
||||
```
|
||||
|
||||
To better understand and allow for testing against the recommended algorithm, a
|
||||
[Sass pkg: test] repository has been made with a rudimentary implementation of
|
||||
the algorithm.
|
||||
|
||||
[sass pkg: test]: https://github.com/oddbird/sass-pkg-test
|
||||
|
||||
### Design Decisions
|
||||
|
||||
#### Using a `pkg:` URL scheme
|
||||
|
||||
We could use the `~` popularized by Webpack's `load-sass` format, but this has
|
||||
been deprecated since 2021. In addition, since this creates a URL that is
|
||||
syntactically a relative URL, it does not make it clear to the implementation or
|
||||
the reader where to find the file.
|
||||
|
||||
While the Dart Sass implementation allows for the use of the `package:` URL
|
||||
scheme, a similar standard doesn't exist in Node. We chose the `pkg:` URL scheme
|
||||
as it clearly communicates to both the user and compiler that the specified files
|
||||
are from a dependency. The `pkg:` URL scheme also does not have known conflicts
|
||||
in the ecosystem.
|
||||
|
||||
#### No built-in `pkg:` resolver for browsers
|
||||
|
||||
Dart Sass will not provide a built-in resolver for browsers to use the `pkg:`
|
||||
scheme. To support a similar functionality, a user would need to ensure that
|
||||
files are served, and the loader would need to fetch the URL. In order to follow
|
||||
the same algorithm for [resolving a file: URL], we would need to make many
|
||||
fetches. If we instead require the browser version to have a fully resolved URL,
|
||||
we negate many of this spec's benefits. Users may write their own custom
|
||||
importers to fit their needs.
|
||||
|
||||
[resolving a file: url]: ../spec/modules.md#resolving-a-file-url
|
||||
|
||||
#### Available as an opt-in importer
|
||||
|
||||
The `pkg:` import loader will be exposed as an opt-in importer as it adds the
|
||||
potential for unexpected file system interaction to `compileString` and
|
||||
`compileStringAsync`. Specifically, we want people who invoke Sass compilation
|
||||
functions to have control over what files get accessed, and there's even a risk
|
||||
of leaking file contents in error messages.
|
||||
|
||||
For the modern API, it will be exported from Sass as a constant value that can
|
||||
be added to the list of `importers`. This allows for multiple Package Importer
|
||||
types with user-defined order.
|
||||
|
||||
#### Available in legacy API
|
||||
|
||||
The built-in Node Package Importer will be added to the legacy API in order to
|
||||
reduce the barrier to adoption. While the legacy API is deprecated, we
|
||||
anticipate the implementation to be straightforward.
|
||||
|
||||
#### Node Resolution Decisions
|
||||
|
||||
The current recommendation for resolving packages in Node is to add
|
||||
`node_modules` to the load paths. We could add `node_modules` to the load paths
|
||||
by default, but that lacks clarity to the implementation and the reader. In
|
||||
addition, a file may have access to multiple `node_modules` directories, and
|
||||
different files may have access to different `node_modules` directories in the
|
||||
same compilation.
|
||||
|
||||
There are a variety of methods currently in use for specifying a location of the
|
||||
default Sass export for npm packages. For the most part, packages contain both
|
||||
JavaScript and styles, and use the `main` or `module` root keys to define the
|
||||
JavaScript entry point. Some packages use the `"sass"` key at the root of their
|
||||
`package.json`.
|
||||
|
||||
Other packages have adopted [conditional exports], driven by build tools like
|
||||
[Vite], [Parcel] and [Sass Loader for Webpack] which all resolve Sass paths
|
||||
using the `"sass"` and the `"style"` custom conditions.
|
||||
|
||||
[conditional exports]: https://nodejs.org/api/packages.html#conditional-exports
|
||||
[Vite]: https://github.com/vitejs/vite/pull/7817
|
||||
[Parcel]: https://github.com/parcel-bundler/parcel/blob/2d2400ded4615375ee6bd53ef77b4857ad1591dd/packages/transformers/sass/src/SassTransformer.js#L163
|
||||
[Sass Loader for Webpack]: https://github.com/webpack-contrib/sass-loader/blob/02df41203adfda96959e56abb43bd35a89ec11ba/src/utils.js#L514
|
||||
|
||||
Because use of conditional exports is flexible and recommended for modern
|
||||
packages, this will be the primary method used for the Node package importer. We
|
||||
will support both the `"sass"` and the `"style"` conditions, as Sass can also
|
||||
use the CSS exports exposed through `"style"`. While in practice, `"style"`
|
||||
tends to be used solely for `css` files, we will support `scss`, `sass` and
|
||||
`css` files for either `"sass"` or `"style"`.
|
||||
|
||||
While conditional exports allows package authors to define specific aliases to internal
|
||||
files, we will still use the Sass conventions for resolving file paths with
|
||||
partials, extensions and indices to discover the intended export alias. However,
|
||||
we will not apply that logic to the destination, and will expect library authors
|
||||
to map the export to the correct place. In other words, given a `package.json`
|
||||
with `exports` as below, The Node package importer will resolve a
|
||||
`@use "pkg:pkgName/variables";` to the destination of the `_variables.scss` export.
|
||||
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
"_variables.scss": {
|
||||
"sass": "./src/sass/_variables.scss"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Node supports two module resolution algorithms: CommonJS and ECMAScript. While
|
||||
these are very similar in most cases, there are corner cases that resolve in
|
||||
different ways. The Node package importer will be implemented based on the
|
||||
ECMAScript algorithm. This means that the Node package importer will not support
|
||||
loading from `NODE_PATH` or `GLOBAL_FOLDERS`, as that is only supported in
|
||||
CommonJS resolution. The Node documentation for [ECMAScript modules] recommends
|
||||
using symlinks if this behavior is desired.
|
||||
|
||||
[ECMAScript modules]: https://nodejs.org/api/esm.html#no-node_path
|
||||
|
||||
## Types
|
||||
|
||||
```ts
|
||||
import {FileImporter, Importer} from '../spec/js-api/importer';
|
||||
```
|
||||
|
||||
### `nodePackageImporter`
|
||||
|
||||
```ts
|
||||
type NodePackageImporter = {
|
||||
_NodePackageImporterBrand: any;
|
||||
};
|
||||
export declare const nodePackageImporter: NodePackageImporter;
|
||||
```
|
||||
|
||||
### Updated `importers` option
|
||||
|
||||
> On implementation, the option key will continue to be `importers`, and this
|
||||
> type definition will replace the existing type definition for `importers`.
|
||||
> Here, we are only specifying it as `importers_new_` to allow for declaration
|
||||
> merging within the spec.
|
||||
|
||||
```ts
|
||||
declare module '../spec/js-api/options' {
|
||||
interface Options<sync extends 'sync' | 'async'> {
|
||||
importers_new_?: (
|
||||
| Importer<sync>
|
||||
| FileImporter<sync>
|
||||
| NodePackageImporter
|
||||
)[];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Before the first bullet points in [`compile`] and [`compileString`] in the
|
||||
Javascript Compile API, insert:
|
||||
|
||||
* If any object in `options.importers` is exactly equal to the object
|
||||
`nodePackageImporter`:
|
||||
|
||||
* If no filesystem is available, throw an error.
|
||||
|
||||
> This primarily refers to a browser environment, but applies to other
|
||||
> sandboxed JavaScript environments as well.
|
||||
|
||||
* Let `pkgImporter` be a [Node Package Importer] with an associated
|
||||
`entryPointURL` of `require.main.filename`.
|
||||
|
||||
* Replace `nodePackageImporter` with `pkgImporter` in a copy of
|
||||
`options.importers`.
|
||||
|
||||
[`compile`]: ../spec/js-api/compile.d.ts.md#compile
|
||||
[`compileString`]: ../spec/js-api/compile.d.ts.md#compilestring
|
||||
[Node Package Importer]: #node-package-importer
|
||||
|
||||
### Legacy API `pkgImporter`
|
||||
|
||||
If set, the compiler will use the specified built-in package importer to resolve
|
||||
any URL with the `pkg:` scheme. This step will be inserted immediately before
|
||||
the existing legacy importer logic, and if the package importer returns `null`,
|
||||
the legacy importer logic will be invoked.
|
||||
|
||||
Currently, the only available package importer is `node`, which follows Node
|
||||
resolution logic to locate Sass files.
|
||||
|
||||
Defaults to undefined.
|
||||
|
||||
```ts
|
||||
declare module '../spec/js-api/legacy/options' {
|
||||
export interface LegacySharedOptions<sync extends 'sync' | 'async'> {
|
||||
pkgImporter?: 'node';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Semantics
|
||||
|
||||
### Package Importers
|
||||
|
||||
This proposal defines the requirements for Package Importers written by users or
|
||||
provided by implementations. It is a type of [Importer] and, in addition to the
|
||||
standard requirements for importers, it must handle only non-canonical URLs that:
|
||||
|
||||
* have the scheme `pkg`, and
|
||||
* whose path begins with a package name, and
|
||||
* optionally followed by a path, with path segments separated with a forward
|
||||
slash.
|
||||
|
||||
The package name will often be the first path segment, but the importer may take
|
||||
into account any conventions in the environment. For instance, Node supports
|
||||
scoped package names, which start with `@` followed by 2 path segments. Note
|
||||
that package names that contain non-alphanumeric characters may be less portable
|
||||
across different package importers.
|
||||
|
||||
Package Importers must reject the following patterns:
|
||||
|
||||
* A URL whose path begins with `/`.
|
||||
* A URL with non-empty/null username, password, host, port, query, or fragment.
|
||||
|
||||
[importer]: ../spec/modules.md#importer
|
||||
|
||||
### Node Package Importer
|
||||
|
||||
The Node Package Importer is an implementation of a [Package Importer] using the
|
||||
standards and conventions of the Node ecosystem. It has an associated absolute
|
||||
`file:` URL named `entryPointURL`.
|
||||
|
||||
When the Node Package Importer is invoked with a string named `string`:
|
||||
|
||||
* If `string` is a relative URL, return null.
|
||||
|
||||
* Let `url` be the result of [parsing `string` as a URL][parsing a URL]. If this
|
||||
returns a failure, throw that failure.
|
||||
|
||||
* If `url`'s scheme is not `pkg:`, return null.
|
||||
|
||||
* If `url`'s path begins with a `/` or is empty, throw an error.
|
||||
|
||||
* If `url` contains a username, password, host, port, query, or fragment, throw
|
||||
an error.
|
||||
|
||||
* Let `sourceFile` be the canonical URL of the [current source file] that
|
||||
contained the load.
|
||||
|
||||
* If `sourceFile`'s scheme is `file:`, let `baseURL` be `sourceFile`.
|
||||
|
||||
* Otherwise, let `baseURL` be `entryPointURL`.
|
||||
|
||||
* Let `resolved` be the result of [resolving a `pkg:` URL as Node] with `url` and
|
||||
`baseURL`.
|
||||
|
||||
* If `resolved` is null, return null.
|
||||
|
||||
* Let `text` be the contents of the file at `resolved`.
|
||||
|
||||
* Let `syntax` be:
|
||||
|
||||
* "scss" if `resolved` ends in `.scss`.
|
||||
|
||||
* "indented" if `resolved` ends in `.sass`.
|
||||
|
||||
* "css" if `resolved` ends in `.css`.
|
||||
|
||||
> The algorithm for [resolving a `pkg:` URL as Node] guarantees that
|
||||
> `resolved` will have one of these extensions.
|
||||
|
||||
* Return `text`, `syntax`, and `resolved`.
|
||||
|
||||
[Package Importer]: #package-importers
|
||||
[parsing a URL]: https://url.spec.whatwg.org/#concept-url-parser
|
||||
[current source file]: ../spec/spec.md#current-source-file
|
||||
[resolving a `pkg:` URL as Node]: #node-algorithm-for-resolving-a-pkg-url
|
||||
|
||||
## Procedures
|
||||
|
||||
### Node Algorithm for Resolving a `pkg:` URL
|
||||
|
||||
This algorithm takes a URL with scheme `pkg:` named `url`, and a URL `baseURL`.
|
||||
It returns a canonical `file:` URL or null.
|
||||
|
||||
* Let `fullPath` be `url`'s path.
|
||||
|
||||
* Let `packageName` be the result of [resolving a package name] with `fullPath`,
|
||||
and `subpath` be `fullPath` without the `packageName`.
|
||||
|
||||
* Let `packageRoot` be the result of [resolving the root directory for a
|
||||
package] with `packageName` and `baseURL`.
|
||||
|
||||
* If a `package.json` file does not exist at `packageRoot`, throw an error.
|
||||
|
||||
* Let `packageManifest` be the result of parsing the `package.json` file at
|
||||
`packageRoot` as [JSON].
|
||||
|
||||
* Let `resolved` be the result of [resolving package exports] with
|
||||
`packageRoot`, `subpath`, and `packageManifest`.
|
||||
|
||||
* If `resolved` has the scheme `file:` and an extension of `sass`, `scss` or
|
||||
`css`, return it.
|
||||
|
||||
* Otherwise, if `resolved` is not null, throw an error.
|
||||
|
||||
* If `subpath` is empty, return the result of [resolving package root values].
|
||||
|
||||
* Let `resolved` be `subpath` resolved relative to `packageRoot`.
|
||||
|
||||
* Return the result of [resolving a `file:` URL] with `resolved`.
|
||||
|
||||
[Resolving package exports]: #resolving-package-exports
|
||||
[resolving package root values]: #resolving-package-root-values
|
||||
[resolving a package name]: #resolving-a-package-name
|
||||
[JSON]: https://datatracker.ietf.org/doc/html/rfc8259
|
||||
[resolving the root directory for a package]: #resolving-the-root-directory-for-a-package
|
||||
[resolving a `file:` URL]: ../spec/modules.md#resolving-a-file-url
|
||||
|
||||
### Resolving a package name
|
||||
|
||||
This algorithm takes a string, `path`, and returns the portion that identifies
|
||||
the Node package.
|
||||
|
||||
* If `path` starts with `@`, it is a scoped package. Return the first 2 [URL path
|
||||
segments], including the separating `/`.
|
||||
|
||||
* Otherwise, return the first URL path segment.
|
||||
|
||||
### Resolving the root directory for a package
|
||||
|
||||
This algorithm takes a string, `packageName`, and an absolute URL `baseURL`, and
|
||||
returns an absolute URL to the root directory for the most proximate installed
|
||||
`packageName`.
|
||||
|
||||
* Return the result of `PACKAGE_RESOLVE(packageName, baseURL)` as defined in
|
||||
the [Node resolution algorithm specification].
|
||||
|
||||
[Node resolution algorithm specification]: https://nodejs.org/api/esm.html#resolution-algorithm-specification
|
||||
|
||||
### Resolving package exports
|
||||
|
||||
This algorithm takes a package.json value `packageManifest`, a directory URL
|
||||
`packageRoot` and a relative URL path `subpath`. It returns a file URL or null.
|
||||
|
||||
* Let `exports` be the value of `packageManifest.exports`.
|
||||
|
||||
* If `exports` is undefined, return null.
|
||||
|
||||
* Let `subpathVariants` be the result of [Export load paths] with `subpath`.
|
||||
|
||||
* Let `resolvedPaths` be a list of the results of calling
|
||||
`PACKAGE_EXPORTS_RESOLVE(packageRoot, subpathVariant, exports, ["sass",
|
||||
"style"])` as defined in the [Node resolution algorithm specification], with
|
||||
each `subpathVariants` as `subpathVariant`.
|
||||
|
||||
> The PACKAGE_EXPORTS_RESOLVE algorithm always includes a `default` condition,
|
||||
> so one does not have to be passed here.
|
||||
|
||||
* If `resolvedPaths` contains more than one resolved URL, throw an error.
|
||||
|
||||
* If `resolvedPaths` contains exactly one resolved URL, return it.
|
||||
|
||||
* If `subpath` has an extension, return null.
|
||||
|
||||
* Let `subpathIndex` be `subpath` + `"/index"`.
|
||||
|
||||
* Let `subpathIndexVariants` be the result of [Export load paths] with `subpathIndex`.
|
||||
|
||||
* Let `resolvedIndexPaths` be a list of the results of calling
|
||||
`PACKAGE_EXPORTS_RESOLVE(packageRoot, subpathVariant, exports, ["sass",
|
||||
"style"])` as defined in the [Node resolution algorithm specification], with
|
||||
each `subpathIndexVariants` as `subpathVariant`.
|
||||
|
||||
* If `resolvedIndexPaths` contains more than one resolved URL, throw an error.
|
||||
|
||||
* If `resolvedIndexPaths` contains exactly one resolved URL, return it.
|
||||
|
||||
* Return null.
|
||||
|
||||
> Where possible in Node, implementations can use [resolve.exports] which
|
||||
> exposes the Node resolution algorithm, allowing for per-path custom
|
||||
> conditions, and without needing filesystem access.
|
||||
|
||||
[Export load paths]: #export-load-paths
|
||||
[resolve.exports]: https://github.com/lukeed/resolve.exports
|
||||
|
||||
### Resolving package root values
|
||||
|
||||
This algorithm takes a string `packagePath`, which is the root directory for a
|
||||
package, and `packageManifest`, which is the contents of that package's
|
||||
`package.json` file, and returns a file URL.
|
||||
|
||||
* Let `sassValue` be the value of `sass` in `packageManifest`.
|
||||
|
||||
* If `sassValue` is a relative path with an extension of `sass`, `scss` or
|
||||
`css`:
|
||||
|
||||
* Return the canonicalized `file:` URL for `${packagePath}/${sassValue}`.
|
||||
|
||||
* Let `styleValue` be the value of `style` in `packageManifest`.
|
||||
|
||||
* If `styleValue` is a relative path with an extension of `sass`, `scss` or
|
||||
`css`:
|
||||
|
||||
* Return the canonicalized `file:` URL for `${packagePath}/${styleValue}`.
|
||||
|
||||
* Otherwise return the result of [resolving a `file:` URL for extensions] with
|
||||
`packagePath + "/index"`.
|
||||
|
||||
[resolving a `file:` URL for extensions]: ../spec/modules.md#resolving-a-file-url-for-extensions
|
||||
|
||||
[URL path segments]: https://url.spec.whatwg.org/#url-path-segment
|
||||
|
||||
### Export Load Paths
|
||||
|
||||
This algorithm takes a relative URL path `subpath` and returns a list of
|
||||
potential subpaths, resolving for partials and file extensions.
|
||||
|
||||
* Let `paths` be a list.
|
||||
|
||||
* If `subpath` ends in `.scss`, `.sass`, or `.css`:
|
||||
|
||||
* Add `subpath` to `paths`.
|
||||
|
||||
* Otherwise, add `subpath` + `.scss`, `subpath` + `.sass`, and `subpath` +
|
||||
`.css` to `paths`.
|
||||
|
||||
* If `subpath`'s [basename] does not start with `_`, for each `item` in
|
||||
`paths`, prepend `"_"` to the basename, and add to `paths`.
|
||||
|
||||
* Return `paths`.
|
||||
|
||||
[basename]: ../spec/modules.md#basename
|
||||
|
||||
## Embedded Protocol
|
||||
|
||||
An Importer that resolves `pkg:` URLs using the [Node resolution algorithm]. It
|
||||
is instantiated with an associated `entry_point_url`.
|
||||
|
||||
```proto
|
||||
message NodePackageImporter {
|
||||
string entry_point_url = 1;
|
||||
}
|
||||
```
|
||||
|
||||
```proto
|
||||
message CompileRequest {
|
||||
message Importer {
|
||||
oneof importer {
|
||||
NodePackageImporter node_package_importer = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ecosystem Notes
|
||||
|
||||
It may be worth adding a [Community Conditions Definition] to the Node
|
||||
Documentation. [WinterCG] has a [Runtime Keys proposal specification] underway
|
||||
in standardizing the usage of custom conditions for runtimes, but Sass doesn't
|
||||
cleanly fit into that specification.
|
||||
|
||||
[community conditions definition]: https://nodejs.org/docs/latest-v20.x/api/packages.html#community-conditions-definitions
|
||||
[wintercg]: https://wintercg.org/
|
||||
[runtime keys proposal specification]: https://runtime-keys.proposal.wintercg.org/#adding-a-key
|
@ -65,7 +65,7 @@ Adjust the list of productions that should produce errors as follows:
|
||||
* Add "A style rule whose selector contains a trailing combinator."
|
||||
|
||||
> While the [bogus combinators] deprecation is in place, style rules with
|
||||
> trailing combinators that _don't_ have nested rules will produce warnings.
|
||||
> trailing combinators that *don't* have nested rules will produce warnings.
|
||||
> Those with nested rules will produce errors since Sass never parsed them
|
||||
> successfully in the first place.
|
||||
|
||||
|
@ -1 +1 @@
|
||||
2.1.0
|
||||
2.3.0
|
||||
|
@ -14,13 +14,13 @@ still supported for backwards-compatibility.
|
||||
## Syntax
|
||||
|
||||
<x><pre>
|
||||
**ImportRule** ::= '@import' (ImportArgumentNoMedia ',')* ImportArgument
|
||||
**ImportRule** ::= '@import' (ImportArgumentNoMedia ',')\* ImportArgument
|
||||
**ImportArgumentNoMedia** ::= ImportUrl ImportModifierNoMedia*
|
||||
**ImportArgument** ::= ImportUrl ImportModifier
|
||||
**ImportModifierNoMedia** ::= InterpolatedIdentifier* (ImportFunction | ImportSupports)
|
||||
**ImportModifier** ::= ImportModifierNoMedia* InterpolatedIdentifier* ImportMedia?
|
||||
**ImportMedia** ::= [MediaFeatureInParens] (',' [MediaQueryList])*
|
||||
  | InterpolatedIdentifier (',' [MediaQueryList])*
|
||||
**ImportModifierNoMedia** ::= InterpolatedIdentifier\* (ImportFunction | ImportSupports)
|
||||
**ImportModifier** ::= ImportModifierNoMedia\* InterpolatedIdentifier\* ImportMedia?
|
||||
**ImportMedia** ::= [MediaFeatureInParens] (',' [MediaQueryList])\*
|
||||
  | InterpolatedIdentifier (',' [MediaQueryList])\*
|
||||
**ImportSupports** ::= 'supports(' SupportsDeclaration ')'
|
||||
**ImportFunction** ::= [InterpolatedIdentifier]¹ '(' InterpolatedDeclarationValue? ')'
|
||||
**ImportUrl** ::= QuotedString | [InterpolatedUrl][]
|
||||
@ -137,10 +137,10 @@ To execute an `@import` rule `rule`:
|
||||
|
||||
* Add `imported`'s [extensions][] to the current module.
|
||||
|
||||
* If the `@import` rule is nested within at-rules and/or style rules, add each
|
||||
* If the `@import` rule is nested within at-rules and/or style rules, add each
|
||||
member in `imported` to the local [scope][].
|
||||
|
||||
* Otherwise, add each member in `imported` to the current import context and
|
||||
* Otherwise, add each member in `imported` to the current import context and
|
||||
the current module.
|
||||
|
||||
> Members defined directly in `imported` will have already been added to
|
||||
|
@ -37,7 +37,7 @@ identifiers are matched case-insensitively:
|
||||
  | '(' Expression³ [\<mf-lt>] Expression³ [\<mf-lt>] Expression³ ')'
|
||||
  | '(' Expression³ [\<mf-gt>] Expression³ [\<mf-gt>] Expression³ ')'
|
||||
  | '(' MediaNot ')'
|
||||
  | '(' MediaInParens (MediaAnd* | MediaOr*) ')'
|
||||
  | '(' MediaInParens (MediaAnd\*| MediaOr\*) ')'
|
||||
</pre></x>
|
||||
|
||||
[InterpolatedIdentifier]: ../syntax.md#interpolatedidentifier
|
||||
|
@ -21,8 +21,6 @@ the current stylesheet, and includes its CSS in the compilation output.
|
||||
A `@use` rule's *module* is a [module][] associated with a `@use` rule. This
|
||||
module is only associated once the rule has been [executed](#semantics).
|
||||
|
||||
[module]: ../modules.md#module
|
||||
|
||||
## Syntax
|
||||
|
||||
The grammar for the `@use` rule is as follows:
|
||||
@ -138,4 +136,3 @@ To execute a `@use` rule `rule`:
|
||||
* If `variable` wasn't declared with a `!default` flag, throw an error.
|
||||
|
||||
* Set [`rule`'s module](#a-use-rules-module) to `module`.
|
||||
|
||||
|
@ -443,7 +443,6 @@ This function is also available as a global function named `hue()`.
|
||||
[percent-converting]: #percent-converting-a-number
|
||||
[to RGB]: https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
||||
|
||||
|
||||
* ```
|
||||
hwb($channels)
|
||||
```
|
||||
|
@ -221,7 +221,6 @@ hypot($numbers...)
|
||||
log($number, $base: null)
|
||||
```
|
||||
|
||||
|
||||
* If `$number` has units, throw an error.
|
||||
|
||||
* Return a unitless number whose value is the result of `log($number.value)` as
|
||||
@ -444,9 +443,10 @@ This function is also available as a global function named `random()`.
|
||||
units as `$limit`.
|
||||
|
||||
> Examples:
|
||||
> - `math.random(123) => 87`
|
||||
> - `math.random(123px) => 43px`
|
||||
> - `math.random(500%) => 238%`
|
||||
>
|
||||
> * `math.random(123) => 87`
|
||||
> * `math.random(123px) => 43px`
|
||||
> * `math.random(500%) => 238%`
|
||||
|
||||
* Otherwise throw an error.
|
||||
|
||||
|
@ -55,7 +55,7 @@ representation of a CSS selector.
|
||||
|
||||
* Append a comma to `text` unless `complex` is the last element of
|
||||
`selector`.
|
||||
|
||||
|
||||
* Otherwise, if `selector` is not a string, throw an error.
|
||||
|
||||
* Otherwise, set `text` to the contents of `selector`.
|
||||
@ -135,8 +135,6 @@ This function is also available as a global function named `selector-replace()`.
|
||||
|
||||
* If any of `selector`, `original`, or `replacement` is [bogus], throw an error.
|
||||
|
||||
[bogus]: ../selectors.md#bogus-selector
|
||||
|
||||
* > Additional semantics have not yet been explicitly written.
|
||||
|
||||
### `simple-selectors()`
|
||||
|
@ -18,7 +18,6 @@ This built-in module is available from the URL `sass:string`.
|
||||
|
||||
## Functions
|
||||
|
||||
|
||||
### `index()`
|
||||
|
||||
```
|
||||
@ -73,12 +72,12 @@ split($string, $separator, $limit: null)
|
||||
|
||||
* If `$limit` is less than 1, throw an error.
|
||||
|
||||
* If `$string` is an empty string, return a list with `$string` as the only
|
||||
* If `$string` is an empty string, return a list with `$string` as the only
|
||||
item.
|
||||
|
||||
* Let `split-list` be an empty list.
|
||||
|
||||
* If `$limit` is `null`, set `$limit` to the value of calling
|
||||
* If `$limit` is `null`, set `$limit` to the value of calling
|
||||
`string.length($string)`.
|
||||
|
||||
* Let `split-counter` equal 0.
|
||||
@ -89,7 +88,7 @@ split($string, $separator, $limit: null)
|
||||
|
||||
* Append `$string` to `split-list`.
|
||||
|
||||
* Set `$string` to an empty string.
|
||||
* Set `$string` to an empty string.
|
||||
|
||||
* Otherwise:
|
||||
|
||||
@ -105,10 +104,10 @@ split($string, $separator, $limit: null)
|
||||
|
||||
* Otherwise:
|
||||
|
||||
* Let `index` be the result of calling
|
||||
* Let `index` be the result of calling
|
||||
`string.index($string, $separator)`.
|
||||
|
||||
* If `index` is `null`, append `$string` to `split-list` and set `$string`
|
||||
* If `index` is `null`, append `$string` to `split-list` and set `$string`
|
||||
to an empty string.
|
||||
|
||||
* Otherwise:
|
||||
@ -117,12 +116,12 @@ split($string, $separator, $limit: null)
|
||||
`string.slice($string, 1, index - 1)`.
|
||||
|
||||
* Append `current-substring` to `split-list`.
|
||||
|
||||
* Set `$string` to
|
||||
|
||||
* Set `$string` to
|
||||
`string.slice($string, index + string.length($separator))`.
|
||||
|
||||
* Increase `split-counter` by 1.
|
||||
|
||||
|
||||
* Return `split-list` as a bracketed, comma-separated list.
|
||||
|
||||
### `to-lower-case()`
|
||||
@ -156,4 +155,3 @@ unquote($string)
|
||||
```
|
||||
|
||||
This function is also available as a global function named `unquote()`.
|
||||
|
||||
|
@ -5,7 +5,7 @@ a Sass implementation and a host environment. It allows the host environment to
|
||||
invoke the Sass compiler on source files, and to define custom functions and
|
||||
importers in the host language.
|
||||
|
||||
Sass implementations are _not_ required to support the embedded protocol.
|
||||
Sass implementations are *not* required to support the embedded protocol.
|
||||
However, if they do, they must adhere to the specification given in this file
|
||||
and [`embedded_sass.proto`] for the compiler endpoint.
|
||||
|
||||
@ -60,12 +60,12 @@ its standard input and output streams.
|
||||
|
||||
### Packet Structure
|
||||
|
||||
Each message in the embedded protocol is sent as a _packet_ which contains two
|
||||
Each message in the embedded protocol is sent as a *packet* which contains two
|
||||
values: an unsigned [varint] up to 32 bits long known as the "compilation ID",
|
||||
and a protocol buffer that contains the protobuf message. For streams (like
|
||||
standard input and output) that don't have built-in message boundaries, every
|
||||
packet must begin with another unsigned varint indicating the length in bytes of
|
||||
the remaining message (_including the compilation ID_). This matches the best
|
||||
the remaining message (*including the compilation ID*). This matches the best
|
||||
practice described in [the protocol buffer documentation].
|
||||
|
||||
Because JavaScript can't easily represent integers larger than 2^53 - 1, the
|
||||
@ -319,7 +319,7 @@ tell if a value is "truthy" (one of those values) or "falsey" (`false` or
|
||||
`null`). It should encourage users to check this rather than directly testing
|
||||
for `true` or `false`.
|
||||
|
||||
Two booleans are equal if they're both `true` or both` false`.
|
||||
Two booleans are equal if they're both `true` or both `false`.
|
||||
|
||||
### Null
|
||||
|
||||
|
@ -2,6 +2,12 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
// This protocol buffer uses optional fields, which requires either [buf] or a
|
||||
// [protoc] version 3.15 or later.
|
||||
//
|
||||
// [buf]: https://buf.build/
|
||||
// [protoc]: https://github.com/protocolbuffers/protobuf#protobuf-compiler-installation
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package sass.embedded_protocol;
|
||||
@ -93,6 +99,14 @@ message InboundMessage {
|
||||
// registered for this compilation.
|
||||
uint32 file_importer_id = 3;
|
||||
}
|
||||
|
||||
// The set of URL schemes that are considered *non-canonical* for this
|
||||
// importer. This must be empty unless `importer.importer_id` is set.
|
||||
//
|
||||
// If any element of this contains a character other than a lowercase
|
||||
// ASCII letter, an ASCII numeral, U+002B (`+`), U+002D (`-`), or U+002E
|
||||
// (`.`), the compiler must treat the compilation as failed.
|
||||
repeated string non_canonical_scheme = 4;
|
||||
}
|
||||
|
||||
// Importers (including load paths on the filesystem) to use when resolving
|
||||
@ -147,7 +161,9 @@ message InboundMessage {
|
||||
// The successfully canonicalized URL.
|
||||
//
|
||||
// If this is not an absolute URL (including scheme), the compiler must
|
||||
// treat that as an error thrown by the importer.
|
||||
// treat that as an error thrown by the importer. If this URL's scheme is
|
||||
// an `Importer.non_canonical_scheme` for the importer being invoked, the
|
||||
// compiler must treat that as an error thrown by the importer.
|
||||
string url = 2;
|
||||
|
||||
// An error message explaining why canonicalization failed.
|
||||
@ -442,15 +458,27 @@ message OutboundMessage {
|
||||
// That is the result of the import.
|
||||
string url = 4;
|
||||
|
||||
/// Whether this request comes from an `@import` rule.
|
||||
///
|
||||
/// When evaluating `@import` rules, URLs should canonicalize to an
|
||||
/// [import-only file] if one exists for the URL being canonicalized.
|
||||
/// Otherwise, canonicalization should be identical for `@import` and `@use`
|
||||
/// rules.
|
||||
///
|
||||
/// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
|
||||
// Whether this request comes from an `@import` rule.
|
||||
//
|
||||
// When evaluating `@import` rules, URLs should canonicalize to an
|
||||
// [import-only file] if one exists for the URL being canonicalized.
|
||||
// Otherwise, canonicalization should be identical for `@import` and `@use`
|
||||
// rules.
|
||||
//
|
||||
// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
|
||||
bool from_import = 5;
|
||||
|
||||
// The canonical URL of the [current source file] that contained the load
|
||||
// to be canonicalized.
|
||||
//
|
||||
// [current source file]: ../spec.md#current-source-file
|
||||
//
|
||||
// The compiler must set this if and only if `url` is relative or its
|
||||
// scheme is an `Importer.non_canonical_scheme` for the importer being
|
||||
// invoked, unless the current source file has no canonical URL.
|
||||
//
|
||||
// [non-canonical-proto]: #non_canonical_scheme
|
||||
optional string containing_url = 6;
|
||||
}
|
||||
|
||||
// A request for a custom importer to load the contents of a stylesheet.
|
||||
@ -486,8 +514,13 @@ message OutboundMessage {
|
||||
// * Let `fromImport` be `true` if the importer is being run for an
|
||||
// `@import` and `false` otherwise.
|
||||
//
|
||||
// * Let `containingUrl` be the canonical URL of the [current source file]
|
||||
// if it has one, or undefined otherwise.
|
||||
//
|
||||
//
|
||||
// * Let `response` be the result of sending a `FileImportRequest` with
|
||||
// `string` as its `url` and `fromImport` as `from_import`.
|
||||
// `string` as its `url`, `fromImport` as `from_import`, and
|
||||
// `containingUrl` as `containing_url`.
|
||||
//
|
||||
// * If `response.result` is null, return null.
|
||||
//
|
||||
@ -511,6 +544,7 @@ message OutboundMessage {
|
||||
//
|
||||
// * Return `text`, `syntax`, and `resolved`.
|
||||
//
|
||||
// [current source file]: ../spec.md#current-source-file
|
||||
// [resolving `url`]: https://github.com/sass/sass/tree/main/spec/modules.md#resolving-a-file-url
|
||||
message FileImportRequest {
|
||||
reserved 2;
|
||||
@ -525,15 +559,20 @@ message OutboundMessage {
|
||||
// The (non-canonicalized) URL of the import.
|
||||
string url = 4;
|
||||
|
||||
/// Whether this request comes from an `@import` rule.
|
||||
///
|
||||
/// When evaluating `@import` rules, filesystem importers should load an
|
||||
/// [import-only file] if one exists for the URL being canonicalized.
|
||||
/// Otherwise, canonicalization should be identical for `@import` and `@use`
|
||||
/// rules.
|
||||
///
|
||||
/// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
|
||||
// Whether this request comes from an `@import` rule.
|
||||
//
|
||||
// When evaluating `@import` rules, filesystem importers should load an
|
||||
// [import-only file] if one exists for the URL being canonicalized.
|
||||
// Otherwise, canonicalization should be identical for `@import` and `@use`
|
||||
// rules.
|
||||
//
|
||||
// [import-only file]: https://sass-lang.com/documentation/at-rules/import#import-only-files
|
||||
bool from_import = 5;
|
||||
|
||||
// The canonical URL of the [current source file] that contained the load
|
||||
// being resolved. The compiler must set this unless the current source file
|
||||
// has no canonical URL.
|
||||
optional string containing_url = 6;
|
||||
}
|
||||
|
||||
// A request to invoke a custom Sass function and return its result.
|
||||
@ -848,6 +887,17 @@ message Value {
|
||||
string signature = 2;
|
||||
}
|
||||
|
||||
// A first-class mixin defined in the compiler. New `CompilerMixin`s may
|
||||
// only be created by the compiler, but the host may pass `CompilerMixin`s
|
||||
// back to the compiler as long as their IDs match IDs of mixins received
|
||||
// by the host during that same compilation.
|
||||
message CompilerMixin {
|
||||
// A unique ID for this mixin. The compiler is responsible for generating
|
||||
// this ID and ensuring it's unique across all mixins passed to the host
|
||||
// for this compilation. Mandatory.
|
||||
uint32 id = 1;
|
||||
}
|
||||
|
||||
// A SassScript argument list value. This represents rest arguments passed to
|
||||
// a function's `$arg...` parameter. Unlike a normal `List`, an argument list
|
||||
// has an associated keywords map which tracks keyword arguments passed in
|
||||
@ -919,6 +969,11 @@ message Value {
|
||||
|
||||
// An unquoted string as created by interpolation for
|
||||
// backwards-compatibility with older Sass syntax.
|
||||
//
|
||||
// The compiler must treat this as identical to a `string` option whose
|
||||
// value is `"(" + interpolation + ")"`.
|
||||
//
|
||||
// This field is deprecated and hosts should avoid using it.
|
||||
string interpolation = 3;
|
||||
|
||||
CalculationOperation operation = 4;
|
||||
@ -956,6 +1011,7 @@ message Value {
|
||||
ArgumentList argument_list = 10;
|
||||
HwbColor hwb_color = 11;
|
||||
Calculation calculation = 12;
|
||||
CompilerMixin compiler_mixin = 13;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
* [Special Variable String](#special-variable-string)
|
||||
* [Syntax](#syntax)
|
||||
* [Semantics](#semantics)
|
||||
* [`EmptyFallbackVar`:](#emptyfallbackvar)
|
||||
* [`EmptyFallbackVar`](#emptyfallbackvar)
|
||||
* [`FunctionCall`](#functioncall)
|
||||
* [Global Functions](#global-functions)
|
||||
* [`adjust-hue()`](#adjust-hue)
|
||||
@ -47,18 +47,14 @@ matching is case-insensitive.
|
||||
## Syntax
|
||||
|
||||
<x><pre>
|
||||
**FunctionExpression**¹ ::= [CssMinMax]
|
||||
  | [SpecialFunctionExpression]
|
||||
  | [CalculationExpression]
|
||||
**FunctionExpression**¹ ::= [SpecialFunctionExpression]
|
||||
  | EmptyFallbackVar
|
||||
  | FunctionCall
|
||||
**EmptyFallbackVar**² ::= 'var(' Expression ',' ')'
|
||||
**FunctionCall**⁴ ::= [NamespacedIdentifier] ArgumentInvocation
|
||||
</pre></x>
|
||||
|
||||
[CssMinMax]: types/calculation.md#cssminmax
|
||||
[SpecialFunctionExpression]: syntax.md#specialfunctionexpression
|
||||
[CalculationExpression]: types/calculation.md#calculationexpression
|
||||
[NamespacedIdentifier]: modules.md#syntax
|
||||
|
||||
1: Both `CssMinMax` and `EmptyFallbackVar` take precedence over `FunctionCall`
|
||||
@ -76,14 +72,12 @@ matching is case-insensitive.
|
||||
**FunctionCall** ::= [NamespacedIdentifier][] ArgumentInvocation
|
||||
</pre></x>
|
||||
|
||||
[NamespacedIdentifier]: modules.md#syntax
|
||||
|
||||
No whitespace is allowed between the `NamespacedIdentifier` and the
|
||||
`ArgumentInvocation` in `FunctionCall`.
|
||||
|
||||
## Semantics
|
||||
|
||||
### `EmptyFallbackVar`:
|
||||
### `EmptyFallbackVar`
|
||||
|
||||
To evaluate an `EmptyFallbackVar` `call`:
|
||||
|
||||
@ -109,6 +103,25 @@ To evaluate a `FunctionCall` `call`:
|
||||
|
||||
* If `function` is null and `name` is not a plain `Identifier`, throw an error.
|
||||
|
||||
* If `function` is null; `name` is case-insensitively equal to `"min"`, `"max"`,
|
||||
`"round"`, or `"abs"`; `call`'s `ArgumentInvocation` doesn't have any
|
||||
`KeywordArgument`s or `RestArgument`s; and all arguments in `call`'s
|
||||
`ArgumentInvocation` are [calculation-safe], return the result of evaluating
|
||||
`call` [as a calculation].
|
||||
|
||||
[calculation-safe]: types/calculation.md#calculation-safe-expression
|
||||
[as a calculation]: types/calculation.md#evaluating-a-functioncall-as-a-calculation
|
||||
|
||||
> For calculation functions that overlap with global Sass function names, we
|
||||
> want anything Sass-specific like this to end up calling the Sass function.
|
||||
> For all other calculation functions, we want those constructs to throw an
|
||||
> error (which they do when evaluating `call` [as a calculation]).
|
||||
|
||||
* If `function` is null and `name` is case-insensitively equal to `"calc"`,
|
||||
`"clamp"`, `"hypot"`, `"sin"`, `"cos"`, `"tan"`, `"asin"`, `"acos"`, `"atan"`,
|
||||
`"sqrt"`, `"exp"`, `"sign"`, `"mod"`, `"rem"`, `"atan2"`, `"pow"`, or `"log"`,
|
||||
return the result of evaluating `call` [as a calculation].
|
||||
|
||||
* If `function` is null, set it to the [global function](#global-functions)
|
||||
named `name`.
|
||||
|
||||
@ -259,6 +272,10 @@ plain CSS function named `"rgb"` that function is named `"rgba"` instead.
|
||||
|
||||
* If `rgb` is not an unbracketed space-separated list, throw an error.
|
||||
|
||||
* If the first element of `rgb` is an unquoted string which is
|
||||
case-insensitively equal to `from`, return a plain CSS function string
|
||||
with the name `"rgb"` and the argument `$channels`.
|
||||
|
||||
* If `rgb` has more than three elements, throw an error.
|
||||
|
||||
* If `rgb` has fewer than three elements:
|
||||
@ -276,6 +293,10 @@ plain CSS function named `"rgb"` that function is named `"rgba"` instead.
|
||||
|
||||
* If `$channels` is not an unbracketed space-separated list, throw an error.
|
||||
|
||||
* If the first element of `$channels` is an unquoted string which is
|
||||
case-insensitively equal to `from`, return a plain CSS function string
|
||||
with the name `"rgb"` and the argument `$channels`.
|
||||
|
||||
* If `$channels` has more than three elements, throw an error.
|
||||
|
||||
* If `$channels` has fewer than three elements:
|
||||
@ -385,6 +406,10 @@ plain CSS function named `"hsl"` that function is named `"hsla"` instead.
|
||||
|
||||
* If `hsl` is not an unbracketed space-separated list, throw an error.
|
||||
|
||||
* If the first element of `hsl` is an unquoted string which is
|
||||
case-insensitively equal to `from`, return a plain CSS function string
|
||||
with the name `"hsl"` and the argument `$channels`.
|
||||
|
||||
* If `hsl` has more than three elements, throw an error.
|
||||
|
||||
* If `hsl` has fewer than three elements:
|
||||
@ -402,6 +427,10 @@ plain CSS function named `"hsl"` that function is named `"hsla"` instead.
|
||||
|
||||
* If `$channels` is not an unbracketed space-separated list, throw an error.
|
||||
|
||||
* If the first element of `$channels` is an unquoted string which is
|
||||
case-insensitively equal to `from`, return a plain CSS function string
|
||||
with the name `"hsl"` and the argument `$channels`.
|
||||
|
||||
* If `$channels` has more than three elements, throw an error.
|
||||
|
||||
* If `$channels` has fewer than three elements:
|
||||
@ -436,8 +465,6 @@ plain CSS function named `"hsl"` that function is named `"hsla"` instead.
|
||||
* Call `hsl()` with `hue`, `saturation`, `lightness`, and `alpha` (if it's
|
||||
defined) as arguments and return the result.
|
||||
|
||||
[special variable string]: #special-variable-string
|
||||
|
||||
### `if()`
|
||||
|
||||
```
|
||||
|
@ -125,8 +125,6 @@ Compiles the Sass `source`:
|
||||
> The structure of the sourceMap can vary from implementation to
|
||||
> implementation.
|
||||
|
||||
[loaded]: ../modules.md#loading-a-source-file
|
||||
|
||||
* If the compilation fails, throw an `Exception`.
|
||||
|
||||
```ts
|
||||
|
@ -11,12 +11,27 @@ import {PromiseOr} from './util/promise_or';
|
||||
## Table of Contents
|
||||
|
||||
* [Types](#types)
|
||||
* [`CanonicalizeContext`](#canonicalizecontext)
|
||||
* [`FileImporter`](#fileimporter)
|
||||
* [`Importer`](#importer)
|
||||
* [`nonCanonicalScheme`](#noncanonicalscheme)
|
||||
* [`ImporterResult`](#importerresult)
|
||||
|
||||
## Types
|
||||
|
||||
### `CanonicalizeContext`
|
||||
|
||||
This is a data object passed into calls to `Importer.canonicalize()` and
|
||||
`FileImporter.findFileUrl()`. Its fields are set as part of the function
|
||||
invocations.
|
||||
|
||||
```ts
|
||||
export interface CanonicalizeContext {
|
||||
fromImport: boolean;
|
||||
containingUrl: URL | null;
|
||||
}
|
||||
```
|
||||
|
||||
### `FileImporter`
|
||||
|
||||
This interface represents an [importer]. When the importer is invoked with a
|
||||
@ -33,14 +48,19 @@ string `string`:
|
||||
* Let `fromImport` be `true` if the importer is being run for an `@import` and
|
||||
`false` otherwise.
|
||||
|
||||
* Let `url` be the result of calling `findFileUrl` with `string` and
|
||||
`fromImport`. If it returns a promise, wait for it to complete and use its
|
||||
value instead, or rethrow its error if it rejects.
|
||||
* Let `containingUrl` be the canonical URL of the [current source file] if it
|
||||
has one, or undefined otherwise.
|
||||
|
||||
* Let `url` be the result of calling `findFileUrl` with `string`, `fromImport`,
|
||||
and `containingUrl`. If it returns a promise, wait for it to complete and use
|
||||
its value instead, or rethrow its error if it rejects.
|
||||
|
||||
* If `url` is null, return null.
|
||||
|
||||
* If `url`'s scheme is not `file`, throw an error.
|
||||
|
||||
[current source file]: ../spec.md#current-source-file
|
||||
|
||||
* Let `resolved` be the result of [resolving `url`].
|
||||
|
||||
[resolving `url`]: ../modules.md#resolving-a-file-url
|
||||
@ -65,7 +85,7 @@ export interface FileImporter<
|
||||
> {
|
||||
findFileUrl(
|
||||
url: string,
|
||||
options: {fromImport: boolean}
|
||||
context: CanonicalizeContext
|
||||
): PromiseOr<URL | null, sync>;
|
||||
|
||||
canonicalize?: never;
|
||||
@ -80,9 +100,18 @@ string `string`:
|
||||
* Let `fromImport` be `true` if the importer is being run for an `@import` and
|
||||
`false` otherwise.
|
||||
|
||||
* Let `url` be the result of calling `canonicalize` with `url` and `fromImport`.
|
||||
If it returns a promise, wait for it to complete and use its value instead, or
|
||||
rethrow its error if it rejects.
|
||||
* If `string` is a relative URL, or if it's an absolute URL whose scheme is
|
||||
non-canonical for this importer, let `containingUrl` be the canonical URL of
|
||||
the [current source file]. Otherwise, or if the current source file has no
|
||||
canonical URL, let `containingUrl` be undefined.
|
||||
|
||||
* Let `url` be the result of calling `canonicalize` with `string`, `fromImport`,
|
||||
and `containingUrl`. If it returns a promise, wait for it to complete and use
|
||||
its value instead, or rethrow its error if it rejects.
|
||||
|
||||
* If the scheme of `url` is [non-canonical] for this importer, throw an error.
|
||||
|
||||
[non-canonical]: #noncanonicalscheme
|
||||
|
||||
* If `url` is null, return null.
|
||||
|
||||
@ -104,13 +133,32 @@ string `string`:
|
||||
export interface Importer<sync extends 'sync' | 'async' = 'sync' | 'async'> {
|
||||
canonicalize(
|
||||
url: string,
|
||||
options: {fromImport: boolean}
|
||||
context: CanonicalizeContext
|
||||
): PromiseOr<URL | null, sync>;
|
||||
|
||||
load(canonicalUrl: URL): PromiseOr<ImporterResult | null, sync>;
|
||||
|
||||
findFileUrl?: never;
|
||||
}
|
||||
```
|
||||
|
||||
#### `nonCanonicalScheme`
|
||||
|
||||
The set of URL schemes that are considered *non-canonical* for this importer. If
|
||||
this is a single string, treat it as a list containing that string.
|
||||
|
||||
Before beginning compilation, throw an error if any element of this is empty or
|
||||
contains a character other than a lowercase ASCII letter, an ASCII numeral,
|
||||
U+002B (`+`), U+002D (`-`), or U+002E (`.`).
|
||||
|
||||
> Uppercase letters are normalized to lowercase in the `URL` constructor, so for
|
||||
> simplicity and efficiency we only allow lowercase here.
|
||||
|
||||
```ts
|
||||
nonCanonicalScheme?: string | string[];
|
||||
```
|
||||
|
||||
```ts
|
||||
} // Importer
|
||||
```
|
||||
|
||||
### `ImporterResult`
|
||||
|
@ -47,7 +47,12 @@ export {
|
||||
compileStringAsync,
|
||||
} from './compile';
|
||||
export {Exception} from './exception';
|
||||
export {FileImporter, Importer, ImporterResult} from './importer';
|
||||
export {
|
||||
CanonicalizeContext,
|
||||
FileImporter,
|
||||
Importer,
|
||||
ImporterResult,
|
||||
} from './importer';
|
||||
export {Logger, SourceSpan, SourceLocation} from './logger';
|
||||
export {
|
||||
CustomFunction,
|
||||
@ -72,6 +77,7 @@ export {
|
||||
SassFunction,
|
||||
SassList,
|
||||
SassMap,
|
||||
SassMixin,
|
||||
SassNumber,
|
||||
SassString,
|
||||
Value,
|
||||
|
@ -135,7 +135,6 @@ charset?: boolean;
|
||||
|
||||
#### `quietDeps`
|
||||
|
||||
|
||||
If true, the compiler must not print deprecation warnings for stylesheets that
|
||||
are transitively loaded through an import path.
|
||||
|
||||
@ -162,7 +161,6 @@ verbose?: boolean;
|
||||
|
||||
#### `logger`
|
||||
|
||||
|
||||
A [custom logger] that provides callbacks for the compiler to use in lieu of its
|
||||
default messaging behavior.
|
||||
|
||||
|
@ -17,7 +17,6 @@ export {SourceSpan} from './source_span';
|
||||
* [`Logger`](#logger-1)
|
||||
* [`silent`](#silent)
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
### `Logger`
|
||||
|
@ -312,7 +312,7 @@ url?: URL;
|
||||
### `StringOptionsWithImporter`
|
||||
|
||||
> This interface is used for calls to [`compileString()`] and
|
||||
> [`compileStringAsync()`] that _do_ pass the `importer` parameter, and so _do_
|
||||
> [`compileStringAsync()`] that *do* pass the `importer` parameter, and so *do*
|
||||
> support relative imports.
|
||||
|
||||
```ts
|
||||
@ -334,7 +334,7 @@ importer: Importer<sync> | FileImporter<sync>;
|
||||
|
||||
The canonical URL of the entrypoint.
|
||||
|
||||
> This _must_ be passed when `importer` is passed, since otherwise there's
|
||||
> This *must* be passed when `importer` is passed, since otherwise there's
|
||||
> nothing to resolve relative URLs relative to.
|
||||
|
||||
```ts
|
||||
|
@ -161,8 +161,6 @@ export type CalculationOperator = '+' | '-' | '*' | '/';
|
||||
|
||||
The JS API representation of a Sass [`CalculationOperation`].
|
||||
|
||||
[CalculationOperation]: ../../types/calculation.md#types
|
||||
|
||||
```ts
|
||||
export class CalculationOperation implements ValueObject {
|
||||
```
|
||||
@ -239,9 +237,15 @@ hashCode(): number;
|
||||
|
||||
### `CalculationInterpolation`
|
||||
|
||||
The JS API representation of a Sass [`CalculationInterpolation`].
|
||||
A deprecated alternative JS API representation of an unquoted Sass string that's
|
||||
always surrounded by parentheses. It's never returned by the Sass compiler, but
|
||||
for backwards-compatibility users may still construct it and pass it to the Sass
|
||||
compiler.
|
||||
|
||||
[`CalculationInterpolation`]: ../../types/calculation.md#types
|
||||
> `CalculationInterpolation`s are no longer generated by the Sass compiler,
|
||||
> because it can now tell at evaluation time whether an interpolation was
|
||||
> originally surrounded by parentheses. However, until we make a breaking
|
||||
> revision of the JS API, users may continue to pass `CalculationInterpolation`s
|
||||
|
||||
```ts
|
||||
export class CalculationInterpolation implements ValueObject {
|
||||
@ -249,13 +253,12 @@ export class CalculationInterpolation implements ValueObject {
|
||||
|
||||
#### `internal`
|
||||
|
||||
A private property like [`Value.internal`] that refers to a Sass
|
||||
[`CalculationInterpolation`].
|
||||
A private property like [`Value.internal`] that refers to a Sass string.
|
||||
|
||||
#### Constructor
|
||||
|
||||
Creates a Sass [`CalculationInterpolation`] by setting the `value` field to the
|
||||
`value` argument and returns it.
|
||||
Creates a `CalculationInterpolation` with `internal` set to an unquoted Sass
|
||||
string with text `"(" + value + ")"` and returns it.
|
||||
|
||||
```ts
|
||||
constructor(value: string);
|
||||
@ -263,9 +266,10 @@ constructor(value: string);
|
||||
|
||||
#### `value`
|
||||
|
||||
Returns [`internal`][ci-internal]'s `value` field.
|
||||
Returns [`internal`][ci-internal]'s `value` field's text, without the leading
|
||||
and trailing parentheses.
|
||||
|
||||
[ci-internal]: #internal-2
|
||||
[ci-internal]: #internal-1
|
||||
|
||||
```ts
|
||||
get value(): string;
|
||||
@ -273,7 +277,8 @@ get value(): string;
|
||||
|
||||
#### `equals`
|
||||
|
||||
Whether [`internal`][ci-internal] is equal to `other.internal` in Sass.
|
||||
Whether `other` is a `CalculationInterpolation` and [`internal`][ci-internal] is
|
||||
equal to `other.internal` in Sass.
|
||||
|
||||
```ts
|
||||
equals(other: unknown): boolean;
|
||||
|
@ -9,6 +9,7 @@ import {SassColor} from './color';
|
||||
import {SassFunction} from './function';
|
||||
import {ListSeparator} from './list';
|
||||
import {SassMap} from './map';
|
||||
import {SassMixin} from './mixin';
|
||||
import {SassNumber} from './number';
|
||||
import {SassString} from './string';
|
||||
|
||||
@ -19,12 +20,13 @@ export {
|
||||
CalculationValue,
|
||||
CalculationOperator,
|
||||
CalculationOperation,
|
||||
CalculationInterpolation
|
||||
CalculationInterpolation,
|
||||
} from './calculation';
|
||||
export {SassColor} from './color';
|
||||
export {SassFunction} from './function';
|
||||
export {SassList, ListSeparator} from './list';
|
||||
export {SassMap} from './map';
|
||||
export {SassMixin} from './mixin';
|
||||
export {SassNumber} from './number';
|
||||
export {SassString} from './string';
|
||||
```
|
||||
@ -48,6 +50,7 @@ export {SassString} from './string';
|
||||
* [`assertColor`](#assertcolor)
|
||||
* [`assertFunction`](#assertfunction)
|
||||
* [`assertMap`](#assertmap)
|
||||
* [`assertMixin`](#assertmixin)
|
||||
* [`assertNumber`](#assertnumber)
|
||||
* [`assertString`](#assertstring)
|
||||
* [`tryMap`](#trymap)
|
||||
@ -141,16 +144,16 @@ get separator(): ListSeparator;
|
||||
Converts the Sass index `sassIndex` to a JS index into the array returned by
|
||||
`asList`:
|
||||
|
||||
- If `sassIndex` is not a unitless Sass number, throw an error.
|
||||
* If `sassIndex` is not a unitless Sass number, throw an error.
|
||||
|
||||
- Let `value` be the value of `sassIndex`. Let `index` be the result of
|
||||
* Let `value` be the value of `sassIndex`. Let `index` be the result of
|
||||
`fuzzyAsInt(value)`. If `index === null`, throw an error.
|
||||
|
||||
- If `index === 0`, or the absolute value of `index` is greater than
|
||||
* If `index === 0`, or the absolute value of `index` is greater than
|
||||
`asList.length`, throw an error.
|
||||
|
||||
- If `index > 0`, return `index - 1`.
|
||||
- Otherwise, if `index < 0`, return `asList.length + index`.
|
||||
* If `index > 0`, return `index - 1`.
|
||||
* Otherwise, if `index < 0`, return `asList.length + index`.
|
||||
|
||||
> Sass indices start counting at 1, and may be negative in order to index from
|
||||
> the end of the list.
|
||||
@ -231,6 +234,18 @@ Return `this.tryMap()` if it's not null, and throw an error otherwise.
|
||||
assertMap(name?: string): SassMap;
|
||||
```
|
||||
|
||||
#### `assertMixin`
|
||||
|
||||
Returns `this` if it's a [`SassMixin`] and throws an error otherwise.
|
||||
|
||||
[`SassMixin`]: mixin.d.ts.md
|
||||
|
||||
> The `name` parameter may be used for error reporting.
|
||||
|
||||
```ts
|
||||
assertMixin(name?: string): SassMixin;
|
||||
```
|
||||
|
||||
#### `assertNumber`
|
||||
|
||||
Returns `this` if it's a [`SassNumber`] and throws an error otherwise.
|
||||
@ -285,7 +300,7 @@ Returns the same number for any two `Value`s that are equal according to
|
||||
|
||||
[`equals`]: #equals
|
||||
|
||||
> This is _not_ required to be different for different values, although having
|
||||
> This is *not* required to be different for different values, although having
|
||||
> overlap between common values is likely to cause performance issues.
|
||||
|
||||
```ts
|
||||
|
@ -33,7 +33,6 @@ The [private `internal` field] refers to a Sass map.
|
||||
|
||||
[private `internal` field]: index.d.ts.md#internal
|
||||
|
||||
|
||||
#### Constructor
|
||||
|
||||
Creates a Sass map:
|
||||
|
40
spec/js-api/value/mixin.d.ts.md
Normal file
40
spec/js-api/value/mixin.d.ts.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Mixin API
|
||||
|
||||
```ts
|
||||
import {Value} from './index';
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Types](#types)
|
||||
* [`SassMixin`](#sassmixin)
|
||||
* [`internal`](#internal)
|
||||
* [Constructor](#constructor)
|
||||
|
||||
## Types
|
||||
|
||||
### `SassMixin`
|
||||
|
||||
The JS API representation of a Sass mixin.
|
||||
|
||||
```ts
|
||||
export class SassMixin extends Value {
|
||||
```
|
||||
|
||||
#### `internal`
|
||||
|
||||
The [private `internal` field] refers to a Sass mixin.
|
||||
|
||||
[private `internal` field]: index.d.ts.md#internal
|
||||
|
||||
#### Constructor
|
||||
|
||||
Throws an error.
|
||||
|
||||
```ts
|
||||
constructor();
|
||||
```
|
||||
|
||||
```ts
|
||||
} // SassMixin
|
||||
```
|
@ -291,7 +291,7 @@ by `newNumerators` and `newDenominators`:
|
||||
* If `newNumerators` and `newDenominators` are both empty, return the result of
|
||||
`new SassNumber(this.value)`.
|
||||
|
||||
* If `internal` is unitless, return the result of:
|
||||
* If `internal` is [unitless], return the result of:
|
||||
|
||||
[unitless]: ../../types/number.md#
|
||||
|
||||
|
@ -28,7 +28,7 @@ combinator*) as well as a sequence of [complex selector components]. The
|
||||
component sequence may be empty only for complex selectors with leading
|
||||
combinators.
|
||||
|
||||
[visible combinator]: #visible-combinator
|
||||
[visible combinator]: #visible-combinator
|
||||
[complex selector components]: #complex-selector-component
|
||||
|
||||
### Complex Selector Component
|
||||
|
@ -102,6 +102,7 @@ a string.
|
||||
> exposes this to the user.
|
||||
|
||||
This algorithm takes:
|
||||
|
||||
* a string `string`,
|
||||
* a syntax `syntax` ("indented", "scss", or "css"),
|
||||
* an optional URL `url`,
|
||||
@ -147,8 +148,6 @@ It runs as follows:
|
||||
* Let `file` be the [source file][] with `ast`, canonical URL `url`, and
|
||||
importer `importer`.
|
||||
|
||||
[source file]: syntax.md#source-file
|
||||
|
||||
* Let `module` be the result of [executing](#executing-a-file) `file`.
|
||||
|
||||
* Let `css` be the result of [resolving `module`'s extensions][].
|
||||
@ -166,10 +165,6 @@ It runs as follows:
|
||||
This algorithm takes a [source file][] `file`, a [configuration][] `config`, an
|
||||
[import context][] `import`, and returns a [module][].
|
||||
|
||||
[module]: modules.md#module
|
||||
[configuration]: modules.md#configuration
|
||||
[import context]: modules.md#import-context
|
||||
|
||||
* Let `module` be an empty module with source file `file`.
|
||||
|
||||
* Let `uses` be an empty map from `@use` rules to modules.
|
||||
|
@ -106,7 +106,6 @@ No whitespace is allowed between components of an `InterpolatedUnquotedUrlConten
|
||||
|
||||
[\<declaration-value>]: https://www.w3.org/TR/css-syntax-3/#typedef-declaration-value
|
||||
[\<an+b>]: https://www.w3.org/TR/css-syntax-3/#the-anb-type
|
||||
[\<ident-token>]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram
|
||||
|
||||
1: The string `of` is matched case-insensitively. In addition, it must be parsed
|
||||
as an identifier.
|
||||
@ -295,7 +294,6 @@ This algorithm consumes input from a stream of [code points][] and returns a
|
||||
sequence of strings and/or expressions. It follows the grammar for an
|
||||
[`InterpolatedIdentifier`][].
|
||||
|
||||
[code points]: https://infra.spec.whatwg.org/#code-point
|
||||
[`InterpolatedIdentifier`]: #interpolatedidentifier
|
||||
|
||||
* Let `components` be an empty list of strings and/or expressions.
|
||||
|
@ -2,88 +2,48 @@
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Syntax](#syntax)
|
||||
* [`CalculationExpression`](#calculationexpression)
|
||||
* [`CssMinMax`](#cssminmax)
|
||||
* [Definitions](#definitions)
|
||||
* [Calculation-Safe Expression](#calculation-safe-expression)
|
||||
* [Types](#types)
|
||||
* [Operations](#operations)
|
||||
* [Equality](#equality)
|
||||
* [Serialization](#serialization)
|
||||
* [Calculation](#calculation)
|
||||
* [`CalculationOperation`](#calculationoperation)
|
||||
* [`CalculationInterpolation`](#calculationinterpolation)
|
||||
* [`Number`](#number)
|
||||
* [Procedures](#procedures)
|
||||
* [Evaluating a `FunctionCall` as a Calculation](#evaluating-a-functioncall-as-a-calculation)
|
||||
* [Evaluating an Expression as a Calculation Value](#evaluating-an-expression-as-a-calculation-value)
|
||||
* [Simplifying a Calculation](#simplifying-a-calculation)
|
||||
* [Simplifying a `CalculationValue`](#simplifying-a-calculationvalue)
|
||||
* [Semantics](#semantics)
|
||||
* [`CalcExpression`](#calcexpression)
|
||||
* [`ClampExpression`](#clampexpression)
|
||||
* [`CssMinMax`](#cssminmax-1)
|
||||
* [`CalcArgument`](#calcargument)
|
||||
* [`CalcSum`](#calcsum)
|
||||
* [`CalcProduct`](#calcproduct)
|
||||
* [`CalcValue`](#calcvalue)
|
||||
* [`ParenthesizedVar`](#parenthesizedvar)
|
||||
* [`FunctionExpression` and `Variable`](#functionexpression-and-variable)
|
||||
* [`SumExpression` and `ProductExpression`](#sumexpression-and-productexpression)
|
||||
* [`SpaceListExpression`](#spacelistexpression)
|
||||
* [`ParenthesizedExpression`](#parenthesizedexpression)
|
||||
* [`InterpolatedIdentifier`](#interpolatedidentifier)
|
||||
|
||||
## Syntax
|
||||
## Definitions
|
||||
|
||||
### `CalculationExpression`
|
||||
### Calculation-Safe Expression
|
||||
|
||||
This production is parsed in a SassScript context when an expression is expected
|
||||
and the input stream starts with an identifier with value `calc` or `clamp`
|
||||
(ignoring case) followed immediately by `(`.
|
||||
An expression is "calculation-safe" if it is one of:
|
||||
|
||||
The grammar for this production is:
|
||||
* A [`FunctionExpression`].
|
||||
* A `ParenthesizedExpression` whose contents is calculation-safe.
|
||||
* A `SumExpression` whose operands are calculation-safe.
|
||||
* A `ProductExpression` whose operator is `*` or `/` and whose operands are
|
||||
calculation-safe.
|
||||
* A `Number`.
|
||||
* A `Variable`.
|
||||
* An `InterpolatedIdentifier`.
|
||||
* An unbracketed `SpaceListExpression` with more than one element, whose
|
||||
elements are all calculation-safe.
|
||||
|
||||
<x><pre>
|
||||
**CalculationExpression** ::= CalcExpression | ClampExpression
|
||||
**CalcExpression** ::= 'calc('¹ CalcArgument ')'
|
||||
**ClampExpression** ::= 'clamp('¹ CalcArgument ( ',' CalcArgument ){2} ')'
|
||||
**CalcArgument**² ::= InterpolatedDeclarationValue† | CalcSum
|
||||
**CalcSum** ::= CalcProduct (('+' | '-')³ CalcProduct)\*
|
||||
**CalcProduct** ::= CalcValue (('\*' | '/') CalcValue)\*
|
||||
**CalcValue** ::= ParenthesizedVar
|
||||
  | '(' CalcArgument⁴ ')'
|
||||
  | CalculationExpression
|
||||
  | CssMinMax
|
||||
  | FunctionExpression⁵
|
||||
  | Number
|
||||
  | Variable†
|
||||
  | [\<ident-token>]
|
||||
**ParenthesizedVar** ::= '(' 'var('¹ ArgumentInvocation ')' ')'
|
||||
</pre></x>
|
||||
[`FunctionExpression`]: ../functions.md#syntax
|
||||
|
||||
[\<ident-token>]: https://drafts.csswg.org/css-syntax-3/#ident-token-diagram
|
||||
|
||||
1: The strings `calc(`, `clamp(`, and `var(` are matched case-insensitively.
|
||||
|
||||
2: A `CalcArgument` is only parsed as an `InterpolatedDeclarationValue` if it
|
||||
includes interpolation, unless that interpolation is within a region bounded by
|
||||
parentheses (a `FunctionExpression` counts as parentheses).
|
||||
|
||||
3: Whitespace is required around these `"+"` and `"-"` tokens.
|
||||
|
||||
4: This `CalcArgument` cannot begin with `var(`, case-insensitively.
|
||||
|
||||
5: This `FunctionExpression` cannot begin with `min(`, `max(`, or `clamp(`,
|
||||
case-insensitively.
|
||||
|
||||
†: These productions are invalid in plain CSS syntax.
|
||||
|
||||
> The `CalcArgument` production provides backwards-compatibility with the
|
||||
> historical use of interpolation to inject SassScript values into `calc()`
|
||||
> expressions. Because interpolation could inject any part of a `calc()`
|
||||
> expression regardless of syntax, for full compatibility it's necessary to
|
||||
> parse it very expansively.
|
||||
|
||||
### `CssMinMax`
|
||||
|
||||
<x><pre>
|
||||
**CssMinMax** ::= ('min(' | 'max(')¹ CalcArgument (',' CalcArgument)* ')'
|
||||
</pre></x>
|
||||
|
||||
1: The strings `min(` and `max(` are matched case-insensitively.
|
||||
> Because calculations have special syntax in CSS, only a subset of SassScript
|
||||
> expressions are valid (and these are interpreted differently than elsewhere).
|
||||
|
||||
## Types
|
||||
|
||||
@ -98,14 +58,9 @@ interface Calculation {
|
||||
type CalculationValue =
|
||||
| Number
|
||||
| UnquotedString
|
||||
| CalculationInterpolation
|
||||
| CalculationOperation
|
||||
| Calculation;
|
||||
|
||||
interface CalculationInterpolation {
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface CalculationOperation {
|
||||
operator: '+' | '-' | '*' | '/';
|
||||
left: CalculationValue;
|
||||
@ -136,8 +91,8 @@ Two calculations are considered equal if their names are equal, they have the
|
||||
same number of arguments, and each argument in one calculation is equal to the
|
||||
corresponding argument in the other.
|
||||
|
||||
`CalculationOperation` and `CalculationInterpolation` values are equal if each
|
||||
field in one value is equal to the corresponding field in the other.
|
||||
`CalculationOperation` values are equal if each field in one value is equal to
|
||||
the corresponding field in the other.
|
||||
|
||||
### Serialization
|
||||
|
||||
@ -153,19 +108,14 @@ To serialize a `CalculationOperation`:
|
||||
* Let `left` and `right` be the result of serializing the left and right values,
|
||||
respectively.
|
||||
|
||||
* If either:
|
||||
|
||||
* the left value is a `CalculationInterpolation`, or
|
||||
* the operator is `"*"` or `"/"` and the left value is a
|
||||
`CalculationOperation` with operator `"+"` or `"-"`,
|
||||
|
||||
emit `"("` followed by `left` followed by `")"`. Otherwise, emit `left`.
|
||||
* If the operator is `"*"` or `"/"` and the left value is a
|
||||
`CalculationOperation` with operator `"+"` or `"-"`, emit `"("` followed by
|
||||
`left` followed by `")"`. Otherwise, emit `left`.
|
||||
|
||||
* Emit `" "`, then the operator, then `" "`.
|
||||
|
||||
* If either:
|
||||
|
||||
* the right value is a `CalculationInterpolation`, or
|
||||
* the operator is `"*"` or `"-"` and the right value is a
|
||||
`CalculationOperation` with operator `"+"` or `"-"`, or
|
||||
* the operator is `"/"` and the right value is a `CalculationOperation`,
|
||||
@ -174,10 +124,6 @@ To serialize a `CalculationOperation`:
|
||||
|
||||
emit `"("` followed by `right` followed by `")"`. Otherwise, emit `right`.
|
||||
|
||||
#### `CalculationInterpolation`
|
||||
|
||||
To serialize a `CalculationInterpolation`, emit its `value`.
|
||||
|
||||
#### `Number`
|
||||
|
||||
To serialize a `Number` within a `CalculationExpression`:
|
||||
@ -197,6 +143,34 @@ To serialize a `Number` within a `CalculationExpression`:
|
||||
|
||||
## Procedures
|
||||
|
||||
### Evaluating a `FunctionCall` as a Calculation
|
||||
|
||||
This algorithm takes a [`FunctionCall`] `call` whose name is a plain identifier
|
||||
and returns a number or a calculation.
|
||||
|
||||
* If `call`'s `ArgumentInvocation` contains one or more `KeywordArgument`s or
|
||||
one or more `RestArgument`s, throw an error.
|
||||
|
||||
* Let `calc` be a calculation whose name is the lower-case value of `call`'s
|
||||
name and whose arguments are the result of evaluating each `Expression` in
|
||||
`call`'s `ArgumentInvocation` [as a calculation value].
|
||||
|
||||
[as a calculation value]: #evaluating-an-expression-as-a-calculation-value
|
||||
|
||||
* Return the result of [simplifying](#simplifying-a-calculation) `calc`.
|
||||
|
||||
### Evaluating an Expression as a Calculation Value
|
||||
|
||||
This algorithm takes an expression `expression` and returns a
|
||||
`CalculationValue`.
|
||||
|
||||
* If `expression` isn't [calculation-safe], throw an error.
|
||||
|
||||
* Otherwise, evaluate `expression` using the semantics defined in the
|
||||
[Semantics] section if available, or the standard semantics otherwise.
|
||||
|
||||
[Semantics]: #semantics
|
||||
|
||||
### Simplifying a Calculation
|
||||
|
||||
This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
@ -207,31 +181,205 @@ This algorithm takes a calculation `calc` and returns a number or a calculation.
|
||||
* If `calc` was parsed from an expression within a `SupportsDeclaration`'s
|
||||
`Expression`, but outside any interpolation, return a `calc` as-is.
|
||||
|
||||
* Let `arguments` be the result of [simplifying](#simplifying-a-calculationvalue) each
|
||||
of `calc`'s arguments.
|
||||
* Let `arguments` be the result of [simplifying] each of `calc`'s arguments.
|
||||
|
||||
* If `calc`'s name is `"calc"`, the syntax guarantees that `arguments` contain
|
||||
only a single argument. If that argument is a number or calculation, return
|
||||
it.
|
||||
[simplifying]: #simplifying-a-calculationvalue
|
||||
|
||||
* If `calc`'s name is `"clamp"`, `arguments` has fewer than three elements, and
|
||||
none of those are unquoted strings or `CalculationInterpolation`s, throw an
|
||||
* If `calc`'s name is `"calc"` and `arguments` contains exactly a single number
|
||||
or calculation, return it.
|
||||
|
||||
* If `calc`'s name is `"mod"`, `"rem"`, `"atan2"`, or `"pow"`; `arguments` has
|
||||
fewer than two elements; and none of those are unquoted strings, throw an
|
||||
error.
|
||||
|
||||
> It's valid to write `clamp(var(--three-args))` or `clamp(#{"1, 2, 3"})`, but
|
||||
> otherwise `clamp()` has to have three physical arguments.
|
||||
> It's valid to write `pow(var(--two-args))` or `pow(#{"2, 3"})`, but
|
||||
> otherwise calculations' arguments must match the expected number.
|
||||
|
||||
* If `calc`'s name is `"clamp"` and `arguments` are all
|
||||
numbers:
|
||||
* If `calc`'s name is `"sin"`, `"cos"`, `"tan"`, `"asin"`, `"acos"`, `"atan"`,
|
||||
`"sqrt"`, `"log"`, or `"round"` and `arguments` contains exactly a single
|
||||
number, return the result of passing that number to the function in
|
||||
[`sass:math`] whose name matches `calc`'s.
|
||||
|
||||
* If those arguments' are mutually [compatible], return the result of calling
|
||||
`math.clamp()` with those arguments.
|
||||
[`sass:math`]: ../built-in-modules/math.md
|
||||
|
||||
* Otherwise, if any two of those arguments are [definitely-incompatible],
|
||||
throw an error.
|
||||
> The `sass:math` functions will check units here for the functions that
|
||||
> require specific or no units.
|
||||
|
||||
* If `calc`'s name is `"abs"` and `arguments` contains exactly a single number
|
||||
with [known units], return the result of passing that number to the function
|
||||
in [`sass:math`] whose name matches `calc`'s.
|
||||
|
||||
[known units]: number.md#known-units
|
||||
|
||||
* If `calc`'s name is `"exp"` and `arguments` contains exactly a single number
|
||||
`number`, return the result of calling `math.pow(math.$e, number)`.
|
||||
|
||||
> This will throw an error if the argument has units.
|
||||
|
||||
* If `calc`'s name is `"sign"` and `arguments` contains exactly a single number
|
||||
`number` with [known units]:
|
||||
|
||||
* If `number`'s value is positive, return `1`.
|
||||
* If `number`'s value is negative, return `-1`.
|
||||
* Otherwise, return a unitless number with the same value as `number`.
|
||||
|
||||
> In this case, `number` is either `+0`, `-0`, or NaN.
|
||||
|
||||
> To match CSS's behavior, these computations *don't* use fuzzy comparisons.
|
||||
|
||||
* If `calc`'s name is `"log"`:
|
||||
|
||||
* If any argument is a number with units, throw an error.
|
||||
|
||||
* Otherwise, if `arguments` contains exactly two numbers, return the result of
|
||||
passing its arguments to the [`log()` function] in [`sass:math`].
|
||||
|
||||
[`log()` function]: ../built-in-modules/math.md#log
|
||||
|
||||
* If `calc`'s name is `"pow"`:
|
||||
|
||||
* If any argument is a number with units, throw an error.
|
||||
|
||||
* Otherwise, if `arguments` contains exactly two numbers, return the result of
|
||||
passing those numbers to the [`pow()` function] in [`sass:math`].
|
||||
|
||||
[`pow()` function]: ../built-in-modules/math.md#pow
|
||||
|
||||
* If `calc`'s name is `"atan2"` and `arguments` contains two numbers which both
|
||||
have [known units], return the result of passing those numbers to the
|
||||
[`atan2()` function] in [`sass:math`].
|
||||
|
||||
> This will throw an error if either argument has units.
|
||||
>
|
||||
> `atan2()` passes percentages along to the browser because they may resolve
|
||||
> to negative values, and `atan2(-x, -y) != atan2(x, y)`.
|
||||
|
||||
[`atan2()` function]: ../built-in-modules/math.md#atan2
|
||||
|
||||
* If `calc`'s name is `"mod"` or `"rem"`:
|
||||
|
||||
* If `arguments` has only one element and it's not an unquoted string, throw
|
||||
an error.
|
||||
|
||||
* Otherwise, if `arguments` contains exactly two numbers `dividend` and
|
||||
`modulus`:
|
||||
|
||||
* If `dividend` and `modulus` are [definitely-incompatible], throw an error.
|
||||
|
||||
* If `dividend` and `modulus` are mutually [compatible]:
|
||||
|
||||
* Let `result` be the result of `dividend % modulus`.
|
||||
|
||||
* If `calc`'s name is `"rem"`, and if `dividend` is positive and `modulus`
|
||||
is negative or vice versa:
|
||||
|
||||
* If `modulus` is infinite, return `dividend`.
|
||||
* If `result` [exactly equals] 0, return `-result`.
|
||||
* Otherwise, return `result - modulus`.
|
||||
|
||||
* Otherwise, return `result`.
|
||||
|
||||
[compatible]: number.md#compatible-units
|
||||
[definitely-incompatible]: number.md#possibly-compatible-numbers
|
||||
[exactly equals]: number.md#exact-equality
|
||||
|
||||
* If `calc`'s name is `"round"`:
|
||||
|
||||
* If `arguments` has exactly three elements, set `strategy`, `number`, and
|
||||
`step` to those arguments respectively.
|
||||
|
||||
* Otherwise, if `arguments` has exactly two elements:
|
||||
|
||||
* If the first element is an unquoted string or interpolation with value
|
||||
`"nearest"`, `"up"`, `"down"`, or `"to-zero"`, and the second argument
|
||||
isn't an unquoted string, throw an error.
|
||||
|
||||
> Normally we allow unquoted strings anywhere in a calculation, but this
|
||||
> helps catch the likely error of a user accidentally writing `round(up,
|
||||
> 10px)` without realizing that it needs a third argument.
|
||||
|
||||
* Otherwise, set `number` and `step` to the two arguments respectively and
|
||||
`strategy` to an unquoted string with value `"nearest"`.
|
||||
|
||||
* Otherwise, if the single argument isn't an unquoted string, throw an error.
|
||||
|
||||
* If `strategy`, `number`, and `step` are set:
|
||||
|
||||
* If `strategy` isn't a [special variable string], nor is it an unquoted
|
||||
string or interpolation with value `"nearest"`, `"up"`, `"down"`, or
|
||||
`"to-zero"`, throw an error.
|
||||
|
||||
* If `strategy` is an unquoted string or interpolation and both `number` and
|
||||
`step` are numbers:
|
||||
|
||||
* If `number` and `step` are [definitely-incompatible], throw an error.
|
||||
|
||||
* If `number` and `step` are mutually [compatible]:
|
||||
|
||||
* If `number`'s and `step`'s values are both infinite, if `step` is
|
||||
[exactly equal] to 0, or if either `number`'s or `step`'s values are
|
||||
NaN, return NaN with the same units as `number`.
|
||||
|
||||
* If `number`'s value is infinite, return `number`.
|
||||
|
||||
* If `step`'s value is infinite:
|
||||
|
||||
* If `strategy`'s value is `"nearest"` or `"to-zero"`, return `+0` if
|
||||
`number`'s value is positive or `+0`, and `-0` otherwise.
|
||||
|
||||
* If `strategy`'s value is `"up"`, return positive infinity if
|
||||
`number`'s value is positive, `+0` if `number`'s value is `+0`, and
|
||||
`-0` otherwise.
|
||||
|
||||
* If `strategy`'s value is `"down"`, return negative infinity if
|
||||
`number`'s value is negative, `-0` if `number`'s value is `-0`, and
|
||||
`+0` otherwise.
|
||||
|
||||
* Set `number` and `step` to the result of [matching units] for `number`
|
||||
and `step`.
|
||||
|
||||
* If `number`'s value is [exactly equal] to `step`'s, return `number`.
|
||||
|
||||
* Let `upper` and `lower` be the two integer multiples of `step` which
|
||||
are closest to `number` such that `upper` is greater than `lower`. If
|
||||
`upper` would be 0, it's specifically `-0`; if `lower` would be zero,
|
||||
it's specifically `-0`.
|
||||
|
||||
* If `strategy`'s value is `"nearest"`, return whichever of `upper` and
|
||||
`lower` has the smallest absolute distance from `number`. If both have
|
||||
an equal difference, return `upper`.
|
||||
|
||||
* If `strategy`'s value is `"up"`, return `upper`.
|
||||
|
||||
* If `strategy`'s value is `"down"`, return `lower`.
|
||||
|
||||
* If `strategy`'s value is `"to-zero"`, return whichever of `upper` and
|
||||
`lower` has the smallest absolute difference from 0.
|
||||
|
||||
[special variable string]: ../functions.md#special-variable-string
|
||||
|
||||
* If `calc`'s name is `"clamp"`:
|
||||
|
||||
* If `arguments` has fewer than three elements, and none of those are unquoted
|
||||
strings, throw an error.
|
||||
|
||||
* Otherwise, if any two elements of `arguments` are [definitely-incompatible]
|
||||
numbers, throw an error.
|
||||
|
||||
* Otherwise, if `arguments` are all mutually [compatible] numbers, return the
|
||||
result of calling `math.clamp()` with those arguments.
|
||||
|
||||
* If `calc`'s name is `"hypot"`:
|
||||
|
||||
* If any two elements of `arguments` are [definitely-incompatible] numbers,
|
||||
throw an error.
|
||||
|
||||
* Otherwise, if all `arguments` are all numbers with [known units] that are
|
||||
mutually [compatible], return the result of calling `math.hypot()` with
|
||||
those arguments.
|
||||
|
||||
> `hypot()` has an exemption for percentages because it squares its inputs,
|
||||
> so `hypot(-x, -y) != -hypot(x, y)`.
|
||||
|
||||
* If `calc`'s name is `"min"` or `"max"` and `arguments` are all numbers:
|
||||
|
||||
@ -260,19 +408,25 @@ This algorithm takes a `CalculationValue` `value` and returns a
|
||||
> This algorithm is intended to return a value that's CSS-semantically identical
|
||||
> to the input.
|
||||
|
||||
* If `value` is a number, unquoted string, or `CalculationInterpolation`, return
|
||||
it as-is.
|
||||
* If `value` is a number or unquoted string, return it as-is.
|
||||
|
||||
* If `value` is a calculation:
|
||||
|
||||
* Let `result` be the result of [simplifying] `value`.
|
||||
* Let `result` be the result of [simplifying] `value`.
|
||||
|
||||
* If `result` is a calculation whose name is `"calc"`, return `result`'s
|
||||
single argument.
|
||||
* If `result` isn't a calculation whose name is `"calc"`, return `result`.
|
||||
|
||||
* Otherwise, return `result`.
|
||||
* If `result`'s argument isn't an unquoted string, return `result`'s argument.
|
||||
|
||||
[simplifying]: #simplifying-a-calculation
|
||||
* If `result`'s argument begins case-insensitively with `"var("`; or if it
|
||||
contains whitespace, `"/"`, or `"*"`; return `"(" +` result's argument `+
|
||||
")"` as an unquoted string.
|
||||
|
||||
> This is ensures that values that could resolve to operations end up
|
||||
> parenthesized if used in other operations. It's potentially a little
|
||||
> overzealous, but that's unlikely to be a major problem given that the
|
||||
> output is still smaller than including the full `calc()` and we don't want
|
||||
> to encourage users to inject calculations with interpolation anyway.
|
||||
|
||||
* Otherwise, `value` must be a `CalculationOperation`. Let `left` and `right` be
|
||||
the result of simplifying `value.left` and `value.right`, respectively.
|
||||
@ -313,133 +467,111 @@ This algorithm takes a `CalculationValue` `value` and returns a
|
||||
|
||||
## Semantics
|
||||
|
||||
### `CalcExpression`
|
||||
The following semantics only apply when evaluating expressions [as calculation
|
||||
values].
|
||||
|
||||
To evaluate a `CalcExpression`:
|
||||
[as calculation values]: #evaluating-an-expression-as-a-calculation-value
|
||||
|
||||
* Let `calc` be a calculation whose name is `"calc"` and whose only argument is
|
||||
the result of [evaluating the expression's `CalcArgument`](#calcargument).
|
||||
### `FunctionExpression` and `Variable`
|
||||
|
||||
* Return the result of [simplifying] `calc`.
|
||||
To evaluate a `FunctionExpression` or a `Variable` as a calculation value,
|
||||
evaluate it using the standard semantics. If the result is a number, an unquoted
|
||||
string, or a calculation, return it. Otherwise, throw an error.
|
||||
|
||||
### `ClampExpression`
|
||||
> Allowing variables to return unquoted strings here supports referential
|
||||
> transparency, so that `$var: fn(); calc($var)` works the same as `calc(fn())`.
|
||||
|
||||
To evaluate a `ClampExpression`:
|
||||
### `SumExpression` and `ProductExpression`
|
||||
|
||||
* Let `clamp` be a calculation whose name is `"clamp"` and whose arguments are the
|
||||
results of [evaluating the expression's `CalcArgument`s](#calcargument).
|
||||
To evaluate a `SumExpresssion` or a `ProductExpression` as a calculation value:
|
||||
|
||||
* Return the result of [simplifying] `clamp`.
|
||||
* Let `left` be the result of evaluating the first operand as a calculation
|
||||
value.
|
||||
|
||||
### `CssMinMax`
|
||||
* For each remaining `"+"`, `"-"`, `"*"`, or `"/"` token `operator` and operand
|
||||
`operand`:
|
||||
|
||||
To evaluate a `CssMinMax`:
|
||||
|
||||
* Let `calc` be a calculation whose name is `"min"` or `"max"` according to the
|
||||
`CssMinMax`'s first token, and whose arguments are the results of [evaluating
|
||||
the expression's `CalcArgument`s](#calcargument).
|
||||
|
||||
* Return the result of [simplifying] `calc`.
|
||||
|
||||
### `CalcArgument`
|
||||
|
||||
To evaluate a `CalcArgument` production `argument` into a `CalculationValue` object:
|
||||
|
||||
* If `argument` is an `InterpolatedDeclarationValue`, evaluate it and return a
|
||||
`CalculationInterpolation` whose `value` is the resulting string.
|
||||
|
||||
* Otherwise, return the result of [evaluating `argument`'s
|
||||
`CalcValue`](#calcvalue).
|
||||
|
||||
### `CalcSum`
|
||||
|
||||
To evaluate a `CalcSum` production `sum` into a `CalculationValue` object:
|
||||
|
||||
* Left `left` be the result of evaluating the first `CalcProduct`.
|
||||
|
||||
* For each remaining "+" or "-" token `operator` and `CalcProduct` `product`:
|
||||
|
||||
* Let `right` be the result of evaluating `product`.
|
||||
* Let `right` be the result of evaluating `operand` as a calculation value.
|
||||
|
||||
* Set `left` to a `CalcOperation` with `operator`, `left`, and `right`.
|
||||
|
||||
* Return `left`.
|
||||
|
||||
### `CalcProduct`
|
||||
### `SpaceListExpression`
|
||||
|
||||
To evaluate a `CalcProduct` production `product` into a `CalculationValue`
|
||||
object:
|
||||
To evaluate a `SpaceListExpresssion` as a calculation value:
|
||||
|
||||
* Left `left` be the result of evaluating the first `CalcValue`.
|
||||
* Let `elements` be the results of evaluating each element as a calculation
|
||||
value.
|
||||
|
||||
* For each remaining "*" or "/" token `operator` and `CalcValue` `value`:
|
||||
* If `elements` has two adjacent elements that aren't unquoted strings, throw an
|
||||
error.
|
||||
|
||||
* Let `right` be the result of evaluating `value`.
|
||||
> This ensures that valid CSS constructs like `calc(1 var(--plus-two))` and
|
||||
> similar Sass constructs like `calc(1 #{"+ 2"})` work while preventing clear
|
||||
> errors like `calc(1 2)`.
|
||||
>
|
||||
> This does allow errors like `calc(a b)`, but the complexity of verifying
|
||||
> that the unquoted strings could actually be a partial operation isn't worth
|
||||
> the benefit of eagerly producing an error in this edge case.
|
||||
|
||||
* Set `left` to a `CalcOperation` with `operator`, `left`, and `right` as its
|
||||
values.
|
||||
* Let `serialized` be an empty list.
|
||||
|
||||
* Return `left`.
|
||||
* For each `element` of `elements`:
|
||||
|
||||
### `CalcValue`
|
||||
* Let `css` be the result of [serializing] `element`.
|
||||
|
||||
To evaluate a `CalcValue` production `value` into a `CalculationValue` object:
|
||||
[serializing]: #serialization
|
||||
|
||||
* If `value` is a `CalcArgument`, `CssMinMax`, `Number`, or `ParenthesizedVar`,
|
||||
return the result of evaluating it.
|
||||
* If `element` is a `CalcOperation` that was produced by evaluating a
|
||||
`ParenthesizedExpression`, set `css` to `"(" + css + ")"`.
|
||||
|
||||
* If `value` is a `FunctionExpression` or `Variable`, evaluate it. If the result
|
||||
is a number, an unquoted string, or a calculation, return it. Otherwise, throw
|
||||
an error.
|
||||
* Append `css` to `serialized`.
|
||||
|
||||
> Allowing variables to return unquoted strings here supports referential
|
||||
> transparency, so that `$var: fn(); calc($var)` works the same as
|
||||
> `calc(fn())`.
|
||||
* Return an unquoted strings whose contents are the elements of `serialized`
|
||||
separated by `" "`.
|
||||
|
||||
* If `value` is case-insensitively equal to `pi`, return 3.141592653589793.
|
||||
### `ParenthesizedExpression`
|
||||
|
||||
> This is the closest double approximation of the mathematical constant π.
|
||||
|
||||
* If `value` is case-insensitively equal to `e`, return 2.718281828459045.
|
||||
|
||||
> This is the closest double approximation of the mathematical constant e.
|
||||
|
||||
* If `value` is case-insensitively equal to `infinity`, return the double
|
||||
`Infinity`.
|
||||
|
||||
* If `value` is case-insensitively equal to `-infinity`, return the double
|
||||
`-Infinity`.
|
||||
|
||||
* If `value` is case-insensitively equal to `nan`, return the double `NaN`.
|
||||
|
||||
* If `value` is any other `<identifier>`, return an `UnquotedString` with
|
||||
`value` as its contents.
|
||||
|
||||
### `ParenthesizedVar`
|
||||
|
||||
> If a `var()` is written directly within parentheses, it's necessary to
|
||||
> preserve those parentheses. CSS resolves `var()` by literally replacing the
|
||||
> function with the value of the variable and *then* parsing the surrounding
|
||||
> context.
|
||||
> If a `var()` or an interpolation is written directly within parentheses, it's
|
||||
> necessary to preserve those parentheses. CSS resolves `var()` by literally
|
||||
> replacing the function with the value of the variable and *then* parsing the
|
||||
> surrounding context.
|
||||
>
|
||||
> For example, if `--ratio: 2/3`, `calc(1 / (var(--ratio)))` is parsed as
|
||||
> `calc(1 / (2/3)) = calc(3/2)` but `calc(1 / var(--ratio))` is parsed as
|
||||
> `calc(1 / 2/3) = calc(1/6)`.
|
||||
|
||||
To evaluate a `ParenthesizedVar` production `value` into an unquoted string:
|
||||
To evaluate a `ParenthesizedExpression` with contents `expression` as a
|
||||
calculation value:
|
||||
|
||||
* Let `function` be a [`FunctionCall`] with `"var"` as its
|
||||
[`NamespacedIdentifier`] and with `value`'s `ArgumentInvocation`.
|
||||
* Let `result` be the result of evaluating `expression` as a calculation value.
|
||||
|
||||
[`FunctionCall`]: ../functions.md#syntax
|
||||
[`NamespacedIdentifier`]: ../modules.md#syntax
|
||||
* If `result` is an unquoted string, return `"(" + result + ")"` as an unquoted
|
||||
string.
|
||||
|
||||
* Let `result` be the result of evaluating `function`.
|
||||
* Otherwise, return `result`.
|
||||
|
||||
* If `result` is a number or a calculation, return it.
|
||||
### `InterpolatedIdentifier`
|
||||
|
||||
> This could happen if the user defines a `var` function in Sass.
|
||||
To evaluate an `InterpolatedIdentifier` `ident` as a calculation value:
|
||||
|
||||
* If `result` is not an unquoted string, throw an error.
|
||||
* If `ident` is case-insensitively equal to `pi`, return 3.141592653589793.
|
||||
|
||||
* Return `"(" + result + ")"` as an unquoted string.
|
||||
> This is the closest double approximation of the mathematical constant π.
|
||||
|
||||
* If `ident` is case-insensitively equal to `e`, return 2.718281828459045.
|
||||
|
||||
> This is the closest double approximation of the mathematical constant e.
|
||||
|
||||
* If `ident` is case-insensitively equal to `infinity`, return the double
|
||||
`Infinity`.
|
||||
|
||||
* If `ident` is case-insensitively equal to `-infinity`, return the double
|
||||
`-Infinity`.
|
||||
|
||||
* If `ident` is case-insensitively equal to `nan`, return the double `NaN`.
|
||||
|
||||
* Otherwise, return the result of evaluating `ident` using standard semantics.
|
||||
|
||||
> This will be an `UnquotedString`.
|
||||
|
@ -10,6 +10,8 @@
|
||||
* [Compatible Units](#compatible-units)
|
||||
* [Possibly-Compatible Units](#possibly-compatible-units)
|
||||
* [Possibly-Compatible Numbers](#possibly-compatible-numbers)
|
||||
* [Known Units](#known-units)
|
||||
* [Exact Equality](#exact-equality)
|
||||
* [Fuzzy Equality](#fuzzy-equality)
|
||||
* [Integer](#integer)
|
||||
* [Potentially Slash-Separated Number](#potentially-slash-separated-number)
|
||||
@ -51,9 +53,9 @@ as defined by [IEEE 754 2019], §3.2-3.3.
|
||||
|
||||
### Degenerate Number
|
||||
|
||||
The doubles `Infinity`, `-Infinity`, and `NaN` are _degenerate_.
|
||||
The doubles `Infinity`, `-Infinity`, and `NaN` are *degenerate*.
|
||||
|
||||
A number is _degenerate_ if its value is degenerate.
|
||||
A number is *degenerate* if its value is degenerate.
|
||||
|
||||
### Conversion Factors
|
||||
|
||||
@ -132,8 +134,6 @@ possible-compatibility.
|
||||
> units; this preserves forwards-compatibility with new units that are
|
||||
> introduced in browsers over time.
|
||||
|
||||
[CSS Values and Units]: https://www.w3.org/TR/css-values-3/
|
||||
|
||||
| Type | Units |
|
||||
| -------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `<length>` | `em`, `ex`, `ch`, `rem`, `vw`, `vh`, `vmin`, `vmax`, `cm`, `mm`, `Q`, `in`, `pt`, `pc`, `px` |
|
||||
@ -161,12 +161,33 @@ Two numbers are *definitely-incompatible* if they are not possibly-compatible.
|
||||
> complex units, but in practice these numbers are already flagged as errors
|
||||
> prior to any possible-compatibility checks.
|
||||
|
||||
### Known Units
|
||||
|
||||
A number has *known units* unless it has unit `%`.
|
||||
|
||||
> This is relevant for calculations, because in plain CSS they resolve
|
||||
> percentages before doing their operations. This means that any non-linear
|
||||
> operations involving percentages must be passed through to plain CSS rather
|
||||
> than handled by Sass.
|
||||
>
|
||||
> More complex units involving percentages are allowed because any non-linear
|
||||
> function will throw for complex units anyway.
|
||||
|
||||
### Exact Equality
|
||||
|
||||
Two [doubles] are said to be *exactly equal* if they are equal according to the
|
||||
`compareQuietEqual` predicate as defined by [IEEE 754 2019], §5.11.
|
||||
|
||||
[doubles]: #double
|
||||
|
||||
> This is as opposed to [fuzzy equality].
|
||||
>
|
||||
> [fuzzy equality]: #fuzzy-equality
|
||||
|
||||
### Fuzzy Equality
|
||||
|
||||
Two [doubles] are said to be *fuzzy equal* to one another if either:
|
||||
|
||||
[doubles]: #double
|
||||
|
||||
* They are equal according to the `compareQuietEqual` predicate as defined
|
||||
by [IEEE 754 2019], §5.11.
|
||||
|
||||
@ -197,47 +218,28 @@ denominator*. A number that is not potentially slash-separated is known as
|
||||
*slash-free*.
|
||||
|
||||
A potentially slash-separated number is created when a `ProductExpression` with
|
||||
a `/` operator is evaluated and both operands are *syntactically* one of the
|
||||
a `/` operator is evaluated and each operand is *syntactically* one of the
|
||||
following:
|
||||
|
||||
* `Number`s,
|
||||
* [`Calculation`]s, or
|
||||
* `ProductExpression`s that can themselves create potentially slash-separated
|
||||
* a `Number`,
|
||||
* a [`FunctionCall`], or
|
||||
* a `ProductExpression` that can itself create potentially slash-separated
|
||||
numbers.
|
||||
|
||||
[`Calculation`]: calculation.md#syntax
|
||||
|
||||
If both operands are evaluated as numbers, the resulting number is potentially
|
||||
slash-separated. The first operand is the original numerator of the potentially
|
||||
slash-separated number returned by the `/` operator, and the second is the
|
||||
original denominator.
|
||||
[`FunctionCall`]: ../functions.md#functioncall
|
||||
|
||||
A potentially slash-separated number is converted to a slash-free number when:
|
||||
If the result of evaluating the `ProductExpression` is a number, that number is
|
||||
potentially slash-separated if all of the following are true:
|
||||
|
||||
* It is the value of a `ParenthesizedExpression`.
|
||||
* the results of evaluating both operands were numbers, and
|
||||
* if either operand was a `FunctionCall`, it was [evaluated as a calculation]
|
||||
and its name was not `"abs"`, `"max"`, `"min"`, or `"round"`.
|
||||
|
||||
> That is, it's in parentheses, such as in `(1 / 2)`. Note that if it's in a
|
||||
> list that's in parentheses, it's *not* converted to a slash-free number.
|
||||
[evaluated as a calculation]: calculation.md#evaluating-a-functioncall-as-a-calculation
|
||||
|
||||
* It is stored in a Sass variable.
|
||||
|
||||
* It is passed to a function or mixin.
|
||||
|
||||
* It is returned by a function.
|
||||
|
||||
> Any expressions that normally produce a new number (such as other mathematical
|
||||
> operations) always produce slash-free numbers, even when their arguments are
|
||||
> slash-separated.
|
||||
>
|
||||
> When a potentially slash-separated number is "converted" to a slash-free
|
||||
> number, a slash-free copy is made of the original. Sass values are always
|
||||
> immutable.
|
||||
|
||||
When a potentially slash-separated number is converted to CSS, either when
|
||||
converted to a string via interpolation or when included in a declaration's
|
||||
value, it is written as the original numerator followed by `/` followed by the
|
||||
original denominator. If either the original numerator or denominator are
|
||||
themselves slash-separated, they're also written this way.
|
||||
If both of these are true, the first operand is the original numerator of the
|
||||
potentially slash-separated number returned by the `/` operator, and the second
|
||||
is the original denominator.
|
||||
|
||||
## Types
|
||||
|
||||
@ -247,8 +249,6 @@ The value type known as a *number* has three components:
|
||||
* A list of strings called *numerator units*.
|
||||
* A list of strings called *denominator units*.
|
||||
|
||||
[double]: #doubles
|
||||
|
||||
Several shorthands exist when referring to numbers:
|
||||
|
||||
* A number's *units* refers to the [set of units] containing its numerator units
|
||||
@ -345,23 +345,29 @@ Let `n1` and `n2` be two numbers. To determine `n1 % n2`:
|
||||
* Let `c1` and `c2` be the result of [matching units] for `n1` and `n2` allowing
|
||||
unitless.
|
||||
|
||||
* If `c2` is infinity and has a different sign than `c1` (including
|
||||
oppositely-signed zero), return NaN with the same units as `c1`.
|
||||
|
||||
> This matches the behavior of CSS's `mod()` function.
|
||||
|
||||
* Let `remainder` be a number whose value is the result of `remainder(c1.value,
|
||||
c2.value)` as defined by [IEEE 754 2019], §5.3.1; and whose units are the same
|
||||
as `c1`'s.
|
||||
|
||||
* If `c2`'s value is less than 0 and `remainder`'s value isn't `0` or `-0`,
|
||||
return `result - c2`.
|
||||
* If `c2`'s value is less than 0 and `remainder`'s value isn't [exactly equal]
|
||||
to `0`, return `remainder - c2`.
|
||||
|
||||
[exactly equal]: #exact-equality
|
||||
|
||||
> This is known as [floored division]. It differs from the standard IEEE 754
|
||||
> specification because it was originally inherited from Ruby when that was
|
||||
> used for Sass's original implementation.
|
||||
> specification, but matches the behavior of CSS's `mod()` function.
|
||||
>
|
||||
> Note: These comparisons are not the same as `c2 < 0` or `remainder == 0`,
|
||||
> because they don't do fuzzy equality.
|
||||
|
||||
[floored division]: https://en.wikipedia.org/wiki/Modulo_operation#Variants_of_the_definition
|
||||
|
||||
* Otherwise, return `result`.
|
||||
* Otherwise, return `remainder`.
|
||||
|
||||
#### Negation
|
||||
|
||||
|
@ -104,6 +104,8 @@ function runLinkCheck(
|
||||
{pattern: /^https?:\/\/twitter\.com(\/|$)/},
|
||||
// tcort/markdown-link-check#260
|
||||
{pattern: /^https?:\/\/blogs\.msdn\.microsoft\.com(\/|$)/},
|
||||
// Link consistently fails within CI
|
||||
{pattern: /^https:\/\/runtime-keys\.proposal\.wintercg\.org(\/|$)/},
|
||||
],
|
||||
},
|
||||
(error, results) => {
|
||||
|
@ -2,7 +2,7 @@ import * as glob from 'glob';
|
||||
import markdownToc = require('markdown-toc');
|
||||
|
||||
/** Files that may contain tables of contents. */
|
||||
export const files = glob.sync('**/*.md', {
|
||||
export const files = glob.sync('**/*.md?(.d.ts)', {
|
||||
ignore: ['node_modules/**/*.md', '**/*.changes.md'],
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user