pulse-js-framework 1.10.0 → 1.10.3
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/compiler/parser/_extract.js +393 -0
- package/compiler/parser/blocks.js +361 -0
- package/compiler/parser/core.js +306 -0
- package/compiler/parser/expressions.js +386 -0
- package/compiler/parser/imports.js +108 -0
- package/compiler/parser/index.js +47 -0
- package/compiler/parser/state.js +155 -0
- package/compiler/parser/style.js +445 -0
- package/compiler/parser/view.js +632 -0
- package/compiler/parser.js +15 -2372
- package/compiler/parser.js.original +2376 -0
- package/package.json +2 -1
- package/runtime/a11y/announcements.js +213 -0
- package/runtime/a11y/contrast.js +125 -0
- package/runtime/a11y/focus.js +412 -0
- package/runtime/a11y/index.js +35 -0
- package/runtime/a11y/preferences.js +121 -0
- package/runtime/a11y/utils.js +164 -0
- package/runtime/a11y/validation.js +258 -0
- package/runtime/a11y/widgets.js +545 -0
- package/runtime/a11y.js +15 -1840
- package/runtime/a11y.js.original +1844 -0
- package/runtime/graphql/cache.js +69 -0
- package/runtime/graphql/client.js +563 -0
- package/runtime/graphql/hooks.js +492 -0
- package/runtime/graphql/index.js +62 -0
- package/runtime/graphql/subscriptions.js +241 -0
- package/runtime/graphql.js +12 -1322
- package/runtime/graphql.js.original +1326 -0
- package/runtime/router/core.js +956 -0
- package/runtime/router/guards.js +90 -0
- package/runtime/router/history.js +204 -0
- package/runtime/router/index.js +36 -0
- package/runtime/router/lazy.js +180 -0
- package/runtime/router/utils.js +226 -0
- package/runtime/router.js +12 -1600
- package/runtime/router.js.original +1605 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse Parser - Style Block Parsing
|
|
3
|
+
*
|
|
4
|
+
* Handles parsing of style blocks including CSS rules and properties
|
|
5
|
+
*
|
|
6
|
+
* @module compiler/parser/style
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { TokenType } from '../lexer.js';
|
|
10
|
+
import { NodeType, ASTNode, Parser } from './core.js';
|
|
11
|
+
|
|
12
|
+
// ============================================================
|
|
13
|
+
// Style Block Parsing
|
|
14
|
+
// ============================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse style block
|
|
18
|
+
*/
|
|
19
|
+
Parser.prototype.parseStyleBlock = function() {
|
|
20
|
+
this.expect(TokenType.STYLE);
|
|
21
|
+
const startBrace = this.expect(TokenType.LBRACE);
|
|
22
|
+
|
|
23
|
+
// Extract raw CSS content for preprocessor support
|
|
24
|
+
// Instead of parsing token by token, collect all tokens until matching }
|
|
25
|
+
const rawTokens = [];
|
|
26
|
+
let braceDepth = 1; // We've already consumed the opening {
|
|
27
|
+
const startPos = this.pos;
|
|
28
|
+
|
|
29
|
+
while (braceDepth > 0 && !this.is(TokenType.EOF)) {
|
|
30
|
+
const token = this.current();
|
|
31
|
+
if (token.type === TokenType.LBRACE) braceDepth++;
|
|
32
|
+
if (token.type === TokenType.RBRACE) braceDepth--;
|
|
33
|
+
|
|
34
|
+
if (braceDepth > 0) {
|
|
35
|
+
rawTokens.push(token);
|
|
36
|
+
this.advance();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.expect(TokenType.RBRACE);
|
|
41
|
+
|
|
42
|
+
// Reconstruct raw CSS from tokens for preprocessor
|
|
43
|
+
const rawCSS = this.reconstructCSS(rawTokens);
|
|
44
|
+
|
|
45
|
+
// Try to parse as structured CSS (will work for plain CSS)
|
|
46
|
+
// If parsing fails, fall back to raw mode for preprocessors
|
|
47
|
+
let rules = [];
|
|
48
|
+
let parseError = null;
|
|
49
|
+
|
|
50
|
+
// Reset to try parsing
|
|
51
|
+
const savedPos = this.pos;
|
|
52
|
+
this.pos = startPos;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
56
|
+
rules.push(this.parseStyleRule());
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Parsing failed - likely preprocessor syntax (LESS/SASS/Stylus)
|
|
60
|
+
parseError = error;
|
|
61
|
+
rules = []; // Clear any partial parse
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Restore position to after the closing }
|
|
65
|
+
this.pos = savedPos;
|
|
66
|
+
|
|
67
|
+
return new ASTNode(NodeType.StyleBlock, {
|
|
68
|
+
rules,
|
|
69
|
+
raw: rawCSS,
|
|
70
|
+
parseError: parseError ? parseError.message : null
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Reconstruct CSS from tokens, preserving formatting
|
|
76
|
+
*/
|
|
77
|
+
Parser.prototype.reconstructCSS = function(tokens) {
|
|
78
|
+
if (!tokens.length) return '';
|
|
79
|
+
|
|
80
|
+
const lines = [];
|
|
81
|
+
let currentLine = [];
|
|
82
|
+
let lastLine = tokens[0].line;
|
|
83
|
+
|
|
84
|
+
for (const token of tokens) {
|
|
85
|
+
if (token.line !== lastLine) {
|
|
86
|
+
lines.push(currentLine.join(''));
|
|
87
|
+
currentLine = [];
|
|
88
|
+
lastLine = token.line;
|
|
89
|
+
}
|
|
90
|
+
currentLine.push(token.raw || token.value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (currentLine.length > 0) {
|
|
94
|
+
lines.push(currentLine.join(''));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return lines.join('\n').trim();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse style rule
|
|
102
|
+
*/
|
|
103
|
+
Parser.prototype.parseStyleRule = function() {
|
|
104
|
+
// Parse selector - preserve spaces between tokens
|
|
105
|
+
const selectorParts = [];
|
|
106
|
+
let lastLine = this.current()?.line;
|
|
107
|
+
let lastToken = null;
|
|
108
|
+
let inAtRule = false; // Track if we're inside an @-rule like @media
|
|
109
|
+
let inParens = 0; // Track parenthesis depth
|
|
110
|
+
|
|
111
|
+
while (!this.is(TokenType.LBRACE) && !this.is(TokenType.EOF)) {
|
|
112
|
+
const token = this.advance();
|
|
113
|
+
const currentLine = token.line;
|
|
114
|
+
const tokenValue = String(token.value);
|
|
115
|
+
|
|
116
|
+
// Track @-rules (media queries, keyframes, etc.)
|
|
117
|
+
if (tokenValue === '@') {
|
|
118
|
+
inAtRule = true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Track parenthesis depth for media queries
|
|
122
|
+
if (tokenValue === '(') inParens++;
|
|
123
|
+
if (tokenValue === ')') inParens--;
|
|
124
|
+
|
|
125
|
+
// Determine if we need a space before this token
|
|
126
|
+
if (selectorParts.length > 0 && currentLine === lastLine) {
|
|
127
|
+
const lastPart = selectorParts[selectorParts.length - 1];
|
|
128
|
+
|
|
129
|
+
// Don't add space after these (they attach to what follows)
|
|
130
|
+
const noSpaceAfter = new Set(['.', '#', '[', '(', '>', '+', '~', '-', '@', ':']);
|
|
131
|
+
|
|
132
|
+
// Don't add space before these (they attach to what precedes)
|
|
133
|
+
// In @media queries inside parens: "max-width:" should not have space before ":"
|
|
134
|
+
const noSpaceBefore = new Set([']', ')', ',', '.', '#', '-', ':']);
|
|
135
|
+
|
|
136
|
+
// CSS units that should attach to numbers (no space before)
|
|
137
|
+
const cssUnits = new Set(['px', 'em', 'rem', 'vh', 'vw', 'vmin', 'vmax', '%', 'fr', 's', 'ms', 'deg', 'rad', 'turn', 'grad', 'ex', 'ch', 'pt', 'pc', 'in', 'cm', 'mm', 'dvh', 'dvw', 'svh', 'svw', 'lvh', 'lvw']);
|
|
138
|
+
|
|
139
|
+
// Special case: . or # after an identifier needs space (descendant selector)
|
|
140
|
+
const expectedNextCol = lastToken ? (lastToken.column + String(lastToken.value).length) : 0;
|
|
141
|
+
const tokensAreAdjacent = token.column === expectedNextCol;
|
|
142
|
+
const isDescendantSelector = (tokenValue === '.' || tokenValue === '#') &&
|
|
143
|
+
lastToken?.type === TokenType.IDENT &&
|
|
144
|
+
!inAtRule &&
|
|
145
|
+
!tokensAreAdjacent;
|
|
146
|
+
|
|
147
|
+
// Special case: hyphenated class/id names
|
|
148
|
+
const lastPartJoined = selectorParts.join('');
|
|
149
|
+
const lastSegmentMatch = lastPartJoined.match(/[.#]([a-zA-Z0-9_-]*)$/);
|
|
150
|
+
const inClassName = lastSegmentMatch && lastSegmentMatch[1].length > 0;
|
|
151
|
+
|
|
152
|
+
const isHyphenatedIdent = (tokenValue === '-' && (lastToken?.type === TokenType.IDENT || lastToken?.type === TokenType.NUMBER)) ||
|
|
153
|
+
(lastToken?.type === TokenType.MINUS) ||
|
|
154
|
+
(inClassName && lastToken?.type === TokenType.NUMBER && token.type === TokenType.IDENT);
|
|
155
|
+
|
|
156
|
+
// Special case: CSS units after numbers (768px, 1.5em)
|
|
157
|
+
const isUnitAfterNumber = cssUnits.has(tokenValue) && lastToken?.type === TokenType.NUMBER;
|
|
158
|
+
|
|
159
|
+
// Special case: @-rule keywords (media, keyframes, etc.) should attach to @
|
|
160
|
+
const isAtRuleKeyword = lastPart === '@' && /^[a-zA-Z]/.test(tokenValue);
|
|
161
|
+
|
|
162
|
+
const needsSpace = !noSpaceAfter.has(lastPart) &&
|
|
163
|
+
!noSpaceBefore.has(tokenValue) &&
|
|
164
|
+
!isHyphenatedIdent &&
|
|
165
|
+
!isUnitAfterNumber &&
|
|
166
|
+
!isAtRuleKeyword ||
|
|
167
|
+
isDescendantSelector;
|
|
168
|
+
|
|
169
|
+
if (needsSpace) {
|
|
170
|
+
selectorParts.push(' ');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
selectorParts.push(tokenValue);
|
|
174
|
+
lastLine = currentLine;
|
|
175
|
+
lastToken = token;
|
|
176
|
+
}
|
|
177
|
+
const selector = selectorParts.join('').trim();
|
|
178
|
+
|
|
179
|
+
this.expect(TokenType.LBRACE);
|
|
180
|
+
|
|
181
|
+
const properties = [];
|
|
182
|
+
const nestedRules = [];
|
|
183
|
+
|
|
184
|
+
while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
185
|
+
// Check if this is a nested rule or a property
|
|
186
|
+
if (this.isNestedRule()) {
|
|
187
|
+
nestedRules.push(this.parseStyleRule());
|
|
188
|
+
} else {
|
|
189
|
+
properties.push(this.parseStyleProperty());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.expect(TokenType.RBRACE);
|
|
194
|
+
return new ASTNode(NodeType.StyleRule, { selector, properties, nestedRules });
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if current position is a nested rule
|
|
199
|
+
*/
|
|
200
|
+
Parser.prototype.isNestedRule = function() {
|
|
201
|
+
const currentToken = this.peek(0);
|
|
202
|
+
if (!currentToken) return false;
|
|
203
|
+
|
|
204
|
+
// & is always a CSS parent selector, never a property name
|
|
205
|
+
if (currentToken.type === TokenType.AMPERSAND) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const startLine = currentToken.line;
|
|
210
|
+
let i = 0;
|
|
211
|
+
|
|
212
|
+
while (this.peek(i) && this.peek(i).type !== TokenType.EOF) {
|
|
213
|
+
const token = this.peek(i);
|
|
214
|
+
|
|
215
|
+
// Found { before : - this is a nested rule
|
|
216
|
+
if (token.type === TokenType.LBRACE) return true;
|
|
217
|
+
|
|
218
|
+
// Found : - this is a property, not a nested rule
|
|
219
|
+
if (token.type === TokenType.COLON) return false;
|
|
220
|
+
|
|
221
|
+
// Found } - end of current rule
|
|
222
|
+
if (token.type === TokenType.RBRACE) return false;
|
|
223
|
+
|
|
224
|
+
if (token.line > startLine && i > 0) {
|
|
225
|
+
const nextLine = token.line;
|
|
226
|
+
let j = i;
|
|
227
|
+
while (this.peek(j) && this.peek(j).line === nextLine) {
|
|
228
|
+
const t = this.peek(j);
|
|
229
|
+
if (t.type === TokenType.LBRACE) return true;
|
|
230
|
+
if (t.type === TokenType.COLON) return false;
|
|
231
|
+
if (t.type === TokenType.RBRACE) return false;
|
|
232
|
+
j++;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
i++;
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Parse style property
|
|
244
|
+
*/
|
|
245
|
+
Parser.prototype.parseStyleProperty = function() {
|
|
246
|
+
// Parse property name (including custom properties with --)
|
|
247
|
+
let name = '';
|
|
248
|
+
let nameTokens = [];
|
|
249
|
+
while (!this.is(TokenType.COLON) && !this.is(TokenType.EOF)) {
|
|
250
|
+
nameTokens.push(this.advance());
|
|
251
|
+
}
|
|
252
|
+
// Join name tokens without spaces (property names don't have spaces)
|
|
253
|
+
name = nameTokens.map(t => t.value).join('').trim();
|
|
254
|
+
|
|
255
|
+
this.expect(TokenType.COLON);
|
|
256
|
+
|
|
257
|
+
// CSS functions that should not have space before (
|
|
258
|
+
const cssFunctions = new Set([
|
|
259
|
+
'rgba', 'rgb', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklch', 'oklab',
|
|
260
|
+
'var', 'calc', 'min', 'max', 'clamp', 'url', 'attr', 'env', 'counter', 'counters',
|
|
261
|
+
'linear-gradient', 'radial-gradient', 'conic-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient',
|
|
262
|
+
'translate', 'translateX', 'translateY', 'translateZ', 'translate3d',
|
|
263
|
+
'rotate', 'rotateX', 'rotateY', 'rotateZ', 'rotate3d',
|
|
264
|
+
'scale', 'scaleX', 'scaleY', 'scaleZ', 'scale3d',
|
|
265
|
+
'skew', 'skewX', 'skewY', 'matrix', 'matrix3d', 'perspective',
|
|
266
|
+
'cubic-bezier', 'steps', 'drop-shadow', 'blur', 'brightness', 'contrast',
|
|
267
|
+
'grayscale', 'hue-rotate', 'invert', 'opacity', 'saturate', 'sepia',
|
|
268
|
+
'minmax', 'repeat', 'fit-content', 'image', 'element', 'cross-fade',
|
|
269
|
+
'color-mix', 'light-dark'
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
// CSS units that should attach to preceding number (no space before)
|
|
273
|
+
const cssUnits = new Set([
|
|
274
|
+
'%', 'px', 'em', 'rem', 'vh', 'vw', 'vmin', 'vmax', 'dvh', 'dvw', 'svh', 'svw', 'lvh', 'lvw',
|
|
275
|
+
'fr', 's', 'ms', 'deg', 'rad', 'turn', 'grad',
|
|
276
|
+
'ex', 'ch', 'cap', 'ic', 'lh', 'rlh',
|
|
277
|
+
'pt', 'pc', 'in', 'cm', 'mm', 'Q',
|
|
278
|
+
'dpi', 'dpcm', 'dppx', 'x'
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
// Tokens that should not have space before them
|
|
282
|
+
const noSpaceBefore = new Set([')', ',', '(', ';']);
|
|
283
|
+
cssUnits.forEach(u => noSpaceBefore.add(u));
|
|
284
|
+
|
|
285
|
+
// Collect value tokens
|
|
286
|
+
let valueTokens = [];
|
|
287
|
+
let lastTokenLine = this.current()?.line || 0;
|
|
288
|
+
|
|
289
|
+
while (!this.is(TokenType.SEMICOLON) && !this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
|
|
290
|
+
const currentToken = this.current();
|
|
291
|
+
|
|
292
|
+
// Check if we're on a new line - if so, check for property start or nested rule
|
|
293
|
+
if (currentToken && currentToken.line > lastTokenLine) {
|
|
294
|
+
if (this.isPropertyStart() || this.isNestedRule()) {
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
lastTokenLine = currentToken.line;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
valueTokens.push(this.advance());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Build value string with proper spacing
|
|
304
|
+
let value = '';
|
|
305
|
+
let inHexColor = false;
|
|
306
|
+
let hexLength = 0;
|
|
307
|
+
let parenDepth = 0;
|
|
308
|
+
let inCssVar = false;
|
|
309
|
+
let inCalc = false;
|
|
310
|
+
let calcDepth = 0;
|
|
311
|
+
|
|
312
|
+
const mathFunctions = new Set(['calc', 'min', 'max', 'clamp']);
|
|
313
|
+
const isValidHex = (str) => /^[0-9a-fA-F]+$/.test(String(str));
|
|
314
|
+
|
|
315
|
+
for (let i = 0; i < valueTokens.length; i++) {
|
|
316
|
+
const token = valueTokens[i];
|
|
317
|
+
const tokenValue = token.raw || String(token.value);
|
|
318
|
+
const prevToken = i > 0 ? valueTokens[i - 1] : null;
|
|
319
|
+
const prevValue = prevToken ? (prevToken.raw || String(prevToken.value)) : '';
|
|
320
|
+
|
|
321
|
+
// Track parenthesis depth
|
|
322
|
+
if (tokenValue === '(') parenDepth++;
|
|
323
|
+
if (tokenValue === ')') parenDepth--;
|
|
324
|
+
|
|
325
|
+
// Track CSS var() context
|
|
326
|
+
if (prevValue === 'var' && tokenValue === '(') {
|
|
327
|
+
inCssVar = true;
|
|
328
|
+
} else if (inCssVar && tokenValue === ')') {
|
|
329
|
+
inCssVar = false;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Track calc/min/max/clamp context
|
|
333
|
+
if (mathFunctions.has(prevValue) && tokenValue === '(') {
|
|
334
|
+
inCalc = true;
|
|
335
|
+
calcDepth = parenDepth;
|
|
336
|
+
} else if (inCalc && tokenValue === ')' && parenDepth < calcDepth) {
|
|
337
|
+
inCalc = false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Handle HEX_COLOR token (from lexer)
|
|
341
|
+
if (token.type === TokenType.HEX_COLOR) {
|
|
342
|
+
inHexColor = false;
|
|
343
|
+
}
|
|
344
|
+
else if (tokenValue === '#') {
|
|
345
|
+
inHexColor = true;
|
|
346
|
+
hexLength = 0;
|
|
347
|
+
} else if (inHexColor) {
|
|
348
|
+
const tokenStr = String(tokenValue);
|
|
349
|
+
if (isValidHex(tokenStr) && hexLength + tokenStr.length <= 8) {
|
|
350
|
+
hexLength += tokenStr.length;
|
|
351
|
+
|
|
352
|
+
const nextToken = valueTokens[i + 1];
|
|
353
|
+
const nextValue = nextToken ? String(nextToken.raw || nextToken.value) : '';
|
|
354
|
+
const cssValueIndicators = new Set(['%', 'px', 'em', 'rem', 'vh', 'vw', ',', ')', ' ', '']);
|
|
355
|
+
|
|
356
|
+
if (hexLength >= 6 || cssValueIndicators.has(nextValue) || nextToken?.type === TokenType.PERCENT || nextToken?.type === TokenType.COMMA || nextToken?.type === TokenType.RPAREN) {
|
|
357
|
+
inHexColor = false;
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
inHexColor = false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Determine if we need space before this token
|
|
365
|
+
let needsSpace = value.length > 0;
|
|
366
|
+
|
|
367
|
+
if (needsSpace) {
|
|
368
|
+
if (prevValue === '#') {
|
|
369
|
+
needsSpace = false;
|
|
370
|
+
}
|
|
371
|
+
else if (prevValue === '(' || prevValue === '.' || prevValue === '/' || prevValue === '@') {
|
|
372
|
+
needsSpace = false;
|
|
373
|
+
}
|
|
374
|
+
else if (prevValue === '!' && tokenValue === 'important') {
|
|
375
|
+
needsSpace = false;
|
|
376
|
+
}
|
|
377
|
+
else if (cssFunctions.has(prevValue) && tokenValue === '(') {
|
|
378
|
+
needsSpace = false;
|
|
379
|
+
}
|
|
380
|
+
else if (noSpaceBefore.has(tokenValue)) {
|
|
381
|
+
needsSpace = false;
|
|
382
|
+
}
|
|
383
|
+
else if (inHexColor && hexLength > 0) {
|
|
384
|
+
needsSpace = false;
|
|
385
|
+
}
|
|
386
|
+
else if (inCssVar) {
|
|
387
|
+
needsSpace = false;
|
|
388
|
+
}
|
|
389
|
+
else if (tokenValue === '-' && !inCalc && (prevToken?.type === TokenType.IDENT || prevToken?.type === TokenType.NUMBER || /^[a-zA-Z]/.test(prevValue))) {
|
|
390
|
+
needsSpace = false;
|
|
391
|
+
}
|
|
392
|
+
else if (!inCalc && (prevValue === '-' || value.endsWith('-')) && (token.type === TokenType.IDENT || token.type === TokenType.NUMBER || /^[a-zA-Z]/.test(tokenValue))) {
|
|
393
|
+
needsSpace = false;
|
|
394
|
+
}
|
|
395
|
+
else if (prevValue === '-' && tokenValue === '-') {
|
|
396
|
+
needsSpace = false;
|
|
397
|
+
}
|
|
398
|
+
else if (prevValue === '--' || value.endsWith('--')) {
|
|
399
|
+
needsSpace = false;
|
|
400
|
+
}
|
|
401
|
+
else if (cssUnits.has(tokenValue) && prevToken?.type === TokenType.NUMBER) {
|
|
402
|
+
needsSpace = false;
|
|
403
|
+
}
|
|
404
|
+
else if (token.type === TokenType.IDENT && prevToken?.type === TokenType.NUMBER) {
|
|
405
|
+
const hyphenNumberPattern = /-\d+$/;
|
|
406
|
+
if (hyphenNumberPattern.test(value)) {
|
|
407
|
+
needsSpace = false;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (needsSpace) {
|
|
413
|
+
value += ' ';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
value += tokenValue;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
value = value.trim();
|
|
420
|
+
|
|
421
|
+
if (this.is(TokenType.SEMICOLON)) {
|
|
422
|
+
this.advance();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return new ASTNode(NodeType.StyleProperty, { name, value });
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Check if current position starts a new property
|
|
430
|
+
*/
|
|
431
|
+
Parser.prototype.isPropertyStart = function() {
|
|
432
|
+
if (!this.is(TokenType.IDENT) && !this.is(TokenType.MINUS) && !this.is(TokenType.MINUSMINUS)) return false;
|
|
433
|
+
|
|
434
|
+
let i = 0;
|
|
435
|
+
while (this.peek(i)) {
|
|
436
|
+
const token = this.peek(i);
|
|
437
|
+
if (token.type === TokenType.IDENT || token.type === TokenType.MINUS || token.type === TokenType.MINUSMINUS) {
|
|
438
|
+
i++;
|
|
439
|
+
} else {
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return this.peek(i)?.type === TokenType.COLON;
|
|
445
|
+
};
|