@jackens/nnn 2026.9.28 → 2026.9.29

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 (4) hide show
  1. package/README.md +256 -126
  2. package/nnn.d.ts +41 -36
  3. package/nnn.js +7 -7
  4. package/package.json +3 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # nnn
2
2
 
3
- A collection of Jackens’ JavaScript helper utilities (version: `2026.9.28`).
3
+ A collection of Jackens’ JavaScript helper utilities (version: `2026.9.29`).
4
4
 
5
5
  ## Installation
6
6
 
@@ -21,10 +21,10 @@ import {
21
21
  CNode,
22
22
  HArgs,
23
23
  HArgs1,
24
- TokenizerSpec,
25
- TokenizerSpecOptions,
24
+ LexerSpec,
25
+ LexerSpecOptions,
26
26
  c,
27
- createTokenizer,
27
+ createLexer,
28
28
  h,
29
29
  monokai,
30
30
  nanolightTs,
@@ -42,26 +42,26 @@ import {
42
42
  Tuple argument type for the [`h`](#h) and [`s`](#s) helpers.
43
43
  - [`HArgs1`](#HArgs1):
44
44
  Single argument type for the [`h`](#h) and [`s`](#s) helpers.
45
- - [`TokenizerSpec`](#TokenizerSpec):
46
- A [`createTokenizer`](#createTokenizer) spec: a `[metadata, pattern, options?]` tuple.
47
- - [`TokenizerSpecOptions`](#TokenizerSpecOptions):
48
- Options of a [`createTokenizer`](#createTokenizer) spec controlling the tokenizer state stack.
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
49
  - [`c`](#c):
50
50
  A minimal CSS-in-JS helper that converts a JavaScript object hierarchy into a CSS string.
51
- - [`createTokenizer`](#createTokenizer):
52
- A helper for building simple stateful tokenizers (see also [`nanolightTs`](#nanolightTs)).
51
+ - [`createLexer`](#createLexer):
52
+ A helper for building simple stateful lexers (see also [`nanolightTs`](#nanolightTs)).
53
53
  - [`h`](#h):
54
54
  A lightweight _HyperScript_-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
55
55
  - [`monokai`](#monokai):
56
- A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) tokenizer.
56
+ A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) lexer.
57
57
  - [`nanolightTs`](#nanolightTs):
58
- A TypeScript/JavaScript syntax highlighting tokenizer built using [`createTokenizer`](#createTokenizer).
58
+ A TypeScript/JavaScript syntax highlighting lexer built using [`createLexer`](#createLexer).
59
59
  - [`rwd`](#rwd):
60
- A responsive web design helper that generates CSS rules for a grid-like layout.
60
+ A responsive web design helper that generates CSS rules for a grid-like layout of floating elements.
61
61
  - [`s`](#s):
62
62
  A lightweight _HyperScript_-style helper for creating and modifying `SVGElement`s (see also [`h`](#h)).
63
63
  - [`svgUse`](#svgUse):
64
- Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
64
+ Shorthand for creating an `<svg>` element with a `<use>` child referencing a symbol (e.g. an icon) by ID.
65
65
 
66
66
  ### CNode
67
67
 
@@ -89,25 +89,25 @@ type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string |
89
89
 
90
90
  Single argument type for the [`h`](#h) and [`s`](#s) helpers.
91
91
 
92
- ### TokenizerSpec
92
+ ### LexerSpec
93
93
 
94
94
  ```ts
95
- type TokenizerSpec<M> = [metadata: M, pattern: string | RegExp, options?: TokenizerSpecOptions]
95
+ type LexerSpec<M> = [metadata: M, pattern: string | RegExp, options?: LexerSpecOptions]
96
96
  ```
97
97
 
98
- A [`createTokenizer`](#createTokenizer) spec: a `[metadata, pattern, options?]` tuple.
98
+ A [`createLexer`](#createLexer) spec: a `[metadata, pattern, options?]` tuple.
99
99
 
100
- ### TokenizerSpecOptions
100
+ ### LexerSpecOptions
101
101
 
102
102
  ```ts
103
- type TokenizerSpecOptions = {
103
+ type LexerSpecOptions = {
104
104
  in?: string | string[]
105
105
  pop?: boolean
106
106
  push?: string
107
107
  }
108
108
  ```
109
109
 
110
- Options of a [`createTokenizer`](#createTokenizer) spec controlling the tokenizer state stack.
110
+ Options of a [`createLexer`](#createLexer) spec controlling the lexer state stack.
111
111
 
112
112
  #### in
113
113
 
@@ -133,12 +133,15 @@ A minimal CSS-in-JS helper that converts a JavaScript object hierarchy into a CS
133
133
 
134
134
  An object describing CSS rules.
135
135
  Keys are selectors or at-rules; values are either CSS property values or nested rule objects.
136
+ Nested selectors are appended to the parent selector as is (e.g. `'.c'` inside `a` → `a.c`), so a leading space is needed for a descendant combinator (e.g. `' .c'` → `a .c`).
136
137
  A selector key may contain comma-separated alternatives (e.g. `'.b,.c'`); when nested, each alternative is combined with each parent alternative (cartesian product), producing a comma-joined selector list (e.g. `'a.b,a.c'`).
138
+ Top-level at-rules (e.g. `'@media …'`) wrap their nested rules; inside a top-level `:root` rule nested keys are concatenated into property names (e.g. `{ ':root': { '--': { bg: 'red' } } }` → `:root{--bg:red}`).
139
+ `null`/`undefined` values are skipped.
137
140
 
138
141
  #### splitter
139
142
 
140
143
  A delimiter used to create unique keys (default: `'$$'`).
141
- The substring from `splitter` to the end of a key is ignored (e.g., `src$$1` → `src`).
144
+ The substring from `splitter` to the end of a key is ignored (e.g. `src$$1` → `src`).
142
145
 
143
146
  #### Returns
144
147
 
@@ -435,16 +438,16 @@ const expected = `
435
438
  expect(actual).toBe(expected)
436
439
  ```
437
440
 
438
- ### createTokenizer
441
+ ### createLexer
439
442
 
440
443
  ```ts
441
- const createTokenizer: <M, T>(
444
+ const createLexer: <M, T>(
442
445
  decorator: (chunk: string, metadata?: M) => T,
443
- ...specs: TokenizerSpec<M>[]
446
+ ...specs: LexerSpec<M>[]
444
447
  ) => (code: string) => T[]
445
448
  ```
446
449
 
447
- A helper for building simple stateful tokenizers (see also [`nanolightTs`](#nanolightTs)).
450
+ A helper for building simple stateful lexers (see also [`nanolightTs`](#nanolightTs)).
448
451
 
449
452
  #### Remarks
450
453
 
@@ -457,32 +460,32 @@ A helper for building simple stateful tokenizers (see also [`nanolightTs`](#nano
457
460
 
458
461
  #### decorator
459
462
 
460
- A function that wraps each matched chunk.
461
- It receives the matched text (`chunk`) and optionally the `metadata` associated with the pattern that produced the match.
462
- For unmatched text between patterns, `metadata` is `undefined`.
463
+ A function that wraps each chunk of the input.
464
+ It receives the matched text (`chunk`) and the `metadata` of the spec whose pattern produced the match.
465
+ For unmatched text between matches, `metadata` is `undefined`.
463
466
 
464
467
  #### specs
465
468
 
466
469
  An array of tuples `[metadata, pattern, options?]` where:
467
470
 
468
471
  - `metadata`: arbitrary data (e.g., a CSS class name) passed to `decorator` when the pattern matches.
469
- - `pattern`: a `string` or `RegExp` to match against the input.
470
- - `options`: optional [`TokenizerSpecOptions`](#TokenizerSpecOptions) (states in which the spec is active and state transitions).
472
+ - `pattern`: a `string` (matched literally) or `RegExp` to match against the input.
473
+ - `options`: optional [`LexerSpecOptions`](#LexerSpecOptions) (states in which the spec is active and state transitions).
471
474
 
472
475
  #### Returns
473
476
 
474
- A tokenizer function that accepts a code string and returns an array of decorated tokens.
477
+ A lexer function that accepts an input string and returns an array of decorated chunks covering the whole input (in order).
475
478
 
476
479
  #### Usage Examples
477
480
 
478
481
  ```ts
479
- const tokenizer = createTokenizer(
482
+ const lexer = createLexer(
480
483
  (chunk, metadata) => ({ chunk, metadata }),
481
484
  ['keyword', /\b(if|else|for)\b/],
482
485
  ['string', /"[^"]*"/]
483
486
  )
484
487
 
485
- const result = tokenizer('if "hello" else "world"')
488
+ const result = lexer('if "hello" else "world"')
486
489
 
487
490
  expect(result).toEqual([
488
491
  { chunk: 'if', metadata: 'keyword' },
@@ -496,25 +499,25 @@ expect(result).toEqual([
496
499
  ```
497
500
 
498
501
  ```ts
499
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['tag', 'BEGIN'], ['end', 'END'])
502
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['tag', 'BEGIN'], ['end', 'END'])
500
503
 
501
- const result = tokenizer('aBEGINbENDc')
504
+ const result = lexer('aBEGINbENDc')
502
505
 
503
506
  expect(result).toEqual(['undefined:a', 'tag:BEGIN', 'undefined:b', 'end:END', 'undefined:c'])
504
507
  ```
505
508
 
506
509
  ```ts
507
- const tokenizer = createTokenizer(chunk => chunk, ['test', /test/])
510
+ const lexer = createLexer(chunk => chunk, ['test', /test/])
508
511
 
509
- const result = tokenizer('')
512
+ const result = lexer('')
510
513
 
511
514
  expect(result).toEqual([])
512
515
  ```
513
516
 
514
517
  ```ts
515
- const tokenizer = createTokenizer((chunk, metadata) => ({ chunk, metadata }), ['start', /^test/])
518
+ const lexer = createLexer((chunk, metadata) => ({ chunk, metadata }), ['start', /^test/])
516
519
 
517
- const result = tokenizer('test here')
520
+ const result = lexer('test here')
518
521
 
519
522
  expect(result).toEqual([
520
523
  { chunk: 'test', metadata: 'start' },
@@ -523,90 +526,90 @@ expect(result).toEqual([
523
526
  ```
524
527
 
525
528
  ```ts
526
- const tokenizer = createTokenizer((chunk, metadata) => metadata, ['later', /x/], ['earlier', /y/])
529
+ const lexer = createLexer((chunk, metadata) => metadata, ['later', /x/], ['earlier', /y/])
527
530
 
528
- const result = tokenizer('yx')
531
+ const result = lexer('yx')
529
532
 
530
533
  expect(result).toEqual(['earlier', 'later'])
531
534
  ```
532
535
 
533
536
  ```ts
534
- const tokenizer = createTokenizer(chunk => chunk, ['short', 'a'], ['long', 'abc'])
537
+ const lexer = createLexer(chunk => chunk, ['short', 'a'], ['long', 'abc'])
535
538
 
536
- const result = tokenizer('abc')
539
+ const result = lexer('abc')
537
540
 
538
541
  expect(result).toEqual(['abc'])
539
542
  ```
540
543
 
541
544
  ```ts
542
- const tokenizer = createTokenizer(chunk => chunk, ['empty', ''], ['word', /\w+/])
545
+ const lexer = createLexer(chunk => chunk, ['empty', ''], ['word', /\w+/])
543
546
 
544
- const result = tokenizer('hello')
547
+ const result = lexer('hello')
545
548
 
546
549
  expect(result).toEqual(['hello'])
547
550
  ```
548
551
 
549
552
  ```ts
550
- const tokenizer = createTokenizer(chunk => chunk, ['test', /xyz/])
553
+ const lexer = createLexer(chunk => chunk, ['test', /xyz/])
551
554
 
552
- const result = tokenizer('abc')
555
+ const result = lexer('abc')
553
556
 
554
557
  expect(result).toEqual(['abc'])
555
558
  ```
556
559
 
557
560
  ```ts
558
- const tokenizer = createTokenizer((_chunk, metadata) => metadata, ['a', 'a'], ['b', 'b'])
561
+ const lexer = createLexer((_chunk, metadata) => metadata, ['a', 'a'], ['b', 'b'])
559
562
 
560
- const result = tokenizer('aabb')
563
+ const result = lexer('aabb')
561
564
 
562
565
  expect(result).toEqual(['a', 'a', 'b', 'b'])
563
566
  ```
564
567
 
565
568
  ```ts
566
- const tokenizer = createTokenizer(
569
+ const lexer = createLexer(
567
570
  (chunk, metadata) => `${metadata}:${chunk}`,
568
571
  ['open', '(', { push: 'inner' }],
569
572
  ['word', /\w+/, { in: 'inner' }],
570
573
  ['close', ')', { in: 'inner', pop: true }]
571
574
  )
572
575
 
573
- const result = tokenizer('a(b)c)')
576
+ const result = lexer('a(b)c)')
574
577
 
575
578
  expect(result).toEqual(['undefined:a', 'open:(', 'word:b', 'close:)', 'undefined:c)'])
576
579
  ```
577
580
 
578
581
  ```ts
579
- const tokenizer = createTokenizer(
582
+ const lexer = createLexer(
580
583
  (chunk, metadata) => `${metadata}:${chunk}`,
581
584
  ['open', '{', { in: ['root', 'nested'], push: 'nested' }],
582
585
  ['close', '}', { in: 'nested', pop: true }]
583
586
  )
584
587
 
585
- const result = tokenizer('{{}}}')
588
+ const result = lexer('{{}}}')
586
589
 
587
590
  expect(result).toEqual(['open:{', 'open:{', 'close:}', 'close:}', 'undefined:}'])
588
591
  ```
589
592
 
590
593
  ```ts
591
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['a', 'a'], ['b', /(?<=a)b/])
594
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['a', 'a'], ['b', /(?<=a)b/])
592
595
 
593
- const result = tokenizer('abcb')
596
+ const result = lexer('abcb')
594
597
 
595
598
  expect(result).toEqual(['a:a', 'b:b', 'undefined:cb'])
596
599
  ```
597
600
 
598
601
  ```ts
599
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['x', /x/y], ['y', 'y'])
602
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['x', /x/y], ['y', 'y'])
600
603
 
601
- const result = tokenizer('xyx.x')
604
+ const result = lexer('xyx.x')
602
605
 
603
606
  expect(result).toEqual(['x:x', 'y:y', 'x:x', 'undefined:.x'])
604
607
  ```
605
608
 
606
609
  ```ts
607
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['digits', /\d*/])
610
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['digits', /\d*/])
608
611
 
609
- const result = tokenizer('ab12')
612
+ const result = lexer('ab12')
610
613
 
611
614
  expect(result).toEqual(['undefined:ab', 'digits:12'])
612
615
  ```
@@ -615,9 +618,9 @@ expect(result).toEqual(['undefined:ab', 'digits:12'])
615
618
 
616
619
  ```ts
617
620
  const h: {
618
- <T extends keyof HTMLElementTagNameMap>(tag: T, ...args1: HArgs1[]): HTMLElementTagNameMap[T]
619
- <N extends Node>(node: N, ...args1: HArgs1[]): N
620
- (tagOrNode: string | Node, ...args1: HArgs1[]): Node
621
+ <T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs1[]): HTMLElementTagNameMap[T]
622
+ <N extends Node>(node: N, ...args: HArgs1[]): N
623
+ (tagOrNode: string | Node, ...args: HArgs1[]): Node
621
624
  }
622
625
  ```
623
626
 
@@ -633,17 +636,17 @@ If a `Node`, that node is modified in place.
633
636
  Additional arguments processed as follows:
634
637
 
635
638
  - `Object`: maps attributes/properties.
636
- Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it (useful e.g. for `$style`).
637
- Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
638
- A value of `false` removes the attribute.
639
+ - Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it (useful e.g. for `$style`).
640
+ - Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
641
+ A value of `true` sets an empty attribute (e.g. `checked=""`), a value of `false` removes the attribute.
639
642
  - `null`/`undefined`: ignored.
640
643
  - `Node`: appended as a child.
641
644
  - `string`/`number`: converted to a `Text` node and appended.
642
- - [`HArgs`](#HArgs) array: processed recursively.
645
+ - [`HArgs`](#HArgs) array: processed recursively (creates or modifies a child node and appends it).
643
646
 
644
647
  #### Returns
645
648
 
646
- The created or modified `HTMLElement`.
649
+ The created `HTMLElement` or the modified node.
647
650
 
648
651
  #### Usage Examples
649
652
 
@@ -753,7 +756,26 @@ expect(elemWithNested.outerHTML).toBe('<div><span>hello</span><b>world</b></div>
753
756
  const monokai: CNode
754
757
  ```
755
758
 
756
- A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) tokenizer.
759
+ A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) lexer.
760
+ It styles `pre` and `code>span` elements with the class names produced by [`nanolightTs`](#nanolightTs) (with light, dark and print variants).
761
+
762
+ #### Usage Examples
763
+
764
+ ```ts
765
+ const style = h('style', c(monokai))
766
+ const pre = h('pre', ['code', ...nanolightTs('const x = 42')])
767
+
768
+ expect(style.textContent).toContain('code>span.keyword1{color:var(--keyword1)}')
769
+ expect(style.textContent).toContain('@media only screen and (prefers-color-scheme: dark){:root{--bg:#2d2a2e;')
770
+ expect(pre.outerHTML).toBe(
771
+ '<pre><code>' +
772
+ '<span class="keyword1">const</span> ' +
773
+ '<span class="identifier4">x</span> ' +
774
+ '<span class="operator">=</span> ' +
775
+ '<span class="number">42</span>' +
776
+ '</code></pre>'
777
+ )
778
+ ```
757
779
 
758
780
  ### nanolightTs
759
781
 
@@ -761,7 +783,7 @@ A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanoligh
761
783
  const nanolightTs: (code: string) => HArgs1[]
762
784
  ```
763
785
 
764
- A TypeScript/JavaScript syntax highlighting tokenizer built using [`createTokenizer`](#createTokenizer).
786
+ A TypeScript/JavaScript syntax highlighting lexer built using [`createLexer`](#createLexer).
765
787
 
766
788
  #### code
767
789
 
@@ -769,7 +791,7 @@ The source code string to tokenize.
769
791
 
770
792
  #### Returns
771
793
 
772
- An array of [`HArgs1`](#HArgs1) elements suitable for rendering with [`h`](#h).
794
+ 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)).
773
795
 
774
796
  #### Usage Examples
775
797
 
@@ -800,77 +822,184 @@ expect(nanolightTs(codeJs)).toEqual([
800
822
  ```
801
823
 
802
824
  ```ts
803
- expect(classes('x = /a\\/b[/]c/gi.test(y) / 2')).toEqual([
804
- 'identifier4:x',
825
+ const codeJs = 'const x = 42 // answer'
826
+
827
+ expect(nanolightTs(codeJs)).toEqual([
828
+ ['span', { class: 'keyword1' }, 'const'],
805
829
  ' ',
806
- 'operator:=',
830
+ ['span', { class: 'identifier4' }, 'x'],
807
831
  ' ',
808
- 'string:/a\\/b[/]c/gi',
809
- 'punctuation:.',
810
- 'identifier2:test',
811
- 'punctuation:(',
812
- 'identifier4:y',
813
- 'punctuation:)',
832
+ ['span', { class: 'operator' }, '='],
814
833
  ' ',
815
- 'operator:/',
834
+ ['span', { class: 'number' }, '42'],
816
835
  ' ',
817
- 'number:2',
836
+ ['span', { class: 'comment' }, '// answer'],
818
837
  ])
838
+ ```
819
839
 
820
- expect(classes('a / b / c')).toEqual([
821
- 'identifier4:a',
840
+ ```ts
841
+ expect(nanolightTs('x = /a\\/b[/]c/gi.test(y) / 2')).toEqual([
842
+ ['span', { class: 'identifier4' }, 'x'],
843
+ ' ',
844
+ ['span', { class: 'operator' }, '='],
845
+ ' ',
846
+ ['span', { class: 'string' }, '/a\\/b[/]c/gi'],
847
+ ['span', { class: 'punctuation' }, '.'],
848
+ ['span', { class: 'identifier2' }, 'test'],
849
+ ['span', { class: 'punctuation' }, '('],
850
+ ['span', { class: 'identifier4' }, 'y'],
851
+ ['span', { class: 'punctuation' }, ')'],
852
+ ' ',
853
+ ['span', { class: 'operator' }, '/'],
854
+ ' ',
855
+ ['span', { class: 'number' }, '2'],
856
+ ])
857
+
858
+ expect(nanolightTs('a / b / c')).toEqual([
859
+ ['span', { class: 'identifier4' }, 'a'],
860
+ ' ',
861
+ ['span', { class: 'operator' }, '/'],
862
+ ' ',
863
+ ['span', { class: 'identifier4' }, 'b'],
864
+ ' ',
865
+ ['span', { class: 'operator' }, '/'],
866
+ ' ',
867
+ ['span', { class: 'identifier4' }, 'c'],
868
+ ])
869
+
870
+ expect(nanolightTs('return / x /')).toEqual([
871
+ ['span', { class: 'keyword1' }, 'return'],
872
+ ' ',
873
+ ['span', { class: 'string' }, '/ x /'],
874
+ ])
875
+ ```
876
+
877
+ ```ts
878
+ expect(nanolightTs('`a ${b + `c ${ {d}.d }`} \\${e} $f`')).toEqual([
879
+ ['span', { class: 'string' }, '`a '],
880
+ ['span', { class: 'punctuation' }, '${'],
881
+ ['span', { class: 'identifier4' }, 'b'],
882
+ ' ',
883
+ ['span', { class: 'operator' }, '+'],
884
+ ' ',
885
+ ['span', { class: 'string' }, '`c '],
886
+ ['span', { class: 'punctuation' }, '${'],
887
+ ' ',
888
+ ['span', { class: 'punctuation' }, '{'],
889
+ ['span', { class: 'identifier4' }, 'd'],
890
+ ['span', { class: 'punctuation' }, '}'],
891
+ ['span', { class: 'punctuation' }, '.'],
892
+ ['span', { class: 'identifier4' }, 'd'],
822
893
  ' ',
823
- 'operator:/',
894
+ ['span', { class: 'punctuation' }, '}'],
895
+ ['span', { class: 'string' }, '`'],
896
+ ['span', { class: 'punctuation' }, '}'],
897
+ ['span', { class: 'string' }, ' \\${e} $f`'],
898
+ ])
899
+
900
+ expect(nanolightTs('`${x}` / 2')).toEqual([
901
+ ['span', { class: 'string' }, '`'],
902
+ ['span', { class: 'punctuation' }, '${'],
903
+ ['span', { class: 'identifier4' }, 'x'],
904
+ ['span', { class: 'punctuation' }, '}'],
905
+ ['span', { class: 'string' }, '`'],
824
906
  ' ',
825
- 'identifier4:b',
907
+ ['span', { class: 'operator' }, '/'],
826
908
  ' ',
827
- 'operator:/',
909
+ ['span', { class: 'number' }, '2'],
910
+ ])
911
+ ```
912
+
913
+ ```ts
914
+ expect(nanolightTs('"a // b" // c')).toEqual([
915
+ ['span', { class: 'string' }, '"a // b"'],
828
916
  ' ',
829
- 'identifier4:c',
917
+ ['span', { class: 'comment' }, '// c'],
830
918
  ])
831
919
 
832
- expect(classes('return / x /')).toEqual(['keyword1:return', ' ', 'string:/ x /'])
920
+ expect(nanolightTs("'it\\'s' /* x")).toEqual([
921
+ ['span', { class: 'string' }, "'it\\'s'"],
922
+ ' ',
923
+ ['span', { class: 'comment' }, '/* x'],
924
+ ])
833
925
  ```
834
926
 
835
927
  ```ts
836
- expect(classes('`a ${b + `c ${ {d}.d }`} \\${e} $f`')).toEqual([
837
- 'string:`a ',
838
- 'punctuation:${',
839
- 'identifier4:b',
928
+ expect(nanolightTs('0xFF + 1_000n + .5e-3')).toEqual([
929
+ ['span', { class: 'number' }, '0xFF'],
840
930
  ' ',
841
- 'operator:+',
931
+ ['span', { class: 'operator' }, '+'],
842
932
  ' ',
843
- 'string:`c ',
844
- 'punctuation:${',
933
+ ['span', { class: 'number' }, '1_000n'],
845
934
  ' ',
846
- 'punctuation:{',
847
- 'identifier4:d',
848
- 'punctuation:}',
849
- 'punctuation:.',
850
- 'identifier4:d',
935
+ ['span', { class: 'operator' }, '+'],
851
936
  ' ',
852
- 'punctuation:}',
853
- 'string:`',
854
- 'punctuation:}',
855
- 'string: \\${e} $f`',
937
+ ['span', { class: 'number' }, '.5e-3'],
856
938
  ])
939
+ ```
857
940
 
858
- expect(classes('`${x}` / 2')).toEqual([
859
- 'string:`',
860
- 'punctuation:${',
861
- 'identifier4:x',
862
- 'punctuation:}',
863
- 'string:`',
941
+ ```ts
942
+ expect(nanolightTs('a++ / 2 / b')).toEqual([
943
+ ['span', { class: 'identifier4' }, 'a'],
944
+ ['span', { class: 'operator' }, '++'],
864
945
  ' ',
865
- 'operator:/',
946
+ ['span', { class: 'operator' }, '/'],
866
947
  ' ',
867
- 'number:2',
948
+ ['span', { class: 'number' }, '2'],
949
+ ' ',
950
+ ['span', { class: 'operator' }, '/'],
951
+ ' ',
952
+ ['span', { class: 'identifier4' }, 'b'],
953
+ ])
954
+
955
+ expect(nanolightTs('x?.5:a?.b')).toEqual([
956
+ ['span', { class: 'identifier4' }, 'x'],
957
+ ['span', { class: 'operator' }, '?'],
958
+ ['span', { class: 'number' }, '.5'],
959
+ ['span', { class: 'operator' }, ':'],
960
+ ['span', { class: 'identifier4' }, 'a'],
961
+ ['span', { class: 'punctuation' }, '?.'],
962
+ ['span', { class: 'identifier4' }, 'b'],
868
963
  ])
869
964
  ```
870
965
 
871
966
  ```ts
872
- expect(classes('"a // b" // c')).toEqual(['string:"a // b"', ' ', 'comment:// c'])
873
- expect(classes("'it\\'s' /* x")).toEqual(["string:'it\\'s'", ' ', 'comment:/* x'])
967
+ expect(nanolightTs('p.catch(obj.in, ...this)')).toEqual([
968
+ ['span', { class: 'identifier4' }, 'p'],
969
+ ['span', { class: 'punctuation' }, '.'],
970
+ ['span', { class: 'identifier2' }, 'catch'],
971
+ ['span', { class: 'punctuation' }, '('],
972
+ ['span', { class: 'identifier4' }, 'obj'],
973
+ ['span', { class: 'punctuation' }, '.'],
974
+ ['span', { class: 'identifier4' }, 'in'],
975
+ ['span', { class: 'punctuation' }, ','],
976
+ ' ',
977
+ ['span', { class: 'operator' }, '...'],
978
+ ['span', { class: 'keyword1' }, 'this'],
979
+ ['span', { class: 'punctuation' }, ')'],
980
+ ])
981
+ ```
982
+
983
+ ```ts
984
+ expect(nanolightTs('#!/usr/bin/env bun\n@sealed class A { #x = 1 }')).toEqual([
985
+ ['span', { class: 'comment' }, '#!/usr/bin/env bun'],
986
+ '\n',
987
+ ['span', { class: 'identifier2' }, '@sealed'],
988
+ ' ',
989
+ ['span', { class: 'keyword1' }, 'class'],
990
+ ' ',
991
+ ['span', { class: 'identifier1' }, 'A'],
992
+ ' ',
993
+ ['span', { class: 'punctuation' }, '{'],
994
+ ' ',
995
+ ['span', { class: 'identifier4' }, '#x'],
996
+ ' ',
997
+ ['span', { class: 'operator' }, '='],
998
+ ' ',
999
+ ['span', { class: 'number' }, '1'],
1000
+ ' ',
1001
+ ['span', { class: 'punctuation' }, '}'],
1002
+ ])
874
1003
  ```
875
1004
 
876
1005
  ### rwd
@@ -885,7 +1014,8 @@ const rwd: (
885
1014
  ) => void
886
1015
  ```
887
1016
 
888
- A responsive web design helper that generates CSS rules for a grid-like layout.
1017
+ A responsive web design helper that generates CSS rules for a grid-like layout of floating elements.
1018
+ It modifies `root` in place: it sets base styles for `selector` and adds a `@media(min-width:…px)` rule for each breakpoint.
889
1019
 
890
1020
  #### root
891
1021
 
@@ -907,7 +1037,7 @@ The base cell height in pixels.
907
1037
 
908
1038
  An array of breakpoint specifications, each a tuple of:
909
1039
 
910
- - `maxWidth`: maximum number of cells per row (defines the viewport breakpoint).
1040
+ - `maxWidth`: number of cells per row; the breakpoint is `min-width: cellWidthPx * maxWidth` (`1` modifies the base rule).
911
1041
  - `width` (optional, default `1`): number of horizontal cells the element spans.
912
1042
  - `height` (optional, default `1`): number of vertical cells the element spans.
913
1043
 
@@ -937,7 +1067,7 @@ expect(style).toEqual({
937
1067
  '.no-border': {
938
1068
  border: 'none',
939
1069
  },
940
- boxSizing: 'border-box',
1070
+ 'box-sizing': 'border-box',
941
1071
  display: 'block',
942
1072
  float: 'left',
943
1073
  width: '100%',
@@ -962,9 +1092,9 @@ expect(style).toEqual({
962
1092
 
963
1093
  ```ts
964
1094
  const s: {
965
- <T extends keyof SVGElementTagNameMap>(tag: T, ...args1: HArgs1[]): SVGElementTagNameMap[T]
966
- <N extends Node>(node: N, ...args1: HArgs1[]): N
967
- (tagOrNode: string | Node, ...args1: HArgs1[]): Node
1095
+ <T extends keyof SVGElementTagNameMap>(tag: T, ...args: HArgs1[]): SVGElementTagNameMap[T]
1096
+ <N extends Node>(node: N, ...args: HArgs1[]): N
1097
+ (tagOrNode: string | Node, ...args: HArgs1[]): Node
968
1098
  }
969
1099
  ```
970
1100
 
@@ -980,17 +1110,17 @@ If a `Node`, that node is modified in place.
980
1110
  Additional arguments processed as follows:
981
1111
 
982
1112
  - `Object`: maps attributes/properties.
983
- Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it.
984
- Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
985
- A value of `false` removes the attribute.
1113
+ - Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it.
1114
+ - Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
1115
+ A value of `true` sets an empty attribute (e.g. `checked=""`), a value of `false` removes the attribute.
986
1116
  - `null`/`undefined`: ignored.
987
1117
  - `Node`: appended as a child.
988
1118
  - `string`/`number`: converted to a `Text` node and appended.
989
- - [`HArgs`](#HArgs) array: processed recursively.
1119
+ - [`HArgs`](#HArgs) array: processed recursively (creates or modifies a child node and appends it).
990
1120
 
991
1121
  #### Returns
992
1122
 
993
- The created or modified `SVGElement`.
1123
+ The created `SVGElement` or the modified node.
994
1124
 
995
1125
  #### Usage Examples
996
1126
 
@@ -1024,7 +1154,7 @@ expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'title')).toBe('42')
1024
1154
  const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement
1025
1155
  ```
1026
1156
 
1027
- Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
1157
+ Shorthand for creating an `<svg>` element with a `<use>` child referencing a symbol (e.g. an icon) by ID.
1028
1158
 
1029
1159
  Equivalent to:
1030
1160
 
package/nnn.d.ts CHANGED
@@ -9,12 +9,15 @@ export type CNode = {
9
9
  *
10
10
  * An object describing CSS rules.
11
11
  * Keys are selectors or at-rules; values are either CSS property values or nested rule objects.
12
+ * Nested selectors are appended to the parent selector as is (e.g. `'.c'` inside `a` → `a.c`), so a leading space is needed for a descendant combinator (e.g. `' .c'` → `a .c`).
12
13
  * A selector key may contain comma-separated alternatives (e.g. `'.b,.c'`); when nested, each alternative is combined with each parent alternative (cartesian product), producing a comma-joined selector list (e.g. `'a.b,a.c'`).
14
+ * Top-level at-rules (e.g. `'@media …'`) wrap their nested rules; inside a top-level `:root` rule nested keys are concatenated into property names (e.g. `{ ':root': { '--': { bg: 'red' } } }` → `:root{--bg:red}`).
15
+ * `null`/`undefined` values are skipped.
13
16
  *
14
17
  * @param splitter
15
18
  *
16
19
  * A delimiter used to create unique keys (default: `'$$'`).
17
- * The substring from `splitter` to the end of a key is ignored (e.g., `src$$1` → `src`).
20
+ * The substring from `splitter` to the end of a key is ignored (e.g. `src$$1` → `src`).
18
21
  *
19
22
  * @returns
20
23
  *
@@ -22,7 +25,7 @@ export type CNode = {
22
25
  */
23
26
  export declare const c: (node: CNode, splitter?: string) => string
24
27
  /**
25
- * Options of a {@link createTokenizer} spec controlling the tokenizer state stack.
28
+ * Options of a {@link createLexer} spec controlling the lexer state stack.
26
29
  *
27
30
  * @property in
28
31
  *
@@ -36,15 +39,15 @@ export declare const c: (node: CNode, splitter?: string) => string
36
39
  *
37
40
  * State to push onto the stack after a match (applied after `pop`).
38
41
  */
39
- export type TokenizerSpecOptions = {
42
+ export type LexerSpecOptions = {
40
43
  in?: string | string[]
41
44
  pop?: boolean
42
45
  push?: string
43
46
  }
44
- /** A {@link createTokenizer} spec: a `[metadata, pattern, options?]` tuple. */
45
- export type TokenizerSpec<M> = [metadata: M, pattern: string | RegExp, options?: TokenizerSpecOptions]
47
+ /** A {@link createLexer} spec: a `[metadata, pattern, options?]` tuple. */
48
+ export type LexerSpec<M> = [metadata: M, pattern: string | RegExp, options?: LexerSpecOptions]
46
49
  /**
47
- * A helper for building simple stateful tokenizers (see also {@link nanolightTs}).
50
+ * A helper for building simple stateful lexers (see also {@link nanolightTs}).
48
51
  *
49
52
  * @remarks
50
53
  *
@@ -57,25 +60,25 @@ export type TokenizerSpec<M> = [metadata: M, pattern: string | RegExp, options?:
57
60
  *
58
61
  * @param decorator
59
62
  *
60
- * A function that wraps each matched chunk.
61
- * It receives the matched text (`chunk`) and optionally the `metadata` associated with the pattern that produced the match.
62
- * For unmatched text between patterns, `metadata` is `undefined`.
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`.
63
66
  *
64
67
  * @param specs
65
68
  *
66
69
  * An array of tuples `[metadata, pattern, options?]` where:
67
70
  *
68
71
  * - `metadata`: arbitrary data (e.g., a CSS class name) passed to `decorator` when the pattern matches.
69
- * - `pattern`: a `string` or `RegExp` to match against the input.
70
- * - `options`: optional {@link TokenizerSpecOptions} (states in which the spec is active and state transitions).
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).
71
74
  *
72
75
  * @returns
73
76
  *
74
- * A tokenizer function that accepts a code string and returns an array of decorated tokens.
77
+ * A lexer function that accepts an input string and returns an array of decorated chunks covering the whole input (in order).
75
78
  */
76
- export declare const createTokenizer: <M, T>(
79
+ export declare const createLexer: <M, T>(
77
80
  decorator: (chunk: string, metadata?: M) => T,
78
- ...specs: TokenizerSpec<M>[]
81
+ ...specs: LexerSpec<M>[]
79
82
  ) => (code: string) => T[]
80
83
  /** Single argument type for the {@link h} and {@link s} helpers. */
81
84
  export type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string | number | HArgs
@@ -94,22 +97,22 @@ export type HArgs = [string | Node, ...HArgs1[]]
94
97
  * Additional arguments processed as follows:
95
98
  *
96
99
  * - `Object`: maps attributes/properties.
97
- * Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it (useful e.g. for `$style`).
98
- * Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
99
- * A value of `false` removes the attribute.
100
+ * - Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it (useful e.g. for `$style`).
101
+ * - Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
102
+ * A value of `true` sets an empty attribute (e.g. `checked=""`), a value of `false` removes the attribute.
100
103
  * - `null`/`undefined`: ignored.
101
104
  * - `Node`: appended as a child.
102
105
  * - `string`/`number`: converted to a `Text` node and appended.
103
- * - {@link HArgs} array: processed recursively.
106
+ * - {@link HArgs} array: processed recursively (creates or modifies a child node and appends it).
104
107
  *
105
108
  * @returns
106
109
  *
107
- * The created or modified `HTMLElement`.
110
+ * The created `HTMLElement` or the modified node.
108
111
  */
109
112
  export declare const h: {
110
- <T extends keyof HTMLElementTagNameMap>(tag: T, ...args1: HArgs1[]): HTMLElementTagNameMap[T]
111
- <N extends Node>(node: N, ...args1: HArgs1[]): N
112
- (tagOrNode: string | Node, ...args1: HArgs1[]): Node
113
+ <T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs1[]): HTMLElementTagNameMap[T]
114
+ <N extends Node>(node: N, ...args: HArgs1[]): N
115
+ (tagOrNode: string | Node, ...args: HArgs1[]): Node
113
116
  }
114
117
  /**
115
118
  * A lightweight _HyperScript_-style helper for creating and modifying `SVGElement`s (see also {@link h}).
@@ -124,25 +127,25 @@ export declare const h: {
124
127
  * Additional arguments processed as follows:
125
128
  *
126
129
  * - `Object`: maps attributes/properties.
127
- * Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it.
128
- * Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
129
- * A value of `false` removes the attribute.
130
+ * - Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it.
131
+ * - Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
132
+ * A value of `true` sets an empty attribute (e.g. `checked=""`), a value of `false` removes the attribute.
130
133
  * - `null`/`undefined`: ignored.
131
134
  * - `Node`: appended as a child.
132
135
  * - `string`/`number`: converted to a `Text` node and appended.
133
- * - {@link HArgs} array: processed recursively.
136
+ * - {@link HArgs} array: processed recursively (creates or modifies a child node and appends it).
134
137
  *
135
138
  * @returns
136
139
  *
137
- * The created or modified `SVGElement`.
140
+ * The created `SVGElement` or the modified node.
138
141
  */
139
142
  export declare const s: {
140
- <T extends keyof SVGElementTagNameMap>(tag: T, ...args1: HArgs1[]): SVGElementTagNameMap[T]
141
- <N extends Node>(node: N, ...args1: HArgs1[]): N
142
- (tagOrNode: string | Node, ...args1: HArgs1[]): Node
143
+ <T extends keyof SVGElementTagNameMap>(tag: T, ...args: HArgs1[]): SVGElementTagNameMap[T]
144
+ <N extends Node>(node: N, ...args: HArgs1[]): N
145
+ (tagOrNode: string | Node, ...args: HArgs1[]): Node
143
146
  }
144
147
  /**
145
- * Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
148
+ * Shorthand for creating an `<svg>` element with a `<use>` child referencing a symbol (e.g. an icon) by ID.
146
149
  *
147
150
  * Equivalent to:
148
151
  *
@@ -164,11 +167,12 @@ export declare const s: {
164
167
  */
165
168
  export declare const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement
166
169
  /**
167
- * A Monokai-inspired color scheme for use with the {@link c} helper and {@link nanolightTs} tokenizer.
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).
168
172
  */
169
173
  export declare const monokai: CNode
170
174
  /**
171
- * A TypeScript/JavaScript syntax highlighting tokenizer built using {@link createTokenizer}.
175
+ * A TypeScript/JavaScript syntax highlighting lexer built using {@link createLexer}.
172
176
  *
173
177
  * @param code
174
178
  *
@@ -176,11 +180,12 @@ export declare const monokai: CNode
176
180
  *
177
181
  * @returns
178
182
  *
179
- * An array of {@link HArgs1} elements suitable for rendering with {@link h}.
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}).
180
184
  */
181
185
  export declare const nanolightTs: (code: string) => HArgs1[]
182
186
  /**
183
- * A responsive web design helper that generates CSS rules for a grid-like layout.
187
+ * A responsive web design helper that generates CSS rules for a grid-like layout of floating elements.
188
+ * It modifies `root` in place: it sets base styles for `selector` and adds a `@media(min-width:…px)` rule for each breakpoint.
184
189
  *
185
190
  * @param root
186
191
  *
@@ -202,7 +207,7 @@ export declare const nanolightTs: (code: string) => HArgs1[]
202
207
  *
203
208
  * An array of breakpoint specifications, each a tuple of:
204
209
  *
205
- * - `maxWidth`: maximum number of cells per row (defines the viewport breakpoint).
210
+ * - `maxWidth`: number of cells per row; the breakpoint is `min-width: cellWidthPx * maxWidth` (`1` modifies the base rule).
206
211
  * - `width` (optional, default `1`): number of horizontal cells the element spans.
207
212
  * - `height` (optional, default `1`): number of vertical cells the element spans.
208
213
  */
package/nnn.js CHANGED
@@ -50,9 +50,9 @@ 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/createTokenizer.ts
53
+ // src/nnn/createLexer.ts
54
54
  var ROOT = "root";
55
- var createTokenizer = (decorator, ...specs) => {
55
+ var createLexer = (decorator, ...specs) => {
56
56
  const compiled = specs.map(([metadata, pattern, options]) => ({
57
57
  metadata,
58
58
  pattern: pattern instanceof RegExp ? new RegExp(pattern.source, pattern.sticky ? pattern.flags : pattern.flags.replace("g", "") + "g") : pattern,
@@ -283,14 +283,14 @@ var PUNCTUATION = "punctuation";
283
283
  var STRING = "string";
284
284
  var ROOT2 = "root";
285
285
  var TEMPLATE = "template";
286
- var nanolightTs = /* @__PURE__ */ createTokenizer((chunk, name) => name != null ? ["span", { class: name }, chunk] : chunk, [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 }], [
286
+ 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
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_]+/], [NUMBER, /0o[01234567_]+/], [NUMBER, /0x[\dabcdef_]+/], [NUMBER, /\d[\d_]*(\.[\d_]+)?(e[+-]?[\d_]+)?/], [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, "?."], [PUNCTUATION, "["], [PUNCTUATION, "]"], [PUNCTUATION, "{", { push: ROOT2 }], [PUNCTUATION, "}", { pop: true }], [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"], [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]);
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]);
290
290
  // src/nnn/rwd.ts
291
291
  var rwd = (root, selector, cellWidthPx, cellHeightPx, ...specs) => {
292
292
  const main = root[selector] ??= {};
293
- main.boxSizing = "border-box";
293
+ main["box-sizing"] = "border-box";
294
294
  main.display = "block";
295
295
  main.float = "left";
296
296
  main.width = "100%";
@@ -313,7 +313,7 @@ var rwd = (root, selector, cellWidthPx, cellHeightPx, ...specs) => {
313
313
  };
314
314
  export {
315
315
  c,
316
- createTokenizer,
316
+ createLexer,
317
317
  h,
318
318
  monokai,
319
319
  nanolightTs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackens/nnn",
3
- "version": "2026.9.28",
3
+ "version": "2026.9.29",
4
4
  "description": "Jackens’ JavaScript helpers.",
5
5
  "keywords": [
6
6
  "CSS-in-JS",
@@ -11,10 +11,10 @@
11
11
  "Responsive Web Design",
12
12
  "SVG",
13
13
  "highlight",
14
+ "lexer",
14
15
  "nanolight",
15
16
  "nnn",
16
- "tokenize",
17
- "tokenizer"
17
+ "tokenize"
18
18
  ],
19
19
  "license": "MIT",
20
20
  "author": "Jackens",