@jackens/nnn 2026.11.16 → 2026.11.17
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.
- package/README.md +37 -17
- package/nnn.d.ts +6 -5
- package/nnn.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nnn
|
|
2
2
|
|
|
3
|
-
A collection of Jackens’ JavaScript helper utilities (version: `2026.11.
|
|
3
|
+
A collection of Jackens’ JavaScript helper utilities (version: `2026.11.17`).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -89,7 +89,8 @@ Keys are selectors or at-rules; values are either CSS property values or nested
|
|
|
89
89
|
Nested selectors are appended to the parent selector as is (e.g. `'.c'` inside `a` → `a.c`), so a leading space is needed for a descendant combinator (e.g. `' .c'` → `a .c`).
|
|
90
90
|
If a nested selector contains `&`, every `&` is replaced with the parent selector instead (e.g. `'&:hover'` → `a:hover`, `'.c &'` → `.c a`).
|
|
91
91
|
A selector key may contain comma-separated alternatives (e.g. `'.b,.c'`); when nested, each alternative is combined with each parent alternative (cartesian product), producing a comma-joined selector list (e.g. `'a.b,a.c'`).
|
|
92
|
-
Commas inside brackets or quotes (e.g. `':is(.b,.c)'`, `'[title="b,c"]'`) do not separate alternatives
|
|
92
|
+
Commas inside brackets or quotes (e.g. `':is(.b,.c)'`, `'[title="b,c"]'`) do not separate alternatives.
|
|
93
|
+
Whitespace after a separating comma belongs to the next alternative, so like a leading space of the first one it makes a descendant combinator when nested (e.g. `' .b, .c'` → `a .b,a .c`, `'.b, .c'` → `a.b,a .c`); leading whitespace of a resulting selector is ignored (e.g. `'h1, h2'` → `h1,h2`, `'.b, &:hover'` → `a.b,a:hover`).
|
|
93
94
|
At-rules (e.g. `'@media …'`) wrap their nested rules; an at-rule nested in a selector wraps that selector (e.g. `{ a: { '@media(…)': { color: 'red' } } }` → `@media(…){a{color:red}}`).
|
|
94
95
|
Inside a top-level `:root` rule nested keys are concatenated into property names (e.g. `{ ':root': { '--': { bg: 'red' } } }` → `:root{--bg:red}`).
|
|
95
96
|
Properties without a selector (top-level or directly in a top-level at-rule, e.g. `@font-face`) are emitted as bare declarations.
|
|
@@ -440,12 +441,12 @@ const expected = `
|
|
|
440
441
|
expect(actual).toBe(expected)
|
|
441
442
|
```
|
|
442
443
|
|
|
443
|
-
Commas inside brackets or quotes do not split selectors
|
|
444
|
+
Commas inside brackets or quotes do not split selectors:
|
|
444
445
|
|
|
445
446
|
```ts
|
|
446
447
|
const actual = c({
|
|
447
448
|
':is(.a,.b)': {
|
|
448
|
-
'.c
|
|
449
|
+
'.c,.d': { margin: 1 },
|
|
449
450
|
'[title="x,y"]': { margin: 2 },
|
|
450
451
|
},
|
|
451
452
|
})
|
|
@@ -462,6 +463,32 @@ const expected = `
|
|
|
462
463
|
expect(actual).toBe(expected)
|
|
463
464
|
```
|
|
464
465
|
|
|
466
|
+
Whitespace after a comma is kept, but leading whitespace of a resulting selector is ignored:
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
const actual = c({
|
|
470
|
+
'h1, h2': {
|
|
471
|
+
margin: 0,
|
|
472
|
+
' .a, .b': { margin: 1 },
|
|
473
|
+
'.c, .d , &:hover': { margin: 2 },
|
|
474
|
+
},
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
const expected = `
|
|
478
|
+
h1,h2{
|
|
479
|
+
margin:0
|
|
480
|
+
}
|
|
481
|
+
h1 .a,h1 .b,h2 .a,h2 .b{
|
|
482
|
+
margin:1
|
|
483
|
+
}
|
|
484
|
+
h1.c,h1 .d,h1:hover,h2.c,h2 .d,h2:hover{
|
|
485
|
+
margin:2
|
|
486
|
+
}
|
|
487
|
+
`.replace(/\n\s*/g, '')
|
|
488
|
+
|
|
489
|
+
expect(actual).toBe(expected)
|
|
490
|
+
```
|
|
491
|
+
|
|
465
492
|
At-rules nested in selectors wrap these selectors:
|
|
466
493
|
|
|
467
494
|
```ts
|
|
@@ -531,9 +558,9 @@ expect(c({ a: { b$$c: 1 } }, '')).toBe('a{b$$c:1}')
|
|
|
531
558
|
```ts
|
|
532
559
|
const h: {
|
|
533
560
|
<T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs1[]): HTMLElementTagNameMap[T]
|
|
534
|
-
(tag: string, ...args: HArgs1[]): HTMLElement
|
|
535
561
|
<N extends Node>(node: N, ...args: HArgs1[]): N
|
|
536
|
-
(
|
|
562
|
+
(tag: string, ...args: HArgs1[]): HTMLElement
|
|
563
|
+
(node: Node, ...args: HArgs1[]): Node
|
|
537
564
|
}
|
|
538
565
|
```
|
|
539
566
|
|
|
@@ -575,13 +602,11 @@ const i = h('i', 'text')
|
|
|
575
602
|
h(b, i)
|
|
576
603
|
|
|
577
604
|
expect(i.outerHTML).toBe('<i>text</i>')
|
|
578
|
-
|
|
579
605
|
expect(b.outerHTML).toBe('<b><i>text</i></b>')
|
|
580
606
|
|
|
581
607
|
h(i, { $className: 'some class' })
|
|
582
608
|
|
|
583
609
|
expect(i.outerHTML).toBe('<i class="some class">text</i>')
|
|
584
|
-
|
|
585
610
|
expect(b.outerHTML).toBe('<b><i class="some class">text</i></b>')
|
|
586
611
|
```
|
|
587
612
|
|
|
@@ -589,11 +614,9 @@ Text content from strings, numbers and `$innerText`:
|
|
|
589
614
|
|
|
590
615
|
```ts
|
|
591
616
|
expect(h('span', 'text').outerHTML).toBe('<span>text</span>')
|
|
592
|
-
|
|
593
617
|
expect(h('span', { $innerText: 'text' }).outerHTML).toBe('<span>text</span>')
|
|
594
618
|
|
|
595
619
|
expect(h('span', '42').outerHTML).toBe('<span>42</span>')
|
|
596
|
-
|
|
597
620
|
expect(h('span', 42).outerHTML).toBe('<span>42</span>')
|
|
598
621
|
```
|
|
599
622
|
|
|
@@ -601,9 +624,7 @@ expect(h('span', 42).outerHTML).toBe('<span>42</span>')
|
|
|
601
624
|
|
|
602
625
|
```ts
|
|
603
626
|
expect(h('div', { style: 'margin:0;padding:0' }).outerHTML).toBe('<div style="margin:0;padding:0"></div>')
|
|
604
|
-
|
|
605
627
|
expect(h('div', { $style: 'margin:0;padding:0' }).outerHTML).toBe('<div style="margin: 0px; padding: 0px;"></div>')
|
|
606
|
-
|
|
607
628
|
expect(h('div', { $style: { margin: 0, padding: 0 } }).outerHTML).toBe(
|
|
608
629
|
'<div style="margin: 0px; padding: 0px;"></div>'
|
|
609
630
|
)
|
|
@@ -639,14 +660,13 @@ expect(checkbox1.checked).toBe(true)
|
|
|
639
660
|
expect(checkbox2.checked).toBe(true)
|
|
640
661
|
|
|
641
662
|
expect(checkbox1.outerHTML).toBe('<input type="checkbox" checked="">')
|
|
642
|
-
|
|
643
663
|
expect(checkbox2.outerHTML).toBe('<input type="checkbox">')
|
|
644
664
|
```
|
|
645
665
|
|
|
646
666
|
Object values of `$`-prefixed properties are merged:
|
|
647
667
|
|
|
648
668
|
```ts
|
|
649
|
-
const div = h('div')
|
|
669
|
+
const div = h('div') as any
|
|
650
670
|
|
|
651
671
|
expect(div.key).toBeUndefined()
|
|
652
672
|
|
|
@@ -691,7 +711,7 @@ Only plain objects are merged into `$`-prefixed properties:
|
|
|
691
711
|
|
|
692
712
|
```ts
|
|
693
713
|
const date = new Date(0)
|
|
694
|
-
const div = h('div', { $date: date, $list: [1, 2] })
|
|
714
|
+
const div = h('div', { $date: date, $list: [1, 2] }) as any
|
|
695
715
|
|
|
696
716
|
expect(div.date).toBe(date)
|
|
697
717
|
|
|
@@ -859,9 +879,9 @@ expect(() => rwd({}, '.x', 100, 50, [2, 1, 1.5])).toThrow(RangeError)
|
|
|
859
879
|
```ts
|
|
860
880
|
const s: {
|
|
861
881
|
<T extends keyof SVGElementTagNameMap>(tag: T, ...args: HArgs1[]): SVGElementTagNameMap[T]
|
|
862
|
-
(tag: string, ...args: HArgs1[]): SVGElement
|
|
863
882
|
<N extends Node>(node: N, ...args: HArgs1[]): N
|
|
864
|
-
(
|
|
883
|
+
(tag: string, ...args: HArgs1[]): SVGElement
|
|
884
|
+
(node: Node, ...args: HArgs1[]): Node
|
|
865
885
|
}
|
|
866
886
|
```
|
|
867
887
|
|
package/nnn.d.ts
CHANGED
|
@@ -12,7 +12,8 @@ export type CNode = {
|
|
|
12
12
|
* Nested selectors are appended to the parent selector as is (e.g. `'.c'` inside `a` → `a.c`), so a leading space is needed for a descendant combinator (e.g. `' .c'` → `a .c`).
|
|
13
13
|
* If a nested selector contains `&`, every `&` is replaced with the parent selector instead (e.g. `'&:hover'` → `a:hover`, `'.c &'` → `.c a`).
|
|
14
14
|
* A selector key may contain comma-separated alternatives (e.g. `'.b,.c'`); when nested, each alternative is combined with each parent alternative (cartesian product), producing a comma-joined selector list (e.g. `'a.b,a.c'`).
|
|
15
|
-
* Commas inside brackets or quotes (e.g. `':is(.b,.c)'`, `'[title="b,c"]'`) do not separate alternatives
|
|
15
|
+
* Commas inside brackets or quotes (e.g. `':is(.b,.c)'`, `'[title="b,c"]'`) do not separate alternatives.
|
|
16
|
+
* Whitespace after a separating comma belongs to the next alternative, so like a leading space of the first one it makes a descendant combinator when nested (e.g. `' .b, .c'` → `a .b,a .c`, `'.b, .c'` → `a.b,a .c`); leading whitespace of a resulting selector is ignored (e.g. `'h1, h2'` → `h1,h2`, `'.b, &:hover'` → `a.b,a:hover`).
|
|
16
17
|
* At-rules (e.g. `'@media …'`) wrap their nested rules; an at-rule nested in a selector wraps that selector (e.g. `{ a: { '@media(…)': { color: 'red' } } }` → `@media(…){a{color:red}}`).
|
|
17
18
|
* Inside a top-level `:root` rule nested keys are concatenated into property names (e.g. `{ ':root': { '--': { bg: 'red' } } }` → `:root{--bg:red}`).
|
|
18
19
|
* Properties without a selector (top-level or directly in a top-level at-rule, e.g. `@font-face`) are emitted as bare declarations.
|
|
@@ -60,9 +61,9 @@ export type HArgs = [string | Node, ...HArgs1[]]
|
|
|
60
61
|
*/
|
|
61
62
|
export declare const h: {
|
|
62
63
|
<T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs1[]): HTMLElementTagNameMap[T]
|
|
63
|
-
(tag: string, ...args: HArgs1[]): HTMLElement
|
|
64
64
|
<N extends Node>(node: N, ...args: HArgs1[]): N
|
|
65
|
-
(
|
|
65
|
+
(tag: string, ...args: HArgs1[]): HTMLElement
|
|
66
|
+
(node: Node, ...args: HArgs1[]): Node
|
|
66
67
|
}
|
|
67
68
|
/**
|
|
68
69
|
* A lightweight _HyperScript_-style helper for creating and modifying `SVGElement`s (see also {@link h}).
|
|
@@ -91,9 +92,9 @@ export declare const h: {
|
|
|
91
92
|
*/
|
|
92
93
|
export declare const s: {
|
|
93
94
|
<T extends keyof SVGElementTagNameMap>(tag: T, ...args: HArgs1[]): SVGElementTagNameMap[T]
|
|
94
|
-
(tag: string, ...args: HArgs1[]): SVGElement
|
|
95
95
|
<N extends Node>(node: N, ...args: HArgs1[]): N
|
|
96
|
-
(
|
|
96
|
+
(tag: string, ...args: HArgs1[]): SVGElement
|
|
97
|
+
(node: Node, ...args: HArgs1[]): Node
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
99
100
|
* Shorthand for creating an `<svg>` element with a `<use>` child referencing a symbol (e.g. an icon) by ID.
|
package/nnn.js
CHANGED
|
@@ -29,7 +29,7 @@ var splitSelectorList = (selectorList) => {
|
|
|
29
29
|
selectors[selectors.length - 1] += char;
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
|
-
return selectors.map((selector
|
|
32
|
+
return selectors.map((selector) => selector.trimEnd());
|
|
33
33
|
};
|
|
34
34
|
var nestSelector = (parent, selector) => {
|
|
35
35
|
let nested = "";
|
|
@@ -42,7 +42,7 @@ var nestSelector = (parent, selector) => {
|
|
|
42
42
|
nested += char;
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
-
return ampersandFound ? nested : parent + selector;
|
|
45
|
+
return (ampersandFound ? nested : parent + selector).trimStart();
|
|
46
46
|
};
|
|
47
47
|
var stripSplitter = (key, splitter) => {
|
|
48
48
|
const splitterIndex = splitter === "" ? -1 : key.indexOf(splitter);
|