@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.
- package/README.md +171 -3
- package/nnn.js +13 -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.
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
288
|
-
/(?<=(?:^|[!%&(*,:;<=>?[^{|}~]|(?<!\+)\+|(?<!-)-|(?<!\.)\b(?:await|case|delete|do|else|in|instanceof|new|of|return|throw|typeof|void|yield))\s*)\/(?![*/])(?:[^\\/[\n]|\\.|\[(?:[^\\\]\n]|\\.)*\]
|
|
289
|
-
|
|
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.
|
|
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
|
-
"
|
|
14
|
+
"Responsive Web Design",
|
|
15
|
+
"RWD",
|
|
16
|
+
"SVG",
|
|
17
|
+
"tokenize",
|
|
18
|
+
"tokenizer"
|
|
18
19
|
],
|
|
19
20
|
"license": "MIT",
|
|
20
21
|
"author": "Jackens",
|