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.
Files changed (37) hide show
  1. package/compiler/parser/_extract.js +393 -0
  2. package/compiler/parser/blocks.js +361 -0
  3. package/compiler/parser/core.js +306 -0
  4. package/compiler/parser/expressions.js +386 -0
  5. package/compiler/parser/imports.js +108 -0
  6. package/compiler/parser/index.js +47 -0
  7. package/compiler/parser/state.js +155 -0
  8. package/compiler/parser/style.js +445 -0
  9. package/compiler/parser/view.js +632 -0
  10. package/compiler/parser.js +15 -2372
  11. package/compiler/parser.js.original +2376 -0
  12. package/package.json +2 -1
  13. package/runtime/a11y/announcements.js +213 -0
  14. package/runtime/a11y/contrast.js +125 -0
  15. package/runtime/a11y/focus.js +412 -0
  16. package/runtime/a11y/index.js +35 -0
  17. package/runtime/a11y/preferences.js +121 -0
  18. package/runtime/a11y/utils.js +164 -0
  19. package/runtime/a11y/validation.js +258 -0
  20. package/runtime/a11y/widgets.js +545 -0
  21. package/runtime/a11y.js +15 -1840
  22. package/runtime/a11y.js.original +1844 -0
  23. package/runtime/graphql/cache.js +69 -0
  24. package/runtime/graphql/client.js +563 -0
  25. package/runtime/graphql/hooks.js +492 -0
  26. package/runtime/graphql/index.js +62 -0
  27. package/runtime/graphql/subscriptions.js +241 -0
  28. package/runtime/graphql.js +12 -1322
  29. package/runtime/graphql.js.original +1326 -0
  30. package/runtime/router/core.js +956 -0
  31. package/runtime/router/guards.js +90 -0
  32. package/runtime/router/history.js +204 -0
  33. package/runtime/router/index.js +36 -0
  34. package/runtime/router/lazy.js +180 -0
  35. package/runtime/router/utils.js +226 -0
  36. package/runtime/router.js +12 -1600
  37. 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
+ };