@jackens/nnn 2026.9.29 → 2026.9.30

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 (3) hide show
  1. package/README.md +171 -3
  2. package/nnn.js +13 -3
  3. package/package.json +8 -7
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # nnn
2
2
 
3
- A collection of Jackens’ JavaScript helper utilities (version: `2026.9.29`).
3
+ A collection of Jackens’ JavaScript helper utilities (version: `2026.9.30`).
4
4
 
5
5
  ## Installation
6
6
 
@@ -149,6 +149,8 @@ A CSS string representing the compiled rules.
149
149
 
150
150
  #### Usage Examples
151
151
 
152
+ Nested selectors split a rule while preserving property order:
153
+
152
154
  ```ts
153
155
  const actual = c({
154
156
  a: {
@@ -176,6 +178,8 @@ const expected = `
176
178
  expect(actual).toBe(expected)
177
179
  ```
178
180
 
181
+ Selectors can be nested at any depth:
182
+
179
183
  ```ts
180
184
  const actual = c({
181
185
  a: {
@@ -205,6 +209,8 @@ const expected = `
205
209
  expect(actual).toBe(expected)
206
210
  ```
207
211
 
212
+ At-rules, keyframes, media queries and repeated keys with the `$$` suffix:
213
+
208
214
  ```ts
209
215
  const actual = c({
210
216
  '@font-face$$1': {
@@ -280,6 +286,8 @@ const expected = `
280
286
  expect(actual).toBe(expected)
281
287
  ```
282
288
 
289
+ Comma-separated nested selectors are expanded:
290
+
283
291
  ```ts
284
292
  const actual = c({
285
293
  a: {
@@ -304,6 +312,8 @@ const expected = `
304
312
  expect(actual).toBe(expected)
305
313
  ```
306
314
 
315
+ Comma-separated top-level selectors are expanded in nested rules:
316
+
307
317
  ```ts
308
318
  const actual = c({
309
319
  '.b,.c': {
@@ -326,6 +336,8 @@ const expected = `
326
336
  expect(actual).toBe(expected)
327
337
  ```
328
338
 
339
+ Comma-separated selectors at both levels produce all combinations:
340
+
329
341
  ```ts
330
342
  const actual = c({
331
343
  '.a,.b': {
@@ -348,6 +360,8 @@ const expected = `
348
360
  expect(actual).toBe(expected)
349
361
  ```
350
362
 
363
+ CSS custom properties from nested `--` objects:
364
+
351
365
  ```ts
352
366
  const actual = c({
353
367
  ':root$$monokai': {
@@ -478,6 +492,8 @@ A lexer function that accepts an input string and returns an array of decorated
478
492
 
479
493
  #### Usage Examples
480
494
 
495
+ Tokenizing keywords and strings with `RegExp` patterns:
496
+
481
497
  ```ts
482
498
  const lexer = createLexer(
483
499
  (chunk, metadata) => ({ chunk, metadata }),
@@ -498,6 +514,8 @@ expect(result).toEqual([
498
514
  ])
499
515
  ```
500
516
 
517
+ String patterns are matched literally and unmatched text gets `undefined` metadata:
518
+
501
519
  ```ts
502
520
  const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['tag', 'BEGIN'], ['end', 'END'])
503
521
 
@@ -506,6 +524,8 @@ const result = lexer('aBEGINbENDc')
506
524
  expect(result).toEqual(['undefined:a', 'tag:BEGIN', 'undefined:b', 'end:END', 'undefined:c'])
507
525
  ```
508
526
 
527
+ Empty input produces an empty result:
528
+
509
529
  ```ts
510
530
  const lexer = createLexer(chunk => chunk, ['test', /test/])
511
531
 
@@ -514,6 +534,8 @@ const result = lexer('')
514
534
  expect(result).toEqual([])
515
535
  ```
516
536
 
537
+ `^` anchors to the start of the whole input:
538
+
517
539
  ```ts
518
540
  const lexer = createLexer((chunk, metadata) => ({ chunk, metadata }), ['start', /^test/])
519
541
 
@@ -525,6 +547,8 @@ expect(result).toEqual([
525
547
  ])
526
548
  ```
527
549
 
550
+ Matches starting earlier take precedence regardless of spec order:
551
+
528
552
  ```ts
529
553
  const lexer = createLexer((chunk, metadata) => metadata, ['later', /x/], ['earlier', /y/])
530
554
 
@@ -533,6 +557,8 @@ const result = lexer('yx')
533
557
  expect(result).toEqual(['earlier', 'later'])
534
558
  ```
535
559
 
560
+ The longer match wins at the same position:
561
+
536
562
  ```ts
537
563
  const lexer = createLexer(chunk => chunk, ['short', 'a'], ['long', 'abc'])
538
564
 
@@ -541,6 +567,8 @@ const result = lexer('abc')
541
567
  expect(result).toEqual(['abc'])
542
568
  ```
543
569
 
570
+ Empty string patterns are ignored:
571
+
544
572
  ```ts
545
573
  const lexer = createLexer(chunk => chunk, ['empty', ''], ['word', /\w+/])
546
574
 
@@ -549,6 +577,8 @@ const result = lexer('hello')
549
577
  expect(result).toEqual(['hello'])
550
578
  ```
551
579
 
580
+ Input without any match is returned as a single chunk:
581
+
552
582
  ```ts
553
583
  const lexer = createLexer(chunk => chunk, ['test', /xyz/])
554
584
 
@@ -557,6 +587,8 @@ const result = lexer('abc')
557
587
  expect(result).toEqual(['abc'])
558
588
  ```
559
589
 
590
+ Consecutive matches are returned as separate chunks:
591
+
560
592
  ```ts
561
593
  const lexer = createLexer((_chunk, metadata) => metadata, ['a', 'a'], ['b', 'b'])
562
594
 
@@ -565,6 +597,8 @@ const result = lexer('aabb')
565
597
  expect(result).toEqual(['a', 'a', 'b', 'b'])
566
598
  ```
567
599
 
600
+ Entering and leaving states with `push` and `pop`:
601
+
568
602
  ```ts
569
603
  const lexer = createLexer(
570
604
  (chunk, metadata) => `${metadata}:${chunk}`,
@@ -578,6 +612,8 @@ const result = lexer('a(b)c)')
578
612
  expect(result).toEqual(['undefined:a', 'open:(', 'word:b', 'close:)', 'undefined:c)'])
579
613
  ```
580
614
 
615
+ Nested states with a spec active in multiple states:
616
+
581
617
  ```ts
582
618
  const lexer = createLexer(
583
619
  (chunk, metadata) => `${metadata}:${chunk}`,
@@ -590,6 +626,8 @@ const result = lexer('{{}}}')
590
626
  expect(result).toEqual(['open:{', 'open:{', 'close:}', 'close:}', 'undefined:}'])
591
627
  ```
592
628
 
629
+ Lookbehinds see the text preceding the current position:
630
+
593
631
  ```ts
594
632
  const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['a', 'a'], ['b', /(?<=a)b/])
595
633
 
@@ -598,6 +636,8 @@ const result = lexer('abcb')
598
636
  expect(result).toEqual(['a:a', 'b:b', 'undefined:cb'])
599
637
  ```
600
638
 
639
+ Sticky patterns match only at the current position:
640
+
601
641
  ```ts
602
642
  const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['x', /x/y], ['y', 'y'])
603
643
 
@@ -606,6 +646,8 @@ const result = lexer('xyx.x')
606
646
  expect(result).toEqual(['x:x', 'y:y', 'x:x', 'undefined:.x'])
607
647
  ```
608
648
 
649
+ Empty `RegExp` matches are skipped:
650
+
609
651
  ```ts
610
652
  const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['digits', /\d*/])
611
653
 
@@ -650,6 +692,8 @@ The created `HTMLElement` or the modified node.
650
692
 
651
693
  #### Usage Examples
652
694
 
695
+ Creating elements, appending children and setting properties:
696
+
653
697
  ```ts
654
698
  const b = h('b')
655
699
 
@@ -670,6 +714,8 @@ expect(i.outerHTML).toBe('<i class="some class">text</i>')
670
714
  expect(b.outerHTML).toBe('<b><i class="some class">text</i></b>')
671
715
  ```
672
716
 
717
+ Text content from strings, numbers and `$innerText`:
718
+
673
719
  ```ts
674
720
  expect(h('span', 'text').outerHTML).toBe('<span>text</span>')
675
721
 
@@ -680,6 +726,8 @@ expect(h('span', '42').outerHTML).toBe('<span>42</span>')
680
726
  expect(h('span', 42).outerHTML).toBe('<span>42</span>')
681
727
  ```
682
728
 
729
+ `style` attribute vs `$style` property (string or object):
730
+
683
731
  ```ts
684
732
  expect(h('div', { style: 'margin:0;padding:0' }).outerHTML).toBe('<div style="margin:0;padding:0"></div>')
685
733
 
@@ -690,6 +738,8 @@ expect(h('div', { $style: { margin: 0, padding: 0 } }).outerHTML).toBe(
690
738
  )
691
739
  ```
692
740
 
741
+ `value` attribute vs `$value` property:
742
+
693
743
  ```ts
694
744
  const input1 = h('input', { value: 42 })
695
745
  const input2 = h('input', { $value: '42' })
@@ -701,6 +751,8 @@ expect(input1.outerHTML).toBe('<input value="42">')
701
751
  expect(input2.outerHTML).toBe('<input>')
702
752
  ```
703
753
 
754
+ `checked` attribute vs `$checked` property:
755
+
704
756
  ```ts
705
757
  const checkbox1 = h('input', {
706
758
  type: 'checkbox',
@@ -720,6 +772,8 @@ expect(checkbox1.outerHTML).toBe('<input type="checkbox" checked="">')
720
772
  expect(checkbox2.outerHTML).toBe('<input type="checkbox">')
721
773
  ```
722
774
 
775
+ Object values of `$`-prefixed properties are merged:
776
+
723
777
  ```ts
724
778
  const div = h('div')
725
779
 
@@ -734,6 +788,8 @@ h(div, { $key: { two: 2 } })
734
788
  expect(div.key).toEqual({ one: 1, two: 2 })
735
789
  ```
736
790
 
791
+ Class attribute, appending text and nested children given as arrays:
792
+
737
793
  ```ts
738
794
  const elemWithClass = h('div', { class: 'test' })
739
795
 
@@ -761,11 +817,14 @@ It styles `pre` and `code>span` elements with the class names produced by [`nano
761
817
 
762
818
  #### Usage Examples
763
819
 
820
+ Styling `nanolightTs` output:
821
+
764
822
  ```ts
765
823
  const style = h('style', c(monokai))
766
824
  const pre = h('pre', ['code', ...nanolightTs('const x = 42')])
767
825
 
768
826
  expect(style.textContent).toContain('code>span.keyword1{color:var(--keyword1)}')
827
+ expect(style.textContent).toContain('code>span.regexp{color:var(--regexp)}')
769
828
  expect(style.textContent).toContain('@media only screen and (prefers-color-scheme: dark){:root{--bg:#2d2a2e;')
770
829
  expect(pre.outerHTML).toBe(
771
830
  '<pre><code>' +
@@ -795,6 +854,8 @@ An array of [`HArgs1`](#HArgs1) elements suitable for rendering with [`h`](#h):
795
854
 
796
855
  #### Usage Examples
797
856
 
857
+ Keywords, identifiers, operators, numbers, punctuation, strings and comments:
858
+
798
859
  ```ts
799
860
  const codeJs = 'const answerToLifeTheUniverseAndEverything = ' + "{ 42: 42 }['42'] /* 42 */"
800
861
 
@@ -821,6 +882,8 @@ expect(nanolightTs(codeJs)).toEqual([
821
882
  ])
822
883
  ```
823
884
 
885
+ Line comments:
886
+
824
887
  ```ts
825
888
  const codeJs = 'const x = 42 // answer'
826
889
 
@@ -837,13 +900,24 @@ expect(nanolightTs(codeJs)).toEqual([
837
900
  ])
838
901
  ```
839
902
 
903
+ Regex literals vs division:
904
+
840
905
  ```ts
841
906
  expect(nanolightTs('x = /a\\/b[/]c/gi.test(y) / 2')).toEqual([
842
907
  ['span', { class: 'identifier4' }, 'x'],
843
908
  ' ',
844
909
  ['span', { class: 'operator' }, '='],
845
910
  ' ',
846
- ['span', { class: 'string' }, '/a\\/b[/]c/gi'],
911
+ ['span', { class: 'regexp' }, '/'],
912
+ ['span', { class: 'regexp' }, 'a'],
913
+ ['span', { class: 'keyword2' }, '\\/'],
914
+ ['span', { class: 'regexp' }, 'b'],
915
+ ['span', { class: 'punctuation' }, '['],
916
+ ['span', { class: 'regexp' }, '/'],
917
+ ['span', { class: 'punctuation' }, ']'],
918
+ ['span', { class: 'regexp' }, 'c'],
919
+ ['span', { class: 'regexp' }, '/'],
920
+ ['span', { class: 'keyword1' }, 'gi'],
847
921
  ['span', { class: 'punctuation' }, '.'],
848
922
  ['span', { class: 'identifier2' }, 'test'],
849
923
  ['span', { class: 'punctuation' }, '('],
@@ -870,10 +944,24 @@ expect(nanolightTs('a / b / c')).toEqual([
870
944
  expect(nanolightTs('return / x /')).toEqual([
871
945
  ['span', { class: 'keyword1' }, 'return'],
872
946
  ' ',
873
- ['span', { class: 'string' }, '/ x /'],
947
+ ['span', { class: 'regexp' }, '/'],
948
+ ['span', { class: 'regexp' }, ' x '],
949
+ ['span', { class: 'regexp' }, '/'],
950
+ ])
951
+
952
+ expect(nanolightTs('x = /=/g')).toEqual([
953
+ ['span', { class: 'identifier4' }, 'x'],
954
+ ' ',
955
+ ['span', { class: 'operator' }, '='],
956
+ ' ',
957
+ ['span', { class: 'regexp' }, '/='],
958
+ ['span', { class: 'regexp' }, '/'],
959
+ ['span', { class: 'keyword1' }, 'g'],
874
960
  ])
875
961
  ```
876
962
 
963
+ Nested template literals with substitutions:
964
+
877
965
  ```ts
878
966
  expect(nanolightTs('`a ${b + `c ${ {d}.d }`} \\${e} $f`')).toEqual([
879
967
  ['span', { class: 'string' }, '`a '],
@@ -910,6 +998,8 @@ expect(nanolightTs('`${x}` / 2')).toEqual([
910
998
  ])
911
999
  ```
912
1000
 
1001
+ Comment markers inside strings and unterminated comments:
1002
+
913
1003
  ```ts
914
1004
  expect(nanolightTs('"a // b" // c')).toEqual([
915
1005
  ['span', { class: 'string' }, '"a // b"'],
@@ -924,6 +1014,8 @@ expect(nanolightTs("'it\\'s' /* x")).toEqual([
924
1014
  ])
925
1015
  ```
926
1016
 
1017
+ Hexadecimal, BigInt, numeric separators and exponents:
1018
+
927
1019
  ```ts
928
1020
  expect(nanolightTs('0xFF + 1_000n + .5e-3')).toEqual([
929
1021
  ['span', { class: 'number' }, '0xFF'],
@@ -938,6 +1030,8 @@ expect(nanolightTs('0xFF + 1_000n + .5e-3')).toEqual([
938
1030
  ])
939
1031
  ```
940
1032
 
1033
+ Division after `++` and optional chaining vs ternary with decimals:
1034
+
941
1035
  ```ts
942
1036
  expect(nanolightTs('a++ / 2 / b')).toEqual([
943
1037
  ['span', { class: 'identifier4' }, 'a'],
@@ -963,6 +1057,8 @@ expect(nanolightTs('x?.5:a?.b')).toEqual([
963
1057
  ])
964
1058
  ```
965
1059
 
1060
+ Keywords as property names and method calls:
1061
+
966
1062
  ```ts
967
1063
  expect(nanolightTs('p.catch(obj.in, ...this)')).toEqual([
968
1064
  ['span', { class: 'identifier4' }, 'p'],
@@ -980,6 +1076,8 @@ expect(nanolightTs('p.catch(obj.in, ...this)')).toEqual([
980
1076
  ])
981
1077
  ```
982
1078
 
1079
+ Hashbang, decorators and private class members:
1080
+
983
1081
  ```ts
984
1082
  expect(nanolightTs('#!/usr/bin/env bun\n@sealed class A { #x = 1 }')).toEqual([
985
1083
  ['span', { class: 'comment' }, '#!/usr/bin/env bun'],
@@ -1002,6 +1100,64 @@ expect(nanolightTs('#!/usr/bin/env bun\n@sealed class A { #x = 1 }')).toEqual([
1002
1100
  ])
1003
1101
  ```
1004
1102
 
1103
+ Escapes, anchors, quantifiers, groups, character sets and flags inside regex literals:
1104
+
1105
+ ```ts
1106
+ expect(nanolightTs('/^(?:\\d{1,3}|[^a-z\\]])+?\\b(?<year>\\d{4})-\\k<year>\\1$/u')).toEqual([
1107
+ ['span', { class: 'regexp' }, '/'],
1108
+ ['span', { class: 'keyword1' }, '^'],
1109
+ ['span', { class: 'punctuation' }, '(?:'],
1110
+ ['span', { class: 'keyword3' }, '\\d'],
1111
+ ['span', { class: 'operator' }, '{1,3}'],
1112
+ ['span', { class: 'operator' }, '|'],
1113
+ ['span', { class: 'punctuation' }, '['],
1114
+ ['span', { class: 'operator' }, '^'],
1115
+ ['span', { class: 'regexp' }, 'a'],
1116
+ ['span', { class: 'operator' }, '-'],
1117
+ ['span', { class: 'regexp' }, 'z'],
1118
+ ['span', { class: 'keyword2' }, '\\]'],
1119
+ ['span', { class: 'punctuation' }, ']'],
1120
+ ['span', { class: 'punctuation' }, ')'],
1121
+ ['span', { class: 'operator' }, '+?'],
1122
+ ['span', { class: 'keyword1' }, '\\b'],
1123
+ ['span', { class: 'punctuation' }, '(?<'],
1124
+ ['span', { class: 'identifier2' }, 'year'],
1125
+ ['span', { class: 'punctuation' }, '>'],
1126
+ ['span', { class: 'keyword3' }, '\\d'],
1127
+ ['span', { class: 'operator' }, '{4}'],
1128
+ ['span', { class: 'punctuation' }, ')'],
1129
+ ['span', { class: 'regexp' }, '-'],
1130
+ ['span', { class: 'identifier2' }, '\\k<year>'],
1131
+ ['span', { class: 'identifier2' }, '\\1'],
1132
+ ['span', { class: 'keyword1' }, '$'],
1133
+ ['span', { class: 'regexp' }, '/'],
1134
+ ['span', { class: 'keyword1' }, 'u'],
1135
+ ])
1136
+
1137
+ expect(nanolightTs('/(?<=\\$)(?!0)\\p{L}+\\.\\u{1F600}|\\x41[.\\b-]/v')).toEqual([
1138
+ ['span', { class: 'regexp' }, '/'],
1139
+ ['span', { class: 'punctuation' }, '(?<='],
1140
+ ['span', { class: 'keyword2' }, '\\$'],
1141
+ ['span', { class: 'punctuation' }, ')'],
1142
+ ['span', { class: 'punctuation' }, '(?!'],
1143
+ ['span', { class: 'regexp' }, '0'],
1144
+ ['span', { class: 'punctuation' }, ')'],
1145
+ ['span', { class: 'keyword3' }, '\\p{L}'],
1146
+ ['span', { class: 'operator' }, '+'],
1147
+ ['span', { class: 'keyword2' }, '\\.'],
1148
+ ['span', { class: 'keyword2' }, '\\u{1F600}'],
1149
+ ['span', { class: 'operator' }, '|'],
1150
+ ['span', { class: 'keyword2' }, '\\x41'],
1151
+ ['span', { class: 'punctuation' }, '['],
1152
+ ['span', { class: 'regexp' }, '.'],
1153
+ ['span', { class: 'keyword2' }, '\\b'],
1154
+ ['span', { class: 'regexp' }, '-'],
1155
+ ['span', { class: 'punctuation' }, ']'],
1156
+ ['span', { class: 'regexp' }, '/'],
1157
+ ['span', { class: 'keyword1' }, 'v'],
1158
+ ])
1159
+ ```
1160
+
1005
1161
  ### rwd
1006
1162
 
1007
1163
  ```ts
@@ -1043,6 +1199,8 @@ An array of breakpoint specifications, each a tuple of:
1043
1199
 
1044
1200
  #### Usage Examples
1045
1201
 
1202
+ Responsive grid cell widths and heights with media queries:
1203
+
1046
1204
  ```ts
1047
1205
  const style: CNode = {
1048
1206
  body: {
@@ -1124,24 +1282,32 @@ The created `SVGElement` or the modified node.
1124
1282
 
1125
1283
  #### Usage Examples
1126
1284
 
1285
+ Namespaced `xlink:` attribute set to `true` gets an empty value:
1286
+
1127
1287
  ```ts
1128
1288
  const svg = s('svg', { 'xlink:href': true })
1129
1289
 
1130
1290
  expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('')
1131
1291
  ```
1132
1292
 
1293
+ Namespaced `xlink:` attribute set to `false` is not set:
1294
+
1133
1295
  ```ts
1134
1296
  const svg = s('svg', { 'xlink:href': false })
1135
1297
 
1136
1298
  expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBeNull()
1137
1299
  ```
1138
1300
 
1301
+ Namespaced `xlink:` attribute with a string value:
1302
+
1139
1303
  ```ts
1140
1304
  const svg = s('svg', { 'xlink:href': 'http://example.com' })
1141
1305
 
1142
1306
  expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('http://example.com')
1143
1307
  ```
1144
1308
 
1309
+ Namespaced `xlink:` attribute with a number value:
1310
+
1145
1311
  ```ts
1146
1312
  const svg = s('svg', { 'xlink:title': 42 })
1147
1313
 
@@ -1176,6 +1342,8 @@ An `SVGSVGElement` containing a `<use>` element.
1176
1342
 
1177
1343
  #### Usage Examples
1178
1344
 
1345
+ SVG `use` element referencing a symbol, with extra attributes:
1346
+
1179
1347
  ```ts
1180
1348
  const svg = svgUse('icon-home')
1181
1349
 
package/nnn.js CHANGED
@@ -201,6 +201,7 @@ var monokai = {
201
201
  number: "#7058be",
202
202
  operator: "#e14775",
203
203
  punctuation: "#918c8e",
204
+ regexp: "#e16032",
204
205
  string: "#cc7a0a"
205
206
  }
206
207
  },
@@ -233,6 +234,7 @@ var monokai = {
233
234
  number: { color: "var(--number)" },
234
235
  operator: { color: "var(--operator)" },
235
236
  punctuation: { color: "var(--punctuation)" },
237
+ regexp: { color: "var(--regexp)" },
236
238
  string: { color: "var(--string)" }
237
239
  },
238
240
  "@media only screen and (prefers-color-scheme: dark)$$monokai": {
@@ -255,6 +257,7 @@ var monokai = {
255
257
  number: "#ae81ff",
256
258
  operator: "#ff6188",
257
259
  punctuation: "#727072",
260
+ regexp: "#fc9867",
258
261
  string: "#ffd866"
259
262
  }
260
263
  }
@@ -280,13 +283,20 @@ var KEYWORD3 = "keyword3";
280
283
  var NUMBER = "number";
281
284
  var OPERATOR = "operator";
282
285
  var PUNCTUATION = "punctuation";
286
+ var REGEXP = "regexp";
283
287
  var STRING = "string";
284
288
  var ROOT2 = "root";
285
289
  var TEMPLATE = "template";
290
+ var REGEXP_BODY = "regexpBody";
291
+ var REGEXP_FLAGS = "regexpFlags";
292
+ var REGEXP_GROUP_NAME = "regexpGroupName";
293
+ var REGEXP_SET = "regexpSet";
294
+ var REGEXP_SET_NEGATION = "regexpSetNegation";
286
295
  var nanolightTs = /* @__PURE__ */ createLexer((chunk, name) => name != null ? ["span", { class: name }, chunk] : chunk, [COMMENT, /^#!.*/], [COMMENT, /\/\*[\s\S]*?(?:\*\/|$)/], [COMMENT, /\/\/.*/], [STRING, /"(?:[^"\\\n]|\\[\s\S])*"?/], [STRING, /'(?:[^'\\\n]|\\[\s\S])*'?/], [STRING, /`(?:[^`\\$]|\\[\s\S]|\$(?!\{))*(?:`|$)/], [STRING, /`(?:[^`\\$]|\\[\s\S]|\$(?!\{))*(?=\$\{)/, { push: TEMPLATE }], [STRING, /(?:[^`\\$]|\\[\s\S]|\$(?!\{))*(?:`|$)/y, { in: TEMPLATE, pop: true }], [STRING, /(?:[^`\\$]|\\[\s\S]|\$(?!\{))+(?=\$\{)/y, { in: TEMPLATE }], [PUNCTUATION, "${", { in: TEMPLATE, push: ROOT2 }], [
287
- STRING,
288
- /(?<=(?:^|[!%&(*,:;<=>?[^{|}~]|(?<!\+)\+|(?<!-)-|(?<!\.)\b(?:await|case|delete|do|else|in|instanceof|new|of|return|throw|typeof|void|yield))\s*)\/(?![*/])(?:[^\\/[\n]|\\.|\[(?:[^\\\]\n]|\\.)*\]?)+\/[dgimsuvy]*/
289
- ], [NUMBER, /0b[01_]+n?/i], [NUMBER, /0o[0-7_]+n?/i], [NUMBER, /0x[\da-f_]+n?/i], [NUMBER, /(?:\d[\d_]*(?:\.[\d_]+)?|\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?n?/i], [OPERATOR, "!"], [OPERATOR, "!="], [OPERATOR, "!=="], [OPERATOR, "%"], [OPERATOR, "%="], [OPERATOR, "&&"], [OPERATOR, "&&="], [OPERATOR, "&"], [OPERATOR, "&="], [OPERATOR, "*"], [OPERATOR, "**"], [OPERATOR, "**="], [OPERATOR, "*="], [OPERATOR, "+"], [OPERATOR, "++"], [OPERATOR, "+="], [OPERATOR, "-"], [OPERATOR, "--"], [OPERATOR, "-="], [OPERATOR, "..."], [OPERATOR, "/"], [OPERATOR, "/="], [OPERATOR, ":"], [OPERATOR, "<"], [OPERATOR, "<<"], [OPERATOR, "<<="], [OPERATOR, "<="], [OPERATOR, "="], [OPERATOR, "=="], [OPERATOR, "==="], [OPERATOR, "=>"], [OPERATOR, ">"], [OPERATOR, ">="], [OPERATOR, ">>"], [OPERATOR, ">>="], [OPERATOR, ">>>"], [OPERATOR, ">>>="], [OPERATOR, "?"], [OPERATOR, "??"], [OPERATOR, "??="], [OPERATOR, "^"], [OPERATOR, "^="], [OPERATOR, "|"], [OPERATOR, "|="], [OPERATOR, "||"], [OPERATOR, "||="], [OPERATOR, "~"], [OPERATOR, "~="], [OPERATOR, /(?<=\s):/], [PUNCTUATION, "("], [PUNCTUATION, ")"], [PUNCTUATION, ","], [PUNCTUATION, "."], [PUNCTUATION, ":"], [PUNCTUATION, ";"], [PUNCTUATION, /\?\.(?!\d)/], [PUNCTUATION, "["], [PUNCTUATION, "]"], [PUNCTUATION, "{", { push: ROOT2 }], [PUNCTUATION, "}", { pop: true }], [IDENTIFIER2, /(?<=(?<!\.)\.)\p{Ll}[\p{L}\d_$]*(?=[(`])/u], [IDENTIFIER4, /(?<=(?<!\.)\.)\p{Ll}[\p{L}\d_$]*/u], [KEYWORD1, "as"], [KEYWORD1, "async"], [KEYWORD1, "await"], [KEYWORD1, "break"], [KEYWORD1, "case"], [KEYWORD1, "catch"], [KEYWORD1, "class"], [KEYWORD1, "const"], [KEYWORD1, "continue"], [KEYWORD1, "debugger"], [KEYWORD1, "default"], [KEYWORD1, "delete"], [KEYWORD1, "do"], [KEYWORD1, "else"], [KEYWORD1, "export"], [KEYWORD1, "extends"], [KEYWORD1, "finally"], [KEYWORD1, "for"], [KEYWORD1, "from"], [KEYWORD1, "function"], [KEYWORD1, "function*"], [KEYWORD1, "goto"], [KEYWORD1, "if"], [KEYWORD1, "import"], [KEYWORD1, "in"], [KEYWORD1, "instanceof"], [KEYWORD1, "is"], [KEYWORD1, "keyof"], [KEYWORD1, "let"], [KEYWORD1, "new"], [KEYWORD1, "of"], [KEYWORD1, "package"], [KEYWORD1, "return"], [KEYWORD1, "super"], [KEYWORD1, "switch"], [KEYWORD1, "this"], [KEYWORD1, "throw"], [KEYWORD1, "try"], [KEYWORD1, "type"], [KEYWORD1, "typeof"], [KEYWORD1, "var"], [KEYWORD1, "void"], [KEYWORD1, "while"], [KEYWORD1, "with"], [KEYWORD1, "yield"], [KEYWORD1, "yield*"], [KEYWORD2, "false"], [KEYWORD2, "Infinity"], [KEYWORD2, "NaN"], [KEYWORD2, "null"], [KEYWORD2, "true"], [KEYWORD2, "undefined"], [KEYWORD3, "any"], [KEYWORD3, "bigint"], [KEYWORD3, "boolean"], [KEYWORD3, "eval"], [KEYWORD3, "number"], [KEYWORD3, "string"], [KEYWORD3, "symbol"], [KEYWORD3, "unknown"], [IDENTIFIER2, /@[\p{L}_$][\p{L}\d_$]*/u], [IDENTIFIER1, /#?[\p{Lu}_$][\p{Lu}\d_$]*/u], [IDENTIFIER2, /#?[\p{L}_$][\p{L}\d_$]*(?=[(`])/u], [IDENTIFIER3, /#?\p{Lu}[\p{L}\d_$]*/u], [IDENTIFIER4, /#?[\p{L}_$][\p{L}\d_$]*/u]);
296
+ REGEXP,
297
+ /(?<=(?:^|[!%&(*,:;<=>?[^{|}~]|(?<!\+)\+|(?<!-)-|(?<!\.)\b(?:await|case|delete|do|else|in|instanceof|new|of|return|throw|typeof|void|yield))\s*)\/(?![*/])(?=(?:[^\\/[\n]|\\.|\[(?:[^\\\]\n]|\\.)*\])+\/)=?/,
298
+ { push: REGEXP_BODY }
299
+ ], [REGEXP, /\/(?=[dgimsuvy])/y, { in: REGEXP_BODY, pop: true, push: REGEXP_FLAGS }], [REGEXP, /\/(?![dgimsuvy])/y, { in: REGEXP_BODY, pop: true }], [KEYWORD1, /[dgimsuvy]+/y, { in: REGEXP_FLAGS, pop: true }], [IDENTIFIER2, /\\[1-9]\d*|\\k<[\p{L}_$][\p{L}\d_$]*>/uy, { in: REGEXP_BODY }], [KEYWORD1, /\\[bB]/y, { in: REGEXP_BODY }], [KEYWORD3, /\\[dDsSwW]|\\[pP]\{[\w=]*\}/y, { in: [REGEXP_BODY, REGEXP_SET] }], [KEYWORD2, /\\(?:c[A-Za-z]|x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|.)/uy, { in: [REGEXP_BODY, REGEXP_SET] }], [KEYWORD1, /[$^]/y, { in: REGEXP_BODY }], [KEYWORD3, ".", { in: REGEXP_BODY }], [OPERATOR, /(?:[*+?]|\{\d+(?:,\d*)?\})\??/y, { in: REGEXP_BODY }], [OPERATOR, "|", { in: REGEXP_BODY }], [PUNCTUATION, /\((?:\?(?:[:=!]|<[=!]))?/y, { in: REGEXP_BODY }], [PUNCTUATION, /\(\?<(?=[\p{L}_$][\p{L}\d_$]*>)/uy, { in: REGEXP_BODY, push: REGEXP_GROUP_NAME }], [IDENTIFIER2, /[\p{L}_$][\p{L}\d_$]*/uy, { in: REGEXP_GROUP_NAME }], [PUNCTUATION, ">", { in: REGEXP_GROUP_NAME, pop: true }], [PUNCTUATION, ")", { in: REGEXP_BODY }], [PUNCTUATION, /\[(?!\^)/y, { in: REGEXP_BODY, push: REGEXP_SET }], [PUNCTUATION, /\[(?=\^)/y, { in: REGEXP_BODY, push: REGEXP_SET_NEGATION }], [OPERATOR, "^", { in: REGEXP_SET_NEGATION, pop: true, push: REGEXP_SET }], [OPERATOR, /(?<!(?<!\\)\[\^?)-(?!\])/y, { in: REGEXP_SET }], [PUNCTUATION, "]", { in: REGEXP_SET, pop: true }], [REGEXP, /[^\\/[()|?*+{^$.\n]+/uy, { in: REGEXP_BODY }], [REGEXP, /[^\\\]\-\n]+/uy, { in: REGEXP_SET }], [REGEXP, /[^\n]/uy, { in: [REGEXP_BODY, REGEXP_SET] }], [NUMBER, /0b[01_]+n?/i], [NUMBER, /0o[0-7_]+n?/i], [NUMBER, /0x[\da-f_]+n?/i], [NUMBER, /(?:\d[\d_]*(?:\.[\d_]+)?|\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?n?/i], [OPERATOR, "!"], [OPERATOR, "!="], [OPERATOR, "!=="], [OPERATOR, "%"], [OPERATOR, "%="], [OPERATOR, "&&"], [OPERATOR, "&&="], [OPERATOR, "&"], [OPERATOR, "&="], [OPERATOR, "*"], [OPERATOR, "**"], [OPERATOR, "**="], [OPERATOR, "*="], [OPERATOR, "+"], [OPERATOR, "++"], [OPERATOR, "+="], [OPERATOR, "-"], [OPERATOR, "--"], [OPERATOR, "-="], [OPERATOR, "..."], [OPERATOR, "/"], [OPERATOR, "/="], [OPERATOR, ":"], [OPERATOR, "<"], [OPERATOR, "<<"], [OPERATOR, "<<="], [OPERATOR, "<="], [OPERATOR, "="], [OPERATOR, "=="], [OPERATOR, "==="], [OPERATOR, "=>"], [OPERATOR, ">"], [OPERATOR, ">="], [OPERATOR, ">>"], [OPERATOR, ">>="], [OPERATOR, ">>>"], [OPERATOR, ">>>="], [OPERATOR, "?"], [OPERATOR, "??"], [OPERATOR, "??="], [OPERATOR, "^"], [OPERATOR, "^="], [OPERATOR, "|"], [OPERATOR, "|="], [OPERATOR, "||"], [OPERATOR, "||="], [OPERATOR, "~"], [OPERATOR, "~="], [OPERATOR, /(?<=\s):/], [PUNCTUATION, "("], [PUNCTUATION, ")"], [PUNCTUATION, ","], [PUNCTUATION, "."], [PUNCTUATION, ":"], [PUNCTUATION, ";"], [PUNCTUATION, /\?\.(?!\d)/], [PUNCTUATION, "["], [PUNCTUATION, "]"], [PUNCTUATION, "{", { push: ROOT2 }], [PUNCTUATION, "}", { pop: true }], [IDENTIFIER2, /(?<=(?<!\.)\.)\p{Ll}[\p{L}\d_$]*(?=[(`])/u], [IDENTIFIER4, /(?<=(?<!\.)\.)\p{Ll}[\p{L}\d_$]*/u], [KEYWORD1, "as"], [KEYWORD1, "async"], [KEYWORD1, "await"], [KEYWORD1, "break"], [KEYWORD1, "case"], [KEYWORD1, "catch"], [KEYWORD1, "class"], [KEYWORD1, "const"], [KEYWORD1, "continue"], [KEYWORD1, "debugger"], [KEYWORD1, "default"], [KEYWORD1, "delete"], [KEYWORD1, "do"], [KEYWORD1, "else"], [KEYWORD1, "export"], [KEYWORD1, "extends"], [KEYWORD1, "finally"], [KEYWORD1, "for"], [KEYWORD1, "from"], [KEYWORD1, "function"], [KEYWORD1, "function*"], [KEYWORD1, "goto"], [KEYWORD1, "if"], [KEYWORD1, "import"], [KEYWORD1, "in"], [KEYWORD1, "instanceof"], [KEYWORD1, "is"], [KEYWORD1, "keyof"], [KEYWORD1, "let"], [KEYWORD1, "new"], [KEYWORD1, "of"], [KEYWORD1, "package"], [KEYWORD1, "return"], [KEYWORD1, "super"], [KEYWORD1, "switch"], [KEYWORD1, "this"], [KEYWORD1, "throw"], [KEYWORD1, "try"], [KEYWORD1, "type"], [KEYWORD1, "typeof"], [KEYWORD1, "var"], [KEYWORD1, "void"], [KEYWORD1, "while"], [KEYWORD1, "with"], [KEYWORD1, "yield"], [KEYWORD1, "yield*"], [KEYWORD2, "false"], [KEYWORD2, "Infinity"], [KEYWORD2, "NaN"], [KEYWORD2, "null"], [KEYWORD2, "true"], [KEYWORD2, "undefined"], [KEYWORD3, "any"], [KEYWORD3, "bigint"], [KEYWORD3, "boolean"], [KEYWORD3, "eval"], [KEYWORD3, "number"], [KEYWORD3, "string"], [KEYWORD3, "symbol"], [KEYWORD3, "unknown"], [IDENTIFIER2, /@[\p{L}_$][\p{L}\d_$]*/u], [IDENTIFIER1, /#?[\p{Lu}_$][\p{Lu}\d_$]*/u], [IDENTIFIER2, /#?[\p{L}_$][\p{L}\d_$]*(?=[(`])/u], [IDENTIFIER3, /#?\p{Lu}[\p{L}\d_$]*/u], [IDENTIFIER4, /#?[\p{L}_$][\p{L}\d_$]*/u]);
290
300
  // src/nnn/rwd.ts
291
301
  var rwd = (root, selector, cellWidthPx, cellHeightPx, ...specs) => {
292
302
  const main = root[selector] ??= {};
package/package.json CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "name": "@jackens/nnn",
3
- "version": "2026.9.29",
3
+ "version": "2026.9.30",
4
4
  "description": "Jackens’ JavaScript helpers.",
5
5
  "keywords": [
6
6
  "CSS-in-JS",
7
+ "highlight",
7
8
  "HyperScript",
8
9
  "JS-to-CSS",
9
- "Monokai",
10
- "RWD",
11
- "Responsive Web Design",
12
- "SVG",
13
- "highlight",
14
10
  "lexer",
11
+ "Monokai",
15
12
  "nanolight",
16
13
  "nnn",
17
- "tokenize"
14
+ "Responsive Web Design",
15
+ "RWD",
16
+ "SVG",
17
+ "tokenize",
18
+ "tokenizer"
18
19
  ],
19
20
  "license": "MIT",
20
21
  "author": "Jackens",