@webflow/variables 0.0.0-stage → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/LICENCE +23 -0
- package/README.md +8 -2
- package/dist/index.cjs +2 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +2 -0
- package/dist/postcss.cjs +1 -0
- package/dist/postcss.d.ts +38 -0
- package/dist/postcss.js +1 -0
- package/package.json +58 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @webflow/variables
|
|
2
|
+
|
|
3
|
+
## 2.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7e7c01f: `@webflow/variables/postcss` reads `@webflow-collection` blocks in `.css` variable declaration files into a style variable catalog (`parseVariableSources`) and strips them from component bundles (`stripWebflowCollectionsPlugin`, a PostCSS plugin). The blocks hold metadata only. Values stay in plain CSS rules, and every problem is reported with its file, line and column.
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
:root {
|
|
11
|
+
--radius--md: 0px;
|
|
12
|
+
--brand: #232f3e;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
[data-theme="dark"] {
|
|
16
|
+
--brand: #f3f3f3;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@webflow-collection colors "Colors" {
|
|
20
|
+
@mode dark "Dark" {
|
|
21
|
+
selector: "[data-theme='dark']";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@variable --brand {
|
|
25
|
+
label: "color/brand";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@variable --radius--md {
|
|
29
|
+
type: "length";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `@variable --id` exports a custom property. `label` is optional and defaults to the id without `--`. `type` is optional: `color`, `length`, `number`, `percentage` or `font-family`. A bare `@variable --id;` is the same as an empty block.
|
|
35
|
+
- `@mode id "Label"` needs a quoted `selector`. A mode value is the declaration of the id on the top-level rule with that selector. A mode with no declaration uses the base value.
|
|
36
|
+
- The base value is the declaration on `:root` or `:host`. A collection-level `selector: "…"` reads it from another top-level rule instead. Declarations inside `@media`, `@layer` or `@supports` are not read, and the parser warns when one would have matched.
|
|
37
|
+
- The type comes from the value. When a value can have more than one type (a bare `0`), the parser uses `number` and warns; add `type: "length"` (or another type) to choose. A value with no Webflow type (a keyword, a shadow) is left out with a warning. An unknown descriptor or `type`, an unquoted `selector`, or a missing base declaration is an error.
|
|
38
|
+
- `var(--x)` aliases a variable exported with `@variable`. Any other `var()` is an error.
|
|
39
|
+
|
|
40
|
+
`stripWebflowCollectionsPlugin({ sources, onUnlisted })` removes `@webflow-collection` blocks from the CSS of every file it processes. `onUnlisted` is called for a file that has blocks but is not in `sources`.
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- 2d4d12d: `validateStyleVariableCatalog` is now the full share contract:
|
|
45
|
+
|
|
46
|
+
- Variable ids must use only ASCII letters, digits, `-` and `_` after the leading `--`.
|
|
47
|
+
- Color values must be colors Webflow can parse (adds a `tinycolor2` dependency).
|
|
48
|
+
- No declared collection may have a name that becomes the same CSS name as the implicit base collection (`RESERVED_BASE_COLLECTION_NAME`, "Base collection") when a variable uses it.
|
|
49
|
+
- Number values must have unit `-` and percentage values must have unit `%`.
|
|
50
|
+
|
|
51
|
+
- Updated dependencies [e9bdf91]
|
|
52
|
+
- @webflow/data-types@2.9.0
|
|
53
|
+
|
|
54
|
+
## 2.8.0
|
|
55
|
+
|
|
56
|
+
### Minor Changes
|
|
57
|
+
|
|
58
|
+
- b84a11f: - Add the new `@webflow/data-types/variables` sub-export with style variable types definitions.
|
|
59
|
+
- Add the new `@webflow/variables` package, which re-exports those types.
|
|
60
|
+
- 70699e7: Add `validateStyleVariableCatalog` and `sanitizeVariableName` to `@webflow/variables`. The validator returns every problem in a library variable catalog with its JSON path, so the CLI and the Webflow server enforce the same rules. It rejects value types that don't match the variable type, unsupported length units, refs to a variable of another type, reference cycles through refs and raw `var()` values, and `var()` fallbacks. `extractVarReferences` is exported for the server.
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- Updated dependencies [ab5c5b8]
|
|
65
|
+
- Updated dependencies [b84a11f]
|
|
66
|
+
- Updated dependencies [348c304]
|
|
67
|
+
- @webflow/data-types@2.8.0
|
package/LICENCE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT license
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Webflow, Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person ob-
|
|
6
|
+
taining a copy of this software and associated documentation
|
|
7
|
+
files (the "Software"), to deal in the Software without restric-
|
|
8
|
+
tion, including without limitation the rights to use, copy, modi-
|
|
9
|
+
fy, merge, publish, distribute, sublicense, and/or sell copies of
|
|
10
|
+
the Software, and to permit persons to whom the Software is fur-
|
|
11
|
+
nished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
18
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
|
|
19
|
+
FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
21
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
22
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @webflow/variables
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tooling for style variables in Webflow code component libraries.
|
|
4
|
+
|
|
5
|
+
## Entry points
|
|
6
|
+
|
|
7
|
+
- `@webflow/variables`: re-exports the `StyleVariableCatalog` types from `@webflow/data-types/variables`, and adds `validateStyleVariableCatalog`, `sanitizeVariableName` and `extractVarReferences`. The CLI and the Webflow server both validate catalogs with it, and the server uses `extractVarReferences` to rewrite `var(--x)` in raw values.
|
|
8
|
+
|
|
9
|
+
The types live in `@webflow/data-types`, which holds the contracts. This package holds the behavior.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var R=require('tinycolor2');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var R__default=/*#__PURE__*/_interopDefault(R);var A=/\s/;function h(a){if(a===void 0)return false;let t=a.charCodeAt(0);return (t>=128||t>=97&&t<=122||t>=65&&t<=90||t>=48&&t<=57||a==="_")&&!A.test(a)}function v(a){let t=a.length,e="",i=0,r=t;for(;i<t&&!h(a[i]);i+=1);for(;r>=1&&!h(a[r-1]);r-=1);for(;i<r;i+=1){let s=a[i];if(s==="/"){for(e[e.length-1]==="-"&&(e=e.substring(0,e.length-1));i<r;i+=1)if(s=a[i],h(s)){e+="--"+s;break}}else h(s)?e+=s:e[e.length-1]!=="-"&&(e+="-");}return e.toLowerCase()}function L(a){let t=a.toLowerCase().trim();if(!/^rgba?\([^)]+\)$/.test(t))return a;let e=t.split(/[,()]/).map(s=>s.trim()).filter(s=>s!==""&&!Number.isNaN(Number(s)));if(e.length<3)return a;e[3]=e[3]||"1";let i=parseFloat(e[3]),r=!Number.isNaN(i)&&i<1;return r||e.pop(),`${r?"rgba":"rgb"}(${e.join(", ")})`}function V(a){return R__default.default(L(a)).isValid()}var p="base",S="Base collection",N="webflow-collection";var D=/var\(\s*(--[^\s,)(]+)/gi,T=/var\(\s*(--[^\s,)(]+)\s*,/gi;function y(a){return [...a.matchAll(D)].map(t=>t[1]).filter(t=>t!==void 0)}function w(a){return [...a.matchAll(T)].map(t=>t[1]).filter(t=>t!==void 0)}var F=/^--[A-Za-z0-9_-]+$/,x=/^(?:--|-?[A-Za-z_\u0080-\uFFFF])[A-Za-z0-9_\-\u0080-\uFFFF]*$/,O={px:true,em:true,rem:true,vh:true,vw:true,dvh:true,dvw:true,lvh:true,lvw:true,svh:true,svw:true,vmax:true,vmin:true,ch:true};function E(a,t,e,i){let r=new Map,s=[];return a.forEach((n,c)=>{let o=t(n),l=r.get(o);l===void 0?r.set(o,c):s.push({path:e(c),message:i(o,l,c)});}),s}function m(a){return a.variables.flatMap((t,e)=>[{path:`variables[${e}].value`,value:t.value,variableIndex:e},...Object.entries(t.modes??{}).map(([i,r])=>({path:`variables[${e}].modes.${i}`,value:r,variableIndex:e}))])}function M(a){let t=new Map;return a.variables.forEach((e,i)=>{t.has(e.id)||t.set(e.id,{variable:e,index:i});}),t}var _=50;function B(a){let t=M(a),e=new Map;for(let{value:o,variableIndex:l}of m(a)){let u=a.variables[l]?.id;if(u===void 0||t.get(u)?.index!==l)continue;let f=o.type==="ref"?o.value.variableId===u?[]:[o.value.variableId]:o.type==="raw"?y(o.value):[],g=e.get(u)??new Set;f.filter(d=>t.has(d)).forEach(d=>g.add(d)),e.set(u,g);}let i=[],r=new Set,s=new Map,n=false,c=o=>{let l=[],u=f=>{s.set(f,"visiting"),l.push({id:f,targets:(e.get(f)??new Set).values()});};for(u(o);l.length>0&&!n;){let f=l[l.length-1],g=f.targets.next();if(g.done){s.set(f.id,"done"),l.pop();continue}let d=g.value,b=s.get(d);if(b==="visiting"){let C=l.slice(l.findIndex($=>$.id===d)).map($=>$.id),I=[...C].sort().join(`
|
|
2
|
+
`);if(!r.has(I)){if(r.size===_){n=true,i.push({path:"variables",message:`More than ${_} circular references, the rest are not shown`});continue}r.add(I),i.push({path:`variables[${t.get(d)?.index}]`,message:`Circular reference: ${[...C,d].join(" \u2192 ")}`});}}else b===void 0&&u(d);}};for(let o of t.keys()){if(n)break;s.has(o)||c(o);}return i}var k=[({variables:a})=>a.flatMap((t,e)=>F.test(t.id)?[]:[{path:`variables[${e}].id`,message:`"${t.id}" is not a valid variable id (it must start with "--" and use only ASCII letters, digits, "-" and "_")`}]),({variables:a})=>E(a,t=>t.id,t=>`variables[${t}].id`,(t,e,i)=>`Duplicate variable id "${t}" at variables[${e}] and variables[${i}]`),({collections:a})=>a.flatMap((t,e)=>[...t.id===p?[{path:`collections[${e}].id`,message:`Collection id "${p}" is reserved`}]:x.test(t.id)?[]:[{path:`collections[${e}].id`,message:`"${t.id}" is not a valid CSS identifier`}],..."isDefault"in t?[{path:`collections[${e}].isDefault`,message:"Library collections cannot set isDefault"}]:[]]),({collections:a})=>E(a,t=>t.id,t=>`collections[${t}].id`,(t,e,i)=>`Duplicate collection id "${t}" at collections[${e}] and collections[${i}]`),({collections:a})=>a.flatMap((t,e)=>[...t.modes.flatMap((i,r)=>{let s=`collections[${e}].modes[${r}].id`;return i.id===p?[{path:s,message:`Mode id "${p}" is reserved`}]:x.test(i.id)?[]:[{path:s,message:`"${i.id}" is not a valid CSS identifier`}]}),...E(t.modes,i=>i.id,i=>`collections[${e}].modes[${i}].id`,(i,r,s)=>`Duplicate mode id "${i}" at modes[${r}] and modes[${s}] of collection "${t.id}"`)]),({collections:a,variables:t})=>{let e=[],i=(n,c)=>{let o=new Map,l=new Set;for(let{item:u,path:f}of n){if(l.has(u.id))continue;l.add(u.id);let g=u.name??u.id,d=v(g);if(d===""){e.push({path:f,message:`"${g}" has no characters usable in a CSS variable name`});continue}let b=o.get(d);b===void 0?o.set(d,{label:g,path:f}):e.push({path:f,message:`"${b.label}" at ${b.path} and "${g}" at ${f} both become "${d}" ${c}`});}},r=t.some(n=>!n.collectionId)?[{item:{id:p,name:S},path:"the implicit base collection"}]:[];i([...r,...a.map((n,c)=>({item:n,path:`collections[${c}]`}))],"in this library"),a.forEach((n,c)=>i(n.modes.map((o,l)=>({item:o,path:`collections[${c}].modes[${l}]`})),`in collection "${n.id}"`));let s=new Map;return t.forEach((n,c)=>{let o=n.collectionId??p,l=s.get(o)??[];l.push({item:n,path:`variables[${c}]`}),s.set(o,l);}),s.forEach((n,c)=>i(n,`in collection "${c}"`)),e},({collections:a,variables:t})=>{let e=new Map(a.map(i=>[i.id,new Set(i.modes.map(r=>r.id))]));return t.flatMap((i,r)=>{let s=i.collectionId===void 0?new Set:e.get(i.collectionId);return s===void 0?[{path:`variables[${r}].collectionId`,message:i.collectionId===p?`Collection id "${p}" is reserved; omit the id for the implicit base collection`:`Collection "${i.collectionId}" is not declared`}]:Object.keys(i.modes??{}).filter(n=>!s.has(n)).map(n=>({path:`variables[${r}].modes.${n}`,message:`Mode "${n}" is not declared in collection "${i.collectionId??p}"`}))})},a=>{let t=new Set(a.variables.map(e=>e.id));return m(a).flatMap(({path:e,value:i,variableIndex:r})=>{if(i.type!=="ref")return [];let s=i.value.variableId;return s===a.variables[r]?.id?[{path:e,message:`Variable "${s}" references itself`}]:t.has(s)?[]:[{path:e,message:`Referenced variable "${s}" does not exist`}]})},a=>{let t=new Set(a.variables.map(e=>e.id));return m(a).flatMap(({path:e,value:i})=>i.type!=="raw"?[]:y(i.value).filter(r=>!t.has(r)).map(r=>({path:e,message:`var(${r}) does not name a variable in this library`})))},a=>m(a).flatMap(({path:t,value:e})=>e.type!=="raw"?[]:w(e.value).map(i=>({path:t,message:`var(${i}, \u2026) has a fallback; library variables do not support var() fallbacks`}))),a=>m(a).flatMap(({path:t,value:e,variableIndex:i})=>{let r=a.variables[i]?.type;return e.type==="ref"||e.type==="raw"?[]:e.type===r?[]:[{path:t,message:`A "${e.type}" value does not match the variable type "${r}"`}]}),a=>m(a).flatMap(({path:t,value:e})=>e.type!=="color"?[]:V(e.value)?[]:[{path:t,message:`"${e.value}" is not a valid color`}]),a=>m(a).flatMap(({path:t,value:e})=>e.type!=="length"?[]:Object.hasOwn(O,e.value.unit)?[]:[{path:t,message:`"${e.value.unit}" is not a supported length unit`}]),a=>m(a).flatMap(({path:t,value:e})=>{if(e.type!=="number"&&e.type!=="percentage")return [];let i=e.type==="number"?"-":"%";return e.value.unit===i?[]:[{path:t,message:`A ${e.type} value must have unit "${i}", not "${e.value.unit}"`}]}),a=>{let t=M(a);return m(a).flatMap(({path:e,value:i,variableIndex:r})=>{if(i.type!=="ref")return [];let s=a.variables[r],n=t.get(i.value.variableId)?.variable;return s===void 0||n===void 0?[]:n.id===s.id?[]:n.type===s.type?[]:[{path:e,message:`"${s.id}" is a ${s.type} variable but references "${n.id}", a ${n.type} variable`}]})},B];function z(a){return k.flatMap(t=>t(a))}exports.RESERVED_BASE_COLLECTION_NAME=S;exports.RESERVED_BASE_ID=p;exports.WEBFLOW_COLLECTION_AT_RULE=N;exports.extractVarReferences=y;exports.sanitizeVariableName=v;exports.validateStyleVariableCatalog=z;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StyleVariableCatalog } from '@webflow/data-types/variables';
|
|
2
|
+
export * from '@webflow/data-types/variables';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Port of `sanitizeVariableName` from the Webflow style-variable utilities.
|
|
6
|
+
* Replaces non-permitted characters with single dashes, turns `/` group
|
|
7
|
+
* separators into `--`, trims, and lowercases. `sanitize.fixture.json` is the
|
|
8
|
+
* parity contract with the server copy.
|
|
9
|
+
*/
|
|
10
|
+
declare function sanitizeVariableName(name: string): string;
|
|
11
|
+
|
|
12
|
+
type StyleVariableCatalogIssue = {
|
|
13
|
+
/** JSON path into the catalog, e.g. `variables[1].id`. */
|
|
14
|
+
path: string;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Checks a library variable catalog against the share contract. Returns every
|
|
19
|
+
* issue found, each with a JSON path; an empty array means the catalog is
|
|
20
|
+
* valid. Shared by the CLI (before share) and the server (on receive).
|
|
21
|
+
*
|
|
22
|
+
* The catalog must already match the `StyleVariableCatalog` shape: the server
|
|
23
|
+
* parses the request body with its schema first.
|
|
24
|
+
*/
|
|
25
|
+
declare function validateStyleVariableCatalog(catalog: StyleVariableCatalog): StyleVariableCatalogIssue[];
|
|
26
|
+
|
|
27
|
+
/** Id of the implicit collection. Reserved as a collection id and as a mode id. */
|
|
28
|
+
declare const RESERVED_BASE_ID = "base";
|
|
29
|
+
/**
|
|
30
|
+
* Name Webflow gives the implicit collection. It is part of the CSS name of
|
|
31
|
+
* every variable in it, so no declared collection may sanitize to the same name.
|
|
32
|
+
*/
|
|
33
|
+
declare const RESERVED_BASE_COLLECTION_NAME = "Base collection";
|
|
34
|
+
/** PostCSS at-rule name, without the leading `@`. */
|
|
35
|
+
declare const WEBFLOW_COLLECTION_AT_RULE = "webflow-collection";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns the custom property names referenced by `var(--x)` in a raw value,
|
|
39
|
+
* in order of appearance, including nested references. The server rewrites
|
|
40
|
+
* these to variable placeholders, so it must match them the same way the
|
|
41
|
+
* validator does.
|
|
42
|
+
*/
|
|
43
|
+
declare function extractVarReferences(raw: string): string[];
|
|
44
|
+
|
|
45
|
+
export { RESERVED_BASE_COLLECTION_NAME, RESERVED_BASE_ID, type StyleVariableCatalogIssue, WEBFLOW_COLLECTION_AT_RULE, extractVarReferences, sanitizeVariableName, validateStyleVariableCatalog };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import R from'tinycolor2';var A=/\s/;function h(a){if(a===void 0)return false;let t=a.charCodeAt(0);return (t>=128||t>=97&&t<=122||t>=65&&t<=90||t>=48&&t<=57||a==="_")&&!A.test(a)}function v(a){let t=a.length,e="",i=0,r=t;for(;i<t&&!h(a[i]);i+=1);for(;r>=1&&!h(a[r-1]);r-=1);for(;i<r;i+=1){let s=a[i];if(s==="/"){for(e[e.length-1]==="-"&&(e=e.substring(0,e.length-1));i<r;i+=1)if(s=a[i],h(s)){e+="--"+s;break}}else h(s)?e+=s:e[e.length-1]!=="-"&&(e+="-");}return e.toLowerCase()}function L(a){let t=a.toLowerCase().trim();if(!/^rgba?\([^)]+\)$/.test(t))return a;let e=t.split(/[,()]/).map(s=>s.trim()).filter(s=>s!==""&&!Number.isNaN(Number(s)));if(e.length<3)return a;e[3]=e[3]||"1";let i=parseFloat(e[3]),r=!Number.isNaN(i)&&i<1;return r||e.pop(),`${r?"rgba":"rgb"}(${e.join(", ")})`}function V(a){return R(L(a)).isValid()}var p="base",S="Base collection",N="webflow-collection";var D=/var\(\s*(--[^\s,)(]+)/gi,T=/var\(\s*(--[^\s,)(]+)\s*,/gi;function y(a){return [...a.matchAll(D)].map(t=>t[1]).filter(t=>t!==void 0)}function w(a){return [...a.matchAll(T)].map(t=>t[1]).filter(t=>t!==void 0)}var F=/^--[A-Za-z0-9_-]+$/,x=/^(?:--|-?[A-Za-z_\u0080-\uFFFF])[A-Za-z0-9_\-\u0080-\uFFFF]*$/,O={px:true,em:true,rem:true,vh:true,vw:true,dvh:true,dvw:true,lvh:true,lvw:true,svh:true,svw:true,vmax:true,vmin:true,ch:true};function E(a,t,e,i){let r=new Map,s=[];return a.forEach((n,c)=>{let o=t(n),l=r.get(o);l===void 0?r.set(o,c):s.push({path:e(c),message:i(o,l,c)});}),s}function m(a){return a.variables.flatMap((t,e)=>[{path:`variables[${e}].value`,value:t.value,variableIndex:e},...Object.entries(t.modes??{}).map(([i,r])=>({path:`variables[${e}].modes.${i}`,value:r,variableIndex:e}))])}function M(a){let t=new Map;return a.variables.forEach((e,i)=>{t.has(e.id)||t.set(e.id,{variable:e,index:i});}),t}var _=50;function B(a){let t=M(a),e=new Map;for(let{value:o,variableIndex:l}of m(a)){let u=a.variables[l]?.id;if(u===void 0||t.get(u)?.index!==l)continue;let f=o.type==="ref"?o.value.variableId===u?[]:[o.value.variableId]:o.type==="raw"?y(o.value):[],g=e.get(u)??new Set;f.filter(d=>t.has(d)).forEach(d=>g.add(d)),e.set(u,g);}let i=[],r=new Set,s=new Map,n=false,c=o=>{let l=[],u=f=>{s.set(f,"visiting"),l.push({id:f,targets:(e.get(f)??new Set).values()});};for(u(o);l.length>0&&!n;){let f=l[l.length-1],g=f.targets.next();if(g.done){s.set(f.id,"done"),l.pop();continue}let d=g.value,b=s.get(d);if(b==="visiting"){let C=l.slice(l.findIndex($=>$.id===d)).map($=>$.id),I=[...C].sort().join(`
|
|
2
|
+
`);if(!r.has(I)){if(r.size===_){n=true,i.push({path:"variables",message:`More than ${_} circular references, the rest are not shown`});continue}r.add(I),i.push({path:`variables[${t.get(d)?.index}]`,message:`Circular reference: ${[...C,d].join(" \u2192 ")}`});}}else b===void 0&&u(d);}};for(let o of t.keys()){if(n)break;s.has(o)||c(o);}return i}var k=[({variables:a})=>a.flatMap((t,e)=>F.test(t.id)?[]:[{path:`variables[${e}].id`,message:`"${t.id}" is not a valid variable id (it must start with "--" and use only ASCII letters, digits, "-" and "_")`}]),({variables:a})=>E(a,t=>t.id,t=>`variables[${t}].id`,(t,e,i)=>`Duplicate variable id "${t}" at variables[${e}] and variables[${i}]`),({collections:a})=>a.flatMap((t,e)=>[...t.id===p?[{path:`collections[${e}].id`,message:`Collection id "${p}" is reserved`}]:x.test(t.id)?[]:[{path:`collections[${e}].id`,message:`"${t.id}" is not a valid CSS identifier`}],..."isDefault"in t?[{path:`collections[${e}].isDefault`,message:"Library collections cannot set isDefault"}]:[]]),({collections:a})=>E(a,t=>t.id,t=>`collections[${t}].id`,(t,e,i)=>`Duplicate collection id "${t}" at collections[${e}] and collections[${i}]`),({collections:a})=>a.flatMap((t,e)=>[...t.modes.flatMap((i,r)=>{let s=`collections[${e}].modes[${r}].id`;return i.id===p?[{path:s,message:`Mode id "${p}" is reserved`}]:x.test(i.id)?[]:[{path:s,message:`"${i.id}" is not a valid CSS identifier`}]}),...E(t.modes,i=>i.id,i=>`collections[${e}].modes[${i}].id`,(i,r,s)=>`Duplicate mode id "${i}" at modes[${r}] and modes[${s}] of collection "${t.id}"`)]),({collections:a,variables:t})=>{let e=[],i=(n,c)=>{let o=new Map,l=new Set;for(let{item:u,path:f}of n){if(l.has(u.id))continue;l.add(u.id);let g=u.name??u.id,d=v(g);if(d===""){e.push({path:f,message:`"${g}" has no characters usable in a CSS variable name`});continue}let b=o.get(d);b===void 0?o.set(d,{label:g,path:f}):e.push({path:f,message:`"${b.label}" at ${b.path} and "${g}" at ${f} both become "${d}" ${c}`});}},r=t.some(n=>!n.collectionId)?[{item:{id:p,name:S},path:"the implicit base collection"}]:[];i([...r,...a.map((n,c)=>({item:n,path:`collections[${c}]`}))],"in this library"),a.forEach((n,c)=>i(n.modes.map((o,l)=>({item:o,path:`collections[${c}].modes[${l}]`})),`in collection "${n.id}"`));let s=new Map;return t.forEach((n,c)=>{let o=n.collectionId??p,l=s.get(o)??[];l.push({item:n,path:`variables[${c}]`}),s.set(o,l);}),s.forEach((n,c)=>i(n,`in collection "${c}"`)),e},({collections:a,variables:t})=>{let e=new Map(a.map(i=>[i.id,new Set(i.modes.map(r=>r.id))]));return t.flatMap((i,r)=>{let s=i.collectionId===void 0?new Set:e.get(i.collectionId);return s===void 0?[{path:`variables[${r}].collectionId`,message:i.collectionId===p?`Collection id "${p}" is reserved; omit the id for the implicit base collection`:`Collection "${i.collectionId}" is not declared`}]:Object.keys(i.modes??{}).filter(n=>!s.has(n)).map(n=>({path:`variables[${r}].modes.${n}`,message:`Mode "${n}" is not declared in collection "${i.collectionId??p}"`}))})},a=>{let t=new Set(a.variables.map(e=>e.id));return m(a).flatMap(({path:e,value:i,variableIndex:r})=>{if(i.type!=="ref")return [];let s=i.value.variableId;return s===a.variables[r]?.id?[{path:e,message:`Variable "${s}" references itself`}]:t.has(s)?[]:[{path:e,message:`Referenced variable "${s}" does not exist`}]})},a=>{let t=new Set(a.variables.map(e=>e.id));return m(a).flatMap(({path:e,value:i})=>i.type!=="raw"?[]:y(i.value).filter(r=>!t.has(r)).map(r=>({path:e,message:`var(${r}) does not name a variable in this library`})))},a=>m(a).flatMap(({path:t,value:e})=>e.type!=="raw"?[]:w(e.value).map(i=>({path:t,message:`var(${i}, \u2026) has a fallback; library variables do not support var() fallbacks`}))),a=>m(a).flatMap(({path:t,value:e,variableIndex:i})=>{let r=a.variables[i]?.type;return e.type==="ref"||e.type==="raw"?[]:e.type===r?[]:[{path:t,message:`A "${e.type}" value does not match the variable type "${r}"`}]}),a=>m(a).flatMap(({path:t,value:e})=>e.type!=="color"?[]:V(e.value)?[]:[{path:t,message:`"${e.value}" is not a valid color`}]),a=>m(a).flatMap(({path:t,value:e})=>e.type!=="length"?[]:Object.hasOwn(O,e.value.unit)?[]:[{path:t,message:`"${e.value.unit}" is not a supported length unit`}]),a=>m(a).flatMap(({path:t,value:e})=>{if(e.type!=="number"&&e.type!=="percentage")return [];let i=e.type==="number"?"-":"%";return e.value.unit===i?[]:[{path:t,message:`A ${e.type} value must have unit "${i}", not "${e.value.unit}"`}]}),a=>{let t=M(a);return m(a).flatMap(({path:e,value:i,variableIndex:r})=>{if(i.type!=="ref")return [];let s=a.variables[r],n=t.get(i.value.variableId)?.variable;return s===void 0||n===void 0?[]:n.id===s.id?[]:n.type===s.type?[]:[{path:e,message:`"${s.id}" is a ${s.type} variable but references "${n.id}", a ${n.type} variable`}]})},B];function z(a){return k.flatMap(t=>t(a))}export{S as RESERVED_BASE_COLLECTION_NAME,p as RESERVED_BASE_ID,N as WEBFLOW_COLLECTION_AT_RULE,y as extractVarReferences,v as sanitizeVariableName,z as validateStyleVariableCatalog};
|
package/dist/postcss.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
'use strict';var tn=require('postcss'),H=require('postcss-value-parser'),Ue=require('tinycolor2'),fs=require('fs'),path=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var tn__default=/*#__PURE__*/_interopDefault(tn);var H__default=/*#__PURE__*/_interopDefault(H);var Ue__default=/*#__PURE__*/_interopDefault(Ue);var O="base";var v="webflow-collection";var Pe=/\s/;function q(t){if(t===void 0)return false;let s=t.charCodeAt(0);return (s>=128||s>=97&&s<=122||s>=65&&s<=90||s>=48&&s<=57||t==="_")&&!Pe.test(t)}function ue(t){let s=t.length,i="",d=0,a=s;for(;d<s&&!q(t[d]);d+=1);for(;a>=1&&!q(t[a-1]);a-=1);for(;d<a;d+=1){let f=t[d];if(f==="/"){for(i[i.length-1]==="-"&&(i=i.substring(0,i.length-1));d<a;d+=1)if(f=t[d],q(f)){i+="--"+f;break}}else q(f)?i+=f:i[i.length-1]!=="-"&&(i+="-");}return i.toLowerCase()}function We(t){let s=t.toLowerCase().trim();if(!/^rgba?\([^)]+\)$/.test(s))return t;let i=s.split(/[,()]/).map(f=>f.trim()).filter(f=>f!==""&&!Number.isNaN(Number(f)));if(i.length<3)return t;i[3]=i[3]||"1";let d=parseFloat(i[3]),a=!Number.isNaN(d)&&d<1;return a||i.pop(),`${a?"rgba":"rgb"}(${i.join(", ")})`}function fe(t){return Ue__default.default(We(t)).isValid()}var qe=new Set("aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato transparent turquoise violet wheat white whitesmoke yellow yellowgreen".split(" ")),Be=/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,N=String.raw`[+-]?(?:\d+\.?\d*|\.\d+)`,Fe=new RegExp(`^${N}%?\\s*,\\s*${N}%?\\s*,\\s*${N}%?(?:\\s*,\\s*${N}%?)?$`),ze=new RegExp(`^${N}%?(?:deg|grad|rad|turn)?\\s+${N}%?\\s+${N}%?(?:\\s*/\\s*${N}%?)?$`),je=/^(rgba?|hsla?)\(([^()]*)\)$/i;function B(t){let s=t.trim();if(Be.test(s)||qe.has(s.toLowerCase()))return true;let i=je.exec(s);if(i===null)return false;let d=i[2].trim();return Fe.test(d)||ze.test(d)}var Y=new Set(["oklch","oklab","lab","lch","hwb","color","color-mix"]),pe=new Set(["rgb","rgba","hsl","hsla"]);var K=["number","length","percentage"],Ge=new Set(["px","em","rem","vh","vw","dvh","dvw","lvh","lvw","svh","svw","vmax","vmin","ch"]),ge=new Set(["deg","grad","rad","turn","s","ms","hz","khz","dpi","dpcm","dppx","x","fr"]),me=new Set(["","calc","clamp","min","max","abs","sign","round","mod","rem","sin","cos","tan","asin","acos","atan","atan2","pow","sqrt","hypot","log","exp"]),Ye=new Set(["pi","e","infinity","-infinity","nan"]),He=new Set(["+","-","*","/"]),Ke=new Set(["nearest","up","down","to-zero"]),Xe=new Set(["serif","sans-serif","monospace","cursive","fantasy","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","emoji","math","fangsong"]),ye=new Set(["auto","none","normal","inherit","initial","unset","revert","revert-layer","currentcolor","min-content","max-content","fit-content","inline","block","flex","grid","hidden","visible","bold","italic"]),X=/^-?[A-Za-z_\u0080-][\w\u0080--]*$/,_="has no matching Webflow variable type (a color, length, number, percentage or font family)",F=t=>t.filter(s=>s.type!=="space"&&s.type!=="comment");function D(t,s=[]){return {error:t,warnings:s}}function x(t,s=[]){return {unsupported:t,warnings:s}}function be(t){return `"${t}" is unquoted, so it can be a font family or a keyword; quote it ("${t}") or add type: "font-family"`}function w(t,s=[]){return {inferred:t,warnings:s}}function he(t){let s=H__default.default.unit(t);return s===false?void 0:{number:parseFloat(s.number),unit:s.unit.toLowerCase()}}function ve(t,s,i){for(let d of t)if(d.type==="function"){if(d.value.toLowerCase()==="var"){let a=F(d.nodes),f=a[0]?.type==="word"?a[0].value:void 0;f?.startsWith("--")&&(s.push(f),a.length>1&&i.push(f));}ve(d.nodes,s,i);}}function Ze(t,s){let i=F(t.nodes),d=i[0]?.type==="word"?i[0].value:void 0;return d===void 0||!d.startsWith("--")?D(`var() must name a custom property, e.g. var(--brand), not "${H__default.default.stringify(t)}"`):(i.length>1&&s.push(`The fallback of var(${d}, \u2026) is dropped: Webflow aliases ${d} without one`),w({value:{type:"ref",value:{variableId:d}},operands:[d]},s))}function Je(t,s,i){let d={length:false,percentage:false},a=[],f,g=false,h=V=>{for(let $ of V){if(f!==void 0)return;if($.type==="word"){let L=$.value;if(He.has(L)||Ke.has(L.toLowerCase())||Ye.has(L.toLowerCase()))continue;let C=he(L);C===void 0?f=`"${L}" is a keyword or unsupported token; ${ye.has(L.toLowerCase())?"keywords are not supported":"only numbers, dimensions and var() are supported"} in a math expression`:C.unit==="%"?d.percentage=true:C.unit!==""&&(ge.has(C.unit)?(f=`"${L}" ${_}`,g=true):d.length=true);}else if($.type==="string")f=`A string in a math expression ${_}`,g=true;else if($.type==="function"){let L=$.value.toLowerCase();if(L==="var"){let C=F($.nodes),p=C[0]?.type==="word"?C[0].value:void 0;p===void 0||!p.startsWith("--")?f="var() must name a custom property, e.g. var(--brand)":C.length>1?f=`var(${p}, \u2026) has a fallback; use var(${p}) without one inside an expression`:a.push(p);}else me.has(L)?h($.nodes):(f=`${L}() inside a math expression ${_}`,g=true);}}};return h(t.nodes),f!==void 0?g?x(f,i):D(f,i):w({value:{type:"raw",value:s},math:d,operands:a},i)}function Qe(t,s){let i=t.value.toLowerCase(),d=[],a=[];return ve(t.nodes,d,a),a.length>0?D(`var(${a[0]}, \u2026) has a fallback; use var(${a[0]}) without one inside an expression`):Y.has(i)||d.length>0?w({value:{type:"raw",value:s},type:"color",operands:d}):w({value:B(s)&&fe(s)?{type:"color",value:s}:{type:"raw",value:s},type:"color",operands:d})}function en(t,s){let i=t.toLowerCase();if(t.startsWith("#"))return B(t)?w({value:{type:"color",value:t},type:"color",operands:[]}):D(`"${t}" is not a valid color`);let d=he(t);if(d!==void 0){let{number:a,unit:f}=d;return f===""?w({value:{type:"number",value:{value:a,unit:"-"}},type:"number",operands:[],zero:a===0}):f==="%"?w({value:{type:"percentage",value:{value:a,unit:"%"}},type:"percentage",operands:[]}):Ge.has(f)?w({value:{type:"length",value:{value:a,unit:f}},type:"length",operands:[]}):ge.has(f)?x(`"${t}" ${_}`):w({value:{type:"raw",value:t},type:"length",operands:[]})}return Xe.has(i)?w({value:{type:"font-family",value:t},type:"font-family",operands:[]}):B(t)?w({value:{type:"color",value:t},type:"color",operands:[]}):ye.has(i)?x(`"${t}" is a keyword; keywords are not supported as variable values`):s&&X.test(t)?w({value:{type:"font-family",value:t},type:"font-family",operands:[]}):x(be(t))}function nn(t,s){let i=[[]];for(let a of t)a.type==="div"&&a.value===","?i.push([]):i[i.length-1].push(a);return i.every(a=>a.length>0&&(a.length===1&&a[0].type==="string"?true:a.every(f=>f.type==="word"&&X.test(f.value))))?w({value:{type:"font-family",value:s},type:"font-family",operands:[]}):x(`"${s}" ${_}`)}function $e(t,{type:s}={}){let i=t.trim(),d=[],a=F(H__default.default(i).nodes);if(a.length===0)return D("the value is empty");if(a.length===1){let g=a[0];if(g.type==="string")return w({value:{type:"font-family",value:i},type:"font-family",operands:[]});if(g.type==="word")return en(g.value,s==="font-family");if(g.type==="function"){let h=g.value.toLowerCase();if(h==="var")return Ze(g,d);if(me.has(h)&&h!=="")return Je(g,i,d);if(pe.has(h)||Y.has(h))return Qe(g,i)}return x(`"${i}" ${_}`)}return a.some(g=>g.type==="div"&&g.value===",")?nn(a,i):a.every(g=>g.type==="word"&&X.test(g.value))?s==="font-family"?w({value:{type:"font-family",value:i},type:"font-family",operands:[]}):x(be(i)):x(`"${i}" ${_}`)}function Z(t,s){if(t.zero)return [...K];if(t.type!==void 0)return [t.type];let i=t.operands.map(s);if(i.some(a=>a===void 0))return;if(t.math===void 0)return [i[0]];let d=i.find(a=>a==="color"||a==="font-family");return d!==void 0?{error:`A ${d} variable cannot be used in a math expression`}:t.math.length||i.includes("length")?["length"]:t.math.percentage||i.includes("percentage")?["percentage"]:["number"]}function J(t,s){return t.zero?s==="length"?{type:"length",value:{value:0,unit:"px"}}:s==="percentage"?{type:"percentage",value:{value:0,unit:"%"}}:t.value:t.value}var R="mode",S="variable",ee="Base",M=[":root",":host"],Se=["color","length","number","percentage","font-family"],Le=new Map([["selector",[v,R]],["label",[S]],["type",[S]]]),T=t=>`${t.file}:${t.line}:${t.column}`,Re=(t,s)=>`Add type: "${s}" to @${S} ${t}`,Ce=t=>t.filter(s=>s.type!=="space"&&s.type!=="comment"),P=t=>{let s=t.trim().replace(/\s+/g," ").replace(/\s*,\s*/g,", ").replace(/'/g,'"'),i="",d=0,a=false;for(let f of s)f==='"'?a=!a:!a&&(f==="["||f==="(")?d++:!a&&(f==="]"||f===")")&&d--,!a&&d===0&&">+~".includes(f)?i=`${i.trimEnd()} ${f} `:f===" "&&i.endsWith(" ")||(i+=f);return i.trim()},Q=t=>t.length<2?t.join(""):`${t.slice(0,-1).join(", ")} or ${t[t.length-1]}`;function on(t){let s=[],i=new Map,d=new Map,a=new Set,f=new Set,g=new Set,h=new Map,V=new Map,$=new Map,L=0,C=(e,n,r)=>s.push({severity:e,message:r,file:n.file,line:n.line,column:n.column}),p=(e,n)=>C("error",e,n),I=(e,n)=>C("warning",e,n),k=(e,n)=>({file:n,line:e.source?.start?.line??1,column:e.source?.start?.column??1});function ne(e,n,r,o){n!==void 0&&(e.label===void 0?(e.label=n,e.labelLoc=r):e.label!==n&&p(r,`${o} has conflicting labels "${e.label}" (${T(e.labelLoc)}) and "${n}" (${T(r)})`));}function Ee(e,n,r,o){e.selector===void 0?(e.selector=n,e.selectorLoc=r):P(e.selector)!==P(n)&&p(r,`${o} has conflicting selectors "${e.selector}" (${T(e.selectorLoc)}) and "${n}" (${T(r)})`);}function Ne(e,n){let r=i.get(e);return r===void 0&&(r={id:e,loc:n,modes:new Map},i.set(e,r)),r}function te(e){let n=Ce(H__default.default(e.params).nodes),[r,o]=n;if(n.length===0)return {};if(!(r?.type!=="word"||n.length>2||o!==void 0&&o.type!=="string"))return {id:r.value,label:o?.value}}function z(e,n,r){let o=e.prop.toLowerCase(),l=Le.get(o);if(l===void 0){let c=[...Le].filter(([,u])=>u.includes(n)).map(([u])=>u).join(", ");p(r,`Unknown descriptor "${e.prop}" in @${n}; expected ${c}`);}else if(!l.includes(n))p(r,`"${o}" is not a descriptor of @${n}; it belongs in ${l.map(c=>`@${c}`).join(" or ")}`);else if(e.important)p(r,`${o}: !important is not supported on a descriptor`);else return o}function j(e){let n=Ce(H__default.default(e.value).nodes);return n.length===1&&n[0].type==="string"?n[0].value:void 0}function oe(e,n,r,o){let l=j(e);if(l===void 0||l.trim()===""){let c=e.value.trim().replace(/^(["'])(.*)\1$/,"$2"),u=c.includes('"')?"'":'"';return p(o,c===""?'selector needs a quoted selector string, e.g. selector: ":root"':`selector must be a quoted string; use selector: ${u}${c}${u}`)}Ee(n,l,o,r);}function xe(e,n,r){let o=k(e,r),l=e.params.trim();if(!/^--[\w\u0080-\uffff-]+$/.test(l))return p(o,`@${S} needs a custom property name, e.g. @${S} --brand`);let c,u,m=new Set;for(let y of e.nodes??[]){if(y.type==="comment")continue;if(y.type!=="decl"){p(k(y,r),`@${S} can hold only descriptors`);continue}let E=k(y,r),A=z(y,S,E);if(A!==void 0){if(m.has(A)){p(E,`Duplicate descriptor "${A}" in @${S} ${l}`);continue}if(m.add(A),A==="label")c=j(y),c===void 0&&p(E,'label must be a quoted string, e.g. label: "color/brand"');else {let de=j(y)??y.value.trim();u=Se.find(Me=>Me===de.toLowerCase()),u===void 0&&p(E,`Unknown type "${de}"; expected ${Se.join(", ")}`);}}}let b=d.get(l);if(b!==void 0)return p(o,`${l} is defined twice: ${T(b.loc)} and ${T(o)}`);d.set(l,{id:l,label:c??l.slice(2),type:u,loc:o,collection:n});}function Ie(e,n,r){let o=k(e,r);if(n.id===void 0)return p(o,`The base collection cannot have modes; give the collection an id with @${v} <id>`);let l=te(e);if(l?.id===void 0)return p(o,`@${R} needs a mode id, e.g. @${R} dark "Dark"`);if(e.nodes===void 0)return p(o,`@${R} needs a block with a selector, e.g. @${R} dark "Dark" { selector: "[data-theme='dark']"; }`);let c=l.id;if(c===O)return p(o,`Mode id "${O}" is reserved`);let u=n.modes.get(c);u===void 0&&(u={loc:o},n.modes.set(c,u));let m=`Mode "${c}" of collection "${n.id}"`;ne(u,l.label,o,m);for(let b of e.nodes){if(b.type==="comment")continue;if(b.type!=="decl"){p(k(b,r),`@${R} can hold only descriptors`);continue}let y=k(b,r);if(b.prop.startsWith("--")){p(y,`${b.prop}: values don't go in @${R}; declare it in a top-level rule with the mode's selector`);continue}z(b,R,y)==="selector"&&(g.add(u),oe(b,u,m,y));}}function Ae(e,n){let r=k(e,n),o=te(e);if(o===void 0)return p(r,`Invalid @${v} prelude "${e.params}"; expected an id and an optional quoted label: @${v} colors "Colors"`);if(e.nodes===void 0)return p(r,`@${v} needs a block with @${S} rules`);let l=o.id===void 0||o.id===O,c=Ne(l?void 0:o.id,r);l?o.label!==void 0&&o.label!==ee&&p(r,`The base collection is always named "${ee}"; remove the label "${o.label}"`):ne(c,o.label,r,`Collection "${o.id}"`);for(let u of e.nodes){if(u.type==="comment")continue;let m=k(u,n);if(u.type==="decl")u.prop.startsWith("--")?p(m,`${u.prop} can't be declared in @${v}; export it with @${S} ${u.prop} and declare its value in a top-level rule`):z(u,v,m)==="selector"&&oe(u,c,`Collection "${c.id??O}"`,m);else if(u.type==="atrule"){let b=u.name.toLowerCase();b===R?Ie(u,c,n):b===S?xe(u,c,n):b===v?p(m,`@${v} must be at the top level of a stylesheet`):p(m,`@${v} can hold only selector, @${R} and @${S}`);}else p(m,`@${v} can hold only selector, @${R} and @${S}`);}}function re(e,n,r){let o=new Set([e.selector,...e.selectors].map(P));e.each(l=>{if(l.type!=="decl"||!l.prop.startsWith("--"))return;let c={value:l.value,important:l.important===true,loc:k(l,n),order:L++};for(let u of o){let m=r.get(u)??new Map;m.set(l.prop,[...m.get(l.prop)??[],c]),r.set(u,m);}});}function Oe(e,n){e.each(r=>{r.type==="rule"?re(r,n,V):r.type==="atrule"&&r.name.toLowerCase()!==v&&r.walkRules(o=>re(o,n,$));});}function _e({file:e,css:n}){let r;try{r=tn__default.default.parse(n,{from:e});}catch(l){let c=l;p({file:e,line:c.line??1,column:c.column??1},c.reason??String(l));return}Oe(r,e);let o=new Set;r.walkAtRules(l=>{let c=l.name.toLowerCase();if(c!==v&&c!==R&&c!==S)return;for(let m=l.parent;m;m=m.parent)if(o.has(m))return;o.add(l);let u=k(l,e);c===v&&l.parent?.type==="root"?Ae(l,e):c===v?p(u,`@${v} must be at the top level of a stylesheet, not inside a rule or at-rule such as @media, @layer or @supports`):p(u,`@${c} must be directly inside @${v}`);});}t.forEach(_e);for(let e of i.values())for(let[n,r]of e.modes)g.has(r)||p(r.loc,`@${R} ${n} needs a selector, e.g. selector: "[data-theme='${n}']"`);function U(e,n,r=V){let o=new Set;for(let l of e)r.get(P(l))?.get(n)?.forEach(c=>o.add(c));return [...o].sort((l,c)=>l.order-c.order)}function ie(e,n,r){let o=U(n,e),l=o[o.length-1];if(l!==void 0&&o.length>1){let c=o.every(u=>u.loc.file===l.loc.file);I(l.loc,`${e} is declared more than once on ${r} (${o.map(u=>c?`${u.loc.line}:${u.loc.column}`:T(u.loc)).join(", ")}); the last declaration is used`);}return l}let ae=(e,n,r)=>{let o=$e(n.value,{type:r});return o.warnings.forEach(l=>I(n.loc,`${e}: ${l}`)),o};for(let e of d.values()){let{id:n,collection:r}=e,o=r.selector,l=o??M.join(" or ");if(o!==void 0&&!M.includes(P(o)))for(let y of U(M,n))I(y.loc,`${n} is declared on :root or :host, but collection "${r.id??O}" reads its values from ${o}; this declaration is ignored`);let c=ie(n,o?[o]:M,l);if(c===void 0){a.add(n);let[y]=U(o?[o]:M,n,$);p(e.loc,`${n} is exported but not declared on ${l}${y?`. It is declared inside an at-rule (${T(y.loc)}), and declarations inside @media, @layer or @supports are not read`:""}`);continue}if(c.important){a.add(n),p(c.loc,`${n}: !important is not supported on a variable value`);continue}let{inferred:u,error:m,unsupported:b}=ae(n,c,e.type);u!==void 0?h.set(n,{inferred:u,text:c.value.trim(),loc:c.loc}):m!==void 0?(a.add(n),p(c.loc,`${n}: ${m}`)):e.type!==void 0?(a.add(n),p(c.loc,`${n} has type: ${e.type}, but ${b}`)):(f.add(n),I(c.loc,`${n}: ${b}. It is left out of the catalog (code-only).`));}let se=(e,n)=>{let r=e.operands.filter(l=>!d.has(l)||f.has(l));if(r.length===0)return true;let o=r.find(l=>!a.has(l));return o!==void 0&&p(n,f.has(o)?`var(${o}) refers to a variable that is left out of the catalog (code-only)`:`var(${o}) is not a library variable; var() can only reference variables exported with @${S} in @${v} blocks`),false};function De(e,n,r){let{id:o,type:l}=e;if(r.length===1){let[c]=r;if(l!==void 0&&l!==c){p(n.loc,`${o} has type: ${l}, but its value ${n.text} is a ${c}`);return}return c}if(l===void 0)return I(n.loc,`${o}: ${n.text} can be a ${Q(r)}. Using number. ${Re(o,"<type>")} to choose.`),"number";if(r.includes(l))return l;p(n.loc,`${o} has type: ${l}, but its value ${n.text} can only be a ${Q(r)}`);}let le=new Map,W=[];function G(e){let n=le.get(e);if(n!==void 0)return n??void 0;let r=d.get(e),o=h.get(e);if(r===void 0||o===void 0)return;let l=W.indexOf(e);if(l!==-1){p(r.loc,`Circular reference: ${[...W.slice(l),e].join(" \u2192 ")}`);return}W.push(e);let c=se(o.inferred,o.loc)?Z(o.inferred,G):void 0;W.pop();let u;return c!==void 0&&!Array.isArray(c)?p(o.loc,`${e}: ${c.error}`):c!==void 0&&(u=De(r,o,c)),le.set(e,u??null),u}let ce=[];for(let e of d.values()){let n=G(e.id),r=h.get(e.id);if(n===void 0||r===void 0)continue;let o={};for(let[l,c]of e.collection.modes){if(c.selector===void 0)continue;let u=ie(e.id,[c.selector],c.selector);if(u===void 0){let[E]=U([c.selector],e.id,$);E!==void 0&&I(E.loc,`${e.id} in mode "${l}" (${c.selector}) is declared only inside an at-rule. Declarations inside @media, @layer or @supports are not read, so the mode uses the base value.`);continue}if(u.important){p(u.loc,`${e.id}: !important is not supported on a variable value`);continue}let m=(E,A="")=>I(u.loc,`${e.id} in mode "${l}" (${c.selector}, ${T(u.loc)})${E}. The mode value is ignored, and the mode uses the base value.${A}`),b=ae(e.id,u,n);if(b.error!==void 0){p(u.loc,`${e.id}: ${b.error}`);continue}if(b.inferred===void 0){m(`: ${b.unsupported}`);continue}if(!se(b.inferred,u.loc))continue;let y=Z(b.inferred,G);y!==void 0&&(Array.isArray(y)?y.includes(n)?o[l]=J(b.inferred,n):m(` ${y.length===1?"is":"can be"} a ${Q(y)}, but its base value is a ${n}`,r.inferred.zero&&y.length===1&&K.includes(y[0])?` ${Re(e.id,y[0])} to use the mode value.`:""):p(u.loc,`${e.id}: ${y.error}`));}ce.push({id:e.id,name:e.label,type:n,...e.collection.id===void 0?{}:{collectionId:e.collection.id},value:J(r.inferred,n),...Object.keys(o).length>0?{modes:o}:{}});}return rn(i,d,p),{catalog:{collections:[...i.values()].filter(e=>e.id!==void 0&&Te(e,d)).map(e=>({id:e.id,...e.label===void 0?{}:{name:e.label},modes:[...e.modes].map(([n,r])=>({id:n,...r.label===void 0?{}:{name:r.label}}))})),variables:ce},diagnostics:s}}function rn(t,s,i){let d=(a,f)=>{let g=new Map;for(let h of a){let V=ue(h.label);if(V===""){i(h.loc,`"${h.label}" has no characters usable in a CSS variable name`);continue}let $=g.get(V);$===void 0?g.set(V,h):i(h.loc,`"${$.label}" (${T($.loc)}) and "${h.label}" (${T(h.loc)}) both become "${V}" ${f}`);}};d([...t.values()].filter(a=>a.id!==void 0||Te(a,s)).map(a=>({label:a.label??a.id??ee,loc:a.labelLoc??a.loc})),"in this library");for(let a of t.values()){let f=`in collection "${a.id??O}"`;d([...a.modes].map(([g,h])=>({label:h.label??g,loc:h.labelLoc??h.loc})),f),d([...s.values()].filter(g=>g.collection===a).map(g=>({label:g.label,loc:g.loc})),f);}}function Te(t,s){return [...s.values()].some(i=>i.collection===t)}var ln=new RegExp(`^${v}$`,"i");function ke(t){try{return fs.realpathSync.native(t)}catch{return path.resolve(t)}}var Ve=t=>{let s=new Set((t?.sources??[]).map(ke));return {postcssPlugin:"webflow-variables-strip-collections",Once(i){let d=false;i.walkAtRules(ln,f=>{f.remove(),d=true;});let a=i.source?.input.file;d&&a!==void 0&&!s.has(ke(a))&&t?.onUnlisted?.(a);}}};Ve.postcss=true;exports.parseVariableSources=on;exports.stripWebflowCollectionsPlugin=Ve;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { StyleVariableCatalog } from '@webflow/data-types/variables';
|
|
2
|
+
import { PluginCreator } from 'postcss';
|
|
3
|
+
|
|
4
|
+
type VariableSource = {
|
|
5
|
+
/** Path shown in diagnostics. */
|
|
6
|
+
file: string;
|
|
7
|
+
css: string;
|
|
8
|
+
};
|
|
9
|
+
type VariableDiagnostic = {
|
|
10
|
+
severity: "error" | "warning";
|
|
11
|
+
message: string;
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
};
|
|
16
|
+
type ParsedVariables = {
|
|
17
|
+
catalog: StyleVariableCatalog;
|
|
18
|
+
diagnostics: VariableDiagnostic[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Reads `@webflow-collection` blocks from the variable declaration files, in
|
|
22
|
+
* the given order, into one catalog. The blocks name the exported variables;
|
|
23
|
+
* their values come from the top-level rules of the same files. Every problem
|
|
24
|
+
* is a diagnostic; the catalog holds what could be read. Callers fail the
|
|
25
|
+
* build on any error and run `validateStyleVariableCatalog` on the catalog.
|
|
26
|
+
*/
|
|
27
|
+
declare function parseVariableSources(sources: readonly VariableSource[]): ParsedVariables;
|
|
28
|
+
|
|
29
|
+
type StripWebflowCollectionsOptions = {
|
|
30
|
+
/** Absolute paths of the files listed in `library.variables.sources`. */
|
|
31
|
+
sources?: readonly string[];
|
|
32
|
+
/** Called with a file that has `@webflow-collection` blocks but is not in `sources`. */
|
|
33
|
+
onUnlisted?: (file: string) => void;
|
|
34
|
+
};
|
|
35
|
+
/** Removes `@webflow-collection` blocks from every file; `onUnlisted` flags the ones not in `sources`. */
|
|
36
|
+
declare const stripWebflowCollectionsPlugin: PluginCreator<StripWebflowCollectionsOptions>;
|
|
37
|
+
|
|
38
|
+
export { type ParsedVariables, type StripWebflowCollectionsOptions, type VariableDiagnostic, type VariableSource, parseVariableSources, stripWebflowCollectionsPlugin };
|
package/dist/postcss.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import tn from'postcss';import H from'postcss-value-parser';import Ue from'tinycolor2';import {realpathSync}from'fs';import {resolve}from'path';var O="base";var v="webflow-collection";var Pe=/\s/;function q(t){if(t===void 0)return false;let s=t.charCodeAt(0);return (s>=128||s>=97&&s<=122||s>=65&&s<=90||s>=48&&s<=57||t==="_")&&!Pe.test(t)}function ue(t){let s=t.length,i="",d=0,a=s;for(;d<s&&!q(t[d]);d+=1);for(;a>=1&&!q(t[a-1]);a-=1);for(;d<a;d+=1){let f=t[d];if(f==="/"){for(i[i.length-1]==="-"&&(i=i.substring(0,i.length-1));d<a;d+=1)if(f=t[d],q(f)){i+="--"+f;break}}else q(f)?i+=f:i[i.length-1]!=="-"&&(i+="-");}return i.toLowerCase()}function We(t){let s=t.toLowerCase().trim();if(!/^rgba?\([^)]+\)$/.test(s))return t;let i=s.split(/[,()]/).map(f=>f.trim()).filter(f=>f!==""&&!Number.isNaN(Number(f)));if(i.length<3)return t;i[3]=i[3]||"1";let d=parseFloat(i[3]),a=!Number.isNaN(d)&&d<1;return a||i.pop(),`${a?"rgba":"rgb"}(${i.join(", ")})`}function fe(t){return Ue(We(t)).isValid()}var qe=new Set("aliceblue antiquewhite aqua aquamarine azure beige bisque black blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen darkslateblue darkslategray darkslategrey darkturquoise darkviolet deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan lightgoldenrodyellow lightgray lightgreen lightgrey lightpink lightsalmon lightseagreen lightskyblue lightslategray lightslategrey lightsteelblue lightyellow lime limegreen linen magenta maroon mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen mediumslateblue mediumspringgreen mediumturquoise mediumvioletred midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise palevioletred papayawhip peachpuff peru pink plum powderblue purple rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown seagreen seashell sienna silver skyblue slateblue slategray slategrey snow springgreen steelblue tan teal thistle tomato transparent turquoise violet wheat white whitesmoke yellow yellowgreen".split(" ")),Be=/^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i,N=String.raw`[+-]?(?:\d+\.?\d*|\.\d+)`,Fe=new RegExp(`^${N}%?\\s*,\\s*${N}%?\\s*,\\s*${N}%?(?:\\s*,\\s*${N}%?)?$`),ze=new RegExp(`^${N}%?(?:deg|grad|rad|turn)?\\s+${N}%?\\s+${N}%?(?:\\s*/\\s*${N}%?)?$`),je=/^(rgba?|hsla?)\(([^()]*)\)$/i;function B(t){let s=t.trim();if(Be.test(s)||qe.has(s.toLowerCase()))return true;let i=je.exec(s);if(i===null)return false;let d=i[2].trim();return Fe.test(d)||ze.test(d)}var Y=new Set(["oklch","oklab","lab","lch","hwb","color","color-mix"]),pe=new Set(["rgb","rgba","hsl","hsla"]);var K=["number","length","percentage"],Ge=new Set(["px","em","rem","vh","vw","dvh","dvw","lvh","lvw","svh","svw","vmax","vmin","ch"]),ge=new Set(["deg","grad","rad","turn","s","ms","hz","khz","dpi","dpcm","dppx","x","fr"]),me=new Set(["","calc","clamp","min","max","abs","sign","round","mod","rem","sin","cos","tan","asin","acos","atan","atan2","pow","sqrt","hypot","log","exp"]),Ye=new Set(["pi","e","infinity","-infinity","nan"]),He=new Set(["+","-","*","/"]),Ke=new Set(["nearest","up","down","to-zero"]),Xe=new Set(["serif","sans-serif","monospace","cursive","fantasy","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","emoji","math","fangsong"]),ye=new Set(["auto","none","normal","inherit","initial","unset","revert","revert-layer","currentcolor","min-content","max-content","fit-content","inline","block","flex","grid","hidden","visible","bold","italic"]),X=/^-?[A-Za-z_\u0080-][\w\u0080--]*$/,_="has no matching Webflow variable type (a color, length, number, percentage or font family)",F=t=>t.filter(s=>s.type!=="space"&&s.type!=="comment");function D(t,s=[]){return {error:t,warnings:s}}function x(t,s=[]){return {unsupported:t,warnings:s}}function be(t){return `"${t}" is unquoted, so it can be a font family or a keyword; quote it ("${t}") or add type: "font-family"`}function w(t,s=[]){return {inferred:t,warnings:s}}function he(t){let s=H.unit(t);return s===false?void 0:{number:parseFloat(s.number),unit:s.unit.toLowerCase()}}function ve(t,s,i){for(let d of t)if(d.type==="function"){if(d.value.toLowerCase()==="var"){let a=F(d.nodes),f=a[0]?.type==="word"?a[0].value:void 0;f?.startsWith("--")&&(s.push(f),a.length>1&&i.push(f));}ve(d.nodes,s,i);}}function Ze(t,s){let i=F(t.nodes),d=i[0]?.type==="word"?i[0].value:void 0;return d===void 0||!d.startsWith("--")?D(`var() must name a custom property, e.g. var(--brand), not "${H.stringify(t)}"`):(i.length>1&&s.push(`The fallback of var(${d}, \u2026) is dropped: Webflow aliases ${d} without one`),w({value:{type:"ref",value:{variableId:d}},operands:[d]},s))}function Je(t,s,i){let d={length:false,percentage:false},a=[],f,g=false,h=V=>{for(let $ of V){if(f!==void 0)return;if($.type==="word"){let L=$.value;if(He.has(L)||Ke.has(L.toLowerCase())||Ye.has(L.toLowerCase()))continue;let C=he(L);C===void 0?f=`"${L}" is a keyword or unsupported token; ${ye.has(L.toLowerCase())?"keywords are not supported":"only numbers, dimensions and var() are supported"} in a math expression`:C.unit==="%"?d.percentage=true:C.unit!==""&&(ge.has(C.unit)?(f=`"${L}" ${_}`,g=true):d.length=true);}else if($.type==="string")f=`A string in a math expression ${_}`,g=true;else if($.type==="function"){let L=$.value.toLowerCase();if(L==="var"){let C=F($.nodes),p=C[0]?.type==="word"?C[0].value:void 0;p===void 0||!p.startsWith("--")?f="var() must name a custom property, e.g. var(--brand)":C.length>1?f=`var(${p}, \u2026) has a fallback; use var(${p}) without one inside an expression`:a.push(p);}else me.has(L)?h($.nodes):(f=`${L}() inside a math expression ${_}`,g=true);}}};return h(t.nodes),f!==void 0?g?x(f,i):D(f,i):w({value:{type:"raw",value:s},math:d,operands:a},i)}function Qe(t,s){let i=t.value.toLowerCase(),d=[],a=[];return ve(t.nodes,d,a),a.length>0?D(`var(${a[0]}, \u2026) has a fallback; use var(${a[0]}) without one inside an expression`):Y.has(i)||d.length>0?w({value:{type:"raw",value:s},type:"color",operands:d}):w({value:B(s)&&fe(s)?{type:"color",value:s}:{type:"raw",value:s},type:"color",operands:d})}function en(t,s){let i=t.toLowerCase();if(t.startsWith("#"))return B(t)?w({value:{type:"color",value:t},type:"color",operands:[]}):D(`"${t}" is not a valid color`);let d=he(t);if(d!==void 0){let{number:a,unit:f}=d;return f===""?w({value:{type:"number",value:{value:a,unit:"-"}},type:"number",operands:[],zero:a===0}):f==="%"?w({value:{type:"percentage",value:{value:a,unit:"%"}},type:"percentage",operands:[]}):Ge.has(f)?w({value:{type:"length",value:{value:a,unit:f}},type:"length",operands:[]}):ge.has(f)?x(`"${t}" ${_}`):w({value:{type:"raw",value:t},type:"length",operands:[]})}return Xe.has(i)?w({value:{type:"font-family",value:t},type:"font-family",operands:[]}):B(t)?w({value:{type:"color",value:t},type:"color",operands:[]}):ye.has(i)?x(`"${t}" is a keyword; keywords are not supported as variable values`):s&&X.test(t)?w({value:{type:"font-family",value:t},type:"font-family",operands:[]}):x(be(t))}function nn(t,s){let i=[[]];for(let a of t)a.type==="div"&&a.value===","?i.push([]):i[i.length-1].push(a);return i.every(a=>a.length>0&&(a.length===1&&a[0].type==="string"?true:a.every(f=>f.type==="word"&&X.test(f.value))))?w({value:{type:"font-family",value:s},type:"font-family",operands:[]}):x(`"${s}" ${_}`)}function $e(t,{type:s}={}){let i=t.trim(),d=[],a=F(H(i).nodes);if(a.length===0)return D("the value is empty");if(a.length===1){let g=a[0];if(g.type==="string")return w({value:{type:"font-family",value:i},type:"font-family",operands:[]});if(g.type==="word")return en(g.value,s==="font-family");if(g.type==="function"){let h=g.value.toLowerCase();if(h==="var")return Ze(g,d);if(me.has(h)&&h!=="")return Je(g,i,d);if(pe.has(h)||Y.has(h))return Qe(g,i)}return x(`"${i}" ${_}`)}return a.some(g=>g.type==="div"&&g.value===",")?nn(a,i):a.every(g=>g.type==="word"&&X.test(g.value))?s==="font-family"?w({value:{type:"font-family",value:i},type:"font-family",operands:[]}):x(be(i)):x(`"${i}" ${_}`)}function Z(t,s){if(t.zero)return [...K];if(t.type!==void 0)return [t.type];let i=t.operands.map(s);if(i.some(a=>a===void 0))return;if(t.math===void 0)return [i[0]];let d=i.find(a=>a==="color"||a==="font-family");return d!==void 0?{error:`A ${d} variable cannot be used in a math expression`}:t.math.length||i.includes("length")?["length"]:t.math.percentage||i.includes("percentage")?["percentage"]:["number"]}function J(t,s){return t.zero?s==="length"?{type:"length",value:{value:0,unit:"px"}}:s==="percentage"?{type:"percentage",value:{value:0,unit:"%"}}:t.value:t.value}var R="mode",S="variable",ee="Base",M=[":root",":host"],Se=["color","length","number","percentage","font-family"],Le=new Map([["selector",[v,R]],["label",[S]],["type",[S]]]),T=t=>`${t.file}:${t.line}:${t.column}`,Re=(t,s)=>`Add type: "${s}" to @${S} ${t}`,Ce=t=>t.filter(s=>s.type!=="space"&&s.type!=="comment"),P=t=>{let s=t.trim().replace(/\s+/g," ").replace(/\s*,\s*/g,", ").replace(/'/g,'"'),i="",d=0,a=false;for(let f of s)f==='"'?a=!a:!a&&(f==="["||f==="(")?d++:!a&&(f==="]"||f===")")&&d--,!a&&d===0&&">+~".includes(f)?i=`${i.trimEnd()} ${f} `:f===" "&&i.endsWith(" ")||(i+=f);return i.trim()},Q=t=>t.length<2?t.join(""):`${t.slice(0,-1).join(", ")} or ${t[t.length-1]}`;function on(t){let s=[],i=new Map,d=new Map,a=new Set,f=new Set,g=new Set,h=new Map,V=new Map,$=new Map,L=0,C=(e,n,r)=>s.push({severity:e,message:r,file:n.file,line:n.line,column:n.column}),p=(e,n)=>C("error",e,n),I=(e,n)=>C("warning",e,n),k=(e,n)=>({file:n,line:e.source?.start?.line??1,column:e.source?.start?.column??1});function ne(e,n,r,o){n!==void 0&&(e.label===void 0?(e.label=n,e.labelLoc=r):e.label!==n&&p(r,`${o} has conflicting labels "${e.label}" (${T(e.labelLoc)}) and "${n}" (${T(r)})`));}function Ee(e,n,r,o){e.selector===void 0?(e.selector=n,e.selectorLoc=r):P(e.selector)!==P(n)&&p(r,`${o} has conflicting selectors "${e.selector}" (${T(e.selectorLoc)}) and "${n}" (${T(r)})`);}function Ne(e,n){let r=i.get(e);return r===void 0&&(r={id:e,loc:n,modes:new Map},i.set(e,r)),r}function te(e){let n=Ce(H(e.params).nodes),[r,o]=n;if(n.length===0)return {};if(!(r?.type!=="word"||n.length>2||o!==void 0&&o.type!=="string"))return {id:r.value,label:o?.value}}function z(e,n,r){let o=e.prop.toLowerCase(),l=Le.get(o);if(l===void 0){let c=[...Le].filter(([,u])=>u.includes(n)).map(([u])=>u).join(", ");p(r,`Unknown descriptor "${e.prop}" in @${n}; expected ${c}`);}else if(!l.includes(n))p(r,`"${o}" is not a descriptor of @${n}; it belongs in ${l.map(c=>`@${c}`).join(" or ")}`);else if(e.important)p(r,`${o}: !important is not supported on a descriptor`);else return o}function j(e){let n=Ce(H(e.value).nodes);return n.length===1&&n[0].type==="string"?n[0].value:void 0}function oe(e,n,r,o){let l=j(e);if(l===void 0||l.trim()===""){let c=e.value.trim().replace(/^(["'])(.*)\1$/,"$2"),u=c.includes('"')?"'":'"';return p(o,c===""?'selector needs a quoted selector string, e.g. selector: ":root"':`selector must be a quoted string; use selector: ${u}${c}${u}`)}Ee(n,l,o,r);}function xe(e,n,r){let o=k(e,r),l=e.params.trim();if(!/^--[\w\u0080-\uffff-]+$/.test(l))return p(o,`@${S} needs a custom property name, e.g. @${S} --brand`);let c,u,m=new Set;for(let y of e.nodes??[]){if(y.type==="comment")continue;if(y.type!=="decl"){p(k(y,r),`@${S} can hold only descriptors`);continue}let E=k(y,r),A=z(y,S,E);if(A!==void 0){if(m.has(A)){p(E,`Duplicate descriptor "${A}" in @${S} ${l}`);continue}if(m.add(A),A==="label")c=j(y),c===void 0&&p(E,'label must be a quoted string, e.g. label: "color/brand"');else {let de=j(y)??y.value.trim();u=Se.find(Me=>Me===de.toLowerCase()),u===void 0&&p(E,`Unknown type "${de}"; expected ${Se.join(", ")}`);}}}let b=d.get(l);if(b!==void 0)return p(o,`${l} is defined twice: ${T(b.loc)} and ${T(o)}`);d.set(l,{id:l,label:c??l.slice(2),type:u,loc:o,collection:n});}function Ie(e,n,r){let o=k(e,r);if(n.id===void 0)return p(o,`The base collection cannot have modes; give the collection an id with @${v} <id>`);let l=te(e);if(l?.id===void 0)return p(o,`@${R} needs a mode id, e.g. @${R} dark "Dark"`);if(e.nodes===void 0)return p(o,`@${R} needs a block with a selector, e.g. @${R} dark "Dark" { selector: "[data-theme='dark']"; }`);let c=l.id;if(c===O)return p(o,`Mode id "${O}" is reserved`);let u=n.modes.get(c);u===void 0&&(u={loc:o},n.modes.set(c,u));let m=`Mode "${c}" of collection "${n.id}"`;ne(u,l.label,o,m);for(let b of e.nodes){if(b.type==="comment")continue;if(b.type!=="decl"){p(k(b,r),`@${R} can hold only descriptors`);continue}let y=k(b,r);if(b.prop.startsWith("--")){p(y,`${b.prop}: values don't go in @${R}; declare it in a top-level rule with the mode's selector`);continue}z(b,R,y)==="selector"&&(g.add(u),oe(b,u,m,y));}}function Ae(e,n){let r=k(e,n),o=te(e);if(o===void 0)return p(r,`Invalid @${v} prelude "${e.params}"; expected an id and an optional quoted label: @${v} colors "Colors"`);if(e.nodes===void 0)return p(r,`@${v} needs a block with @${S} rules`);let l=o.id===void 0||o.id===O,c=Ne(l?void 0:o.id,r);l?o.label!==void 0&&o.label!==ee&&p(r,`The base collection is always named "${ee}"; remove the label "${o.label}"`):ne(c,o.label,r,`Collection "${o.id}"`);for(let u of e.nodes){if(u.type==="comment")continue;let m=k(u,n);if(u.type==="decl")u.prop.startsWith("--")?p(m,`${u.prop} can't be declared in @${v}; export it with @${S} ${u.prop} and declare its value in a top-level rule`):z(u,v,m)==="selector"&&oe(u,c,`Collection "${c.id??O}"`,m);else if(u.type==="atrule"){let b=u.name.toLowerCase();b===R?Ie(u,c,n):b===S?xe(u,c,n):b===v?p(m,`@${v} must be at the top level of a stylesheet`):p(m,`@${v} can hold only selector, @${R} and @${S}`);}else p(m,`@${v} can hold only selector, @${R} and @${S}`);}}function re(e,n,r){let o=new Set([e.selector,...e.selectors].map(P));e.each(l=>{if(l.type!=="decl"||!l.prop.startsWith("--"))return;let c={value:l.value,important:l.important===true,loc:k(l,n),order:L++};for(let u of o){let m=r.get(u)??new Map;m.set(l.prop,[...m.get(l.prop)??[],c]),r.set(u,m);}});}function Oe(e,n){e.each(r=>{r.type==="rule"?re(r,n,V):r.type==="atrule"&&r.name.toLowerCase()!==v&&r.walkRules(o=>re(o,n,$));});}function _e({file:e,css:n}){let r;try{r=tn.parse(n,{from:e});}catch(l){let c=l;p({file:e,line:c.line??1,column:c.column??1},c.reason??String(l));return}Oe(r,e);let o=new Set;r.walkAtRules(l=>{let c=l.name.toLowerCase();if(c!==v&&c!==R&&c!==S)return;for(let m=l.parent;m;m=m.parent)if(o.has(m))return;o.add(l);let u=k(l,e);c===v&&l.parent?.type==="root"?Ae(l,e):c===v?p(u,`@${v} must be at the top level of a stylesheet, not inside a rule or at-rule such as @media, @layer or @supports`):p(u,`@${c} must be directly inside @${v}`);});}t.forEach(_e);for(let e of i.values())for(let[n,r]of e.modes)g.has(r)||p(r.loc,`@${R} ${n} needs a selector, e.g. selector: "[data-theme='${n}']"`);function U(e,n,r=V){let o=new Set;for(let l of e)r.get(P(l))?.get(n)?.forEach(c=>o.add(c));return [...o].sort((l,c)=>l.order-c.order)}function ie(e,n,r){let o=U(n,e),l=o[o.length-1];if(l!==void 0&&o.length>1){let c=o.every(u=>u.loc.file===l.loc.file);I(l.loc,`${e} is declared more than once on ${r} (${o.map(u=>c?`${u.loc.line}:${u.loc.column}`:T(u.loc)).join(", ")}); the last declaration is used`);}return l}let ae=(e,n,r)=>{let o=$e(n.value,{type:r});return o.warnings.forEach(l=>I(n.loc,`${e}: ${l}`)),o};for(let e of d.values()){let{id:n,collection:r}=e,o=r.selector,l=o??M.join(" or ");if(o!==void 0&&!M.includes(P(o)))for(let y of U(M,n))I(y.loc,`${n} is declared on :root or :host, but collection "${r.id??O}" reads its values from ${o}; this declaration is ignored`);let c=ie(n,o?[o]:M,l);if(c===void 0){a.add(n);let[y]=U(o?[o]:M,n,$);p(e.loc,`${n} is exported but not declared on ${l}${y?`. It is declared inside an at-rule (${T(y.loc)}), and declarations inside @media, @layer or @supports are not read`:""}`);continue}if(c.important){a.add(n),p(c.loc,`${n}: !important is not supported on a variable value`);continue}let{inferred:u,error:m,unsupported:b}=ae(n,c,e.type);u!==void 0?h.set(n,{inferred:u,text:c.value.trim(),loc:c.loc}):m!==void 0?(a.add(n),p(c.loc,`${n}: ${m}`)):e.type!==void 0?(a.add(n),p(c.loc,`${n} has type: ${e.type}, but ${b}`)):(f.add(n),I(c.loc,`${n}: ${b}. It is left out of the catalog (code-only).`));}let se=(e,n)=>{let r=e.operands.filter(l=>!d.has(l)||f.has(l));if(r.length===0)return true;let o=r.find(l=>!a.has(l));return o!==void 0&&p(n,f.has(o)?`var(${o}) refers to a variable that is left out of the catalog (code-only)`:`var(${o}) is not a library variable; var() can only reference variables exported with @${S} in @${v} blocks`),false};function De(e,n,r){let{id:o,type:l}=e;if(r.length===1){let[c]=r;if(l!==void 0&&l!==c){p(n.loc,`${o} has type: ${l}, but its value ${n.text} is a ${c}`);return}return c}if(l===void 0)return I(n.loc,`${o}: ${n.text} can be a ${Q(r)}. Using number. ${Re(o,"<type>")} to choose.`),"number";if(r.includes(l))return l;p(n.loc,`${o} has type: ${l}, but its value ${n.text} can only be a ${Q(r)}`);}let le=new Map,W=[];function G(e){let n=le.get(e);if(n!==void 0)return n??void 0;let r=d.get(e),o=h.get(e);if(r===void 0||o===void 0)return;let l=W.indexOf(e);if(l!==-1){p(r.loc,`Circular reference: ${[...W.slice(l),e].join(" \u2192 ")}`);return}W.push(e);let c=se(o.inferred,o.loc)?Z(o.inferred,G):void 0;W.pop();let u;return c!==void 0&&!Array.isArray(c)?p(o.loc,`${e}: ${c.error}`):c!==void 0&&(u=De(r,o,c)),le.set(e,u??null),u}let ce=[];for(let e of d.values()){let n=G(e.id),r=h.get(e.id);if(n===void 0||r===void 0)continue;let o={};for(let[l,c]of e.collection.modes){if(c.selector===void 0)continue;let u=ie(e.id,[c.selector],c.selector);if(u===void 0){let[E]=U([c.selector],e.id,$);E!==void 0&&I(E.loc,`${e.id} in mode "${l}" (${c.selector}) is declared only inside an at-rule. Declarations inside @media, @layer or @supports are not read, so the mode uses the base value.`);continue}if(u.important){p(u.loc,`${e.id}: !important is not supported on a variable value`);continue}let m=(E,A="")=>I(u.loc,`${e.id} in mode "${l}" (${c.selector}, ${T(u.loc)})${E}. The mode value is ignored, and the mode uses the base value.${A}`),b=ae(e.id,u,n);if(b.error!==void 0){p(u.loc,`${e.id}: ${b.error}`);continue}if(b.inferred===void 0){m(`: ${b.unsupported}`);continue}if(!se(b.inferred,u.loc))continue;let y=Z(b.inferred,G);y!==void 0&&(Array.isArray(y)?y.includes(n)?o[l]=J(b.inferred,n):m(` ${y.length===1?"is":"can be"} a ${Q(y)}, but its base value is a ${n}`,r.inferred.zero&&y.length===1&&K.includes(y[0])?` ${Re(e.id,y[0])} to use the mode value.`:""):p(u.loc,`${e.id}: ${y.error}`));}ce.push({id:e.id,name:e.label,type:n,...e.collection.id===void 0?{}:{collectionId:e.collection.id},value:J(r.inferred,n),...Object.keys(o).length>0?{modes:o}:{}});}return rn(i,d,p),{catalog:{collections:[...i.values()].filter(e=>e.id!==void 0&&Te(e,d)).map(e=>({id:e.id,...e.label===void 0?{}:{name:e.label},modes:[...e.modes].map(([n,r])=>({id:n,...r.label===void 0?{}:{name:r.label}}))})),variables:ce},diagnostics:s}}function rn(t,s,i){let d=(a,f)=>{let g=new Map;for(let h of a){let V=ue(h.label);if(V===""){i(h.loc,`"${h.label}" has no characters usable in a CSS variable name`);continue}let $=g.get(V);$===void 0?g.set(V,h):i(h.loc,`"${$.label}" (${T($.loc)}) and "${h.label}" (${T(h.loc)}) both become "${V}" ${f}`);}};d([...t.values()].filter(a=>a.id!==void 0||Te(a,s)).map(a=>({label:a.label??a.id??ee,loc:a.labelLoc??a.loc})),"in this library");for(let a of t.values()){let f=`in collection "${a.id??O}"`;d([...a.modes].map(([g,h])=>({label:h.label??g,loc:h.labelLoc??h.loc})),f),d([...s.values()].filter(g=>g.collection===a).map(g=>({label:g.label,loc:g.loc})),f);}}function Te(t,s){return [...s.values()].some(i=>i.collection===t)}var ln=new RegExp(`^${v}$`,"i");function ke(t){try{return realpathSync.native(t)}catch{return resolve(t)}}var Ve=t=>{let s=new Set((t?.sources??[]).map(ke));return {postcssPlugin:"webflow-variables-strip-collections",Once(i){let d=false;i.walkAtRules(ln,f=>{f.remove(),d=true;});let a=i.source?.input.file;d&&a!==void 0&&!s.has(ke(a))&&t?.onUnlisted?.(a);}}};Ve.postcss=true;export{on as parseVariableSources,Ve as stripWebflowCollectionsPlugin};
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webflow/variables",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2.9.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./postcss": {
|
|
18
|
+
"types": "./dist/postcss.d.ts",
|
|
19
|
+
"import": "./dist/postcss.js",
|
|
20
|
+
"require": "./dist/postcss.cjs",
|
|
21
|
+
"default": "./dist/postcss.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"typesVersions": {
|
|
26
|
+
"*": {
|
|
27
|
+
"postcss": [
|
|
28
|
+
"./dist/postcss.d.ts"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"CHANGELOG.md",
|
|
34
|
+
"dist/**/*"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"lint": "eslint .",
|
|
39
|
+
"test": "jest tests --ci",
|
|
40
|
+
"test:unit": "jest tests --ci --testPathIgnorePatterns=/node_modules/ --testPathIgnorePatterns=package-entry.test",
|
|
41
|
+
"test:local": "jest tests",
|
|
42
|
+
"type-check": "tsc -p tsconfig.typecheck.json"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@webflow/data-types": "^2.9.0",
|
|
46
|
+
"postcss": "^8.5.23",
|
|
47
|
+
"postcss-value-parser": "^4.2.0",
|
|
48
|
+
"tinycolor2": "1.4.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/jest": "^30.0.0",
|
|
52
|
+
"@types/tinycolor2": "1.4.3",
|
|
53
|
+
"eslint-config-custom": "*",
|
|
54
|
+
"jest": "^30.4.2",
|
|
55
|
+
"jest-config-custom": "*",
|
|
56
|
+
"tsconfig": "*",
|
|
57
|
+
"tsup": "^8.4.0",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
|
+
}
|
|
60
|
+
}
|