@jackens/nnn 2026.8.31 → 2026.9.2

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/nnn.d.ts +200 -423
  2. package/nnn.js +113 -138
  3. package/package.json +1 -3
  4. package/readme.md +296 -670
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # nnn
2
2
 
3
- A collection of Jackens’ JavaScript helper utilities (version: `2026.8.31`).
3
+ A collection of Jackens’ JavaScript helper utilities (version: `2026.9.2`).
4
4
 
5
5
  ## Installation
6
6
 
@@ -24,18 +24,8 @@ import {
24
24
  csvParse,
25
25
  fixPlTypography,
26
26
  h,
27
- hasOwn,
28
- isArray,
29
- isFiniteNumber,
30
- isInteger,
31
- isNumber,
32
- isRecord,
33
- isString,
34
- jsOnParse,
35
27
  monokai,
36
28
  nanolightTs,
37
- omit,
38
- pick,
39
29
  rwd,
40
30
  s,
41
31
  svgUse,
@@ -47,9 +37,8 @@ import {
47
37
  ## Exports
48
38
 
49
39
  - [`CNode`](#CNode):
50
- Represents a CSS rule node for the [`c`](#c) helper.
51
- - [`CRoot`](#CRoot):
52
- Represents the root CSS object for the [`c`](#c) helper.
40
+ Represents a CSS rule node for the [`c`](#c) helper. Keys
41
+ are CSS properties or nested selectors.
53
42
  - [`HArgs`](#HArgs):
54
43
  Tuple argument type for the [`h`](#h) and [`s`](#s)
55
44
  helpers.
@@ -57,74 +46,43 @@ import {
57
46
  Single argument type for the [`h`](#h) and [`s`](#s)
58
47
  helpers.
59
48
  - [`c`](#c):
60
- A minimal CSS-in-JS helper
61
- that converts a JavaScript object hierarchy
62
- into a CSS string.
49
+ A minimal CSS-in-JS helper that converts a JavaScript
50
+ object hierarchy into a CSS string.
63
51
  - [`createEscape`](#createEscape):
64
- Creates a tag function
65
- for escaping interpolated values in template literals.
52
+ Creates a tag function for escaping interpolated values
53
+ in template literals.
66
54
  - [`createTokenizer`](#createTokenizer):
67
- A helper for building simple tokenizers
68
- (see also [`nanolightTs`](#nanolightTs)).
55
+ A helper for building simple tokenizers (see also
56
+ [`nanolightTs`](#nanolightTs)).
69
57
  - [`csvParse`](#csvParse):
70
- Parses a CSV string
71
- into a two-dimensional array of strings.
58
+ Parses a CSV string into a two-dimensional array of
59
+ strings.
72
60
  - [`fixPlTypography`](#fixPlTypography):
73
- Applies Polish-specific typographic corrections
74
- to a DOM subtree.
61
+ Applies Polish-specific typographic corrections to a DOM
62
+ subtree.
75
63
  - [`h`](#h):
76
- A lightweight *HyperScript*-style helper
77
- for creating and modifying `HTMLElement`s
78
- (see also [`s`](#s)).
79
- - [`hasOwn`](#hasOwn):
80
- Checks whether an object has the specified key
81
- as its own property.
82
- - [`isArray`](#isArray):
83
- Checks whether the argument is an array.
84
- - [`isFiniteNumber`](#isFiniteNumber):
85
- Checks whether the argument is a finite number
86
- (excludes `±Infinity` and `NaN`).
87
- - [`isInteger`](#isInteger):
88
- Checks whether the argument is an integer number.
89
- - [`isNumber`](#isNumber):
90
- Checks whether the argument is of type `number`
91
- (includes `NaN` and `±Infinity`).
92
- - [`isRecord`](#isRecord):
93
- Checks whether the argument is a plain object
94
- (not `null` and not an array).
95
- - [`isString`](#isString):
96
- Checks whether the argument is a string.
97
- - [`jsOnParse`](#jsOnParse):
98
- Parses JSON with support for
99
- handler-based value transformation (“JavaScript ON”).
64
+ A lightweight _HyperScript_-style helper for creating and
65
+ modifying `HTMLElement`s (see also [`s`](#s)).
100
66
  - [`monokai`](#monokai):
101
- A Monokai-inspired color scheme
102
- for use with the [`c`](#c) helper
103
- and [`nanolightTs`](#nanolightTs) tokenizer.
67
+ A Monokai-inspired color scheme for use with the [`c`](#c)
68
+ helper and [`nanolightTs`](#nanolightTs) tokenizer.
104
69
  - [`nanolightTs`](#nanolightTs):
105
70
  A TypeScript/JavaScript syntax highlighting tokenizer
106
71
  built using [`createTokenizer`](#createTokenizer).
107
- - [`omit`](#omit):
108
- Creates a new object excluding the specified keys
109
- from the source object.
110
- - [`pick`](#pick):
111
- Creates a new object containing only the specified keys
112
- from the source object.
113
72
  - [`rwd`](#rwd):
114
- A responsive web design helper
115
- that generates CSS rules for a grid-like layout.
73
+ A responsive web design helper that generates CSS rules
74
+ for a grid-like layout.
116
75
  - [`s`](#s):
117
- A lightweight *HyperScript*-style helper
118
- for creating and modifying `SVGElement`s
119
- (see also [`h`](#h)).
76
+ A lightweight _HyperScript_-style helper for creating and
77
+ modifying `SVGElement`s (see also [`h`](#h)).
120
78
  - [`svgUse`](#svgUse):
121
- Shorthand for creating an SVG element
122
- with a `<use>` child referencing an icon by ID.
79
+ Shorthand for creating an SVG element with a `<use>`
80
+ child referencing an icon by ID.
123
81
  - [`uuidV1`](#uuidV1):
124
82
  Generates a UUID v1 (time-based) identifier.
125
83
  - [`vivify`](#vivify):
126
- A Proxy-based helper for auto-vivification
127
- of nested object structures.
84
+ A Proxy-based helper for auto-vivification of nested
85
+ object structures.
128
86
 
129
87
  ### CNode
130
88
 
@@ -134,17 +92,8 @@ type CNode = {
134
92
  };
135
93
  ```
136
94
 
137
- Represents a CSS rule node for the [`c`](#c) helper.
138
- Keys are CSS properties or nested selectors.
139
-
140
- ### CRoot
141
-
142
- ```ts
143
- type CRoot = Record<PropertyKey, CNode>;
144
- ```
145
-
146
- Represents the root CSS object for the [`c`](#c) helper.
147
- Keys are top-level selectors or at-rules.
95
+ Represents a CSS rule node for the [`c`](#c) helper. Keys
96
+ are CSS properties or nested selectors.
148
97
 
149
98
  ### HArgs
150
99
 
@@ -167,46 +116,32 @@ helpers.
167
116
  ### c
168
117
 
169
118
  ```ts
170
- const c: (root: CRoot, splitter?: string) => string;
119
+ const c: (node: CNode, splitter?: string) => string;
171
120
  ```
172
121
 
173
- A minimal CSS-in-JS helper
174
- that converts a JavaScript object hierarchy
175
- into a CSS string.
122
+ A minimal CSS-in-JS helper that converts a JavaScript
123
+ object hierarchy into a CSS string.
176
124
 
177
- #### root
125
+ #### node
178
126
 
179
- An object describing CSS rules.
180
- Keys are selectors or at-rules;
181
- values are either CSS property values
182
- or nested rule objects.
127
+ An object describing CSS rules. Keys are
128
+ selectors or at-rules; values are either CSS property
129
+ values or nested rule objects. A selector key may
130
+ contain comma-separated alternatives (e.g. `'.b,.c'`);
131
+ when nested, each alternative is combined with each
132
+ parent alternative (cartesian product), producing a
133
+ comma-joined selector list (e.g. `'a.b,a.c'`).
183
134
 
184
135
  #### splitter
185
136
 
186
- A delimiter used to create unique keys (default: `'$$'`).
187
- The substring from `splitter` to the end of a key
188
- is ignored (e.g., `src$$1` → `src`).
137
+ A delimiter used to create unique keys
138
+ (default: `'$$'`). The substring from `splitter` to the
139
+ end of a key is ignored (e.g., `src$$1` → `src`).
189
140
 
190
141
  #### Returns
191
142
 
192
143
  A CSS string representing the compiled rules.
193
144
 
194
- #### Remarks
195
-
196
- - Keys whose values are primitives (`string` | `number`)
197
- are treated as CSS properties.
198
- - In property keys, uppercase letters
199
- become lowercase with a `-` prefix
200
- (e.g., `fontFamily` → `font-family`);
201
- underscores become hyphens
202
- (e.g., `font_family` → `font-family`).
203
- - Comma-separated selector keys
204
- expand into multiple selectors
205
- (e.g., `{ div: { '.a,.b': { margin: 1 } } }` →
206
- `div.a,div.b{margin:1}`).
207
- - Top-level keys starting with `@` (at-rules)
208
- are not concatenated with child selectors.
209
-
210
145
  #### Usage Examples
211
146
 
212
147
  ```ts
@@ -262,16 +197,16 @@ expect(actual2).to.equal(expected2)
262
197
 
263
198
  const actual3 = c({
264
199
  '@font-face$$1': {
265
- fontFamily: 'Jackens',
200
+ 'font-family': 'Jackens',
266
201
  src$$1: 'url(otf/jackens.otf)',
267
202
  src$$2:
268
203
  "url(otf/jackens.otf) format('opentype')," +
269
204
  "url(svg/jackens.svg) format('svg')",
270
- font_weight: 'normal',
205
+ 'font-weight': 'normal',
271
206
  'font-style': 'normal',
272
207
  },
273
208
  '@font-face$$2': {
274
- font_family: 'C64',
209
+ 'font-family': 'C64',
275
210
  src: 'url(fonts/C64_Pro_Mono-STYLE.woff)',
276
211
  },
277
212
  '@keyframes spin': {
@@ -281,8 +216,8 @@ const actual3 = c({
281
216
  div: {
282
217
  border: 'solid red 1px',
283
218
  '.c1': { 'background-color': '#000' },
284
- ' .c1': { background_color: 'black' },
285
- '.c2': { backgroundColor: 'rgb(0,0,0)' },
219
+ ' .c1': { 'background-color': 'black' },
220
+ '.c2': { 'background-color': 'rgb(0,0,0)' },
286
221
  },
287
222
  '@media(min-width:200px)': {
288
223
  div: { margin: 0, padding: 0 },
@@ -392,6 +327,94 @@ const expected6 = `
392
327
  }`.replace(/\n\s*/g, '')
393
328
 
394
329
  expect(actual6).to.equal(expected6)
330
+
331
+ const actual7 = c({
332
+ ':root$$monokai': {
333
+ '--': {
334
+ bg: '#faf4f2',
335
+ fg: '#29242a',
336
+ comment: '#918c8e',
337
+ identifier: {
338
+ 1: '#7058be',
339
+ 2: '#269d69',
340
+ 3: '#1c8ca8',
341
+ 4: '#29242a',
342
+ },
343
+ keyword: {
344
+ 1: '#e14775',
345
+ 2: '#7058be',
346
+ 3: '#1c8ca8',
347
+ },
348
+ number: '#7058be',
349
+ operator: '#e14775',
350
+ punctuation: '#918c8e',
351
+ string: '#cc7a0a',
352
+ },
353
+ },
354
+ '@media only screen and (prefers-color-scheme: dark)$$monokai':
355
+ {
356
+ ':root': {
357
+ '--': {
358
+ bg: '#2d2a2e',
359
+ fg: '#fcfcfa',
360
+ comment: '#727072',
361
+ identifier: {
362
+ 1: '#ae81ff',
363
+ 2: '#a9dc76',
364
+ 3: '#66d9ef',
365
+ 4: '#fcfcfa',
366
+ },
367
+ keyword: {
368
+ 1: '#ff6188',
369
+ 2: '#ae81ff',
370
+ 3: '#66d9ef',
371
+ },
372
+ number: '#ae81ff',
373
+ operator: '#ff6188',
374
+ punctuation: '#727072',
375
+ string: '#ffd866',
376
+ },
377
+ },
378
+ },
379
+ })
380
+
381
+ const expected7 = `
382
+ :root{
383
+ --bg:#faf4f2;
384
+ --fg:#29242a;
385
+ --comment:#918c8e;
386
+ --identifier1:#7058be;
387
+ --identifier2:#269d69;
388
+ --identifier3:#1c8ca8;
389
+ --identifier4:#29242a;
390
+ --keyword1:#e14775;
391
+ --keyword2:#7058be;
392
+ --keyword3:#1c8ca8;
393
+ --number:#7058be;
394
+ --operator:#e14775;
395
+ --punctuation:#918c8e;
396
+ --string:#cc7a0a
397
+ }
398
+ @media only screen and (prefers-color-scheme: dark){
399
+ :root{
400
+ --bg:#2d2a2e;
401
+ --fg:#fcfcfa;
402
+ --comment:#727072;
403
+ --identifier1:#ae81ff;
404
+ --identifier2:#a9dc76;
405
+ --identifier3:#66d9ef;
406
+ --identifier4:#fcfcfa;
407
+ --keyword1:#ff6188;
408
+ --keyword2:#ae81ff;
409
+ --keyword3:#66d9ef;
410
+ --number:#ae81ff;
411
+ --operator:#ff6188;
412
+ --punctuation:#727072;
413
+ --string:#ffd866
414
+ }
415
+ }`.replace(/\n\s*/g, '')
416
+
417
+ expect(actual7).to.equal(expected7)
395
418
  ```
396
419
 
397
420
  ### createEscape
@@ -400,24 +423,24 @@ expect(actual6).to.equal(expected6)
400
423
  const createEscape: (escapeFn: (value: any) => string) => (template: TemplateStringsArray, ...values: unknown[]) => string;
401
424
  ```
402
425
 
403
- Creates a tag function
404
- for escaping interpolated values in template literals.
426
+ Creates a tag function for escaping interpolated values
427
+ in template literals.
405
428
 
406
429
  #### escapeFn
407
430
 
408
- A function that takes a value
409
- and returns its escaped string representation.
431
+ A function that takes a value and returns
432
+ its escaped string representation.
410
433
 
411
434
  #### Returns
412
435
 
413
436
  A tag function that escapes interpolated values
414
- using the provided escape map.
437
+ using the provided escape function.
415
438
 
416
439
  #### Usage Examples
417
440
 
418
441
  ```ts
419
442
  const escapeFn = (value: any): string =>
420
- isArray(value)
443
+ Array.isArray(value)
421
444
  ? value.map(escapeFn).join(', ')
422
445
  : value === true || value === false
423
446
  ? `b'${+value}'`
@@ -425,9 +448,9 @@ const escapeFn = (value: any): string =>
425
448
  ? `'${value
426
449
  .toISOString()
427
450
  .replace(/^(.+)T(.+)\..*$/, '$1 $2')}'`
428
- : isFiniteNumber(value)
451
+ : Number.isFinite(value)
429
452
  ? `${value}`
430
- : isString(value)
453
+ : typeof value === 'string'
431
454
  ? `'${value.replace(/'/g, "''")}'`
432
455
  : 'NULL'
433
456
 
@@ -467,40 +490,41 @@ expect(actual).to.equal(expected)
467
490
  const createTokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
468
491
  ```
469
492
 
470
- A helper for building simple tokenizers
471
- (see also [`nanolightTs`](#nanolightTs)).
493
+ A helper for building simple tokenizers (see also
494
+ [`nanolightTs`](#nanolightTs)).
495
+
496
+ #### Remarks
497
+
498
+ 1. Matches starting at an earlier position take
499
+ precedence.
500
+ 2. Among matches at the same position, the longer one
501
+ wins.
502
+ 3. Among matches of the same position and length, the one
503
+ defined earlier wins.
472
504
 
473
505
  #### decorator
474
506
 
475
- A function that wraps each matched chunk.
476
- It receives the matched text (`chunk`)
477
- and optionally the `metadata`
478
- associated with the pattern that produced the match.
479
- For unmatched text between patterns,
480
- `metadata` is `undefined`.
507
+ A function that wraps each matched
508
+ chunk. It receives the matched text (`chunk`) and
509
+ optionally the `metadata` associated with the pattern
510
+ that produced the match. For unmatched text between
511
+ patterns, `metadata` is `undefined`.
481
512
 
482
513
  #### specs
483
514
 
484
- An array of tuples `[metadata, pattern]` where:
515
+ An array of tuples `[metadata, pattern]`
516
+ where:
517
+
485
518
  - `metadata`: arbitrary data (e.g., a CSS class name)
486
519
  passed to `decorator` when the pattern matches.
487
- - `pattern`: a `string` or `RegExp`
488
- to match against the input.
520
+ - `pattern`: a `string` or `RegExp` to match against the
521
+ input.
489
522
 
490
523
  #### Returns
491
524
 
492
525
  A tokenizer function that accepts a code string
493
526
  and returns an array of decorated tokens.
494
527
 
495
- #### Remarks
496
-
497
- 1. Matches starting at an earlier position
498
- take precedence.
499
- 2. Among matches at the same position,
500
- the longer one wins.
501
- 3. Among matches of the same position and length,
502
- the one defined earlier wins.
503
-
504
528
  #### Usage Examples
505
529
 
506
530
  ```ts
@@ -615,8 +639,8 @@ expect(result9).to.deep.equal(['a', 'a', 'b', 'b'])
615
639
  const csvParse: (csv: string, separator?: string) => string[][];
616
640
  ```
617
641
 
618
- Parses a CSV string
619
- into a two-dimensional array of strings.
642
+ Parses a CSV string into a two-dimensional array of
643
+ strings.
620
644
 
621
645
  Supports quoted fields with escaped double quotes (`""`).
622
646
  Carriage returns are normalized.
@@ -631,8 +655,8 @@ The field delimiter (default: `','`).
631
655
 
632
656
  #### Returns
633
657
 
634
- A 2D array where each inner array
635
- represents a row of fields.
658
+ A 2D array where each inner array represents a
659
+ row of fields.
636
660
 
637
661
  #### Usage Examples
638
662
 
@@ -659,23 +683,25 @@ expect(csvParse(text)).to.deep.equal([
659
683
  const fixPlTypography: (node: Node) => void;
660
684
  ```
661
685
 
662
- Applies Polish-specific typographic corrections
663
- to a DOM subtree.
686
+ Applies Polish-specific typographic corrections to a DOM
687
+ subtree.
664
688
 
665
689
  This function prevents orphaned conjunctions
666
690
  (single-letter words like “a”, “i”, “o”, “u”, “w”, “z”)
667
- from appearing at the end of a line
668
- by wrapping them with the following word
669
- in a non-breaking span.
670
- It also inserts zero-width spaces
671
- after slashes and dots to allow line breaks.
691
+ and the em dash (“—”) from appearing at the end of a
692
+ line. It wraps each such character together with the
693
+ whitespace that follows it in a `white-space:nowrap`
694
+ span, removing the only line-break opportunity between it
695
+ and the next word so the two stay glued together. It also
696
+ inserts zero-width spaces after slashes and dots to allow
697
+ line breaks.
672
698
 
673
699
  #### node
674
700
 
675
- The root DOM node to process.
676
- All descendant text nodes are corrected recursively,
677
- except those inside `IFRAME`, `NOSCRIPT`, `PRE`,
678
- `SCRIPT`, `STYLE`, or `TEXTAREA` elements.
701
+ The root DOM node to process. All descendant
702
+ text nodes are corrected recursively, except those
703
+ inside `IFRAME`, `NOSCRIPT`, `PRE`, `SCRIPT`, `STYLE`,
704
+ or `TEXTAREA` elements.
679
705
 
680
706
  #### Usage Examples
681
707
 
@@ -711,28 +737,33 @@ const h: {
711
737
  };
712
738
  ```
713
739
 
714
- A lightweight *HyperScript*-style helper
715
- for creating and modifying `HTMLElement`s
716
- (see also [`s`](#s)).
740
+ A lightweight _HyperScript_-style helper for creating and
741
+ modifying `HTMLElement`s (see also [`s`](#s)).
717
742
 
718
743
  #### tagOrNode
719
744
 
720
- If a `string`, it is treated as the tag name
721
- for a new element.
722
- If a `Node`, that node is modified in place.
745
+ If a `string`, it is treated as the tag
746
+ name for a new element. If a `Node`, that node is
747
+ modified in place.
723
748
 
724
749
  #### args
725
750
 
726
751
  Additional arguments processed as follows:
727
- - `Object`: maps attributes/properties.
728
- Keys starting with `$` set element properties
729
- (without the `$` prefix);
730
- other keys set attributes via `setAttribute`.
731
- A value of `false` removes the attribute.
752
+
753
+ - `Object`: maps attributes/properties. Keys starting
754
+ with `$` set element properties (without the `$`
755
+ prefix); if the value is a plain object, it is
756
+ shallow-merged into the existing property instead of
757
+ replacing it (useful e.g. for `$style`). Other keys
758
+ set attributes: a key prefixed with `xlink:` sets the
759
+ attribute via `setAttributeNS` in the XLink
760
+ namespace; any other key (without a colon) sets it
761
+ via `setAttribute`. A value of `false` removes the
762
+ attribute.
732
763
  - `null`/`undefined`: ignored.
733
764
  - `Node`: appended as a child.
734
- - `string`/`number`: converted to a `Text` node
735
- and appended.
765
+ - `string`/`number`: converted to a `Text` node and
766
+ appended.
736
767
  - [`HArgs`](#HArgs) array: processed recursively.
737
768
 
738
769
  #### Returns
@@ -855,376 +886,14 @@ expect(elemWithNested.outerHTML).to.equal(
855
886
  )
856
887
  ```
857
888
 
858
- ### hasOwn
859
-
860
- ```ts
861
- const hasOwn: (ref: unknown, key: unknown) => boolean;
862
- ```
863
-
864
- Checks whether an object has the specified key
865
- as its own property.
866
-
867
- A null-safe wrapper around `Object.hasOwn`.
868
-
869
- #### ref
870
-
871
- The object to check.
872
-
873
- #### key
874
-
875
- The property key to look for.
876
-
877
- #### Returns
878
-
879
- `true` if `ref` is not nullish
880
- and has `key` as an own property, `false` otherwise.
881
-
882
- #### Usage Examples
883
-
884
- ```ts
885
- const obj = { 42: null, null: 'k,e,y', 'k,e,y': 42 }
886
-
887
- expect(42 in obj).to.be.true
888
- expect(hasOwn(obj, 42)).to.be.true
889
-
890
- expect('42' in obj).to.be.true
891
- expect(hasOwn(obj, '42')).to.be.true
892
-
893
- expect('null' in obj).to.be.true
894
- expect(hasOwn(obj, 'null')).to.be.true
895
-
896
- expect(null in obj).to.be.true
897
- expect(hasOwn(obj, null)).to.be.true
898
-
899
- expect('k,e,y' in obj).to.be.true
900
- expect(hasOwn(obj, 'k,e,y')).to.be.true
901
-
902
- expect(['k', 'e', 'y'] in obj).to.be.true
903
- expect(hasOwn(obj, ['k', 'e', 'y'])).to.be.true
904
-
905
- expect('toString' in obj).to.be.true
906
- expect(hasOwn(obj, 'toString')).to.be.false
907
-
908
- expect(() => 'key' in null).to.throw
909
- expect(hasOwn(null, 'key')).to.be.false
910
-
911
- expect(() => 'key' in undefined).to.throw
912
- expect(hasOwn(undefined, 'key')).to.be.false
913
- ```
914
-
915
- ### isArray
916
-
917
- ```ts
918
- const isArray: (arg: unknown) => arg is unknown[];
919
- ```
920
-
921
- Checks whether the argument is an array.
922
-
923
- #### arg
924
-
925
- The value to check.
926
-
927
- #### Returns
928
-
929
- `true` if `arg` is an array, `false` otherwise.
930
-
931
- #### Usage Examples
932
-
933
- ```ts
934
- expect(isArray([])).to.be.true
935
-
936
- const fakeArray1 = Object.create({ constructor: Array })
937
-
938
- expect(isArray(fakeArray1)).to.be.false
939
-
940
- const fakeArray2 = Object.create({
941
- [Symbol.toStringTag]: Array.name,
942
- })
943
-
944
- expect(isArray(fakeArray2)).to.be.false
945
- ```
946
-
947
- ### isFiniteNumber
948
-
949
- ```ts
950
- const isFiniteNumber: (arg: unknown) => arg is number;
951
- ```
952
-
953
- Checks whether the argument is a finite number
954
- (excludes `±Infinity` and `NaN`).
955
-
956
- #### arg
957
-
958
- The value to check.
959
-
960
- #### Returns
961
-
962
- `true` if `arg` is a finite number, `false` otherwise.
963
-
964
- #### Usage Examples
965
-
966
- ```ts
967
- expect(isFiniteNumber(42)).to.be.true
968
-
969
- expect(isFiniteNumber(Number(42))).to.be.true
970
-
971
- expect(isFiniteNumber(new Number(42))).to.be.false
972
-
973
- expect(isFiniteNumber(NaN)).to.be.false
974
-
975
- expect(isFiniteNumber(Infinity)).to.be.false
976
- ```
977
-
978
- ### isInteger
979
-
980
- ```ts
981
- const isInteger: (arg: unknown) => arg is number;
982
- ```
983
-
984
- Checks whether the argument is an integer number.
985
-
986
- #### arg
987
-
988
- The value to check.
989
-
990
- #### Returns
991
-
992
- `true` if `arg` is an integer number, `false` otherwise.
993
-
994
- #### Usage Examples
995
-
996
- ```ts
997
- expect(isInteger(42)).to.be.true
998
-
999
- expect(isInteger(42.00000000000001)).to.be.false
1000
-
1001
- expect(isInteger(42.000000000000001)).to.be.true
1002
- // ^because of loss of precision
1003
-
1004
- expect(isInteger(Number(42))).to.be.true
1005
-
1006
- expect(isInteger(new Number(42))).to.be.false
1007
-
1008
- expect(isInteger(NaN)).to.be.false
1009
-
1010
- expect(isInteger(Infinity)).to.be.false
1011
- ```
1012
-
1013
- ### isNumber
1014
-
1015
- ```ts
1016
- const isNumber: (arg: unknown) => arg is number;
1017
- ```
1018
-
1019
- Checks whether the argument is of type `number`
1020
- (includes `NaN` and `±Infinity`).
1021
-
1022
- #### arg
1023
-
1024
- The value to check.
1025
-
1026
- #### Returns
1027
-
1028
- `true` if `typeof arg === 'number'`, `false` otherwise.
1029
-
1030
- #### Usage Examples
1031
-
1032
- ```ts
1033
- expect(isNumber(42)).to.be.true
1034
-
1035
- expect(isNumber(Number(42))).to.be.true
1036
-
1037
- expect(isNumber(new Number(42))).to.be.false
1038
-
1039
- expect(isNumber(NaN)).to.be.true
1040
-
1041
- expect(isNumber(Infinity)).to.be.true
1042
- ```
1043
-
1044
- ### isRecord
1045
-
1046
- ```ts
1047
- const isRecord: (arg: unknown) => arg is Record<PropertyKey, unknown>;
1048
- ```
1049
-
1050
- Checks whether the argument is a plain object
1051
- (not `null` and not an array).
1052
-
1053
- #### arg
1054
-
1055
- The value to check.
1056
-
1057
- #### Returns
1058
-
1059
- `true` if `arg` is a plain object, `false` otherwise.
1060
-
1061
- #### Usage Examples
1062
-
1063
- ```ts
1064
- expect(isRecord({})).to.be.true
1065
-
1066
- expect(isRecord([])).to.be.false
1067
-
1068
- expect(isRecord(Object.create(null))).to.be.true
1069
-
1070
- const fakeNumber1 = Object.create({ constructor: Number })
1071
-
1072
- expect(isRecord(fakeNumber1)).to.be.true
1073
-
1074
- const fakeNumber2 = Object.create({
1075
- [Symbol.toStringTag]: Number.name,
1076
- })
1077
-
1078
- expect(isRecord(fakeNumber2)).to.be.true
1079
-
1080
- expect(isRecord(new Number(42))).to.be.true
1081
-
1082
- expect(isRecord(new String('42'))).to.be.true
1083
-
1084
- class FooBar {}
1085
-
1086
- expect(isRecord(new FooBar())).to.be.true
1087
- ```
1088
-
1089
- ### isString
1090
-
1091
- ```ts
1092
- const isString: (arg: unknown) => arg is string;
1093
- ```
1094
-
1095
- Checks whether the argument is a string.
1096
-
1097
- #### arg
1098
-
1099
- The value to check.
1100
-
1101
- #### Returns
1102
-
1103
- `true` if `typeof arg === 'string'`, `false` otherwise.
1104
-
1105
- #### Usage Examples
1106
-
1107
- ```ts
1108
- expect(isString('42')).to.be.true
1109
- expect(isString(String('42'))).to.be.true
1110
- expect(isString(new String('42'))).to.be.false
1111
- ```
1112
-
1113
- ### jsOnParse
1114
-
1115
- ```ts
1116
- const jsOnParse: (handlers: Record<PropertyKey, Function>, text: string) => any;
1117
- ```
1118
-
1119
- Parses JSON with support for
1120
- handler-based value transformation (“JavaScript ON”).
1121
-
1122
- Objects with exactly one property
1123
- whose key exists in `handlers`
1124
- and whose value is an array
1125
- are replaced by invoking the corresponding handler
1126
- with the array elements as arguments.
1127
-
1128
- #### handlers
1129
-
1130
- An object mapping handler names to functions.
1131
-
1132
- #### text
1133
-
1134
- The JSON string to parse.
1135
-
1136
- #### Returns
1137
-
1138
- The parsed value with handler substitutions applied.
1139
-
1140
- #### Usage Examples
1141
-
1142
- ```ts
1143
- const handlers = {
1144
- $add: (a: number, b: number) => a + b,
1145
- $hello: (name: string) => `Hello ${name}!`,
1146
- $foo: () => 'bar',
1147
- }
1148
-
1149
- const actual1 = jsOnParse(
1150
- handlers,
1151
- `[
1152
- {
1153
- "$add": [1, 2]
1154
- },
1155
- {
1156
- "$hello": ["World"]
1157
- },
1158
- {
1159
- "nested": {
1160
- "$hello": ["nested World"]
1161
- },
1162
- "one": 1,
1163
- "two": 2
1164
- },
1165
- {
1166
- "$foo": []
1167
- },
1168
- {
1169
- "$foo": ["The parent object does not have exactly one property!"],
1170
- "one": 1,
1171
- "two": 2
1172
- }
1173
- ]`
1174
- )
1175
-
1176
- expect(actual1).to.deep.equal([
1177
- 3,
1178
- 'Hello World!',
1179
- {
1180
- nested: 'Hello nested World!',
1181
- one: 1,
1182
- two: 2,
1183
- },
1184
- 'bar',
1185
- {
1186
- $foo: [
1187
- 'The parent object does not have ' +
1188
- 'exactly one property!',
1189
- ],
1190
- one: 1,
1191
- two: 2,
1192
- },
1193
- ])
1194
-
1195
- const actual2 = jsOnParse(
1196
- { $notFunc: 'handler not being a function' } as any,
1197
- '{"$notFunc": [1, 2]}'
1198
- )
1199
-
1200
- expect(actual2).to.deep.equal({ $notFunc: [1, 2] })
1201
-
1202
- const actual3 = jsOnParse(
1203
- handlers,
1204
- '{"$unknown_handler_key": [1, 2]}'
1205
- )
1206
-
1207
- expect(actual3).to.deep.equal({
1208
- $unknown_handler_key: [1, 2],
1209
- })
1210
-
1211
- const actual4 = jsOnParse(
1212
- handlers,
1213
- '{"$add": {"not": "array"}}'
1214
- )
1215
-
1216
- expect(actual4).to.deep.equal({ $add: { not: 'array' } })
1217
- ```
1218
-
1219
889
  ### monokai
1220
890
 
1221
891
  ```ts
1222
- const monokai: CRoot;
892
+ const monokai: CNode;
1223
893
  ```
1224
894
 
1225
- A Monokai-inspired color scheme
1226
- for use with the [`c`](#c) helper
1227
- and [`nanolightTs`](#nanolightTs) tokenizer.
895
+ A Monokai-inspired color scheme for use with the [`c`](#c)
896
+ helper and [`nanolightTs`](#nanolightTs) tokenizer.
1228
897
 
1229
898
  ### nanolightTs
1230
899
 
@@ -1241,8 +910,8 @@ The source code string to tokenize.
1241
910
 
1242
911
  #### Returns
1243
912
 
1244
- An array of [`HArgs1`](#HArgs1) elements
1245
- suitable for rendering with [`h`](#h).
913
+ An array of [`HArgs1`](#HArgs1) elements suitable for
914
+ rendering with [`h`](#h).
1246
915
 
1247
916
  #### Usage Examples
1248
917
 
@@ -1252,11 +921,11 @@ const codeJs =
1252
921
  "{ 42: 42 }['42'] /* 42 */"
1253
922
 
1254
923
  expect(nanolightTs(codeJs)).to.deep.equal([
1255
- ['span', { class: 'keyword-1' }, 'const'],
924
+ ['span', { class: 'keyword1' }, 'const'],
1256
925
  ' ',
1257
926
  [
1258
927
  'span',
1259
- { class: 'identifier-4' },
928
+ { class: 'identifier4' },
1260
929
  'answerToLifeTheUniverseAndEverything',
1261
930
  ],
1262
931
  ' ',
@@ -1278,87 +947,19 @@ expect(nanolightTs(codeJs)).to.deep.equal([
1278
947
  ])
1279
948
  ```
1280
949
 
1281
- ### omit
1282
-
1283
- ```ts
1284
- const omit: <T, K extends keyof T>(ref: T, keys: unknown[]) => Omit<T, K>;
1285
- ```
1286
-
1287
- Creates a new object excluding the specified keys
1288
- from the source object.
1289
-
1290
- A runtime equivalent
1291
- of TypeScript’s `Omit<T, K>` utility type.
1292
- See also [`pick`](#pick).
1293
-
1294
- #### ref
1295
-
1296
- The source object.
1297
-
1298
- #### keys
1299
-
1300
- An array of keys to exclude from the result.
1301
-
1302
- #### Returns
1303
-
1304
- A new object without the specified keys.
1305
-
1306
- #### Usage Examples
1307
-
1308
- ```ts
1309
- const ref = { a: 42, b: '42', c: 17 }
1310
-
1311
- expect(omit(ref, ['c'])).to.deep.equal({ a: 42, b: '42' })
1312
- ```
1313
-
1314
- ### pick
1315
-
1316
- ```ts
1317
- const pick: <T, K extends keyof T>(ref: T, keys: unknown[]) => Pick<T, K>;
1318
- ```
1319
-
1320
- Creates a new object containing only the specified keys
1321
- from the source object.
1322
-
1323
- A runtime equivalent
1324
- of TypeScript’s `Pick<T, K>` utility type.
1325
- See also [`omit`](#omit).
1326
-
1327
- #### ref
1328
-
1329
- The source object.
1330
-
1331
- #### keys
1332
-
1333
- An array of keys to include in the result.
1334
-
1335
- #### Returns
1336
-
1337
- A new object with only the specified keys.
1338
-
1339
- #### Usage Examples
1340
-
1341
- ```ts
1342
- const ref = { a: 42, b: '42', c: 17 }
1343
-
1344
- expect(pick(ref, ['a', 'b'])).to.deep.equal({
1345
- a: 42,
1346
- b: '42',
1347
- })
1348
- ```
1349
-
1350
950
  ### rwd
1351
951
 
1352
952
  ```ts
1353
- const rwd: (root: CRoot, selector: string, cellWidthPx: number, cellHeightPx: number, ...specs: [number, number?, number?][]) => void;
953
+ const rwd: (root: CNode, selector: string, cellWidthPx: number, cellHeightPx: number, ...specs: [number, number?, number?][]) => void;
1354
954
  ```
1355
955
 
1356
- A responsive web design helper
1357
- that generates CSS rules for a grid-like layout.
956
+ A responsive web design helper that generates CSS rules
957
+ for a grid-like layout.
1358
958
 
1359
959
  #### root
1360
960
 
1361
- The CSS root object to populate (see [`c`](#c)).
961
+ The CSS root object to populate (see
962
+ [`c`](#c)).
1362
963
 
1363
964
  #### selector
1364
965
 
@@ -1374,18 +975,20 @@ The base cell height in pixels.
1374
975
 
1375
976
  #### specs
1376
977
 
1377
- An array of breakpoint specifications, each a tuple of:
1378
- - `maxWidth`: maximum number of cells per row
1379
- (defines the viewport breakpoint).
1380
- - `width` (optional, default `1`):
1381
- number of horizontal cells the element spans.
1382
- - `height` (optional, default `1`):
1383
- number of vertical cells the element spans.
978
+ An array of breakpoint specifications, each
979
+ a tuple of:
980
+
981
+ - `maxWidth`: maximum number of cells per row (defines
982
+ the viewport breakpoint).
983
+ - `width` (optional, default `1`): number of horizontal
984
+ cells the element spans.
985
+ - `height` (optional, default `1`): number of vertical
986
+ cells the element spans.
1384
987
 
1385
988
  #### Usage Examples
1386
989
 
1387
990
  ```ts
1388
- const style: CRoot = {
991
+ const style: CNode = {
1389
992
  body: {
1390
993
  margin: 0,
1391
994
  },
@@ -1439,28 +1042,32 @@ const s: {
1439
1042
  };
1440
1043
  ```
1441
1044
 
1442
- A lightweight *HyperScript*-style helper
1443
- for creating and modifying `SVGElement`s
1444
- (see also [`h`](#h)).
1045
+ A lightweight _HyperScript_-style helper for creating and
1046
+ modifying `SVGElement`s (see also [`h`](#h)).
1445
1047
 
1446
1048
  #### tagOrNode
1447
1049
 
1448
- If a `string`, it is treated as the tag name
1449
- for a new element.
1450
- If a `Node`, that node is modified in place.
1050
+ If a `string`, it is treated as the tag
1051
+ name for a new element. If a `Node`, that node is
1052
+ modified in place.
1451
1053
 
1452
1054
  #### args
1453
1055
 
1454
1056
  Additional arguments processed as follows:
1455
- - `Object`: maps attributes/properties.
1456
- Keys starting with `$` set element properties
1457
- (without the `$` prefix);
1458
- other keys set attributes via `setAttributeNS`.
1459
- A value of `false` removes the attribute.
1057
+
1058
+ - `Object`: maps attributes/properties. Keys starting
1059
+ with `$` set element properties (without the `$`
1060
+ prefix); if the value is a plain object, it is
1061
+ shallow-merged into the existing property instead of
1062
+ replacing it. Other keys set attributes: a key
1063
+ prefixed with `xlink:` sets the attribute via
1064
+ `setAttributeNS` in the XLink namespace; any other
1065
+ key (without a colon) sets it via `setAttribute`. A
1066
+ value of `false` removes the attribute.
1460
1067
  - `null`/`undefined`: ignored.
1461
1068
  - `Node`: appended as a child.
1462
- - `string`/`number`: converted to a `Text` node
1463
- and appended.
1069
+ - `string`/`number`: converted to a `Text` node and
1070
+ appended.
1464
1071
  - [`HArgs`](#HArgs) array: processed recursively.
1465
1072
 
1466
1073
  #### Returns
@@ -1501,20 +1108,28 @@ expect(svg4.getAttributeNS(XLINK_NS, 'title')).to.equal(
1501
1108
  const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement;
1502
1109
  ```
1503
1110
 
1504
- Shorthand for creating an SVG element
1505
- with a `<use>` child referencing an icon by ID.
1111
+ Shorthand for creating an SVG element with a `<use>`
1112
+ child referencing an icon by ID.
1506
1113
 
1507
1114
  Equivalent to:
1508
- `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
1115
+
1116
+ ```ts
1117
+ s(
1118
+ 'svg',
1119
+ ['use', { 'xlink:href': '#' + id }],
1120
+ ...args
1121
+ )
1122
+ ```
1509
1123
 
1510
1124
  #### id
1511
1125
 
1512
- The ID of the symbol to reference
1513
- (without the `#` prefix).
1126
+ The ID of the symbol to reference (without the
1127
+ `#` prefix).
1514
1128
 
1515
1129
  #### args
1516
1130
 
1517
- Additional arguments passed to the outer `<svg>` element.
1131
+ Additional arguments passed to the outer
1132
+ `<svg>` element.
1518
1133
 
1519
1134
  #### Returns
1520
1135
 
@@ -1606,10 +1221,9 @@ The date to use for the timestamp portion
1606
1221
  #### node
1607
1222
 
1608
1223
  A hexadecimal `string` for the node portion
1609
- (default: random).
1610
- Must match `/^[0-9a-f]*$/`;
1611
- it is trimmed to the last 12 characters
1612
- and left-padded with zeros if shorter.
1224
+ (default: random). Must match `/^[0-9a-f]*$/`; it is
1225
+ trimmed to the last 12 characters and left-padded with
1226
+ zeros if shorter.
1613
1227
 
1614
1228
  #### Returns
1615
1229
 
@@ -1672,36 +1286,48 @@ expect(
1672
1286
  const vivify: (ref: unknown) => any;
1673
1287
  ```
1674
1288
 
1675
- A Proxy-based helper for auto-vivification
1676
- of nested object structures.
1677
-
1678
- Accessing, assigning, or deleting any nested property
1679
- on the returned proxy
1680
- automatically creates intermediate objects
1681
- (or arrays for numeric-string keys matching
1682
- `^(0|[1-9]\d*)$`) as needed,
1683
- allowing deep operations without explicit null checks.
1684
-
1685
- Intermediates of the last level
1686
- in a get-only property chain are NOT auto-created.
1687
- For example, `vivify(ref).one.two` will create
1688
- `ref.one` as `{}`, but will NOT create `ref.one.two`.
1689
- Only when a deeper access, assignment, or deletion occurs
1690
- (e.g. `delete vivify(ref).one.two.three`
1691
- or `vivify(ref).one.two.three = 4`)
1692
- will `ref.one.two` be materialized.
1693
-
1694
- When traversal reaches a primitive value,
1695
- no auto-creation happens;
1696
- the primitive’s own property is returned instead
1697
- (e.g. accessing `.toString.name` on a number yields
1698
- `'toString'` without modifying the underlying structure).
1699
-
1700
- Deletion on a non-existing intermediate path
1701
- will auto-create intermediates up to the parent
1702
- of the deleted key
1703
- (e.g. `delete vivify(ref).one.two.three`
1704
- will create `ref.one.two` as `{}` if it does not exist).
1289
+ A Proxy-based helper for auto-vivification of nested
1290
+ object structures.
1291
+
1292
+ Accessing, assigning, or deleting any nested property on
1293
+ the returned proxy automatically creates intermediate
1294
+ objects (or arrays for numeric-string keys matching
1295
+ `^(0|[1-9]\d*)$`) as needed, allowing deep operations
1296
+ without explicit null checks.
1297
+
1298
+ Intermediates of the last level in a get-only property
1299
+ chain are NOT auto-created. For example,
1300
+ `vivify(ref).one.two` will create `ref.one` as `{}`, but
1301
+ will NOT create `ref.one.two`. Only when a deeper access,
1302
+ assignment, or deletion occurs, e.g.
1303
+
1304
+ ```ts
1305
+ delete vivify(ref).one.two.three
1306
+ ```
1307
+
1308
+ or
1309
+
1310
+ ```ts
1311
+ vivify(ref).one.two.three = 4
1312
+ ```
1313
+
1314
+ Will `ref.one.two` be materialized.
1315
+
1316
+ When traversal reaches a primitive value, no
1317
+ auto-creation happens; the primitive’s own property is
1318
+ returned instead (e.g. accessing `.toString.name` on a
1319
+ number yields `'toString'` without modifying the
1320
+ underlying structure).
1321
+
1322
+ Deletion on a non-existing intermediate path will
1323
+ auto-create intermediates up to the parent of the deleted
1324
+ key, e.g.
1325
+
1326
+ ```ts
1327
+ delete vivify(ref).one.two.three
1328
+ ```
1329
+
1330
+ Will create `ref.one.two` as `{}` if it does not exist.
1705
1331
 
1706
1332
  #### ref
1707
1333