Compare commits

...

82 Commits

Author SHA1 Message Date
yuanchuan dffd83078b Add experimental once function 2023-02-19 12:30:17 +08:00
yuanchuan 787bb390c0 Change cycle shift direction 2023-02-19 09:27:11 +08:00
yuanchuan 54077cb530 0.34.1 2023-02-15 09:38:01 +08:00
yuanchuan f0c6bd2fa5 Fix gap property break and remove redundant styles 2023-02-14 20:58:54 +08:00
yuanchuan 1b69cf9a17 0.34.0 2023-02-14 14:23:56 +08:00
yuanchuan 162b1c7a13 Gerenate new seed only once 2023-02-14 09:43:32 +08:00
yuanchuan f415b9c9e0 Set seed value correctly 2023-02-14 06:30:54 +08:00
yuanchuan c3173271a2 Revert "Fallback contain to overflow"
This reverts commit 768f4bd750.
2023-02-13 20:29:06 +08:00
yuanchuan 768f4bd750 Fallback contain to overflow 2023-02-13 20:27:27 +08:00
yuanchuan d40fd27a94 Add dx and dy distance functions 2023-02-13 20:01:30 +08:00
yuanchuan df4c69ecc1 Fix CSS parser for statement break 2023-02-13 15:38:59 +08:00
yuanchuan 673410119f Enable auto aspect-ratio to cells 2023-02-13 07:58:45 +08:00
yuanchuan f6366ab07c Add gap property 2023-02-12 23:26:29 +08:00
yuanchuan ccff4647da Add rotate, scale and flex options for grid property 2023-02-12 23:18:32 +08:00
yuanchuan 114d7d72f2 Support 0 index value in m function 2023-02-12 10:50:31 +08:00
yuanchuan e97a38c442 Fix parsing function composition 2023-02-12 10:46:18 +08:00
yuanchuan bc6edb5951 Add no-clip keyword for grid property 2023-02-12 09:41:41 +08:00
yuanchuan b7d3405708 Readd contain property for doodle element 2023-02-12 05:55:57 +08:00
yuanchuan 772a561b9e Extend grid property to support aspect-ratio 2023-02-11 22:51:51 +08:00
yuanchuan 21fa24cd3e Extend grid propery to support doodle background color 2023-02-11 20:05:04 +08:00
yuanchuan 57fc20d331 0.33.1 2023-02-08 22:32:16 +08:00
yuanchuan 7978410f4a Fix `@pn` index value inside `@m` function 2023-02-08 22:22:52 +08:00
yuanchuan 1c3d4b8905 Fix svg function detection 2023-02-08 18:38:48 +08:00
yuanchuan 17f7309463 0.33.0 2023-02-04 20:35:03 +08:00
yuanchuan e6cbd41e3a Add SVG playground link 2023-02-04 20:33:30 +08:00
yuanchuan 932bcfadb4 Add expand alias for padding because I can't tell which one is better 2023-02-04 18:35:00 +08:00
yuanchuan 1cc4ed63a9 Ignore invalid viewBox value 2023-02-04 18:02:27 +08:00
yuanchuan db9aa9ffd3 Support padding in SVG viewBox value.
For example:

  svg {
    viewBox: 0 0 1 1 padding .1
  }
2023-02-04 16:41:29 +08:00
yuanchuan dfc6fdd932 Check base to prevent NaN values 2023-01-08 08:44:51 +08:00
yuanchuan 6c783dff4b Add Svg function which returns raw svg code 2023-01-08 08:20:30 +08:00
yuanchuan 5b72fd34d3 Add svg-pattern function 2023-01-08 08:11:58 +08:00
yuanchuan 902a8788cd Do not use `??` operator at the moment.
Fix #110
2023-01-05 22:45:47 +08:00
yuanchuan 792397c033 0.32.1 2023-01-05 06:42:41 +08:00
yuanchuan 1a7a383944 Fix css parser for reading tail function names 2023-01-05 06:05:23 +08:00
yuanchuan 1783e6572d Build grid without DOM object 2022-12-27 21:36:29 +08:00
yuanchuan 277ca2b1a8 0.32.0 2022-12-17 13:42:29 +08:00
yuanchuan a548f2f907 Reduce imports of the exported svg function 2022-12-17 13:02:55 +08:00
yuanchuan fb9e96dcd9 Add `@unicode` function to insert unicode elegantly. 2022-12-17 12:53:42 +08:00
yuanchuan 7393101214 Fix parsing quotes in content 2022-12-17 12:04:00 +08:00
yuanchuan dd38c4dd45 Add shorthand to calc index values
For example:

@i5 === @calc(@i + 5)
@i(*10) === @calc(@i * 10)
@i(-10) === @calc(@i - 10)
@i(/10) === @calc(@i / 10)
@i(%10) === @calc(@i % 10)
@i(%10) === @calc(@i % 10)

@i(10/) === @calc(10 / @i)
@i(10-) === @calc(10 - @i)
2022-12-17 08:47:02 +08:00
yuanchuan 0ae460d6b7 Add `@mirror` and `@Mirror` function 2022-12-16 21:18:08 +08:00
yuanchuan 2e206c48e7 Improve `@cycle` to support comma-separated values 2022-12-13 10:18:18 +08:00
yuanchuan 27a8d2c8be Rebuild grid correctly 2022-12-13 09:21:02 +08:00
yuanchuan 704eebccf9 Fix svg-extended style 2022-12-11 19:25:40 +08:00
yuanchuan 166b53546d Fix bad reverse function implementation 2022-12-11 13:50:43 +08:00
yuanchuan d2e1144c39 0.31.0 2022-12-11 13:11:55 +08:00
yuanchuan 5bab81b8cc Fix function name detection 2022-12-11 13:11:43 +08:00
yuanchuan a31a652fbe Add `@content` property for textnode content 2022-12-11 12:47:24 +08:00
yuanchuan 9b72bf9b30 The `@` symbol can be ommited if the functions are chained together 2022-12-11 10:41:58 +08:00
yuanchuan c01f3b5d29 Patch reverse function to support both SVG path commands and general
arguments
2022-12-11 10:21:16 +08:00
yuanchuan a5c16bc862 Fix style tag parsing for complex css selectors 2022-12-11 09:50:07 +08:00
yuanchuan 82449124ed Skip variable properties 2022-12-05 21:56:42 +08:00
yuanchuan ca11666be3 I'm not sure which function names are better so I added a `@pnr` alias for `@pr`. 2022-12-02 17:17:58 +08:00
yuanchuan 61278b715d 0.30.9 2022-12-02 16:36:12 +08:00
yuanchuan 007f705615 Simplify image detection 2022-12-02 11:45:00 +08:00
yuanchuan 3831fd24c4 Use single rotate for inner `@place` transformation 2022-12-02 11:11:29 +08:00
yuanchuan a31e596bc0 Use `@pn` and `@position` as aliases 2022-12-02 10:17:42 +08:00
yuanchuan 6293f3f5d9 Compute index instead of reversing the whole array 2022-12-02 08:53:13 +08:00
yuanchuan c7ac4e4372 Support variables in standalone SVG export function 2022-12-02 08:38:11 +08:00
yuanchuan b038d3452e 0.30.8 2022-11-21 16:28:52 +08:00
yuanchuan 60fcc89df1 Fix commas 2022-11-21 16:21:36 +08:00
yuanchuan 37e3023cad Add pr function for picking from right to left
Also add an alias pl to pn
2022-11-21 16:08:50 +08:00
yuanchuan b9edb539b4 0.30.7 2022-11-11 14:25:09 +08:00
yuanchuan ff888cbb0d Add cycle function 2022-11-11 14:15:35 +08:00
yuanchuan a51db4e906 0.30.6 2022-10-31 18:28:08 +08:00
yuanchuan 5643b53a3d I was wrong. Only set `background-size` to `pattern`, `canvas`, and `shader`. 2022-10-31 18:21:17 +08:00
yuanchuan 8269704dc9 0.30.5 2022-10-31 17:22:11 +08:00
yuanchuan ecdbda2e0d Always add `background-size:100%` to background properties 2022-10-31 17:17:25 +08:00
yuanchuan c93d227daa 0.30.4 2022-10-17 15:40:11 +08:00
yuanchuan 5696cf112d Normalize quoted attribute values 2022-10-12 16:47:28 +08:00
yuanchuan e5b13f1a0f 0.30.3 2022-10-09 10:56:35 +08:00
yuanchuan be9fb9a3bd Fix SVG generator on cutting values 2022-10-09 10:53:56 +08:00
yuanchuan 8ce6786020 Do not break for decodeURI 2022-10-08 10:47:09 +08:00
yuanchuan 4ff1271086 0.30.2 2022-10-05 20:36:40 +08:00
yuanchuan 1ba7ddbdc9 Prevent type error 2022-10-05 20:15:52 +08:00
yuanchuan 0ca2f12646 0.30.1 2022-10-05 13:16:14 +08:00
yuanchuan 62ecf66d5d Improve CSS and SVG parser on quotes 2022-10-05 13:08:08 +08:00
yuanchuan b3eb97a556 Fix tail parens 2022-10-04 22:55:54 +08:00
yuanchuan 3500881a62 Remove unnecessary part of patterns 2022-10-04 19:48:11 +08:00
yuanchuan 591ca51c3e Skip evaluation for specific patterns 2022-10-04 19:45:53 +08:00
yuanchuan 68f7ace62c Add range support for m function
resolve #109
2022-10-04 14:53:21 +08:00
yuanchuan c0d3668e16 Increase range limit 2022-10-04 14:09:10 +08:00
20 changed files with 1676 additions and 400 deletions

View File

@ -1,3 +1,361 @@
## 0.34.1
* Fix gap property break and remove redundant styles.
<br /> <br />
## 0.34.0
### Breaking
* The css-doodle element now is by default restrict the visible area to its dimension,
which means it's overflow hidden. There are two ways to change this behavior:
1. Append the <code>noclip</code> keyword to the `@grid` property:
```css
@grid: 1 / 400px noclip;
```
2. Overide the `contain` property:
```css
:doodle {
@grid: 1 / 400px;
contain: none;
}
```
### Features
* Extended `@grid` to support doodle background color, after the second `/` symbol.
```css
@grid: 1 / 400px / #000;
```
* Extended `@grid` and `@size` to support <code>aspect-ratio</code> if one of the `width` and `width` is `auto`.
```css
@grid: 1 / 400px auto 2;
@grid: 1 / 400px auto (4/3);
@size: 100px auto 1.5;
```
* Extended `@grid` to support <code>rotate</code> and <code>scale</code> to <code>:container</code>.
```css
@grid: 1 / 400px + 2; /* + means scale */
@grid: 1 / 400px * 45deg; /* * means scale */
@grid: 1 / 400px + 2 * 45deg;
```
* Extended `@grid` to change the layout to `Flex` layout.
```css
@grid: - 8x2 / 400px; /* - means using flex */
@grid: | 8x2 / 400px; /* | means using flex and vertical direction */
```
* Added support for 0 index value in `@m` function.
```css
/* 0 1 2 3 4 */
@content: @M0-4.n
```
* Added `@gap` property.
```css
@grid: 10 / 400px;
@gap: 1px;
```
* Added `@dx` and `@dy` functions.
```css
/* -2,-2 -1,-2 0,-2 1,-2 2,-2
-2,-1 -1,-1 0,-1 1,-1 2,-1
-2,0 -1,0 0,0 1,0 2,0
-2,1 -1,1 0,1 1,1 2,1
-2,2 -1,2 0,2 1,2 2,2 */
@grid: 5 / 400px;
@content: @dx, @dy;
```
Adjust offset.
```css
/* -3,-3 -2,-3 -1,-3 0,-3 1,-3
-3,-2 -2,-2 -1,-2 0,-2 1,-2
-3,-1 -2,-1 -1,-1 0,-1 1,-1
-3,0 -2,0 -1,0 0,0 1,0
-3,1 -2,1 -1,1 0,1 1,1 */
@grid: 5 / 400px;
@content: @dx(1), @dy(1);
```
### Fixes
* Fixed CSS parsing on function composition.
* Fixed seed value at pre-compose phase.
<br /> <br />
## 0.33.1
* Fixed `@Svg` function detection for transforming the `multiplication` feature.
* Fixed `@pn` index value inside `@m` function.
<br /> <br />
## 0.33.0
* Added new `@svg-pattern` function to reduce boilerplate code.
```css
background: @svg-pattern(
width, height: 10%;
viewBox: 0 0 10 10;
circle { }
);
```
* Added new `@Svg` function to return raw SVG code rather than encoded URI value.
```css
@content: @Svg(
viewBox: -1 -1 2 2;
circle {
r: 1;
}
);
```
* Added `padding` value for SVG `viewBox` property.
```css
viewBox: -1 -1 2 2 padding 1;
/* equals to */
viewBox: -2 2 4 4;
```
<br /> <br />
## 0.32.2
* Do not use `??` operator at the moment.
<br /> <br />
## 0.32.1
* Build grid without DOM object.
* Fix CSS parser for reading tail function names.
<br /> <br />
## 0.32.0
### Features
* Add `@unicode` function to insert Unicode and it can be used both in HTML and CSS.
```css
/* in HTML */
@content: @unicode(0x2500);
/* in CSS */
:after {
content: @unicode(0x2500);
}
```
A sequence of Unicode characters.
```css
@content: @pn.unicode(0x2500, 0x257f, 0x2588);
```
* Add `@mirror/@Mirror` function to transform input items, simlar to `@cycle` and `@reverse`.
```css
/* 1, 2, 3, 4, 5, 5, 4, 3, 2, 1 */
@mirror(1, 2, 3, 4, 5);
/* 1, 2, 3, 4, 5, 4, 3, 2, 1 */
@Mirror(1, 2, 3, 4, 5);
```
* All index functions, `@i/I`, `@x/X`, `@y/Y`, and `@n/N/nx/ny` accept extra arguments to do calulations.
```css
@i5 === @calc(@i + 5)
@i(*10) === @calc(@i * 10)
@i(-10) === @calc(@i - 10)
@i(/10) === @calc(@i / 10)
@i(%10) === @calc(@i % 10)
@i(10/) === @calc(10 / @i)
@i(10-) === @calc(10 - @i)
```
### Patches
* Reduce imports of the exported svg function.
* Improve `@cycle` to support comma-separated values.
* Fix grid build for `@content`.
* Fix parsing quotes in content.
```css
/* There used to be bugs` */
content: '");';
```
<br /> <br />
## 0.31.2
* Fix svg-extended style tag generation.
<br /> <br />
## 0.31.1
* Fix bad reverse function implementation.
<br /> <br />
## 0.31.0
* Add `@content` property for text content so that they can be selectable.
* The `@` symbol can be ommited if the functions are chained together.
* Patch reverse function to support both SVG path commands and general arguments.
* Skip variable properties for SVG generation.
* Fix style tag parsing inside SVG for complex CSS selectors.
<br /> <br />
## 0.30.10
* Add `@pnr` alias for `@pr`.
<br /> <br />
## 0.30.9
* Support variables in standalone SVG export function.
* Compute index instead of reversing the whole array.
* Use single rotate for inner `@place` transformation.
* Simplify image detection.
* Use `@pn` and `@position` as aliases.
<br /> <br />
## 0.30.8
Experimenting the new added `pr` function.
<br /> <br />
## 0.30.7
Experimenting the new added `cycle()` function.
<br /> <br />
## 0.30.6
I was wrong. Only set `background-size:100%` to `pattern`, `canvas`, and `shader`.
<br /> <br />
## 0.30.5
* Always add `background-size:100%` to background properties.
<br /> <br />
## 0.30.4
* Normalize quoted SVG attribute values.
<br /> <br />
## 0.30.3
* Do not break for decodeURI.
* Fix SVG generator on cutting values.
<br /> <br />
## 0.30.2
* Fix type error.
<br /> <br />
## 0.30.1
* Fix CSS and SVG parser on quotes and parens.
* Add range support for `@m` function.
```css
::after {
/* 5 4 3 2 1 */
content: @M5-1.@n;
}
```
<br /> <br />
## 0.30.0
* Add exports.
@ -30,6 +388,9 @@
`);
```
<br /> <br />
## 0.29.2

View File

@ -28,6 +28,7 @@ A web component for drawing patterns with CSS.
* [Tabbied](https://tabbied.com) -- Doodle with generated patterns
* [Shapes](https://css-doodle.com/shapes) -- Discover new CSS polygon shapes
* [SVG playground](https://css-doodle.com/svg) -- Generate SVG code with new syntax
## Resources

File diff suppressed because it is too large Load Diff

2
css-doodle.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "css-doodle",
"version": "0.30.0",
"version": "0.34.1",
"description": "A web component for drawing patterns with CSS",
"main": "css-doodle.js",
"scripts": {

View File

@ -1,14 +1,24 @@
import { generate_css } from '../../generator/css.js';
import parse_css from './../../parser/parse-css.js';
import parse_grid from './../../parser/parse-grid.js';
import { make_tag_function } from '../../utils/index.js';
import parse_svg from './../../parser/parse-svg.js';
export default function svg(rules) {
let parsedSvg = parse_svg(rules);
let inlineVariable = '';
for (let item of parsedSvg.value) {
if (item.variable) {
inlineVariable += `${item.name}: ${item.value};`;
}
}
let result = generate_css(
parse_css(`background: @svg(${rules});`),
parse_grid('1x1')
parse_css(`${inlineVariable} background: @svg(${rules});`),
{ x: 1, y: 1, z: 1, count: 1, ratio: 1 }
);
let raw = result && result.styles && result.styles.cells || '';
let cut = raw.substring(52, raw.length - 5);
return decodeURIComponent(cut);
let cut = raw.substring(raw.indexOf('%3'), raw.lastIndexOf('");'));
try {
return decodeURIComponent(cut);
} catch (e) {
return '';
}
}

View File

@ -1,7 +1,7 @@
import { create_svg_url, normalize_svg } from './utils/svg.js';
import { generate_svg } from './generator/svg.js';
import { cell_id, is_letter, is_nil, add_alias, unique_id, lerp } from './utils/index.js';
import { cell_id, is_letter, is_nil, is_empty, add_alias, unique_id, lerp } from './utils/index.js';
import { lazy, clamp, sequence, get_value } from './utils/index.js';
import { by_unit, by_charcode } from './utils/transform.js';
import { last } from './utils/list.js';
@ -26,7 +26,7 @@ function make_sequence(c) {
if (!actions || !n) return '';
let count = get_value(n());
let evaluated = count;
if (/\D/.test(count)){
if (/\D/.test(count) && !/\d+[x-]\d+/.test(count)) {
evaluated = calc(count);
if (evaluated === 0) {
evaluated = count;
@ -61,62 +61,107 @@ function map2d(value, min, max, amp = 1) {
return lerp((value - ma) / (mb - ma), min * amp, max * amp);
}
function calc_with(base) {
return v => {
if (is_empty(v) || is_empty(base)) {
return base;
}
if (/^[+*-\/%][.\d\s]/.test(v)) {
let op = v[0];
let num = Number(v.substr(1).trim()) || 0;
switch (op) {
case '+': return base + num;
case '-': return base - num;
case '*': return base * num;
case '/': return base / num;
case '%': return base % num;
}
}
else if (/[+*-\/%]$/.test(v)) {
let op = v.substr(-1);
let num = Number(v.substr(0, v.length - 1).trim()) || 0;
switch (op) {
case '+': return num + base;
case '-': return num - base;
case '*': return num * base;
case '/': return num / base;
case '%': return num % base;
}
}
return base + (Number(v) || 0);
}
}
const Expose = add_alias({
i({ count }) {
return _ => count;
return calc_with(count);
},
y({ y }) {
return _ => y;
return calc_with(y);
},
x({ x }) {
return _ => x;
return calc_with(x);
},
z({ z }) {
return _ => z;
return calc_with(z);
},
I({ grid }) {
return _ => grid.count;
return calc_with(grid.count);
},
Y({ grid }) {
return _ => grid.y;
return calc_with(grid.y);
},
X({ grid }) {
return _ => grid.x;
return calc_with(grid.x);
},
Z({ grid }) {
return _ => grid.z;
return calc_with(grid.z);
},
id({ x, y, z }) {
return _ => cell_id(x, y, z);
},
dx({ x, grid }) {
return n => {
n = Number(n) || 0;
return x - .5 - n - grid.x / 2;
}
},
dy({ y, grid }) {
return n => {
n = Number(n) || 0;
return y - .5 - n - grid.y / 2;
}
},
n({ extra }) {
let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[0] + (Number(n) || 0)) : '@n';
return lastExtra ? calc_with(lastExtra[0]) : '@n';
},
nx({ extra }) {
let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[1] + (Number(n) || 0)) : '@nx';
return lastExtra ? calc_with(lastExtra[1]) : '@nx';
},
ny({ extra }) {
let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[2] + (Number(n) || 0)) : '@ny';
return lastExtra ? calc_with(lastExtra[2]) : '@ny';
},
N({ extra }) {
let lastExtra = last(extra);
return n => lastExtra ? (lastExtra[3] + (Number(n) || 0)) : '@N';
return lastExtra ? calc_with(lastExtra[3]) : '@N';
},
m: make_sequence(','),
@ -167,21 +212,38 @@ const Expose = add_alias({
});
},
pn({ context, extra, position }) {
pl({ context, extra, position }) {
let lastExtra = last(extra);
let sig = lastExtra ? last(lastExtra) : '';
let counter = 'pn-counter' + position + sig;
let counter = 'pl-counter' + position + sig;
return expand((...args) => {
if (!context[counter]) context[counter] = 0;
context[counter] += 1;
let max = args.length;
let [idx = context[counter]] = lastExtra || [];
let idx = lastExtra && lastExtra[6];
if (is_nil(idx)) idx = context[counter];
let pos = (idx - 1) % max;
let value = args[pos];
return push_stack(context, 'last_pick', value);
});
},
pr({ context, extra, position }) {
let lastExtra = last(extra);
let sig = lastExtra ? last(lastExtra) : '';
let counter = 'pr-counter' + position + sig;
return expand((...args) => {
if (!context[counter]) context[counter] = 0;
context[counter] += 1;
let max = args.length;
let idx = lastExtra && lastExtra[6];
if (is_nil(idx)) idx = context[counter];
let pos = (idx - 1) % max;
let value = args[max - pos - 1];
return push_stack(context, 'last_pick', value);
});
},
pd({ context, extra, position, shuffle }) {
let lastExtra = last(extra);
let sig = lastExtra ? last(lastExtra) : '';
@ -194,7 +256,8 @@ const Expose = add_alias({
context[values] = shuffle(args || []);
}
let max = args.length;
let [idx = context[counter]] = lastExtra || [];
let idx = lastExtra && lastExtra[6];
if (is_nil(idx)) idx = context[counter];
let pos = (idx - 1) % max;
let value = context[values][pos];
return push_stack(context, 'last_pick', value);
@ -321,6 +384,15 @@ const Expose = add_alias({
return create_svg_url(svg);
}),
Svg: lazy((_, ...args) => {
let value = args.map(input => get_value(input())).join(',');
if (!value.startsWith('<')) {
let parsed = parse_svg(value);
value = generate_svg(parsed);
}
return normalize_svg(value);
}),
filter: lazy((upstream, ...args) => {
let values = args.map(input => get_value(input()));
let value = values.join(',');
@ -391,6 +463,20 @@ const Expose = add_alias({
return create_svg_url(svg, id);
}),
'svg-pattern': lazy((_, ...args) => {
let value = args.map(input => get_value(input())).join(',');
let parsed = parse_svg(`
viewBox: 0 0 1 1;
preserveAspectRatio: xMidYMid slice;
rect {
width, height: 100%;
fill: defs pattern { ${ value } }
}
`);
let svg = generate_svg(parsed);
return create_svg_url(svg);
}),
var() {
return value => `var(${ get_value(value) })`;
},
@ -539,13 +625,76 @@ const Expose = add_alias({
}
},
reverse(...args) {
return commands => {
let parsed = parse_svg_path(commands);
if (!parsed.valid) return commands;
return parsed.commands.reverse().map(({ name, value }) => {
return name + value.join(' ');
}).join(' ');
reverse() {
return (...args) => {
let commands = args.map(get_value);
let parsed = parse_svg_path(commands.join(','));
if (parsed.valid) {
let result = [];
for (let i = parsed.commands.length - 1; i >= 0; --i) {
let { name, value } = parsed.commands[i];
result.push(name + value.join(' '));
}
return result.join(' ');
}
return commands.reverse();
}
},
cycle() {
return (...args) => {
let list = [];
let separator;
if (args.length == 1) {
separator = ' ';;
list = parse_value_group(args[0], { symbol: separator });
} else {
separator = ',';
list = parse_value_group(args.map(get_value).join(separator), { symbol: separator});
}
let size = list.length - 1;
let result = [list.join(separator)];
// Just ignore the performance
for (let i = 0; i < size; ++i) {
let item = list.shift();
list.push(item);
result.push(list.join(separator));
}
return result;
}
},
mirror() {
return (...args) => {
for (let i = args.length - 1; i >= 0; --i) {
args.push(args[i]);
}
return args;
}
},
Mirror() {
return (...args) => {
for (let i = args.length - 2; i >= 0; --i) {
args.push(args[i]);
}
return args;
}
},
unicode() {
return (...args) => {
return args.map(code => String.fromCharCode(code));
}
},
once({ context, extra, position }) {
let counter = 'once-counter' + position;
return (...args) => {
if (is_nil(context[counter])) {
context[counter] = args;
}
return context[counter];
}
},
@ -557,6 +706,8 @@ const Expose = add_alias({
'depth': 'z',
'rand': 'r',
'pick': 'p',
'pn': 'pl',
'pnr': 'pr',
// error prone
'stripes': 'stripe',
@ -588,8 +739,8 @@ const Expose = add_alias({
'sz': 'Z',
'size-z': 'Z',
'size-depth': 'Z',
'pick-by-turn': 'pn',
'pick-n': 'pn',
'pick-by-turn': 'pl',
'pick-n': 'pl',
'pick-d': 'pd',
'offset': 'plot',
'Offset': 'Plot',

View File

@ -2,11 +2,12 @@ import Func from '../function.js';
import Property from '../property.js';
import Selector from '../selector.js';
import parse_value_group from '../parser/parse-value-group.js';
import { uniform_time } from '../uniforms.js';
import { prefixer } from '../utils/prefixer.js';
import calc from '../calc.js';
import { uniform_time } from '../uniforms.js';
import { seedrandom } from '../lib/seedrandom.js';
import { prefixer } from '../utils/prefixer.js';
import { maybe, cell_id, is_nil, get_value, lerp, unique_id } from '../utils/index.js';
import { join, make_array, remove_empty_values } from '../utils/list.js'
@ -22,6 +23,10 @@ function is_special_selector(s) {
return is_host_selector(s) || is_parent_selector(s);
}
function is_pseudo_selecotr(s) {
return /\:before|\:after/.test(s);
}
const MathFunc = {};
for (let name of Object.getOwnPropertyNames(Math)) {
MathFunc[name] = () => (...args) => {
@ -43,6 +48,7 @@ class Rules {
this.grid = null;
this.seed = null;
this.is_grid_defined = false;
this.is_gap_defined = false;
this.coords = [];
this.doodles = {};
this.canvas = {};
@ -51,6 +57,7 @@ class Rules {
this.reset();
this.custom_properties = {};
this.uniforms = {};
this.content = {};
}
reset() {
@ -65,6 +72,7 @@ class Rules {
this.canvas = {};
this.pattern = {};
this.shaders = {};
this.content = {};
for (let key in this.rules) {
if (key.startsWith('#c')) {
delete this.rules[key];
@ -322,6 +330,29 @@ class Rules {
}
}
add_grid_style({ fill, clip, rotate, scale, flexRow, flexColumn }) {
if (fill) {
this.add_rule(':host', `background-color: ${fill};`);
}
if (!clip) {
this.add_rule(':host', 'contain: none;');
}
if (rotate) {
this.add_rule(':container', `rotate: ${rotate};`);
}
if (scale) {
this.add_rule(':container', `scale: ${scale};`);
}
if (flexRow) {
this.add_rule(':container', `display: flex`);
this.add_rule('cell', `flex: 1`);
}
if (flexColumn) {
this.add_rule(':container', `display: flex; flex-direction: column;`);
this.add_rule('cell', `flex: 1`);
}
}
compose_rule(token, _coords, selector) {
let coords = Object.assign({}, _coords);
let prop = token.property;
@ -400,7 +431,11 @@ class Rules {
}
}
if (prop === 'background' && (value.includes('shader') || value.includes('canvas') || value.includes('pattern'))) {
let is_image = (
/^(background|background\-image)$/.test(prop) &&
/\$\{(canvas|shader|pattern)/.test(value)
);
if (is_image) {
rule += 'background-size: 100% 100%;';
}
@ -430,6 +465,7 @@ class Rules {
case 'grid': {
if (is_host_selector(selector)) {
rule = transformed.size || '';
this.add_grid_style(transformed);
} else {
rule = '';
if (!this.is_grid_defined) {
@ -439,12 +475,27 @@ class Rules {
max_grid: coords.max_grid
});
this.add_rule(':host', transformed.size || '');
this.add_grid_style(transformed);
}
}
this.grid = coords.grid;
this.is_grid_defined = true;
break;
}
case 'gap': {
rule = '';
if (!this.is_gap_defined) {
this.add_rule(':container', `gap: ${transformed};`);
this.is_gap_defined = true;
}
break;
}
case 'content': {
rule = '';
if (transformed !== undefined && !is_pseudo_selecotr(selector) && !is_parent_selector(selector)) {
this.content[this.compose_selector(coords)] = transformed;
}
}
case 'seed': {
rule = '';
break;
@ -531,7 +582,9 @@ class Rules {
}
}
});
if (!is_nil(this.seed)) {
if (is_nil(this.seed)) {
this.seed = coords.seed_value;
} else {
coords.update_random(this.seed);
}
}
@ -673,6 +726,7 @@ class Rules {
canvas: this.canvas,
pattern: this.pattern,
uniforms: this.uniforms,
content: this.content,
}
}
@ -716,6 +770,7 @@ function generate_css(tokens, grid_size, seed_value, max_grid, seed_random) {
grid: { x: 1, y: 1, z: 1, count: 1 },
random, rand, pick, shuffle,
max_grid, update_random,
seed_value,
});
let { grid, seed } = rules.output();

View File

@ -7,7 +7,10 @@ function generate(token, last) {
} else {
result += token.name + (isInline ? ' ' : '{');
}
if (Array.isArray(token.value) && token.value.length) {
if (token.name === 'style') {
result += token.value;
}
else if (Array.isArray(token.value) && token.value.length) {
let lastGroup = '';
for (let t of token.value) {
result += generate(t, lastGroup);;

View File

@ -49,11 +49,12 @@ class Tag {
}
toString() {
if (this.isTextNode()) {
return this.body;
return removeQuotes(this.body);
}
let attrs = [''];
let body = [];
for (let [name, value] of Object.entries(this.attrs)) {
value = removeQuotes(value);
attrs.push(`${name}="${value}"`);
}
for (let tag of this.body) {
@ -67,11 +68,27 @@ function composeStyleRule(name, value) {
return `${name}:${value};`
}
function composeStyle(block) {
const style = block.value
.map(n => (n.type === 'block') ? composeStyle(n) : composeStyleRule(n.name, n.value))
.join('');
return `${block.name}{${style}}`;
function removeQuotes(text) {
text = String(text);
let double = text.startsWith('"') && text.endsWith('"');
let single = text.startsWith("'") && text.endsWith("'");
if (double || single) {
return text.substring(1, text.length - 1);
}
return text;
}
function transformViewBox(token) {
let viewBox = token.detail.value;
let p = token.detail.padding || token.detail.expand;
if (!viewBox.length) {
return '';
}
let [x, y, w, h] = viewBox;
if (p) {
[x, y, w, h] = [x-p, x-p, w+p*2, h+p*2];
}
return `${x} ${y} ${w} ${h}`;
}
function generate(token, element, parent, root) {
@ -82,17 +99,9 @@ function generate(token, element, parent, root) {
if (token.type === 'block') {
// style tag
if (token.name === 'style') {
if (Array.isArray(token.value)) {
let el = new Tag('style');
let styles = [];
for (let block of token.value) {
if (block.type === 'block') {
styles.push(composeStyle(block));
}
}
el.append(styles.join(''));
element.append(el);
}
let el = new Tag('style');
el.append(token.value);
element.append(el);
}
// normal svg elements
else {
@ -123,9 +132,8 @@ function generate(token, element, parent, root) {
}
}
}
if (token.type === 'statement') {
let isTextNode = parent && /^(text|tspan|textPath|title|desc)$/i.test(parent.name);
if (isTextNode && token.name === 'content') {
if (token.type === 'statement' && !token.variable) {
if (token.name === 'content') {
let text = new Tag('text-node', token.value);
element.append(text);
}
@ -147,7 +155,14 @@ function generate(token, element, parent, root) {
value = `#${id}`;
}
}
element.attr(token.name, value);
if (/viewBox/i.test(token.name)) {
value = transformViewBox(token, value);
if (value) {
element.attr(token.name, value);
}
} else {
element.attr(token.name, value);
}
if (token.name.includes('xlink:')) {
root.attr('xmlns:xlink', NSXLink);
}

View File

@ -58,44 +58,42 @@ if (typeof customElements !== 'undefined') {
update(styles) {
this.cleanup();
let use = this.get_use();
if (!styles) styles = un_entity(this.innerHTML);
this.innerHTML = styles;
// Use old rules to update
if (!styles) {
styles = un_entity(this.innerHTML);
}
if (this.innerHTML !== styles) {
this.innerHTML = styles;
}
if (!this.grid_size) {
this.grid_size = this.get_grid();
}
let { x: gx, y: gy, z: gz } = this.grid_size;
const { x: gx, y: gy, z: gz } = this.grid_size;
const use = this.get_use();
const compiled = this.generate(
parse_css(use + styles, this.extra)
let old_content = '';
if (this.compiled) {
old_content = this.compiled.content;
}
const compiled = this.generate(parse_css(use + styles, this.extra));
let grid = compiled.grid || this.get_grid();
let { x, y, z } = grid;
let should_rebuild = (
!this.shadowRoot.innerHTML
|| (gx !== x || gy !== y || gz !== z)
|| (JSON.stringify(old_content) !== JSON.stringify(compiled.content))
);
if (!this.shadowRoot.innerHTML) {
Object.assign(this.grid_size, compiled.grid);
return this.build_grid(compiled, compiled.grid);
}
Object.assign(this.grid_size, grid);
if (compiled.grid) {
let { x, y, z } = compiled.grid;
if (gx !== x || gy !== y || gz !== z) {
Object.assign(this.grid_size, compiled.grid);
return this.build_grid(compiled, compiled.grid);
}
Object.assign(this.grid_size, compiled.grid);
}
else {
let grid = this.get_grid();
let { x, y, z } = grid;
if (gx !== x || gy !== y || gz !== z) {
Object.assign(this.grid_size, grid);
return this.build_grid(
this.generate(parse_css(use + styles, this.extra)),
grid
);
}
if (should_rebuild) {
return compiled.grid
? this.build_grid(compiled, grid)
: this.build_grid(this.generate(parse_css(use + styles, this.extra)), grid);
}
let replace = this.replace(compiled);
@ -196,7 +194,7 @@ if (typeof customElements !== 'undefined') {
const { keyframes, host, container, cells } = compiled.styles;
let replace = this.replace(compiled);
let grid_container = create_grid(grid);
let grid_container = create_grid(grid, compiled.content);
let size = (options && options.width && options.height)
? `width="${ options.width }" height="${ options.height }"`
@ -391,7 +389,7 @@ if (typeof customElements !== 'undefined') {
let style_container = get_grid_styles(grid) + host + container;
let style_cells = has_delay ? '' : cells;
const { uniforms } = compiled;
const { uniforms, content } = compiled;
let replace = this.replace(compiled);
@ -401,7 +399,7 @@ if (typeof customElements !== 'undefined') {
<style class="style-container">${ style_container }</style>
<style class="style-cells">${ style_cells }</style>
<svg id="defs" xmlns="http://www.w3.org/2000/svg" style="width:0;height:0"></svg>
${ create_grid(grid) }
${ create_grid(grid, content) }
`;
this.set_content('.style-container', replace(style_container));
@ -588,13 +586,14 @@ function get_basic_styles() {
return `
*, *::after, *::before {
box-sizing: border-box;
animation-play-state: var(--cssd-animation-play-state) !important;
animation-play-state: var(--cssd-animation-play-state) !important
}
:host, .host {
display: block;
visibility: visible;
width: auto;
height: auto;
contain: content;
--${ uniform_time.name }: 0
}
:host([hidden]), .host[hidden] {
@ -607,20 +606,19 @@ function get_basic_styles() {
display: grid;
${ inherited_grid_props }
}
cell:empty {
cell {
position: relative;
line-height: 1;
display: grid;
place-items: center
}
svg {
position: absolute;
width: 100%;
height: 100%;
height: 100%
}
:host([cssd-paused-animation]) {
--cssd-animation-play-state: paused;
animation-play-state: paused !important;
animation-play-state: paused !important
}
`;
}
@ -635,41 +633,44 @@ function get_grid_styles(grid_obj) {
`;
}
function create_cell(x, y, z) {
let cell = document.createElement('cell');
cell.id = cell_id(x, y, z);
return cell;
function get_content(input) {
return is_nil(input) ? '' : input;
}
function create_grid(grid_obj) {
function create_cell(x, y, z, content, child = '') {
let id = cell_id(x, y, z);
let head = get_content(content['#' + id]);
let tail = get_content(child);
return `<cell id="${id}">${head}${tail}</cell>`;
}
function create_grid(grid_obj, content) {
let { x, y, z } = grid_obj || {};
let grid = document.createElement('grid');
let root = document.createDocumentFragment();
let result = '';
if (z == 1) {
for (let j = 1; j <= y; ++j) {
for (let i = 1; i <= x; ++i) {
root.appendChild(create_cell(i, j, 1));
result += create_cell(i, j, 1, content);
}
}
}
else {
let temp = null;
for (let i = 1; i <= z; ++i) {
let cell = create_cell(1, 1, i);
(temp || root).appendChild(cell);
temp = cell;
let child = '';
for (let i = z; i >= 1; i--) {
let cell = create_cell(1, 1, i, content, child);
child = cell;
}
temp = null;
result = child;
}
grid.className = 'container';
grid.appendChild(root);
return grid.outerHTML;
return `<grid class="container">${result}</grid>`;
}
export default make_tag_function(rules => {
let doodle = document.createElement('css-doodle');
if (doodle.update) {
doodle.update(rules);
if (typeof document !== 'undefined') {
let doodle = document.createElement('css-doodle');
if (doodle.update) {
doodle.update(rules);
}
return doodle;
}
return doodle;
});

View File

@ -265,9 +265,14 @@ function read_arguments(it, composition, doodle) {
let raw = '';
while (!it.end()) {
c = it.curr();
let prev = it.curr(-1);
let next = it.curr(1);
let start = it.index();
if ((/[\('"`]/.test(c) && it.curr(-1) !== '\\')) {
if ((/[\('"`]/.test(c) && prev !== '\\')) {
if (stack.length) {
if ((c !== '(') && last(stack) === '(') {
stack.pop();
}
if (c != '(' && c === last(stack)) {
stack.pop();
} else {
@ -278,7 +283,7 @@ function read_arguments(it, composition, doodle) {
}
arg += c;
}
else if (c == '@' && !doodle) {
else if ((c == '@' || (prev === '.' && composition)) && !doodle) {
if (!group.length) {
arg = arg.trimLeft();
}
@ -290,7 +295,7 @@ function read_arguments(it, composition, doodle) {
}
else if (doodle && /[)]/.test(c) || (!doodle && /[,)]/.test(c))) {
if (stack.length) {
if (c == ')') {
if (c == ')' && last(stack) === '(') {
stack.pop();
}
arg += c;
@ -324,7 +329,7 @@ function read_arguments(it, composition, doodle) {
}
arg += c;
}
if (composition && (it.curr(1) == ')' || !/[0-9a-zA-Z_\-.]/.test(it.curr())) && !stack.length) {
if (composition && ((next == ')' || next == ';') || !/[0-9a-zA-Z_\-.]/.test(it.curr())) && !stack.length) {
if (group.length) {
args.push(normalize_argument(group));
}
@ -377,14 +382,14 @@ function normalize_argument(group) {
function seperate_func_name(name) {
let fname = '', extra = '';
if ((/\D$/.test(name) && !/\d+x\d+/.test(name)) || Math[name.substr(1)]) {
if ((/\D$/.test(name) && !/\d+[x-]\d+/.test(name)) || Math[name.substr(1)]) {
return { fname: name, extra }
}
for (let i = name.length - 1; i >= 0; i--) {
let c = name[i];
let prev = name[i - 1];
let next = name[i + 1];
if (/[\d.]/.test(c) || ((c == 'x') && /\d/.test(prev) && /\d/.test(next))) {
if (/[\d.]/.test(c) || ((c == 'x' || c == '-') && /\d/.test(prev) && /\d/.test(next))) {
extra = c + extra;
} else {
fname = name.substring(0, i + 1);
@ -399,21 +404,28 @@ function has_times_syntax(token) {
return str.includes('pureName') && str.includes('times');
}
function is_svg(name) {
return /^@svg$/i.test(name);
}
function read_func(it) {
let func = Tokens.func();
let name = '@', c;
let name = it.curr(), c;
let has_argument = false;
it.next();
if (name === '@') {
it.next();
} else {
name = '@';
}
while (!it.end()) {
c = it.curr();
let composition = (c == '.' && it.curr(1) == '@');
let next = it.curr(1);
let composition = (c == '.' && (next == '@' || /[a-zA-Z]/.test(next)));
if (c == '(' || composition) {
has_argument = true;
it.next();
let [args, raw_args] = read_arguments(it, composition, composible(name));
if (name === '@svg' && /\d\s*{/.test(raw_args)) {
if (is_svg(name) && /\d\s*{/.test(raw_args)) {
let parsed_svg = parse_svg(raw_args);
if (has_times_syntax(parsed_svg)) {
let svg = generate_svg_extended(parsed_svg);
@ -425,16 +437,14 @@ function read_func(it) {
}
func.arguments = args;
break;
} else if (!has_argument && next !== '(' && !/[0-9a-zA-Z_\-.]/.test(next)) {
} else if (/[0-9a-zA-Z_\-.]/.test(c)) {
name += c;
break;
}
else {
name += c;
if (!has_argument && next !== '(' && !/[0-9a-zA-Z_\-.]/.test(next)) {
break;
}
it.next();
}
let { fname, extra } = seperate_func_name(name);
func.name = fname;
@ -451,8 +461,9 @@ function read_func(it) {
function read_value(it) {
let text = Tokens.text(), idx = 0, skip = true, c;
const value = [], stack = [];
const value = [];
value[idx] = [];
let stack = [], quote_stack = [];
while (!it.end()) {
c = it.curr();
@ -475,20 +486,29 @@ function read_value(it) {
value[++idx] = [];
skip = true;
}
else if (/[;}<]/.test(c)) {
else if (/[;}<]/.test(c) && !quote_stack.length) {
if (text.value.length) {
value[idx].push(text);
text = Tokens.text();
}
break;
}
else if (c == '@') {
else if (c == '@' && /\w/.test(it.curr(1))) {
if (text.value.length) {
value[idx].push(text);
text = Tokens.text();
}
value[idx].push(read_func(it));
}
else if (c === '"' || c === "'") {
let quote = last(quote_stack);
if (c === quote) {
quote_stack.pop();
} else if (!quote_stack.length) {
quote_stack.push(c);
}
text.value += c;
}
else if (!is.white_space(c) || !is.white_space(it.curr(-1))) {
if (c == '(') stack.push(c);
if (c == ')') stack.pop();
@ -496,9 +516,11 @@ function read_value(it) {
if (symbols[c] && !/[0-9]/.test(it.curr(-1))) {
c = symbols[c];
}
text.value += c;
}
if (it.curr() === ';' || it.curr() == '}') {
break;
}
it.next();
}
if (text.value.length) {

View File

@ -8,19 +8,19 @@ function readStatement(iter, token) {
let stackParen = [];
while (iter.next()) {
let { curr, next } = iter.get();
let isStatementBreak = !stackQuote.length && !stackParen.length && (!next || curr.isSymbol(';') || next.isSymbol('}'));
if (curr.isSymbol('(')) {
if (curr.isSymbol('(') && !stackQuote.length) {
stackParen.push(curr);
} else if (curr.isSymbol(')')) {
} else if (curr.isSymbol(')') && !stackQuote.length) {
stackParen.pop();
}
let isStatementBreak = !stackQuote.length && !stackParen.length && (!next || curr.isSymbol(';') || next.isSymbol('}'));
if (curr.isSymbol("'", '"')) {
if (curr.status === 'open') {
stackQuote.push(curr);
} else {
stackQuote.pop();
}
if (next.isSymbol('}') && !stackQuote.length) {
if ((next && next.isSymbol('}')) && !stackQuote.length) {
isStatementBreak = true;
}
}
@ -47,18 +47,13 @@ function readStatement(iter, token) {
}
break;
}
// skip quotes
let skip = (curr.status == 'open' && stackQuote.length == 1)
|| (curr.status == 'close' && !stackQuote.length);
if (!skip) {
fragment.push(curr);
}
fragment.push(curr);
if (isStatementBreak) {
break;
}
}
if (fragment.length && !inlineBlock) {
token._valueTokens = fragment;
token.value = joinToken(fragment);
} else if (inlineBlock) {
token.value = inlineBlock;
@ -66,7 +61,26 @@ function readStatement(iter, token) {
if (token.origin) {
token.origin.value = token.value;
}
return token
return token;
}
function readStyle(iter) {
let stack = [];
let style = [];
while (iter.next()) {
let { curr } = iter.get();
if (curr.isSymbol('{')) {
stack.push(curr.value);
} else if (curr.isSymbol('}')) {
if (stack.length) {
stack.pop();
} else {
break;
}
}
style.push(curr.value);
}
return style.join('');
}
function walk(iter, parentToken) {
@ -77,13 +91,13 @@ function walk(iter, parentToken) {
while (iter.next()) {
let { prev, curr, next } = iter.get();
let isBlockBreak = !next || curr.isSymbol('}');
if (curr.isSymbol('(')) {
stack.push(curr.value);
}
if (curr.isSymbol(')')) {
stack.pop();
}
let isBlockBreak = !next || curr.isSymbol('}');
if (isBlock(tokenType) && isBlockBreak) {
if (!next && rules.length && !curr.isSymbol('}')) {
let last = rules[rules.length - 1].value;
@ -104,19 +118,29 @@ function walk(iter, parentToken) {
}
let tokenName = selectors.pop();
let skip = isSkip(...selectors, parentToken.name, tokenName);
let block = resolveId(walk(iter, splitTimes(tokenName, {
type: 'block',
name: tokenName,
value: []
})), skip);
while (tokenName = selectors.pop()) {
block = resolveId(splitTimes(tokenName, {
if (tokenName === 'style') {
rules.push({
type: 'block',
name: tokenName,
value: [block]
}), skip);
value: readStyle(iter)
});
} else {
let block = resolveId(walk(iter, splitTimes(tokenName, {
type: 'block',
name: tokenName,
value: []
})), skip);
while (tokenName = selectors.pop()) {
block = resolveId(splitTimes(tokenName, {
type: 'block',
name: tokenName,
value: [block]
}), skip);
}
rules.push(block);
}
rules.push(block);
fragment = [];
}
else if (
@ -137,15 +161,21 @@ function walk(iter, parentToken) {
};
}
let statement = readStatement(iter, intial);
let groupdValue = parseValueGroup(statement.value);
let expand = (props.length > 1 && groupdValue.length === props.length);
props.forEach((prop, i) => {
let item = Object.assign({}, statement, { name: prop });
if (/^\-\-/.test(prop)) {
item.variable = true;
}
if (expand) {
item.value = groupdValue[i];
}
if (/viewBox/i.test(prop)) {
item.detail = parseViewBox(item.value, item._valueTokens);
}
delete item._valueTokens;
rules.push(item);
});
if (isBlock(tokenType)) {
@ -250,6 +280,30 @@ function getSelectors(tokens) {
return result;
}
function parseViewBox(value, tokens) {
const viewBox = { value: [] };
let temp;
if (!Array.isArray(tokens)) {
return viewBox;
}
for (let token of tokens) {
if (token.isSpace() || token.isSymbol(',', ';')) {
continue;
}
if (viewBox.value.length < 4 && token.isNumber()) {
viewBox.value.push(Number(token.value));
}
else if (token.isNumber() && temp) {
viewBox[temp] = Number(token.value);
temp = null;
}
else if (token.isWord()) {
temp = token.value;
}
}
return viewBox;
}
function splitTimes(name, object) {
let target = Object.assign({}, object);
if (/\*\s*[0-9]/.test(name)) {
@ -271,12 +325,20 @@ function isBlock(type) {
}
function skipHeadSVG(block) {
let head = block && block.value && block.value[0];
if (head && head.name === 'svg' && isBlock(head.type)) {
return skipHeadSVG(head);
} else {
return block;
let headSVG, headVariables = [];
for (let item of block.value) {
if (item.name === 'svg') {
headSVG = item;
}
if (item.variable) {
headVariables.push(item);
}
}
if (headSVG) {
headSVG.value.push(...headVariables);
return headSVG;
}
return block;
}
function parse(source, root) {

View File

@ -1,12 +1,13 @@
import { is_empty } from '../utils/index.js';
import { scan, iterator } from './tokenizer.js';
function parse(input, option = {symbol: ',', noSpace: false}) {
function parse(input, option = {symbol: ',', noSpace: false, verbose: false }) {
let group = [];
let skip = false;
let tokens = [];
let parenStack = [];
let quoteStack = [];
let lastGroupName = '';
if (is_empty(input)) {
return group;
@ -15,11 +16,25 @@ function parse(input, option = {symbol: ',', noSpace: false}) {
let iter = iterator(scan(input));
function isSeperator(token) {
let symbol = option.symbol || ',';
if (option.noSpace) {
return token.isSymbol(symbol);
let symbol = option.symbol || [','];
if (!Array.isArray(symbol)) {
symbol = [symbol];
}
if (option.noSpace) {
return token.isSymbol(...symbol);
}
return token.isSymbol(...symbol) || token.isSpace();
}
function addGroup(tokens) {
let value = joinTokens(tokens);
if (option.verbose) {
if (lastGroupName.length || value.length) {
group.push({ group: lastGroupName, value });
}
} else {
group.push(value);
}
return token.isSymbol(symbol) || token.isSpace();
}
while (iter.next()) {
@ -43,7 +58,9 @@ function parse(input, option = {symbol: ',', noSpace: false}) {
if (isNextSpace || isPrevSpace) continue;
}
if (emptyStack && isSeperator(curr)) {
group.push(joinTokens(tokens));
let groupName = lastGroupName;
addGroup(tokens);
lastGroupName = curr.value;
tokens = [];
} else {
tokens.push(curr);
@ -51,7 +68,7 @@ function parse(input, option = {symbol: ',', noSpace: false}) {
}
if (tokens.length) {
group.push(joinTokens(tokens));
addGroup(tokens);
}
return group;

View File

@ -7,6 +7,7 @@ const symbols = [
':', ';', ',', '(', ')', '[', ']',
'{', '}', 'π', '±', '+', '-', '*',
'/', '%', '"', "'", '`', '@', '=',
'^',
];
const is = {

View File

@ -23,7 +23,7 @@ const map_top_bottom = {
export default add_alias({
size(value, { is_special_selector, grid }) {
let [w, h = w] = parse_value_group(value);
let [w, h = w, ratio] = parse_value_group(value);
if (is_preset(w)) {
[w, h] = get_preset(w, h);
}
@ -31,11 +31,22 @@ export default add_alias({
width: ${ w };
height: ${ h };
`;
if (is_special_selector) {
if (w === 'auto' || h === 'auto') {
styles += `aspect-ratio: ${ grid.ratio };`;
if (w === 'auto' || h === 'auto') {
if (ratio) {
if (/^\(.+\)$/.test(ratio)) {
ratio = ratio.substring(1, ratio.length - 1);
} else if (!/^calc/.test(ratio)) {
ratio = `calc(${ratio})`;
}
if (!is_special_selector) {
styles += `aspect-ratio: ${ ratio };`;
}
}
} else {
if (is_special_selector) {
styles += `aspect-ratio: ${ ratio || grid.ratio };`;
}
}
if (!is_special_selector) {
styles += `
--internal-cell-width: ${ w };
--internal-cell-height: ${ h };
@ -44,7 +55,7 @@ export default add_alias({
return styles;
},
position(value, { extra }) {
place(value, { extra }) {
let [left, top = '50%'] = parse_value_group(value);
left = map_left_right[left] || left;
top = map_top_bottom[top] || top;
@ -60,16 +71,45 @@ export default add_alias({
margin-top: calc(${ ch } / -2);
grid-area: unset;
--plot-angle: ${ extra || 0 };
transform: rotate(${ extra || 0 }deg);
rotate: ${ extra || 0 }deg;
`;
},
grid(value, options) {
let [grid, size] = parse_value_group(value, { symbol: '/', noSpace: true });
return {
grid: parse_grid(grid, options.max_grid),
size: size ? this.size(size, options) : ''
let result = {
clip: true,
};
if (/no\-*clip/i.test(value)) {
result.clip = false;
value = value.replace(/no\-*clip/i, '');
}
let groups = parse_value_group(value, {
symbol: ['/', '+', '*', '|', '-'],
noSpace: true,
verbose: true
});
for (let { group, value } of groups) {
if (group === '+') result.scale = value;
if (group === '*') result.rotate = value;
if (group === '/') {
if (result.size === undefined) result.size = this.size(value, options);
else result.fill = value;
}
if ((group === '|' || group == '-' || group == '') && !result.grid) {
result.grid = parse_grid(value, options.max_grid);
if (group === '|') {
result.flexColumn = true;
}
if (group === '-') {
result.flexRow = true;
}
}
}
return result;
},
gap(value) {
return value;
},
seed(value) {
@ -91,11 +131,13 @@ export default add_alias({
}
},
content(value) {
return value;
},
}, {
'place': 'position',
// legacy names.
'place-cell': 'position',
'offset': 'position',
'place-cell': 'place',
'offset': 'place',
'position': 'place',
});

View File

@ -19,7 +19,7 @@ function range(start, stop, step) {
|| (step < 0 && start > stop)) {
range.push(start);
start += step;
if (count++ >= 1000) break;
if (count++ >= 65535) break;
}
if (!range.length) range.push(old);
return range;
@ -57,17 +57,43 @@ function lazy(fn) {
}
function sequence(count, fn) {
let [x, y = 1] = String(count).split('x');
x = clamp(Math.ceil(x) || 1, 1, 65536);
y = clamp(Math.ceil(y) || 1, 1, 65536);
let [x, y = 1] = String(count).split(/[x-]/);
let [cx, cy] = [Math.ceil(x), Math.ceil(y)];
if (is_invalid_number(cx)) cx = 1;
if (is_invalid_number(cy)) cy = 1;
x = clamp(cx, 0, 65536);
y = clamp(cy, 0, 65536);
let max = x * y;
let ret = [];
let index = 1;
for (let i = 1; i <= y; ++i) {
for (let j = 1; j <= x; ++j) {
ret.push(fn(index++, j, i, max, x, y));
if (/x/.test(count)) {
for (let i = 1; i <= y; ++i) {
for (let j = 1; j <= x; ++j) {
ret.push(fn(index++, j, i, max, x, y, index));
}
}
}
else if (/-/.test(count)) {
max = Math.abs(x - y) + 1;
if (x <= y) {
for (let i = x; i <= y; ++i) {
ret.push(fn(i, i, 1, max, max, 1, index++));
}
} else {
for (let i = x; i >= y; --i) {
ret.push(fn(i, i, 1, max, max, 1, index++));
}
}
}
else {
for (let i = 1; i <= x; ++i) {
ret.push(fn(i, i, 1, x, x, 1, index++));
}
}
return ret;
}

View File

@ -130,6 +130,30 @@ test('multiple content', t => {
);
});
test('remove quotes around text', t => {
compare(t,
`text {
content: "hello";
}`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<text>hello</text>
</svg>
`)
);
compare(t,
`text {
content: 'hello';
}`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<text>hello</text>
</svg>
`)
);
});
test('style tag', t => {
compare(t,
`svg {
@ -138,6 +162,8 @@ test('style tag', t => {
circle {
fill: red;
}
circle:nth-child(1) {
}
}
}`,
trim(`
@ -145,6 +171,7 @@ test('style tag', t => {
<circle></circle>
<style>
circle{fill:red;}
circle:nth-child(1){}
</style>
</svg>
`)
@ -259,3 +286,28 @@ test('do not group elements for empty id', t => {
`)
);
});
test('Normalize quoted attribute values', t => {
compare(t,
`circle {
name: "hello";
}
`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<circle name="hello"></circle>
</svg>
`)
);
compare(t,
`circle {
name: 'hello';
}
`,
trim(`
<svg xmlns="http://www.w3.org/2000/svg">
<circle name="hello"></circle>
</svg>
`)
);
});

View File

@ -88,7 +88,6 @@ test('edge cases', t => {
});
test('statement', t => {
compare(t, 'viewBox: 0 0 0 10', {
@ -97,7 +96,24 @@ test('statement', t => {
value: [{
type: 'statement',
name: 'viewBox',
value: '0 0 0 10'
value: '0 0 0 10',
detail: {
value: [0, 0, 0, 10]
}
}]
});
compare(t, 'viewBox: 0 0 10 10 padding .2', {
name: 'svg',
type: 'block',
value: [{
type: 'statement',
name: 'viewBox',
value: '0 0 10 10 padding .2',
detail: {
value: [0, 0, 10, 10],
padding: .2
}
}]
});
@ -116,7 +132,6 @@ test('statement', t => {
});
test('group property', t => {
compare(t, 'cx, cy: 5', {
name: 'svg',
@ -135,10 +150,8 @@ test('group property', t => {
{ type: 'statement', origin: { name: ['cx', 'cy'], value: '5 6' }, name: 'cy', value: '6'},
]
});
});
test('semicolon separated values', t => {
compare(t, 'values: 60; 100; 180', {
name: 'svg',
@ -158,7 +171,6 @@ test('semicolon separated values', t => {
});
});
test('colon separated properties', t => {
compare(t, 'xlink:href: url(#app)', {
name: 'svg',
@ -177,7 +189,6 @@ test('colon separated properties', t => {
});
});
test('block names', t => {
compare(t, 'g circle { } ', {
name: 'svg',
@ -230,7 +241,6 @@ test('block names', t => {
}
]
});
});
test('id expand', t => {
@ -272,7 +282,6 @@ test('id expand', t => {
]
}]
});
});
test('empty id expand', t => {
@ -312,7 +321,7 @@ test('statement end on quotes', t => {
value: [{
name: 'content',
type: 'statement',
value: ''
value: '\'\''
}]
}, {
name: 'g',
@ -322,6 +331,24 @@ test('statement end on quotes', t => {
});
});
test('content values', t => {
compare(t, `
text { content: "world;}" }
`, {
type: 'block',
name: 'svg',
value: [{
type: 'block',
name: 'text',
value: [{
name: 'content',
type: 'statement',
value: '"world;}"'
}]
}]
});
});
test('times syntax', t => {
compare(t, `
circle*10 {}
@ -350,8 +377,6 @@ test('times syntax', t => {
value: []
}]
});
});
test('complex values with parens', t => {
@ -373,3 +398,27 @@ test('complex values with parens', t => {
}]
});
});
test('svg variable', t => {
compare(t, '--a: 1', {
name: 'svg',
type: 'block',
value: [{
type: 'statement',
name: '--a',
value: '1',
variable: true
}]
});
compare(t, '--a: 1; svg {}', {
name: 'svg',
type: 'block',
value: [{
type: 'statement',
name: '--a',
value: '1',
variable: true
}]
});
});

View File

@ -71,3 +71,29 @@ test('grid value', t => {
});
test('space as separator', t => {
compare.use(input => {
return parseValueGroup(input, { symbol: ' ' });
});
compare(t, '5 100%', ['5', '100%']);
compare(t, '5,100%', ['5,100%']);
compare(t, '5, 100% 5', ['5,100%', '5']);
compare(t, '5, 100% 5 8', ['5,100%', '5', '8']);
});
test('verbose option', t => {
compare.use(input => {
return parseValueGroup(input, { symbol: ['v', 'h'], noSpace: true, verbose: true });
});
compare(t, 'v 10 h -10 v 5', [
{ group: 'v', value: '10' },
{ group: 'h', value: '-10' },
{ group: 'v', value: '5' },
]);
});