@cssints/gradient-2d 0.0.0-stage → 0.4.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/LICENSE +21 -0
- package/README.md +157 -2
- package/dist/index.d.mts +41 -0
- package/dist/index.mjs +255 -0
- package/dist/runtime.d.mts +1 -0
- package/dist/runtime.mjs +207 -0
- package/package.json +40 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexei Accio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,158 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cssints/gradient-2d
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`mesh-gradient()` and `freeform-gradient()` for [cssints](../../README.md), as styles of `cssints/plugin` `scope()`.
|
|
4
|
+
Where the browser has the CSS function, the style uses it. Where it does not, a runtime draws the gradient on a canvas and
|
|
5
|
+
sets the drawing as the background. **No browser has either function yet**, so today the runtime draws every gradient; the
|
|
6
|
+
first paint is freeform's stack of radial gradients or mesh's average colour.
|
|
7
|
+
|
|
8
|
+
**Status (2026-10-17).** The CSSWG adopted mesh and freeform gradients into css-images-5 on 2026-10-14, as an explainer
|
|
9
|
+
([css-images-5/explainers/2d-gradients.md](https://github.com/w3c/csswg-drafts/blob/main/css-images-5/explainers/2d-gradients.md)).
|
|
10
|
+
That is not a spec draft: the syntax can still change. Freeform may be renamed `diffusion-gradient()`, and neither
|
|
11
|
+
function's rendering rule is public, so both drawings here are **this package's algorithm**, not a browser's or
|
|
12
|
+
Illustrator's.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { cn, color } from "cssints" with { type: "cssints" };
|
|
16
|
+
import { freeformGradient, meshGradient } from "@cssints/gradient-2d" with { type: "cssints" };
|
|
17
|
+
|
|
18
|
+
export const hero = cn(meshGradient("red, blue; green, yellow"), color("white"));
|
|
19
|
+
export const panel = freeformGradient("red top left, gold 4 60% 40%, yellow bottom right");
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
@layer _.k {
|
|
24
|
+
._gradient2d-core {
|
|
25
|
+
background-image: var(--gradient2d-fallback);
|
|
26
|
+
background-repeat: no-repeat;
|
|
27
|
+
background-size: 100% 100%;
|
|
28
|
+
}
|
|
29
|
+
/* only where the browser parses the function */
|
|
30
|
+
@supports (background-image: mesh-gradient(red, blue; green, yellow)) {
|
|
31
|
+
._gradient2d-core {
|
|
32
|
+
background-image: var(--gradient2d-native);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
._gradient2d-ff {
|
|
36
|
+
background-image: var(--gradient2d-ff-fallback);
|
|
37
|
+
background-repeat: no-repeat;
|
|
38
|
+
background-size: 100% 100%;
|
|
39
|
+
}
|
|
40
|
+
@supports (background-image: freeform-gradient(red top left, blue bottom right)) {
|
|
41
|
+
._gradient2d-ff {
|
|
42
|
+
background-image: var(--gradient2d-ff-native);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
@layer _.a {
|
|
47
|
+
._1gxhgq5 {
|
|
48
|
+
--gradient2d-native: mesh-gradient(red, blue; green, yellow);
|
|
49
|
+
--gradient2d-fallback: linear-gradient(#918d78, #918d78);
|
|
50
|
+
--gradient2d-data: '{"space":"oklab","rows":[[[1,0,0,1],[0,0,1,1]],[[0,0.502,0,1],[1,1,0,1]]]}';
|
|
51
|
+
}
|
|
52
|
+
._1ps31mi {
|
|
53
|
+
--gradient2d-ff-native: freeform-gradient(red top left, gold 4 60% 40%, yellow bottom right);
|
|
54
|
+
--gradient2d-ff-fallback:
|
|
55
|
+
radial-gradient(circle at 0% 0%, #ff0000, transparent),
|
|
56
|
+
radial-gradient(circle at 60% 40%, #ffd700, transparent),
|
|
57
|
+
radial-gradient(circle at 100% 100%, #ffff00, transparent);
|
|
58
|
+
--gradient2d-ff-data: '{"space":"oklab","points":[[0,0,1,[1,0,0,1]],[0.6,0.4,4,[1,0.8431,0,1]],[1,1,1,[1,1,0,1]]]}';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The native value is the spec's text, as written. The data is the stops or points as sRGB numbers, for the runtime. Mesh's
|
|
64
|
+
fallback is the average colour of its stops, in oklab; freeform's is a radial layer per point.
|
|
65
|
+
|
|
66
|
+
## Syntax
|
|
67
|
+
|
|
68
|
+
### `meshGradient(mesh)`
|
|
69
|
+
|
|
70
|
+
- **`meshGradient(mesh)`**, one string: rows of stops, rows separated by `;` from the top of the box down, stops by `,`
|
|
71
|
+
from the left. A stop is a colour: `red`, `#888`, `oklch(...)`, or `var(--token)`.
|
|
72
|
+
- **`in oklab`** or **`in srgb`** may open the text, as the spec has it: `meshGradient("in oklab, red, blue; green, yellow")`.
|
|
73
|
+
A colour after it (`in oklab red, ...`) is the colour outside the mesh. It is checked and kept in the native value, but
|
|
74
|
+
never drawn: the grid covers the whole box.
|
|
75
|
+
- **Build errors**, at the call: rows of different lengths, fewer than 2 rows of 2 stops, a colour that does not parse,
|
|
76
|
+
another colour space than oklab or srgb, and a stop with a position, `by` or `with`.
|
|
77
|
+
- **Not supported yet:** positions (`red 0% 0%`) and the control points (`by`, `with`). The grid is regular: the corners
|
|
78
|
+
sit at the corners of the box and the other stops are spaced evenly. A position needs the grid warped, which the
|
|
79
|
+
runtime does not do yet. Both are build errors, not ignored.
|
|
80
|
+
- **`var(--token)` stops** work. The browser resolves them: the drawing waits for the runtime, and the first paint has no
|
|
81
|
+
fallback (`--gradient2d-fallback: none`).
|
|
82
|
+
|
|
83
|
+
### `freeformGradient(points)`
|
|
84
|
+
|
|
85
|
+
- **`freeformGradient(points)`**, one string: points separated by `,`, each `<color> <number>? <position>`. The number is
|
|
86
|
+
the point's **spread**, its weight against the others (1 when it is left out). The position is a keyword
|
|
87
|
+
(`top left`, `bottom right`, `center`) or one or two percentages, as `background-position` reads it
|
|
88
|
+
(`gold 4 60% 40%`, `blue right 10%` is 90% across).
|
|
89
|
+
- **`in oklab`** or **`in srgb`** may open the text, as the spec has it: `freeformGradient("in srgb, red 20% 30%, blue")`.
|
|
90
|
+
The function has no colour outside the points, so a colour after the method is a build error.
|
|
91
|
+
- **Build errors**, at the call: a point without a position, fewer than 2 points, two points at the same position (their
|
|
92
|
+
weights are both infinite there), a colour that does not parse, a length (`10px` needs the box), a line (a point with
|
|
93
|
+
more than one position, the spec's future work) and a shape.
|
|
94
|
+
- **Not supported yet:** lines, shapes and lengths, as above. A `var(--token)` point works: the browser resolves it, in
|
|
95
|
+
the fallback too.
|
|
96
|
+
|
|
97
|
+
## Browsers
|
|
98
|
+
|
|
99
|
+
- **Native**: neither function is in any browser. The installed Chrome of `test:browser` reports no support, so the
|
|
100
|
+
runtime draws. The `@supports` tests are the spec's simplest examples: `mesh-gradient(red, blue; green, yellow)` and
|
|
101
|
+
`freeform-gradient(red top left, blue bottom right)`. The two are decided apart, so a browser with mesh and without
|
|
102
|
+
freeform takes mesh's branch only (`_gradient2d-ff` is a class of its own).
|
|
103
|
+
- **The runtime** is in every build whose browserslist targets include a browser that BCD tracks. The table in
|
|
104
|
+
`@cssints/css-grammar` says every such browser lacks `css.types.gradient.mesh-gradient` and
|
|
105
|
+
`css.types.gradient.freeform-gradient` (BCD has no entry for either). When BCD or the table has a browser with one of
|
|
106
|
+
them, the runtime ships only to the targets that lack it.
|
|
107
|
+
- **The runtime needs** `ResizeObserver`, canvas and `MutationObserver` (where the page has them, the drawing works).
|
|
108
|
+
|
|
109
|
+
## The runtime
|
|
110
|
+
|
|
111
|
+
- **The algorithm is this package's, not the spec's and not Illustrator's.** Mesh is a bicubic (Catmull-Rom) through the
|
|
112
|
+
4 × 4 stops around a point; the spec interpolates a tensor-product patch with Bézier control points, which this package
|
|
113
|
+
does not model. Freeform is **inverse-distance weighting**: the colour at a point is the weighted mean of the stops,
|
|
114
|
+
the weight of a stop `spread / d²`, in oklab. The spec suggests a Delaunay triangulation; freeform's rendering rule is
|
|
115
|
+
not public (Josh Tumath, 2026-06-25), so no browser or editor agrees on it yet. IDW was chosen over a Delaunay because
|
|
116
|
+
every pixel of the box then has a weight: a triangulation paints only the points' convex hull and leaves the rest of
|
|
117
|
+
the box undefined (the spike in `docs/research/2d-gradients.md` saw exactly that hard hull edge; this package's own
|
|
118
|
+
reading of an IDW render is soft round blobs, one per stop, with the spread visible). The drawing is a close relative
|
|
119
|
+
of a browser's freeform, not the same surface. Compare by eye.
|
|
120
|
+
- **Resolution.** The canvas is a quarter of the box (64 px at least) and the browser stretches it to the box. A large box
|
|
121
|
+
is soft, not sharp. In Chrome (the installed one, 1000 × 600, median of six resizes between 800 and 1000 px, the canvas
|
|
122
|
+
250 × 150, the time to the new background, layout and PNG encoding included): the mesh redraws in 49 ms, the freeform in
|
|
123
|
+
34 ms. The distances of freeform are measured in the box's pixels (height / width), as the fallback's radial layers are,
|
|
124
|
+
so a point's influence is a circle on screen and not an ellipse.
|
|
125
|
+
- **Size.** 3.9 kB minified, 1.9 kB gzipped for both rules (measured with rolldown's minifier; the `dist/runtime.mjs` of
|
|
126
|
+
`vp pack` is not minified). Mesh alone is 2.9 kB / 1.6 kB, as it was before freeform joined it; `@cssints/balance`'s
|
|
127
|
+
script is 552 bytes.
|
|
128
|
+
- **Data and tokens.** The runtime reads `--gradient2d-data` or `--gradient2d-ff-data` (JSON in a CSS string). A `var()`
|
|
129
|
+
stop is read through a hidden child of the element, so it inherits the element's custom properties, and a 1 × 1 canvas
|
|
130
|
+
turns the computed colour into sRGB.
|
|
131
|
+
- **Redraw.** On a size change, and when an element gets the marker class (added to the page or given the class later).
|
|
132
|
+
Both rules share one `ResizeObserver` and one `MutationObserver` (src/canvas.ts). A drawing that is outdated when a newer
|
|
133
|
+
one finishes is dropped. A previous blob URL is revoked.
|
|
134
|
+
- **First paint without JS.** Freeform's fallback is a stack of radial gradients, one layer per point: its colour at the
|
|
135
|
+
point fading to transparent at the farthest corner of the box. It is an approximation with its own blend (alpha, in the
|
|
136
|
+
browser's space, not `spread / d²` in oklab; the spread is not in it), and it holds a `var()` point, which mesh's
|
|
137
|
+
average colour cannot.
|
|
138
|
+
- **CSP.** The fallback is a `blob:` URL. The page needs `img-src blob:` (or `img-src` with `blob:` in it) for the drawing.
|
|
139
|
+
- **SSR.** The runtime does nothing without `document`. The first paint is the kernel's fallback.
|
|
140
|
+
|
|
141
|
+
## Limits
|
|
142
|
+
|
|
143
|
+
- **Freeform's fallback and its drawing do not agree pixel for pixel.** The stack of radial gradients is a low-fidelity
|
|
144
|
+
first paint, not the IDW rule: its blend is linear in the distance and in the browser's colour space.
|
|
145
|
+
- **A condition can change the data, and the runtime reads it at the draw.** `hover(meshGradient(...))` puts the data
|
|
146
|
+
under `:hover` (checked: the engine prints it); the drawing does not change on hover, only on the next resize.
|
|
147
|
+
- **An element removed from the page keeps its observer and its blob URL** until the page unloads. Fine for pages, not
|
|
148
|
+
for a long single-page app that churns gradients; a fix would observe removals.
|
|
149
|
+
- **Printing** takes the raster fallback. A PDF of a browser's freeform is a patch shading, not a bitmap, and freeform is
|
|
150
|
+
large in PDF.
|
|
151
|
+
- **The syntax can change** until the spec draft, and freeform's name with it (`diffusion-gradient()`); the build error
|
|
152
|
+
messages name the part they reject.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cd packages/gradient-2d && bun run test # the maths in Node, the engine (kernels, data, errors, the runtime by targets)
|
|
156
|
+
cd packages/gradient-2d && bun run typecheck # and the type tests
|
|
157
|
+
cd packages/gradient-2d && bun run test:browser # Chrome (installed): both first paints, the pixels against the maths, resize, an added element
|
|
158
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
declare const mesh: import("cssints/plugin").Scope<"gradient2d", {
|
|
3
|
+
readonly "#core": {
|
|
4
|
+
readonly css: "&{background-image:var(--_fallback);background-repeat:no-repeat;background-size:100% 100%}@supports (background-image:mesh-gradient(red,blue;green,yellow)){&{background-image:var(--_native)}}";
|
|
5
|
+
};
|
|
6
|
+
readonly "#script": {
|
|
7
|
+
readonly polyfills: ["css.types.gradient.mesh-gradient"];
|
|
8
|
+
readonly script: "@cssints/gradient-2d/runtime";
|
|
9
|
+
};
|
|
10
|
+
readonly "#ff": {
|
|
11
|
+
readonly css: "&{background-image:var(--_ff-fallback);background-repeat:no-repeat;background-size:100% 100%}@supports (background-image:freeform-gradient(red top left, blue bottom right)){&{background-image:var(--_ff-native)}}";
|
|
12
|
+
};
|
|
13
|
+
readonly "#ffScript": {
|
|
14
|
+
readonly polyfills: ["css.types.gradient.freeform-gradient"];
|
|
15
|
+
readonly script: "@cssints/gradient-2d/runtime";
|
|
16
|
+
};
|
|
17
|
+
readonly meshGradient: {
|
|
18
|
+
readonly args: import("cssints/plugin").Typed<string>;
|
|
19
|
+
readonly uses: readonly ["core", "script"];
|
|
20
|
+
};
|
|
21
|
+
readonly freeformGradient: {
|
|
22
|
+
readonly args: import("cssints/plugin").Typed<string>;
|
|
23
|
+
readonly uses: readonly ["ff", "ffScript"];
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
type Members = ReturnType<typeof mesh.export>;
|
|
27
|
+
/**
|
|
28
|
+
* `meshGradient("red, blue; green, yellow")`: a mesh gradient of a grid of colour stops (rows `;`, stops `,`), with the
|
|
29
|
+
* spec's `in oklab` first if wanted. The native `mesh-gradient()` where the browser has it, else drawn by the runtime.
|
|
30
|
+
* A build error for ragged rows, fewer than 2 by 2, a colour it cannot read, and a position, `by` or `with`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const meshGradient: Members["meshGradient"];
|
|
33
|
+
/**
|
|
34
|
+
* `freeformGradient("red top left, gold 4 60% 40%, yellow bottom right")`: a freeform (diffusion) gradient of points,
|
|
35
|
+
* each `<color> <number>? <position>` (the number is the spread), with the spec's `in oklab` first if wanted. The native
|
|
36
|
+
* `freeform-gradient()` where the browser has it; where it does not, a stack of radial gradients paints the first one
|
|
37
|
+
* and the runtime redraws it. A build error for a point without a position, two points at one position, fewer than 2
|
|
38
|
+
* points, a colour it cannot read, a length, a line and a shape.
|
|
39
|
+
*/
|
|
40
|
+
export declare const freeformGradient: Members["freeformGradient"];
|
|
41
|
+
//#endregion
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { arg, scope } from "cssints/plugin";
|
|
2
|
+
import { formatHex, formatHex8, parse, rgb } from "culori";
|
|
3
|
+
//#region src/grid.ts
|
|
4
|
+
const lin = (c) => c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
|
|
5
|
+
const gam = (c) => c <= .0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - .055;
|
|
6
|
+
const toOklab = ([r, g, b, a]) => {
|
|
7
|
+
const R = lin(r);
|
|
8
|
+
const G = lin(g);
|
|
9
|
+
const B = lin(b);
|
|
10
|
+
const l = Math.cbrt(.4122214708 * R + .5363325363 * G + .0514459929 * B);
|
|
11
|
+
const m = Math.cbrt(.2119034982 * R + .6806995451 * G + .1073969566 * B);
|
|
12
|
+
const s = Math.cbrt(.0883024619 * R + .2817188376 * G + .6299787005 * B);
|
|
13
|
+
return [
|
|
14
|
+
.2104542553 * l + .793617785 * m - .0040720468 * s,
|
|
15
|
+
1.9779984951 * l - 2.428592205 * m + .4505937099 * s,
|
|
16
|
+
.0259040371 * l + .7827717662 * m - .808675766 * s,
|
|
17
|
+
a
|
|
18
|
+
];
|
|
19
|
+
};
|
|
20
|
+
const toSrgb = ([L, A, B, a]) => {
|
|
21
|
+
const l = (L + .3963377774 * A + .2158037573 * B) ** 3;
|
|
22
|
+
const m = (L - .1055613458 * A - .0638541728 * B) ** 3;
|
|
23
|
+
const s = (L - .0894841775 * A - 1.291485548 * B) ** 3;
|
|
24
|
+
return [
|
|
25
|
+
gam(4.0767416621 * l - 3.3077115913 * m + .2309699292 * s),
|
|
26
|
+
gam(-1.2684380046 * l + 2.6097574011 * m - .3413193965 * s),
|
|
27
|
+
gam(-.0041960863 * l - .7034186147 * m + 1.707614701 * s),
|
|
28
|
+
a
|
|
29
|
+
];
|
|
30
|
+
};
|
|
31
|
+
const fromSpace = (space, q) => space === "oklab" ? toSrgb(q) : q;
|
|
32
|
+
const mean = (qs) => [
|
|
33
|
+
0,
|
|
34
|
+
1,
|
|
35
|
+
2,
|
|
36
|
+
3
|
|
37
|
+
].map((k) => qs.reduce((s, q) => s + q[k], 0) / qs.length);
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/index.ts
|
|
40
|
+
const cut = (text, sep) => {
|
|
41
|
+
const { out, cur } = [...text].reduce((acc, ch) => {
|
|
42
|
+
const depth = acc.depth + Number(ch === "(") - Number(ch === ")");
|
|
43
|
+
return depth === 0 && sep(ch) ? {
|
|
44
|
+
out: [...acc.out, acc.cur.trim()],
|
|
45
|
+
cur: "",
|
|
46
|
+
depth
|
|
47
|
+
} : {
|
|
48
|
+
out: acc.out,
|
|
49
|
+
cur: acc.cur + ch,
|
|
50
|
+
depth
|
|
51
|
+
};
|
|
52
|
+
}, {
|
|
53
|
+
out: [],
|
|
54
|
+
cur: "",
|
|
55
|
+
depth: 0
|
|
56
|
+
});
|
|
57
|
+
return [...out, cur.trim()];
|
|
58
|
+
};
|
|
59
|
+
const colour = (text, call) => {
|
|
60
|
+
if (text.includes("var(")) return text;
|
|
61
|
+
const c = parse(text);
|
|
62
|
+
if (!c) throw new Error(`${call}(): ${JSON.stringify(text)} is not a colour`);
|
|
63
|
+
const { r, g, b, alpha = 1 } = rgb(c);
|
|
64
|
+
return [
|
|
65
|
+
r,
|
|
66
|
+
g,
|
|
67
|
+
b,
|
|
68
|
+
alpha
|
|
69
|
+
];
|
|
70
|
+
};
|
|
71
|
+
const parseMesh = (text) => {
|
|
72
|
+
const rows = cut(text, (c) => c === ";").map((row) => cut(row, (c) => c === ","));
|
|
73
|
+
const head = /^in\s+(\S+)(?:\s+(.*))?$/i.exec(rows[0]?.[0] ?? "");
|
|
74
|
+
const [, method = "oklab", outside = ""] = head ?? [];
|
|
75
|
+
if (method !== "oklab" && method !== "srgb") throw new Error(`meshGradient(): in ${method} is not supported yet (oklab and srgb)`);
|
|
76
|
+
if (outside) colour(outside, "meshGradient");
|
|
77
|
+
const space = method === "srgb" ? "srgb" : "oklab";
|
|
78
|
+
const stops = (head ? [rows[0].slice(1), ...rows.slice(1)] : rows).map((row) => row.map((p) => {
|
|
79
|
+
if (cut(p, (c) => /\s/.test(c)).length > 1) throw new Error(`meshGradient(): ${JSON.stringify(p)}: a stop is one colour, its position, by and with are not supported yet`);
|
|
80
|
+
return colour(p, "meshGradient");
|
|
81
|
+
}));
|
|
82
|
+
const lengths = stops.map((r) => r.length);
|
|
83
|
+
if (lengths.some((n) => n !== lengths[0])) throw new Error(`meshGradient(): the rows have ${lengths.join(", ")} stops: every row needs the same number`);
|
|
84
|
+
if (stops.length < 2 || lengths[0] < 2) throw new Error(`meshGradient(): a mesh needs at least 2 rows of 2 stops, not ${stops.length} of ${lengths[0] ?? 0}`);
|
|
85
|
+
return {
|
|
86
|
+
space,
|
|
87
|
+
rows: stops
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const clamp = (x) => Math.min(1, Math.max(0, x));
|
|
91
|
+
const hex = ([r, g, b, a]) => {
|
|
92
|
+
const c = {
|
|
93
|
+
mode: "rgb",
|
|
94
|
+
r: clamp(r),
|
|
95
|
+
g: clamp(g),
|
|
96
|
+
b: clamp(b),
|
|
97
|
+
alpha: clamp(a)
|
|
98
|
+
};
|
|
99
|
+
return c.alpha < 1 ? formatHex8(c) : formatHex(c);
|
|
100
|
+
};
|
|
101
|
+
const average = (stops) => {
|
|
102
|
+
const known = stops.filter((s) => typeof s !== "string");
|
|
103
|
+
if (known.length < stops.length) return "none";
|
|
104
|
+
const h = hex(fromSpace("oklab", mean(known.map((q) => toOklab(q)))));
|
|
105
|
+
return `linear-gradient(${h}, ${h})`;
|
|
106
|
+
};
|
|
107
|
+
const KEYWORD = /^(left|right|top|bottom|center)$/;
|
|
108
|
+
const PERCENT = /^(-?\d*\.?\d+)%$/;
|
|
109
|
+
const NUMBER = /^[+-]?(\d+\.?\d*|\.\d+)$/;
|
|
110
|
+
const LENGTH = /^[+-]?(\d+\.?\d*|\.\d+)[a-z]+$/;
|
|
111
|
+
const A_LINE_OR_SHAPE = /^(line|shape|curve)\b/;
|
|
112
|
+
const CALL = "freeformGradient";
|
|
113
|
+
const position = (words) => {
|
|
114
|
+
const pct = (w) => Number(PERCENT.exec(w)[1]) / 100;
|
|
115
|
+
const axis = (w) => w === "left" || w === "right" ? "x" : w === "top" || w === "bottom" ? "y" : "c";
|
|
116
|
+
const edge = (w) => w === "left" || w === "top" ? 0 : 1;
|
|
117
|
+
const [a, b] = words;
|
|
118
|
+
if (b === void 0) {
|
|
119
|
+
if (!KEYWORD.test(a)) return [pct(a), .5];
|
|
120
|
+
if (a === "center") return [.5, .5];
|
|
121
|
+
return axis(a) === "x" ? [edge(a), .5] : [.5, edge(a)];
|
|
122
|
+
}
|
|
123
|
+
const ka = KEYWORD.test(a);
|
|
124
|
+
const kb = KEYWORD.test(b);
|
|
125
|
+
if (!ka && !kb) return [pct(a), pct(b)];
|
|
126
|
+
if (ka && kb) {
|
|
127
|
+
const x = [a, b].find((w) => axis(w) === "x");
|
|
128
|
+
const y = [a, b].find((w) => axis(w) === "y");
|
|
129
|
+
return [x ? edge(x) : .5, y ? edge(y) : .5];
|
|
130
|
+
}
|
|
131
|
+
if (ka) {
|
|
132
|
+
if (axis(a) === "y") return [.5, a === "top" ? pct(b) : 1 - pct(b)];
|
|
133
|
+
if (axis(a) === "x") return [a === "left" ? pct(b) : 1 - pct(b), .5];
|
|
134
|
+
return [.5, pct(b)];
|
|
135
|
+
}
|
|
136
|
+
return axis(b) === "y" ? [pct(a), edge(b)] : [pct(a), .5];
|
|
137
|
+
};
|
|
138
|
+
const point = (text) => {
|
|
139
|
+
const words = cut(text, (c) => /\s/.test(c));
|
|
140
|
+
const c = words[0] ?? "";
|
|
141
|
+
const all = words.slice(1);
|
|
142
|
+
const wants = () => /* @__PURE__ */ new Error(`${CALL}(): ${JSON.stringify(text)}: a point needs a position, \`<color> <number>? <position>\``);
|
|
143
|
+
if (all.length === 0) throw wants();
|
|
144
|
+
const spread = NUMBER.test(all[0]) ? Number(all[0]) : 1;
|
|
145
|
+
const rest = NUMBER.test(all[0]) ? all.slice(1) : all;
|
|
146
|
+
if (rest.length === 0) throw wants();
|
|
147
|
+
const part = rest.find((w) => A_LINE_OR_SHAPE.test(w));
|
|
148
|
+
if (part) throw new Error(`${CALL}(): ${JSON.stringify(part)}: a line or a shape is not supported yet`);
|
|
149
|
+
const len = rest.find((w) => LENGTH.test(w));
|
|
150
|
+
if (len) throw new Error(`${CALL}(): ${JSON.stringify(len)} is a length: a position is a percentage or a keyword`);
|
|
151
|
+
if (rest.length > 2) {
|
|
152
|
+
const bad = rest.find((w) => !KEYWORD.test(w) && !PERCENT.test(w));
|
|
153
|
+
throw new Error(bad ? `${CALL}(): ${JSON.stringify(bad)} is not a position: a keyword (top, left, ...) or a percentage` : `${CALL}(): ${JSON.stringify(text)}: a line (a point with more than one position) is not supported yet`);
|
|
154
|
+
}
|
|
155
|
+
const [u = .5, v = .5] = position(rest);
|
|
156
|
+
return {
|
|
157
|
+
q: colour(c, CALL),
|
|
158
|
+
u,
|
|
159
|
+
v,
|
|
160
|
+
spread
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
const parseFreeform = (text) => {
|
|
164
|
+
const items = cut(text, (c) => c === ",");
|
|
165
|
+
const head = /^in\s+(\S+)(?:\s+(.*))?$/i.exec(items[0] ?? "");
|
|
166
|
+
const [, method = "oklab", extra = ""] = head ?? [];
|
|
167
|
+
if (method !== "oklab" && method !== "srgb") throw new Error(`${CALL}(): in ${method} is not supported yet (oklab and srgb)`);
|
|
168
|
+
if (extra) throw new Error(`${CALL}(): ${JSON.stringify(items[0])}: the interpolation method takes no colour (freeform-gradient() has none)`);
|
|
169
|
+
const space = method === "srgb" ? "srgb" : "oklab";
|
|
170
|
+
const body = head ? items.slice(1) : items;
|
|
171
|
+
if (body.length < 2) throw new Error(`${CALL}(): a gradient needs at least 2 points, not ${body.length}`);
|
|
172
|
+
const points = body.map(point);
|
|
173
|
+
const seen = /* @__PURE__ */ new Map();
|
|
174
|
+
for (const [i, p] of points.entries()) {
|
|
175
|
+
const at = `${p.u},${p.v}`;
|
|
176
|
+
const had = seen.get(at);
|
|
177
|
+
if (had !== void 0) throw new Error(`${CALL}(): ${JSON.stringify(had)} and ${JSON.stringify(body[i])} are the same position: every point needs its own`);
|
|
178
|
+
seen.set(at, body[i]);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
space,
|
|
182
|
+
points
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
const pct = (x) => `${Math.round(x * 1e6) / 1e4}%`;
|
|
186
|
+
const radials = (points) => points.map((p) => {
|
|
187
|
+
const c = typeof p.q === "string" ? p.q : hex(p.q);
|
|
188
|
+
return `radial-gradient(circle at ${pct(p.u)} ${pct(p.v)}, ${c}, transparent)`;
|
|
189
|
+
}).join(", ");
|
|
190
|
+
const members = scope("gradient2d", {
|
|
191
|
+
"#core": { css: "&{background-image:var(--_fallback);background-repeat:no-repeat;background-size:100% 100%}@supports (background-image:mesh-gradient(red,blue;green,yellow)){&{background-image:var(--_native)}}" },
|
|
192
|
+
"#script": {
|
|
193
|
+
polyfills: ["css.types.gradient.mesh-gradient"],
|
|
194
|
+
script: "@cssints/gradient-2d/runtime"
|
|
195
|
+
},
|
|
196
|
+
"#ff": { css: "&{background-image:var(--_ff-fallback);background-repeat:no-repeat;background-size:100% 100%}@supports (background-image:freeform-gradient(red top left, blue bottom right)){&{background-image:var(--_ff-native)}}" },
|
|
197
|
+
"#ffScript": {
|
|
198
|
+
polyfills: ["css.types.gradient.freeform-gradient"],
|
|
199
|
+
script: "@cssints/gradient-2d/runtime"
|
|
200
|
+
},
|
|
201
|
+
meshGradient: {
|
|
202
|
+
args: arg(),
|
|
203
|
+
uses: ["core", "script"]
|
|
204
|
+
},
|
|
205
|
+
freeformGradient: {
|
|
206
|
+
args: arg(),
|
|
207
|
+
uses: ["ff", "ffScript"]
|
|
208
|
+
}
|
|
209
|
+
}).build({
|
|
210
|
+
meshGradient: (text) => {
|
|
211
|
+
const { space, rows } = parseMesh(text);
|
|
212
|
+
const data = {
|
|
213
|
+
space,
|
|
214
|
+
rows: rows.map((row) => row.map((s) => typeof s === "string" ? s : s.map((n) => Math.round(n * 1e4) / 1e4)))
|
|
215
|
+
};
|
|
216
|
+
return { decls: [
|
|
217
|
+
["--_native", `mesh-gradient(${text})`],
|
|
218
|
+
["--_fallback", average(rows.flat())],
|
|
219
|
+
["--_data", JSON.stringify(JSON.stringify(data))]
|
|
220
|
+
] };
|
|
221
|
+
},
|
|
222
|
+
freeformGradient: (text) => {
|
|
223
|
+
const { space, points } = parseFreeform(text);
|
|
224
|
+
const data = {
|
|
225
|
+
space,
|
|
226
|
+
points: points.map((p) => [
|
|
227
|
+
Math.round(p.u * 1e4) / 1e4,
|
|
228
|
+
Math.round(p.v * 1e4) / 1e4,
|
|
229
|
+
p.spread,
|
|
230
|
+
typeof p.q === "string" ? p.q : p.q.map((n) => Math.round(n * 1e4) / 1e4)
|
|
231
|
+
])
|
|
232
|
+
};
|
|
233
|
+
return { decls: [
|
|
234
|
+
["--_ff-native", `freeform-gradient(${text})`],
|
|
235
|
+
["--_ff-fallback", radials(points)],
|
|
236
|
+
["--_ff-data", JSON.stringify(JSON.stringify(data))]
|
|
237
|
+
] };
|
|
238
|
+
}
|
|
239
|
+
}).export();
|
|
240
|
+
/**
|
|
241
|
+
* `meshGradient("red, blue; green, yellow")`: a mesh gradient of a grid of colour stops (rows `;`, stops `,`), with the
|
|
242
|
+
* spec's `in oklab` first if wanted. The native `mesh-gradient()` where the browser has it, else drawn by the runtime.
|
|
243
|
+
* A build error for ragged rows, fewer than 2 by 2, a colour it cannot read, and a position, `by` or `with`.
|
|
244
|
+
*/
|
|
245
|
+
const meshGradient = members.meshGradient;
|
|
246
|
+
/**
|
|
247
|
+
* `freeformGradient("red top left, gold 4 60% 40%, yellow bottom right")`: a freeform (diffusion) gradient of points,
|
|
248
|
+
* each `<color> <number>? <position>` (the number is the spread), with the spec's `in oklab` first if wanted. The native
|
|
249
|
+
* `freeform-gradient()` where the browser has it; where it does not, a stack of radial gradients paints the first one
|
|
250
|
+
* and the runtime redraws it. A build error for a point without a position, two points at one position, fewer than 2
|
|
251
|
+
* points, a colour it cannot read, a length, a line and a shape.
|
|
252
|
+
*/
|
|
253
|
+
const freeformGradient = members.freeformGradient;
|
|
254
|
+
//#endregion
|
|
255
|
+
export { freeformGradient, meshGradient };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/runtime.mjs
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
//#region src/canvas.ts
|
|
2
|
+
const drawn = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
const resolve = (el, text, ctx) => {
|
|
4
|
+
const probe = document.createElement("span");
|
|
5
|
+
probe.hidden = true;
|
|
6
|
+
probe.style.color = text;
|
|
7
|
+
el.append(probe);
|
|
8
|
+
const css = getComputedStyle(probe).color;
|
|
9
|
+
probe.remove();
|
|
10
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
11
|
+
ctx.fillStyle = css;
|
|
12
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
13
|
+
const px = ctx.getImageData(0, 0, 1, 1).data;
|
|
14
|
+
return [
|
|
15
|
+
px[0] / 255,
|
|
16
|
+
px[1] / 255,
|
|
17
|
+
px[2] / 255,
|
|
18
|
+
px[3] / 255
|
|
19
|
+
];
|
|
20
|
+
};
|
|
21
|
+
const draw = (el, d) => {
|
|
22
|
+
if (!el.clientWidth || !el.clientHeight) return;
|
|
23
|
+
const w = Math.max(64, Math.round(el.clientWidth / 4));
|
|
24
|
+
const h = Math.max(64, Math.round(el.clientHeight / 4));
|
|
25
|
+
const cv = document.createElement("canvas");
|
|
26
|
+
cv.width = w;
|
|
27
|
+
cv.height = h;
|
|
28
|
+
const ctx = cv.getContext("2d");
|
|
29
|
+
if (!ctx) return;
|
|
30
|
+
ctx.putImageData(d.paint(el, ctx, w, h), 0, 0);
|
|
31
|
+
const s = drawn.get(el) ?? { id: 0 };
|
|
32
|
+
const id = ++s.id;
|
|
33
|
+
drawn.set(el, s);
|
|
34
|
+
cv.toBlob((blob) => {
|
|
35
|
+
if (!blob || drawn.get(el)?.id !== id) return;
|
|
36
|
+
if (s.url) URL.revokeObjectURL(s.url);
|
|
37
|
+
s.url = URL.createObjectURL(blob);
|
|
38
|
+
el.style.setProperty(d.fallback, `url("${s.url}")`);
|
|
39
|
+
}, "image/png");
|
|
40
|
+
};
|
|
41
|
+
const run = (drawings) => {
|
|
42
|
+
const live = drawings.filter((d) => !CSS.supports("background-image", d.native));
|
|
43
|
+
if (!live.length) return;
|
|
44
|
+
const sized = new ResizeObserver((es) => {
|
|
45
|
+
for (const e of es) {
|
|
46
|
+
const el = e.target;
|
|
47
|
+
for (const d of live) if (el.classList.contains(d.marker)) draw(el, d);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const scan = (root) => {
|
|
51
|
+
for (const d of live) for (const el of root.matches(`.${d.marker}`) ? [root] : root.querySelectorAll(`.${d.marker}`)) sized.observe(el);
|
|
52
|
+
};
|
|
53
|
+
new MutationObserver((rs) => {
|
|
54
|
+
for (const r of rs) if (r.type === "attributes") scan(r.target);
|
|
55
|
+
else for (const n of r.addedNodes) if (n.nodeType === 1) scan(n);
|
|
56
|
+
}).observe(document.documentElement, {
|
|
57
|
+
subtree: true,
|
|
58
|
+
childList: true,
|
|
59
|
+
attributes: true,
|
|
60
|
+
attributeFilter: ["class"]
|
|
61
|
+
});
|
|
62
|
+
scan(document.documentElement);
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/grid.ts
|
|
66
|
+
const lin = (c) => c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
|
|
67
|
+
const gam = (c) => c <= .0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - .055;
|
|
68
|
+
const toOklab = ([r, g, b, a]) => {
|
|
69
|
+
const R = lin(r);
|
|
70
|
+
const G = lin(g);
|
|
71
|
+
const B = lin(b);
|
|
72
|
+
const l = Math.cbrt(.4122214708 * R + .5363325363 * G + .0514459929 * B);
|
|
73
|
+
const m = Math.cbrt(.2119034982 * R + .6806995451 * G + .1073969566 * B);
|
|
74
|
+
const s = Math.cbrt(.0883024619 * R + .2817188376 * G + .6299787005 * B);
|
|
75
|
+
return [
|
|
76
|
+
.2104542553 * l + .793617785 * m - .0040720468 * s,
|
|
77
|
+
1.9779984951 * l - 2.428592205 * m + .4505937099 * s,
|
|
78
|
+
.0259040371 * l + .7827717662 * m - .808675766 * s,
|
|
79
|
+
a
|
|
80
|
+
];
|
|
81
|
+
};
|
|
82
|
+
const toSrgb = ([L, A, B, a]) => {
|
|
83
|
+
const l = (L + .3963377774 * A + .2158037573 * B) ** 3;
|
|
84
|
+
const m = (L - .1055613458 * A - .0638541728 * B) ** 3;
|
|
85
|
+
const s = (L - .0894841775 * A - 1.291485548 * B) ** 3;
|
|
86
|
+
return [
|
|
87
|
+
gam(4.0767416621 * l - 3.3077115913 * m + .2309699292 * s),
|
|
88
|
+
gam(-1.2684380046 * l + 2.6097574011 * m - .3413193965 * s),
|
|
89
|
+
gam(-.0041960863 * l - .7034186147 * m + 1.707614701 * s),
|
|
90
|
+
a
|
|
91
|
+
];
|
|
92
|
+
};
|
|
93
|
+
const toSpace = (space, q) => space === "oklab" ? toOklab(q) : q;
|
|
94
|
+
const fromSpace = (space, q) => space === "oklab" ? toSrgb(q) : q;
|
|
95
|
+
const cr = (p0, p1, p2, p3, t) => p1 + .5 * t * (p2 - p0 + t * (2 * p0 - 5 * p1 + 4 * p2 - p3 + t * (3 * (p1 - p2) + p3 - p0)));
|
|
96
|
+
const sample = (g, u, v) => {
|
|
97
|
+
const rows = g.length;
|
|
98
|
+
const cols = g[0].length;
|
|
99
|
+
const x = u * (cols - 1);
|
|
100
|
+
const y = v * (rows - 1);
|
|
101
|
+
const i = Math.min(Math.floor(x), cols - 2);
|
|
102
|
+
const j = Math.min(Math.floor(y), rows - 2);
|
|
103
|
+
const tx = x - i;
|
|
104
|
+
const ty = y - j;
|
|
105
|
+
const at = (r, c) => g[Math.min(Math.max(r, 0), rows - 1)][Math.min(Math.max(c, 0), cols - 1)];
|
|
106
|
+
const line = (r) => {
|
|
107
|
+
const p = [
|
|
108
|
+
-1,
|
|
109
|
+
0,
|
|
110
|
+
1,
|
|
111
|
+
2
|
|
112
|
+
].map((d) => at(r, i + d));
|
|
113
|
+
return [
|
|
114
|
+
0,
|
|
115
|
+
1,
|
|
116
|
+
2,
|
|
117
|
+
3
|
|
118
|
+
].map((k) => cr(p[0][k], p[1][k], p[2][k], p[3][k], tx));
|
|
119
|
+
};
|
|
120
|
+
const q = [
|
|
121
|
+
-1,
|
|
122
|
+
0,
|
|
123
|
+
1,
|
|
124
|
+
2
|
|
125
|
+
].map((d) => line(j + d));
|
|
126
|
+
return [
|
|
127
|
+
0,
|
|
128
|
+
1,
|
|
129
|
+
2,
|
|
130
|
+
3
|
|
131
|
+
].map((k) => cr(q[0][k], q[1][k], q[2][k], q[3][k], ty));
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/points.ts
|
|
135
|
+
const freeform = (points, u, v, ratio = 1) => {
|
|
136
|
+
const ws = points.map((p) => {
|
|
137
|
+
const du = u - p.u;
|
|
138
|
+
const dv = (v - p.v) * ratio;
|
|
139
|
+
return {
|
|
140
|
+
p,
|
|
141
|
+
d2: du * du + dv * dv
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
const hit = ws.find((x) => x.d2 < 1e-12);
|
|
145
|
+
if (hit) return hit.p.q;
|
|
146
|
+
const w = ws.map((x) => Math.max(0, x.p.spread) / x.d2);
|
|
147
|
+
const sum = w.reduce((s, x) => s + x, 0);
|
|
148
|
+
if (!(sum > 0)) return points[0].q;
|
|
149
|
+
return [
|
|
150
|
+
0,
|
|
151
|
+
1,
|
|
152
|
+
2,
|
|
153
|
+
3
|
|
154
|
+
].map((k) => ws.reduce((s, x, i) => s + w[i] * x.p.q[k], 0) / sum);
|
|
155
|
+
};
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/runtime.ts
|
|
158
|
+
const mesh = {
|
|
159
|
+
marker: "_gradient2d-core",
|
|
160
|
+
fallback: "--gradient2d-fallback",
|
|
161
|
+
native: "mesh-gradient(red, blue; green, yellow)",
|
|
162
|
+
paint: (el, ctx, w, h) => {
|
|
163
|
+
const raw = getComputedStyle(el).getPropertyValue("--gradient2d-data").trim();
|
|
164
|
+
const { space, rows } = JSON.parse(JSON.parse(raw));
|
|
165
|
+
const grid = rows.map((row) => row.map((s) => toSpace(space, typeof s === "string" ? resolve(el, s, ctx) : s)));
|
|
166
|
+
const img = ctx.createImageData(w, h);
|
|
167
|
+
Array.from({ length: w * h }, (_, i) => i).forEach((i) => {
|
|
168
|
+
const q = fromSpace(space, sample(grid, (i % w + .5) / w, (Math.floor(i / w) + .5) / h));
|
|
169
|
+
img.data.set([
|
|
170
|
+
q[0] * 255,
|
|
171
|
+
q[1] * 255,
|
|
172
|
+
q[2] * 255,
|
|
173
|
+
q[3] * 255
|
|
174
|
+
], 4 * i);
|
|
175
|
+
});
|
|
176
|
+
return img;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const points = {
|
|
180
|
+
marker: "_gradient2d-ff",
|
|
181
|
+
fallback: "--gradient2d-ff-fallback",
|
|
182
|
+
native: "freeform-gradient(red top left, blue bottom right)",
|
|
183
|
+
paint: (el, ctx, w, h) => {
|
|
184
|
+
const raw = getComputedStyle(el).getPropertyValue("--gradient2d-ff-data").trim();
|
|
185
|
+
const { space, points } = JSON.parse(JSON.parse(raw));
|
|
186
|
+
const ratio = h / w;
|
|
187
|
+
const ps = points.map(([u, v, spread, c]) => ({
|
|
188
|
+
u,
|
|
189
|
+
v,
|
|
190
|
+
spread,
|
|
191
|
+
q: toSpace(space, typeof c === "string" ? resolve(el, c, ctx) : c)
|
|
192
|
+
}));
|
|
193
|
+
const img = ctx.createImageData(w, h);
|
|
194
|
+
Array.from({ length: w * h }, (_, i) => i).forEach((i) => {
|
|
195
|
+
const q = fromSpace(space, freeform(ps, (i % w + .5) / w, (Math.floor(i / w) + .5) / h, ratio));
|
|
196
|
+
img.data.set([
|
|
197
|
+
q[0] * 255,
|
|
198
|
+
q[1] * 255,
|
|
199
|
+
q[2] * 255,
|
|
200
|
+
q[3] * 255
|
|
201
|
+
], 4 * i);
|
|
202
|
+
});
|
|
203
|
+
return img;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
if (typeof document !== "undefined" && typeof ResizeObserver !== "undefined") run([mesh, points]);
|
|
207
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cssints/gradient-2d",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "mesh-gradient() and freeform-gradient() for cssints: the native function where the browser has it, else a canvas drawn by a runtime",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/alexeiaccio/css-in-ts-compiler.git",
|
|
9
|
+
"directory": "packages/gradient-2d"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://cssints.dev",
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"./src/runtime.ts",
|
|
18
|
+
"./dist/runtime.mjs"
|
|
19
|
+
],
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.mts",
|
|
23
|
+
"default": "./dist/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./runtime": {
|
|
26
|
+
"types": "./dist/runtime.d.mts",
|
|
27
|
+
"default": "./dist/runtime.mjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"culori": "^4.0.2"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"cssints": "^0.4.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|