@jackens/nnn 2026.9.7 → 2026.9.28

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 (5) hide show
  1. package/README.md +1099 -0
  2. package/nnn.d.ts +164 -243
  3. package/nnn.js +81 -114
  4. package/package.json +2 -13
  5. package/readme.md +0 -1541
package/readme.md DELETED
@@ -1,1541 +0,0 @@
1
- # nnn
2
-
3
- A collection of Jackens’ JavaScript helper utilities (version: `2026.9.7`).
4
-
5
- ## Installation
6
-
7
- ```sh
8
- bun i @jackens/nnn
9
- ```
10
-
11
- or
12
-
13
- ```sh
14
- npm i @jackens/nnn
15
- ```
16
-
17
- ## Usage
18
-
19
- ```js
20
- import {
21
- CNode,
22
- HArgs,
23
- HArgs1,
24
- c,
25
- createEscape,
26
- createTokenizer,
27
- csvParse,
28
- fixPlTypography,
29
- h,
30
- monokai,
31
- nanolightTs,
32
- rwd,
33
- s,
34
- svgUse,
35
- uuidV1,
36
- vivify,
37
- } from '@jackens/nnn' // or './node_modules/@jackens/nnn/nnn.js'
38
- ```
39
-
40
- ## Exports
41
-
42
- - [`CNode`](#CNode):
43
- Represents a CSS rule node for the [`c`](#c) helper. Keys
44
- are CSS properties or nested selectors.
45
- - [`HArgs`](#HArgs):
46
- Tuple argument type for the [`h`](#h) and [`s`](#s)
47
- helpers.
48
- - [`HArgs1`](#HArgs1):
49
- Single argument type for the [`h`](#h) and [`s`](#s)
50
- helpers.
51
- - [`c`](#c):
52
- A minimal CSS-in-JS helper that converts a JavaScript
53
- object hierarchy into a CSS string.
54
- - [`createEscape`](#createEscape):
55
- Creates a tag function for escaping interpolated values
56
- in template literals.
57
- - [`createTokenizer`](#createTokenizer):
58
- A helper for building simple tokenizers (see also
59
- [`nanolightTs`](#nanolightTs)).
60
- - [`csvParse`](#csvParse):
61
- Parses a CSV string into a two-dimensional array of
62
- strings.
63
- - [`fixPlTypography`](#fixPlTypography):
64
- Applies Polish-specific typographic corrections to a DOM
65
- subtree.
66
- - [`h`](#h):
67
- A lightweight _HyperScript_-style helper for creating and
68
- modifying `HTMLElement`s (see also [`s`](#s)).
69
- - [`monokai`](#monokai):
70
- A Monokai-inspired color scheme for use with the [`c`](#c)
71
- helper and [`nanolightTs`](#nanolightTs) tokenizer.
72
- - [`nanolightTs`](#nanolightTs):
73
- A TypeScript/JavaScript syntax highlighting tokenizer
74
- built using [`createTokenizer`](#createTokenizer).
75
- - [`rwd`](#rwd):
76
- A responsive web design helper that generates CSS rules
77
- for a grid-like layout.
78
- - [`s`](#s):
79
- A lightweight _HyperScript_-style helper for creating and
80
- modifying `SVGElement`s (see also [`h`](#h)).
81
- - [`svgUse`](#svgUse):
82
- Shorthand for creating an SVG element with a `<use>`
83
- child referencing an icon by ID.
84
- - [`uuidV1`](#uuidV1):
85
- Generates a UUID v1 (time-based) identifier.
86
- - [`vivify`](#vivify):
87
- A Proxy-based helper for auto-vivification of nested
88
- object structures.
89
-
90
- ### CNode
91
-
92
- ```ts
93
- type CNode = {
94
- [attributeOrSelector: string]: string | number | CNode | undefined;
95
- };
96
- ```
97
-
98
- Represents a CSS rule node for the [`c`](#c) helper. Keys
99
- are CSS properties or nested selectors.
100
-
101
- ### HArgs
102
-
103
- ```ts
104
- type HArgs = [string | Node, ...HArgs1[]];
105
- ```
106
-
107
- Tuple argument type for the [`h`](#h) and [`s`](#s)
108
- helpers.
109
-
110
- ### HArgs1
111
-
112
- ```ts
113
- type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string | number | HArgs;
114
- ```
115
-
116
- Single argument type for the [`h`](#h) and [`s`](#s)
117
- helpers.
118
-
119
- ### c
120
-
121
- ```ts
122
- const c: (node: CNode, splitter?: string) => string;
123
- ```
124
-
125
- A minimal CSS-in-JS helper that converts a JavaScript
126
- object hierarchy into a CSS string.
127
-
128
- #### node
129
-
130
- An object describing CSS rules. Keys are
131
- selectors or at-rules; values are either CSS property
132
- values or nested rule objects. A selector key may
133
- contain comma-separated alternatives (e.g. `'.b,.c'`);
134
- when nested, each alternative is combined with each
135
- parent alternative (cartesian product), producing a
136
- comma-joined selector list (e.g. `'a.b,a.c'`).
137
-
138
- #### splitter
139
-
140
- A delimiter used to create unique keys
141
- (default: `'$$'`). The substring from `splitter` to the
142
- end of a key is ignored (e.g., `src$$1` → `src`).
143
-
144
- #### Returns
145
-
146
- A CSS string representing the compiled rules.
147
-
148
- #### Usage Examples
149
-
150
- ```ts
151
- const actual = c({
152
- a: {
153
- color: 'red',
154
- margin: 1,
155
- '.c': { margin: 2, padding: 2 },
156
- padding: 1,
157
- },
158
- })
159
-
160
- const expected = `
161
- a{
162
- color:red;
163
- margin:1
164
- }
165
- a.c{
166
- margin:2;
167
- padding:2
168
- }
169
- a{
170
- padding:1
171
- }
172
- `.replace(/\n\s*/g, '')
173
-
174
- expect(actual).toBe(expected)
175
- ```
176
-
177
- ```ts
178
- const actual = c({
179
- a: {
180
- '.b': {
181
- color: 'red',
182
- margin: 1,
183
- '.c': { margin: 2, padding: 2 },
184
- padding: 1,
185
- },
186
- },
187
- })
188
-
189
- const expected = `
190
- a.b{
191
- color:red;
192
- margin:1
193
- }
194
- a.b.c{
195
- margin:2;
196
- padding:2
197
- }
198
- a.b{
199
- padding:1
200
- }
201
- `.replace(/\n\s*/g, '')
202
-
203
- expect(actual).toBe(expected)
204
- ```
205
-
206
- ```ts
207
- const actual = c({
208
- '@font-face$$1': {
209
- 'font-family': 'Jackens',
210
- src$$1: 'url(otf/jackens.otf)',
211
- src$$2:
212
- "url(otf/jackens.otf) format('opentype')," +
213
- "url(svg/jackens.svg) format('svg')",
214
- 'font-weight': 'normal',
215
- 'font-style': 'normal',
216
- },
217
- '@font-face$$2': {
218
- 'font-family': 'C64',
219
- src: 'url(fonts/C64_Pro_Mono-STYLE.woff)',
220
- },
221
- '@keyframes spin': {
222
- '0%': { transform: 'rotate(0deg)' },
223
- '100%': { transform: 'rotate(360deg)' },
224
- },
225
- div: {
226
- border: 'solid red 1px',
227
- '.c1': { 'background-color': '#000' },
228
- ' .c1': { 'background-color': 'black' },
229
- '.c2': { 'background-color': 'rgb(0,0,0)' },
230
- },
231
- '@media(min-width:200px)': {
232
- div: { margin: 0, padding: 0 },
233
- span: { color: '#000' },
234
- },
235
- })
236
-
237
- const expected = `
238
- @font-face{
239
- font-family:Jackens;
240
- src:url(otf/jackens.otf);
241
- src:url(otf/jackens.otf) format('opentype'),url(svg/jackens.svg) format('svg');
242
- font-weight:normal;
243
- font-style:normal
244
- }
245
- @font-face{
246
- font-family:C64;
247
- src:url(fonts/C64_Pro_Mono-STYLE.woff)
248
- }
249
- @keyframes spin{
250
- 0%{
251
- transform:rotate(0deg)
252
- }
253
- 100%{
254
- transform:rotate(360deg)
255
- }
256
- }
257
- div{
258
- border:solid red 1px
259
- }
260
- div.c1{
261
- background-color:#000
262
- }
263
- div .c1{
264
- background-color:black
265
- }
266
- div.c2{
267
- background-color:rgb(0,0,0)
268
- }
269
- @media(min-width:200px){
270
- div{
271
- margin:0;
272
- padding:0
273
- }
274
- span{
275
- color:#000
276
- }
277
- }
278
- `.replace(/\n\s*/g, '')
279
-
280
- expect(actual).toBe(expected)
281
- ```
282
-
283
- ```ts
284
- const actual = c({
285
- a: {
286
- '.b,.c': {
287
- margin: 1,
288
- '.d': {
289
- margin: 2,
290
- },
291
- },
292
- },
293
- })
294
-
295
- const expected = `
296
- a.b,a.c{
297
- margin:1
298
- }
299
- a.b.d,a.c.d{
300
- margin:2
301
- }
302
- `.replace(/\n\s*/g, '')
303
-
304
- expect(actual).toBe(expected)
305
- ```
306
-
307
- ```ts
308
- const actual = c({
309
- '.b,.c': {
310
- margin: 1,
311
- '.d': {
312
- margin: 2,
313
- },
314
- },
315
- })
316
-
317
- const expected = `
318
- .b,.c{
319
- margin:1
320
- }
321
- .b.d,.c.d{
322
- margin:2
323
- }
324
- `.replace(/\n\s*/g, '')
325
-
326
- expect(actual).toBe(expected)
327
- ```
328
-
329
- ```ts
330
- const actual = c({
331
- '.a,.b': {
332
- margin: 1,
333
- '.c,.d': {
334
- margin: 2,
335
- },
336
- },
337
- })
338
-
339
- const expected = `
340
- .a,.b{
341
- margin:1
342
- }
343
- .a.c,.a.d,.b.c,.b.d{
344
- margin:2
345
- }
346
- `.replace(/\n\s*/g, '')
347
-
348
- expect(actual).toBe(expected)
349
- ```
350
-
351
- ```ts
352
- const actual = c({
353
- ':root$$monokai': {
354
- '--': {
355
- bg: '#faf4f2',
356
- fg: '#29242a',
357
- comment: '#918c8e',
358
- identifier: {
359
- 1: '#7058be',
360
- 2: '#269d69',
361
- 3: '#1c8ca8',
362
- 4: '#29242a',
363
- },
364
- keyword: {
365
- 1: '#e14775',
366
- 2: '#7058be',
367
- 3: '#1c8ca8',
368
- },
369
- number: '#7058be',
370
- operator: '#e14775',
371
- punctuation: '#918c8e',
372
- string: '#cc7a0a',
373
- },
374
- },
375
- '@media only screen and (prefers-color-scheme: dark)$$monokai':
376
- {
377
- ':root': {
378
- '--': {
379
- bg: '#2d2a2e',
380
- fg: '#fcfcfa',
381
- comment: '#727072',
382
- identifier: {
383
- 1: '#ae81ff',
384
- 2: '#a9dc76',
385
- 3: '#66d9ef',
386
- 4: '#fcfcfa',
387
- },
388
- keyword: {
389
- 1: '#ff6188',
390
- 2: '#ae81ff',
391
- 3: '#66d9ef',
392
- },
393
- number: '#ae81ff',
394
- operator: '#ff6188',
395
- punctuation: '#727072',
396
- string: '#ffd866',
397
- },
398
- },
399
- },
400
- })
401
-
402
- const expected = `
403
- :root{
404
- --bg:#faf4f2;
405
- --fg:#29242a;
406
- --comment:#918c8e;
407
- --identifier1:#7058be;
408
- --identifier2:#269d69;
409
- --identifier3:#1c8ca8;
410
- --identifier4:#29242a;
411
- --keyword1:#e14775;
412
- --keyword2:#7058be;
413
- --keyword3:#1c8ca8;
414
- --number:#7058be;
415
- --operator:#e14775;
416
- --punctuation:#918c8e;
417
- --string:#cc7a0a
418
- }
419
- @media only screen and (prefers-color-scheme: dark){
420
- :root{
421
- --bg:#2d2a2e;
422
- --fg:#fcfcfa;
423
- --comment:#727072;
424
- --identifier1:#ae81ff;
425
- --identifier2:#a9dc76;
426
- --identifier3:#66d9ef;
427
- --identifier4:#fcfcfa;
428
- --keyword1:#ff6188;
429
- --keyword2:#ae81ff;
430
- --keyword3:#66d9ef;
431
- --number:#ae81ff;
432
- --operator:#ff6188;
433
- --punctuation:#727072;
434
- --string:#ffd866
435
- }
436
- }
437
- `.replace(/\n\s*/g, '')
438
-
439
- expect(actual).toBe(expected)
440
- ```
441
-
442
- ### createEscape
443
-
444
- ```ts
445
- const createEscape: (escapeFn: (value: any) => string) => (template: TemplateStringsArray, ...values: unknown[]) => string;
446
- ```
447
-
448
- Creates a tag function for escaping interpolated values
449
- in template literals.
450
-
451
- #### escapeFn
452
-
453
- A function that takes a value and returns
454
- its escaped string representation.
455
-
456
- #### Returns
457
-
458
- A tag function that escapes interpolated values
459
- using the provided escape function.
460
-
461
- #### Usage Examples
462
-
463
- ```ts
464
- const escapeFn = (value: any): string =>
465
- Array.isArray(value)
466
- ? value.map(escapeFn).join(', ')
467
- : value === true || value === false
468
- ? `b'${+value}'`
469
- : value instanceof Date
470
- ? `'${value
471
- .toISOString()
472
- .replace(/^(.+)T(.+)\..*$/, '$1 $2')}'`
473
- : Number.isFinite(value)
474
- ? `${value}`
475
- : typeof value === 'string'
476
- ? `'${value.replace(/'/g, "''")}'`
477
- : 'NULL'
478
-
479
- const sql = createEscape(escapeFn)
480
-
481
- const actual = sql`
482
- SELECT *
483
- FROM table_name
484
- WHERE column_name IN (
485
- ${[
486
- true,
487
- null,
488
- undefined,
489
- NaN,
490
- Infinity,
491
- 42,
492
- '42',
493
- "4'2",
494
- /42/,
495
- new Date(323325000000),
496
- ]}
497
- )`
498
-
499
- const expected = `
500
- SELECT *
501
- FROM table_name
502
- WHERE column_name IN (
503
- b'1', NULL, NULL, NULL, NULL, 42, '42', '4''2', NULL, '1980-03-31 04:30:00'
504
- )`
505
-
506
- expect(actual).toBe(expected)
507
- ```
508
-
509
- ### createTokenizer
510
-
511
- ```ts
512
- const createTokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
513
- ```
514
-
515
- A helper for building simple tokenizers (see also
516
- [`nanolightTs`](#nanolightTs)).
517
-
518
- #### Remarks
519
-
520
- 1. Matches starting at an earlier position take
521
- precedence.
522
- 2. Among matches at the same position, the longer one
523
- wins.
524
- 3. Among matches of the same position and length, the one
525
- defined earlier wins.
526
-
527
- #### decorator
528
-
529
- A function that wraps each matched
530
- chunk. It receives the matched text (`chunk`) and
531
- optionally the `metadata` associated with the pattern
532
- that produced the match. For unmatched text between
533
- patterns, `metadata` is `undefined`.
534
-
535
- #### specs
536
-
537
- An array of tuples `[metadata, pattern]`
538
- where:
539
-
540
- - `metadata`: arbitrary data (e.g., a CSS class name)
541
- passed to `decorator` when the pattern matches.
542
- - `pattern`: a `string` or `RegExp` to match against the
543
- input.
544
-
545
- #### Returns
546
-
547
- A tokenizer function that accepts a code string
548
- and returns an array of decorated tokens.
549
-
550
- #### Usage Examples
551
-
552
- ```ts
553
- const tokenizer = createTokenizer(
554
- (chunk, metadata) => ({ chunk, metadata }),
555
- ['keyword', /\b(if|else|for)\b/],
556
- ['string', /"[^"]*"/]
557
- )
558
-
559
- const result = tokenizer('if "hello" else "world"')
560
-
561
- expect(result).toEqual([
562
- { chunk: 'if', metadata: 'keyword' },
563
- { chunk: ' ', metadata: undefined },
564
- { chunk: '"hello"', metadata: 'string' },
565
- { chunk: ' ', metadata: undefined },
566
- { chunk: 'else', metadata: 'keyword' },
567
- { chunk: ' ', metadata: undefined },
568
- { chunk: '"world"', metadata: 'string' },
569
- ])
570
- ```
571
-
572
- ```ts
573
- const tokenizer = createTokenizer(
574
- (chunk, metadata) => `${metadata}:${chunk}`,
575
- ['tag', 'BEGIN'],
576
- ['end', 'END']
577
- )
578
-
579
- const result = tokenizer('aBEGINbENDc')
580
-
581
- expect(result).toEqual([
582
- 'undefined:a',
583
- 'tag:BEGIN',
584
- 'undefined:b',
585
- 'end:END',
586
- 'undefined:c',
587
- ])
588
- ```
589
-
590
- ```ts
591
- const tokenizer = createTokenizer(
592
- chunk => chunk,
593
- ['test', /test/]
594
- )
595
-
596
- const result = tokenizer('')
597
-
598
- expect(result).toEqual([])
599
- ```
600
-
601
- ```ts
602
- const tokenizer = createTokenizer(
603
- (chunk, metadata) => ({ chunk, metadata }),
604
- ['start', /^test/]
605
- )
606
-
607
- const result = tokenizer('test here')
608
-
609
- expect(result).toEqual([
610
- { chunk: 'test', metadata: 'start' },
611
- { chunk: ' here', metadata: undefined },
612
- ])
613
- ```
614
-
615
- ```ts
616
- const tokenizer = createTokenizer(
617
- (chunk, metadata) => metadata,
618
- ['later', /x/],
619
- ['earlier', /y/]
620
- )
621
-
622
- const result = tokenizer('yx')
623
-
624
- expect(result).toEqual(['earlier', 'later'])
625
- ```
626
-
627
- ```ts
628
- const tokenizer = createTokenizer(
629
- chunk => chunk,
630
- ['short', 'a'],
631
- ['long', 'abc']
632
- )
633
-
634
- const result = tokenizer('abc')
635
-
636
- expect(result).toEqual(['abc'])
637
- ```
638
-
639
- ```ts
640
- const tokenizer = createTokenizer(
641
- chunk => chunk,
642
- ['empty', ''],
643
- ['word', /\w+/]
644
- )
645
-
646
- const result = tokenizer('hello')
647
-
648
- expect(result).toEqual(['hello'])
649
- ```
650
-
651
- ```ts
652
- const tokenizer = createTokenizer(
653
- chunk => chunk,
654
- ['test', /xyz/]
655
- )
656
-
657
- const result = tokenizer('abc')
658
-
659
- expect(result).toEqual(['abc'])
660
- ```
661
-
662
- ```ts
663
- const tokenizer = createTokenizer(
664
- (_chunk, metadata) => metadata,
665
- ['a', 'a'],
666
- ['b', 'b']
667
- )
668
-
669
- const result = tokenizer('aabb')
670
-
671
- expect(result).toEqual(['a', 'a', 'b', 'b'])
672
- ```
673
-
674
- ### csvParse
675
-
676
- ```ts
677
- const csvParse: (csv: string, separator?: string) => string[][];
678
- ```
679
-
680
- Parses a CSV string into a two-dimensional array of
681
- strings.
682
-
683
- Supports quoted fields with escaped double quotes (`""`).
684
- Carriage returns are normalized.
685
-
686
- #### csv
687
-
688
- The CSV string to parse.
689
-
690
- #### separator
691
-
692
- The field delimiter (default: `','`).
693
-
694
- #### Returns
695
-
696
- A 2D array where each inner array represents a
697
- row of fields.
698
-
699
- #### Usage Examples
700
-
701
- ```ts
702
- const text = `"aaa
703
- ""aaa""
704
- aaa",bbb, "ccc,ccc"
705
- "xxx,xxx", "yyy
706
- yyy",zzz
707
- 42 , "42" , 17
708
-
709
- `
710
-
711
- expect(csvParse(text)).toEqual([
712
- ['aaa\n"aaa"\naaa', 'bbb', 'ccc,ccc'],
713
- ['xxx,xxx', 'yyy\nyyy', 'zzz'],
714
- [' 42 ', '42', ' 17'],
715
- ])
716
- ```
717
-
718
- ### fixPlTypography
719
-
720
- ```ts
721
- const fixPlTypography: (node: Node) => void;
722
- ```
723
-
724
- Applies Polish-specific typographic corrections to a DOM
725
- subtree.
726
-
727
- This function prevents orphaned conjunctions
728
- (single-letter words like “a”, “i”, “o”, “u”, “w”, “z”)
729
- and the em dash (“—”) from appearing at the end of a
730
- line. It wraps each such character together with the
731
- whitespace that follows it in a `white-space:nowrap`
732
- span, removing the only line-break opportunity between it
733
- and the next word so the two stay glued together. It also
734
- inserts zero-width spaces after slashes and dots to allow
735
- line breaks.
736
-
737
- #### node
738
-
739
- The root DOM node to process. All descendant
740
- text nodes are corrected recursively, except those
741
- inside `IFRAME`, `NOSCRIPT`, `PRE`, `SCRIPT`, `STYLE`,
742
- or `TEXTAREA` elements.
743
-
744
- #### Usage Examples
745
-
746
- ```ts
747
- const p = h(
748
- 'p',
749
- 'Pchnąć w tę łódź jeża lub ośm skrzyń fig ' +
750
- '(zob. https://pl.wikipedia.org/wiki/Pangram).',
751
- ['br'],
752
- ['b', 'Zażółć gęślą jaźń.']
753
- )
754
-
755
- fixPlTypography(p)
756
-
757
- expect(p.innerHTML).toEqual(
758
- 'Pchnąć ' +
759
- '<span style="white-space:nowrap">w </span>' +
760
- 'tę łódź jeża lub ośm skrzyń fig ' +
761
- '(zob. https://\u200Bpl.\u200Bwikipedia.\u200Borg/' +
762
- '\u200Bwiki/\u200BPangram).' +
763
- '<br>' +
764
- '<b>Zażółć gęślą jaźń.</b>'
765
- )
766
- ```
767
-
768
- ### h
769
-
770
- ```ts
771
- const h: {
772
- <T extends keyof HTMLElementTagNameMap>(tag: T, ...args1: HArgs1[]): HTMLElementTagNameMap[T];
773
- <N extends Node>(node: N, ...args1: HArgs1[]): N;
774
- (tagOrNode: string | Node, ...args1: HArgs1[]): Node;
775
- };
776
- ```
777
-
778
- A lightweight _HyperScript_-style helper for creating and
779
- modifying `HTMLElement`s (see also [`s`](#s)).
780
-
781
- #### tagOrNode
782
-
783
- If a `string`, it is treated as the tag
784
- name for a new element. If a `Node`, that node is
785
- modified in place.
786
-
787
- #### args
788
-
789
- Additional arguments processed as follows:
790
-
791
- - `Object`: maps attributes/properties. Keys starting
792
- with `$` set element properties (without the `$`
793
- prefix); if the value is a plain object, it is
794
- shallow-merged into the existing property instead of
795
- replacing it (useful e.g. for `$style`). Other keys
796
- set attributes: a key prefixed with `xlink:` sets the
797
- attribute via `setAttributeNS` in the XLink
798
- namespace; any other key (without a colon) sets it
799
- via `setAttribute`. A value of `false` removes the
800
- attribute.
801
- - `null`/`undefined`: ignored.
802
- - `Node`: appended as a child.
803
- - `string`/`number`: converted to a `Text` node and
804
- appended.
805
- - [`HArgs`](#HArgs) array: processed recursively.
806
-
807
- #### Returns
808
-
809
- The created or modified `HTMLElement`.
810
-
811
- #### Usage Examples
812
-
813
- ```ts
814
- const b = h('b')
815
-
816
- expect(b.outerHTML).toBe('<b></b>')
817
-
818
- const i = h('i', 'text')
819
-
820
- h(b, i)
821
-
822
- expect(i.outerHTML).toBe('<i>text</i>')
823
-
824
- expect(b.outerHTML).toBe('<b><i>text</i></b>')
825
-
826
- h(i, { $className: 'some class' })
827
-
828
- expect(i.outerHTML).toBe('<i class="some class">text</i>')
829
-
830
- expect(b.outerHTML).toBe(
831
- '<b><i class="some class">text</i></b>'
832
- )
833
- ```
834
-
835
- ```ts
836
- expect(h('span', 'text').outerHTML).toBe(
837
- '<span>text</span>'
838
- )
839
-
840
- expect(h('span', { $innerText: 'text' }).outerHTML).toBe(
841
- '<span>text</span>'
842
- )
843
-
844
- expect(h('span', '42').outerHTML).toBe('<span>42</span>')
845
-
846
- expect(h('span', 42).outerHTML).toBe('<span>42</span>')
847
- ```
848
-
849
- ```ts
850
- expect(
851
- h('div', { style: 'margin:0;padding:0' }).outerHTML
852
- ).toBe('<div style="margin:0;padding:0"></div>')
853
-
854
- expect(
855
- h('div', { $style: 'margin:0;padding:0' }).outerHTML
856
- ).toBe('<div style="margin: 0px; padding: 0px;"></div>')
857
-
858
- expect(
859
- h('div', { $style: { margin: 0, padding: 0 } })
860
- .outerHTML
861
- ).toBe('<div style="margin: 0px; padding: 0px;"></div>')
862
- ```
863
-
864
- ```ts
865
- const input1 = h('input', { value: 42 })
866
- const input2 = h('input', { $value: '42' })
867
-
868
- expect(input1.value).toBe('42')
869
- expect(input2.value).toBe('42')
870
-
871
- expect(input1.outerHTML).toBe('<input value="42">')
872
- expect(input2.outerHTML).toBe('<input>')
873
- ```
874
-
875
- ```ts
876
- const checkbox1 = h('input', {
877
- type: 'checkbox',
878
- checked: true,
879
- })
880
-
881
- const checkbox2 = h('input', {
882
- type: 'checkbox',
883
- $checked: true,
884
- })
885
-
886
- expect(checkbox1.checked).toBe(true)
887
- expect(checkbox2.checked).toBe(true)
888
-
889
- expect(checkbox1.outerHTML).toBe(
890
- '<input type="checkbox" checked="">'
891
- )
892
-
893
- expect(checkbox2.outerHTML).toBe(
894
- '<input type="checkbox">'
895
- )
896
- ```
897
-
898
- ```ts
899
- const div = h('div')
900
-
901
- expect(div.key).toBeUndefined()
902
-
903
- h(div, { $key: { one: 1 } })
904
-
905
- expect(div.key).toEqual({ one: 1 })
906
-
907
- h(div, { $key: { two: 2 } })
908
-
909
- expect(div.key).toEqual({ one: 1, two: 2 })
910
- ```
911
-
912
- ```ts
913
- const elemWithClass = h('div', { class: 'test' })
914
-
915
- expect(elemWithClass.getAttribute('class')).toBe('test')
916
-
917
- const elemWithText = h('div', 'initial')
918
-
919
- h(elemWithText, ' more')
920
-
921
- expect(elemWithText.outerHTML).toBe(
922
- '<div>initial more</div>'
923
- )
924
-
925
- const elemWithNested = h(
926
- 'div',
927
- ['span', 'hello'],
928
- ['b', 'world']
929
- )
930
-
931
- expect(elemWithNested.outerHTML).toBe(
932
- '<div><span>hello</span><b>world</b></div>'
933
- )
934
- ```
935
-
936
- ### monokai
937
-
938
- ```ts
939
- const monokai: CNode;
940
- ```
941
-
942
- A Monokai-inspired color scheme for use with the [`c`](#c)
943
- helper and [`nanolightTs`](#nanolightTs) tokenizer.
944
-
945
- ### nanolightTs
946
-
947
- ```ts
948
- const nanolightTs: (code: string) => HArgs1[];
949
- ```
950
-
951
- A TypeScript/JavaScript syntax highlighting tokenizer
952
- built using [`createTokenizer`](#createTokenizer).
953
-
954
- #### code
955
-
956
- The source code string to tokenize.
957
-
958
- #### Returns
959
-
960
- An array of [`HArgs1`](#HArgs1) elements suitable for
961
- rendering with [`h`](#h).
962
-
963
- #### Usage Examples
964
-
965
- ```ts
966
- const codeJs =
967
- 'const answerToLifeTheUniverseAndEverything = ' +
968
- "{ 42: 42 }['42'] /* 42 */"
969
-
970
- expect(nanolightTs(codeJs)).toEqual([
971
- ['span', { class: 'keyword1' }, 'const'],
972
- ' ',
973
- [
974
- 'span',
975
- { class: 'identifier4' },
976
- 'answerToLifeTheUniverseAndEverything',
977
- ],
978
- ' ',
979
- ['span', { class: 'operator' }, '='],
980
- ' ',
981
- ['span', { class: 'punctuation' }, '{'],
982
- ' ',
983
- ['span', { class: 'number' }, '42'],
984
- ['span', { class: 'operator' }, ':'],
985
- ' ',
986
- ['span', { class: 'number' }, '42'],
987
- ' ',
988
- ['span', { class: 'punctuation' }, '}'],
989
- ['span', { class: 'punctuation' }, '['],
990
- ['span', { class: 'string' }, "'42'"],
991
- ['span', { class: 'punctuation' }, ']'],
992
- ' ',
993
- ['span', { class: 'comment' }, '/* 42 */'],
994
- ])
995
- ```
996
-
997
- ### rwd
998
-
999
- ```ts
1000
- const rwd: (root: CNode, selector: string, cellWidthPx: number, cellHeightPx: number, ...specs: [number, number?, number?][]) => void;
1001
- ```
1002
-
1003
- A responsive web design helper that generates CSS rules
1004
- for a grid-like layout.
1005
-
1006
- #### root
1007
-
1008
- The CSS root object to populate (see
1009
- [`c`](#c)).
1010
-
1011
- #### selector
1012
-
1013
- The CSS selector for the grid item.
1014
-
1015
- #### cellWidthPx
1016
-
1017
- The base cell width in pixels.
1018
-
1019
- #### cellHeightPx
1020
-
1021
- The base cell height in pixels.
1022
-
1023
- #### specs
1024
-
1025
- An array of breakpoint specifications, each
1026
- a tuple of:
1027
-
1028
- - `maxWidth`: maximum number of cells per row (defines
1029
- the viewport breakpoint).
1030
- - `width` (optional, default `1`): number of horizontal
1031
- cells the element spans.
1032
- - `height` (optional, default `1`): number of vertical
1033
- cells the element spans.
1034
-
1035
- #### Usage Examples
1036
-
1037
- ```ts
1038
- const style: CNode = {
1039
- body: {
1040
- margin: 0,
1041
- },
1042
- '.r6': {
1043
- border: 'solid red 1px',
1044
- '.no-border': {
1045
- border: 'none',
1046
- },
1047
- },
1048
- }
1049
-
1050
- rwd(style, '.r6', 200, 50, [6], [3], [1, 1, 2])
1051
-
1052
- expect(style).toEqual({
1053
- body: {
1054
- margin: 0,
1055
- },
1056
- '.r6': {
1057
- border: 'solid red 1px',
1058
- '.no-border': {
1059
- border: 'none',
1060
- },
1061
- boxSizing: 'border-box',
1062
- display: 'block',
1063
- float: 'left',
1064
- width: '100%',
1065
- height: '100px',
1066
- },
1067
- '@media(min-width:600px)': {
1068
- '.r6': {
1069
- width: 'calc(100% / 3)',
1070
- height: '50px',
1071
- },
1072
- },
1073
- '@media(min-width:1200px)': {
1074
- '.r6': {
1075
- width: 'calc(50% / 3)',
1076
- height: '50px',
1077
- },
1078
- },
1079
- })
1080
- ```
1081
-
1082
- ### s
1083
-
1084
- ```ts
1085
- const s: {
1086
- <T extends keyof SVGElementTagNameMap>(tag: T, ...args1: HArgs1[]): SVGElementTagNameMap[T];
1087
- <N extends Node>(node: N, ...args1: HArgs1[]): N;
1088
- (tagOrNode: string | Node, ...args1: HArgs1[]): Node;
1089
- };
1090
- ```
1091
-
1092
- A lightweight _HyperScript_-style helper for creating and
1093
- modifying `SVGElement`s (see also [`h`](#h)).
1094
-
1095
- #### tagOrNode
1096
-
1097
- If a `string`, it is treated as the tag
1098
- name for a new element. If a `Node`, that node is
1099
- modified in place.
1100
-
1101
- #### args
1102
-
1103
- Additional arguments processed as follows:
1104
-
1105
- - `Object`: maps attributes/properties. Keys starting
1106
- with `$` set element properties (without the `$`
1107
- prefix); if the value is a plain object, it is
1108
- shallow-merged into the existing property instead of
1109
- replacing it. Other keys set attributes: a key
1110
- prefixed with `xlink:` sets the attribute via
1111
- `setAttributeNS` in the XLink namespace; any other
1112
- key (without a colon) sets it via `setAttribute`. A
1113
- value of `false` removes the attribute.
1114
- - `null`/`undefined`: ignored.
1115
- - `Node`: appended as a child.
1116
- - `string`/`number`: converted to a `Text` node and
1117
- appended.
1118
- - [`HArgs`](#HArgs) array: processed recursively.
1119
-
1120
- #### Returns
1121
-
1122
- The created or modified `SVGElement`.
1123
-
1124
- #### Usage Examples
1125
-
1126
- ```ts
1127
- const svg = s('svg', { 'xlink:href': true })
1128
-
1129
- expect(
1130
- svg.getAttributeNS(
1131
- 'http://www.w3.org/1999/xlink',
1132
- 'href'
1133
- )
1134
- ).toBe('')
1135
- ```
1136
-
1137
- ```ts
1138
- const svg = s('svg', { 'xlink:href': false })
1139
-
1140
- expect(
1141
- svg.getAttributeNS(
1142
- 'http://www.w3.org/1999/xlink',
1143
- 'href'
1144
- )
1145
- ).toBeNull()
1146
- ```
1147
-
1148
- ```ts
1149
- const svg = s('svg', {
1150
- 'xlink:href': 'http://example.com',
1151
- })
1152
-
1153
- expect(
1154
- svg.getAttributeNS(
1155
- 'http://www.w3.org/1999/xlink',
1156
- 'href'
1157
- )
1158
- ).toBe('http://example.com')
1159
- ```
1160
-
1161
- ```ts
1162
- const svg = s('svg', { 'xlink:title': 42 })
1163
-
1164
- expect(
1165
- svg.getAttributeNS(
1166
- 'http://www.w3.org/1999/xlink',
1167
- 'title'
1168
- )
1169
- ).toBe('42')
1170
- ```
1171
-
1172
- ### svgUse
1173
-
1174
- ```ts
1175
- const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement;
1176
- ```
1177
-
1178
- Shorthand for creating an SVG element with a `<use>`
1179
- child referencing an icon by ID.
1180
-
1181
- Equivalent to:
1182
-
1183
- ```ts
1184
- s(
1185
- 'svg',
1186
- ['use', { 'xlink:href': '#' + id }],
1187
- ...args
1188
- )
1189
- ```
1190
-
1191
- #### id
1192
-
1193
- The ID of the symbol to reference (without the
1194
- `#` prefix).
1195
-
1196
- #### args
1197
-
1198
- Additional arguments passed to the outer
1199
- `<svg>` element.
1200
-
1201
- #### Returns
1202
-
1203
- An `SVGSVGElement` containing a `<use>` element.
1204
-
1205
- #### Usage Examples
1206
-
1207
- ```ts
1208
- const svg = svgUse('icon-home')
1209
-
1210
- expect(svg.children.length).toBe(1)
1211
-
1212
- const useElement = svg.children[0]
1213
-
1214
- expect(
1215
- useElement.getAttributeNS(
1216
- 'http://www.w3.org/1999/xlink',
1217
- 'href'
1218
- )
1219
- ).toBe('#icon-home')
1220
-
1221
- const svgWithViewBox = svgUse('icon-star', {
1222
- viewBox: '0 0 24 24',
1223
- })
1224
-
1225
- expect(svgWithViewBox.getAttribute('viewBox')).toBe(
1226
- '0 0 24 24'
1227
- )
1228
-
1229
- const useViewBox = svgWithViewBox.children[0]
1230
-
1231
- expect(
1232
- useViewBox.getAttributeNS(
1233
- 'http://www.w3.org/1999/xlink',
1234
- 'href'
1235
- )
1236
- ).toBe('#icon-star')
1237
-
1238
- const svgWithClass = svgUse('icon-menu', {
1239
- class: 'icon-btn',
1240
- })
1241
-
1242
- expect(svgWithClass.getAttribute('class')).toBe(
1243
- 'icon-btn'
1244
- )
1245
-
1246
- expect(svgWithClass.children.length).toBe(1)
1247
-
1248
- const useClass = svgWithClass.children[0]
1249
-
1250
- expect(
1251
- useClass.getAttributeNS(
1252
- 'http://www.w3.org/1999/xlink',
1253
- 'href'
1254
- )
1255
- ).toBe('#icon-menu')
1256
-
1257
- const svgWithMultipleAttrs = svgUse('icon-settings', {
1258
- width: 24,
1259
- height: 24,
1260
- class: 'icon',
1261
- })
1262
-
1263
- expect(svgWithMultipleAttrs.getAttribute('width')).toBe(
1264
- '24'
1265
- )
1266
-
1267
- expect(svgWithMultipleAttrs.getAttribute('height')).toBe(
1268
- '24'
1269
- )
1270
-
1271
- expect(svgWithMultipleAttrs.getAttribute('class')).toBe(
1272
- 'icon'
1273
- )
1274
-
1275
- const useMultiple = svgWithMultipleAttrs.children[0]
1276
-
1277
- expect(
1278
- useMultiple.getAttributeNS(
1279
- 'http://www.w3.org/1999/xlink',
1280
- 'href'
1281
- )
1282
- ).toBe('#icon-settings')
1283
- ```
1284
-
1285
- ### uuidV1
1286
-
1287
- ```ts
1288
- const uuidV1: (date?: Date, node?: string) => string;
1289
- ```
1290
-
1291
- Generates a UUID v1 (time-based) identifier.
1292
-
1293
- #### date
1294
-
1295
- The date to use for the timestamp portion
1296
- (default: current date/time).
1297
-
1298
- #### node
1299
-
1300
- A hexadecimal `string` for the node portion
1301
- (default: random). Must match `/^[0-9a-f]*$/`; it is
1302
- trimmed to the last 12 characters and left-padded with
1303
- zeros if shorter.
1304
-
1305
- #### Returns
1306
-
1307
- A UUID v1 `string` in the standard format
1308
- `xxxxxxxx-xxxx-1xxx-xxxx-xxxxxxxxxxxx`.
1309
-
1310
- #### Usage Examples
1311
-
1312
- ```ts
1313
- for (let i = 1; i <= 22136; ++i) {
1314
- const uuid = uuidV1()
1315
-
1316
- if (i === 1) {
1317
- expect(uuid.split('-')[3]).toBe('8001')
1318
- }
1319
-
1320
- if (i === 4095) {
1321
- expect(uuid.split('-')[3]).toBe('8fff')
1322
- }
1323
-
1324
- if (i === 4096) {
1325
- expect(uuid.split('-')[3]).toBe('9000')
1326
- }
1327
-
1328
- if (i === 9029) {
1329
- expect(uuid.split('-')[3]).toBe('a345')
1330
- }
1331
-
1332
- if (i === 13398) {
1333
- expect(uuid.split('-')[3]).toBe('b456')
1334
- }
1335
-
1336
- if (i === 16384) {
1337
- expect(uuid.split('-')[3]).toBe('8000')
1338
- }
1339
-
1340
- if (i === 17767) {
1341
- expect(uuid.split('-')[3]).toBe('8567')
1342
- }
1343
- }
1344
- ```
1345
-
1346
- ```ts
1347
- expect(
1348
- uuidV1(new Date(), '000123456789abc').split('-')[4]
1349
- ).toBe('123456789abc')
1350
-
1351
- expect(
1352
- uuidV1(new Date(), '123456789').split('-')[4]
1353
- ).toBe('000123456789')
1354
-
1355
- expect(
1356
- uuidV1(new Date(323325000000)).startsWith(
1357
- 'c1399400-9a71-11bd'
1358
- )
1359
- ).toBe(true)
1360
- ```
1361
-
1362
- ### vivify
1363
-
1364
- ```ts
1365
- const vivify: (ref: unknown) => any;
1366
- ```
1367
-
1368
- A Proxy-based helper for auto-vivification of nested
1369
- object structures.
1370
-
1371
- Accessing, assigning, or deleting any nested property on
1372
- the returned proxy automatically creates intermediate
1373
- objects (or arrays for numeric-string keys matching
1374
- `^(0|[1-9]\d*)$`) as needed, allowing deep operations
1375
- without explicit null checks.
1376
-
1377
- Intermediates of the last level in a get-only property
1378
- chain are NOT auto-created. For example,
1379
- `vivify(ref).one.two` will create `ref.one` as `{}`, but
1380
- will NOT create `ref.one.two`. Only when a deeper access,
1381
- assignment, or deletion occurs, e.g.
1382
-
1383
- ```ts
1384
- delete vivify(ref).one.two.three
1385
- ```
1386
-
1387
- or
1388
-
1389
- ```ts
1390
- vivify(ref).one.two.three = 4
1391
- ```
1392
-
1393
- Will `ref.one.two` be materialized.
1394
-
1395
- When traversal reaches a primitive value, no
1396
- auto-creation happens; the primitive’s own property is
1397
- returned instead (e.g. accessing `.toString.name` on a
1398
- number yields `'toString'` without modifying the
1399
- underlying structure).
1400
-
1401
- Deletion on a non-existing intermediate path will
1402
- auto-create intermediates up to the parent of the deleted
1403
- key, e.g.
1404
-
1405
- ```ts
1406
- delete vivify(ref).one.two.three
1407
- ```
1408
-
1409
- Will create `ref.one.two` as `{}` if it does not exist.
1410
-
1411
- #### ref
1412
-
1413
- The root object to wrap.
1414
-
1415
- #### Returns
1416
-
1417
- A proxy that auto-creates nested objects/arrays
1418
- on property access.
1419
-
1420
- #### Usage Examples
1421
-
1422
- ```ts
1423
- const ref: any = {}
1424
-
1425
- vivify(ref).one.two[3][4]
1426
-
1427
- expect(ref).toEqual({
1428
- one: {
1429
- two: [undefined, undefined, undefined, []],
1430
- },
1431
- })
1432
-
1433
- vivify(ref).one.two[3][4] = 5
1434
-
1435
- expect(ref).toEqual({
1436
- one: {
1437
- two: [
1438
- undefined,
1439
- undefined,
1440
- undefined,
1441
- [undefined, undefined, undefined, undefined, 5],
1442
- ],
1443
- },
1444
- })
1445
-
1446
- vivify(ref).one.two[3].length = 1
1447
-
1448
- expect(ref).toEqual({
1449
- one: {
1450
- two: [undefined, undefined, undefined, [undefined]],
1451
- },
1452
- })
1453
-
1454
- vivify(ref).one.two[3] = 4
1455
-
1456
- expect(ref).toEqual({
1457
- one: {
1458
- two: [undefined, undefined, undefined, 4],
1459
- },
1460
- })
1461
-
1462
- expect(vivify(ref).one.two.length).toBe(4)
1463
-
1464
- expect(ref).toEqual({
1465
- one: {
1466
- two: [undefined, undefined, undefined, 4],
1467
- },
1468
- })
1469
-
1470
- vivify(ref).one.two = 3
1471
-
1472
- expect(ref).toEqual({ one: { two: 3 } })
1473
-
1474
- vivify(ref).one.two = undefined
1475
-
1476
- expect(ref).toEqual({ one: { two: undefined } })
1477
-
1478
- delete vivify(ref).one.two
1479
-
1480
- expect(ref).toEqual({ one: {} })
1481
-
1482
- expect(vivify(ref).one.toString instanceof Function).toBe(
1483
- true
1484
- )
1485
-
1486
- expect(vivify(ref).one.toString.name).toBe('toString')
1487
-
1488
- expect(ref).toEqual({ one: {} })
1489
-
1490
- delete vivify(ref).one.two.three
1491
-
1492
- expect(ref).toEqual({ one: { two: {} } })
1493
-
1494
- vivify(ref).one.two.three.four
1495
-
1496
- expect(ref).toEqual({ one: { two: { three: {} } } })
1497
-
1498
- vivify(ref).one.two[3]
1499
-
1500
- expect(ref).toEqual({ one: { two: { three: {} } } })
1501
-
1502
- vivify(ref).one.two.three.four = 5
1503
-
1504
- expect(ref).toEqual({
1505
- one: { two: { three: { four: 5 } } },
1506
- })
1507
-
1508
- expect(vivify(ref).one.two.three.four.toString.name).toBe(
1509
- 'toString'
1510
- )
1511
-
1512
- expect(ref).toEqual({
1513
- one: { two: { three: { four: 5 } } },
1514
- })
1515
- ```
1516
-
1517
- ```ts
1518
- const u = undefined
1519
-
1520
- vivify(u).one.two = 3
1521
-
1522
- expect(u).toBeUndefined()
1523
-
1524
- const n = null
1525
-
1526
- delete vivify(n).one.two.three
1527
-
1528
- expect(n).toBeNull()
1529
- ```
1530
-
1531
- ## License
1532
-
1533
- The MIT License (MIT)
1534
-
1535
- Copyright (c) 2016+ Jackens
1536
-
1537
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1538
-
1539
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1540
-
1541
- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.