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,632 @@
1
+ /**
2
+ * Pulse Parser - View Block Parsing
3
+ *
4
+ * Handles parsing of view blocks including elements, directives, and components
5
+ *
6
+ * @module compiler/parser/view
7
+ */
8
+
9
+ import { TokenType } from '../lexer.js';
10
+ import { NodeType, ASTNode, Parser } from './core.js';
11
+ import { SUGGESTIONS } from '../../runtime/errors.js';
12
+
13
+ // ============================================================
14
+ // View Block Parsing
15
+ // ============================================================
16
+
17
+ Parser.prototype.parseViewBlock = function() {
18
+ this.expect(TokenType.VIEW);
19
+ this.expect(TokenType.LBRACE);
20
+
21
+ const children = [];
22
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
23
+ children.push(this.parseViewChild());
24
+ }
25
+
26
+ this.expect(TokenType.RBRACE);
27
+ return new ASTNode(NodeType.ViewBlock, { children });
28
+ }
29
+
30
+ /**
31
+ * Parse a view child (element, directive, slot, or text)
32
+ */
33
+ Parser.prototype.parseViewChild = function() {
34
+ if (this.is(TokenType.AT)) {
35
+ return this.parseDirective();
36
+ }
37
+ // Slot element
38
+ if (this.is(TokenType.SLOT)) {
39
+ return this.parseSlotElement();
40
+ }
41
+ if (this.is(TokenType.SELECTOR) || this.is(TokenType.IDENT)) {
42
+ return this.parseElement();
43
+ }
44
+ if (this.is(TokenType.STRING)) {
45
+ return this.parseTextNode();
46
+ }
47
+
48
+ const token = this.current();
49
+ throw this.createError(
50
+ `Unexpected token '${token?.value || token?.type}' in view block. ` +
51
+ `Expected: element selector, @directive, slot, or "text"`
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Parse slot element for component composition
57
+ * Supports:
58
+ * slot - default slot
59
+ * slot "name" - named slot
60
+ * slot { default content }
61
+ */
62
+ Parser.prototype.parseSlotElement = function() {
63
+ const startToken = this.expect(TokenType.SLOT);
64
+ let name = 'default';
65
+ const fallback = [];
66
+
67
+ // Named slot: slot "header"
68
+ if (this.is(TokenType.STRING)) {
69
+ name = this.advance().value;
70
+ }
71
+
72
+ // Fallback content: slot { ... }
73
+ if (this.is(TokenType.LBRACE)) {
74
+ this.advance();
75
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
76
+ fallback.push(this.parseViewChild());
77
+ }
78
+ this.expect(TokenType.RBRACE);
79
+ }
80
+
81
+ return new ASTNode(NodeType.SlotElement, {
82
+ name,
83
+ fallback,
84
+ line: startToken.line,
85
+ column: startToken.column
86
+ });
87
+ }
88
+
89
+ /**
90
+ * Parse an element
91
+ */
92
+ Parser.prototype.parseElement = function() {
93
+ const selector = this.isAny(TokenType.SELECTOR, TokenType.IDENT)
94
+ ? this.advance().value
95
+ : '';
96
+
97
+ const directives = [];
98
+ const textContent = [];
99
+ const children = [];
100
+ const props = []; // Props passed to component
101
+
102
+ // Check if this is a component with props: Component(prop=value, ...)
103
+ if (this.is(TokenType.LPAREN)) {
104
+ this.advance(); // consume (
105
+ while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
106
+ props.push(this.parseComponentProp());
107
+ if (this.is(TokenType.COMMA)) {
108
+ this.advance();
109
+ }
110
+ }
111
+ this.expect(TokenType.RPAREN);
112
+ }
113
+
114
+ // Parse inline directives and text
115
+ while (!this.is(TokenType.LBRACE) && !this.is(TokenType.RBRACE) &&
116
+ !this.is(TokenType.SELECTOR) && !this.is(TokenType.EOF)) {
117
+ if (this.is(TokenType.AT)) {
118
+ // Check if this is a block directive (@if, @for, @each) - if so, break
119
+ const nextToken = this.peek();
120
+ if (nextToken && (nextToken.type === TokenType.IF ||
121
+ nextToken.type === TokenType.FOR ||
122
+ nextToken.type === TokenType.EACH)) {
123
+ break;
124
+ }
125
+ directives.push(this.parseInlineDirective());
126
+ } else if (this.is(TokenType.STRING)) {
127
+ textContent.push(this.parseTextNode());
128
+ } else if (this.is(TokenType.IDENT) && !this.couldBeElement()) {
129
+ break;
130
+ } else {
131
+ break;
132
+ }
133
+ }
134
+
135
+ // Parse children if there's a block
136
+ if (this.is(TokenType.LBRACE)) {
137
+ this.advance();
138
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
139
+ children.push(this.parseViewChild());
140
+ }
141
+ this.expect(TokenType.RBRACE);
142
+ }
143
+
144
+ return new ASTNode(NodeType.Element, {
145
+ selector,
146
+ directives,
147
+ textContent,
148
+ children,
149
+ props
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Parse a component prop: name=value or name={expression}
155
+ */
156
+ Parser.prototype.parseComponentProp = function() {
157
+ const name = this.expect(TokenType.IDENT);
158
+ this.expect(TokenType.EQ);
159
+
160
+ let value;
161
+ if (this.is(TokenType.LBRACE)) {
162
+ this.advance();
163
+ value = this.parseExpression();
164
+ this.expect(TokenType.RBRACE);
165
+ } else {
166
+ value = this.tryParseLiteral();
167
+ if (!value) {
168
+ if (this.is(TokenType.IDENT)) {
169
+ value = this.parseIdentifierOrExpression();
170
+ } else {
171
+ throw this.createError(`Unexpected token in prop value: ${this.current()?.type}`);
172
+ }
173
+ }
174
+ }
175
+
176
+ return new ASTNode(NodeType.Property, { name: name.value, value });
177
+ }
178
+
179
+ /**
180
+ * Check if current position could be an element
181
+ */
182
+ Parser.prototype.couldBeElement = function() {
183
+ const next = this.peek();
184
+ return next?.type === TokenType.LBRACE ||
185
+ next?.type === TokenType.AT ||
186
+ next?.type === TokenType.STRING;
187
+ };
188
+
189
+ /**
190
+ * Parse a text node
191
+ */
192
+ Parser.prototype.parseTextNode = function() {
193
+ const token = this.expect(TokenType.STRING);
194
+ const parts = this.parseInterpolatedString(token.value);
195
+ return new ASTNode(NodeType.TextNode, { parts });
196
+ }
197
+
198
+ /**
199
+ * Parse interpolated string into parts
200
+ * "Hello, {name}!" -> ["Hello, ", { expr: "name" }, "!"]
201
+ */
202
+ Parser.prototype.parseInterpolatedString = function(str) {
203
+ const parts = [];
204
+ let current = '';
205
+ let i = 0;
206
+
207
+ while (i < str.length) {
208
+ if (str[i] === '{') {
209
+ if (current) {
210
+ parts.push(current);
211
+ current = '';
212
+ }
213
+ i++; // skip {
214
+ let expr = '';
215
+ let braceCount = 1;
216
+ while (i < str.length && braceCount > 0) {
217
+ if (str[i] === '{') braceCount++;
218
+ else if (str[i] === '}') braceCount--;
219
+ if (braceCount > 0) expr += str[i];
220
+ i++;
221
+ }
222
+ parts.push(new ASTNode(NodeType.Interpolation, { expression: expr.trim() }));
223
+ } else {
224
+ current += str[i];
225
+ i++;
226
+ }
227
+ }
228
+
229
+ if (current) {
230
+ parts.push(current);
231
+ }
232
+
233
+ return parts;
234
+ }
235
+
236
+ /**
237
+ * Parse a directive (@if, @for, @each, @click, @link, @outlet, @navigate, etc.)
238
+ */
239
+ Parser.prototype.parseDirective = function() {
240
+ this.expect(TokenType.AT);
241
+
242
+ // Handle @if - IF is a keyword token, not IDENT
243
+ if (this.is(TokenType.IF)) {
244
+ this.advance();
245
+ return this.parseIfDirective();
246
+ }
247
+
248
+ // Handle @for - FOR is a keyword token, not IDENT
249
+ if (this.is(TokenType.FOR)) {
250
+ this.advance();
251
+ return this.parseEachDirective();
252
+ }
253
+
254
+ // Handle router directives
255
+ if (this.is(TokenType.LINK)) {
256
+ this.advance();
257
+ return this.parseLinkDirective();
258
+ }
259
+ if (this.is(TokenType.OUTLET)) {
260
+ this.advance();
261
+ return this.parseOutletDirective();
262
+ }
263
+ if (this.is(TokenType.NAVIGATE)) {
264
+ this.advance();
265
+ return this.parseNavigateDirective();
266
+ }
267
+ if (this.is(TokenType.BACK)) {
268
+ this.advance();
269
+ return new ASTNode(NodeType.NavigateDirective, { action: 'back' });
270
+ }
271
+ if (this.is(TokenType.FORWARD)) {
272
+ this.advance();
273
+ return new ASTNode(NodeType.NavigateDirective, { action: 'forward' });
274
+ }
275
+
276
+ const name = this.expect(TokenType.IDENT).value;
277
+
278
+ // Collect modifiers (.prevent, .stop, .enter, .lazy, etc.)
279
+ const modifiers = [];
280
+ while (this.is(TokenType.DIRECTIVE_MOD)) {
281
+ modifiers.push(this.advance().value);
282
+ }
283
+
284
+ if (name === 'if') {
285
+ return this.parseIfDirective();
286
+ }
287
+ if (name === 'each' || name === 'for') {
288
+ return this.parseEachDirective();
289
+ }
290
+
291
+ // Accessibility directives
292
+ if (name === 'a11y') {
293
+ return this.parseA11yDirective();
294
+ }
295
+ if (name === 'live') {
296
+ return this.parseLiveDirective();
297
+ }
298
+ if (name === 'focusTrap') {
299
+ return this.parseFocusTrapDirective();
300
+ }
301
+ if (name === 'srOnly') {
302
+ return this.parseSrOnlyDirective();
303
+ }
304
+
305
+ // SSR directives
306
+ if (name === 'client') {
307
+ return new ASTNode(NodeType.ClientDirective, {});
308
+ }
309
+ if (name === 'server') {
310
+ return new ASTNode(NodeType.ServerDirective, {});
311
+ }
312
+
313
+ // @model directive for two-way binding
314
+ if (name === 'model') {
315
+ return this.parseModelDirective(modifiers);
316
+ }
317
+
318
+ // Event directive like @click
319
+ return this.parseEventDirective(name, modifiers);
320
+ }
321
+
322
+ /**
323
+ * Parse inline directive
324
+ */
325
+ Parser.prototype.parseInlineDirective = function() {
326
+ this.expect(TokenType.AT);
327
+ const name = this.expect(TokenType.IDENT).value;
328
+
329
+ // Collect modifiers (.prevent, .stop, .enter, .lazy, etc.)
330
+ const modifiers = [];
331
+ while (this.is(TokenType.DIRECTIVE_MOD)) {
332
+ modifiers.push(this.advance().value);
333
+ }
334
+
335
+ // Check for a11y directives
336
+ if (name === 'a11y') {
337
+ return this.parseA11yDirective();
338
+ }
339
+ if (name === 'live') {
340
+ return this.parseLiveDirective();
341
+ }
342
+ if (name === 'focusTrap') {
343
+ return this.parseFocusTrapDirective();
344
+ }
345
+ if (name === 'srOnly') {
346
+ return this.parseSrOnlyDirective();
347
+ }
348
+
349
+ // SSR directives
350
+ if (name === 'client') {
351
+ return new ASTNode(NodeType.ClientDirective, {});
352
+ }
353
+ if (name === 'server') {
354
+ return new ASTNode(NodeType.ServerDirective, {});
355
+ }
356
+
357
+ // @model directive for two-way binding
358
+ if (name === 'model') {
359
+ return this.parseModelDirective(modifiers);
360
+ }
361
+
362
+ // Event directive (click, submit, etc.)
363
+ this.expect(TokenType.LPAREN);
364
+ const expression = this.parseExpression();
365
+ this.expect(TokenType.RPAREN);
366
+
367
+ return new ASTNode(NodeType.EventDirective, { event: name, handler: expression, modifiers });
368
+ }
369
+
370
+ /**
371
+ * Parse @if directive with @else-if/@else chains
372
+ * Syntax: @if (cond) { } @else-if (cond) { } @else { }
373
+ */
374
+ Parser.prototype.parseIfDirective = function() {
375
+ this.expect(TokenType.LPAREN);
376
+ const condition = this.parseExpression();
377
+ this.expect(TokenType.RPAREN);
378
+
379
+ this.expect(TokenType.LBRACE);
380
+ const consequent = [];
381
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
382
+ consequent.push(this.parseViewChild());
383
+ }
384
+ this.expect(TokenType.RBRACE);
385
+
386
+ const elseIfBranches = [];
387
+ let alternate = null;
388
+
389
+ // Parse @else-if and @else chains
390
+ while (this.is(TokenType.AT)) {
391
+ const nextToken = this.peek();
392
+
393
+ // Check for @else or @else-if
394
+ if (nextToken?.value === 'else') {
395
+ this.advance(); // @
396
+ this.advance(); // else
397
+
398
+ // Check if followed by @if or -if (making @else @if or @else-if)
399
+ if (this.is(TokenType.AT) && (this.peek()?.type === TokenType.IF || this.peek()?.value === 'if')) {
400
+ // @else @if pattern
401
+ this.advance(); // @
402
+ this.advance(); // if
403
+
404
+ this.expect(TokenType.LPAREN);
405
+ const elseIfCondition = this.parseExpression();
406
+ this.expect(TokenType.RPAREN);
407
+
408
+ this.expect(TokenType.LBRACE);
409
+ const elseIfConsequent = [];
410
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
411
+ elseIfConsequent.push(this.parseViewChild());
412
+ }
413
+ this.expect(TokenType.RBRACE);
414
+
415
+ elseIfBranches.push({ condition: elseIfCondition, consequent: elseIfConsequent });
416
+ }
417
+ // Check for -if pattern (@else-if as hyphenated)
418
+ else if (this.is(TokenType.MINUS) && (this.peek()?.type === TokenType.IF || this.peek()?.value === 'if')) {
419
+ this.advance(); // -
420
+ this.advance(); // if
421
+
422
+ this.expect(TokenType.LPAREN);
423
+ const elseIfCondition = this.parseExpression();
424
+ this.expect(TokenType.RPAREN);
425
+
426
+ this.expect(TokenType.LBRACE);
427
+ const elseIfConsequent = [];
428
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
429
+ elseIfConsequent.push(this.parseViewChild());
430
+ }
431
+ this.expect(TokenType.RBRACE);
432
+
433
+ elseIfBranches.push({ condition: elseIfCondition, consequent: elseIfConsequent });
434
+ }
435
+ // Plain @else
436
+ else {
437
+ this.expect(TokenType.LBRACE);
438
+ alternate = [];
439
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
440
+ alternate.push(this.parseViewChild());
441
+ }
442
+ this.expect(TokenType.RBRACE);
443
+ break; // @else terminates the chain
444
+ }
445
+ } else {
446
+ break; // Not an @else variant
447
+ }
448
+ }
449
+
450
+ return new ASTNode(NodeType.IfDirective, { condition, consequent, elseIfBranches, alternate });
451
+ }
452
+
453
+ /**
454
+ * Parse @each/@for directive with optional key function
455
+ * Syntax: @for (item of items) key(item.id) { ... }
456
+ */
457
+ Parser.prototype.parseEachDirective = function() {
458
+ this.expect(TokenType.LPAREN);
459
+ const itemName = this.expect(TokenType.IDENT).value;
460
+ // Accept both 'in' and 'of' keywords
461
+ if (this.is(TokenType.IN)) {
462
+ this.advance();
463
+ } else if (this.is(TokenType.OF)) {
464
+ this.advance();
465
+ } else {
466
+ throw this.createError('Expected "in" or "of" in loop directive');
467
+ }
468
+ const iterable = this.parseExpression();
469
+ this.expect(TokenType.RPAREN);
470
+
471
+ // Parse optional key function: key(item.id)
472
+ let keyExpr = null;
473
+ if (this.is(TokenType.IDENT) && this.current().value === 'key') {
474
+ this.advance(); // consume 'key'
475
+ this.expect(TokenType.LPAREN);
476
+ keyExpr = this.parseExpression();
477
+ this.expect(TokenType.RPAREN);
478
+ }
479
+
480
+ this.expect(TokenType.LBRACE);
481
+ const template = [];
482
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
483
+ template.push(this.parseViewChild());
484
+ }
485
+ this.expect(TokenType.RBRACE);
486
+
487
+ return new ASTNode(NodeType.EachDirective, { itemName, iterable, template, keyExpr });
488
+ }
489
+
490
+ /**
491
+ * Parse event directive with optional modifiers
492
+ * @param {string} event - Event name (click, keydown, etc.)
493
+ * @param {string[]} modifiers - Array of modifier names (prevent, stop, enter, etc.)
494
+ */
495
+ Parser.prototype.parseEventDirective = function(event, modifiers = []) {
496
+ this.expect(TokenType.LPAREN);
497
+ const handler = this.parseExpression();
498
+ this.expect(TokenType.RPAREN);
499
+
500
+ const children = [];
501
+ if (this.is(TokenType.LBRACE)) {
502
+ this.advance();
503
+ while (!this.is(TokenType.RBRACE) && !this.is(TokenType.EOF)) {
504
+ children.push(this.parseViewChild());
505
+ }
506
+ this.expect(TokenType.RBRACE);
507
+ }
508
+
509
+ return new ASTNode(NodeType.EventDirective, { event, handler, children, modifiers });
510
+ }
511
+
512
+ /**
513
+ * Parse @model directive for two-way binding
514
+ * @model(name) or @model.lazy(name) or @model.lazy.trim(name)
515
+ * @param {string[]} modifiers - Array of modifier names (lazy, trim, number)
516
+ */
517
+ Parser.prototype.parseModelDirective = function(modifiers = []) {
518
+ this.expect(TokenType.LPAREN);
519
+ const binding = this.parseExpression();
520
+ this.expect(TokenType.RPAREN);
521
+
522
+ return new ASTNode(NodeType.ModelDirective, { binding, modifiers });
523
+ }
524
+
525
+ /**
526
+ * Parse @a11y directive - sets aria attributes
527
+ * @a11y(label="Close menu") or @a11y(label="Close", describedby="desc")
528
+ */
529
+ Parser.prototype.parseA11yDirective = function() {
530
+ this.expect(TokenType.LPAREN);
531
+
532
+ const attrs = {};
533
+
534
+ // Parse key=value pairs
535
+ while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
536
+ const key = this.expect(TokenType.IDENT).value;
537
+ this.expect(TokenType.EQ);
538
+
539
+ let value;
540
+ if (this.is(TokenType.STRING)) {
541
+ value = this.advance().value;
542
+ } else if (this.is(TokenType.TRUE)) {
543
+ value = true;
544
+ this.advance();
545
+ } else if (this.is(TokenType.FALSE)) {
546
+ value = false;
547
+ this.advance();
548
+ } else if (this.is(TokenType.IDENT)) {
549
+ // Treat unquoted identifier as a string (e.g., role=dialog -> "dialog")
550
+ value = this.advance().value;
551
+ } else {
552
+ value = this.parseExpression();
553
+ }
554
+
555
+ attrs[key] = value;
556
+
557
+ if (this.is(TokenType.COMMA)) {
558
+ this.advance();
559
+ }
560
+ }
561
+
562
+ this.expect(TokenType.RPAREN);
563
+
564
+ return new ASTNode(NodeType.A11yDirective, { attrs });
565
+ }
566
+
567
+ /**
568
+ * Parse @live directive - creates live region for screen readers
569
+ * @live(polite) or @live(assertive)
570
+ */
571
+ Parser.prototype.parseLiveDirective = function() {
572
+ this.expect(TokenType.LPAREN);
573
+
574
+ let priority = 'polite';
575
+ if (this.is(TokenType.IDENT)) {
576
+ priority = this.advance().value;
577
+ }
578
+
579
+ this.expect(TokenType.RPAREN);
580
+
581
+ return new ASTNode(NodeType.LiveDirective, { priority });
582
+ }
583
+
584
+ /**
585
+ * Parse @focusTrap directive - traps focus within element
586
+ * @focusTrap or @focusTrap(autoFocus=true)
587
+ */
588
+ Parser.prototype.parseFocusTrapDirective = function() {
589
+ const options = {};
590
+
591
+ if (this.is(TokenType.LPAREN)) {
592
+ this.advance();
593
+
594
+ while (!this.is(TokenType.RPAREN) && !this.is(TokenType.EOF)) {
595
+ const key = this.expect(TokenType.IDENT).value;
596
+
597
+ if (this.is(TokenType.EQ)) {
598
+ this.advance();
599
+ if (this.is(TokenType.TRUE)) {
600
+ options[key] = true;
601
+ this.advance();
602
+ } else if (this.is(TokenType.FALSE)) {
603
+ options[key] = false;
604
+ this.advance();
605
+ } else if (this.is(TokenType.STRING)) {
606
+ options[key] = this.advance().value;
607
+ } else {
608
+ options[key] = this.parseExpression();
609
+ }
610
+ } else {
611
+ options[key] = true;
612
+ }
613
+
614
+ if (this.is(TokenType.COMMA)) {
615
+ this.advance();
616
+ }
617
+ }
618
+
619
+ this.expect(TokenType.RPAREN);
620
+ }
621
+
622
+ return new ASTNode(NodeType.FocusTrapDirective, { options });
623
+ }
624
+
625
+ /**
626
+ * Parse @srOnly directive - visually hidden but accessible text
627
+ */
628
+ Parser.prototype.parseSrOnlyDirective = function() {
629
+ return new ASTNode(NodeType.A11yDirective, {
630
+ attrs: { srOnly: true }
631
+ });
632
+ };