@jackens/nnn 2024.3.2 → 2024.3.5

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 +30 -25
  2. package/nnn.js +59 -57
  3. package/package.json +3 -2
  4. package/readme.md +248 -232
package/nnn.d.ts CHANGED
@@ -1,28 +1,3 @@
1
- /**
2
- * The type of arguments of the `c` helper.
3
- */
4
- export type CNode = {
5
- [attributeOrSelector: string]: string | number | CNode | undefined;
6
- };
7
-
8
- /**
9
- * The type of arguments of the `c` helper.
10
- */
11
- export type CRoot = Partial<Record<PropertyKey, CNode>>;
12
-
13
- /**
14
- * A simple CSS-in-JS helper.
15
- *
16
- * The `root` parameter provides a hierarchical description of CSS rules.
17
- *
18
- * - Keys of sub-objects whose values are NOT objects are treated as CSS attribute, and values are treated as values of those CSS attributes; the concatenation of keys of all parent objects is a CSS rule.
19
- * - All keys ignore the part starting with a splitter (default: `$$`) sign until the end of the key (e.g. `src$$1` → `src`, `@font-face$$1` → `@font-face`).
20
- * - In keys specifying CSS attribute, all uppercase letters are replaced by lowercase letters with an additional `-` character preceding them (e.g. `fontFamily` → `font-family`).
21
- * - Commas in keys that makes a CSS rule cause it to “split” and create separate rules for each part (e.g. `{div:{margin:1,'.a,.b,.c':{margin:2}}}` → `div{margin:1}div.a,div.b,div.c{margin:2}`).
22
- * - Top-level keys that begin with `@` are not concatenated with sub-object keys.
23
- */
24
- export declare const c: (root: CRoot, splitter?: string) => string;
25
-
26
1
  /**
27
2
  * A helper for creating a chart based on a table (conf. <https://jackens.github.io/nnn/chartable/>).
28
3
  *
@@ -132,6 +107,11 @@ export declare const s: {
132
107
  (tagOrNode: string | Node, ...args1: HArgs1[]): Node;
133
108
  };
134
109
 
110
+ /**
111
+ * A convenient shortcut for `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
112
+ */
113
+ export declare const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement;
114
+
135
115
  /**
136
116
  * A replacement for the `in` operator (not to be confused with the `for-in` loop) that works properly.
137
117
  */
@@ -151,6 +131,31 @@ export declare const is: {
151
131
  <T extends abstract new (...args: any[]) => any>(type: T, arg: any): arg is InstanceType<T>;
152
132
  };
153
133
 
134
+ /**
135
+ * The type of arguments of the `jc` helper.
136
+ */
137
+ export type JcNode = {
138
+ [attributeOrSelector: string]: string | number | JcNode | undefined;
139
+ };
140
+
141
+ /**
142
+ * The type of arguments of the `jc` helper.
143
+ */
144
+ export type JcRoot = Partial<Record<PropertyKey, JcNode>>;
145
+
146
+ /**
147
+ * A simple JS-to-CSS (aka CSS-in-JS) helper.
148
+ *
149
+ * The `root` parameter provides a hierarchical description of CSS rules.
150
+ *
151
+ * - Keys of sub-objects whose values are NOT objects are treated as CSS attribute, and values are treated as values of those CSS attributes; the concatenation of keys of all parent objects is a CSS rule.
152
+ * - All keys ignore the part starting with a splitter (default: `$$`) sign until the end of the key (e.g. `src$$1` → `src`, `@font-face$$1` → `@font-face`).
153
+ * - In keys specifying CSS attribute, all uppercase letters are replaced by lowercase letters with an additional `-` character preceding them (e.g. `fontFamily` → `font-family`).
154
+ * - Commas in keys that makes a CSS rule cause it to “split” and create separate rules for each part (e.g. `{div:{margin:1,'.a,.b,.c':{margin:2}}}` → `div{margin:1}div.a,div.b,div.c{margin:2}`).
155
+ * - Top-level keys that begin with `@` are not concatenated with sub-object keys.
156
+ */
157
+ export declare const jc: (root: JcRoot, splitter?: string) => string;
158
+
154
159
  /**
155
160
  * `JSON.parse` with “JavaScript turned on”.
156
161
  *
package/nnn.js CHANGED
@@ -1,59 +1,6 @@
1
1
  // src/nnn/is.ts
2
2
  var is = (type, arg) => arg?.constructor === type;
3
3
 
4
- // src/nnn/c.ts
5
- var _c = (node, prefix, result, split) => {
6
- const queue = [[node, prefix]];
7
- while (queue.length > 0) {
8
- const [style0, prefix0] = queue.shift() ?? [];
9
- if (style0 == null || prefix0 == null) {
10
- continue;
11
- }
12
- if (is(Array, style0)) {
13
- result.push(prefix0, prefix0 !== "" ? "{" : "", style0.join(";"), prefix0 !== "" ? "}" : "");
14
- } else {
15
- const todo = [];
16
- let attributes = [];
17
- let attributesPushed = false;
18
- for (const key in style0) {
19
- const value = style0[key];
20
- if (is(String, value) || is(Number, value)) {
21
- if (!attributesPushed) {
22
- attributesPushed = true;
23
- attributes = [];
24
- todo.push([attributes, prefix0]);
25
- }
26
- attributes.push(`${split(key).replace(/([A-Z])/g, (_, letter) => "-" + letter.toLowerCase())}:${value}`);
27
- } else if (value != null) {
28
- attributesPushed = false;
29
- const prefixN = [];
30
- const keyChunks = key.split(",");
31
- prefix0.split(",").forEach((prefixChunk) => keyChunks.forEach((keyChunk) => prefixN.push(prefixChunk + keyChunk)));
32
- todo.push([value, prefixN.join(",")]);
33
- }
34
- }
35
- queue.unshift(...todo);
36
- }
37
- }
38
- };
39
- var c = (root, splitter = "$$") => {
40
- const split = (text) => text.split(splitter)[0];
41
- const chunks = [];
42
- for (const key in root) {
43
- const value = root[key];
44
- if (value != null) {
45
- if (key[0] === "@") {
46
- chunks.push(split(key) + "{");
47
- _c(value, "", chunks, split);
48
- chunks.push("}");
49
- } else {
50
- _c(value, split(key), chunks, split);
51
- }
52
- }
53
- }
54
- return chunks.join("");
55
- };
56
-
57
4
  // src/nnn/h.ts
58
5
  var NS = {
59
6
  xlink: "http://www.w3.org/1999/xlink"
@@ -117,6 +64,7 @@ var _h = (namespaceURI) => {
117
64
  };
118
65
  var h = _h();
119
66
  var s = _h("http://www.w3.org/2000/svg");
67
+ var svgUse = (id, ...args) => s("svg", ["use", { "xlink:href": "#" + id }], ...args);
120
68
 
121
69
  // src/nnn/chartable.ts
122
70
  var COLORS = ["#e22", "#e73", "#fc3", "#ad4", "#4d9", "#3be", "#45d", "#c3e"];
@@ -141,9 +89,9 @@ var chartable = ({
141
89
  const zxValues = [];
142
90
  const xLabels = [];
143
91
  const zLabels = [];
144
- table.querySelectorAll("tr").forEach((row, r) => row.querySelectorAll("td,th").forEach((col, c2) => {
92
+ table.querySelectorAll("tr").forEach((row, r) => row.querySelectorAll("td,th").forEach((col, c) => {
145
93
  const x = r - 1;
146
- const z = c2 - +headerColumn;
94
+ const z = c - +headerColumn;
147
95
  const value = col.innerText;
148
96
  if (x >= 0 && z >= 0) {
149
97
  zxValues[z] = zxValues[z] ?? [];
@@ -303,6 +251,59 @@ var fixTypography = (node) => {
303
251
  // src/nnn/has.ts
304
252
  var has = (key, ref) => (is(String, key) || is(Number, key) || is(Symbol, key)) && Object.hasOwnProperty.call(ref ?? Object, key);
305
253
 
254
+ // src/nnn/jc.ts
255
+ var _jc = (node, prefix, result, split) => {
256
+ const queue = [[node, prefix]];
257
+ while (queue.length > 0) {
258
+ const [style0, prefix0] = queue.shift() ?? [];
259
+ if (style0 == null || prefix0 == null) {
260
+ continue;
261
+ }
262
+ if (is(Array, style0)) {
263
+ result.push(prefix0, prefix0 !== "" ? "{" : "", style0.join(";"), prefix0 !== "" ? "}" : "");
264
+ } else {
265
+ const todo = [];
266
+ let attributes = [];
267
+ let attributesPushed = false;
268
+ for (const key in style0) {
269
+ const value = style0[key];
270
+ if (is(String, value) || is(Number, value)) {
271
+ if (!attributesPushed) {
272
+ attributesPushed = true;
273
+ attributes = [];
274
+ todo.push([attributes, prefix0]);
275
+ }
276
+ attributes.push(`${split(key).replace(/([A-Z])/g, (_, letter) => "-" + letter.toLowerCase())}:${value}`);
277
+ } else if (value != null) {
278
+ attributesPushed = false;
279
+ const prefixN = [];
280
+ const keyChunks = key.split(",");
281
+ prefix0.split(",").forEach((prefixChunk) => keyChunks.forEach((keyChunk) => prefixN.push(prefixChunk + keyChunk)));
282
+ todo.push([value, prefixN.join(",")]);
283
+ }
284
+ }
285
+ queue.unshift(...todo);
286
+ }
287
+ }
288
+ };
289
+ var jc = (root, splitter = "$$") => {
290
+ const split = (text) => text.split(splitter)[0];
291
+ const chunks = [];
292
+ for (const key in root) {
293
+ const value = root[key];
294
+ if (value != null) {
295
+ if (key[0] === "@") {
296
+ chunks.push(split(key) + "{");
297
+ _jc(value, "", chunks, split);
298
+ chunks.push("}");
299
+ } else {
300
+ _jc(value, split(key), chunks, split);
301
+ }
302
+ }
303
+ }
304
+ return chunks.join("");
305
+ };
306
+
306
307
  // src/nnn/jsOnParse.ts
307
308
  var jsOnParse = (handlers, text) => JSON.parse(text, (key, value) => {
308
309
  if (is(Object, value)) {
@@ -398,6 +399,7 @@ var uuid1 = ({
398
399
  };
399
400
  export {
400
401
  uuid1,
402
+ svgUse,
401
403
  s,
402
404
  refsInfo,
403
405
  pro,
@@ -408,6 +410,7 @@ export {
408
410
  nanolight,
409
411
  locale,
410
412
  jsOnParse,
413
+ jc,
411
414
  is,
412
415
  has,
413
416
  h,
@@ -415,6 +418,5 @@ export {
415
418
  escapeValues,
416
419
  escape,
417
420
  eq,
418
- chartable,
419
- c
421
+ chartable
420
422
  };
package/package.json CHANGED
@@ -20,7 +20,8 @@
20
20
  "HyperScript",
21
21
  "in",
22
22
  "is",
23
- "jcss",
23
+ "jc",
24
+ "JS-to-CSS",
24
25
  "JSON",
25
26
  "jsOnParse",
26
27
  "locale",
@@ -42,5 +43,5 @@
42
43
  "types": "nnn.d.ts",
43
44
  "name": "@jackens/nnn",
44
45
  "type": "module",
45
- "version": "2024.3.2"
46
+ "version": "2024.3.5"
46
47
  }
package/readme.md CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Jackens’ JavaScript helpers.
4
4
 
5
- <sub>Version: <code class="version">2024.3.2</code></sub>
5
+ <sub>Version: <code class="version">2024.3.5</code></sub>
6
6
 
7
7
  ## Examples
8
8
 
9
- - [Chartable Demo](/nnn/chartable/)
10
- - [Chessboard Demo](/nnn/chessboard/)
11
- - [Documentation](/nnn/doc/)
12
- - [Gant Chart Demo](/nnn/gantt/)
13
- - [Responsive Web Design Demo](/nnn/rwd/)
9
+ - [Chartable Demo](https://jackens.github.io/nnn/chartable/)
10
+ - [Chessboard Demo](https://jackens.github.io/nnn/chessboard/)
11
+ - [Documentation](https://jackens.github.io/nnn/doc/)
12
+ - [Gant Chart Demo](https://jackens.github.io/nnn/gantt/)
13
+ - [Responsive Web Design Demo](https://jackens.github.io/nnn/rwd/)
14
14
 
15
15
  ## Installation
16
16
 
@@ -40,12 +40,11 @@ import { «something» } from './node_modules/@jackens/nnn/nnn.js'
40
40
 
41
41
  ## Exports
42
42
 
43
- - `CNode`: The type of arguments of the `c` helper.
44
- - `CRoot`: The type of arguments of the `c` helper.
45
43
  - `EscapeMap`: The type of arguments of the `escapeValues` and `escape` helpers.
46
44
  - `HArgs`: The type of arguments of the `h` and `s` helpers.
47
45
  - `HArgs1`: The type of arguments of the `h` and `s` helpers.
48
- - `c`: A simple CSS-in-JS helper.
46
+ - `JcNode`: The type of arguments of the `jc` helper.
47
+ - `JcRoot`: The type of arguments of the `jc` helper.
49
48
  - `chartable`: A helper for creating a chart based on a table (conf. <https://jackens.github.io/nnn/chartable/>).
50
49
  - `eq`: A helper that checks equality of the given arguments.
51
50
  - `escape`: A generic helper for escaping `values` by given `escapeMap` (in *TemplateStrings* flavor).
@@ -54,6 +53,7 @@ import { «something» } from './node_modules/@jackens/nnn/nnn.js'
54
53
  - `h`: A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also `s`).
55
54
  - `has`: A replacement for the `in` operator (not to be confused with the `for-in` loop) that works properly.
56
55
  - `is`: A helper that checks if the given argument is of a certain type.
56
+ - `jc`: A simple JS-to-CSS (aka CSS-in-JS) helper.
57
57
  - `jsOnParse`: `JSON.parse` with “JavaScript turned on”.
58
58
  - `locale`: Language translations helper.
59
59
  - `nanolight`: A generic helper for syntax highlighting (see also `nanolightJs`).
@@ -64,26 +64,9 @@ import { «something» } from './node_modules/@jackens/nnn/nnn.js'
64
64
  - `pro`: A helper that protects calls to nested properties by a `Proxy` that initializes non-existent values with an empty object.
65
65
  - `refsInfo`: A helper that provides information about the given `refs`.
66
66
  - `s`: A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also `h`).
67
+ - `svgUse`: A convenient shortcut for `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
67
68
  - `uuid1`: A helper that generates a UUID v1 identifier (with a creation timestamp).
68
69
 
69
- ### CNode
70
-
71
- ```ts
72
- type CNode = {
73
- [attributeOrSelector: string]: string | number | CNode | undefined;
74
- };
75
- ```
76
-
77
- The type of arguments of the `c` helper.
78
-
79
- ### CRoot
80
-
81
- ```ts
82
- type CRoot = Partial<Record<PropertyKey, CNode>>;
83
- ```
84
-
85
- The type of arguments of the `c` helper.
86
-
87
70
  ### EscapeMap
88
71
 
89
72
  ```ts
@@ -108,217 +91,23 @@ type HArgs1 = Partial<Record<PropertyKey, any>> | null | undefined | Node | stri
108
91
 
109
92
  The type of arguments of the `h` and `s` helpers.
110
93
 
111
- ### c
94
+ ### JcNode
112
95
 
113
96
  ```ts
114
- const c: (root: CRoot, splitter?: string) => string;
115
- ```
116
-
117
- A simple CSS-in-JS helper.
118
-
119
- The `root` parameter provides a hierarchical description of CSS rules.
120
-
121
- - Keys of sub-objects whose values are NOT objects are treated as CSS attribute, and values are treated as values of those CSS attributes; the concatenation of keys of all parent objects is a CSS rule.
122
- - All keys ignore the part starting with a splitter (default: `$$`) sign until the end of the key (e.g. `src$$1` → `src`, `@font-face$$1` → `@font-face`).
123
- - In keys specifying CSS attribute, all uppercase letters are replaced by lowercase letters with an additional `-` character preceding them (e.g. `fontFamily` → `font-family`).
124
- - Commas in keys that makes a CSS rule cause it to “split” and create separate rules for each part (e.g. `{div:{margin:1,'.a,.b,.c':{margin:2}}}` → `div{margin:1}div.a,div.b,div.c{margin:2}`).
125
- - Top-level keys that begin with `@` are not concatenated with sub-object keys.
126
-
127
- #### Usage Examples
128
-
129
- ```js
130
- const actual = c({
131
- a: {
132
- color: 'red',
133
- margin: 1,
134
- '.c': { margin: 2, padding: 2 },
135
- padding: 1
136
- }
137
- })
138
-
139
- const expected = `
140
- a{
141
- color:red;
142
- margin:1
143
- }
144
- a.c{
145
- margin:2;
146
- padding:2
147
- }
148
- a{
149
- padding:1
150
- }`.replace(/\n\s*/g, '')
151
-
152
- expect(actual).toStrictEqual(expected)
153
- ```
154
-
155
- ```js
156
- const actual = c({
157
- a: {
158
- '.b': {
159
- color: 'red',
160
- margin: 1,
161
- '.c': { margin: 2, padding: 2 },
162
- padding: 1
163
- }
164
- }
165
- })
166
-
167
- const expected = `
168
- a.b{
169
- color:red;
170
- margin:1
171
- }
172
- a.b.c{
173
- margin:2;
174
- padding:2
175
- }
176
- a.b{
177
- padding:1
178
- }`.replace(/\n\s*/g, '')
179
-
180
- expect(actual).toStrictEqual(expected)
181
- ```
182
-
183
- ```js
184
- const actual = c({
185
- '@font-face$$1': {
186
- fontFamily: 'Jackens',
187
- src$$1: 'url(otf/jackens.otf)',
188
- src$$2: "url(otf/jackens.otf) format('opentype')," +
189
- "url(svg/jackens.svg) format('svg')",
190
- fontWeight: 'normal',
191
- fontStyle: 'normal'
192
- },
193
- '@font-face$$2': {
194
- fontFamily: 'C64',
195
- src: 'url(fonts/C64_Pro_Mono-STYLE.woff)'
196
- },
197
- '@keyframes spin': {
198
- '0%': { transform: 'rotate(0deg)' },
199
- '100%': { transform: 'rotate(360deg)' }
200
- },
201
- div: {
202
- border: 'solid red 1px',
203
- '.c1': { 'background-color': '#000' },
204
- ' .c1': { backgroundColor: 'black' },
205
- '.c2': { backgroundColor: 'rgb(0,0,0)' }
206
- },
207
- '@media(min-width:200px)': {
208
- div: { margin: 0, padding: 0 },
209
- span: { color: '#000' }
210
- }
211
- })
212
-
213
- const expected = `
214
- @font-face{
215
- font-family:Jackens;
216
- src:url(otf/jackens.otf);
217
- src:url(otf/jackens.otf) format('opentype'),url(svg/jackens.svg) format('svg');
218
- font-weight:normal;
219
- font-style:normal
220
- }
221
- @font-face{
222
- font-family:C64;
223
- src:url(fonts/C64_Pro_Mono-STYLE.woff)
224
- }
225
- @keyframes spin{
226
- 0%{
227
- transform:rotate(0deg)
228
- }
229
- 100%{
230
- transform:rotate(360deg)
231
- }
232
- }
233
- div{
234
- border:solid red 1px
235
- }
236
- div.c1{
237
- background-color:#000
238
- }
239
- div .c1{
240
- background-color:black
241
- }
242
- div.c2{
243
- background-color:rgb(0,0,0)
244
- }
245
- @media(min-width:200px){
246
- div{
247
- margin:0;
248
- padding:0
249
- }
250
- span{
251
- color:#000
252
- }
253
- }`.replace(/\n\s*/g, '')
254
-
255
- expect(actual).toStrictEqual(expected)
256
- ```
257
-
258
- ```js
259
- const actual = c({
260
- a: {
261
- '.b,.c': {
262
- margin: 1,
263
- '.d': {
264
- margin: 2
265
- }
266
- }
267
- }
268
- })
269
-
270
- const expected = `
271
- a.b,a.c{
272
- margin:1
273
- }
274
- a.b.d,a.c.d{
275
- margin:2
276
- }`.replace(/\n\s*/g, '')
277
-
278
- expect(actual).toStrictEqual(expected)
97
+ type JcNode = {
98
+ [attributeOrSelector: string]: string | number | JcNode | undefined;
99
+ };
279
100
  ```
280
101
 
281
- ```js
282
- const actual = c({
283
- '.b,.c': {
284
- margin: 1,
285
- '.d': {
286
- margin: 2
287
- }
288
- }
289
- })
102
+ The type of arguments of the `jc` helper.
290
103
 
291
- const expected = `
292
- .b,.c{
293
- margin:1
294
- }
295
- .b.d,.c.d{
296
- margin:2
297
- }`.replace(/\n\s*/g, '')
104
+ ### JcRoot
298
105
 
299
- expect(actual).toStrictEqual(expected)
106
+ ```ts
107
+ type JcRoot = Partial<Record<PropertyKey, JcNode>>;
300
108
  ```
301
109
 
302
- ```js
303
- const actual = c({
304
- '.a,.b': {
305
- margin: 1,
306
- '.c,.d': {
307
- margin: 2
308
- }
309
- }
310
- })
311
-
312
- const expected = `
313
- .a,.b{
314
- margin:1
315
- }
316
- .a.c,.a.d,.b.c,.b.d{
317
- margin:2
318
- }`.replace(/\n\s*/g, '')
319
-
320
- expect(actual).toStrictEqual(expected)
321
- ```
110
+ The type of arguments of the `jc` helper.
322
111
 
323
112
  ### chartable
324
113
 
@@ -665,6 +454,218 @@ try {
665
454
  expect(is(Number, num)).toBeTrue()
666
455
  ```
667
456
 
457
+ ### jc
458
+
459
+ ```ts
460
+ const jc: (root: JcRoot, splitter?: string) => string;
461
+ ```
462
+
463
+ A simple JS-to-CSS (aka CSS-in-JS) helper.
464
+
465
+ The `root` parameter provides a hierarchical description of CSS rules.
466
+
467
+ - Keys of sub-objects whose values are NOT objects are treated as CSS attribute, and values are treated as values of those CSS attributes; the concatenation of keys of all parent objects is a CSS rule.
468
+ - All keys ignore the part starting with a splitter (default: `$$`) sign until the end of the key (e.g. `src$$1` → `src`, `@font-face$$1` → `@font-face`).
469
+ - In keys specifying CSS attribute, all uppercase letters are replaced by lowercase letters with an additional `-` character preceding them (e.g. `fontFamily` → `font-family`).
470
+ - Commas in keys that makes a CSS rule cause it to “split” and create separate rules for each part (e.g. `{div:{margin:1,'.a,.b,.c':{margin:2}}}` → `div{margin:1}div.a,div.b,div.c{margin:2}`).
471
+ - Top-level keys that begin with `@` are not concatenated with sub-object keys.
472
+
473
+ #### Usage Examples
474
+
475
+ ```js
476
+ const actual = jc({
477
+ a: {
478
+ color: 'red',
479
+ margin: 1,
480
+ '.c': { margin: 2, padding: 2 },
481
+ padding: 1
482
+ }
483
+ })
484
+
485
+ const expected = `
486
+ a{
487
+ color:red;
488
+ margin:1
489
+ }
490
+ a.c{
491
+ margin:2;
492
+ padding:2
493
+ }
494
+ a{
495
+ padding:1
496
+ }`.replace(/\n\s*/g, '')
497
+
498
+ expect(actual).toStrictEqual(expected)
499
+ ```
500
+
501
+ ```js
502
+ const actual = jc({
503
+ a: {
504
+ '.b': {
505
+ color: 'red',
506
+ margin: 1,
507
+ '.c': { margin: 2, padding: 2 },
508
+ padding: 1
509
+ }
510
+ }
511
+ })
512
+
513
+ const expected = `
514
+ a.b{
515
+ color:red;
516
+ margin:1
517
+ }
518
+ a.b.c{
519
+ margin:2;
520
+ padding:2
521
+ }
522
+ a.b{
523
+ padding:1
524
+ }`.replace(/\n\s*/g, '')
525
+
526
+ expect(actual).toStrictEqual(expected)
527
+ ```
528
+
529
+ ```js
530
+ const actual = jc({
531
+ '@font-face$$1': {
532
+ fontFamily: 'Jackens',
533
+ src$$1: 'url(otf/jackens.otf)',
534
+ src$$2: "url(otf/jackens.otf) format('opentype')," +
535
+ "url(svg/jackens.svg) format('svg')",
536
+ fontWeight: 'normal',
537
+ fontStyle: 'normal'
538
+ },
539
+ '@font-face$$2': {
540
+ fontFamily: 'C64',
541
+ src: 'url(fonts/C64_Pro_Mono-STYLE.woff)'
542
+ },
543
+ '@keyframes spin': {
544
+ '0%': { transform: 'rotate(0deg)' },
545
+ '100%': { transform: 'rotate(360deg)' }
546
+ },
547
+ div: {
548
+ border: 'solid red 1px',
549
+ '.c1': { 'background-color': '#000' },
550
+ ' .c1': { backgroundColor: 'black' },
551
+ '.c2': { backgroundColor: 'rgb(0,0,0)' }
552
+ },
553
+ '@media(min-width:200px)': {
554
+ div: { margin: 0, padding: 0 },
555
+ span: { color: '#000' }
556
+ }
557
+ })
558
+
559
+ const expected = `
560
+ @font-face{
561
+ font-family:Jackens;
562
+ src:url(otf/jackens.otf);
563
+ src:url(otf/jackens.otf) format('opentype'),url(svg/jackens.svg) format('svg');
564
+ font-weight:normal;
565
+ font-style:normal
566
+ }
567
+ @font-face{
568
+ font-family:C64;
569
+ src:url(fonts/C64_Pro_Mono-STYLE.woff)
570
+ }
571
+ @keyframes spin{
572
+ 0%{
573
+ transform:rotate(0deg)
574
+ }
575
+ 100%{
576
+ transform:rotate(360deg)
577
+ }
578
+ }
579
+ div{
580
+ border:solid red 1px
581
+ }
582
+ div.c1{
583
+ background-color:#000
584
+ }
585
+ div .c1{
586
+ background-color:black
587
+ }
588
+ div.c2{
589
+ background-color:rgb(0,0,0)
590
+ }
591
+ @media(min-width:200px){
592
+ div{
593
+ margin:0;
594
+ padding:0
595
+ }
596
+ span{
597
+ color:#000
598
+ }
599
+ }`.replace(/\n\s*/g, '')
600
+
601
+ expect(actual).toStrictEqual(expected)
602
+ ```
603
+
604
+ ```js
605
+ const actual = jc({
606
+ a: {
607
+ '.b,.c': {
608
+ margin: 1,
609
+ '.d': {
610
+ margin: 2
611
+ }
612
+ }
613
+ }
614
+ })
615
+
616
+ const expected = `
617
+ a.b,a.c{
618
+ margin:1
619
+ }
620
+ a.b.d,a.c.d{
621
+ margin:2
622
+ }`.replace(/\n\s*/g, '')
623
+
624
+ expect(actual).toStrictEqual(expected)
625
+ ```
626
+
627
+ ```js
628
+ const actual = jc({
629
+ '.b,.c': {
630
+ margin: 1,
631
+ '.d': {
632
+ margin: 2
633
+ }
634
+ }
635
+ })
636
+
637
+ const expected = `
638
+ .b,.c{
639
+ margin:1
640
+ }
641
+ .b.d,.c.d{
642
+ margin:2
643
+ }`.replace(/\n\s*/g, '')
644
+
645
+ expect(actual).toStrictEqual(expected)
646
+ ```
647
+
648
+ ```js
649
+ const actual = jc({
650
+ '.a,.b': {
651
+ margin: 1,
652
+ '.c,.d': {
653
+ margin: 2
654
+ }
655
+ }
656
+ })
657
+
658
+ const expected = `
659
+ .a,.b{
660
+ margin:1
661
+ }
662
+ .a.c,.a.d,.b.c,.b.d{
663
+ margin:2
664
+ }`.replace(/\n\s*/g, '')
665
+
666
+ expect(actual).toStrictEqual(expected)
667
+ ```
668
+
668
669
  ### jsOnParse
669
670
 
670
671
  ```ts
@@ -783,8 +784,15 @@ A helper for highlighting JavaScript.
783
784
  ```js
784
785
  const codeJs = 'const answerToLifeTheUniverseAndEverything = 42'
785
786
 
786
- expect(h('pre', ['code', ...nanolightJs(codeJs)]).outerHTML).toStrictEqual(
787
- '<pre><code><span class="keyword">const</span> <span class="literal">answerToLifeTheUniverseAndEverything</span> <span class="operator">=</span> <span class="number">42</span></code></pre>')
787
+ expect(nanolightJs(codeJs)).toStrictEqual([
788
+ ['span', { class: 'keyword' }, 'const'],
789
+ ' ',
790
+ ['span', { class: 'literal' }, 'answerToLifeTheUniverseAndEverything'],
791
+ ' ',
792
+ ['span', { class: 'operator' }, '='],
793
+ ' ',
794
+ ['span', { class: 'number' }, '42']
795
+ ])
788
796
  ```
789
797
 
790
798
  ### omit
@@ -950,6 +958,14 @@ A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style help
950
958
  - All other arguments of type `string`/`number` are converted to `Text` nodes and appended to the element being created or modified.
951
959
  - All other arguments of type `HArgs` are passed to `s` and the results are appended to the element being created or modified.
952
960
 
961
+ ### svgUse
962
+
963
+ ```ts
964
+ const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement;
965
+ ```
966
+
967
+ A convenient shortcut for `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
968
+
953
969
  ### uuid1
954
970
 
955
971
  ```ts