@mapsight/vector-style-compiler 8.0.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/README.md +366 -0
- package/bin/vector-style-compiler.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +83 -0
- package/dist/cli.js.map +1 -0
- package/dist/cssToRules/mapDeclaration.d.ts +16 -0
- package/dist/cssToRules/mapDeclaration.d.ts.map +1 -0
- package/dist/cssToRules/mapDeclaration.js +24 -0
- package/dist/cssToRules/mapDeclaration.js.map +1 -0
- package/dist/cssToRules/mapRule.d.ts +41 -0
- package/dist/cssToRules/mapRule.d.ts.map +1 -0
- package/dist/cssToRules/mapRule.js +60 -0
- package/dist/cssToRules/mapRule.js.map +1 -0
- package/dist/cssToRules/mapSelector.d.ts +27 -0
- package/dist/cssToRules/mapSelector.d.ts.map +1 -0
- package/dist/cssToRules/mapSelector.js +61 -0
- package/dist/cssToRules/mapSelector.js.map +1 -0
- package/dist/cssToRules/mapSelectorPart.d.ts +48 -0
- package/dist/cssToRules/mapSelectorPart.d.ts.map +1 -0
- package/dist/cssToRules/mapSelectorPart.js +106 -0
- package/dist/cssToRules/mapSelectorPart.js.map +1 -0
- package/dist/cssToRules/mapValue.d.ts +20 -0
- package/dist/cssToRules/mapValue.d.ts.map +1 -0
- package/dist/cssToRules/mapValue.js +67 -0
- package/dist/cssToRules/mapValue.js.map +1 -0
- package/dist/cssToRules.d.ts +50 -0
- package/dist/cssToRules.d.ts.map +1 -0
- package/dist/cssToRules.js +32 -0
- package/dist/cssToRules.js.map +1 -0
- package/dist/helpers/Replacer.d.ts +11 -0
- package/dist/helpers/Replacer.d.ts.map +1 -0
- package/dist/helpers/Replacer.js +28 -0
- package/dist/helpers/Replacer.js.map +1 -0
- package/dist/helpers/compileDeclarationBlock.d.ts +14 -0
- package/dist/helpers/compileDeclarationBlock.d.ts.map +1 -0
- package/dist/helpers/compileDeclarationBlock.js +38 -0
- package/dist/helpers/compileDeclarationBlock.js.map +1 -0
- package/dist/helpers/createModulesMapFromDependencies.d.ts +5 -0
- package/dist/helpers/createModulesMapFromDependencies.d.ts.map +1 -0
- package/dist/helpers/createModulesMapFromDependencies.js +31 -0
- package/dist/helpers/createModulesMapFromDependencies.js.map +1 -0
- package/dist/helpers/ensureObject.d.ts +3 -0
- package/dist/helpers/ensureObject.d.ts.map +1 -0
- package/dist/helpers/ensureObject.js +12 -0
- package/dist/helpers/ensureObject.js.map +1 -0
- package/dist/helpers/pathToExpression.d.ts +2 -0
- package/dist/helpers/pathToExpression.d.ts.map +1 -0
- package/dist/helpers/pathToExpression.js +6 -0
- package/dist/helpers/pathToExpression.js.map +1 -0
- package/dist/helpers/uniqueSerialized.d.ts +2 -0
- package/dist/helpers/uniqueSerialized.d.ts.map +1 -0
- package/dist/helpers/uniqueSerialized.js +5 -0
- package/dist/helpers/uniqueSerialized.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/rulesToTree.d.ts +20 -0
- package/dist/rulesToTree.d.ts.map +1 -0
- package/dist/rulesToTree.js +43 -0
- package/dist/rulesToTree.js.map +1 -0
- package/dist/template.d.ts +16 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +22 -0
- package/dist/template.js.map +1 -0
- package/dist/template.source.d.ts +3 -0
- package/dist/template.source.d.ts.map +1 -0
- package/dist/template.source.js +50 -0
- package/dist/template.source.js.map +1 -0
- package/dist/treeToProgram.d.ts +3 -0
- package/dist/treeToProgram.d.ts.map +1 -0
- package/dist/treeToProgram.js +158 -0
- package/dist/treeToProgram.js.map +1 -0
- package/package.json +67 -0
- package/src/data/custom-css-properties.json +200 -0
- package/src/js/cli.ts +98 -0
- package/src/js/cssToRules/mapDeclaration.ts +45 -0
- package/src/js/cssToRules/mapRule.ts +98 -0
- package/src/js/cssToRules/mapSelector.ts +78 -0
- package/src/js/cssToRules/mapSelectorPart.ts +161 -0
- package/src/js/cssToRules/mapValue.ts +84 -0
- package/src/js/cssToRules.ts +49 -0
- package/src/js/helpers/Replacer.ts +50 -0
- package/src/js/helpers/compileDeclarationBlock.ts +60 -0
- package/src/js/helpers/createModulesMapFromDependencies.ts +40 -0
- package/src/js/helpers/ensureObject.ts +17 -0
- package/src/js/helpers/pathToExpression.ts +5 -0
- package/src/js/helpers/uniqueSerialized.ts +7 -0
- package/src/js/index.ts +28 -0
- package/src/js/rulesToTree.ts +83 -0
- package/src/js/template.source.js +56 -0
- package/src/js/template.ts +50 -0
- package/src/js/treeToProgram.ts +220 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "circle-fill-color",
|
|
4
|
+
"description": "",
|
|
5
|
+
"deprecated": false,
|
|
6
|
+
"sinceVersion": "v0.0.0"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "circle-radius",
|
|
10
|
+
"description": "",
|
|
11
|
+
"deprecated": false,
|
|
12
|
+
"sinceVersion": "v0.0.0"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "circle-stroke-color",
|
|
16
|
+
"description": "",
|
|
17
|
+
"deprecated": false,
|
|
18
|
+
"sinceVersion": "v0.0.0"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "circle-stroke-width",
|
|
22
|
+
"description": "",
|
|
23
|
+
"deprecated": false,
|
|
24
|
+
"sinceVersion": "v0.0.0"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "fill-color",
|
|
28
|
+
"description": "",
|
|
29
|
+
"deprecated": false,
|
|
30
|
+
"sinceVersion": "v0.0.0"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "icon-anchorx",
|
|
34
|
+
"description": "",
|
|
35
|
+
"deprecated": false,
|
|
36
|
+
"sinceVersion": "v0.0.0"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "icon-anchory",
|
|
40
|
+
"description": "",
|
|
41
|
+
"deprecated": false,
|
|
42
|
+
"sinceVersion": "v0.0.0"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "icon-offsetx",
|
|
46
|
+
"description": "",
|
|
47
|
+
"deprecated": false,
|
|
48
|
+
"sinceVersion": "v0.0.0"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "icon-offsety",
|
|
52
|
+
"description": "",
|
|
53
|
+
"deprecated": false,
|
|
54
|
+
"sinceVersion": "v0.0.0"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "icon-opacity",
|
|
58
|
+
"description": "",
|
|
59
|
+
"deprecated": false,
|
|
60
|
+
"sinceVersion": "v0.0.0"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "icon-scale",
|
|
64
|
+
"description": "",
|
|
65
|
+
"deprecated": false,
|
|
66
|
+
"sinceVersion": "v0.0.0"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "icon-sizex",
|
|
70
|
+
"description": "",
|
|
71
|
+
"deprecated": false,
|
|
72
|
+
"sinceVersion": "v0.0.0"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "icon-sizey",
|
|
76
|
+
"description": "",
|
|
77
|
+
"deprecated": false,
|
|
78
|
+
"sinceVersion": "v0.0.0"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "icon-snaptopixel",
|
|
82
|
+
"description": "",
|
|
83
|
+
"deprecated": false,
|
|
84
|
+
"sinceVersion": "v0.0.0"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "icon-src",
|
|
88
|
+
"description": "",
|
|
89
|
+
"deprecated": false,
|
|
90
|
+
"sinceVersion": "v0.0.0"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "image-circle-fill-color",
|
|
94
|
+
"description": "",
|
|
95
|
+
"deprecated": false,
|
|
96
|
+
"sinceVersion": "v0.0.0"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "image-circle-radius",
|
|
100
|
+
"description": "",
|
|
101
|
+
"deprecated": false,
|
|
102
|
+
"sinceVersion": "v0.0.0"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "image-type",
|
|
106
|
+
"description": "",
|
|
107
|
+
"deprecated": false,
|
|
108
|
+
"sinceVersion": "v0.0.0"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "stroke-color",
|
|
112
|
+
"description": "",
|
|
113
|
+
"deprecated": false,
|
|
114
|
+
"sinceVersion": "v0.0.0"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "stroke-linedash",
|
|
118
|
+
"description": "",
|
|
119
|
+
"deprecated": false,
|
|
120
|
+
"sinceVersion": "v0.0.0"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "text-alignment",
|
|
124
|
+
"description": "",
|
|
125
|
+
"deprecated": false,
|
|
126
|
+
"sinceVersion": "v0.0.0"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "text-fill-color",
|
|
130
|
+
"description": "",
|
|
131
|
+
"deprecated": false,
|
|
132
|
+
"sinceVersion": "v0.0.0"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "text-font",
|
|
136
|
+
"description": "",
|
|
137
|
+
"deprecated": false,
|
|
138
|
+
"sinceVersion": "v0.0.0"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "text-offsetx",
|
|
142
|
+
"description": "",
|
|
143
|
+
"deprecated": false,
|
|
144
|
+
"sinceVersion": "v0.0.0"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "text-offsety",
|
|
148
|
+
"description": "",
|
|
149
|
+
"deprecated": false,
|
|
150
|
+
"sinceVersion": "v0.0.0"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "text-placement",
|
|
154
|
+
"description": "",
|
|
155
|
+
"deprecated": false,
|
|
156
|
+
"sinceVersion": "v0.0.0"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "text-stroke-color",
|
|
160
|
+
"description": "",
|
|
161
|
+
"deprecated": false,
|
|
162
|
+
"sinceVersion": "v0.0.0"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "text-stroke-width",
|
|
166
|
+
"description": "",
|
|
167
|
+
"deprecated": false,
|
|
168
|
+
"sinceVersion": "v0.0.0"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "text-testalign",
|
|
172
|
+
"description": "",
|
|
173
|
+
"deprecated": false,
|
|
174
|
+
"sinceVersion": "v0.0.0"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"name": "text-text",
|
|
178
|
+
"description": "",
|
|
179
|
+
"deprecated": false,
|
|
180
|
+
"sinceVersion": "v0.0.0"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "text-textalign",
|
|
184
|
+
"description": "",
|
|
185
|
+
"deprecated": false,
|
|
186
|
+
"sinceVersion": "v0.0.0"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"name": "text-textbaseline",
|
|
190
|
+
"description": "",
|
|
191
|
+
"deprecated": false,
|
|
192
|
+
"sinceVersion": "v0.0.0"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "zindex",
|
|
196
|
+
"description": "",
|
|
197
|
+
"deprecated": false,
|
|
198
|
+
"sinceVersion": "v0.0.0"
|
|
199
|
+
}
|
|
200
|
+
]
|
package/src/js/cli.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {parseArgs} from "node:util";
|
|
4
|
+
|
|
5
|
+
import {watch} from "chokidar";
|
|
6
|
+
import * as sass from "sass";
|
|
7
|
+
|
|
8
|
+
import vectorStyleCompiler from "./index.ts";
|
|
9
|
+
|
|
10
|
+
interface CliOptions {
|
|
11
|
+
output: string;
|
|
12
|
+
name: string;
|
|
13
|
+
include: string[];
|
|
14
|
+
watch: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const debounceAsync = (fn: () => Promise<void>, delay: number) => {
|
|
18
|
+
let timer: NodeJS.Timeout;
|
|
19
|
+
return () => {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
timer = setTimeout(() => {
|
|
22
|
+
fn().catch(console.error);
|
|
23
|
+
}, delay);
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
async function compile(input: string, opts: CliOptions) {
|
|
28
|
+
let css: string;
|
|
29
|
+
if (input.endsWith(".scss") || input.endsWith(".sass")) {
|
|
30
|
+
const result = sass.compile(input, {
|
|
31
|
+
loadPaths: [
|
|
32
|
+
"node_modules",
|
|
33
|
+
...opts.include.map((p: string) => path.resolve(p)),
|
|
34
|
+
],
|
|
35
|
+
style: "expanded" as const,
|
|
36
|
+
quietDeps: true,
|
|
37
|
+
silenceDeprecations: ["legacy-js-api"],
|
|
38
|
+
sourceMap: false,
|
|
39
|
+
});
|
|
40
|
+
css = result.css.toString();
|
|
41
|
+
} else {
|
|
42
|
+
css = await fs.readFile(input, "utf8");
|
|
43
|
+
}
|
|
44
|
+
const outDir = path.resolve(opts.output);
|
|
45
|
+
const cssPath = path.join(outDir, `${opts.name}.css`);
|
|
46
|
+
const jsPath = path.join(outDir, `${opts.name}.js`);
|
|
47
|
+
await fs.mkdir(outDir, {recursive: true});
|
|
48
|
+
await fs.writeFile(cssPath, css);
|
|
49
|
+
const js = vectorStyleCompiler(css);
|
|
50
|
+
await fs.writeFile(jsPath, js);
|
|
51
|
+
console.log(`Updated: ${cssPath}, ${jsPath}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const {values, positionals} = parseArgs({
|
|
55
|
+
options: {
|
|
56
|
+
output: {
|
|
57
|
+
type: "string",
|
|
58
|
+
short: "o",
|
|
59
|
+
default: "tmp/mapsight-vector-styles",
|
|
60
|
+
},
|
|
61
|
+
name: {
|
|
62
|
+
type: "string",
|
|
63
|
+
short: "n",
|
|
64
|
+
default: "default",
|
|
65
|
+
},
|
|
66
|
+
include: {
|
|
67
|
+
type: "string",
|
|
68
|
+
short: "i",
|
|
69
|
+
default: "node_modules",
|
|
70
|
+
},
|
|
71
|
+
watch: {
|
|
72
|
+
type: "boolean",
|
|
73
|
+
default: false,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
allowPositionals: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const input = positionals[0];
|
|
80
|
+
|
|
81
|
+
if (!input) {
|
|
82
|
+
throw new Error("input SCSS or CSS file is required");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const options: CliOptions = {
|
|
86
|
+
output: values.output,
|
|
87
|
+
name: values.name,
|
|
88
|
+
include: values.include.split(","),
|
|
89
|
+
watch: values.watch,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
await compile(input, options);
|
|
93
|
+
|
|
94
|
+
if (options.watch) {
|
|
95
|
+
const debouncedCompile = debounceAsync(() => compile(input, options), 300);
|
|
96
|
+
watch(input).on("change", debouncedCompile);
|
|
97
|
+
console.log(`Watching ${input}...`);
|
|
98
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type {Declaration as CssDeclaration} from "css";
|
|
2
|
+
|
|
3
|
+
import mapValue from "./mapValue.ts";
|
|
4
|
+
|
|
5
|
+
export type DeclarationLeaf = {value: string | number | null};
|
|
6
|
+
|
|
7
|
+
export interface DeclarationNode {
|
|
8
|
+
[key: string]: DeclarationNode | DeclarationLeaf;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type Declaration = {
|
|
12
|
+
declaration: DeclarationNode;
|
|
13
|
+
__meta: ReturnType<typeof mapValue>["__meta"] & {
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default function mapDeclaration(
|
|
19
|
+
declaration: CssDeclaration,
|
|
20
|
+
): Declaration {
|
|
21
|
+
const {value, __meta: valueMeta} = mapValue(declaration.value);
|
|
22
|
+
|
|
23
|
+
if (!declaration.property) {
|
|
24
|
+
throw new Error("Declaration is lacking property");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const keyParts = declaration.property.split("-");
|
|
28
|
+
|
|
29
|
+
// build deep object
|
|
30
|
+
const result: DeclarationNode = {};
|
|
31
|
+
let current: DeclarationNode = result;
|
|
32
|
+
for (let i = 0; i < keyParts.length - 1; i++) {
|
|
33
|
+
const part = keyParts[i]!;
|
|
34
|
+
const next: DeclarationNode = {};
|
|
35
|
+
current[part] = next;
|
|
36
|
+
current = next;
|
|
37
|
+
}
|
|
38
|
+
const lastPart = keyParts[keyParts.length - 1]!;
|
|
39
|
+
current[lastPart] = {value};
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
declaration: result,
|
|
43
|
+
__meta: {name: keyParts[0]!, ...valueMeta},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type css from "css";
|
|
2
|
+
|
|
3
|
+
import unique from "@mapsight/lib-js/array/unique";
|
|
4
|
+
import {isTruthy} from "@mapsight/lib-js/boolean";
|
|
5
|
+
import deepMerge from "@mapsight/lib-js/object/deep-extend";
|
|
6
|
+
|
|
7
|
+
import uniqueSerialized from "../helpers/uniqueSerialized.ts";
|
|
8
|
+
import type {DeclarationNode} from "./mapDeclaration.ts";
|
|
9
|
+
import mapDeclaration from "./mapDeclaration.ts";
|
|
10
|
+
import mapSelector, {type Selector} from "./mapSelector.ts";
|
|
11
|
+
|
|
12
|
+
const isDeclaration = (
|
|
13
|
+
val: css.Declaration | css.Comment,
|
|
14
|
+
): val is css.Declaration => val.type === "declaration";
|
|
15
|
+
|
|
16
|
+
export default function mapRule(rule: css.Rule) {
|
|
17
|
+
const declarations =
|
|
18
|
+
rule.declarations
|
|
19
|
+
?.filter(isDeclaration)
|
|
20
|
+
.map(mapDeclaration)
|
|
21
|
+
.filter((a) => !!a) ?? [];
|
|
22
|
+
|
|
23
|
+
const mergedDeclaration: DeclarationNode = deepMerge(
|
|
24
|
+
{},
|
|
25
|
+
...declarations.map((a) => a.declaration),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// meta data
|
|
29
|
+
const mergedDeclarationNames = unique(
|
|
30
|
+
declarations.map((declaration) => declaration.__meta.name),
|
|
31
|
+
);
|
|
32
|
+
const mergedStyleProps = unique(
|
|
33
|
+
declarations.flatMap((declaration) => declaration.__meta.styleProps),
|
|
34
|
+
);
|
|
35
|
+
const mergedStylePropExpressions = unique(
|
|
36
|
+
declarations.flatMap(
|
|
37
|
+
(declaration) => declaration.__meta.stylePropExpressions,
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const selectors = uniqueSerialized(rule.selectors?.map(mapSelector) ?? []);
|
|
42
|
+
const groupedSelectors: Record<string, Array<Selector>> = {};
|
|
43
|
+
selectors.forEach((selector) => {
|
|
44
|
+
const existing = groupedSelectors[selector.group];
|
|
45
|
+
if (Array.isArray(existing)) {
|
|
46
|
+
existing.push(selector);
|
|
47
|
+
} else {
|
|
48
|
+
groupedSelectors[selector.group] = [selector];
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return Object.keys(groupedSelectors).map((group) => {
|
|
53
|
+
const conditions = uniqueSerialized(groupedSelectors[group]!);
|
|
54
|
+
const mergedStateNames = unique(
|
|
55
|
+
conditions.flatMap((condition) => condition.__meta.stateNames),
|
|
56
|
+
);
|
|
57
|
+
const mergedConditionStyleProps = unique(
|
|
58
|
+
conditions.flatMap((condition) => condition.__meta.styleProps),
|
|
59
|
+
);
|
|
60
|
+
const mergedConditionStylePropExpressions = unique(
|
|
61
|
+
conditions.flatMap(
|
|
62
|
+
(condition) => condition.__meta.stylePropExpressions,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
conditions: conditions,
|
|
68
|
+
declarations: {
|
|
69
|
+
[group]: mergedDeclaration,
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
__meta: {
|
|
73
|
+
styleNames: unique(
|
|
74
|
+
conditions
|
|
75
|
+
.map((condition) => condition.style)
|
|
76
|
+
.filter(isTruthy),
|
|
77
|
+
),
|
|
78
|
+
stateNames: [
|
|
79
|
+
...unique(
|
|
80
|
+
conditions
|
|
81
|
+
.map((condition) => condition.state)
|
|
82
|
+
.filter(isTruthy),
|
|
83
|
+
),
|
|
84
|
+
...mergedStateNames,
|
|
85
|
+
],
|
|
86
|
+
groupNames: unique(
|
|
87
|
+
conditions.map((condition) => condition.group),
|
|
88
|
+
),
|
|
89
|
+
declarationNames: mergedDeclarationNames,
|
|
90
|
+
styleProps: [...mergedConditionStyleProps, ...mergedStyleProps],
|
|
91
|
+
stylePropExpressions: [
|
|
92
|
+
...mergedConditionStylePropExpressions,
|
|
93
|
+
...mergedStylePropExpressions,
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import unique from "@mapsight/lib-js/array/unique";
|
|
2
|
+
import {isTruthy} from "@mapsight/lib-js/boolean";
|
|
3
|
+
import {ensureNonNullable} from "@mapsight/lib-js/nonNullable";
|
|
4
|
+
|
|
5
|
+
import mapSelectorPart from "./mapSelectorPart.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Finds
|
|
9
|
+
* A) Words incl. whitespace enclosed by matching single quotes ('), square brackets ([,]), not (:not(,)) or matching double quotes (") and
|
|
10
|
+
* B) Words (groups of non-whitespace characters)
|
|
11
|
+
*
|
|
12
|
+
* @type {RegExp}
|
|
13
|
+
*/
|
|
14
|
+
const REGEX_SELECTOR_PART: RegExp = /('.*?'|\[.*?]|:not\(.*?\)|".*?"|\S+)/g;
|
|
15
|
+
|
|
16
|
+
export type Selector = ReturnType<typeof mapSelector>;
|
|
17
|
+
|
|
18
|
+
export default function mapSelector(selector: string) {
|
|
19
|
+
const selectorParts = ensureNonNullable(selector.match(REGEX_SELECTOR_PART))
|
|
20
|
+
.map((part) => mapSelectorPart(part))
|
|
21
|
+
.filter(isTruthy);
|
|
22
|
+
const checks = unique(
|
|
23
|
+
selectorParts.map((a) => "check" in a && a.check).filter(isTruthy),
|
|
24
|
+
);
|
|
25
|
+
const mergedStateNames = unique(
|
|
26
|
+
selectorParts
|
|
27
|
+
.map((part) =>
|
|
28
|
+
"stateNames" in part.__meta
|
|
29
|
+
? part.__meta.stateNames
|
|
30
|
+
: undefined,
|
|
31
|
+
)
|
|
32
|
+
.filter(isTruthy)
|
|
33
|
+
.flat(),
|
|
34
|
+
);
|
|
35
|
+
const mergedStyleProps = unique(
|
|
36
|
+
selectorParts
|
|
37
|
+
.map((part) =>
|
|
38
|
+
"styleProps" in part.__meta
|
|
39
|
+
? part.__meta.styleProps
|
|
40
|
+
: undefined,
|
|
41
|
+
)
|
|
42
|
+
.filter(isTruthy)
|
|
43
|
+
.flat(),
|
|
44
|
+
);
|
|
45
|
+
const mergedStylePropExpressions = unique(
|
|
46
|
+
selectorParts
|
|
47
|
+
.map((part) =>
|
|
48
|
+
"stylePropExpressions" in part.__meta
|
|
49
|
+
? part.__meta.stylePropExpressions
|
|
50
|
+
: undefined,
|
|
51
|
+
)
|
|
52
|
+
.filter(isTruthy)
|
|
53
|
+
.flat(),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
let style;
|
|
57
|
+
let state;
|
|
58
|
+
let group;
|
|
59
|
+
for (const part of selectorParts) {
|
|
60
|
+
if (!state && "group" in part) group = part.group;
|
|
61
|
+
if (!style && "style" in part) style = part.style;
|
|
62
|
+
if (!state && "state" in part) state = part.state;
|
|
63
|
+
if (state && style && group) break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
style: style,
|
|
68
|
+
state: state,
|
|
69
|
+
group: group || "default",
|
|
70
|
+
checks: checks?.length ? checks : undefined,
|
|
71
|
+
|
|
72
|
+
__meta: {
|
|
73
|
+
stateNames: mergedStateNames,
|
|
74
|
+
styleProps: mergedStyleProps,
|
|
75
|
+
stylePropExpressions: mergedStylePropExpressions,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import trimQuotes from "@mapsight/lib-js/string/trimQuotes";
|
|
2
|
+
|
|
3
|
+
import mapValue from "./mapValue.ts";
|
|
4
|
+
|
|
5
|
+
type JsCheck = {
|
|
6
|
+
type: "js";
|
|
7
|
+
expression: string;
|
|
8
|
+
negate: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type GeometryTypeCheck = {
|
|
12
|
+
type: "geometryType";
|
|
13
|
+
value: string;
|
|
14
|
+
negate: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type ValueCheck = {
|
|
18
|
+
type: "value";
|
|
19
|
+
target: "props" | "env";
|
|
20
|
+
path: string[];
|
|
21
|
+
value?: string | number | null;
|
|
22
|
+
negate: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type Check = JsCheck | GeometryTypeCheck | ValueCheck;
|
|
26
|
+
|
|
27
|
+
function mapAttributeSelectorPart(
|
|
28
|
+
part: string,
|
|
29
|
+
negate = false,
|
|
30
|
+
): {
|
|
31
|
+
check: Check;
|
|
32
|
+
__meta: {
|
|
33
|
+
styleProps?: string[];
|
|
34
|
+
stylePropExpressions?: string[];
|
|
35
|
+
stateNames?: string[];
|
|
36
|
+
};
|
|
37
|
+
} {
|
|
38
|
+
const operands = part
|
|
39
|
+
.slice(1, -1) // remove square brackets
|
|
40
|
+
.split("="); // split by first equal sign
|
|
41
|
+
let leftHandOperand = operands?.shift()?.trim() || "";
|
|
42
|
+
const rightHandOperand = operands.length
|
|
43
|
+
? trimQuotes(operands.join("=").trim())
|
|
44
|
+
: undefined;
|
|
45
|
+
|
|
46
|
+
// special case: js expression
|
|
47
|
+
if (leftHandOperand.startsWith("|js")) {
|
|
48
|
+
return {
|
|
49
|
+
check: {
|
|
50
|
+
type: "js",
|
|
51
|
+
expression: rightHandOperand || "",
|
|
52
|
+
negate,
|
|
53
|
+
},
|
|
54
|
+
__meta: {},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// special case: geometry type
|
|
59
|
+
if (leftHandOperand === "geometry|type") {
|
|
60
|
+
const {value, __meta: valueMeta} = mapValue(rightHandOperand);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
check: {
|
|
64
|
+
type: "geometryType",
|
|
65
|
+
value: String(value ?? ""),
|
|
66
|
+
negate,
|
|
67
|
+
},
|
|
68
|
+
__meta: valueMeta,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let target: "props" | "env" = "props";
|
|
73
|
+
|
|
74
|
+
// env target
|
|
75
|
+
if (leftHandOperand.startsWith("env|")) {
|
|
76
|
+
target = "env";
|
|
77
|
+
leftHandOperand = leftHandOperand.slice(4);
|
|
78
|
+
} else if (leftHandOperand.startsWith("props|")) {
|
|
79
|
+
// trim optional prefix
|
|
80
|
+
leftHandOperand = leftHandOperand.slice(6);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// kebab case to dot separated string
|
|
84
|
+
const path = leftHandOperand.split("-");
|
|
85
|
+
|
|
86
|
+
// keep track of props used for styling
|
|
87
|
+
let stateNames: string[] = [];
|
|
88
|
+
let styleProps: string[] = [];
|
|
89
|
+
let stylePropExpressions: string[] = [];
|
|
90
|
+
|
|
91
|
+
if (target === "props") {
|
|
92
|
+
styleProps.push(path[0]!);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let value = undefined;
|
|
96
|
+
if (rightHandOperand !== undefined) {
|
|
97
|
+
const mappedValue = mapValue(rightHandOperand);
|
|
98
|
+
value = mappedValue.value;
|
|
99
|
+
styleProps = styleProps.concat(mappedValue.__meta.styleProps);
|
|
100
|
+
stylePropExpressions = stylePropExpressions.concat(
|
|
101
|
+
mappedValue.__meta.stylePropExpressions,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (leftHandOperand === "state") {
|
|
105
|
+
stateNames = [rightHandOperand];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
check: {
|
|
111
|
+
type: "value",
|
|
112
|
+
target,
|
|
113
|
+
path,
|
|
114
|
+
value,
|
|
115
|
+
negate,
|
|
116
|
+
},
|
|
117
|
+
__meta: {
|
|
118
|
+
stateNames: stateNames,
|
|
119
|
+
styleProps: styleProps,
|
|
120
|
+
stylePropExpressions: stylePropExpressions,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default function mapSelectorPart(part: string, negate = false) {
|
|
126
|
+
// Handle :not(...) negation using recursion
|
|
127
|
+
if (part.startsWith(":not(") && part.endsWith(")")) {
|
|
128
|
+
const inner = part.slice(5, -1).trim(); // remove :not( and )
|
|
129
|
+
return mapSelectorPart(inner, !negate);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const firstLetter = part.charAt(0);
|
|
133
|
+
if (firstLetter === "[") {
|
|
134
|
+
return mapAttributeSelectorPart(part, negate);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (negate) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"Cannot negate selector part [" + part + "] with :not().",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (firstLetter === "*") {
|
|
144
|
+
return {__meta: {}} as const;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const rest = part.slice(1);
|
|
148
|
+
if (firstLetter === ":") {
|
|
149
|
+
return {state: rest, __meta: {}} as const;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (firstLetter === "#") {
|
|
153
|
+
return {style: rest, __meta: {}} as const;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (firstLetter === ".") {
|
|
157
|
+
return {group: rest, __meta: {}} as const;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return null;
|
|
161
|
+
}
|