@jackens/nnn 2026.9.9 → 2026.9.29

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