@jackens/nnn 2026.9.28 → 2026.9.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +424 -126
  2. package/nnn.d.ts +41 -36
  3. package/nnn.js +18 -8
  4. package/package.json +6 -5
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.30`).
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
 
@@ -146,6 +149,8 @@ A CSS string representing the compiled rules.
146
149
 
147
150
  #### Usage Examples
148
151
 
152
+ Nested selectors split a rule while preserving property order:
153
+
149
154
  ```ts
150
155
  const actual = c({
151
156
  a: {
@@ -173,6 +178,8 @@ const expected = `
173
178
  expect(actual).toBe(expected)
174
179
  ```
175
180
 
181
+ Selectors can be nested at any depth:
182
+
176
183
  ```ts
177
184
  const actual = c({
178
185
  a: {
@@ -202,6 +209,8 @@ const expected = `
202
209
  expect(actual).toBe(expected)
203
210
  ```
204
211
 
212
+ At-rules, keyframes, media queries and repeated keys with the `$$` suffix:
213
+
205
214
  ```ts
206
215
  const actual = c({
207
216
  '@font-face$$1': {
@@ -277,6 +286,8 @@ const expected = `
277
286
  expect(actual).toBe(expected)
278
287
  ```
279
288
 
289
+ Comma-separated nested selectors are expanded:
290
+
280
291
  ```ts
281
292
  const actual = c({
282
293
  a: {
@@ -301,6 +312,8 @@ const expected = `
301
312
  expect(actual).toBe(expected)
302
313
  ```
303
314
 
315
+ Comma-separated top-level selectors are expanded in nested rules:
316
+
304
317
  ```ts
305
318
  const actual = c({
306
319
  '.b,.c': {
@@ -323,6 +336,8 @@ const expected = `
323
336
  expect(actual).toBe(expected)
324
337
  ```
325
338
 
339
+ Comma-separated selectors at both levels produce all combinations:
340
+
326
341
  ```ts
327
342
  const actual = c({
328
343
  '.a,.b': {
@@ -345,6 +360,8 @@ const expected = `
345
360
  expect(actual).toBe(expected)
346
361
  ```
347
362
 
363
+ CSS custom properties from nested `--` objects:
364
+
348
365
  ```ts
349
366
  const actual = c({
350
367
  ':root$$monokai': {
@@ -435,16 +452,16 @@ const expected = `
435
452
  expect(actual).toBe(expected)
436
453
  ```
437
454
 
438
- ### createTokenizer
455
+ ### createLexer
439
456
 
440
457
  ```ts
441
- const createTokenizer: <M, T>(
458
+ const createLexer: <M, T>(
442
459
  decorator: (chunk: string, metadata?: M) => T,
443
- ...specs: TokenizerSpec<M>[]
460
+ ...specs: LexerSpec<M>[]
444
461
  ) => (code: string) => T[]
445
462
  ```
446
463
 
447
- A helper for building simple stateful tokenizers (see also [`nanolightTs`](#nanolightTs)).
464
+ A helper for building simple stateful lexers (see also [`nanolightTs`](#nanolightTs)).
448
465
 
449
466
  #### Remarks
450
467
 
@@ -457,32 +474,34 @@ A helper for building simple stateful tokenizers (see also [`nanolightTs`](#nano
457
474
 
458
475
  #### decorator
459
476
 
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`.
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`.
463
480
 
464
481
  #### specs
465
482
 
466
483
  An array of tuples `[metadata, pattern, options?]` where:
467
484
 
468
485
  - `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).
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).
471
488
 
472
489
  #### Returns
473
490
 
474
- A tokenizer function that accepts a code string and returns an array of decorated tokens.
491
+ A lexer function that accepts an input string and returns an array of decorated chunks covering the whole input (in order).
475
492
 
476
493
  #### Usage Examples
477
494
 
495
+ Tokenizing keywords and strings with `RegExp` patterns:
496
+
478
497
  ```ts
479
- const tokenizer = createTokenizer(
498
+ const lexer = createLexer(
480
499
  (chunk, metadata) => ({ chunk, metadata }),
481
500
  ['keyword', /\b(if|else|for)\b/],
482
501
  ['string', /"[^"]*"/]
483
502
  )
484
503
 
485
- const result = tokenizer('if "hello" else "world"')
504
+ const result = lexer('if "hello" else "world"')
486
505
 
487
506
  expect(result).toEqual([
488
507
  { chunk: 'if', metadata: 'keyword' },
@@ -495,26 +514,32 @@ expect(result).toEqual([
495
514
  ])
496
515
  ```
497
516
 
517
+ String patterns are matched literally and unmatched text gets `undefined` metadata:
518
+
498
519
  ```ts
499
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['tag', 'BEGIN'], ['end', 'END'])
520
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['tag', 'BEGIN'], ['end', 'END'])
500
521
 
501
- const result = tokenizer('aBEGINbENDc')
522
+ const result = lexer('aBEGINbENDc')
502
523
 
503
524
  expect(result).toEqual(['undefined:a', 'tag:BEGIN', 'undefined:b', 'end:END', 'undefined:c'])
504
525
  ```
505
526
 
527
+ Empty input produces an empty result:
528
+
506
529
  ```ts
507
- const tokenizer = createTokenizer(chunk => chunk, ['test', /test/])
530
+ const lexer = createLexer(chunk => chunk, ['test', /test/])
508
531
 
509
- const result = tokenizer('')
532
+ const result = lexer('')
510
533
 
511
534
  expect(result).toEqual([])
512
535
  ```
513
536
 
537
+ `^` anchors to the start of the whole input:
538
+
514
539
  ```ts
515
- const tokenizer = createTokenizer((chunk, metadata) => ({ chunk, metadata }), ['start', /^test/])
540
+ const lexer = createLexer((chunk, metadata) => ({ chunk, metadata }), ['start', /^test/])
516
541
 
517
- const result = tokenizer('test here')
542
+ const result = lexer('test here')
518
543
 
519
544
  expect(result).toEqual([
520
545
  { chunk: 'test', metadata: 'start' },
@@ -522,91 +547,111 @@ expect(result).toEqual([
522
547
  ])
523
548
  ```
524
549
 
550
+ Matches starting earlier take precedence regardless of spec order:
551
+
525
552
  ```ts
526
- const tokenizer = createTokenizer((chunk, metadata) => metadata, ['later', /x/], ['earlier', /y/])
553
+ const lexer = createLexer((chunk, metadata) => metadata, ['later', /x/], ['earlier', /y/])
527
554
 
528
- const result = tokenizer('yx')
555
+ const result = lexer('yx')
529
556
 
530
557
  expect(result).toEqual(['earlier', 'later'])
531
558
  ```
532
559
 
560
+ The longer match wins at the same position:
561
+
533
562
  ```ts
534
- const tokenizer = createTokenizer(chunk => chunk, ['short', 'a'], ['long', 'abc'])
563
+ const lexer = createLexer(chunk => chunk, ['short', 'a'], ['long', 'abc'])
535
564
 
536
- const result = tokenizer('abc')
565
+ const result = lexer('abc')
537
566
 
538
567
  expect(result).toEqual(['abc'])
539
568
  ```
540
569
 
570
+ Empty string patterns are ignored:
571
+
541
572
  ```ts
542
- const tokenizer = createTokenizer(chunk => chunk, ['empty', ''], ['word', /\w+/])
573
+ const lexer = createLexer(chunk => chunk, ['empty', ''], ['word', /\w+/])
543
574
 
544
- const result = tokenizer('hello')
575
+ const result = lexer('hello')
545
576
 
546
577
  expect(result).toEqual(['hello'])
547
578
  ```
548
579
 
580
+ Input without any match is returned as a single chunk:
581
+
549
582
  ```ts
550
- const tokenizer = createTokenizer(chunk => chunk, ['test', /xyz/])
583
+ const lexer = createLexer(chunk => chunk, ['test', /xyz/])
551
584
 
552
- const result = tokenizer('abc')
585
+ const result = lexer('abc')
553
586
 
554
587
  expect(result).toEqual(['abc'])
555
588
  ```
556
589
 
590
+ Consecutive matches are returned as separate chunks:
591
+
557
592
  ```ts
558
- const tokenizer = createTokenizer((_chunk, metadata) => metadata, ['a', 'a'], ['b', 'b'])
593
+ const lexer = createLexer((_chunk, metadata) => metadata, ['a', 'a'], ['b', 'b'])
559
594
 
560
- const result = tokenizer('aabb')
595
+ const result = lexer('aabb')
561
596
 
562
597
  expect(result).toEqual(['a', 'a', 'b', 'b'])
563
598
  ```
564
599
 
600
+ Entering and leaving states with `push` and `pop`:
601
+
565
602
  ```ts
566
- const tokenizer = createTokenizer(
603
+ const lexer = createLexer(
567
604
  (chunk, metadata) => `${metadata}:${chunk}`,
568
605
  ['open', '(', { push: 'inner' }],
569
606
  ['word', /\w+/, { in: 'inner' }],
570
607
  ['close', ')', { in: 'inner', pop: true }]
571
608
  )
572
609
 
573
- const result = tokenizer('a(b)c)')
610
+ const result = lexer('a(b)c)')
574
611
 
575
612
  expect(result).toEqual(['undefined:a', 'open:(', 'word:b', 'close:)', 'undefined:c)'])
576
613
  ```
577
614
 
615
+ Nested states with a spec active in multiple states:
616
+
578
617
  ```ts
579
- const tokenizer = createTokenizer(
618
+ const lexer = createLexer(
580
619
  (chunk, metadata) => `${metadata}:${chunk}`,
581
620
  ['open', '{', { in: ['root', 'nested'], push: 'nested' }],
582
621
  ['close', '}', { in: 'nested', pop: true }]
583
622
  )
584
623
 
585
- const result = tokenizer('{{}}}')
624
+ const result = lexer('{{}}}')
586
625
 
587
626
  expect(result).toEqual(['open:{', 'open:{', 'close:}', 'close:}', 'undefined:}'])
588
627
  ```
589
628
 
629
+ Lookbehinds see the text preceding the current position:
630
+
590
631
  ```ts
591
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['a', 'a'], ['b', /(?<=a)b/])
632
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['a', 'a'], ['b', /(?<=a)b/])
592
633
 
593
- const result = tokenizer('abcb')
634
+ const result = lexer('abcb')
594
635
 
595
636
  expect(result).toEqual(['a:a', 'b:b', 'undefined:cb'])
596
637
  ```
597
638
 
639
+ Sticky patterns match only at the current position:
640
+
598
641
  ```ts
599
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['x', /x/y], ['y', 'y'])
642
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['x', /x/y], ['y', 'y'])
600
643
 
601
- const result = tokenizer('xyx.x')
644
+ const result = lexer('xyx.x')
602
645
 
603
646
  expect(result).toEqual(['x:x', 'y:y', 'x:x', 'undefined:.x'])
604
647
  ```
605
648
 
649
+ Empty `RegExp` matches are skipped:
650
+
606
651
  ```ts
607
- const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['digits', /\d*/])
652
+ const lexer = createLexer((chunk, metadata) => `${metadata}:${chunk}`, ['digits', /\d*/])
608
653
 
609
- const result = tokenizer('ab12')
654
+ const result = lexer('ab12')
610
655
 
611
656
  expect(result).toEqual(['undefined:ab', 'digits:12'])
612
657
  ```
@@ -615,9 +660,9 @@ expect(result).toEqual(['undefined:ab', 'digits:12'])
615
660
 
616
661
  ```ts
617
662
  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
663
+ <T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs1[]): HTMLElementTagNameMap[T]
664
+ <N extends Node>(node: N, ...args: HArgs1[]): N
665
+ (tagOrNode: string | Node, ...args: HArgs1[]): Node
621
666
  }
622
667
  ```
623
668
 
@@ -633,20 +678,22 @@ If a `Node`, that node is modified in place.
633
678
  Additional arguments processed as follows:
634
679
 
635
680
  - `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.
681
+ - 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`).
682
+ - 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`.
683
+ A value of `true` sets an empty attribute (e.g. `checked=""`), a value of `false` removes the attribute.
639
684
  - `null`/`undefined`: ignored.
640
685
  - `Node`: appended as a child.
641
686
  - `string`/`number`: converted to a `Text` node and appended.
642
- - [`HArgs`](#HArgs) array: processed recursively.
687
+ - [`HArgs`](#HArgs) array: processed recursively (creates or modifies a child node and appends it).
643
688
 
644
689
  #### Returns
645
690
 
646
- The created or modified `HTMLElement`.
691
+ The created `HTMLElement` or the modified node.
647
692
 
648
693
  #### Usage Examples
649
694
 
695
+ Creating elements, appending children and setting properties:
696
+
650
697
  ```ts
651
698
  const b = h('b')
652
699
 
@@ -667,6 +714,8 @@ expect(i.outerHTML).toBe('<i class="some class">text</i>')
667
714
  expect(b.outerHTML).toBe('<b><i class="some class">text</i></b>')
668
715
  ```
669
716
 
717
+ Text content from strings, numbers and `$innerText`:
718
+
670
719
  ```ts
671
720
  expect(h('span', 'text').outerHTML).toBe('<span>text</span>')
672
721
 
@@ -677,6 +726,8 @@ expect(h('span', '42').outerHTML).toBe('<span>42</span>')
677
726
  expect(h('span', 42).outerHTML).toBe('<span>42</span>')
678
727
  ```
679
728
 
729
+ `style` attribute vs `$style` property (string or object):
730
+
680
731
  ```ts
681
732
  expect(h('div', { style: 'margin:0;padding:0' }).outerHTML).toBe('<div style="margin:0;padding:0"></div>')
682
733
 
@@ -687,6 +738,8 @@ expect(h('div', { $style: { margin: 0, padding: 0 } }).outerHTML).toBe(
687
738
  )
688
739
  ```
689
740
 
741
+ `value` attribute vs `$value` property:
742
+
690
743
  ```ts
691
744
  const input1 = h('input', { value: 42 })
692
745
  const input2 = h('input', { $value: '42' })
@@ -698,6 +751,8 @@ expect(input1.outerHTML).toBe('<input value="42">')
698
751
  expect(input2.outerHTML).toBe('<input>')
699
752
  ```
700
753
 
754
+ `checked` attribute vs `$checked` property:
755
+
701
756
  ```ts
702
757
  const checkbox1 = h('input', {
703
758
  type: 'checkbox',
@@ -717,6 +772,8 @@ expect(checkbox1.outerHTML).toBe('<input type="checkbox" checked="">')
717
772
  expect(checkbox2.outerHTML).toBe('<input type="checkbox">')
718
773
  ```
719
774
 
775
+ Object values of `$`-prefixed properties are merged:
776
+
720
777
  ```ts
721
778
  const div = h('div')
722
779
 
@@ -731,6 +788,8 @@ h(div, { $key: { two: 2 } })
731
788
  expect(div.key).toEqual({ one: 1, two: 2 })
732
789
  ```
733
790
 
791
+ Class attribute, appending text and nested children given as arrays:
792
+
734
793
  ```ts
735
794
  const elemWithClass = h('div', { class: 'test' })
736
795
 
@@ -753,7 +812,29 @@ expect(elemWithNested.outerHTML).toBe('<div><span>hello</span><b>world</b></div>
753
812
  const monokai: CNode
754
813
  ```
755
814
 
756
- A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) tokenizer.
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
+ ```
757
838
 
758
839
  ### nanolightTs
759
840
 
@@ -761,7 +842,7 @@ A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanoligh
761
842
  const nanolightTs: (code: string) => HArgs1[]
762
843
  ```
763
844
 
764
- A TypeScript/JavaScript syntax highlighting tokenizer built using [`createTokenizer`](#createTokenizer).
845
+ A TypeScript/JavaScript syntax highlighting lexer built using [`createLexer`](#createLexer).
765
846
 
766
847
  #### code
767
848
 
@@ -769,10 +850,12 @@ The source code string to tokenize.
769
850
 
770
851
  #### Returns
771
852
 
772
- An array of [`HArgs1`](#HArgs1) elements suitable for rendering with [`h`](#h).
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)).
773
854
 
774
855
  #### Usage Examples
775
856
 
857
+ Keywords, identifiers, operators, numbers, punctuation, strings and comments:
858
+
776
859
  ```ts
777
860
  const codeJs = 'const answerToLifeTheUniverseAndEverything = ' + "{ 42: 42 }['42'] /* 42 */"
778
861
 
@@ -799,78 +882,280 @@ expect(nanolightTs(codeJs)).toEqual([
799
882
  ])
800
883
  ```
801
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
+
802
965
  ```ts
803
- expect(classes('x = /a\\/b[/]c/gi.test(y) / 2')).toEqual([
804
- 'identifier4:x',
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'],
805
970
  ' ',
806
- 'operator:=',
971
+ ['span', { class: 'operator' }, '+'],
807
972
  ' ',
808
- 'string:/a\\/b[/]c/gi',
809
- 'punctuation:.',
810
- 'identifier2:test',
811
- 'punctuation:(',
812
- 'identifier4:y',
813
- 'punctuation:)',
973
+ ['span', { class: 'string' }, '`c '],
974
+ ['span', { class: 'punctuation' }, '${'],
814
975
  ' ',
815
- 'operator:/',
976
+ ['span', { class: 'punctuation' }, '{'],
977
+ ['span', { class: 'identifier4' }, 'd'],
978
+ ['span', { class: 'punctuation' }, '}'],
979
+ ['span', { class: 'punctuation' }, '.'],
980
+ ['span', { class: 'identifier4' }, 'd'],
816
981
  ' ',
817
- 'number:2',
982
+ ['span', { class: 'punctuation' }, '}'],
983
+ ['span', { class: 'string' }, '`'],
984
+ ['span', { class: 'punctuation' }, '}'],
985
+ ['span', { class: 'string' }, ' \\${e} $f`'],
818
986
  ])
819
987
 
820
- expect(classes('a / b / c')).toEqual([
821
- 'identifier4:a',
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' }, '`'],
822
994
  ' ',
823
- 'operator:/',
995
+ ['span', { class: 'operator' }, '/'],
824
996
  ' ',
825
- 'identifier4:b',
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"'],
826
1006
  ' ',
827
- 'operator:/',
1007
+ ['span', { class: 'comment' }, '// c'],
1008
+ ])
1009
+
1010
+ expect(nanolightTs("'it\\'s' /* x")).toEqual([
1011
+ ['span', { class: 'string' }, "'it\\'s'"],
828
1012
  ' ',
829
- 'identifier4:c',
1013
+ ['span', { class: 'comment' }, '/* x'],
830
1014
  ])
1015
+ ```
1016
+
1017
+ Hexadecimal, BigInt, numeric separators and exponents:
831
1018
 
832
- expect(classes('return / x /')).toEqual(['keyword1:return', ' ', 'string:/ x /'])
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
+ ])
833
1031
  ```
834
1032
 
1033
+ Division after `++` and optional chaining vs ternary with decimals:
1034
+
835
1035
  ```ts
836
- expect(classes('`a ${b + `c ${ {d}.d }`} \\${e} $f`')).toEqual([
837
- 'string:`a ',
838
- 'punctuation:${',
839
- 'identifier4:b',
1036
+ expect(nanolightTs('a++ / 2 / b')).toEqual([
1037
+ ['span', { class: 'identifier4' }, 'a'],
1038
+ ['span', { class: 'operator' }, '++'],
1039
+ ' ',
1040
+ ['span', { class: 'operator' }, '/'],
840
1041
  ' ',
841
- 'operator:+',
1042
+ ['span', { class: 'number' }, '2'],
842
1043
  ' ',
843
- 'string:`c ',
844
- 'punctuation:${',
1044
+ ['span', { class: 'operator' }, '/'],
845
1045
  ' ',
846
- 'punctuation:{',
847
- 'identifier4:d',
848
- 'punctuation:}',
849
- 'punctuation:.',
850
- 'identifier4:d',
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' }, ','],
851
1072
  ' ',
852
- 'punctuation:}',
853
- 'string:`',
854
- 'punctuation:}',
855
- 'string: \\${e} $f`',
1073
+ ['span', { class: 'operator' }, '...'],
1074
+ ['span', { class: 'keyword1' }, 'this'],
1075
+ ['span', { class: 'punctuation' }, ')'],
856
1076
  ])
1077
+ ```
857
1078
 
858
- expect(classes('`${x}` / 2')).toEqual([
859
- 'string:`',
860
- 'punctuation:${',
861
- 'identifier4:x',
862
- 'punctuation:}',
863
- 'string:`',
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'],
864
1090
  ' ',
865
- 'operator:/',
1091
+ ['span', { class: 'punctuation' }, '{'],
1092
+ ' ',
1093
+ ['span', { class: 'identifier4' }, '#x'],
1094
+ ' ',
1095
+ ['span', { class: 'operator' }, '='],
866
1096
  ' ',
867
- 'number:2',
1097
+ ['span', { class: 'number' }, '1'],
1098
+ ' ',
1099
+ ['span', { class: 'punctuation' }, '}'],
868
1100
  ])
869
1101
  ```
870
1102
 
1103
+ Escapes, anchors, quantifiers, groups, character sets and flags inside regex literals:
1104
+
871
1105
  ```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'])
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
+ ])
874
1159
  ```
875
1160
 
876
1161
  ### rwd
@@ -885,7 +1170,8 @@ const rwd: (
885
1170
  ) => void
886
1171
  ```
887
1172
 
888
- A responsive web design helper that generates CSS rules for a grid-like layout.
1173
+ A responsive web design helper that generates CSS rules for a grid-like layout of floating elements.
1174
+ It modifies `root` in place: it sets base styles for `selector` and adds a `@media(min-width:…px)` rule for each breakpoint.
889
1175
 
890
1176
  #### root
891
1177
 
@@ -907,12 +1193,14 @@ The base cell height in pixels.
907
1193
 
908
1194
  An array of breakpoint specifications, each a tuple of:
909
1195
 
910
- - `maxWidth`: maximum number of cells per row (defines the viewport breakpoint).
1196
+ - `maxWidth`: number of cells per row; the breakpoint is `min-width: cellWidthPx * maxWidth` (`1` modifies the base rule).
911
1197
  - `width` (optional, default `1`): number of horizontal cells the element spans.
912
1198
  - `height` (optional, default `1`): number of vertical cells the element spans.
913
1199
 
914
1200
  #### Usage Examples
915
1201
 
1202
+ Responsive grid cell widths and heights with media queries:
1203
+
916
1204
  ```ts
917
1205
  const style: CNode = {
918
1206
  body: {
@@ -937,7 +1225,7 @@ expect(style).toEqual({
937
1225
  '.no-border': {
938
1226
  border: 'none',
939
1227
  },
940
- boxSizing: 'border-box',
1228
+ 'box-sizing': 'border-box',
941
1229
  display: 'block',
942
1230
  float: 'left',
943
1231
  width: '100%',
@@ -962,9 +1250,9 @@ expect(style).toEqual({
962
1250
 
963
1251
  ```ts
964
1252
  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
1253
+ <T extends keyof SVGElementTagNameMap>(tag: T, ...args: HArgs1[]): SVGElementTagNameMap[T]
1254
+ <N extends Node>(node: N, ...args: HArgs1[]): N
1255
+ (tagOrNode: string | Node, ...args: HArgs1[]): Node
968
1256
  }
969
1257
  ```
970
1258
 
@@ -980,38 +1268,46 @@ If a `Node`, that node is modified in place.
980
1268
  Additional arguments processed as follows:
981
1269
 
982
1270
  - `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.
1271
+ - 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.
1272
+ - 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`.
1273
+ A value of `true` sets an empty attribute (e.g. `checked=""`), a value of `false` removes the attribute.
986
1274
  - `null`/`undefined`: ignored.
987
1275
  - `Node`: appended as a child.
988
1276
  - `string`/`number`: converted to a `Text` node and appended.
989
- - [`HArgs`](#HArgs) array: processed recursively.
1277
+ - [`HArgs`](#HArgs) array: processed recursively (creates or modifies a child node and appends it).
990
1278
 
991
1279
  #### Returns
992
1280
 
993
- The created or modified `SVGElement`.
1281
+ The created `SVGElement` or the modified node.
994
1282
 
995
1283
  #### Usage Examples
996
1284
 
1285
+ Namespaced `xlink:` attribute set to `true` gets an empty value:
1286
+
997
1287
  ```ts
998
1288
  const svg = s('svg', { 'xlink:href': true })
999
1289
 
1000
1290
  expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('')
1001
1291
  ```
1002
1292
 
1293
+ Namespaced `xlink:` attribute set to `false` is not set:
1294
+
1003
1295
  ```ts
1004
1296
  const svg = s('svg', { 'xlink:href': false })
1005
1297
 
1006
1298
  expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBeNull()
1007
1299
  ```
1008
1300
 
1301
+ Namespaced `xlink:` attribute with a string value:
1302
+
1009
1303
  ```ts
1010
1304
  const svg = s('svg', { 'xlink:href': 'http://example.com' })
1011
1305
 
1012
1306
  expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('http://example.com')
1013
1307
  ```
1014
1308
 
1309
+ Namespaced `xlink:` attribute with a number value:
1310
+
1015
1311
  ```ts
1016
1312
  const svg = s('svg', { 'xlink:title': 42 })
1017
1313
 
@@ -1024,7 +1320,7 @@ expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'title')).toBe('42')
1024
1320
  const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement
1025
1321
  ```
1026
1322
 
1027
- Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
1323
+ Shorthand for creating an `<svg>` element with a `<use>` child referencing a symbol (e.g. an icon) by ID.
1028
1324
 
1029
1325
  Equivalent to:
1030
1326
 
@@ -1046,6 +1342,8 @@ An `SVGSVGElement` containing a `<use>` element.
1046
1342
 
1047
1343
  #### Usage Examples
1048
1344
 
1345
+ SVG `use` element referencing a symbol, with extra attributes:
1346
+
1049
1347
  ```ts
1050
1348
  const svg = svgUse('icon-home')
1051
1349
 
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,
@@ -201,6 +201,7 @@ var monokai = {
201
201
  number: "#7058be",
202
202
  operator: "#e14775",
203
203
  punctuation: "#918c8e",
204
+ regexp: "#e16032",
204
205
  string: "#cc7a0a"
205
206
  }
206
207
  },
@@ -233,6 +234,7 @@ var monokai = {
233
234
  number: { color: "var(--number)" },
234
235
  operator: { color: "var(--operator)" },
235
236
  punctuation: { color: "var(--punctuation)" },
237
+ regexp: { color: "var(--regexp)" },
236
238
  string: { color: "var(--string)" }
237
239
  },
238
240
  "@media only screen and (prefers-color-scheme: dark)$$monokai": {
@@ -255,6 +257,7 @@ var monokai = {
255
257
  number: "#ae81ff",
256
258
  operator: "#ff6188",
257
259
  punctuation: "#727072",
260
+ regexp: "#fc9867",
258
261
  string: "#ffd866"
259
262
  }
260
263
  }
@@ -280,17 +283,24 @@ var KEYWORD3 = "keyword3";
280
283
  var NUMBER = "number";
281
284
  var OPERATOR = "operator";
282
285
  var PUNCTUATION = "punctuation";
286
+ var REGEXP = "regexp";
283
287
  var STRING = "string";
284
288
  var ROOT2 = "root";
285
289
  var TEMPLATE = "template";
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 }], [
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]);
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]);
290
300
  // src/nnn/rwd.ts
291
301
  var rwd = (root, selector, cellWidthPx, cellHeightPx, ...specs) => {
292
302
  const main = root[selector] ??= {};
293
- main.boxSizing = "border-box";
303
+ main["box-sizing"] = "border-box";
294
304
  main.display = "block";
295
305
  main.float = "left";
296
306
  main.width = "100%";
@@ -313,7 +323,7 @@ var rwd = (root, selector, cellWidthPx, cellHeightPx, ...specs) => {
313
323
  };
314
324
  export {
315
325
  c,
316
- createTokenizer,
326
+ createLexer,
317
327
  h,
318
328
  monokai,
319
329
  nanolightTs,
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "@jackens/nnn",
3
- "version": "2026.9.28",
3
+ "version": "2026.9.30",
4
4
  "description": "Jackens’ JavaScript helpers.",
5
5
  "keywords": [
6
6
  "CSS-in-JS",
7
+ "highlight",
7
8
  "HyperScript",
8
9
  "JS-to-CSS",
10
+ "lexer",
9
11
  "Monokai",
10
- "RWD",
11
- "Responsive Web Design",
12
- "SVG",
13
- "highlight",
14
12
  "nanolight",
15
13
  "nnn",
14
+ "Responsive Web Design",
15
+ "RWD",
16
+ "SVG",
16
17
  "tokenize",
17
18
  "tokenizer"
18
19
  ],