@jackens/nnn 2026.9.1 → 2026.9.3

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 +209 -366
  2. package/nnn.js +10 -31
  3. package/package.json +1 -11
  4. package/readme.md +562 -527
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # nnn
2
2
 
3
- A collection of Jackens’ JavaScript helper utilities (version: `2026.9.1`).
3
+ A collection of Jackens’ JavaScript helper utilities (version: `2026.9.3`).
4
4
 
5
5
  ## Installation
6
6
 
@@ -18,22 +18,14 @@ npm i @jackens/nnn
18
18
 
19
19
  ```js
20
20
  import {
21
+ c,
22
+ createEscape,
21
23
  createTokenizer,
22
24
  csvParse,
23
25
  fixPlTypography,
24
26
  h,
25
- hasOwn,
26
- isArray,
27
- isFiniteNumber,
28
- isInteger,
29
- isNumber,
30
- isRecord,
31
- isString,
32
- jsOnParse,
33
27
  monokai,
34
28
  nanolightTs,
35
- omit,
36
- pick,
37
29
  rwd,
38
30
  s,
39
31
  svgUse,
@@ -44,74 +36,64 @@ import {
44
36
 
45
37
  ## Exports
46
38
 
39
+ - [`CNode`](#CNode):
40
+ Represents a CSS rule node for the [`c`](#c) helper. Keys
41
+ are CSS properties or nested selectors.
47
42
  - [`HArgs`](#HArgs):
48
43
  Tuple argument type for the [`h`](#h) and [`s`](#s)
49
44
  helpers.
50
45
  - [`HArgs1`](#HArgs1):
51
46
  Single argument type for the [`h`](#h) and [`s`](#s)
52
47
  helpers.
48
+ - [`c`](#c):
49
+ A minimal CSS-in-JS helper that converts a JavaScript
50
+ object hierarchy into a CSS string.
51
+ - [`createEscape`](#createEscape):
52
+ Creates a tag function for escaping interpolated values
53
+ in template literals.
53
54
  - [`createTokenizer`](#createTokenizer):
54
- A helper for building simple tokenizers
55
- (see also [`nanolightTs`](#nanolightTs)).
55
+ A helper for building simple tokenizers (see also
56
+ [`nanolightTs`](#nanolightTs)).
56
57
  - [`csvParse`](#csvParse):
57
- Parses a CSV string
58
- into a two-dimensional array of strings.
58
+ Parses a CSV string into a two-dimensional array of
59
+ strings.
59
60
  - [`fixPlTypography`](#fixPlTypography):
60
- Applies Polish-specific typographic corrections
61
- to a DOM subtree.
61
+ Applies Polish-specific typographic corrections to a DOM
62
+ subtree.
62
63
  - [`h`](#h):
63
- A lightweight *HyperScript*-style helper
64
- for creating and modifying `HTMLElement`s
65
- (see also [`s`](#s)).
66
- - [`hasOwn`](#hasOwn):
67
- Checks whether an object has the specified key
68
- as its own property.
69
- - [`isArray`](#isArray):
70
- Checks whether the argument is an array.
71
- - [`isFiniteNumber`](#isFiniteNumber):
72
- Checks whether the argument is a finite number
73
- (excludes `±Infinity` and `NaN`).
74
- - [`isInteger`](#isInteger):
75
- Checks whether the argument is an integer number.
76
- - [`isNumber`](#isNumber):
77
- Checks whether the argument is of type `number`
78
- (includes `NaN` and `±Infinity`).
79
- - [`isRecord`](#isRecord):
80
- Checks whether the argument is a plain object
81
- (not `null` and not an array).
82
- - [`isString`](#isString):
83
- Checks whether the argument is a string.
84
- - [`jsOnParse`](#jsOnParse):
85
- Parses JSON with support for
86
- handler-based value transformation (“JavaScript ON”).
64
+ A lightweight _HyperScript_-style helper for creating and
65
+ modifying `HTMLElement`s (see also [`s`](#s)).
87
66
  - [`monokai`](#monokai):
88
- A Monokai-inspired color scheme
89
- for use with the {@link c} helper
90
- and [`nanolightTs`](#nanolightTs) tokenizer.
67
+ A Monokai-inspired color scheme for use with the [`c`](#c)
68
+ helper and [`nanolightTs`](#nanolightTs) tokenizer.
91
69
  - [`nanolightTs`](#nanolightTs):
92
70
  A TypeScript/JavaScript syntax highlighting tokenizer
93
71
  built using [`createTokenizer`](#createTokenizer).
94
- - [`omit`](#omit):
95
- Creates a new object excluding the specified keys
96
- from the source object.
97
- - [`pick`](#pick):
98
- Creates a new object containing only the specified keys
99
- from the source object.
100
72
  - [`rwd`](#rwd):
101
- A responsive web design helper
102
- 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.
103
75
  - [`s`](#s):
104
- A lightweight *HyperScript*-style helper
105
- for creating and modifying `SVGElement`s
106
- (see also [`h`](#h)).
76
+ A lightweight _HyperScript_-style helper for creating and
77
+ modifying `SVGElement`s (see also [`h`](#h)).
107
78
  - [`svgUse`](#svgUse):
108
- Shorthand for creating an SVG element
109
- 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.
110
81
  - [`uuidV1`](#uuidV1):
111
82
  Generates a UUID v1 (time-based) identifier.
112
83
  - [`vivify`](#vivify):
113
- A Proxy-based helper for auto-vivification
114
- of nested object structures.
84
+ A Proxy-based helper for auto-vivification of nested
85
+ object structures.
86
+
87
+ ### CNode
88
+
89
+ ```ts
90
+ type CNode = {
91
+ [attributeOrSelector: string]: string | number | CNode | undefined;
92
+ };
93
+ ```
94
+
95
+ Represents a CSS rule node for the [`c`](#c) helper. Keys
96
+ are CSS properties or nested selectors.
115
97
 
116
98
  ### HArgs
117
99
 
@@ -131,46 +113,418 @@ type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string |
131
113
  Single argument type for the [`h`](#h) and [`s`](#s)
132
114
  helpers.
133
115
 
116
+ ### c
117
+
118
+ ```ts
119
+ const c: (node: CNode, splitter?: string) => string;
120
+ ```
121
+
122
+ A minimal CSS-in-JS helper that converts a JavaScript
123
+ object hierarchy into a CSS string.
124
+
125
+ #### node
126
+
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'`).
134
+
135
+ #### splitter
136
+
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`).
140
+
141
+ #### Returns
142
+
143
+ A CSS string representing the compiled rules.
144
+
145
+ #### Usage Examples
146
+
147
+ ```ts
148
+ const actual1 = c({
149
+ a: {
150
+ color: 'red',
151
+ margin: 1,
152
+ '.c': { margin: 2, padding: 2 },
153
+ padding: 1,
154
+ },
155
+ })
156
+
157
+ const expected1 = `
158
+ a{
159
+ color:red;
160
+ margin:1
161
+ }
162
+ a.c{
163
+ margin:2;
164
+ padding:2
165
+ }
166
+ a{
167
+ padding:1
168
+ }`.replace(/\n\s*/g, '')
169
+
170
+ expect(actual1).to.equal(expected1)
171
+
172
+ const actual2 = c({
173
+ a: {
174
+ '.b': {
175
+ color: 'red',
176
+ margin: 1,
177
+ '.c': { margin: 2, padding: 2 },
178
+ padding: 1,
179
+ },
180
+ },
181
+ })
182
+
183
+ const expected2 = `
184
+ a.b{
185
+ color:red;
186
+ margin:1
187
+ }
188
+ a.b.c{
189
+ margin:2;
190
+ padding:2
191
+ }
192
+ a.b{
193
+ padding:1
194
+ }`.replace(/\n\s*/g, '')
195
+
196
+ expect(actual2).to.equal(expected2)
197
+
198
+ const actual3 = c({
199
+ '@font-face$$1': {
200
+ 'font-family': 'Jackens',
201
+ src$$1: 'url(otf/jackens.otf)',
202
+ src$$2:
203
+ "url(otf/jackens.otf) format('opentype')," +
204
+ "url(svg/jackens.svg) format('svg')",
205
+ 'font-weight': 'normal',
206
+ 'font-style': 'normal',
207
+ },
208
+ '@font-face$$2': {
209
+ 'font-family': 'C64',
210
+ src: 'url(fonts/C64_Pro_Mono-STYLE.woff)',
211
+ },
212
+ '@keyframes spin': {
213
+ '0%': { transform: 'rotate(0deg)' },
214
+ '100%': { transform: 'rotate(360deg)' },
215
+ },
216
+ div: {
217
+ border: 'solid red 1px',
218
+ '.c1': { 'background-color': '#000' },
219
+ ' .c1': { 'background-color': 'black' },
220
+ '.c2': { 'background-color': 'rgb(0,0,0)' },
221
+ },
222
+ '@media(min-width:200px)': {
223
+ div: { margin: 0, padding: 0 },
224
+ span: { color: '#000' },
225
+ },
226
+ })
227
+
228
+ const expected3 = `
229
+ @font-face{
230
+ font-family:Jackens;
231
+ src:url(otf/jackens.otf);
232
+ src:url(otf/jackens.otf) format('opentype'),url(svg/jackens.svg) format('svg');
233
+ font-weight:normal;
234
+ font-style:normal
235
+ }
236
+ @font-face{
237
+ font-family:C64;
238
+ src:url(fonts/C64_Pro_Mono-STYLE.woff)
239
+ }
240
+ @keyframes spin{
241
+ 0%{
242
+ transform:rotate(0deg)
243
+ }
244
+ 100%{
245
+ transform:rotate(360deg)
246
+ }
247
+ }
248
+ div{
249
+ border:solid red 1px
250
+ }
251
+ div.c1{
252
+ background-color:#000
253
+ }
254
+ div .c1{
255
+ background-color:black
256
+ }
257
+ div.c2{
258
+ background-color:rgb(0,0,0)
259
+ }
260
+ @media(min-width:200px){
261
+ div{
262
+ margin:0;
263
+ padding:0
264
+ }
265
+ span{
266
+ color:#000
267
+ }
268
+ }`.replace(/\n\s*/g, '')
269
+
270
+ expect(actual3).to.equal(expected3)
271
+
272
+ const actual4 = c({
273
+ a: {
274
+ '.b,.c': {
275
+ margin: 1,
276
+ '.d': {
277
+ margin: 2,
278
+ },
279
+ },
280
+ },
281
+ })
282
+
283
+ const expected4 = `
284
+ a.b,a.c{
285
+ margin:1
286
+ }
287
+ a.b.d,a.c.d{
288
+ margin:2
289
+ }`.replace(/\n\s*/g, '')
290
+
291
+ expect(actual4).to.equal(expected4)
292
+
293
+ const actual5 = c({
294
+ '.b,.c': {
295
+ margin: 1,
296
+ '.d': {
297
+ margin: 2,
298
+ },
299
+ },
300
+ })
301
+
302
+ const expected5 = `
303
+ .b,.c{
304
+ margin:1
305
+ }
306
+ .b.d,.c.d{
307
+ margin:2
308
+ }`.replace(/\n\s*/g, '')
309
+
310
+ expect(actual5).to.equal(expected5)
311
+
312
+ const actual6 = c({
313
+ '.a,.b': {
314
+ margin: 1,
315
+ '.c,.d': {
316
+ margin: 2,
317
+ },
318
+ },
319
+ })
320
+
321
+ const expected6 = `
322
+ .a,.b{
323
+ margin:1
324
+ }
325
+ .a.c,.a.d,.b.c,.b.d{
326
+ margin:2
327
+ }`.replace(/\n\s*/g, '')
328
+
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)
418
+ ```
419
+
420
+ ### createEscape
421
+
422
+ ```ts
423
+ const createEscape: (escapeFn: (value: any) => string) => (template: TemplateStringsArray, ...values: unknown[]) => string;
424
+ ```
425
+
426
+ Creates a tag function for escaping interpolated values
427
+ in template literals.
428
+
429
+ #### escapeFn
430
+
431
+ A function that takes a value and returns
432
+ its escaped string representation.
433
+
434
+ #### Returns
435
+
436
+ A tag function that escapes interpolated values
437
+ using the provided escape function.
438
+
439
+ #### Usage Examples
440
+
441
+ ```ts
442
+ const escapeFn = (value: any): string =>
443
+ Array.isArray(value)
444
+ ? value.map(escapeFn).join(', ')
445
+ : value === true || value === false
446
+ ? `b'${+value}'`
447
+ : value instanceof Date
448
+ ? `'${value
449
+ .toISOString()
450
+ .replace(/^(.+)T(.+)\..*$/, '$1 $2')}'`
451
+ : Number.isFinite(value)
452
+ ? `${value}`
453
+ : typeof value === 'string'
454
+ ? `'${value.replace(/'/g, "''")}'`
455
+ : 'NULL'
456
+
457
+ const sql = createEscape(escapeFn)
458
+
459
+ const actual = sql`
460
+ SELECT *
461
+ FROM table_name
462
+ WHERE column_name IN (
463
+ ${[
464
+ true,
465
+ null,
466
+ undefined,
467
+ NaN,
468
+ Infinity,
469
+ 42,
470
+ '42',
471
+ "4'2",
472
+ /42/,
473
+ new Date(323325000000),
474
+ ]}
475
+ )`
476
+
477
+ const expected = `
478
+ SELECT *
479
+ FROM table_name
480
+ WHERE column_name IN (
481
+ b'1', NULL, NULL, NULL, NULL, 42, '42', '4''2', NULL, '1980-03-31 04:30:00'
482
+ )`
483
+
484
+ expect(actual).to.equal(expected)
485
+ ```
486
+
134
487
  ### createTokenizer
135
488
 
136
489
  ```ts
137
490
  const createTokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
138
491
  ```
139
492
 
140
- A helper for building simple tokenizers
141
- (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.
142
504
 
143
505
  #### decorator
144
506
 
145
- A function that wraps each matched chunk.
146
- It receives the matched text (`chunk`)
147
- and optionally the `metadata`
148
- associated with the pattern that produced the match.
149
- For unmatched text between patterns,
150
- `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`.
151
512
 
152
513
  #### specs
153
514
 
154
- An array of tuples `[metadata, pattern]` where:
515
+ An array of tuples `[metadata, pattern]`
516
+ where:
517
+
155
518
  - `metadata`: arbitrary data (e.g., a CSS class name)
156
- passed to `decorator` when the pattern matches.
157
- - `pattern`: a `string` or `RegExp`
158
- to match against the input.
519
+ passed to `decorator` when the pattern matches.
520
+ - `pattern`: a `string` or `RegExp` to match against the
521
+ input.
159
522
 
160
523
  #### Returns
161
524
 
162
525
  A tokenizer function that accepts a code string
163
526
  and returns an array of decorated tokens.
164
527
 
165
- #### Remarks
166
-
167
- 1. Matches starting at an earlier position
168
- take precedence.
169
- 2. Among matches at the same position,
170
- the longer one wins.
171
- 3. Among matches of the same position and length,
172
- the one defined earlier wins.
173
-
174
528
  #### Usage Examples
175
529
 
176
530
  ```ts
@@ -285,8 +639,8 @@ expect(result9).to.deep.equal(['a', 'a', 'b', 'b'])
285
639
  const csvParse: (csv: string, separator?: string) => string[][];
286
640
  ```
287
641
 
288
- Parses a CSV string
289
- into a two-dimensional array of strings.
642
+ Parses a CSV string into a two-dimensional array of
643
+ strings.
290
644
 
291
645
  Supports quoted fields with escaped double quotes (`""`).
292
646
  Carriage returns are normalized.
@@ -301,8 +655,8 @@ The field delimiter (default: `','`).
301
655
 
302
656
  #### Returns
303
657
 
304
- A 2D array where each inner array
305
- represents a row of fields.
658
+ A 2D array where each inner array represents a
659
+ row of fields.
306
660
 
307
661
  #### Usage Examples
308
662
 
@@ -329,23 +683,25 @@ expect(csvParse(text)).to.deep.equal([
329
683
  const fixPlTypography: (node: Node) => void;
330
684
  ```
331
685
 
332
- Applies Polish-specific typographic corrections
333
- to a DOM subtree.
686
+ Applies Polish-specific typographic corrections to a DOM
687
+ subtree.
334
688
 
335
689
  This function prevents orphaned conjunctions
336
690
  (single-letter words like “a”, “i”, “o”, “u”, “w”, “z”)
337
- from appearing at the end of a line
338
- by wrapping them with the following word
339
- in a non-breaking span.
340
- It also inserts zero-width spaces
341
- 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.
342
698
 
343
699
  #### node
344
700
 
345
- The root DOM node to process.
346
- All descendant text nodes are corrected recursively,
347
- except those inside `IFRAME`, `NOSCRIPT`, `PRE`,
348
- `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.
349
705
 
350
706
  #### Usage Examples
351
707
 
@@ -381,28 +737,33 @@ const h: {
381
737
  };
382
738
  ```
383
739
 
384
- A lightweight *HyperScript*-style helper
385
- for creating and modifying `HTMLElement`s
386
- (see also [`s`](#s)).
740
+ A lightweight _HyperScript_-style helper for creating and
741
+ modifying `HTMLElement`s (see also [`s`](#s)).
387
742
 
388
743
  #### tagOrNode
389
744
 
390
- If a `string`, it is treated as the tag name
391
- for a new element.
392
- 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.
393
748
 
394
749
  #### args
395
750
 
396
751
  Additional arguments processed as follows:
397
- - `Object`: maps attributes/properties.
398
- Keys starting with `$` set element properties
399
- (without the `$` prefix);
400
- other keys set attributes via `setAttribute`.
401
- 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.
402
763
  - `null`/`undefined`: ignored.
403
764
  - `Node`: appended as a child.
404
- - `string`/`number`: converted to a `Text` node
405
- and appended.
765
+ - `string`/`number`: converted to a `Text` node and
766
+ appended.
406
767
  - [`HArgs`](#HArgs) array: processed recursively.
407
768
 
408
769
  #### Returns
@@ -525,297 +886,14 @@ expect(elemWithNested.outerHTML).to.equal(
525
886
  )
526
887
  ```
527
888
 
528
- ### hasOwn
529
-
530
- ```ts
531
- const hasOwn: (ref: unknown, key: unknown) => boolean;
532
- ```
533
-
534
- Checks whether an object has the specified key
535
- as its own property.
536
-
537
- A null-safe wrapper around `Object.hasOwn`.
538
-
539
- #### ref
540
-
541
- The object to check.
542
-
543
- #### key
544
-
545
- The property key to look for.
546
-
547
- #### Returns
548
-
549
- `true` if `ref` is not nullish
550
- and has `key` as an own property, `false` otherwise.
551
-
552
- #### Usage Examples
553
-
554
- ```ts
555
- const obj = { 42: null, null: 'k,e,y', 'k,e,y': 42 }
556
-
557
- expect(42 in obj).to.be.true
558
- expect(hasOwn(obj, 42)).to.be.true
559
-
560
- expect('42' in obj).to.be.true
561
- expect(hasOwn(obj, '42')).to.be.true
562
-
563
- expect('null' in obj).to.be.true
564
- expect(hasOwn(obj, 'null')).to.be.true
565
-
566
- expect(null in obj).to.be.true
567
- expect(hasOwn(obj, null)).to.be.true
568
-
569
- expect('k,e,y' in obj).to.be.true
570
- expect(hasOwn(obj, 'k,e,y')).to.be.true
571
-
572
- expect(['k', 'e', 'y'] in obj).to.be.true
573
- expect(hasOwn(obj, ['k', 'e', 'y'])).to.be.true
574
-
575
- expect('toString' in obj).to.be.true
576
- expect(hasOwn(obj, 'toString')).to.be.false
577
-
578
- expect(() => 'key' in null).to.throw
579
- expect(hasOwn(null, 'key')).to.be.false
580
-
581
- expect(() => 'key' in undefined).to.throw
582
- expect(hasOwn(undefined, 'key')).to.be.false
583
- ```
584
-
585
- ### isArray
586
-
587
- ```ts
588
- const isArray: (arg: unknown) => arg is unknown[];
589
- ```
590
-
591
- Checks whether the argument is an array.
592
-
593
- #### arg
594
-
595
- The value to check.
596
-
597
- #### Returns
598
-
599
- `true` if `arg` is an array, `false` otherwise.
600
-
601
- #### Usage Examples
602
-
603
- ```ts
604
- expect(isArray([])).to.be.true
605
-
606
- const fakeArray1 = Object.create({ constructor: Array })
607
-
608
- expect(isArray(fakeArray1)).to.be.false
609
-
610
- const fakeArray2 = Object.create({
611
- [Symbol.toStringTag]: Array.name,
612
- })
613
-
614
- expect(isArray(fakeArray2)).to.be.false
615
- ```
616
-
617
- ### isFiniteNumber
618
-
619
- ```ts
620
- const isFiniteNumber: (arg: unknown) => arg is number;
621
- ```
622
-
623
- Checks whether the argument is a finite number
624
- (excludes `±Infinity` and `NaN`).
625
-
626
- #### arg
627
-
628
- The value to check.
629
-
630
- #### Returns
631
-
632
- `true` if `arg` is a finite number, `false` otherwise.
633
-
634
- #### Usage Examples
635
-
636
- ```ts
637
- expect(isFiniteNumber(42)).to.be.true
638
-
639
- expect(isFiniteNumber(Number(42))).to.be.true
640
-
641
- expect(isFiniteNumber(new Number(42))).to.be.false
642
-
643
- expect(isFiniteNumber(NaN)).to.be.false
644
-
645
- expect(isFiniteNumber(Infinity)).to.be.false
646
- ```
647
-
648
- ### isInteger
649
-
650
- ```ts
651
- const isInteger: (arg: unknown) => arg is number;
652
- ```
653
-
654
- Checks whether the argument is an integer number.
655
-
656
- #### arg
657
-
658
- The value to check.
659
-
660
- #### Returns
661
-
662
- `true` if `arg` is an integer number, `false` otherwise.
663
-
664
- #### Usage Examples
665
-
666
- ```ts
667
- expect(isInteger(42)).to.be.true
668
-
669
- expect(isInteger(42.00000000000001)).to.be.false
670
-
671
- expect(isInteger(42.000000000000001)).to.be.true
672
- // ^because of loss of precision
673
-
674
- expect(isInteger(Number(42))).to.be.true
675
-
676
- expect(isInteger(new Number(42))).to.be.false
677
-
678
- expect(isInteger(NaN)).to.be.false
679
-
680
- expect(isInteger(Infinity)).to.be.false
681
- ```
682
-
683
- ### isNumber
684
-
685
- ```ts
686
- const isNumber: (arg: unknown) => arg is number;
687
- ```
688
-
689
- Checks whether the argument is of type `number`
690
- (includes `NaN` and `±Infinity`).
691
-
692
- #### arg
693
-
694
- The value to check.
695
-
696
- #### Returns
697
-
698
- `true` if `typeof arg === 'number'`, `false` otherwise.
699
-
700
- #### Usage Examples
701
-
702
- ```ts
703
- expect(isNumber(42)).to.be.true
704
-
705
- expect(isNumber(Number(42))).to.be.true
706
-
707
- expect(isNumber(new Number(42))).to.be.false
708
-
709
- expect(isNumber(NaN)).to.be.true
710
-
711
- expect(isNumber(Infinity)).to.be.true
712
- ```
713
-
714
- ### isRecord
715
-
716
- ```ts
717
- const isRecord: (arg: unknown) => arg is Record<PropertyKey, unknown>;
718
- ```
719
-
720
- Checks whether the argument is a plain object
721
- (not `null` and not an array).
722
-
723
- #### arg
724
-
725
- The value to check.
726
-
727
- #### Returns
728
-
729
- `true` if `arg` is a plain object, `false` otherwise.
730
-
731
- #### Usage Examples
732
-
733
- ```ts
734
- expect(isRecord({})).to.be.true
735
-
736
- expect(isRecord([])).to.be.false
737
-
738
- expect(isRecord(Object.create(null))).to.be.true
739
-
740
- const fakeNumber1 = Object.create({ constructor: Number })
741
-
742
- expect(isRecord(fakeNumber1)).to.be.true
743
-
744
- const fakeNumber2 = Object.create({
745
- [Symbol.toStringTag]: Number.name,
746
- })
747
-
748
- expect(isRecord(fakeNumber2)).to.be.true
749
-
750
- expect(isRecord(new Number(42))).to.be.true
751
-
752
- expect(isRecord(new String('42'))).to.be.true
753
-
754
- class FooBar {}
755
-
756
- expect(isRecord(new FooBar())).to.be.true
757
- ```
758
-
759
- ### isString
760
-
761
- ```ts
762
- const isString: (arg: unknown) => arg is string;
763
- ```
764
-
765
- Checks whether the argument is a string.
766
-
767
- #### arg
768
-
769
- The value to check.
770
-
771
- #### Returns
772
-
773
- `true` if `typeof arg === 'string'`, `false` otherwise.
774
-
775
- #### Usage Examples
776
-
777
- ```ts
778
- expect(isString('42')).to.be.true
779
- expect(isString(String('42'))).to.be.true
780
- expect(isString(new String('42'))).to.be.false
781
- ```
782
-
783
- ### jsOnParse
784
-
785
- ```ts
786
- const jsOnParse: (handlers: Record<PropertyKey, Function>, text: string) => any;
787
- ```
788
-
789
- Parses JSON with support for
790
- handler-based value transformation (“JavaScript ON”).
791
-
792
- Objects with exactly one property
793
- whose key exists in `handlers`
794
- and whose value is an array
795
- are replaced by invoking the corresponding handler
796
- with the array elements as arguments.
797
-
798
- #### handlers
799
-
800
- An object mapping handler names to functions.
801
-
802
- #### text
803
-
804
- The JSON string to parse.
805
-
806
- #### Returns
807
-
808
- The parsed value with handler substitutions applied.
809
-
810
889
  ### monokai
811
890
 
812
891
  ```ts
813
892
  const monokai: CNode;
814
893
  ```
815
894
 
816
- A Monokai-inspired color scheme
817
- for use with the {@link c} helper
818
- and [`nanolightTs`](#nanolightTs) tokenizer.
895
+ A Monokai-inspired color scheme for use with the [`c`](#c)
896
+ helper and [`nanolightTs`](#nanolightTs) tokenizer.
819
897
 
820
898
  ### nanolightTs
821
899
 
@@ -832,8 +910,8 @@ The source code string to tokenize.
832
910
 
833
911
  #### Returns
834
912
 
835
- An array of [`HArgs1`](#HArgs1) elements
836
- suitable for rendering with [`h`](#h).
913
+ An array of [`HArgs1`](#HArgs1) elements suitable for
914
+ rendering with [`h`](#h).
837
915
 
838
916
  #### Usage Examples
839
917
 
@@ -869,87 +947,19 @@ expect(nanolightTs(codeJs)).to.deep.equal([
869
947
  ])
870
948
  ```
871
949
 
872
- ### omit
873
-
874
- ```ts
875
- const omit: <T, K extends keyof T>(ref: T, keys: unknown[]) => Omit<T, K>;
876
- ```
877
-
878
- Creates a new object excluding the specified keys
879
- from the source object.
880
-
881
- A runtime equivalent
882
- of TypeScript’s `Omit<T, K>` utility type.
883
- See also [`pick`](#pick).
884
-
885
- #### ref
886
-
887
- The source object.
888
-
889
- #### keys
890
-
891
- An array of keys to exclude from the result.
892
-
893
- #### Returns
894
-
895
- A new object without the specified keys.
896
-
897
- #### Usage Examples
898
-
899
- ```ts
900
- const ref = { a: 42, b: '42', c: 17 }
901
-
902
- expect(omit(ref, ['c'])).to.deep.equal({ a: 42, b: '42' })
903
- ```
904
-
905
- ### pick
906
-
907
- ```ts
908
- const pick: <T, K extends keyof T>(ref: T, keys: unknown[]) => Pick<T, K>;
909
- ```
910
-
911
- Creates a new object containing only the specified keys
912
- from the source object.
913
-
914
- A runtime equivalent
915
- of TypeScript’s `Pick<T, K>` utility type.
916
- See also [`omit`](#omit).
917
-
918
- #### ref
919
-
920
- The source object.
921
-
922
- #### keys
923
-
924
- An array of keys to include in the result.
925
-
926
- #### Returns
927
-
928
- A new object with only the specified keys.
929
-
930
- #### Usage Examples
931
-
932
- ```ts
933
- const ref = { a: 42, b: '42', c: 17 }
934
-
935
- expect(pick(ref, ['a', 'b'])).to.deep.equal({
936
- a: 42,
937
- b: '42',
938
- })
939
- ```
940
-
941
950
  ### rwd
942
951
 
943
952
  ```ts
944
953
  const rwd: (root: CNode, selector: string, cellWidthPx: number, cellHeightPx: number, ...specs: [number, number?, number?][]) => void;
945
954
  ```
946
955
 
947
- A responsive web design helper
948
- 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.
949
958
 
950
959
  #### root
951
960
 
952
- The CSS root object to populate (see {@link c}).
961
+ The CSS root object to populate (see
962
+ [`c`](#c)).
953
963
 
954
964
  #### selector
955
965
 
@@ -965,13 +975,15 @@ The base cell height in pixels.
965
975
 
966
976
  #### specs
967
977
 
968
- An array of breakpoint specifications, each a tuple of:
969
- - `maxWidth`: maximum number of cells per row
970
- (defines the viewport breakpoint).
971
- - `width` (optional, default `1`):
972
- number of horizontal cells the element spans.
973
- - `height` (optional, default `1`):
974
- 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.
975
987
 
976
988
  #### Usage Examples
977
989
 
@@ -1030,28 +1042,32 @@ const s: {
1030
1042
  };
1031
1043
  ```
1032
1044
 
1033
- A lightweight *HyperScript*-style helper
1034
- for creating and modifying `SVGElement`s
1035
- (see also [`h`](#h)).
1045
+ A lightweight _HyperScript_-style helper for creating and
1046
+ modifying `SVGElement`s (see also [`h`](#h)).
1036
1047
 
1037
1048
  #### tagOrNode
1038
1049
 
1039
- If a `string`, it is treated as the tag name
1040
- for a new element.
1041
- 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.
1042
1053
 
1043
1054
  #### args
1044
1055
 
1045
1056
  Additional arguments processed as follows:
1046
- - `Object`: maps attributes/properties.
1047
- Keys starting with `$` set element properties
1048
- (without the `$` prefix);
1049
- other keys set attributes via `setAttributeNS`.
1050
- 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.
1051
1067
  - `null`/`undefined`: ignored.
1052
1068
  - `Node`: appended as a child.
1053
- - `string`/`number`: converted to a `Text` node
1054
- and appended.
1069
+ - `string`/`number`: converted to a `Text` node and
1070
+ appended.
1055
1071
  - [`HArgs`](#HArgs) array: processed recursively.
1056
1072
 
1057
1073
  #### Returns
@@ -1092,20 +1108,28 @@ expect(svg4.getAttributeNS(XLINK_NS, 'title')).to.equal(
1092
1108
  const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement;
1093
1109
  ```
1094
1110
 
1095
- Shorthand for creating an SVG element
1096
- 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.
1097
1113
 
1098
1114
  Equivalent to:
1099
- `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
1115
+
1116
+ ```ts
1117
+ s(
1118
+ 'svg',
1119
+ ['use', { 'xlink:href': '#' + id }],
1120
+ ...args
1121
+ )
1122
+ ```
1100
1123
 
1101
1124
  #### id
1102
1125
 
1103
- The ID of the symbol to reference
1104
- (without the `#` prefix).
1126
+ The ID of the symbol to reference (without the
1127
+ `#` prefix).
1105
1128
 
1106
1129
  #### args
1107
1130
 
1108
- Additional arguments passed to the outer `<svg>` element.
1131
+ Additional arguments passed to the outer
1132
+ `<svg>` element.
1109
1133
 
1110
1134
  #### Returns
1111
1135
 
@@ -1197,10 +1221,9 @@ The date to use for the timestamp portion
1197
1221
  #### node
1198
1222
 
1199
1223
  A hexadecimal `string` for the node portion
1200
- (default: random).
1201
- Must match `/^[0-9a-f]*$/`;
1202
- it is trimmed to the last 12 characters
1203
- 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.
1204
1227
 
1205
1228
  #### Returns
1206
1229
 
@@ -1263,36 +1286,48 @@ expect(
1263
1286
  const vivify: (ref: unknown) => any;
1264
1287
  ```
1265
1288
 
1266
- A Proxy-based helper for auto-vivification
1267
- of nested object structures.
1268
-
1269
- Accessing, assigning, or deleting any nested property
1270
- on the returned proxy
1271
- automatically creates intermediate objects
1272
- (or arrays for numeric-string keys matching
1273
- `^(0|[1-9]\d*)$`) as needed,
1274
- allowing deep operations without explicit null checks.
1275
-
1276
- Intermediates of the last level
1277
- in a get-only property chain are NOT auto-created.
1278
- For example, `vivify(ref).one.two` will create
1279
- `ref.one` as `{}`, but will NOT create `ref.one.two`.
1280
- Only when a deeper access, assignment, or deletion occurs
1281
- (e.g. `delete vivify(ref).one.two.three`
1282
- or `vivify(ref).one.two.three = 4`)
1283
- will `ref.one.two` be materialized.
1284
-
1285
- When traversal reaches a primitive value,
1286
- no auto-creation happens;
1287
- the primitive’s own property is returned instead
1288
- (e.g. accessing `.toString.name` on a number yields
1289
- `'toString'` without modifying the underlying structure).
1290
-
1291
- Deletion on a non-existing intermediate path
1292
- will auto-create intermediates up to the parent
1293
- of the deleted key
1294
- (e.g. `delete vivify(ref).one.two.three`
1295
- 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.
1296
1331
 
1297
1332
  #### ref
1298
1333