@matthesketh/utopia-compiler 0.4.0 → 0.7.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/dist/index.cjs CHANGED
@@ -32,16 +32,19 @@ __export(index_exports, {
32
32
  module.exports = __toCommonJS(index_exports);
33
33
 
34
34
  // src/parser.ts
35
+ var BLOCK_RE = /<(template|script|style|test)([\s][^>]*)?\s*>/g;
36
+ var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
35
37
  function parse(source, filename = "anonymous.utopia") {
36
38
  const descriptor = {
37
39
  template: null,
38
40
  script: null,
39
41
  style: null,
42
+ test: null,
40
43
  filename
41
44
  };
42
- const blockRe = /<(template|script|style)([\s][^>]*)?\s*>/g;
45
+ BLOCK_RE.lastIndex = 0;
43
46
  let match;
44
- while ((match = blockRe.exec(source)) !== null) {
47
+ while ((match = BLOCK_RE.exec(source)) !== null) {
45
48
  const tagName = match[1];
46
49
  const attrString = match[2] || "";
47
50
  const openTagStart = match.index;
@@ -72,11 +75,10 @@ function parse(source, filename = "anonymous.utopia") {
72
75
  );
73
76
  }
74
77
  descriptor[tagName] = block;
75
- blockRe.lastIndex = blockEnd;
78
+ BLOCK_RE.lastIndex = blockEnd;
76
79
  }
77
80
  return descriptor;
78
81
  }
79
- var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
80
82
  function parseAttributes(raw) {
81
83
  const attrs = {};
82
84
  let m;
@@ -113,6 +115,16 @@ var SFCParseError = class extends Error {
113
115
  };
114
116
 
115
117
  // src/template-compiler.ts
118
+ var TAG_NAME_CHAR_RE = /[a-zA-Z0-9\-_]/;
119
+ var ATTR_NAME_CHAR_RE = /[a-zA-Z0-9\-_:@.]/;
120
+ var ATTR_VALUE_END_RE = /[\s/>]/;
121
+ var WHITESPACE_RE = /\s/;
122
+ var TAG_START_CHAR_RE = /[a-zA-Z]/;
123
+ var U_FOR_EXPR_RE = /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/;
124
+ var COMPONENT_TAG_RE = /^[A-Z][a-zA-Z0-9_$]*$/;
125
+ var BACKSLASH_RE = /\\/g;
126
+ var SINGLE_QUOTE_RE = /'/g;
127
+ var HTML_ENTITY_RE = /&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g;
116
128
  function compileTemplate(template, options = {}) {
117
129
  const ast = parseTemplate(template);
118
130
  return generate(ast, options);
@@ -294,7 +306,7 @@ var TemplateParser = class {
294
306
  // ---- Low-level helpers --------------------------------------------------
295
307
  readTagName() {
296
308
  const start = this.pos;
297
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_]/.test(this.source[this.pos])) {
309
+ while (this.pos < this.source.length && TAG_NAME_CHAR_RE.test(this.source[this.pos])) {
298
310
  this.pos++;
299
311
  }
300
312
  const name = this.source.slice(start, this.pos);
@@ -303,7 +315,7 @@ var TemplateParser = class {
303
315
  }
304
316
  readAttributeName() {
305
317
  const start = this.pos;
306
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_:@.]/.test(this.source[this.pos])) {
318
+ while (this.pos < this.source.length && ATTR_NAME_CHAR_RE.test(this.source[this.pos])) {
307
319
  this.pos++;
308
320
  }
309
321
  return this.source.slice(start, this.pos);
@@ -320,13 +332,13 @@ var TemplateParser = class {
320
332
  return value;
321
333
  }
322
334
  const start = this.pos;
323
- while (this.pos < this.source.length && !/[\s/>]/.test(this.source[this.pos])) {
335
+ while (this.pos < this.source.length && !ATTR_VALUE_END_RE.test(this.source[this.pos])) {
324
336
  this.pos++;
325
337
  }
326
338
  return this.source.slice(start, this.pos);
327
339
  }
328
340
  skipWhitespace() {
329
- while (this.pos < this.source.length && /\s/.test(this.source[this.pos])) {
341
+ while (this.pos < this.source.length && WHITESPACE_RE.test(this.source[this.pos])) {
330
342
  this.pos++;
331
343
  }
332
344
  }
@@ -336,7 +348,7 @@ var TemplateParser = class {
336
348
  /** Returns true when the char after `<` looks like the start of a tag name. */
337
349
  peekTagStart() {
338
350
  const next = this.source[this.pos + 1];
339
- return next !== void 0 && /[a-zA-Z]/.test(next);
351
+ return next !== void 0 && TAG_START_CHAR_RE.test(next);
340
352
  }
341
353
  expect(str) {
342
354
  if (!this.lookingAt(str)) {
@@ -406,7 +418,7 @@ function classifyDirective(name, value) {
406
418
  return null;
407
419
  }
408
420
  function isDirectiveKind(s) {
409
- return s === "on" || s === "bind" || s === "if" || s === "else" || s === "else-if" || s === "for" || s === "model";
421
+ return s === "on" || s === "bind" || s === "if" || s === "else" || s === "else-if" || s === "for" || s === "model" || s === "transition";
410
422
  }
411
423
  var CodeGenerator = class {
412
424
  constructor(options) {
@@ -535,6 +547,9 @@ ${fnBody}
535
547
  case "model":
536
548
  this.genModel(elVar, dir, scope);
537
549
  break;
550
+ case "transition":
551
+ this.genTransition(elVar, dir);
552
+ break;
538
553
  }
539
554
  }
540
555
  genOn(elVar, dir, scope) {
@@ -583,6 +598,19 @@ ${fnBody}
583
598
  this.emit(`createEffect(() => setAttr(${elVar}, 'value', ${signalRef}()))`);
584
599
  this.emit(`addEventListener(${elVar}, 'input', (e) => ${signalRef}.set(e.target.value))`);
585
600
  }
601
+ // ---- u-transition ---------------------------------------------------------
602
+ genTransition(elVar, dir) {
603
+ this.helpers.add("createTransition");
604
+ const name = dir.arg || dir.expression || "fade";
605
+ let durationOpt = "";
606
+ for (const mod of dir.modifiers) {
607
+ if (mod.startsWith("duration-")) {
608
+ const ms = mod.slice("duration-".length);
609
+ durationOpt = `, duration: ${ms}`;
610
+ }
611
+ }
612
+ this.emit(`createTransition(${elVar}, { name: '${escapeStr(name)}'${durationOpt} })`);
613
+ }
586
614
  // ---- Structural: u-if ---------------------------------------------------
587
615
  genIf(node, dir, scope, elseIfChain, elseNode) {
588
616
  this.helpers.add("createIf");
@@ -657,9 +685,7 @@ ${fnBody}
657
685
  const anchorVar = this.freshVar();
658
686
  this.helpers.add("createComment");
659
687
  this.emit(`const ${anchorVar} = createComment('u-for')`);
660
- const forMatch = dir.expression.match(
661
- /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/
662
- );
688
+ const forMatch = dir.expression.match(U_FOR_EXPR_RE);
663
689
  if (!forMatch) {
664
690
  throw new Error(`Invalid u-for expression: "${dir.expression}"`);
665
691
  }
@@ -862,10 +888,10 @@ ${fnBody}
862
888
  }
863
889
  };
864
890
  function isComponentTag(tag) {
865
- return /^[A-Z][a-zA-Z0-9_$]*$/.test(tag);
891
+ return COMPONENT_TAG_RE.test(tag);
866
892
  }
867
893
  function escapeStr(s) {
868
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
894
+ return s.replace(BACKSLASH_RE, "\\\\").replace(SINGLE_QUOTE_RE, "\\'");
869
895
  }
870
896
  var ENTITY_MAP = {
871
897
  "&amp;": "&",
@@ -894,7 +920,8 @@ var ENTITY_MAP = {
894
920
  "&divide;": "\xF7"
895
921
  };
896
922
  function decodeEntities(text) {
897
- return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g, (match, dec, hex, named) => {
923
+ HTML_ENTITY_RE.lastIndex = 0;
924
+ return text.replace(HTML_ENTITY_RE, (match, dec, hex, named) => {
898
925
  if (dec) {
899
926
  const code = parseInt(dec, 10);
900
927
  if (code >= 0 && code <= 1114111) {
@@ -927,6 +954,9 @@ function generate(ast, options) {
927
954
  }
928
955
 
929
956
  // src/style-compiler.ts
957
+ var WHITESPACE_RE2 = /\s/;
958
+ var KEYFRAMES_RE = /^@(?:-\w+-)?keyframes\b/;
959
+ var PSEUDO_SELECTOR_RE = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
930
960
  function compileStyle(options) {
931
961
  const { source, filename, scoped, scopeId: overrideScopeId } = options;
932
962
  if (!scoped) {
@@ -947,7 +977,7 @@ function scopeSelectors(css, scopeId) {
947
977
  const result = [];
948
978
  let pos = 0;
949
979
  while (pos < css.length) {
950
- if (/\s/.test(css[pos])) {
980
+ if (WHITESPACE_RE2.test(css[pos])) {
951
981
  result.push(css[pos]);
952
982
  pos++;
953
983
  continue;
@@ -996,7 +1026,7 @@ function consumeAtRule(css, pos, scopeId) {
996
1026
  return { text: css.slice(start), end: css.length };
997
1027
  }
998
1028
  const header = css.slice(start, headerEnd);
999
- const isKeyframes = /^@(?:-\w+-)?keyframes\b/.test(header.trim());
1029
+ const isKeyframes = KEYFRAMES_RE.test(header.trim());
1000
1030
  depth = 1;
1001
1031
  let bodyStart = headerEnd + 1;
1002
1032
  let bodyEnd = headerEnd + 1;
@@ -1039,8 +1069,7 @@ function consumeRuleSet(css, pos, scopeId) {
1039
1069
  function scopeSingleSelector(selector, scopeId) {
1040
1070
  if (!selector) return selector;
1041
1071
  const attr = `[${scopeId}]`;
1042
- const pseudoRe = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
1043
- const pseudoMatch = selector.match(pseudoRe);
1072
+ const pseudoMatch = selector.match(PSEUDO_SELECTOR_RE);
1044
1073
  if (pseudoMatch) {
1045
1074
  const beforePseudo = selector.slice(0, pseudoMatch.index);
1046
1075
  return `${beforePseudo}${attr}${pseudoMatch[0]}`;
@@ -1049,6 +1078,7 @@ function scopeSingleSelector(selector, scopeId) {
1049
1078
  }
1050
1079
 
1051
1080
  // src/a11y.ts
1081
+ var HEADING_LEVEL_RE = /^h([1-6])$/;
1052
1082
  var VALID_ARIA_ROLES = /* @__PURE__ */ new Set([
1053
1083
  "alert",
1054
1084
  "alertdialog",
@@ -1218,7 +1248,7 @@ var rules = {
1218
1248
  }
1219
1249
  },
1220
1250
  "heading-order"(node, warnings, ctx) {
1221
- const match = node.tag.match(/^h([1-6])$/);
1251
+ const match = node.tag.match(HEADING_LEVEL_RE);
1222
1252
  if (!match) return;
1223
1253
  const level = parseInt(match[1], 10);
1224
1254
  if (ctx.lastHeadingLevel > 0 && level > ctx.lastHeadingLevel + 1) {
package/dist/index.d.cts CHANGED
@@ -14,6 +14,7 @@ interface SFCDescriptor {
14
14
  template: SFCBlock | null;
15
15
  script: SFCBlock | null;
16
16
  style: SFCBlock | null;
17
+ test: SFCBlock | null;
17
18
  filename: string;
18
19
  }
19
20
  /**
@@ -84,7 +85,7 @@ interface Directive {
84
85
  expression: string;
85
86
  modifiers: string[];
86
87
  }
87
- type DirectiveKind = 'on' | 'bind' | 'if' | 'else' | 'else-if' | 'for' | 'model';
88
+ type DirectiveKind = 'on' | 'bind' | 'if' | 'else' | 'else-if' | 'for' | 'model' | 'transition';
88
89
  /** Exported for testing — parse a template string into an AST. */
89
90
  declare function parseTemplate(source: string): TemplateNode[];
90
91
 
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ interface SFCDescriptor {
14
14
  template: SFCBlock | null;
15
15
  script: SFCBlock | null;
16
16
  style: SFCBlock | null;
17
+ test: SFCBlock | null;
17
18
  filename: string;
18
19
  }
19
20
  /**
@@ -84,7 +85,7 @@ interface Directive {
84
85
  expression: string;
85
86
  modifiers: string[];
86
87
  }
87
- type DirectiveKind = 'on' | 'bind' | 'if' | 'else' | 'else-if' | 'for' | 'model';
88
+ type DirectiveKind = 'on' | 'bind' | 'if' | 'else' | 'else-if' | 'for' | 'model' | 'transition';
88
89
  /** Exported for testing — parse a template string into an AST. */
89
90
  declare function parseTemplate(source: string): TemplateNode[];
90
91
 
package/dist/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  // src/parser.ts
2
+ var BLOCK_RE = /<(template|script|style|test)([\s][^>]*)?\s*>/g;
3
+ var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
2
4
  function parse(source, filename = "anonymous.utopia") {
3
5
  const descriptor = {
4
6
  template: null,
5
7
  script: null,
6
8
  style: null,
9
+ test: null,
7
10
  filename
8
11
  };
9
- const blockRe = /<(template|script|style)([\s][^>]*)?\s*>/g;
12
+ BLOCK_RE.lastIndex = 0;
10
13
  let match;
11
- while ((match = blockRe.exec(source)) !== null) {
14
+ while ((match = BLOCK_RE.exec(source)) !== null) {
12
15
  const tagName = match[1];
13
16
  const attrString = match[2] || "";
14
17
  const openTagStart = match.index;
@@ -39,11 +42,10 @@ function parse(source, filename = "anonymous.utopia") {
39
42
  );
40
43
  }
41
44
  descriptor[tagName] = block;
42
- blockRe.lastIndex = blockEnd;
45
+ BLOCK_RE.lastIndex = blockEnd;
43
46
  }
44
47
  return descriptor;
45
48
  }
46
- var ATTR_RE = /([a-zA-Z_][\w-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
47
49
  function parseAttributes(raw) {
48
50
  const attrs = {};
49
51
  let m;
@@ -80,6 +82,16 @@ var SFCParseError = class extends Error {
80
82
  };
81
83
 
82
84
  // src/template-compiler.ts
85
+ var TAG_NAME_CHAR_RE = /[a-zA-Z0-9\-_]/;
86
+ var ATTR_NAME_CHAR_RE = /[a-zA-Z0-9\-_:@.]/;
87
+ var ATTR_VALUE_END_RE = /[\s/>]/;
88
+ var WHITESPACE_RE = /\s/;
89
+ var TAG_START_CHAR_RE = /[a-zA-Z]/;
90
+ var U_FOR_EXPR_RE = /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/;
91
+ var COMPONENT_TAG_RE = /^[A-Z][a-zA-Z0-9_$]*$/;
92
+ var BACKSLASH_RE = /\\/g;
93
+ var SINGLE_QUOTE_RE = /'/g;
94
+ var HTML_ENTITY_RE = /&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g;
83
95
  function compileTemplate(template, options = {}) {
84
96
  const ast = parseTemplate(template);
85
97
  return generate(ast, options);
@@ -261,7 +273,7 @@ var TemplateParser = class {
261
273
  // ---- Low-level helpers --------------------------------------------------
262
274
  readTagName() {
263
275
  const start = this.pos;
264
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_]/.test(this.source[this.pos])) {
276
+ while (this.pos < this.source.length && TAG_NAME_CHAR_RE.test(this.source[this.pos])) {
265
277
  this.pos++;
266
278
  }
267
279
  const name = this.source.slice(start, this.pos);
@@ -270,7 +282,7 @@ var TemplateParser = class {
270
282
  }
271
283
  readAttributeName() {
272
284
  const start = this.pos;
273
- while (this.pos < this.source.length && /[a-zA-Z0-9\-_:@.]/.test(this.source[this.pos])) {
285
+ while (this.pos < this.source.length && ATTR_NAME_CHAR_RE.test(this.source[this.pos])) {
274
286
  this.pos++;
275
287
  }
276
288
  return this.source.slice(start, this.pos);
@@ -287,13 +299,13 @@ var TemplateParser = class {
287
299
  return value;
288
300
  }
289
301
  const start = this.pos;
290
- while (this.pos < this.source.length && !/[\s/>]/.test(this.source[this.pos])) {
302
+ while (this.pos < this.source.length && !ATTR_VALUE_END_RE.test(this.source[this.pos])) {
291
303
  this.pos++;
292
304
  }
293
305
  return this.source.slice(start, this.pos);
294
306
  }
295
307
  skipWhitespace() {
296
- while (this.pos < this.source.length && /\s/.test(this.source[this.pos])) {
308
+ while (this.pos < this.source.length && WHITESPACE_RE.test(this.source[this.pos])) {
297
309
  this.pos++;
298
310
  }
299
311
  }
@@ -303,7 +315,7 @@ var TemplateParser = class {
303
315
  /** Returns true when the char after `<` looks like the start of a tag name. */
304
316
  peekTagStart() {
305
317
  const next = this.source[this.pos + 1];
306
- return next !== void 0 && /[a-zA-Z]/.test(next);
318
+ return next !== void 0 && TAG_START_CHAR_RE.test(next);
307
319
  }
308
320
  expect(str) {
309
321
  if (!this.lookingAt(str)) {
@@ -373,7 +385,7 @@ function classifyDirective(name, value) {
373
385
  return null;
374
386
  }
375
387
  function isDirectiveKind(s) {
376
- return s === "on" || s === "bind" || s === "if" || s === "else" || s === "else-if" || s === "for" || s === "model";
388
+ return s === "on" || s === "bind" || s === "if" || s === "else" || s === "else-if" || s === "for" || s === "model" || s === "transition";
377
389
  }
378
390
  var CodeGenerator = class {
379
391
  constructor(options) {
@@ -502,6 +514,9 @@ ${fnBody}
502
514
  case "model":
503
515
  this.genModel(elVar, dir, scope);
504
516
  break;
517
+ case "transition":
518
+ this.genTransition(elVar, dir);
519
+ break;
505
520
  }
506
521
  }
507
522
  genOn(elVar, dir, scope) {
@@ -550,6 +565,19 @@ ${fnBody}
550
565
  this.emit(`createEffect(() => setAttr(${elVar}, 'value', ${signalRef}()))`);
551
566
  this.emit(`addEventListener(${elVar}, 'input', (e) => ${signalRef}.set(e.target.value))`);
552
567
  }
568
+ // ---- u-transition ---------------------------------------------------------
569
+ genTransition(elVar, dir) {
570
+ this.helpers.add("createTransition");
571
+ const name = dir.arg || dir.expression || "fade";
572
+ let durationOpt = "";
573
+ for (const mod of dir.modifiers) {
574
+ if (mod.startsWith("duration-")) {
575
+ const ms = mod.slice("duration-".length);
576
+ durationOpt = `, duration: ${ms}`;
577
+ }
578
+ }
579
+ this.emit(`createTransition(${elVar}, { name: '${escapeStr(name)}'${durationOpt} })`);
580
+ }
553
581
  // ---- Structural: u-if ---------------------------------------------------
554
582
  genIf(node, dir, scope, elseIfChain, elseNode) {
555
583
  this.helpers.add("createIf");
@@ -624,9 +652,7 @@ ${fnBody}
624
652
  const anchorVar = this.freshVar();
625
653
  this.helpers.add("createComment");
626
654
  this.emit(`const ${anchorVar} = createComment('u-for')`);
627
- const forMatch = dir.expression.match(
628
- /^\s*(?:\(\s*(\w+)\s*(?:,\s*(\w+)\s*)?\)|(\w+))\s+in\s+(.+)$/
629
- );
655
+ const forMatch = dir.expression.match(U_FOR_EXPR_RE);
630
656
  if (!forMatch) {
631
657
  throw new Error(`Invalid u-for expression: "${dir.expression}"`);
632
658
  }
@@ -829,10 +855,10 @@ ${fnBody}
829
855
  }
830
856
  };
831
857
  function isComponentTag(tag) {
832
- return /^[A-Z][a-zA-Z0-9_$]*$/.test(tag);
858
+ return COMPONENT_TAG_RE.test(tag);
833
859
  }
834
860
  function escapeStr(s) {
835
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
861
+ return s.replace(BACKSLASH_RE, "\\\\").replace(SINGLE_QUOTE_RE, "\\'");
836
862
  }
837
863
  var ENTITY_MAP = {
838
864
  "&amp;": "&",
@@ -861,7 +887,8 @@ var ENTITY_MAP = {
861
887
  "&divide;": "\xF7"
862
888
  };
863
889
  function decodeEntities(text) {
864
- return text.replace(/&(?:#(\d+)|#x([0-9a-fA-F]+)|(\w+));/g, (match, dec, hex, named) => {
890
+ HTML_ENTITY_RE.lastIndex = 0;
891
+ return text.replace(HTML_ENTITY_RE, (match, dec, hex, named) => {
865
892
  if (dec) {
866
893
  const code = parseInt(dec, 10);
867
894
  if (code >= 0 && code <= 1114111) {
@@ -894,6 +921,9 @@ function generate(ast, options) {
894
921
  }
895
922
 
896
923
  // src/style-compiler.ts
924
+ var WHITESPACE_RE2 = /\s/;
925
+ var KEYFRAMES_RE = /^@(?:-\w+-)?keyframes\b/;
926
+ var PSEUDO_SELECTOR_RE = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
897
927
  function compileStyle(options) {
898
928
  const { source, filename, scoped, scopeId: overrideScopeId } = options;
899
929
  if (!scoped) {
@@ -914,7 +944,7 @@ function scopeSelectors(css, scopeId) {
914
944
  const result = [];
915
945
  let pos = 0;
916
946
  while (pos < css.length) {
917
- if (/\s/.test(css[pos])) {
947
+ if (WHITESPACE_RE2.test(css[pos])) {
918
948
  result.push(css[pos]);
919
949
  pos++;
920
950
  continue;
@@ -963,7 +993,7 @@ function consumeAtRule(css, pos, scopeId) {
963
993
  return { text: css.slice(start), end: css.length };
964
994
  }
965
995
  const header = css.slice(start, headerEnd);
966
- const isKeyframes = /^@(?:-\w+-)?keyframes\b/.test(header.trim());
996
+ const isKeyframes = KEYFRAMES_RE.test(header.trim());
967
997
  depth = 1;
968
998
  let bodyStart = headerEnd + 1;
969
999
  let bodyEnd = headerEnd + 1;
@@ -1006,8 +1036,7 @@ function consumeRuleSet(css, pos, scopeId) {
1006
1036
  function scopeSingleSelector(selector, scopeId) {
1007
1037
  if (!selector) return selector;
1008
1038
  const attr = `[${scopeId}]`;
1009
- const pseudoRe = /(?:::?[\w-]+(?:\([^)]*\))?)+$/;
1010
- const pseudoMatch = selector.match(pseudoRe);
1039
+ const pseudoMatch = selector.match(PSEUDO_SELECTOR_RE);
1011
1040
  if (pseudoMatch) {
1012
1041
  const beforePseudo = selector.slice(0, pseudoMatch.index);
1013
1042
  return `${beforePseudo}${attr}${pseudoMatch[0]}`;
@@ -1016,6 +1045,7 @@ function scopeSingleSelector(selector, scopeId) {
1016
1045
  }
1017
1046
 
1018
1047
  // src/a11y.ts
1048
+ var HEADING_LEVEL_RE = /^h([1-6])$/;
1019
1049
  var VALID_ARIA_ROLES = /* @__PURE__ */ new Set([
1020
1050
  "alert",
1021
1051
  "alertdialog",
@@ -1185,7 +1215,7 @@ var rules = {
1185
1215
  }
1186
1216
  },
1187
1217
  "heading-order"(node, warnings, ctx) {
1188
- const match = node.tag.match(/^h([1-6])$/);
1218
+ const match = node.tag.match(HEADING_LEVEL_RE);
1189
1219
  if (!match) return;
1190
1220
  const level = parseInt(match[1], 10);
1191
1221
  if (ctx.lastHeadingLevel > 0 && level > ctx.lastHeadingLevel + 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/utopia-compiler",
3
- "version": "0.4.0",
3
+ "version": "0.7.0",
4
4
  "description": "Compiler for .utopia single-file components",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "dist"
40
40
  ],
41
41
  "dependencies": {
42
- "@matthesketh/utopia-core": "0.4.0"
42
+ "@matthesketh/utopia-core": "0.7.0"
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsup src/index.ts --format esm,cjs --dts",