@jackens/nnn 2026.9.30 → 2026.10.1
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 +1 -604
- package/nnn.d.ts +0 -73
- package/nnn.js +0 -193
- package/package.json +2 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nnn
|
|
2
2
|
|
|
3
|
-
A collection of Jackens’ JavaScript helper utilities (version: `2026.
|
|
3
|
+
A collection of Jackens’ JavaScript helper utilities (version: `2026.10.1`).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -21,13 +21,8 @@ import {
|
|
|
21
21
|
CNode,
|
|
22
22
|
HArgs,
|
|
23
23
|
HArgs1,
|
|
24
|
-
LexerSpec,
|
|
25
|
-
LexerSpecOptions,
|
|
26
24
|
c,
|
|
27
|
-
createLexer,
|
|
28
25
|
h,
|
|
29
|
-
monokai,
|
|
30
|
-
nanolightTs,
|
|
31
26
|
rwd,
|
|
32
27
|
s,
|
|
33
28
|
svgUse,
|
|
@@ -42,20 +37,10 @@ import {
|
|
|
42
37
|
Tuple argument type for the [`h`](#h) and [`s`](#s) helpers.
|
|
43
38
|
- [`HArgs1`](#HArgs1):
|
|
44
39
|
Single argument type for the [`h`](#h) and [`s`](#s) helpers.
|
|
45
|
-
- [`LexerSpec`](#LexerSpec):
|
|
46
|
-
A [`createLexer`](#createLexer) spec: a `[metadata, pattern, options?]` tuple.
|
|
47
|
-
- [`LexerSpecOptions`](#LexerSpecOptions):
|
|
48
|
-
Options of a [`createLexer`](#createLexer) spec controlling the lexer state stack.
|
|
49
40
|
- [`c`](#c):
|
|
50
41
|
A minimal CSS-in-JS helper that converts a JavaScript object hierarchy into a CSS string.
|
|
51
|
-
- [`createLexer`](#createLexer):
|
|
52
|
-
A helper for building simple stateful lexers (see also [`nanolightTs`](#nanolightTs)).
|
|
53
42
|
- [`h`](#h):
|
|
54
43
|
A lightweight _HyperScript_-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
|
|
55
|
-
- [`monokai`](#monokai):
|
|
56
|
-
A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) lexer.
|
|
57
|
-
- [`nanolightTs`](#nanolightTs):
|
|
58
|
-
A TypeScript/JavaScript syntax highlighting lexer built using [`createLexer`](#createLexer).
|
|
59
44
|
- [`rwd`](#rwd):
|
|
60
45
|
A responsive web design helper that generates CSS rules for a grid-like layout of floating elements.
|
|
61
46
|
- [`s`](#s):
|
|
@@ -89,38 +74,6 @@ type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string |
|
|
|
89
74
|
|
|
90
75
|
Single argument type for the [`h`](#h) and [`s`](#s) helpers.
|
|
91
76
|
|
|
92
|
-
### LexerSpec
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
type LexerSpec<M> = [metadata: M, pattern: string | RegExp, options?: LexerSpecOptions]
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
A [`createLexer`](#createLexer) spec: a `[metadata, pattern, options?]` tuple.
|
|
99
|
-
|
|
100
|
-
### LexerSpecOptions
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
type LexerSpecOptions = {
|
|
104
|
-
in?: string | string[]
|
|
105
|
-
pop?: boolean
|
|
106
|
-
push?: string
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Options of a [`createLexer`](#createLexer) spec controlling the lexer state stack.
|
|
111
|
-
|
|
112
|
-
#### in
|
|
113
|
-
|
|
114
|
-
State(s) in which the spec is active (default: `'root'`).
|
|
115
|
-
|
|
116
|
-
#### pop
|
|
117
|
-
|
|
118
|
-
Whether to pop the current state off the stack after a match (the `'root'` state at the bottom is never popped).
|
|
119
|
-
|
|
120
|
-
#### push
|
|
121
|
-
|
|
122
|
-
State to push onto the stack after a match (applied after `pop`).
|
|
123
|
-
|
|
124
77
|
### c
|
|
125
78
|
|
|
126
79
|
```ts
|
|
@@ -452,210 +405,6 @@ const expected = `
|
|
|
452
405
|
expect(actual).toBe(expected)
|
|
453
406
|
```
|
|
454
407
|
|
|
455
|
-
### createLexer
|
|
456
|
-
|
|
457
|
-
```ts
|
|
458
|
-
const createLexer: <M, T>(
|
|
459
|
-
decorator: (chunk: string, metadata?: M) => T,
|
|
460
|
-
...specs: LexerSpec<M>[]
|
|
461
|
-
) => (code: string) => T[]
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
A helper for building simple stateful lexers (see also [`nanolightTs`](#nanolightTs)).
|
|
465
|
-
|
|
466
|
-
#### Remarks
|
|
467
|
-
|
|
468
|
-
1. Matches starting at an earlier position take precedence.
|
|
469
|
-
2. Among matches at the same position, the longer one wins.
|
|
470
|
-
3. Among matches of the same position and length, the one defined earlier wins.
|
|
471
|
-
4. Only specs active in the current state (the top of the state stack, initially `'root'`) are considered.
|
|
472
|
-
5. Patterns are matched against the whole input, so `^`, `$`, `\b` and lookbehinds see the text around the current position; a sticky (`y`) `RegExp` matches only at the current position.
|
|
473
|
-
6. Empty matches are ignored.
|
|
474
|
-
|
|
475
|
-
#### decorator
|
|
476
|
-
|
|
477
|
-
A function that wraps each chunk of the input.
|
|
478
|
-
It receives the matched text (`chunk`) and the `metadata` of the spec whose pattern produced the match.
|
|
479
|
-
For unmatched text between matches, `metadata` is `undefined`.
|
|
480
|
-
|
|
481
|
-
#### specs
|
|
482
|
-
|
|
483
|
-
An array of tuples `[metadata, pattern, options?]` where:
|
|
484
|
-
|
|
485
|
-
- `metadata`: arbitrary data (e.g., a CSS class name) passed to `decorator` when the pattern matches.
|
|
486
|
-
- `pattern`: a `string` (matched literally) or `RegExp` to match against the input.
|
|
487
|
-
- `options`: optional [`LexerSpecOptions`](#LexerSpecOptions) (states in which the spec is active and state transitions).
|
|
488
|
-
|
|
489
|
-
#### Returns
|
|
490
|
-
|
|
491
|
-
A lexer function that accepts an input string and returns an array of decorated chunks covering the whole input (in order).
|
|
492
|
-
|
|
493
|
-
#### Usage Examples
|
|
494
|
-
|
|
495
|
-
Tokenizing keywords and strings with `RegExp` patterns:
|
|
496
|
-
|
|
497
|
-
```ts
|
|
498
|
-
const lexer = createLexer(
|
|
499
|
-
(chunk, metadata) => ({ chunk, metadata }),
|
|
500
|
-
['keyword', /\b(if|else|for)\b/],
|
|
501
|
-
['string', /"[^"]*"/]
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
const result = lexer('if "hello" else "world"')
|
|
505
|
-
|
|
506
|
-
expect(result).toEqual([
|
|
507
|
-
{ chunk: 'if', metadata: 'keyword' },
|
|
508
|
-
{ chunk: ' ', metadata: undefined },
|
|
509
|
-
{ chunk: '"hello"', metadata: 'string' },
|
|
510
|
-
{ chunk: ' ', metadata: undefined },
|
|
511
|
-
{ chunk: 'else', metadata: 'keyword' },
|
|
512
|
-
{ chunk: ' ', metadata: undefined },
|
|
513
|
-
{ chunk: '"world"', metadata: 'string' },
|
|
514
|
-
])
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
String patterns are matched literally and unmatched text gets `undefined` metadata:
|
|
518
|
-
|
|
519
|
-
```ts
|
|
520
|
-
const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['tag', 'BEGIN'], ['end', 'END'])
|
|
521
|
-
|
|
522
|
-
const result = lexer('aBEGINbENDc')
|
|
523
|
-
|
|
524
|
-
expect(result).toEqual(['undefined:a', 'tag:BEGIN', 'undefined:b', 'end:END', 'undefined:c'])
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
Empty input produces an empty result:
|
|
528
|
-
|
|
529
|
-
```ts
|
|
530
|
-
const lexer = createLexer(chunk => chunk, ['test', /test/])
|
|
531
|
-
|
|
532
|
-
const result = lexer('')
|
|
533
|
-
|
|
534
|
-
expect(result).toEqual([])
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
`^` anchors to the start of the whole input:
|
|
538
|
-
|
|
539
|
-
```ts
|
|
540
|
-
const lexer = createLexer((chunk, metadata) => ({ chunk, metadata }), ['start', /^test/])
|
|
541
|
-
|
|
542
|
-
const result = lexer('test here')
|
|
543
|
-
|
|
544
|
-
expect(result).toEqual([
|
|
545
|
-
{ chunk: 'test', metadata: 'start' },
|
|
546
|
-
{ chunk: ' here', metadata: undefined },
|
|
547
|
-
])
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
Matches starting earlier take precedence regardless of spec order:
|
|
551
|
-
|
|
552
|
-
```ts
|
|
553
|
-
const lexer = createLexer((chunk, metadata) => metadata, ['later', /x/], ['earlier', /y/])
|
|
554
|
-
|
|
555
|
-
const result = lexer('yx')
|
|
556
|
-
|
|
557
|
-
expect(result).toEqual(['earlier', 'later'])
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
The longer match wins at the same position:
|
|
561
|
-
|
|
562
|
-
```ts
|
|
563
|
-
const lexer = createLexer(chunk => chunk, ['short', 'a'], ['long', 'abc'])
|
|
564
|
-
|
|
565
|
-
const result = lexer('abc')
|
|
566
|
-
|
|
567
|
-
expect(result).toEqual(['abc'])
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
Empty string patterns are ignored:
|
|
571
|
-
|
|
572
|
-
```ts
|
|
573
|
-
const lexer = createLexer(chunk => chunk, ['empty', ''], ['word', /\w+/])
|
|
574
|
-
|
|
575
|
-
const result = lexer('hello')
|
|
576
|
-
|
|
577
|
-
expect(result).toEqual(['hello'])
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
Input without any match is returned as a single chunk:
|
|
581
|
-
|
|
582
|
-
```ts
|
|
583
|
-
const lexer = createLexer(chunk => chunk, ['test', /xyz/])
|
|
584
|
-
|
|
585
|
-
const result = lexer('abc')
|
|
586
|
-
|
|
587
|
-
expect(result).toEqual(['abc'])
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
Consecutive matches are returned as separate chunks:
|
|
591
|
-
|
|
592
|
-
```ts
|
|
593
|
-
const lexer = createLexer((_chunk, metadata) => metadata, ['a', 'a'], ['b', 'b'])
|
|
594
|
-
|
|
595
|
-
const result = lexer('aabb')
|
|
596
|
-
|
|
597
|
-
expect(result).toEqual(['a', 'a', 'b', 'b'])
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
Entering and leaving states with `push` and `pop`:
|
|
601
|
-
|
|
602
|
-
```ts
|
|
603
|
-
const lexer = createLexer(
|
|
604
|
-
(chunk, metadata) => `${metadata}:${chunk}`,
|
|
605
|
-
['open', '(', { push: 'inner' }],
|
|
606
|
-
['word', /\w+/, { in: 'inner' }],
|
|
607
|
-
['close', ')', { in: 'inner', pop: true }]
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
const result = lexer('a(b)c)')
|
|
611
|
-
|
|
612
|
-
expect(result).toEqual(['undefined:a', 'open:(', 'word:b', 'close:)', 'undefined:c)'])
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
Nested states with a spec active in multiple states:
|
|
616
|
-
|
|
617
|
-
```ts
|
|
618
|
-
const lexer = createLexer(
|
|
619
|
-
(chunk, metadata) => `${metadata}:${chunk}`,
|
|
620
|
-
['open', '{', { in: ['root', 'nested'], push: 'nested' }],
|
|
621
|
-
['close', '}', { in: 'nested', pop: true }]
|
|
622
|
-
)
|
|
623
|
-
|
|
624
|
-
const result = lexer('{{}}}')
|
|
625
|
-
|
|
626
|
-
expect(result).toEqual(['open:{', 'open:{', 'close:}', 'close:}', 'undefined:}'])
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
Lookbehinds see the text preceding the current position:
|
|
630
|
-
|
|
631
|
-
```ts
|
|
632
|
-
const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['a', 'a'], ['b', /(?<=a)b/])
|
|
633
|
-
|
|
634
|
-
const result = lexer('abcb')
|
|
635
|
-
|
|
636
|
-
expect(result).toEqual(['a:a', 'b:b', 'undefined:cb'])
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
Sticky patterns match only at the current position:
|
|
640
|
-
|
|
641
|
-
```ts
|
|
642
|
-
const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['x', /x/y], ['y', 'y'])
|
|
643
|
-
|
|
644
|
-
const result = lexer('xyx.x')
|
|
645
|
-
|
|
646
|
-
expect(result).toEqual(['x:x', 'y:y', 'x:x', 'undefined:.x'])
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
Empty `RegExp` matches are skipped:
|
|
650
|
-
|
|
651
|
-
```ts
|
|
652
|
-
const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['digits', /\d*/])
|
|
653
|
-
|
|
654
|
-
const result = lexer('ab12')
|
|
655
|
-
|
|
656
|
-
expect(result).toEqual(['undefined:ab', 'digits:12'])
|
|
657
|
-
```
|
|
658
|
-
|
|
659
408
|
### h
|
|
660
409
|
|
|
661
410
|
```ts
|
|
@@ -806,358 +555,6 @@ const elemWithNested = h('div', ['span', 'hello'], ['b', 'world'])
|
|
|
806
555
|
expect(elemWithNested.outerHTML).toBe('<div><span>hello</span><b>world</b></div>')
|
|
807
556
|
```
|
|
808
557
|
|
|
809
|
-
### monokai
|
|
810
|
-
|
|
811
|
-
```ts
|
|
812
|
-
const monokai: CNode
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) lexer.
|
|
816
|
-
It styles `pre` and `code>span` elements with the class names produced by [`nanolightTs`](#nanolightTs) (with light, dark and print variants).
|
|
817
|
-
|
|
818
|
-
#### Usage Examples
|
|
819
|
-
|
|
820
|
-
Styling `nanolightTs` output:
|
|
821
|
-
|
|
822
|
-
```ts
|
|
823
|
-
const style = h('style', c(monokai))
|
|
824
|
-
const pre = h('pre', ['code', ...nanolightTs('const x = 42')])
|
|
825
|
-
|
|
826
|
-
expect(style.textContent).toContain('code>span.keyword1{color:var(--keyword1)}')
|
|
827
|
-
expect(style.textContent).toContain('code>span.regexp{color:var(--regexp)}')
|
|
828
|
-
expect(style.textContent).toContain('@media only screen and (prefers-color-scheme: dark){:root{--bg:#2d2a2e;')
|
|
829
|
-
expect(pre.outerHTML).toBe(
|
|
830
|
-
'<pre><code>' +
|
|
831
|
-
'<span class="keyword1">const</span> ' +
|
|
832
|
-
'<span class="identifier4">x</span> ' +
|
|
833
|
-
'<span class="operator">=</span> ' +
|
|
834
|
-
'<span class="number">42</span>' +
|
|
835
|
-
'</code></pre>'
|
|
836
|
-
)
|
|
837
|
-
```
|
|
838
|
-
|
|
839
|
-
### nanolightTs
|
|
840
|
-
|
|
841
|
-
```ts
|
|
842
|
-
const nanolightTs: (code: string) => HArgs1[]
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
A TypeScript/JavaScript syntax highlighting lexer built using [`createLexer`](#createLexer).
|
|
846
|
-
|
|
847
|
-
#### code
|
|
848
|
-
|
|
849
|
-
The source code string to tokenize.
|
|
850
|
-
|
|
851
|
-
#### Returns
|
|
852
|
-
|
|
853
|
-
An array of [`HArgs1`](#HArgs1) elements suitable for rendering with [`h`](#h): plain `string`s for unrecognized text (e.g. whitespace) and `['span', { class }, chunk]` tuples for recognized tokens (class names are styled by [`monokai`](#monokai)).
|
|
854
|
-
|
|
855
|
-
#### Usage Examples
|
|
856
|
-
|
|
857
|
-
Keywords, identifiers, operators, numbers, punctuation, strings and comments:
|
|
858
|
-
|
|
859
|
-
```ts
|
|
860
|
-
const codeJs = 'const answerToLifeTheUniverseAndEverything = ' + "{ 42: 42 }['42'] /* 42 */"
|
|
861
|
-
|
|
862
|
-
expect(nanolightTs(codeJs)).toEqual([
|
|
863
|
-
['span', { class: 'keyword1' }, 'const'],
|
|
864
|
-
' ',
|
|
865
|
-
['span', { class: 'identifier4' }, 'answerToLifeTheUniverseAndEverything'],
|
|
866
|
-
' ',
|
|
867
|
-
['span', { class: 'operator' }, '='],
|
|
868
|
-
' ',
|
|
869
|
-
['span', { class: 'punctuation' }, '{'],
|
|
870
|
-
' ',
|
|
871
|
-
['span', { class: 'number' }, '42'],
|
|
872
|
-
['span', { class: 'operator' }, ':'],
|
|
873
|
-
' ',
|
|
874
|
-
['span', { class: 'number' }, '42'],
|
|
875
|
-
' ',
|
|
876
|
-
['span', { class: 'punctuation' }, '}'],
|
|
877
|
-
['span', { class: 'punctuation' }, '['],
|
|
878
|
-
['span', { class: 'string' }, "'42'"],
|
|
879
|
-
['span', { class: 'punctuation' }, ']'],
|
|
880
|
-
' ',
|
|
881
|
-
['span', { class: 'comment' }, '/* 42 */'],
|
|
882
|
-
])
|
|
883
|
-
```
|
|
884
|
-
|
|
885
|
-
Line comments:
|
|
886
|
-
|
|
887
|
-
```ts
|
|
888
|
-
const codeJs = 'const x = 42 // answer'
|
|
889
|
-
|
|
890
|
-
expect(nanolightTs(codeJs)).toEqual([
|
|
891
|
-
['span', { class: 'keyword1' }, 'const'],
|
|
892
|
-
' ',
|
|
893
|
-
['span', { class: 'identifier4' }, 'x'],
|
|
894
|
-
' ',
|
|
895
|
-
['span', { class: 'operator' }, '='],
|
|
896
|
-
' ',
|
|
897
|
-
['span', { class: 'number' }, '42'],
|
|
898
|
-
' ',
|
|
899
|
-
['span', { class: 'comment' }, '// answer'],
|
|
900
|
-
])
|
|
901
|
-
```
|
|
902
|
-
|
|
903
|
-
Regex literals vs division:
|
|
904
|
-
|
|
905
|
-
```ts
|
|
906
|
-
expect(nanolightTs('x = /a\\/b[/]c/gi.test(y) / 2')).toEqual([
|
|
907
|
-
['span', { class: 'identifier4' }, 'x'],
|
|
908
|
-
' ',
|
|
909
|
-
['span', { class: 'operator' }, '='],
|
|
910
|
-
' ',
|
|
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'],
|
|
921
|
-
['span', { class: 'punctuation' }, '.'],
|
|
922
|
-
['span', { class: 'identifier2' }, 'test'],
|
|
923
|
-
['span', { class: 'punctuation' }, '('],
|
|
924
|
-
['span', { class: 'identifier4' }, 'y'],
|
|
925
|
-
['span', { class: 'punctuation' }, ')'],
|
|
926
|
-
' ',
|
|
927
|
-
['span', { class: 'operator' }, '/'],
|
|
928
|
-
' ',
|
|
929
|
-
['span', { class: 'number' }, '2'],
|
|
930
|
-
])
|
|
931
|
-
|
|
932
|
-
expect(nanolightTs('a / b / c')).toEqual([
|
|
933
|
-
['span', { class: 'identifier4' }, 'a'],
|
|
934
|
-
' ',
|
|
935
|
-
['span', { class: 'operator' }, '/'],
|
|
936
|
-
' ',
|
|
937
|
-
['span', { class: 'identifier4' }, 'b'],
|
|
938
|
-
' ',
|
|
939
|
-
['span', { class: 'operator' }, '/'],
|
|
940
|
-
' ',
|
|
941
|
-
['span', { class: 'identifier4' }, 'c'],
|
|
942
|
-
])
|
|
943
|
-
|
|
944
|
-
expect(nanolightTs('return / x /')).toEqual([
|
|
945
|
-
['span', { class: 'keyword1' }, 'return'],
|
|
946
|
-
' ',
|
|
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'],
|
|
960
|
-
])
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
Nested template literals with substitutions:
|
|
964
|
-
|
|
965
|
-
```ts
|
|
966
|
-
expect(nanolightTs('`a ${b + `c ${ {d}.d }`} \\${e} $f`')).toEqual([
|
|
967
|
-
['span', { class: 'string' }, '`a '],
|
|
968
|
-
['span', { class: 'punctuation' }, '${'],
|
|
969
|
-
['span', { class: 'identifier4' }, 'b'],
|
|
970
|
-
' ',
|
|
971
|
-
['span', { class: 'operator' }, '+'],
|
|
972
|
-
' ',
|
|
973
|
-
['span', { class: 'string' }, '`c '],
|
|
974
|
-
['span', { class: 'punctuation' }, '${'],
|
|
975
|
-
' ',
|
|
976
|
-
['span', { class: 'punctuation' }, '{'],
|
|
977
|
-
['span', { class: 'identifier4' }, 'd'],
|
|
978
|
-
['span', { class: 'punctuation' }, '}'],
|
|
979
|
-
['span', { class: 'punctuation' }, '.'],
|
|
980
|
-
['span', { class: 'identifier4' }, 'd'],
|
|
981
|
-
' ',
|
|
982
|
-
['span', { class: 'punctuation' }, '}'],
|
|
983
|
-
['span', { class: 'string' }, '`'],
|
|
984
|
-
['span', { class: 'punctuation' }, '}'],
|
|
985
|
-
['span', { class: 'string' }, ' \\${e} $f`'],
|
|
986
|
-
])
|
|
987
|
-
|
|
988
|
-
expect(nanolightTs('`${x}` / 2')).toEqual([
|
|
989
|
-
['span', { class: 'string' }, '`'],
|
|
990
|
-
['span', { class: 'punctuation' }, '${'],
|
|
991
|
-
['span', { class: 'identifier4' }, 'x'],
|
|
992
|
-
['span', { class: 'punctuation' }, '}'],
|
|
993
|
-
['span', { class: 'string' }, '`'],
|
|
994
|
-
' ',
|
|
995
|
-
['span', { class: 'operator' }, '/'],
|
|
996
|
-
' ',
|
|
997
|
-
['span', { class: 'number' }, '2'],
|
|
998
|
-
])
|
|
999
|
-
```
|
|
1000
|
-
|
|
1001
|
-
Comment markers inside strings and unterminated comments:
|
|
1002
|
-
|
|
1003
|
-
```ts
|
|
1004
|
-
expect(nanolightTs('"a // b" // c')).toEqual([
|
|
1005
|
-
['span', { class: 'string' }, '"a // b"'],
|
|
1006
|
-
' ',
|
|
1007
|
-
['span', { class: 'comment' }, '// c'],
|
|
1008
|
-
])
|
|
1009
|
-
|
|
1010
|
-
expect(nanolightTs("'it\\'s' /* x")).toEqual([
|
|
1011
|
-
['span', { class: 'string' }, "'it\\'s'"],
|
|
1012
|
-
' ',
|
|
1013
|
-
['span', { class: 'comment' }, '/* x'],
|
|
1014
|
-
])
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
Hexadecimal, BigInt, numeric separators and exponents:
|
|
1018
|
-
|
|
1019
|
-
```ts
|
|
1020
|
-
expect(nanolightTs('0xFF + 1_000n + .5e-3')).toEqual([
|
|
1021
|
-
['span', { class: 'number' }, '0xFF'],
|
|
1022
|
-
' ',
|
|
1023
|
-
['span', { class: 'operator' }, '+'],
|
|
1024
|
-
' ',
|
|
1025
|
-
['span', { class: 'number' }, '1_000n'],
|
|
1026
|
-
' ',
|
|
1027
|
-
['span', { class: 'operator' }, '+'],
|
|
1028
|
-
' ',
|
|
1029
|
-
['span', { class: 'number' }, '.5e-3'],
|
|
1030
|
-
])
|
|
1031
|
-
```
|
|
1032
|
-
|
|
1033
|
-
Division after `++` and optional chaining vs ternary with decimals:
|
|
1034
|
-
|
|
1035
|
-
```ts
|
|
1036
|
-
expect(nanolightTs('a++ / 2 / b')).toEqual([
|
|
1037
|
-
['span', { class: 'identifier4' }, 'a'],
|
|
1038
|
-
['span', { class: 'operator' }, '++'],
|
|
1039
|
-
' ',
|
|
1040
|
-
['span', { class: 'operator' }, '/'],
|
|
1041
|
-
' ',
|
|
1042
|
-
['span', { class: 'number' }, '2'],
|
|
1043
|
-
' ',
|
|
1044
|
-
['span', { class: 'operator' }, '/'],
|
|
1045
|
-
' ',
|
|
1046
|
-
['span', { class: 'identifier4' }, 'b'],
|
|
1047
|
-
])
|
|
1048
|
-
|
|
1049
|
-
expect(nanolightTs('x?.5:a?.b')).toEqual([
|
|
1050
|
-
['span', { class: 'identifier4' }, 'x'],
|
|
1051
|
-
['span', { class: 'operator' }, '?'],
|
|
1052
|
-
['span', { class: 'number' }, '.5'],
|
|
1053
|
-
['span', { class: 'operator' }, ':'],
|
|
1054
|
-
['span', { class: 'identifier4' }, 'a'],
|
|
1055
|
-
['span', { class: 'punctuation' }, '?.'],
|
|
1056
|
-
['span', { class: 'identifier4' }, 'b'],
|
|
1057
|
-
])
|
|
1058
|
-
```
|
|
1059
|
-
|
|
1060
|
-
Keywords as property names and method calls:
|
|
1061
|
-
|
|
1062
|
-
```ts
|
|
1063
|
-
expect(nanolightTs('p.catch(obj.in, ...this)')).toEqual([
|
|
1064
|
-
['span', { class: 'identifier4' }, 'p'],
|
|
1065
|
-
['span', { class: 'punctuation' }, '.'],
|
|
1066
|
-
['span', { class: 'identifier2' }, 'catch'],
|
|
1067
|
-
['span', { class: 'punctuation' }, '('],
|
|
1068
|
-
['span', { class: 'identifier4' }, 'obj'],
|
|
1069
|
-
['span', { class: 'punctuation' }, '.'],
|
|
1070
|
-
['span', { class: 'identifier4' }, 'in'],
|
|
1071
|
-
['span', { class: 'punctuation' }, ','],
|
|
1072
|
-
' ',
|
|
1073
|
-
['span', { class: 'operator' }, '...'],
|
|
1074
|
-
['span', { class: 'keyword1' }, 'this'],
|
|
1075
|
-
['span', { class: 'punctuation' }, ')'],
|
|
1076
|
-
])
|
|
1077
|
-
```
|
|
1078
|
-
|
|
1079
|
-
Hashbang, decorators and private class members:
|
|
1080
|
-
|
|
1081
|
-
```ts
|
|
1082
|
-
expect(nanolightTs('#!/usr/bin/env bun\n@sealed class A { #x = 1 }')).toEqual([
|
|
1083
|
-
['span', { class: 'comment' }, '#!/usr/bin/env bun'],
|
|
1084
|
-
'\n',
|
|
1085
|
-
['span', { class: 'identifier2' }, '@sealed'],
|
|
1086
|
-
' ',
|
|
1087
|
-
['span', { class: 'keyword1' }, 'class'],
|
|
1088
|
-
' ',
|
|
1089
|
-
['span', { class: 'identifier1' }, 'A'],
|
|
1090
|
-
' ',
|
|
1091
|
-
['span', { class: 'punctuation' }, '{'],
|
|
1092
|
-
' ',
|
|
1093
|
-
['span', { class: 'identifier4' }, '#x'],
|
|
1094
|
-
' ',
|
|
1095
|
-
['span', { class: 'operator' }, '='],
|
|
1096
|
-
' ',
|
|
1097
|
-
['span', { class: 'number' }, '1'],
|
|
1098
|
-
' ',
|
|
1099
|
-
['span', { class: 'punctuation' }, '}'],
|
|
1100
|
-
])
|
|
1101
|
-
```
|
|
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
|
-
|
|
1161
558
|
### rwd
|
|
1162
559
|
|
|
1163
560
|
```ts
|
package/nnn.d.ts
CHANGED
|
@@ -24,62 +24,6 @@ export type CNode = {
|
|
|
24
24
|
* A CSS string representing the compiled rules.
|
|
25
25
|
*/
|
|
26
26
|
export declare const c: (node: CNode, splitter?: string) => string
|
|
27
|
-
/**
|
|
28
|
-
* Options of a {@link createLexer} spec controlling the lexer state stack.
|
|
29
|
-
*
|
|
30
|
-
* @property in
|
|
31
|
-
*
|
|
32
|
-
* State(s) in which the spec is active (default: `'root'`).
|
|
33
|
-
*
|
|
34
|
-
* @property pop
|
|
35
|
-
*
|
|
36
|
-
* Whether to pop the current state off the stack after a match (the `'root'` state at the bottom is never popped).
|
|
37
|
-
*
|
|
38
|
-
* @property push
|
|
39
|
-
*
|
|
40
|
-
* State to push onto the stack after a match (applied after `pop`).
|
|
41
|
-
*/
|
|
42
|
-
export type LexerSpecOptions = {
|
|
43
|
-
in?: string | string[]
|
|
44
|
-
pop?: boolean
|
|
45
|
-
push?: string
|
|
46
|
-
}
|
|
47
|
-
/** A {@link createLexer} spec: a `[metadata, pattern, options?]` tuple. */
|
|
48
|
-
export type LexerSpec<M> = [metadata: M, pattern: string | RegExp, options?: LexerSpecOptions]
|
|
49
|
-
/**
|
|
50
|
-
* A helper for building simple stateful lexers (see also {@link nanolightTs}).
|
|
51
|
-
*
|
|
52
|
-
* @remarks
|
|
53
|
-
*
|
|
54
|
-
* 1. Matches starting at an earlier position take precedence.
|
|
55
|
-
* 2. Among matches at the same position, the longer one wins.
|
|
56
|
-
* 3. Among matches of the same position and length, the one defined earlier wins.
|
|
57
|
-
* 4. Only specs active in the current state (the top of the state stack, initially `'root'`) are considered.
|
|
58
|
-
* 5. Patterns are matched against the whole input, so `^`, `$`, `\b` and lookbehinds see the text around the current position; a sticky (`y`) `RegExp` matches only at the current position.
|
|
59
|
-
* 6. Empty matches are ignored.
|
|
60
|
-
*
|
|
61
|
-
* @param decorator
|
|
62
|
-
*
|
|
63
|
-
* A function that wraps each chunk of the input.
|
|
64
|
-
* It receives the matched text (`chunk`) and the `metadata` of the spec whose pattern produced the match.
|
|
65
|
-
* For unmatched text between matches, `metadata` is `undefined`.
|
|
66
|
-
*
|
|
67
|
-
* @param specs
|
|
68
|
-
*
|
|
69
|
-
* An array of tuples `[metadata, pattern, options?]` where:
|
|
70
|
-
*
|
|
71
|
-
* - `metadata`: arbitrary data (e.g., a CSS class name) passed to `decorator` when the pattern matches.
|
|
72
|
-
* - `pattern`: a `string` (matched literally) or `RegExp` to match against the input.
|
|
73
|
-
* - `options`: optional {@link LexerSpecOptions} (states in which the spec is active and state transitions).
|
|
74
|
-
*
|
|
75
|
-
* @returns
|
|
76
|
-
*
|
|
77
|
-
* A lexer function that accepts an input string and returns an array of decorated chunks covering the whole input (in order).
|
|
78
|
-
*/
|
|
79
|
-
export declare const createLexer: <M, T>(
|
|
80
|
-
decorator: (chunk: string, metadata?: M) => T,
|
|
81
|
-
...specs: LexerSpec<M>[]
|
|
82
|
-
) => (code: string) => T[]
|
|
83
27
|
/** Single argument type for the {@link h} and {@link s} helpers. */
|
|
84
28
|
export type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string | number | HArgs
|
|
85
29
|
/** Tuple argument type for the {@link h} and {@link s} helpers. */
|
|
@@ -166,23 +110,6 @@ export declare const s: {
|
|
|
166
110
|
* An `SVGSVGElement` containing a `<use>` element.
|
|
167
111
|
*/
|
|
168
112
|
export declare const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement
|
|
169
|
-
/**
|
|
170
|
-
* A Monokai-inspired color scheme for use with the {@link c} helper and {@link nanolightTs} lexer.
|
|
171
|
-
* It styles `pre` and `code>span` elements with the class names produced by {@link nanolightTs} (with light, dark and print variants).
|
|
172
|
-
*/
|
|
173
|
-
export declare const monokai: CNode
|
|
174
|
-
/**
|
|
175
|
-
* A TypeScript/JavaScript syntax highlighting lexer built using {@link createLexer}.
|
|
176
|
-
*
|
|
177
|
-
* @param code
|
|
178
|
-
*
|
|
179
|
-
* The source code string to tokenize.
|
|
180
|
-
*
|
|
181
|
-
* @returns
|
|
182
|
-
*
|
|
183
|
-
* An array of {@link HArgs1} elements suitable for rendering with {@link h}: plain `string`s for unrecognized text (e.g. whitespace) and `['span', { class }, chunk]` tuples for recognized tokens (class names are styled by {@link monokai}).
|
|
184
|
-
*/
|
|
185
|
-
export declare const nanolightTs: (code: string) => HArgs1[]
|
|
186
113
|
/**
|
|
187
114
|
* A responsive web design helper that generates CSS rules for a grid-like layout of floating elements.
|
|
188
115
|
* It modifies `root` in place: it sets base styles for `selector` and adds a `@media(min-width:…px)` rule for each breakpoint.
|
package/nnn.js
CHANGED
|
@@ -50,79 +50,6 @@ var _c = (node, splitter = DEFAULT_SPLITTER, prefix = "", flatten = false) => {
|
|
|
50
50
|
return out.join(flatten ? ";" : "");
|
|
51
51
|
};
|
|
52
52
|
var c = (node, splitter = DEFAULT_SPLITTER) => _c(node, splitter);
|
|
53
|
-
// src/nnn/createLexer.ts
|
|
54
|
-
var ROOT = "root";
|
|
55
|
-
var createLexer = (decorator, ...specs) => {
|
|
56
|
-
const compiled = specs.map(([metadata, pattern, options]) => ({
|
|
57
|
-
metadata,
|
|
58
|
-
pattern: pattern instanceof RegExp ? new RegExp(pattern.source, pattern.sticky ? pattern.flags : pattern.flags.replace("g", "") + "g") : pattern,
|
|
59
|
-
states: [options?.in ?? ROOT].flat(),
|
|
60
|
-
pop: options?.pop ?? false,
|
|
61
|
-
push: options?.push
|
|
62
|
-
}));
|
|
63
|
-
const find = (pattern, code, position) => {
|
|
64
|
-
if (typeof pattern === "string") {
|
|
65
|
-
const index = pattern.length > 0 ? code.indexOf(pattern, position) : -1;
|
|
66
|
-
return [index, pattern];
|
|
67
|
-
}
|
|
68
|
-
pattern.lastIndex = position;
|
|
69
|
-
for (let match = pattern.exec(code);match != null; match = pattern.exec(code)) {
|
|
70
|
-
if (match[0].length > 0) {
|
|
71
|
-
return [match.index, match[0]];
|
|
72
|
-
}
|
|
73
|
-
if (pattern.sticky) {
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
pattern.lastIndex = match.index + 1;
|
|
77
|
-
}
|
|
78
|
-
return [-1, ""];
|
|
79
|
-
};
|
|
80
|
-
return (code) => {
|
|
81
|
-
const result = [];
|
|
82
|
-
const stack = [ROOT];
|
|
83
|
-
const cache = compiled.map(() => ({ from: -1, index: -1, chunk: "" }));
|
|
84
|
-
let position = 0;
|
|
85
|
-
while (position < code.length) {
|
|
86
|
-
const state = stack.at(-1);
|
|
87
|
-
let best = -1;
|
|
88
|
-
let bestIndex = Infinity;
|
|
89
|
-
let bestChunk = "";
|
|
90
|
-
compiled.forEach(({ pattern, states }, i) => {
|
|
91
|
-
if (!states.includes(state)) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const cached = cache[i];
|
|
95
|
-
const isValid = cached.from >= 0 && (pattern instanceof RegExp && pattern.sticky ? cached.from === position : cached.from <= position && (cached.index < 0 || cached.index >= position));
|
|
96
|
-
if (!isValid) {
|
|
97
|
-
[cached.index, cached.chunk] = find(pattern, code, position);
|
|
98
|
-
cached.from = position;
|
|
99
|
-
}
|
|
100
|
-
if (cached.index >= 0 && (cached.index < bestIndex || cached.index === bestIndex && cached.chunk.length > bestChunk.length)) {
|
|
101
|
-
best = i;
|
|
102
|
-
bestIndex = cached.index;
|
|
103
|
-
bestChunk = cached.chunk;
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
if (best < 0) {
|
|
107
|
-
result.push(decorator(code.slice(position)));
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
const { metadata, pop, push } = compiled[best];
|
|
111
|
-
if (bestIndex > position) {
|
|
112
|
-
result.push(decorator(code.slice(position, bestIndex)));
|
|
113
|
-
}
|
|
114
|
-
result.push(decorator(bestChunk, metadata));
|
|
115
|
-
position = bestIndex + bestChunk.length;
|
|
116
|
-
if (pop && stack.length > 1) {
|
|
117
|
-
stack.pop();
|
|
118
|
-
}
|
|
119
|
-
if (push != null) {
|
|
120
|
-
stack.push(push);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return result;
|
|
124
|
-
};
|
|
125
|
-
};
|
|
126
53
|
// src/nnn/h.ts
|
|
127
54
|
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
128
55
|
var _h = (createElement, tagOrNode, ...args) => {
|
|
@@ -180,123 +107,6 @@ var _h = (createElement, tagOrNode, ...args) => {
|
|
|
180
107
|
var h = _h.bind(null, (tag) => document.createElement(tag));
|
|
181
108
|
var s = _h.bind(null, (tag) => document.createElementNS("http://www.w3.org/2000/svg", tag));
|
|
182
109
|
var svgUse = (id, ...args) => s("svg", ["use", { "xlink:href": "#" + id }], ...args);
|
|
183
|
-
// src/nnn/monokai.ts
|
|
184
|
-
var monokai = {
|
|
185
|
-
":root$$monokai": {
|
|
186
|
-
"--": {
|
|
187
|
-
bg: "#faf4f2",
|
|
188
|
-
fg: "#29242a",
|
|
189
|
-
comment: "#918c8e",
|
|
190
|
-
identifier: {
|
|
191
|
-
1: "#7058be",
|
|
192
|
-
2: "#269d69",
|
|
193
|
-
3: "#1c8ca8",
|
|
194
|
-
4: "#29242a"
|
|
195
|
-
},
|
|
196
|
-
keyword: {
|
|
197
|
-
1: "#e14775",
|
|
198
|
-
2: "#7058be",
|
|
199
|
-
3: "#1c8ca8"
|
|
200
|
-
},
|
|
201
|
-
number: "#7058be",
|
|
202
|
-
operator: "#e14775",
|
|
203
|
-
punctuation: "#918c8e",
|
|
204
|
-
regexp: "#e16032",
|
|
205
|
-
string: "#cc7a0a"
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
pre$$monokai: {
|
|
209
|
-
"background-color": "var(--bg)",
|
|
210
|
-
color: "var(--fg)",
|
|
211
|
-
margin: 0,
|
|
212
|
-
padding: "1em",
|
|
213
|
-
overflow: "visible",
|
|
214
|
-
" code": {
|
|
215
|
-
"font-family": '"Source Code Pro"',
|
|
216
|
-
padding: 0
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
"code>span.$$monokai": {
|
|
220
|
-
bg: { color: "var(--bg)" },
|
|
221
|
-
fg: { color: "var(--fg)" },
|
|
222
|
-
comment: { color: "var(--comment)" },
|
|
223
|
-
identifier: {
|
|
224
|
-
1: { color: "var(--identifier1)" },
|
|
225
|
-
2: { color: "var(--identifier2)" },
|
|
226
|
-
3: { color: "var(--identifier3)" },
|
|
227
|
-
4: { color: "var(--identifier4)" }
|
|
228
|
-
},
|
|
229
|
-
keyword: {
|
|
230
|
-
1: { color: "var(--keyword1)" },
|
|
231
|
-
2: { color: "var(--keyword2)" },
|
|
232
|
-
3: { color: "var(--keyword3)" }
|
|
233
|
-
},
|
|
234
|
-
number: { color: "var(--number)" },
|
|
235
|
-
operator: { color: "var(--operator)" },
|
|
236
|
-
punctuation: { color: "var(--punctuation)" },
|
|
237
|
-
regexp: { color: "var(--regexp)" },
|
|
238
|
-
string: { color: "var(--string)" }
|
|
239
|
-
},
|
|
240
|
-
"@media only screen and (prefers-color-scheme: dark)$$monokai": {
|
|
241
|
-
":root": {
|
|
242
|
-
"--": {
|
|
243
|
-
bg: "#2d2a2e",
|
|
244
|
-
fg: "#fcfcfa",
|
|
245
|
-
comment: "#727072",
|
|
246
|
-
identifier: {
|
|
247
|
-
1: "#ae81ff",
|
|
248
|
-
2: "#a9dc76",
|
|
249
|
-
3: "#66d9ef",
|
|
250
|
-
4: "#fcfcfa"
|
|
251
|
-
},
|
|
252
|
-
keyword: {
|
|
253
|
-
1: "#ff6188",
|
|
254
|
-
2: "#ae81ff",
|
|
255
|
-
3: "#66d9ef"
|
|
256
|
-
},
|
|
257
|
-
number: "#ae81ff",
|
|
258
|
-
operator: "#ff6188",
|
|
259
|
-
punctuation: "#727072",
|
|
260
|
-
regexp: "#fc9867",
|
|
261
|
-
string: "#ffd866"
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
"@media print$$monokai": {
|
|
266
|
-
pre: {
|
|
267
|
-
"background-color": "white"
|
|
268
|
-
},
|
|
269
|
-
":root": {
|
|
270
|
-
"--bg": "white"
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
};
|
|
274
|
-
// src/nnn/nanolightTs.ts
|
|
275
|
-
var COMMENT = "comment";
|
|
276
|
-
var IDENTIFIER1 = "identifier1";
|
|
277
|
-
var IDENTIFIER2 = "identifier2";
|
|
278
|
-
var IDENTIFIER3 = "identifier3";
|
|
279
|
-
var IDENTIFIER4 = "identifier4";
|
|
280
|
-
var KEYWORD1 = "keyword1";
|
|
281
|
-
var KEYWORD2 = "keyword2";
|
|
282
|
-
var KEYWORD3 = "keyword3";
|
|
283
|
-
var NUMBER = "number";
|
|
284
|
-
var OPERATOR = "operator";
|
|
285
|
-
var PUNCTUATION = "punctuation";
|
|
286
|
-
var REGEXP = "regexp";
|
|
287
|
-
var STRING = "string";
|
|
288
|
-
var ROOT2 = "root";
|
|
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";
|
|
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 }], [
|
|
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]);
|
|
300
110
|
// src/nnn/rwd.ts
|
|
301
111
|
var rwd = (root, selector, cellWidthPx, cellHeightPx, ...specs) => {
|
|
302
112
|
const main = root[selector] ??= {};
|
|
@@ -323,10 +133,7 @@ var rwd = (root, selector, cellWidthPx, cellHeightPx, ...specs) => {
|
|
|
323
133
|
};
|
|
324
134
|
export {
|
|
325
135
|
c,
|
|
326
|
-
createLexer,
|
|
327
136
|
h,
|
|
328
|
-
monokai,
|
|
329
|
-
nanolightTs,
|
|
330
137
|
rwd,
|
|
331
138
|
s,
|
|
332
139
|
svgUse
|
package/package.json
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackens/nnn",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.10.1",
|
|
4
4
|
"description": "Jackens’ JavaScript helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CSS-in-JS",
|
|
7
|
-
"highlight",
|
|
8
7
|
"HyperScript",
|
|
9
8
|
"JS-to-CSS",
|
|
10
|
-
"lexer",
|
|
11
|
-
"Monokai",
|
|
12
|
-
"nanolight",
|
|
13
9
|
"nnn",
|
|
14
10
|
"Responsive Web Design",
|
|
15
|
-
"RWD"
|
|
16
|
-
"SVG",
|
|
17
|
-
"tokenize",
|
|
18
|
-
"tokenizer"
|
|
11
|
+
"RWD"
|
|
19
12
|
],
|
|
20
13
|
"license": "MIT",
|
|
21
14
|
"author": "Jackens",
|