@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.
Files changed (94) hide show
  1. package/README.md +366 -0
  2. package/bin/vector-style-compiler.js +2 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +83 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/cssToRules/mapDeclaration.d.ts +16 -0
  8. package/dist/cssToRules/mapDeclaration.d.ts.map +1 -0
  9. package/dist/cssToRules/mapDeclaration.js +24 -0
  10. package/dist/cssToRules/mapDeclaration.js.map +1 -0
  11. package/dist/cssToRules/mapRule.d.ts +41 -0
  12. package/dist/cssToRules/mapRule.d.ts.map +1 -0
  13. package/dist/cssToRules/mapRule.js +60 -0
  14. package/dist/cssToRules/mapRule.js.map +1 -0
  15. package/dist/cssToRules/mapSelector.d.ts +27 -0
  16. package/dist/cssToRules/mapSelector.d.ts.map +1 -0
  17. package/dist/cssToRules/mapSelector.js +61 -0
  18. package/dist/cssToRules/mapSelector.js.map +1 -0
  19. package/dist/cssToRules/mapSelectorPart.d.ts +48 -0
  20. package/dist/cssToRules/mapSelectorPart.d.ts.map +1 -0
  21. package/dist/cssToRules/mapSelectorPart.js +106 -0
  22. package/dist/cssToRules/mapSelectorPart.js.map +1 -0
  23. package/dist/cssToRules/mapValue.d.ts +20 -0
  24. package/dist/cssToRules/mapValue.d.ts.map +1 -0
  25. package/dist/cssToRules/mapValue.js +67 -0
  26. package/dist/cssToRules/mapValue.js.map +1 -0
  27. package/dist/cssToRules.d.ts +50 -0
  28. package/dist/cssToRules.d.ts.map +1 -0
  29. package/dist/cssToRules.js +32 -0
  30. package/dist/cssToRules.js.map +1 -0
  31. package/dist/helpers/Replacer.d.ts +11 -0
  32. package/dist/helpers/Replacer.d.ts.map +1 -0
  33. package/dist/helpers/Replacer.js +28 -0
  34. package/dist/helpers/Replacer.js.map +1 -0
  35. package/dist/helpers/compileDeclarationBlock.d.ts +14 -0
  36. package/dist/helpers/compileDeclarationBlock.d.ts.map +1 -0
  37. package/dist/helpers/compileDeclarationBlock.js +38 -0
  38. package/dist/helpers/compileDeclarationBlock.js.map +1 -0
  39. package/dist/helpers/createModulesMapFromDependencies.d.ts +5 -0
  40. package/dist/helpers/createModulesMapFromDependencies.d.ts.map +1 -0
  41. package/dist/helpers/createModulesMapFromDependencies.js +31 -0
  42. package/dist/helpers/createModulesMapFromDependencies.js.map +1 -0
  43. package/dist/helpers/ensureObject.d.ts +3 -0
  44. package/dist/helpers/ensureObject.d.ts.map +1 -0
  45. package/dist/helpers/ensureObject.js +12 -0
  46. package/dist/helpers/ensureObject.js.map +1 -0
  47. package/dist/helpers/pathToExpression.d.ts +2 -0
  48. package/dist/helpers/pathToExpression.d.ts.map +1 -0
  49. package/dist/helpers/pathToExpression.js +6 -0
  50. package/dist/helpers/pathToExpression.js.map +1 -0
  51. package/dist/helpers/uniqueSerialized.d.ts +2 -0
  52. package/dist/helpers/uniqueSerialized.d.ts.map +1 -0
  53. package/dist/helpers/uniqueSerialized.js +5 -0
  54. package/dist/helpers/uniqueSerialized.js.map +1 -0
  55. package/dist/index.d.ts +8 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +15 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/rulesToTree.d.ts +20 -0
  60. package/dist/rulesToTree.d.ts.map +1 -0
  61. package/dist/rulesToTree.js +43 -0
  62. package/dist/rulesToTree.js.map +1 -0
  63. package/dist/template.d.ts +16 -0
  64. package/dist/template.d.ts.map +1 -0
  65. package/dist/template.js +22 -0
  66. package/dist/template.js.map +1 -0
  67. package/dist/template.source.d.ts +3 -0
  68. package/dist/template.source.d.ts.map +1 -0
  69. package/dist/template.source.js +50 -0
  70. package/dist/template.source.js.map +1 -0
  71. package/dist/treeToProgram.d.ts +3 -0
  72. package/dist/treeToProgram.d.ts.map +1 -0
  73. package/dist/treeToProgram.js +158 -0
  74. package/dist/treeToProgram.js.map +1 -0
  75. package/package.json +67 -0
  76. package/src/data/custom-css-properties.json +200 -0
  77. package/src/js/cli.ts +98 -0
  78. package/src/js/cssToRules/mapDeclaration.ts +45 -0
  79. package/src/js/cssToRules/mapRule.ts +98 -0
  80. package/src/js/cssToRules/mapSelector.ts +78 -0
  81. package/src/js/cssToRules/mapSelectorPart.ts +161 -0
  82. package/src/js/cssToRules/mapValue.ts +84 -0
  83. package/src/js/cssToRules.ts +49 -0
  84. package/src/js/helpers/Replacer.ts +50 -0
  85. package/src/js/helpers/compileDeclarationBlock.ts +60 -0
  86. package/src/js/helpers/createModulesMapFromDependencies.ts +40 -0
  87. package/src/js/helpers/ensureObject.ts +17 -0
  88. package/src/js/helpers/pathToExpression.ts +5 -0
  89. package/src/js/helpers/uniqueSerialized.ts +7 -0
  90. package/src/js/index.ts +28 -0
  91. package/src/js/rulesToTree.ts +83 -0
  92. package/src/js/template.source.js +56 -0
  93. package/src/js/template.ts +50 -0
  94. 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
+ }