@jackens/nnn 2026.9.9 → 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.
- package/README.md +1099 -0
- package/nnn.d.ts +175 -204
- package/nnn.js +81 -92
- package/package.json +2 -13
- package/readme.md +0 -1370
package/README.md
ADDED
|
@@ -0,0 +1,1099 @@
|
|
|
1
|
+
# nnn
|
|
2
|
+
|
|
3
|
+
A collection of Jackens’ JavaScript helper utilities (version: `2026.9.28`).
|
|
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
|
+
TokenizerSpec,
|
|
25
|
+
TokenizerSpecOptions,
|
|
26
|
+
c,
|
|
27
|
+
createTokenizer,
|
|
28
|
+
h,
|
|
29
|
+
monokai,
|
|
30
|
+
nanolightTs,
|
|
31
|
+
rwd,
|
|
32
|
+
s,
|
|
33
|
+
svgUse,
|
|
34
|
+
} from '@jackens/nnn' // or './node_modules/@jackens/nnn/nnn.js'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Exports
|
|
38
|
+
|
|
39
|
+
- [`CNode`](#CNode):
|
|
40
|
+
Represents a CSS rule node for the [`c`](#c) helper. Keys are CSS properties or nested selectors.
|
|
41
|
+
- [`HArgs`](#HArgs):
|
|
42
|
+
Tuple argument type for the [`h`](#h) and [`s`](#s) helpers.
|
|
43
|
+
- [`HArgs1`](#HArgs1):
|
|
44
|
+
Single argument type for the [`h`](#h) and [`s`](#s) helpers.
|
|
45
|
+
- [`TokenizerSpec`](#TokenizerSpec):
|
|
46
|
+
A [`createTokenizer`](#createTokenizer) spec: a `[metadata, pattern, options?]` tuple.
|
|
47
|
+
- [`TokenizerSpecOptions`](#TokenizerSpecOptions):
|
|
48
|
+
Options of a [`createTokenizer`](#createTokenizer) spec controlling the tokenizer state stack.
|
|
49
|
+
- [`c`](#c):
|
|
50
|
+
A minimal CSS-in-JS helper that converts a JavaScript object hierarchy into a CSS string.
|
|
51
|
+
- [`createTokenizer`](#createTokenizer):
|
|
52
|
+
A helper for building simple stateful tokenizers (see also [`nanolightTs`](#nanolightTs)).
|
|
53
|
+
- [`h`](#h):
|
|
54
|
+
A lightweight _HyperScript_-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
|
|
55
|
+
- [`monokai`](#monokai):
|
|
56
|
+
A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) tokenizer.
|
|
57
|
+
- [`nanolightTs`](#nanolightTs):
|
|
58
|
+
A TypeScript/JavaScript syntax highlighting tokenizer built using [`createTokenizer`](#createTokenizer).
|
|
59
|
+
- [`rwd`](#rwd):
|
|
60
|
+
A responsive web design helper that generates CSS rules for a grid-like layout.
|
|
61
|
+
- [`s`](#s):
|
|
62
|
+
A lightweight _HyperScript_-style helper for creating and modifying `SVGElement`s (see also [`h`](#h)).
|
|
63
|
+
- [`svgUse`](#svgUse):
|
|
64
|
+
Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
|
|
65
|
+
|
|
66
|
+
### CNode
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
type CNode = {
|
|
70
|
+
[attributeOrSelector: string]: string | number | CNode | undefined
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Represents a CSS rule node for the [`c`](#c) helper. Keys are CSS properties or nested selectors.
|
|
75
|
+
|
|
76
|
+
### HArgs
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
type HArgs = [string | Node, ...HArgs1[]]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Tuple argument type for the [`h`](#h) and [`s`](#s) helpers.
|
|
83
|
+
|
|
84
|
+
### HArgs1
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string | number | HArgs
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Single argument type for the [`h`](#h) and [`s`](#s) helpers.
|
|
91
|
+
|
|
92
|
+
### TokenizerSpec
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
type TokenizerSpec<M> = [metadata: M, pattern: string | RegExp, options?: TokenizerSpecOptions]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A [`createTokenizer`](#createTokenizer) spec: a `[metadata, pattern, options?]` tuple.
|
|
99
|
+
|
|
100
|
+
### TokenizerSpecOptions
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
type TokenizerSpecOptions = {
|
|
104
|
+
in?: string | string[]
|
|
105
|
+
pop?: boolean
|
|
106
|
+
push?: string
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Options of a [`createTokenizer`](#createTokenizer) spec controlling the tokenizer state stack.
|
|
111
|
+
|
|
112
|
+
#### in
|
|
113
|
+
|
|
114
|
+
State(s) in which the spec is active (default: `'root'`).
|
|
115
|
+
|
|
116
|
+
#### pop
|
|
117
|
+
|
|
118
|
+
Whether to pop the current state off the stack after a match (the `'root'` state at the bottom is never popped).
|
|
119
|
+
|
|
120
|
+
#### push
|
|
121
|
+
|
|
122
|
+
State to push onto the stack after a match (applied after `pop`).
|
|
123
|
+
|
|
124
|
+
### c
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const c: (node: CNode, splitter?: string) => string
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
A minimal CSS-in-JS helper that converts a JavaScript object hierarchy into a CSS string.
|
|
131
|
+
|
|
132
|
+
#### node
|
|
133
|
+
|
|
134
|
+
An object describing CSS rules.
|
|
135
|
+
Keys are selectors or at-rules; values are either CSS property values or nested rule objects.
|
|
136
|
+
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'`).
|
|
137
|
+
|
|
138
|
+
#### splitter
|
|
139
|
+
|
|
140
|
+
A delimiter used to create unique keys (default: `'$$'`).
|
|
141
|
+
The substring from `splitter` to the end of a key is ignored (e.g., `src$$1` → `src`).
|
|
142
|
+
|
|
143
|
+
#### Returns
|
|
144
|
+
|
|
145
|
+
A CSS string representing the compiled rules.
|
|
146
|
+
|
|
147
|
+
#### Usage Examples
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const actual = c({
|
|
151
|
+
a: {
|
|
152
|
+
color: 'red',
|
|
153
|
+
margin: 1,
|
|
154
|
+
'.c': { margin: 2, padding: 2 },
|
|
155
|
+
padding: 1,
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const expected = `
|
|
160
|
+
a{
|
|
161
|
+
color:red;
|
|
162
|
+
margin:1
|
|
163
|
+
}
|
|
164
|
+
a.c{
|
|
165
|
+
margin:2;
|
|
166
|
+
padding:2
|
|
167
|
+
}
|
|
168
|
+
a{
|
|
169
|
+
padding:1
|
|
170
|
+
}
|
|
171
|
+
`.replace(/\n\s*/g, '')
|
|
172
|
+
|
|
173
|
+
expect(actual).toBe(expected)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const actual = c({
|
|
178
|
+
a: {
|
|
179
|
+
'.b': {
|
|
180
|
+
color: 'red',
|
|
181
|
+
margin: 1,
|
|
182
|
+
'.c': { margin: 2, padding: 2 },
|
|
183
|
+
padding: 1,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const expected = `
|
|
189
|
+
a.b{
|
|
190
|
+
color:red;
|
|
191
|
+
margin:1
|
|
192
|
+
}
|
|
193
|
+
a.b.c{
|
|
194
|
+
margin:2;
|
|
195
|
+
padding:2
|
|
196
|
+
}
|
|
197
|
+
a.b{
|
|
198
|
+
padding:1
|
|
199
|
+
}
|
|
200
|
+
`.replace(/\n\s*/g, '')
|
|
201
|
+
|
|
202
|
+
expect(actual).toBe(expected)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const actual = c({
|
|
207
|
+
'@font-face$$1': {
|
|
208
|
+
'font-family': 'Jackens',
|
|
209
|
+
src$$1: 'url(otf/jackens.otf)',
|
|
210
|
+
src$$2: "url(otf/jackens.otf) format('opentype')," + "url(svg/jackens.svg) format('svg')",
|
|
211
|
+
'font-weight': 'normal',
|
|
212
|
+
'font-style': 'normal',
|
|
213
|
+
},
|
|
214
|
+
'@font-face$$2': {
|
|
215
|
+
'font-family': 'C64',
|
|
216
|
+
src: 'url(fonts/C64_Pro_Mono-STYLE.woff)',
|
|
217
|
+
},
|
|
218
|
+
'@keyframes spin': {
|
|
219
|
+
'0%': { transform: 'rotate(0deg)' },
|
|
220
|
+
'100%': { transform: 'rotate(360deg)' },
|
|
221
|
+
},
|
|
222
|
+
div: {
|
|
223
|
+
border: 'solid red 1px',
|
|
224
|
+
'.c1': { 'background-color': '#000' },
|
|
225
|
+
' .c1': { 'background-color': 'black' },
|
|
226
|
+
'.c2': { 'background-color': 'rgb(0,0,0)' },
|
|
227
|
+
},
|
|
228
|
+
'@media(min-width:200px)': {
|
|
229
|
+
div: { margin: 0, padding: 0 },
|
|
230
|
+
span: { color: '#000' },
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const expected = `
|
|
235
|
+
@font-face{
|
|
236
|
+
font-family:Jackens;
|
|
237
|
+
src:url(otf/jackens.otf);
|
|
238
|
+
src:url(otf/jackens.otf) format('opentype'),url(svg/jackens.svg) format('svg');
|
|
239
|
+
font-weight:normal;
|
|
240
|
+
font-style:normal
|
|
241
|
+
}
|
|
242
|
+
@font-face{
|
|
243
|
+
font-family:C64;
|
|
244
|
+
src:url(fonts/C64_Pro_Mono-STYLE.woff)
|
|
245
|
+
}
|
|
246
|
+
@keyframes spin{
|
|
247
|
+
0%{
|
|
248
|
+
transform:rotate(0deg)
|
|
249
|
+
}
|
|
250
|
+
100%{
|
|
251
|
+
transform:rotate(360deg)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
div{
|
|
255
|
+
border:solid red 1px
|
|
256
|
+
}
|
|
257
|
+
div.c1{
|
|
258
|
+
background-color:#000
|
|
259
|
+
}
|
|
260
|
+
div .c1{
|
|
261
|
+
background-color:black
|
|
262
|
+
}
|
|
263
|
+
div.c2{
|
|
264
|
+
background-color:rgb(0,0,0)
|
|
265
|
+
}
|
|
266
|
+
@media(min-width:200px){
|
|
267
|
+
div{
|
|
268
|
+
margin:0;
|
|
269
|
+
padding:0
|
|
270
|
+
}
|
|
271
|
+
span{
|
|
272
|
+
color:#000
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
`.replace(/\n\s*/g, '')
|
|
276
|
+
|
|
277
|
+
expect(actual).toBe(expected)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
const actual = c({
|
|
282
|
+
a: {
|
|
283
|
+
'.b,.c': {
|
|
284
|
+
margin: 1,
|
|
285
|
+
'.d': {
|
|
286
|
+
margin: 2,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const expected = `
|
|
293
|
+
a.b,a.c{
|
|
294
|
+
margin:1
|
|
295
|
+
}
|
|
296
|
+
a.b.d,a.c.d{
|
|
297
|
+
margin:2
|
|
298
|
+
}
|
|
299
|
+
`.replace(/\n\s*/g, '')
|
|
300
|
+
|
|
301
|
+
expect(actual).toBe(expected)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const actual = c({
|
|
306
|
+
'.b,.c': {
|
|
307
|
+
margin: 1,
|
|
308
|
+
'.d': {
|
|
309
|
+
margin: 2,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
const expected = `
|
|
315
|
+
.b,.c{
|
|
316
|
+
margin:1
|
|
317
|
+
}
|
|
318
|
+
.b.d,.c.d{
|
|
319
|
+
margin:2
|
|
320
|
+
}
|
|
321
|
+
`.replace(/\n\s*/g, '')
|
|
322
|
+
|
|
323
|
+
expect(actual).toBe(expected)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
const actual = c({
|
|
328
|
+
'.a,.b': {
|
|
329
|
+
margin: 1,
|
|
330
|
+
'.c,.d': {
|
|
331
|
+
margin: 2,
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const expected = `
|
|
337
|
+
.a,.b{
|
|
338
|
+
margin:1
|
|
339
|
+
}
|
|
340
|
+
.a.c,.a.d,.b.c,.b.d{
|
|
341
|
+
margin:2
|
|
342
|
+
}
|
|
343
|
+
`.replace(/\n\s*/g, '')
|
|
344
|
+
|
|
345
|
+
expect(actual).toBe(expected)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
const actual = c({
|
|
350
|
+
':root$$monokai': {
|
|
351
|
+
'--': {
|
|
352
|
+
bg: '#faf4f2',
|
|
353
|
+
fg: '#29242a',
|
|
354
|
+
comment: '#918c8e',
|
|
355
|
+
identifier: {
|
|
356
|
+
1: '#7058be',
|
|
357
|
+
2: '#269d69',
|
|
358
|
+
3: '#1c8ca8',
|
|
359
|
+
4: '#29242a',
|
|
360
|
+
},
|
|
361
|
+
keyword: {
|
|
362
|
+
1: '#e14775',
|
|
363
|
+
2: '#7058be',
|
|
364
|
+
3: '#1c8ca8',
|
|
365
|
+
},
|
|
366
|
+
number: '#7058be',
|
|
367
|
+
operator: '#e14775',
|
|
368
|
+
punctuation: '#918c8e',
|
|
369
|
+
string: '#cc7a0a',
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
'@media only screen and (prefers-color-scheme: dark)$$monokai': {
|
|
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
|
+
### createTokenizer
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
const createTokenizer: <M, T>(
|
|
442
|
+
decorator: (chunk: string, metadata?: M) => T,
|
|
443
|
+
...specs: TokenizerSpec<M>[]
|
|
444
|
+
) => (code: string) => T[]
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
A helper for building simple stateful tokenizers (see also [`nanolightTs`](#nanolightTs)).
|
|
448
|
+
|
|
449
|
+
#### Remarks
|
|
450
|
+
|
|
451
|
+
1. Matches starting at an earlier position take precedence.
|
|
452
|
+
2. Among matches at the same position, the longer one wins.
|
|
453
|
+
3. Among matches of the same position and length, the one defined earlier wins.
|
|
454
|
+
4. Only specs active in the current state (the top of the state stack, initially `'root'`) are considered.
|
|
455
|
+
5. Patterns are matched against the whole input, so `^`, `$`, `\b` and lookbehinds see the text around the current position; a sticky (`y`) `RegExp` matches only at the current position.
|
|
456
|
+
6. Empty matches are ignored.
|
|
457
|
+
|
|
458
|
+
#### decorator
|
|
459
|
+
|
|
460
|
+
A function that wraps each matched chunk.
|
|
461
|
+
It receives the matched text (`chunk`) and optionally the `metadata` associated with the pattern that produced the match.
|
|
462
|
+
For unmatched text between patterns, `metadata` is `undefined`.
|
|
463
|
+
|
|
464
|
+
#### specs
|
|
465
|
+
|
|
466
|
+
An array of tuples `[metadata, pattern, options?]` where:
|
|
467
|
+
|
|
468
|
+
- `metadata`: arbitrary data (e.g., a CSS class name) passed to `decorator` when the pattern matches.
|
|
469
|
+
- `pattern`: a `string` or `RegExp` to match against the input.
|
|
470
|
+
- `options`: optional [`TokenizerSpecOptions`](#TokenizerSpecOptions) (states in which the spec is active and state transitions).
|
|
471
|
+
|
|
472
|
+
#### Returns
|
|
473
|
+
|
|
474
|
+
A tokenizer function that accepts a code string and returns an array of decorated tokens.
|
|
475
|
+
|
|
476
|
+
#### Usage Examples
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
const tokenizer = createTokenizer(
|
|
480
|
+
(chunk, metadata) => ({ chunk, metadata }),
|
|
481
|
+
['keyword', /\b(if|else|for)\b/],
|
|
482
|
+
['string', /"[^"]*"/]
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
const result = tokenizer('if "hello" else "world"')
|
|
486
|
+
|
|
487
|
+
expect(result).toEqual([
|
|
488
|
+
{ chunk: 'if', metadata: 'keyword' },
|
|
489
|
+
{ chunk: ' ', metadata: undefined },
|
|
490
|
+
{ chunk: '"hello"', metadata: 'string' },
|
|
491
|
+
{ chunk: ' ', metadata: undefined },
|
|
492
|
+
{ chunk: 'else', metadata: 'keyword' },
|
|
493
|
+
{ chunk: ' ', metadata: undefined },
|
|
494
|
+
{ chunk: '"world"', metadata: 'string' },
|
|
495
|
+
])
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['tag', 'BEGIN'], ['end', 'END'])
|
|
500
|
+
|
|
501
|
+
const result = tokenizer('aBEGINbENDc')
|
|
502
|
+
|
|
503
|
+
expect(result).toEqual(['undefined:a', 'tag:BEGIN', 'undefined:b', 'end:END', 'undefined:c'])
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
const tokenizer = createTokenizer(chunk => chunk, ['test', /test/])
|
|
508
|
+
|
|
509
|
+
const result = tokenizer('')
|
|
510
|
+
|
|
511
|
+
expect(result).toEqual([])
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
const tokenizer = createTokenizer((chunk, metadata) => ({ chunk, metadata }), ['start', /^test/])
|
|
516
|
+
|
|
517
|
+
const result = tokenizer('test here')
|
|
518
|
+
|
|
519
|
+
expect(result).toEqual([
|
|
520
|
+
{ chunk: 'test', metadata: 'start' },
|
|
521
|
+
{ chunk: ' here', metadata: undefined },
|
|
522
|
+
])
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
const tokenizer = createTokenizer((chunk, metadata) => metadata, ['later', /x/], ['earlier', /y/])
|
|
527
|
+
|
|
528
|
+
const result = tokenizer('yx')
|
|
529
|
+
|
|
530
|
+
expect(result).toEqual(['earlier', 'later'])
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
```ts
|
|
534
|
+
const tokenizer = createTokenizer(chunk => chunk, ['short', 'a'], ['long', 'abc'])
|
|
535
|
+
|
|
536
|
+
const result = tokenizer('abc')
|
|
537
|
+
|
|
538
|
+
expect(result).toEqual(['abc'])
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
const tokenizer = createTokenizer(chunk => chunk, ['empty', ''], ['word', /\w+/])
|
|
543
|
+
|
|
544
|
+
const result = tokenizer('hello')
|
|
545
|
+
|
|
546
|
+
expect(result).toEqual(['hello'])
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
const tokenizer = createTokenizer(chunk => chunk, ['test', /xyz/])
|
|
551
|
+
|
|
552
|
+
const result = tokenizer('abc')
|
|
553
|
+
|
|
554
|
+
expect(result).toEqual(['abc'])
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
const tokenizer = createTokenizer((_chunk, metadata) => metadata, ['a', 'a'], ['b', 'b'])
|
|
559
|
+
|
|
560
|
+
const result = tokenizer('aabb')
|
|
561
|
+
|
|
562
|
+
expect(result).toEqual(['a', 'a', 'b', 'b'])
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
const tokenizer = createTokenizer(
|
|
567
|
+
(chunk, metadata) => `${metadata}:${chunk}`,
|
|
568
|
+
['open', '(', { push: 'inner' }],
|
|
569
|
+
['word', /\w+/, { in: 'inner' }],
|
|
570
|
+
['close', ')', { in: 'inner', pop: true }]
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
const result = tokenizer('a(b)c)')
|
|
574
|
+
|
|
575
|
+
expect(result).toEqual(['undefined:a', 'open:(', 'word:b', 'close:)', 'undefined:c)'])
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const tokenizer = createTokenizer(
|
|
580
|
+
(chunk, metadata) => `${metadata}:${chunk}`,
|
|
581
|
+
['open', '{', { in: ['root', 'nested'], push: 'nested' }],
|
|
582
|
+
['close', '}', { in: 'nested', pop: true }]
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
const result = tokenizer('{{}}}')
|
|
586
|
+
|
|
587
|
+
expect(result).toEqual(['open:{', 'open:{', 'close:}', 'close:}', 'undefined:}'])
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
```ts
|
|
591
|
+
const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['a', 'a'], ['b', /(?<=a)b/])
|
|
592
|
+
|
|
593
|
+
const result = tokenizer('abcb')
|
|
594
|
+
|
|
595
|
+
expect(result).toEqual(['a:a', 'b:b', 'undefined:cb'])
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['x', /x/y], ['y', 'y'])
|
|
600
|
+
|
|
601
|
+
const result = tokenizer('xyx.x')
|
|
602
|
+
|
|
603
|
+
expect(result).toEqual(['x:x', 'y:y', 'x:x', 'undefined:.x'])
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
```ts
|
|
607
|
+
const tokenizer = createTokenizer((chunk, metadata) => `${metadata}:${chunk}`, ['digits', /\d*/])
|
|
608
|
+
|
|
609
|
+
const result = tokenizer('ab12')
|
|
610
|
+
|
|
611
|
+
expect(result).toEqual(['undefined:ab', 'digits:12'])
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### h
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
const h: {
|
|
618
|
+
<T extends keyof HTMLElementTagNameMap>(tag: T, ...args1: HArgs1[]): HTMLElementTagNameMap[T]
|
|
619
|
+
<N extends Node>(node: N, ...args1: HArgs1[]): N
|
|
620
|
+
(tagOrNode: string | Node, ...args1: HArgs1[]): Node
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
A lightweight _HyperScript_-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
|
|
625
|
+
|
|
626
|
+
#### tagOrNode
|
|
627
|
+
|
|
628
|
+
If a `string`, it is treated as the tag name for a new element.
|
|
629
|
+
If a `Node`, that node is modified in place.
|
|
630
|
+
|
|
631
|
+
#### args
|
|
632
|
+
|
|
633
|
+
Additional arguments processed as follows:
|
|
634
|
+
|
|
635
|
+
- `Object`: maps attributes/properties.
|
|
636
|
+
Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it (useful e.g. for `$style`).
|
|
637
|
+
Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
|
|
638
|
+
A value of `false` removes the attribute.
|
|
639
|
+
- `null`/`undefined`: ignored.
|
|
640
|
+
- `Node`: appended as a child.
|
|
641
|
+
- `string`/`number`: converted to a `Text` node and appended.
|
|
642
|
+
- [`HArgs`](#HArgs) array: processed recursively.
|
|
643
|
+
|
|
644
|
+
#### Returns
|
|
645
|
+
|
|
646
|
+
The created or modified `HTMLElement`.
|
|
647
|
+
|
|
648
|
+
#### Usage Examples
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
const b = h('b')
|
|
652
|
+
|
|
653
|
+
expect(b.outerHTML).toBe('<b></b>')
|
|
654
|
+
|
|
655
|
+
const i = h('i', 'text')
|
|
656
|
+
|
|
657
|
+
h(b, i)
|
|
658
|
+
|
|
659
|
+
expect(i.outerHTML).toBe('<i>text</i>')
|
|
660
|
+
|
|
661
|
+
expect(b.outerHTML).toBe('<b><i>text</i></b>')
|
|
662
|
+
|
|
663
|
+
h(i, { $className: 'some class' })
|
|
664
|
+
|
|
665
|
+
expect(i.outerHTML).toBe('<i class="some class">text</i>')
|
|
666
|
+
|
|
667
|
+
expect(b.outerHTML).toBe('<b><i class="some class">text</i></b>')
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
expect(h('span', 'text').outerHTML).toBe('<span>text</span>')
|
|
672
|
+
|
|
673
|
+
expect(h('span', { $innerText: 'text' }).outerHTML).toBe('<span>text</span>')
|
|
674
|
+
|
|
675
|
+
expect(h('span', '42').outerHTML).toBe('<span>42</span>')
|
|
676
|
+
|
|
677
|
+
expect(h('span', 42).outerHTML).toBe('<span>42</span>')
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
```ts
|
|
681
|
+
expect(h('div', { style: 'margin:0;padding:0' }).outerHTML).toBe('<div style="margin:0;padding:0"></div>')
|
|
682
|
+
|
|
683
|
+
expect(h('div', { $style: 'margin:0;padding:0' }).outerHTML).toBe('<div style="margin: 0px; padding: 0px;"></div>')
|
|
684
|
+
|
|
685
|
+
expect(h('div', { $style: { margin: 0, padding: 0 } }).outerHTML).toBe(
|
|
686
|
+
'<div style="margin: 0px; padding: 0px;"></div>'
|
|
687
|
+
)
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
const input1 = h('input', { value: 42 })
|
|
692
|
+
const input2 = h('input', { $value: '42' })
|
|
693
|
+
|
|
694
|
+
expect(input1.value).toBe('42')
|
|
695
|
+
expect(input2.value).toBe('42')
|
|
696
|
+
|
|
697
|
+
expect(input1.outerHTML).toBe('<input value="42">')
|
|
698
|
+
expect(input2.outerHTML).toBe('<input>')
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
```ts
|
|
702
|
+
const checkbox1 = h('input', {
|
|
703
|
+
type: 'checkbox',
|
|
704
|
+
checked: true,
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
const checkbox2 = h('input', {
|
|
708
|
+
type: 'checkbox',
|
|
709
|
+
$checked: true,
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
expect(checkbox1.checked).toBe(true)
|
|
713
|
+
expect(checkbox2.checked).toBe(true)
|
|
714
|
+
|
|
715
|
+
expect(checkbox1.outerHTML).toBe('<input type="checkbox" checked="">')
|
|
716
|
+
|
|
717
|
+
expect(checkbox2.outerHTML).toBe('<input type="checkbox">')
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
```ts
|
|
721
|
+
const div = h('div')
|
|
722
|
+
|
|
723
|
+
expect(div.key).toBeUndefined()
|
|
724
|
+
|
|
725
|
+
h(div, { $key: { one: 1 } })
|
|
726
|
+
|
|
727
|
+
expect(div.key).toEqual({ one: 1 })
|
|
728
|
+
|
|
729
|
+
h(div, { $key: { two: 2 } })
|
|
730
|
+
|
|
731
|
+
expect(div.key).toEqual({ one: 1, two: 2 })
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
const elemWithClass = h('div', { class: 'test' })
|
|
736
|
+
|
|
737
|
+
expect(elemWithClass.getAttribute('class')).toBe('test')
|
|
738
|
+
|
|
739
|
+
const elemWithText = h('div', 'initial')
|
|
740
|
+
|
|
741
|
+
h(elemWithText, ' more')
|
|
742
|
+
|
|
743
|
+
expect(elemWithText.outerHTML).toBe('<div>initial more</div>')
|
|
744
|
+
|
|
745
|
+
const elemWithNested = h('div', ['span', 'hello'], ['b', 'world'])
|
|
746
|
+
|
|
747
|
+
expect(elemWithNested.outerHTML).toBe('<div><span>hello</span><b>world</b></div>')
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### monokai
|
|
751
|
+
|
|
752
|
+
```ts
|
|
753
|
+
const monokai: CNode
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolightTs`](#nanolightTs) tokenizer.
|
|
757
|
+
|
|
758
|
+
### nanolightTs
|
|
759
|
+
|
|
760
|
+
```ts
|
|
761
|
+
const nanolightTs: (code: string) => HArgs1[]
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
A TypeScript/JavaScript syntax highlighting tokenizer built using [`createTokenizer`](#createTokenizer).
|
|
765
|
+
|
|
766
|
+
#### code
|
|
767
|
+
|
|
768
|
+
The source code string to tokenize.
|
|
769
|
+
|
|
770
|
+
#### Returns
|
|
771
|
+
|
|
772
|
+
An array of [`HArgs1`](#HArgs1) elements suitable for rendering with [`h`](#h).
|
|
773
|
+
|
|
774
|
+
#### Usage Examples
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
const codeJs = 'const answerToLifeTheUniverseAndEverything = ' + "{ 42: 42 }['42'] /* 42 */"
|
|
778
|
+
|
|
779
|
+
expect(nanolightTs(codeJs)).toEqual([
|
|
780
|
+
['span', { class: 'keyword1' }, 'const'],
|
|
781
|
+
' ',
|
|
782
|
+
['span', { class: 'identifier4' }, 'answerToLifeTheUniverseAndEverything'],
|
|
783
|
+
' ',
|
|
784
|
+
['span', { class: 'operator' }, '='],
|
|
785
|
+
' ',
|
|
786
|
+
['span', { class: 'punctuation' }, '{'],
|
|
787
|
+
' ',
|
|
788
|
+
['span', { class: 'number' }, '42'],
|
|
789
|
+
['span', { class: 'operator' }, ':'],
|
|
790
|
+
' ',
|
|
791
|
+
['span', { class: 'number' }, '42'],
|
|
792
|
+
' ',
|
|
793
|
+
['span', { class: 'punctuation' }, '}'],
|
|
794
|
+
['span', { class: 'punctuation' }, '['],
|
|
795
|
+
['span', { class: 'string' }, "'42'"],
|
|
796
|
+
['span', { class: 'punctuation' }, ']'],
|
|
797
|
+
' ',
|
|
798
|
+
['span', { class: 'comment' }, '/* 42 */'],
|
|
799
|
+
])
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
```ts
|
|
803
|
+
expect(classes('x = /a\\/b[/]c/gi.test(y) / 2')).toEqual([
|
|
804
|
+
'identifier4:x',
|
|
805
|
+
' ',
|
|
806
|
+
'operator:=',
|
|
807
|
+
' ',
|
|
808
|
+
'string:/a\\/b[/]c/gi',
|
|
809
|
+
'punctuation:.',
|
|
810
|
+
'identifier2:test',
|
|
811
|
+
'punctuation:(',
|
|
812
|
+
'identifier4:y',
|
|
813
|
+
'punctuation:)',
|
|
814
|
+
' ',
|
|
815
|
+
'operator:/',
|
|
816
|
+
' ',
|
|
817
|
+
'number:2',
|
|
818
|
+
])
|
|
819
|
+
|
|
820
|
+
expect(classes('a / b / c')).toEqual([
|
|
821
|
+
'identifier4:a',
|
|
822
|
+
' ',
|
|
823
|
+
'operator:/',
|
|
824
|
+
' ',
|
|
825
|
+
'identifier4:b',
|
|
826
|
+
' ',
|
|
827
|
+
'operator:/',
|
|
828
|
+
' ',
|
|
829
|
+
'identifier4:c',
|
|
830
|
+
])
|
|
831
|
+
|
|
832
|
+
expect(classes('return / x /')).toEqual(['keyword1:return', ' ', 'string:/ x /'])
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
expect(classes('`a ${b + `c ${ {d}.d }`} \\${e} $f`')).toEqual([
|
|
837
|
+
'string:`a ',
|
|
838
|
+
'punctuation:${',
|
|
839
|
+
'identifier4:b',
|
|
840
|
+
' ',
|
|
841
|
+
'operator:+',
|
|
842
|
+
' ',
|
|
843
|
+
'string:`c ',
|
|
844
|
+
'punctuation:${',
|
|
845
|
+
' ',
|
|
846
|
+
'punctuation:{',
|
|
847
|
+
'identifier4:d',
|
|
848
|
+
'punctuation:}',
|
|
849
|
+
'punctuation:.',
|
|
850
|
+
'identifier4:d',
|
|
851
|
+
' ',
|
|
852
|
+
'punctuation:}',
|
|
853
|
+
'string:`',
|
|
854
|
+
'punctuation:}',
|
|
855
|
+
'string: \\${e} $f`',
|
|
856
|
+
])
|
|
857
|
+
|
|
858
|
+
expect(classes('`${x}` / 2')).toEqual([
|
|
859
|
+
'string:`',
|
|
860
|
+
'punctuation:${',
|
|
861
|
+
'identifier4:x',
|
|
862
|
+
'punctuation:}',
|
|
863
|
+
'string:`',
|
|
864
|
+
' ',
|
|
865
|
+
'operator:/',
|
|
866
|
+
' ',
|
|
867
|
+
'number:2',
|
|
868
|
+
])
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
```ts
|
|
872
|
+
expect(classes('"a // b" // c')).toEqual(['string:"a // b"', ' ', 'comment:// c'])
|
|
873
|
+
expect(classes("'it\\'s' /* x")).toEqual(["string:'it\\'s'", ' ', 'comment:/* x'])
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### rwd
|
|
877
|
+
|
|
878
|
+
```ts
|
|
879
|
+
const rwd: (
|
|
880
|
+
root: CNode,
|
|
881
|
+
selector: string,
|
|
882
|
+
cellWidthPx: number,
|
|
883
|
+
cellHeightPx: number,
|
|
884
|
+
...specs: [number, number?, number?][]
|
|
885
|
+
) => void
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
A responsive web design helper that generates CSS rules for a grid-like layout.
|
|
889
|
+
|
|
890
|
+
#### root
|
|
891
|
+
|
|
892
|
+
The CSS root object to populate (see [`c`](#c)).
|
|
893
|
+
|
|
894
|
+
#### selector
|
|
895
|
+
|
|
896
|
+
The CSS selector for the grid item.
|
|
897
|
+
|
|
898
|
+
#### cellWidthPx
|
|
899
|
+
|
|
900
|
+
The base cell width in pixels.
|
|
901
|
+
|
|
902
|
+
#### cellHeightPx
|
|
903
|
+
|
|
904
|
+
The base cell height in pixels.
|
|
905
|
+
|
|
906
|
+
#### specs
|
|
907
|
+
|
|
908
|
+
An array of breakpoint specifications, each a tuple of:
|
|
909
|
+
|
|
910
|
+
- `maxWidth`: maximum number of cells per row (defines the viewport breakpoint).
|
|
911
|
+
- `width` (optional, default `1`): number of horizontal cells the element spans.
|
|
912
|
+
- `height` (optional, default `1`): number of vertical cells the element spans.
|
|
913
|
+
|
|
914
|
+
#### Usage Examples
|
|
915
|
+
|
|
916
|
+
```ts
|
|
917
|
+
const style: CNode = {
|
|
918
|
+
body: {
|
|
919
|
+
margin: 0,
|
|
920
|
+
},
|
|
921
|
+
'.r6': {
|
|
922
|
+
border: 'solid red 1px',
|
|
923
|
+
'.no-border': {
|
|
924
|
+
border: 'none',
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
rwd(style, '.r6', 200, 50, [6], [3], [1, 1, 2])
|
|
930
|
+
|
|
931
|
+
expect(style).toEqual({
|
|
932
|
+
body: {
|
|
933
|
+
margin: 0,
|
|
934
|
+
},
|
|
935
|
+
'.r6': {
|
|
936
|
+
border: 'solid red 1px',
|
|
937
|
+
'.no-border': {
|
|
938
|
+
border: 'none',
|
|
939
|
+
},
|
|
940
|
+
boxSizing: 'border-box',
|
|
941
|
+
display: 'block',
|
|
942
|
+
float: 'left',
|
|
943
|
+
width: '100%',
|
|
944
|
+
height: '100px',
|
|
945
|
+
},
|
|
946
|
+
'@media(min-width:600px)': {
|
|
947
|
+
'.r6': {
|
|
948
|
+
width: 'calc(100% / 3)',
|
|
949
|
+
height: '50px',
|
|
950
|
+
},
|
|
951
|
+
},
|
|
952
|
+
'@media(min-width:1200px)': {
|
|
953
|
+
'.r6': {
|
|
954
|
+
width: 'calc(50% / 3)',
|
|
955
|
+
height: '50px',
|
|
956
|
+
},
|
|
957
|
+
},
|
|
958
|
+
})
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
### s
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
const s: {
|
|
965
|
+
<T extends keyof SVGElementTagNameMap>(tag: T, ...args1: HArgs1[]): SVGElementTagNameMap[T]
|
|
966
|
+
<N extends Node>(node: N, ...args1: HArgs1[]): N
|
|
967
|
+
(tagOrNode: string | Node, ...args1: HArgs1[]): Node
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
A lightweight _HyperScript_-style helper for creating and modifying `SVGElement`s (see also [`h`](#h)).
|
|
972
|
+
|
|
973
|
+
#### tagOrNode
|
|
974
|
+
|
|
975
|
+
If a `string`, it is treated as the tag name for a new element.
|
|
976
|
+
If a `Node`, that node is modified in place.
|
|
977
|
+
|
|
978
|
+
#### args
|
|
979
|
+
|
|
980
|
+
Additional arguments processed as follows:
|
|
981
|
+
|
|
982
|
+
- `Object`: maps attributes/properties.
|
|
983
|
+
Keys starting with `$` set element properties (without the `$` prefix); if the value is a plain object, it is shallow-merged into the existing property instead of replacing it.
|
|
984
|
+
Other keys set attributes: a key prefixed with `xlink:` sets the attribute via `setAttributeNS` in the XLink namespace; any other key (without a colon) sets it via `setAttribute`.
|
|
985
|
+
A value of `false` removes the attribute.
|
|
986
|
+
- `null`/`undefined`: ignored.
|
|
987
|
+
- `Node`: appended as a child.
|
|
988
|
+
- `string`/`number`: converted to a `Text` node and appended.
|
|
989
|
+
- [`HArgs`](#HArgs) array: processed recursively.
|
|
990
|
+
|
|
991
|
+
#### Returns
|
|
992
|
+
|
|
993
|
+
The created or modified `SVGElement`.
|
|
994
|
+
|
|
995
|
+
#### Usage Examples
|
|
996
|
+
|
|
997
|
+
```ts
|
|
998
|
+
const svg = s('svg', { 'xlink:href': true })
|
|
999
|
+
|
|
1000
|
+
expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('')
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
```ts
|
|
1004
|
+
const svg = s('svg', { 'xlink:href': false })
|
|
1005
|
+
|
|
1006
|
+
expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBeNull()
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
```ts
|
|
1010
|
+
const svg = s('svg', { 'xlink:href': 'http://example.com' })
|
|
1011
|
+
|
|
1012
|
+
expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('http://example.com')
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
```ts
|
|
1016
|
+
const svg = s('svg', { 'xlink:title': 42 })
|
|
1017
|
+
|
|
1018
|
+
expect(svg.getAttributeNS('http://www.w3.org/1999/xlink', 'title')).toBe('42')
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
### svgUse
|
|
1022
|
+
|
|
1023
|
+
```ts
|
|
1024
|
+
const svgUse: (id: string, ...args: HArgs1[]) => SVGSVGElement
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
|
|
1028
|
+
|
|
1029
|
+
Equivalent to:
|
|
1030
|
+
|
|
1031
|
+
```ts
|
|
1032
|
+
s('svg', ['use', { 'xlink:href': '#' + id }], ...args)
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
#### id
|
|
1036
|
+
|
|
1037
|
+
The ID of the symbol to reference (without the `#` prefix).
|
|
1038
|
+
|
|
1039
|
+
#### args
|
|
1040
|
+
|
|
1041
|
+
Additional arguments passed to the outer `<svg>` element.
|
|
1042
|
+
|
|
1043
|
+
#### Returns
|
|
1044
|
+
|
|
1045
|
+
An `SVGSVGElement` containing a `<use>` element.
|
|
1046
|
+
|
|
1047
|
+
#### Usage Examples
|
|
1048
|
+
|
|
1049
|
+
```ts
|
|
1050
|
+
const svg = svgUse('icon-home')
|
|
1051
|
+
|
|
1052
|
+
expect(svg.children.length).toBe(1)
|
|
1053
|
+
|
|
1054
|
+
const useElement = svg.children[0]
|
|
1055
|
+
|
|
1056
|
+
expect(useElement.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('#icon-home')
|
|
1057
|
+
|
|
1058
|
+
const svgWithViewBox = svgUse('icon-star', { viewBox: '0 0 24 24' })
|
|
1059
|
+
|
|
1060
|
+
expect(svgWithViewBox.getAttribute('viewBox')).toBe('0 0 24 24')
|
|
1061
|
+
|
|
1062
|
+
const useViewBox = svgWithViewBox.children[0]
|
|
1063
|
+
|
|
1064
|
+
expect(useViewBox.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('#icon-star')
|
|
1065
|
+
|
|
1066
|
+
const svgWithClass = svgUse('icon-menu', { class: 'icon-btn' })
|
|
1067
|
+
|
|
1068
|
+
expect(svgWithClass.getAttribute('class')).toBe('icon-btn')
|
|
1069
|
+
|
|
1070
|
+
expect(svgWithClass.children.length).toBe(1)
|
|
1071
|
+
|
|
1072
|
+
const useClass = svgWithClass.children[0]
|
|
1073
|
+
|
|
1074
|
+
expect(useClass.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('#icon-menu')
|
|
1075
|
+
|
|
1076
|
+
const svgWithMultipleAttrs = svgUse('icon-settings', { width: 24, height: 24, class: 'icon' })
|
|
1077
|
+
|
|
1078
|
+
expect(svgWithMultipleAttrs.getAttribute('width')).toBe('24')
|
|
1079
|
+
|
|
1080
|
+
expect(svgWithMultipleAttrs.getAttribute('height')).toBe('24')
|
|
1081
|
+
|
|
1082
|
+
expect(svgWithMultipleAttrs.getAttribute('class')).toBe('icon')
|
|
1083
|
+
|
|
1084
|
+
const useMultiple = svgWithMultipleAttrs.children[0]
|
|
1085
|
+
|
|
1086
|
+
expect(useMultiple.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe('#icon-settings')
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
## License
|
|
1090
|
+
|
|
1091
|
+
The MIT License (MIT)
|
|
1092
|
+
|
|
1093
|
+
Copyright (c) 2016+ Jackens
|
|
1094
|
+
|
|
1095
|
+
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:
|
|
1096
|
+
|
|
1097
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
1098
|
+
|
|
1099
|
+
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.
|