@jackens/nnn 2023.10.14 → 2023.11.3

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 (2) hide show
  1. package/nnn.js +64 -821
  2. package/package.json +1 -1
package/nnn.js CHANGED
@@ -1,91 +1,5 @@
1
- /* eslint-disable no-console */
2
-
3
- export const _test = {}
4
- export const _version = '2023.10.14'
5
-
6
- /**
7
- * ```ts
8
- * export type EscapeMap = Map<any, (value?: any) => string>;
9
- * ```
10
- */
11
-
12
- /**
13
- * @typedef {Map<any, (value?: any) => string>} EscapeMap
14
- *
15
- * The type of arguments of the `escapeValues` and `escape` helpers.
16
- */
17
-
18
- /**
19
- * ```ts
20
- * export type HArgs = [
21
- * string | Node,
22
- * ...(Record<string, any> | null | undefined | Node | string | number | HArgs)[]
23
- * ];
24
- * ```
25
- */
26
-
27
- /**
28
- * @typedef {[
29
- * string | Node,
30
- * ...(Record<string, any> | null | undefined | Node | string | number | HArgs)[]
31
- * ]} HArgs
32
- *
33
- * The type of arguments of the `h` and `s` helpers.
34
- */
35
-
36
- /**
37
- * ```ts
38
- * export type JcssNode = {
39
- * [attributeOrSelector: string]: string | number | JcssNode;
40
- * };
41
- * ```
42
- */
43
-
44
- /**
45
- * @typedef {{
46
- * [attributeOrSelector: string]: string | number | JcssNode;
47
- * }} JcssNode
48
- *
49
- * The type of arguments of the `jcss` helper.
50
- */
51
-
52
- /**
53
- * ```ts
54
- * export type JcssRoot = Record<string, JcssNode>;
55
- * ```
56
- */
57
-
58
- /**
59
- * @typedef {Record<string, JcssNode>} JcssRoot
60
- *
61
- * The type of arguments of the `jcss` helper.
62
- */
63
-
64
1
  const _COLORS = ['#e22', '#e73', '#fc3', '#ad4', '#4d9', '#3be', '#45d', '#c3e']
65
2
 
66
- /**
67
- * ```ts
68
- * export function chartable(options?: {
69
- * bottom?: number;
70
- * gapX?: number;
71
- * gapY?: number;
72
- * headerColumn?: boolean;
73
- * headerRow?: boolean;
74
- * id?: string;
75
- * left?: number;
76
- * maxY?: number;
77
- * right?: number;
78
- * singleScale?: boolean;
79
- * table?: HTMLTableElement;
80
- * title?: string;
81
- * top?: number;
82
- * xLabels?: string[];
83
- * zLabels?: string[];
84
- * zxY?: number[][];
85
- * }): SVGSVGElement;
86
- * ```
87
- */
88
-
89
3
  /**
90
4
  * A helper for creating a chart based on a table (conf. <https://jackens.github.io/nnn/chartable/>).
91
5
  *
@@ -174,8 +88,7 @@ export const chartable = ({
174
88
  })
175
89
 
176
90
  if (singleScale) {
177
- bestScales = ranges.reduce(([totalMin, totalMax], [min, max]) =>
178
- [Math.min(totalMin, min), Math.max(totalMax, max)], bestScales)
91
+ bestScales = ranges.reduce(([totalMin, totalMax], [min, max]) => [Math.min(totalMin, min), Math.max(totalMax, max)], bestScales)
179
92
  } else {
180
93
  ranges.forEach(([min1, max0]) => ranges.forEach(([min0, max1]) => {
181
94
  if (min0 >= min1 && max1 >= max0) {
@@ -216,15 +129,14 @@ export const chartable = ({
216
129
  const h = gapY * (maxY - 1)
217
130
  const svgW = left + w + right
218
131
  const svgH = top + h + bottom
219
- const /** @type {HArgs} */ graph = ['g', { transform: `translate(${left} ${top})` },
132
+ const /** @type {import('./h.js').HArgs} */ graph = ['g', { transform: `translate(${left} ${top})` },
220
133
  ...yi.map(y => ['line', { x1: 0, x2: w, y1: y, y2: y, stroke: '#8888' }]),
221
134
  ...xi.map(x => ['line', { x1: x, x2: x, y1: 0, y2: h, stroke: '#8888' }])]
222
- const /** @type {HArgs} */ lColors = ['g', { class: 'chartable-z-colors', transform: 'translate(-80 0)' }]
223
- const /** @type {HArgs} */ lLabels = ['g', { class: 'chartable-z-labels', transform: 'translate(-95 0)' }]
224
- const /** @type {HArgs} */ rColors = ['g', { class: 'chartable-z-colors', transform: 'translate(80 0)' }]
225
- const /** @type {HArgs} */ rLabels = ['g', { class: 'chartable-z-labels', transform: 'translate(95 0)' }]
226
- const yLabel = (/** @type {number} */ min, /** @type {number} */ max, /** @type {number} */ y) =>
227
- (min + (max - min) * (maxY - 1 - y) / (maxY - 1)).toFixed(2).replace(/\.?0+$/, '')
135
+ const /** @type {import('./h.js').HArgs} */ lColors = ['g', { class: 'chartable-z-colors', transform: 'translate(-80 0)' }]
136
+ const /** @type {import('./h.js').HArgs} */ lLabels = ['g', { class: 'chartable-z-labels', transform: 'translate(-95 0)' }]
137
+ const /** @type {import('./h.js').HArgs} */ rColors = ['g', { class: 'chartable-z-colors', transform: 'translate(80 0)' }]
138
+ const /** @type {import('./h.js').HArgs} */ rLabels = ['g', { class: 'chartable-z-labels', transform: 'translate(95 0)' }]
139
+ const yLabel = (/** @type {number} */ min, /** @type {number} */ max, /** @type {number} */ y) => (min + (max - min) * (maxY - 1 - y) / (maxY - 1)).toFixed(2).replace(/\.?0+$/, '')
228
140
 
229
141
  zxY.forEach((xa, z) => {
230
142
  const cls = `chartable-z-${z + 1}`
@@ -297,16 +209,10 @@ export const chartable = ({
297
209
  )
298
210
  }
299
211
 
300
- /**
301
- * ```ts
302
- * export function eq(x: any, y: any): boolean;
303
- * ```
304
- */
305
-
306
212
  /**
307
213
  * A helper that checks equality of the given arguments.
308
214
  */
309
- export const eq = /** @returns {boolean} */ (/** @type {any} */ x, /** @type {any} */ y) => {
215
+ export const eq = /** @return {boolean} */ (/** @type {any} */ x, /** @type {any} */ y) => {
310
216
  if (x === y) {
311
217
  return true
312
218
  }
@@ -348,47 +254,19 @@ export const eq = /** @returns {boolean} */ (/** @type {any} */ x, /** @type {an
348
254
  return false
349
255
  }
350
256
 
351
- _test.eq = () => {
352
- console.assert(eq(true, true))
353
- console.assert(eq(NaN, NaN))
354
- console.assert(!eq(null, undefined))
355
- console.assert(eq(42, 42))
356
- // eslint-disable-next-line no-new-wrappers
357
- console.assert(eq(42, new Number(42)))
358
- console.assert(eq(42, Number(42)))
359
- // eslint-disable-next-line no-new-wrappers
360
- console.assert(eq(new Number(42), Number(42)))
361
- console.assert(!eq(42, '42'))
362
- console.assert(eq('42', '42'))
363
- // eslint-disable-next-line no-new-wrappers
364
- console.assert(eq('42', new String('42')))
365
- console.assert(eq('42', String('42')))
366
- // eslint-disable-next-line no-new-wrappers
367
- console.assert(eq(String('42'), new String('42')))
368
- console.assert(eq(/42/, /42/))
369
- console.assert(!eq(/42/, /42/g))
370
- console.assert(eq(new Date(42), new Date(42)))
371
- console.assert(!eq(new Date(), new Date(42)))
372
- console.assert(eq({ j: '42', c: 42 }, { c: 42, j: '42' }))
373
- console.assert(eq([42, '42'], [42, '42']))
374
- console.assert(eq(new Set(['42', 42]), new Set([42, '42'])))
375
- console.assert(!eq(new Set(['42', 42]), new Set([42])))
376
- console.assert(!eq(new Set([42, undefined]), new Set([42])))
377
- console.assert(eq(
378
- new Map([[{ j: 42 }, { J: '42' }], [{ c: 42 }, { C: '42' }]]),
379
- new Map([[{ c: 42 }, { C: '42' }], [{ j: 42 }, { J: '42' }]])
380
- ))
381
- console.assert(!eq(
382
- new Map([[{ j: 42 }, { J: '42' }], [{ c: 42 }, { C: '42' }]]),
383
- new Map([[{ j: '42' }, { J: 42 }], [{ c: '42' }, { C: 42 }]])
384
- ))
385
- }
257
+ /**
258
+ * @typedef {Map<any, (value?: any) => string>} EscapeMap
259
+ *
260
+ * The type of arguments of the `escapeValues` and `escape` helpers.
261
+ */
386
262
 
387
263
  /**
388
- * ```ts
389
- * export function escape(escapeMap: EscapeMap, template: TemplateStringsArray, ...values: any[]): string;
390
- * ```
264
+ * A generic helper for escaping `values` by given `escapeMap`.
391
265
  */
266
+ export const escapeValues = (
267
+ /** @type {EscapeMap} */ escapeMap,
268
+ /** @type {any[]} */ values
269
+ ) => values.map(value => (escapeMap.get(value?.constructor) ?? escapeMap.get(undefined))?.(value) ?? '')
392
270
 
393
271
  /**
394
272
  * A generic helper for escaping `values` by given `escapeMap` (in *TemplateStrings* flavor).
@@ -399,55 +277,8 @@ export const escape = (
399
277
  /** @type {any[]} */ ...values
400
278
  ) => String.raw(template, ...escapeValues(escapeMap, values))
401
279
 
402
- _test.escape = () => {
403
- // @ts-expect-error
404
- const /** @type {EscapeMap} */ escapeMap = new Map([
405
- [undefined, () => 'NULL'],
406
- [Array, (/** @type {any[]} */ values) => escapeValues(escapeMap, values).join(', ')],
407
- [Boolean, (/** @type {boolean} */ value) => `b'${+value}'`],
408
- [Date, (/** @type {Date} */ value) => `'${value.toISOString().replace(/^(.+)T(.+)\..*$/, '$1 $2')}'`],
409
- [Number, (/** @type {number} */ value) => `${value}`],
410
- [String, (/** @type {string} */ value) => `'${value.replace(/'/g, "''")}'`]
411
- ])
412
-
413
- // @ts-expect-error
414
- const sql = escape.bind(null, escapeMap)
415
-
416
- const actual = sql`
417
- SELECT *
418
- FROM table_name
419
- WHERE column_name IN (${[true, null, undefined, 42, '42', "4'2", /42/, new Date(323325e6)]})`
420
-
421
- const expected = `
422
- SELECT *
423
- FROM table_name
424
- WHERE column_name IN (b'1', NULL, NULL, 42, '42', '4''2', NULL, '1980-03-31 04:30:00')`
425
-
426
- console.assert(actual === expected)
427
- }
428
-
429
- /**
430
- * ```ts
431
- * export function escapeValues(escapeMap: EscapeMap, values: any[]): string[];
432
- * ```
433
- */
434
-
435
- /**
436
- * A generic helper for escaping `values` by given `escapeMap`.
437
- */
438
- export const escapeValues = (
439
- /** @type {EscapeMap} */ escapeMap,
440
- /** @type {any[]} */ values
441
- ) => values.map(value => (escapeMap.get(value?.constructor) ?? escapeMap.get(undefined))?.(value) ?? '')
442
-
443
280
  const _TAGS_TO_SKIP = { IFRAME: 1, NOSCRIPT: 1, PRE: 1, SCRIPT: 1, STYLE: 1, TEXTAREA: 1 }
444
281
 
445
- /**
446
- * ```ts
447
- * export function fixTypography(node: Node): void;
448
- * ```
449
- */
450
-
451
282
  /**
452
283
  * A helper that implements typographic corrections specific to Polish typography.
453
284
  */
@@ -491,15 +322,14 @@ export const fixTypography = (/** @type {Node} */ node) => {
491
322
  }
492
323
  }
493
324
 
494
- _test.fixTypography = () => {
495
- const p = h('p', 'Pchnąć w tę łódź jeża lub ośm skrzyń fig (zob. https://pl.wikipedia.org/wiki/Pangram).')
496
-
497
- fixTypography(p)
498
-
499
- console.assert(p.innerHTML ===
500
- 'Pchnąć <span style="white-space:nowrap">w </span>tę łódź jeża lub ośm skrzyń fig ' +
501
- '(zob. https://\u200Bpl.\u200Bwikipedia.\u200Borg/\u200Bwiki/\u200BPangram).')
502
- }
325
+ /**
326
+ * @typedef {[
327
+ * string | Node,
328
+ * ...(Record<string, any> | null | undefined | Node | string | number | HArgs)[]
329
+ * ]} HArgs
330
+ *
331
+ * The type of arguments of the `h` and `s` helpers.
332
+ */
503
333
 
504
334
  const /** @type {Record<string, string>} */ _NS = {
505
335
  xlink: 'http://www.w3.org/1999/xlink'
@@ -595,14 +425,6 @@ const _h = (/** @type {string?=} */ namespaceURI) => {
595
425
  return h
596
426
  }
597
427
 
598
- /**
599
- * ```ts
600
- * export function h<T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs[1][]): HTMLElementTagNameMap[T];
601
- * export function h<N extends Node>(node: N, ...args: HArgs[1][]): N;
602
- * export function h(...args: HArgs): Node;
603
- * ```
604
- */
605
-
606
428
  /**
607
429
  * A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also `s`).
608
430
  *
@@ -620,129 +442,27 @@ const _h = (/** @type {string?=} */ namespaceURI) => {
620
442
  */
621
443
  export const h = _h()
622
444
 
623
- _test.h = () => {
624
- const b = h('b')
625
-
626
- console.assert(b.outerHTML === '<b></b>')
627
-
628
- const i = h('i', 'text')
629
-
630
- h(b, i)
631
-
632
- console.assert(i.outerHTML === '<i>text</i>')
633
- console.assert(b.outerHTML === '<b><i>text</i></b>')
634
-
635
- h(i, { $className: 'some class' })
636
-
637
- console.assert(i.outerHTML === '<i class="some class">text</i>')
638
- console.assert(b.outerHTML === '<b><i class="some class">text</i></b>')
639
- }
640
-
641
- _test['h: innerText vs items'] = () => {
642
- console.assert(h('span', 'text').outerHTML === '<span>text</span>')
643
- console.assert(h('span', { $innerText: 'text' }).outerHTML === '<span>text</span>')
644
- }
645
-
646
- _test['h: style'] = () => {
647
- console.assert(h('div', { style: 'margin:0;padding:0' }).outerHTML ===
648
- '<div style="margin:0;padding:0"></div>')
649
- console.assert(h('div', { $style: 'margin:0;padding:0' }).outerHTML ===
650
- '<div style="margin: 0px; padding: 0px;"></div>')
651
- console.assert(h('div', { $style: { margin: 0, padding: 0 } }).outerHTML ===
652
- '<div style="margin: 0px; padding: 0px;"></div>')
653
- }
654
-
655
- _test['h: attributes vs properties'] = () => {
656
- const input1 = h('input', { value: 42 })
657
- const input2 = h('input', { $value: '42' })
658
-
659
- console.assert(input1.value === '42')
660
- console.assert(input2.value === '42')
661
-
662
- console.assert(input1.outerHTML === '<input value="42">')
663
- console.assert(input2.outerHTML === '<input>')
664
-
665
- const checkbox1 = h('input', { type: 'checkbox', checked: true })
666
- const checkbox2 = h('input', { type: 'checkbox', $checked: true })
667
-
668
- console.assert(checkbox1.checked === true)
669
- console.assert(checkbox2.checked === true)
670
-
671
- console.assert(checkbox1.outerHTML === '<input type="checkbox" checked="">')
672
- console.assert(checkbox2.outerHTML === '<input type="checkbox">')
673
- }
674
-
675
- _test['h: nested properties'] = () => {
676
- const div = h('div')
677
-
678
- // @ts-expect-error
679
- console.assert(div.key === undefined)
680
-
681
- h(div, { $key: { one: 1 } })
682
-
683
- // @ts-expect-error
684
- console.assert(eq(div.key, { one: 1 }))
685
-
686
- h(div, { $key: { two: 2 } })
687
-
688
- // @ts-expect-error
689
- console.assert(eq(div.key, { one: 1, two: 2 }))
690
- }
691
-
692
445
  /**
693
- * ```ts
694
- * export function has(key: any, ref: any): boolean;
695
- * ```
446
+ * A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also `h`).
447
+ *
448
+ * - The first argument of type `string` specifies the tag of the element to be created.
449
+ * - The first argument of type `Node` specifies the element to be modified.
450
+ * - All other arguments of type `Record<string, any>` are mappings of attributes and properties.
451
+ * Keys starting with `$` specify *properties* (without the leading `$`) to be set on the element being created or modified.
452
+ * (Note that `$` is not a valid attribute name character.)
453
+ * All other keys specify *attributes* to be set by `setAttributeNS`.
454
+ * An attribute equal to `false` causes the attribute to be removed by `removeAttributeNS`.
455
+ * - All other arguments of type `null` or `undefined` are simply ignored.
456
+ * - All other arguments of type `Node` are appended to the element being created or modified.
457
+ * - All other arguments of type `string`/`number` are converted to `Text` nodes and appended to the element being created or modified.
458
+ * - All other arguments of type `HArgs` are passed to `s` and the results are appended to the element being created or modified.
696
459
  */
460
+ export const s = _h('http://www.w3.org/2000/svg')
697
461
 
698
462
  /**
699
463
  * A replacement for the `in` operator (not to be confused with the `for-in` loop) that works properly.
700
464
  */
701
- export const has = (/** @type {any} */ key, /** @type {any} */ ref) =>
702
- (is(String, key) || is(Number, key) || is(Symbol, key)) && Object.hasOwnProperty.call(ref ?? Object, key)
703
-
704
- _test.has = () => {
705
- const obj = { key: 'K', null: 'N' }
706
-
707
- console.assert('key' in obj)
708
- console.assert(has('key', obj))
709
-
710
- console.assert('null' in obj)
711
- console.assert(has('null', obj))
712
-
713
- // @ts-expect-error
714
- console.assert(null in obj)
715
- console.assert(!has(null, obj))
716
-
717
- console.assert('toString' in obj)
718
- console.assert(!has('toString', obj))
719
- }
720
-
721
- _test['has: null'] = () => {
722
- let typeError
723
-
724
- try {
725
- // @ts-expect-error
726
- console.assert('key' in null)
727
- } catch (error) {
728
- typeError = error
729
- }
730
-
731
- console.assert(typeError instanceof TypeError) // Cannot use 'in' operator to search for 'key' in null
732
- console.assert(!has('key', null))
733
- }
734
-
735
- /**
736
- * ```ts
737
- * export function is(type: BigIntConstructor, arg: any): arg is bigint;
738
- * export function is(type: BooleanConstructor, arg: any): arg is boolean;
739
- * export function is(type: NumberConstructor, arg: any): arg is number;
740
- * export function is(type: StringConstructor, arg: any): arg is string;
741
- * export function is(type: SymbolConstructor, arg: any): arg is symbol;
742
- * export function is(type: undefined, arg: any): arg is null | undefined;
743
- * export function is<T extends abstract new (...args: any[]) => any>(type: T, arg: any): arg is InstanceType<T>;
744
- * ```
745
- */
465
+ export const has = (/** @type {any} */ key, /** @type {any} */ ref) => (is(String, key) || is(Number, key) || is(Symbol, key)) && Object.hasOwnProperty.call(ref ?? Object, key)
746
466
 
747
467
  /**
748
468
  * A helper that checks if the given argument is of a certain type.
@@ -759,42 +479,23 @@ _test['has: null'] = () => {
759
479
  * <T extends abstract new (...args: any[]) => any>(type: T, arg: any): arg is InstanceType<T>;
760
480
  * }}
761
481
  *
762
- * @returns {arg is bigint | boolean | number | string | symbol | undefined | null | InstanceType<T>}
482
+ * @return {arg is bigint | boolean | number | string | symbol | undefined | null | InstanceType<T>}
763
483
  */
764
484
  export const is = (/** @type {T} */ type, /** @type {any} */ arg) => arg?.constructor === type
765
485
 
766
- _test.is = () => {
767
- console.assert(is(Number, 42))
768
- console.assert(is(Number, Number(42)))
769
- // eslint-disable-next-line no-new-wrappers
770
- console.assert(is(Number, new Number(42)))
771
- console.assert(is(Number, NaN))
772
- console.assert(is(String, '42'))
773
- console.assert(is(String, String('42')))
774
- // eslint-disable-next-line no-new-wrappers
775
- console.assert(is(String, new String('42')))
776
- console.assert(is(Symbol, Symbol('42')))
777
- console.assert(is(Symbol, Object(Symbol('42'))))
778
- console.assert(is(undefined, undefined))
779
- console.assert(is(undefined, null))
780
- console.assert(is(Object, {}))
781
- console.assert(is(Array, []))
782
- console.assert(is(RegExp, /42/))
783
- console.assert(is(Date, new Date(42)))
784
- console.assert(is(Set, new Set(['42', 42])))
785
- console.assert(is(Map, new Map([[{ j: 42 }, { J: '42' }], [{ c: 42 }, { C: '42' }]])))
786
- }
787
-
788
- _test['is vs ‘toString.call’'] = () => {
789
- class FooBar { }
790
-
791
- console.assert(is(FooBar, new FooBar()))
792
-
793
- const fakeFooBar = { [Symbol.toStringTag]: 'FooBar' }
486
+ /**
487
+ * @typedef {{
488
+ * [attributeOrSelector: string]: string | number | JcssNode;
489
+ * }} JcssNode
490
+ *
491
+ * The type of arguments of the `jcss` helper.
492
+ */
794
493
 
795
- console.assert(({}).toString.call(new FooBar()) === '[object Object]')
796
- console.assert(({}).toString.call(fakeFooBar) === '[object FooBar]')
797
- }
494
+ /**
495
+ * @typedef {Record<string, JcssNode>} JcssRoot
496
+ *
497
+ * The type of arguments of the `jcss` helper.
498
+ */
798
499
 
799
500
  const _jcss = (
800
501
  /** @type {JcssNode} */ node,
@@ -850,12 +551,6 @@ const _jcss = (
850
551
  }
851
552
  }
852
553
 
853
- /**
854
- * ```ts
855
- * export function jcss(root: JcssRoot, splitter?: string): string;
856
- * ```
857
- */
858
-
859
554
  /**
860
555
  * A simple CSS-in-JS helper.
861
556
  *
@@ -886,206 +581,6 @@ export const jcss = (/** @type {JcssRoot} */ root, splitter = '$$') => {
886
581
  return result.join('')
887
582
  }
888
583
 
889
- _test['jcss: #1'] = () => {
890
- const actual = jcss({
891
- a: {
892
- color: 'red',
893
- margin: 1,
894
- '.c': { margin: 2, padding: 2 },
895
- padding: 1
896
- }
897
- })
898
-
899
- const expected = `
900
- a{
901
- color:red;
902
- margin:1
903
- }
904
- a.c{
905
- margin:2;
906
- padding:2
907
- }
908
- a{
909
- padding:1
910
- }`.replace(/\n\s*/g, '')
911
-
912
- console.assert(actual === expected)
913
- }
914
-
915
- _test['jcss: #2'] = () => {
916
- const actual = jcss({
917
- a: {
918
- '.b': {
919
- color: 'red',
920
- margin: 1,
921
- '.c': { margin: 2, padding: 2 },
922
- padding: 1
923
- }
924
- }
925
- })
926
-
927
- const expected = `
928
- a.b{
929
- color:red;
930
- margin:1
931
- }
932
- a.b.c{
933
- margin:2;
934
- padding:2
935
- }
936
- a.b{
937
- padding:1
938
- }`.replace(/\n\s*/g, '')
939
-
940
- console.assert(actual === expected)
941
- }
942
-
943
- _test['jcss: #3'] = () => {
944
- const actual = jcss({
945
- '@font-face$$1': {
946
- fontFamily: 'Jackens',
947
- src$$1: 'url(otf/jackens.otf)',
948
- src$$2: "url(otf/jackens.otf) format('opentype')," +
949
- "url(svg/jackens.svg) format('svg')",
950
- fontWeight: 'normal',
951
- fontStyle: 'normal'
952
- },
953
- '@font-face$$2': {
954
- fontFamily: 'C64',
955
- src: 'url(fonts/C64_Pro_Mono-STYLE.woff)'
956
- },
957
- '@keyframes spin': {
958
- '0%': { transform: 'rotate(0deg)' },
959
- '100%': { transform: 'rotate(360deg)' }
960
- },
961
- div: {
962
- border: 'solid red 1px',
963
- '.c1': { 'background-color': '#000' },
964
- ' .c1': { backgroundColor: 'black' },
965
- '.c2': { backgroundColor: 'rgb(0,0,0)' }
966
- },
967
- '@media(min-width:200px)': {
968
- div: { margin: 0, padding: 0 },
969
- span: { color: '#000' }
970
- }
971
- })
972
-
973
- const expected = `
974
- @font-face{
975
- font-family:Jackens;
976
- src:url(otf/jackens.otf);
977
- src:url(otf/jackens.otf) format('opentype'),url(svg/jackens.svg) format('svg');
978
- font-weight:normal;
979
- font-style:normal
980
- }
981
- @font-face{
982
- font-family:C64;
983
- src:url(fonts/C64_Pro_Mono-STYLE.woff)
984
- }
985
- @keyframes spin{
986
- 0%{
987
- transform:rotate(0deg)
988
- }
989
- 100%{
990
- transform:rotate(360deg)
991
- }
992
- }
993
- div{
994
- border:solid red 1px
995
- }
996
- div.c1{
997
- background-color:#000
998
- }
999
- div .c1{
1000
- background-color:black
1001
- }
1002
- div.c2{
1003
- background-color:rgb(0,0,0)
1004
- }
1005
- @media(min-width:200px){
1006
- div{
1007
- margin:0;
1008
- padding:0
1009
- }
1010
- span{
1011
- color:#000
1012
- }
1013
- }`.replace(/\n\s*/g, '')
1014
-
1015
- console.assert(actual === expected)
1016
- }
1017
-
1018
- _test['jcss: #4'] = () => {
1019
- const actual = jcss({
1020
- a: {
1021
- '.b,.c': {
1022
- margin: 1,
1023
- '.d': {
1024
- margin: 2
1025
- }
1026
- }
1027
- }
1028
- })
1029
-
1030
- const expected = `
1031
- a.b,a.c{
1032
- margin:1
1033
- }
1034
- a.b.d,a.c.d{
1035
- margin:2
1036
- }`.replace(/\n\s*/g, '')
1037
-
1038
- console.assert(actual === expected)
1039
- }
1040
-
1041
- _test['jcss: #5'] = () => {
1042
- const actual = jcss({
1043
- '.b,.c': {
1044
- margin: 1,
1045
- '.d': {
1046
- margin: 2
1047
- }
1048
- }
1049
- })
1050
-
1051
- const expected = `
1052
- .b,.c{
1053
- margin:1
1054
- }
1055
- .b.d,.c.d{
1056
- margin:2
1057
- }`.replace(/\n\s*/g, '')
1058
-
1059
- console.assert(actual === expected)
1060
- }
1061
-
1062
- _test['jcss: #6'] = () => {
1063
- const actual = jcss({
1064
- '.a,.b': {
1065
- margin: 1,
1066
- '.c,.d': {
1067
- margin: 2
1068
- }
1069
- }
1070
- })
1071
-
1072
- const expected = `
1073
- .a,.b{
1074
- margin:1
1075
- }
1076
- .a.c,.a.d,.b.c,.b.d{
1077
- margin:2
1078
- }`.replace(/\n\s*/g, '')
1079
-
1080
- console.assert(actual === expected)
1081
- }
1082
-
1083
- /**
1084
- * ```ts
1085
- * export function jsOnParse(handlers: Record<string, Function>, text: string): any;
1086
- * ```
1087
- */
1088
-
1089
584
  /**
1090
585
  * `JSON.parse` with “JavaScript turned on”.
1091
586
  *
@@ -1123,49 +618,6 @@ export const jsOnParse = (
1123
618
  return value
1124
619
  })
1125
620
 
1126
- _test.jsOnParse = () => {
1127
- const handlers = {
1128
- $hello: (/** @type {string} */ name) => `Hello ${name}!`,
1129
- $foo: () => 'bar'
1130
- }
1131
- const actual = jsOnParse(handlers, `
1132
- [
1133
- {
1134
- "$hello": ["World"]
1135
- },
1136
- {
1137
- "nested": {
1138
- "$hello": ["nested World"]
1139
- },
1140
- "one": 1,
1141
- "two": 2
1142
- },
1143
- {
1144
- "$foo": []
1145
- },
1146
- {
1147
- "$foo": ["The parent object does not have exactly one property!"],
1148
- "one": 1,
1149
- "two": 2
1150
- }
1151
- ]`)
1152
- const expected = [
1153
- 'Hello World!',
1154
- {
1155
- nested: 'Hello nested World!',
1156
- one: 1,
1157
- two: 2
1158
- },
1159
- 'bar',
1160
- {
1161
- $foo: ['The parent object does not have exactly one property!'],
1162
- one: 1,
1163
- two: 2
1164
- }]
1165
-
1166
- console.assert(eq(actual, expected))
1167
- }
1168
-
1169
621
  const _locale = (
1170
622
  /** @type {Record<string, Record<string, string | Record<string, string>>>} */ locales,
1171
623
  /** @type {string} */ language,
@@ -1184,16 +636,6 @@ const _locale = (
1184
636
  return is(String, t) ? t : text
1185
637
  }
1186
638
 
1187
- /**
1188
- * ```ts
1189
- * export function locale(
1190
- * locales: Record<string, Record<string, string | Record<string, string>>>,
1191
- * defaultLanguage: string,
1192
- * languages?: readonly string[]
1193
- * ): (text?: string, version?: string) => string;
1194
- * ```
1195
- */
1196
-
1197
639
  /**
1198
640
  * Language translations helper.
1199
641
  */
@@ -1213,48 +655,15 @@ export const locale = (
1213
655
  return _locale.bind(0, locales, defaultLanguage)
1214
656
  }
1215
657
 
1216
- _test.locale = () => {
1217
- const locales = {
1218
- pl: {
1219
- Password: 'Hasło',
1220
- button: { Login: 'Zaloguj' }
1221
- }
1222
- }
1223
- const _ = locale(locales, 'pl', [])
1224
-
1225
- console.assert(_('Login') === 'Login')
1226
- console.assert(_('Password') === 'Hasło')
1227
-
1228
- console.assert(_('Undefined text') === 'Undefined text')
1229
-
1230
- console.assert(_('Login', 'button') === 'Zaloguj')
1231
-
1232
- console.assert(_('Password', 'undefined_version') === 'Hasło')
1233
- console.assert(_('Undefined text', 'undefined_version') === 'Undefined text')
1234
-
1235
- console.assert(_('toString') === 'toString')
1236
- console.assert(_('toString', 'undefined_version') === 'toString')
1237
- }
1238
-
1239
- /**
1240
- * ```ts
1241
- * export function nanolight(
1242
- * pattern: RegExp,
1243
- * highlighters: ((chunk: string, index: number) => HArgs[1])[],
1244
- * code: string
1245
- * ): HArgs[1][];
1246
- * ```
1247
- */
1248
-
1249
658
  /**
1250
659
  * A generic helper for syntax highlighting (see also `nanolightJs`).
1251
660
  */
1252
661
  export const nanolight = (
1253
662
  /** @type {RegExp} */ pattern,
1254
- /** @type {((chunk: string, index: number) => HArgs[1])[]} */ highlighters,
663
+ /** @type {((chunk: string, index: number) => import('./h.js').HArgs[1])[]} */ highlighters,
1255
664
  /** @type {string} */ code
1256
665
  ) => {
1257
- const /** @type {HArgs[1][]} */ result = []
666
+ const /** @type {import('./h.js').HArgs[1][]} */ result = []
1258
667
 
1259
668
  code.split(pattern).forEach((chunk, index) => {
1260
669
  index %= highlighters.length
@@ -1266,12 +675,6 @@ export const nanolight = (
1266
675
  return result
1267
676
  }
1268
677
 
1269
- /**
1270
- * ```ts
1271
- * export function nanolightJs(codeJs: string): HArgs[1][];
1272
- * ```
1273
- */
1274
-
1275
678
  /**
1276
679
  * A helper for highlighting JavaScript.
1277
680
  */
@@ -1290,19 +693,6 @@ export const nanolightJs = nanolight.bind(0,
1290
693
  ]
1291
694
  )
1292
695
 
1293
- _test.nanolightJs = () => {
1294
- const codeJs = 'const answerToLifeTheUniverseAndEverything = 42'
1295
-
1296
- console.assert(h('pre', ['code', ...nanolightJs(codeJs)]).outerHTML ===
1297
- '<pre><code><b>const</b> <i>answerToLifeTheUniverseAndEverything</i> <b>=</b> <u>42</u></code></pre>')
1298
- }
1299
-
1300
- /**
1301
- * ```ts
1302
- * export function plUral(singular: string, plural2: string, plural5: string, value: number): string;
1303
- * ```
1304
- */
1305
-
1306
696
  /**
1307
697
  * A helper for choosing the correct singular and plural.
1308
698
  */
@@ -1324,30 +714,6 @@ export const plUral = (
1324
714
  : plural5
1325
715
  }
1326
716
 
1327
- _test.plUral = () => {
1328
- // @ts-expect-error
1329
- const auto = plUral.bind(null, 'auto', 'auta', 'aut')
1330
-
1331
- console.assert(auto(0) === 'aut')
1332
- console.assert(auto(1) === 'auto')
1333
- console.assert(auto(17) === 'aut')
1334
- console.assert(auto(42) === 'auta')
1335
-
1336
- // @ts-expect-error
1337
- const car = plUral.bind(null, 'car', 'cars', 'cars')
1338
-
1339
- console.assert(car(0) === 'cars')
1340
- console.assert(car(1) === 'car')
1341
- console.assert(car(17) === 'cars')
1342
- console.assert(car(42) === 'cars')
1343
- }
1344
-
1345
- /**
1346
- * ```ts
1347
- * export function pro(ref: any): any;
1348
- * ```
1349
- */
1350
-
1351
717
  /**
1352
718
  * A helper that protect calls to nested properties by `Proxy` which initializes non-existent values with an empty object.
1353
719
  */
@@ -1358,40 +724,6 @@ export const pro = (/** @type {any} */ ref) => new Proxy(ref, {
1358
724
  }
1359
725
  })
1360
726
 
1361
- _test.pro = () => {
1362
- const ref = {}
1363
-
1364
- pro(ref).one.two[3][4] = 1234
1365
-
1366
- console.assert(eq(ref, { one: { two: { 3: { 4: 1234 } } } }))
1367
-
1368
- pro(ref).one.two.tree = 123
1369
-
1370
- console.assert(eq(ref, { one: { two: { 3: { 4: 1234 }, tree: 123 } } }))
1371
-
1372
- pro(ref).one.two = undefined
1373
-
1374
- console.assert(eq(ref, { one: { two: undefined } }))
1375
-
1376
- delete pro(ref).one.two
1377
-
1378
- console.assert(eq(ref, { one: {} }))
1379
-
1380
- pro(ref).one.two.three.four
1381
-
1382
- console.assert(eq(ref, { one: { two: { three: { four: {} } } } }))
1383
-
1384
- pro(ref).one.two.three.four = 1234
1385
-
1386
- console.assert(eq(ref, { one: { two: { three: { four: 1234 } } } }))
1387
- }
1388
-
1389
- /**
1390
- * ```ts
1391
- * export function refsInfo(...refs: any[]): [string, string, string[]][];
1392
- * ```
1393
- */
1394
-
1395
727
  /**
1396
728
  * A helper that provides information about the given `refs`.
1397
729
  *
@@ -1407,81 +739,15 @@ export const refsInfo = (/** @type {any[]} */ ...refs) => {
1407
739
  }
1408
740
  })
1409
741
 
1410
- return Array.from(fns.values()).map(/** @returns {[string, string, string[]]} */ fn => [
742
+ return Array.from(fns.values()).map(/** @return {[string, string, string[]]} */ fn => [
1411
743
  fn.name,
1412
744
  Object.getPrototypeOf(fn)?.name ?? '',
1413
745
  Object.getOwnPropertyNames(fn.prototype ?? Object.create(null)).sort()
1414
746
  ]).sort((a, b) => -(a[0] < b[0]))
1415
747
  }
1416
748
 
1417
- _test.refsInfo = () => {
1418
- const info = refsInfo(Array, Function)
1419
-
1420
- console.assert(info.find(([name]) => name === 'Array')?.[2]?.includes?.('length'))
1421
- console.assert(info.find(([name]) => name === 'Function')?.[2]?.includes?.('length'))
1422
- }
1423
-
1424
- _test['refsInfo: browserFingerprint'] = () => {
1425
- const browserFingerprint = () => {
1426
- // @ts-expect-error
1427
- const refs = Object.getOwnPropertyNames(window).map(name => window[name])
1428
- const info = refsInfo(...refs)
1429
- const json = JSON.stringify(info)
1430
- const hash = Array(32).fill(0)
1431
- let j = 0
1432
-
1433
- for (let i = 0; i < json.length; i++) {
1434
- let charCode = json.charCodeAt(i)
1435
-
1436
- while (charCode > 0) {
1437
- hash[j] = hash[j] ^ (charCode & 15)
1438
- charCode >>= 4
1439
- j = (j + 1) & 31
1440
- }
1441
- }
1442
-
1443
- return hash.map(x => x.toString(16)).join('')
1444
- }
1445
-
1446
- console.log(browserFingerprint())
1447
- }
1448
-
1449
- /**
1450
- * ```ts
1451
- * export function s<T extends keyof SVGElementTagNameMap>(tag: T, ...args: HArgs[1][]): SVGElementTagNameMap[T];
1452
- * export function s<N extends Node>(node: N, ...args: HArgs[1][]): N;
1453
- * export function s(...args: HArgs): Node;
1454
- * ```
1455
- */
1456
-
1457
- /**
1458
- * A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also `h`).
1459
- *
1460
- * - The first argument of type `string` specifies the tag of the element to be created.
1461
- * - The first argument of type `Node` specifies the element to be modified.
1462
- * - All other arguments of type `Record<string, any>` are mappings of attributes and properties.
1463
- * Keys starting with `$` specify *properties* (without the leading `$`) to be set on the element being created or modified.
1464
- * (Note that `$` is not a valid attribute name character.)
1465
- * All other keys specify *attributes* to be set by `setAttributeNS`.
1466
- * An attribute equal to `false` causes the attribute to be removed by `removeAttributeNS`.
1467
- * - All other arguments of type `null` or `undefined` are simply ignored.
1468
- * - All other arguments of type `Node` are appended to the element being created or modified.
1469
- * - All other arguments of type `string`/`number` are converted to `Text` nodes and appended to the element being created or modified.
1470
- * - All other arguments of type `HArgs` are passed to `s` and the results are appended to the element being created or modified.
1471
- */
1472
- export const s = _h('http://www.w3.org/2000/svg')
1473
-
1474
- const _ZEROS = '0'.repeat(16)
1475
- let _counter = 0
1476
-
1477
- /**
1478
- * ```ts
1479
- * export function uuid1(options?: {
1480
- * date?: Date;
1481
- * node?: string;
1482
- * }): string;
1483
- * ```
1484
- */
749
+ const ZEROS = '0'.repeat(16)
750
+ let counter = 0
1485
751
 
1486
752
  /**
1487
753
  * A helper that generates a UUID v1 identifier (with a creation timestamp).
@@ -1493,9 +759,9 @@ export const uuid1 = ({
1493
759
  date = new Date(),
1494
760
  node = Math.random().toString(16).slice(2)
1495
761
  } = {}) => {
1496
- const time = _ZEROS + (10_000 * (+date + 12_219_292_800_000)).toString(16)
762
+ const time = ZEROS + (10000 * (+date + 12219292800000)).toString(16)
1497
763
 
1498
- _counter = (_counter + 1) & 0x3fff
764
+ counter = (counter + 1) & 16383
1499
765
 
1500
766
  return time.slice(-8).concat(
1501
767
  '-',
@@ -1504,31 +770,8 @@ export const uuid1 = ({
1504
770
  -1,
1505
771
  time.slice(-15, -12),
1506
772
  '-',
1507
- (8 | (_counter >> 12)).toString(16),
1508
- (_ZEROS + (_counter & 0xfff).toString(16)).slice(-3),
773
+ (8 | (counter >> 12)).toString(16),
774
+ (ZEROS + (counter & 4095).toString(16)).slice(-3),
1509
775
  '-',
1510
- (_ZEROS + node).slice(-12))
1511
- }
1512
-
1513
- _test.uuid1 = () => {
1514
- for (let counter = 1; counter <= 0x5678; ++counter) {
1515
- const uuid = uuid1()
1516
-
1517
- counter === 0x0001 && console.assert(uuid.split('-')[3] === '8001')
1518
- counter === 0x0fff && console.assert(uuid.split('-')[3] === '8fff')
1519
- counter === 0x1000 && console.assert(uuid.split('-')[3] === '9000')
1520
- counter === 0x2345 && console.assert(uuid.split('-')[3] === 'a345')
1521
- counter === 0x3456 && console.assert(uuid.split('-')[3] === 'b456')
1522
- counter === 0x4000 && console.assert(uuid.split('-')[3] === '8000')
1523
- counter === 0x4567 && console.assert(uuid.split('-')[3] === '8567')
1524
- }
1525
- }
1526
-
1527
- _test['uuid1: node'] = () => {
1528
- console.assert(uuid1({ node: '000123456789abc' }).split('-')[4] === '123456789abc')
1529
- console.assert(uuid1({ node: '123456789' }).split('-')[4] === '000123456789')
1530
- }
1531
-
1532
- _test['uuid1: date'] = () => {
1533
- console.assert(uuid1({ date: new Date(323325e6) }).startsWith('c1399400-9a71-11bd'))
776
+ (ZEROS + node).slice(-12))
1534
777
  }
package/package.json CHANGED
@@ -40,5 +40,5 @@
40
40
  "types": "nnn.d.ts",
41
41
  "name": "@jackens/nnn",
42
42
  "type": "module",
43
- "version": "2023.10.14"
43
+ "version": "2023.11.3"
44
44
  }