@jackens/nnn 2026.2.13 → 2026.2.19

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 +116 -127
  2. package/nnn.js +147 -112
  3. package/package.json +1 -1
  4. package/readme.md +127 -143
package/nnn.d.ts CHANGED
@@ -35,33 +35,6 @@ export type CRoot = Record<PropertyKey, CNode>;
35
35
  * - Top-level keys starting with `@` (at-rules) are not concatenated with child selectors.
36
36
  */
37
37
  export declare const c: (root: CRoot, splitter?: string) => string;
38
- /**
39
- * A responsive web design helper that generates CSS rules for a grid-like layout.
40
- *
41
- * @param root
42
- *
43
- * The CSS root object to populate (see {@link c}).
44
- *
45
- * @param selector
46
- *
47
- * The CSS selector for the grid item.
48
- *
49
- * @param cell_width_px
50
- *
51
- * The base cell width in pixels.
52
- *
53
- * @param cell_height_px
54
- *
55
- * The base cell height in pixels.
56
- *
57
- * @param specs
58
- *
59
- * An array of breakpoint specifications, each a tuple of:
60
- * - `max_width`: maximum number of cells per row (defines the viewport breakpoint).
61
- * - `width` (optional, default `1`): number of horizontal cells the element spans.
62
- * - `height` (optional, default `1`): number of vertical cells the element spans.
63
- */
64
- export declare const rwd: (root: CRoot, selector: string, cell_width_px: number, cell_height_px: number, ...specs: [number, number?, number?][]) => void;
65
38
  /**
66
39
  * Parses a CSV string into a two-dimensional array of strings.
67
40
  *
@@ -80,40 +53,6 @@ export declare const rwd: (root: CRoot, selector: string, cell_width_px: number,
80
53
  * A 2D array where each inner array represents a row of fields.
81
54
  */
82
55
  export declare const csv_parse: (csv: string, separator?: string) => string[][];
83
- /**
84
- * A map from value constructors (or `null`/`undefined`) to escape functions.
85
- *
86
- * Used by {@link escape_values} and {@link new_escape}.
87
- */
88
- export type EscapeMap = Map<unknown, (value?: unknown) => string>;
89
- /**
90
- * Escapes an array of values using the provided escape map.
91
- *
92
- * @param escape_map
93
- *
94
- * A map where keys are constructors (e.g., `String`, `Number`) and values are escape functions.
95
- *
96
- * @param values
97
- *
98
- * The array of values to escape.
99
- *
100
- * @returns
101
- *
102
- * An array of escaped strings.
103
- */
104
- export declare const escape_values: (escape_map: EscapeMap, values: unknown[]) => string[];
105
- /**
106
- * Creates a tag function for escaping interpolated values in template literals.
107
- *
108
- * @param escape_map
109
- *
110
- * A map where keys are constructors and values are escape functions.
111
- *
112
- * @returns
113
- *
114
- * A tag function that escapes interpolated values using the provided escape map.
115
- */
116
- export declare const new_escape: (escape_map: EscapeMap) => (template: TemplateStringsArray, ...values: unknown[]) => string;
117
56
  /**
118
57
  * Applies Polish-specific typographic corrections to a DOM subtree.
119
58
  *
@@ -136,8 +75,7 @@ export type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | st
136
75
  */
137
76
  export type HArgs = [string | Node, ...HArgs1[]];
138
77
  /**
139
- * A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
140
- * for creating and modifying `HTMLElement`s (see also {@link s}).
78
+ * A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also {@link s}).
141
79
  *
142
80
  * @param tag_or_node
143
81
  *
@@ -164,8 +102,7 @@ export declare const h: {
164
102
  (tag_or_node: string | Node, ...args1: HArgs1[]): Node;
165
103
  };
166
104
  /**
167
- * A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
168
- * for creating and modifying `SVGElement`s (see also {@link h}).
105
+ * A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also {@link h}).
169
106
  *
170
107
  * @param tag_or_node
171
108
  *
@@ -191,24 +128,6 @@ export declare const s: {
191
128
  <N extends Node>(node: N, ...args1: HArgs1[]): N;
192
129
  (tag_or_node: string | Node, ...args1: HArgs1[]): Node;
193
130
  };
194
- /**
195
- * Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
196
- *
197
- * Equivalent to: `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
198
- *
199
- * @param id
200
- *
201
- * The ID of the symbol to reference (without the `#` prefix).
202
- *
203
- * @param args
204
- *
205
- * Additional arguments passed to the outer `<svg>` element.
206
- *
207
- * @returns
208
- *
209
- * An `SVGSVGElement` containing a `<use>` element.
210
- */
211
- export declare const svg_use: (id: string, ...args: HArgs1[]) => SVGSVGElement;
212
131
  /**
213
132
  * Checks whether an object has the specified key as its own property.
214
133
  *
@@ -306,11 +225,61 @@ export declare const is_string: (arg: unknown) => arg is string;
306
225
  * The parsed value with handler substitutions applied.
307
226
  */
308
227
  export declare const js_on_parse: (handlers: Record<PropertyKey, Function>, text: string) => any;
228
+ /**
229
+ * A Monokai-inspired color scheme for use with the {@link c} helper and {@link nanolight_ts} tokenizer.
230
+ */
231
+ export declare const monokai: CRoot;
232
+ /**
233
+ * A TypeScript/JavaScript syntax highlighting tokenizer built using {@link new_tokenizer}.
234
+ *
235
+ * @param code
236
+ *
237
+ * The source code string to tokenize.
238
+ *
239
+ * @returns
240
+ *
241
+ * An array of {@link HArgs1} elements suitable for rendering with {@link h}.
242
+ */
243
+ export declare const nanolight_ts: (code: string) => HArgs1[];
244
+ /**
245
+ * A map from value constructors (or `null`/`undefined`) to escape functions.
246
+ *
247
+ * Used by {@link escape_values} and {@link new_escape}.
248
+ */
249
+ export type EscapeMap = Map<unknown, (value?: unknown) => string>;
250
+ /**
251
+ * Escapes an array of values using the provided escape map.
252
+ *
253
+ * @param escape_map
254
+ *
255
+ * A map where keys are constructors (e.g., `String`, `Number`) and values are escape functions.
256
+ *
257
+ * @param values
258
+ *
259
+ * The array of values to escape.
260
+ *
261
+ * @returns
262
+ *
263
+ * An array of escaped strings.
264
+ */
265
+ export declare const escape_values: (escape_map: EscapeMap, values: unknown[]) => string[];
266
+ /**
267
+ * Creates a tag function for escaping interpolated values in template literals.
268
+ *
269
+ * @param escape_map
270
+ *
271
+ * A map where keys are constructors and values are escape functions.
272
+ *
273
+ * @returns
274
+ *
275
+ * A tag function that escapes interpolated values using the provided escape map.
276
+ */
277
+ export declare const new_escape: (escape_map: EscapeMap) => (template: TemplateStringsArray, ...values: unknown[]) => string;
309
278
  /**
310
279
  * Creates a function that returns the appropriate noun form based on a numeric value using `Intl.PluralRules`.
311
280
  *
312
- * Different languages have different plural rules. The `Intl.PluralRules` API provides
313
- * locale-aware plural category selection. Possible categories are:
281
+ * Different languages have different plural rules. The `Intl.PluralRules` API provides locale-aware plural category selection.
282
+ * Possible categories are:
314
283
  *
315
284
  * - `zero`: for zero items (used in some languages like Arabic, Latvian)
316
285
  * - `one`: for singular (e.g., 1 item)
@@ -334,23 +303,31 @@ export declare const js_on_parse: (handlers: Record<PropertyKey, Function>, text
334
303
  */
335
304
  export declare const new_noun_form: (locale: string, forms: Partial<Record<Intl.LDMLPluralRule, string>>) => (value: number) => string;
336
305
  /**
337
- * Creates a new object containing only the specified keys from the source object.
338
- *
339
- * A runtime equivalent of TypeScript’s `Pick<T, K>` utility type. See also {@link omit}.
306
+ * A helper for building simple tokenizers (see also {@link nanolight_ts}).
340
307
  *
341
- * @param ref
308
+ * @param decorator
342
309
  *
343
- * The source object.
310
+ * A function that wraps each matched chunk. It receives the matched text (`chunk`)
311
+ * and optionally the `metadata` associated with the pattern that produced the match.
312
+ * For unmatched text between patterns, `metadata` is `undefined`.
344
313
  *
345
- * @param keys
314
+ * @param specs
346
315
  *
347
- * An array of keys to include in the result.
316
+ * An array of tuples `[metadata, pattern]` where:
317
+ * - `metadata`: arbitrary data (e.g., a CSS class name) passed to `decorator` when the pattern matches.
318
+ * - `pattern`: a `string` or `RegExp` to match against the input.
348
319
  *
349
320
  * @returns
350
321
  *
351
- * A new object with only the specified keys.
322
+ * A tokenizer function that accepts a code string and returns an array of decorated tokens.
323
+ *
324
+ * @remarks
325
+ *
326
+ * 1. Matches starting at an earlier position take precedence.
327
+ * 2. Among matches at the same position, the longer one wins.
328
+ * 3. Among matches of the same position and length, the one defined earlier wins.
352
329
  */
353
- export declare const pick: <T, K extends keyof T>(ref: T, keys: K[]) => Pick<T, K>;
330
+ export declare const new_tokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
354
331
  /**
355
332
  * Creates a new object excluding the specified keys from the source object.
356
333
  *
@@ -370,56 +347,68 @@ export declare const pick: <T, K extends keyof T>(ref: T, keys: K[]) => Pick<T,
370
347
  */
371
348
  export declare const omit: <T, K extends keyof T>(ref: T, keys: unknown[]) => Omit<T, K>;
372
349
  /**
373
- * A helper for building simple tokenizers (see also {@link nanolight_ts}).
374
- *
375
- * @param decorator
350
+ * Creates a new object containing only the specified keys from the source object.
376
351
  *
377
- * A function that wraps each matched chunk. It receives the matched text (`chunk`)
378
- * and optionally the `metadata` associated with the pattern that produced the match.
379
- * For unmatched text between patterns, `metadata` is `undefined`.
352
+ * A runtime equivalent of TypeScript’s `Pick<T, K>` utility type. See also {@link omit}.
380
353
  *
381
- * @param specs
354
+ * @param ref
382
355
  *
383
- * An array of tuples `[metadata, pattern]` where:
384
- * - `metadata`: arbitrary data (e.g., a CSS class name) passed to `decorator` when the pattern matches.
385
- * - `pattern`: a `string` or `RegExp` to match against the input.
356
+ * The source object.
386
357
  *
387
- * @returns
358
+ * @param keys
388
359
  *
389
- * A tokenizer function that accepts a code string and returns an array of decorated tokens.
360
+ * An array of keys to include in the result.
390
361
  *
391
- * @remarks
362
+ * @returns
392
363
  *
393
- * 1. Matches starting at an earlier position take precedence.
394
- * 2. Among matches at the same position, the longer one wins.
395
- * 3. Among matches of the same position and length, the one defined earlier wins.
364
+ * A new object with only the specified keys.
396
365
  */
397
- export declare const new_tokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
366
+ export declare const pick: <T, K extends keyof T>(ref: T, keys: K[]) => Pick<T, K>;
398
367
  /**
399
- * A TypeScript/JavaScript syntax highlighting tokenizer built using {@link new_tokenizer}.
368
+ * A responsive web design helper that generates CSS rules for a grid-like layout.
400
369
  *
401
- * @param code
370
+ * @param root
402
371
  *
403
- * The source code string to tokenize.
372
+ * The CSS root object to populate (see {@link c}).
404
373
  *
405
- * @returns
374
+ * @param selector
406
375
  *
407
- * An array of {@link HArgs1} elements suitable for rendering with {@link h}.
376
+ * The CSS selector for the grid item.
377
+ *
378
+ * @param cell_width_px
379
+ *
380
+ * The base cell width in pixels.
381
+ *
382
+ * @param cell_height_px
383
+ *
384
+ * The base cell height in pixels.
385
+ *
386
+ * @param specs
387
+ *
388
+ * An array of breakpoint specifications, each a tuple of:
389
+ * - `max_width`: maximum number of cells per row (defines the viewport breakpoint).
390
+ * - `width` (optional, default `1`): number of horizontal cells the element spans.
391
+ * - `height` (optional, default `1`): number of vertical cells the element spans.
408
392
  */
409
- export declare const nanolight_ts: (code: string) => HArgs1[];
393
+ export declare const rwd: (root: CRoot, selector: string, cell_width_px: number, cell_height_px: number, ...specs: [number, number?, number?][]) => void;
410
394
  /**
411
- * A Monokai-inspired color scheme for use with the {@link c} helper and {@link nanolight_ts} tokenizer.
395
+ * Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
412
396
  *
413
- * Defines CSS custom properties for eight colors (`--black`, `--blue`, `--green`, `--grey`,
414
- * `--purple`, `--red`, `--white`, `--yellow`) with both light and dark mode variants.
397
+ * Equivalent to: `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
415
398
  *
416
- * @remarks
399
+ * @param id
400
+ *
401
+ * The ID of the symbol to reference (without the `#` prefix).
402
+ *
403
+ * @param args
417
404
  *
418
- * - In light mode, `--black` is a light background and `--white` is dark text.
419
- * - In dark mode (via `prefers-color-scheme: dark`), the palette inverts to a dark background with light text.
420
- * - Includes base styles for `body`, `pre` and `code>span.*` (syntax highlighting).
405
+ * Additional arguments passed to the outer `<svg>` element.
406
+ *
407
+ * @returns
408
+ *
409
+ * An `SVGSVGElement` containing a `<use>` element.
421
410
  */
422
- export declare const monokai: CRoot;
411
+ export declare const svg_use: (id: string, ...args: HArgs1[]) => SVGSVGElement;
423
412
  /**
424
413
  * Generates a UUID v1 (time-based) identifier.
425
414
  *
@@ -446,14 +435,14 @@ export declare const uuid_v1: (date?: Date, node?: string) => string;
446
435
  * Intermediates of the last level in a get-only property chain are NOT auto-created.
447
436
  * For example, `vivify(ref).one.two` will create `ref.one` as `{}`, but will NOT create `ref.one.two`.
448
437
  * Only when a deeper access, assignment, or deletion occurs
449
- * (e.g. `vivify(ref).one.two[3]` or `vivify(ref).one.two.three = 42`) will `ref.one.two` be materialized.
438
+ * (e.g. `delete vivify(ref).one.two.three` or `vivify(ref).one.two.three = 4`) will `ref.one.two` be materialized.
450
439
  *
451
440
  * When traversal reaches a primitive value, no auto-creation happens;
452
441
  * the primitive’s own property is returned instead
453
442
  * (e.g. accessing `.toString.name` on a number yields `'toString'` without modifying the underlying structure).
454
443
  *
455
444
  * Deletion on a non-existing intermediate path will auto-create intermediates up to the parent of the deleted key
456
- * (e.g. `delete vivify(ref).a.b.c` will create `ref.a.b` as `{}` if it does not exist).
445
+ * (e.g. `delete vivify(ref).one.two.three` will create `ref.one.two` as `{}` if it does not exist).
457
446
  *
458
447
  * @param ref
459
448
  *
package/nnn.js CHANGED
@@ -1,33 +1,11 @@
1
- // src/nnn/is.ts
1
+ // src/nnn/is_array.ts
2
2
  var is_array = Array.isArray;
3
- var is_finite_number = Number.isFinite;
3
+
4
+ // src/nnn/is_number.ts
4
5
  var is_number = (arg) => typeof arg === "number";
5
- var is_record = (arg) => typeof arg === "object" && arg != null && !is_array(arg);
6
- var is_string = (arg) => typeof arg === "string";
7
6
 
8
- // src/nnn/vivify.ts
9
- var ARRAY_INDEX_REGEXP = /^(0|[1-9]\d*)$/;
10
- var _is_object = (ref) => typeof ref === "object";
11
- var _get_target = (parent, parent_key, key) => parent[parent_key] ??= is_string(key) && ARRAY_INDEX_REGEXP.test(key) ? [] : {};
12
- var _vivify = (parent, parent_key) => {
13
- return parent != null && _is_object(parent) ? new Proxy(parent, {
14
- get(_, key) {
15
- const target = _get_target(parent, parent_key, key);
16
- const value = target[key];
17
- return is_string(key) && _is_object(target) && (value == null || _is_object(value)) ? _vivify(target, key) : value;
18
- },
19
- set(_, key, value) {
20
- const target = _get_target(parent, parent_key, key);
21
- target[key] = value;
22
- return true;
23
- },
24
- deleteProperty(_, key) {
25
- const target = _get_target(parent, parent_key, key);
26
- return delete target[key];
27
- }
28
- }) : parent?.[parent_key];
29
- };
30
- var vivify = (ref) => _vivify({ _: ref }, "_");
7
+ // src/nnn/is_string.ts
8
+ var is_string = (arg) => typeof arg === "string";
31
9
 
32
10
  // src/nnn/c.ts
33
11
  var _c = (node, prefix, result, splitter) => {
@@ -81,37 +59,15 @@ var c = (root, splitter = "$$") => {
81
59
  }
82
60
  return chunks.join("");
83
61
  };
84
- var rwd = (root, selector, cell_width_px, cell_height_px, ...specs) => {
85
- const main = vivify(root)[selector];
86
- main.boxSizing = "border-box";
87
- main.display = "block";
88
- main.float = "left";
89
- main.width = "100%";
90
- main.height = `${cell_height_px}px`;
91
- specs.sort(([a], [b]) => a - b);
92
- for (let [max_width, width, height] of specs) {
93
- const node = max_width === 1 ? main : vivify(root)[`@media(min-width:${cell_width_px * max_width}px)`][selector];
94
- width ??= 1;
95
- height ??= 1;
96
- let gcd = 100 * width;
97
- let tmp = max_width;
98
- while (tmp > 0) {
99
- [gcd, tmp] = [tmp, gcd % tmp];
100
- }
101
- const w_100_per_gcd = 100 * width / gcd;
102
- node.width = max_width === gcd ? `${w_100_per_gcd}%` : `calc(${w_100_per_gcd}% / ${max_width / gcd})`;
103
- node.height = `${cell_height_px * height}px`;
104
- }
105
- };
106
62
  // src/nnn/csv_parse.ts
63
+ var MAIN_PATTERN = /\n|(?<!")("(?:[^"]|"")*")(?!")/g;
107
64
  var csv_parse = (csv, separator = ",") => {
108
- const main_pattern = /\n|(?<!")("(?:[^"]|"")*")(?!")/g;
109
65
  const line_pattern = new RegExp(`${separator}|(?<!")\\s*"((?:[^"]|"")*)"\\s*(?!")`, "g");
110
- return csv.replace(/\r/g, "").replace(/\n+$/, "").replace(main_pattern, (_, chunk) => chunk ?? "\r").split("\r").map((line) => line.replace(line_pattern, (_, chunk) => chunk == null ? "\r" : chunk.replace(/""/g, '"')).split("\r"));
66
+ return csv.replace(/\r/g, "").replace(/\n+$/, "").replace(MAIN_PATTERN, (_, chunk) => chunk ?? "\r").split("\r").map((line) => line.replace(line_pattern, (_, chunk) => chunk == null ? "\r" : chunk.replace(/""/g, '"')).split("\r"));
111
67
  };
112
- // src/nnn/escape.ts
113
- var escape_values = (escape_map, values) => values.map((value) => (value == null ? escape_map.get(value) : escape_map.get(value?.constructor))?.(value) ?? "");
114
- var new_escape = (escape_map) => (template, ...values) => String.raw(template, ...escape_values(escape_map, values));
68
+ // src/nnn/is_record.ts
69
+ var is_record = (arg) => typeof arg === "object" && arg != null && !is_array(arg);
70
+
115
71
  // src/nnn/h.ts
116
72
  var _h = (namespace_uri) => {
117
73
  const create_element = namespace_uri == null ? (tag) => document.createElement(tag) : (tag) => document.createElementNS(namespace_uri, tag);
@@ -173,7 +129,6 @@ var _h = (namespace_uri) => {
173
129
  };
174
130
  var h = /* @__PURE__ */ _h();
175
131
  var s = /* @__PURE__ */ _h("http://www.w3.org/2000/svg");
176
- var svg_use = (id, ...args) => s("svg", ["use", { "xlink:href": "#" + id }], ...args);
177
132
 
178
133
  // src/nnn/fix_pl_typography.ts
179
134
  var TAGS_TO_SKIP = ["IFRAME", "NOSCRIPT", "PRE", "SCRIPT", "STYLE", "TEXTAREA"];
@@ -208,6 +163,8 @@ var fix_pl_typography = (node) => {
208
163
  };
209
164
  // src/nnn/has_own.ts
210
165
  var has_own = (ref, key) => ref != null && Object.hasOwn(ref, key);
166
+ // src/nnn/is_finite_number.ts
167
+ var is_finite_number = Number.isFinite;
211
168
  // src/nnn/js_on_parse.ts
212
169
  var js_on_parse = (handlers, text) => JSON.parse(text, (key, value) => {
213
170
  if (is_record(value)) {
@@ -226,13 +183,71 @@ var js_on_parse = (handlers, text) => JSON.parse(text, (key, value) => {
226
183
  }
227
184
  return value;
228
185
  });
229
- // src/nnn/new_noun_form.ts
230
- var PLURAL_RULES = {};
231
- var new_noun_form = (locale, forms) => (value) => forms[(PLURAL_RULES[locale] ??= new Intl.PluralRules(locale)).select(value)] ?? forms.other ?? "";
232
- // src/nnn/omit_pick.ts
233
- var pick = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => keys.includes(key)));
234
- var omit = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => !keys.includes(key)));
235
- // src/nnn/tokenizer.ts
186
+ // src/nnn/monokai.ts
187
+ var monokai = {
188
+ ":root": {
189
+ __bg: "#faf4f2",
190
+ __fg: "#29242a",
191
+ __comment: "#918c8e",
192
+ __identifier_1: "#7058be",
193
+ __identifier_2: "#269d69",
194
+ __identifier_3: "#1c8ca8",
195
+ __identifier_4: "#29242a",
196
+ __keyword_1: "#e14775",
197
+ __keyword_2: "#7058be",
198
+ __keyword_3: "#1c8ca8",
199
+ __number: "#7058be",
200
+ __operator: "#e14775",
201
+ __punctuation: "#918c8e",
202
+ __string: "#cc7a0a"
203
+ },
204
+ pre: {
205
+ backgroundColor: "var(--bg)",
206
+ color: "var(--fg)",
207
+ margin: 0,
208
+ padding: "1em",
209
+ overflow: "visible",
210
+ " code": {
211
+ fontFamily: '"Source Code Pro"',
212
+ padding: 0
213
+ }
214
+ },
215
+ "code>span.": {
216
+ bg: { color: "var(--bg)" },
217
+ fg: { color: "var(--fg)" },
218
+ comment: { color: "var(--comment)" },
219
+ "identifier-1": { color: "var(--identifier-1)" },
220
+ "identifier-2": { color: "var(--identifier-2)" },
221
+ "identifier-3": { color: "var(--identifier-3)" },
222
+ "identifier-4": { color: "var(--identifier-4)" },
223
+ "keyword-1": { color: "var(--keyword-1)" },
224
+ "keyword-2": { color: "var(--keyword-2)" },
225
+ "keyword-3": { color: "var(--keyword-3)" },
226
+ number: { color: "var(--number)" },
227
+ operator: { color: "var(--operator)" },
228
+ punctuation: { color: "var(--punctuation)" },
229
+ string: { color: "var(--string)" }
230
+ },
231
+ "@media only screen and (prefers-color-scheme: dark)": {
232
+ ":root": {
233
+ __bg: "#2d2a2e",
234
+ __fg: "#fcfcfa",
235
+ __comment: "#727072",
236
+ __identifier_1: "#ae81ff",
237
+ __identifier_2: "#a9dc76",
238
+ __identifier_3: "#66d9ef",
239
+ __identifier_4: "#fcfcfa",
240
+ __keyword_1: "#ff6188",
241
+ __keyword_2: "#ae81ff",
242
+ __keyword_3: "#66d9ef",
243
+ __number: "#ae81ff",
244
+ __operator: "#ff6188",
245
+ __punctuation: "#727072",
246
+ __string: "#ffd866"
247
+ }
248
+ }
249
+ };
250
+ // src/nnn/new_tokenizer.ts
236
251
  var new_tokenizer = (decorator, ...specs) => (code) => {
237
252
  const result = [];
238
253
  while (code.length > 0) {
@@ -270,60 +285,80 @@ var new_tokenizer = (decorator, ...specs) => (code) => {
270
285
  }
271
286
  return result;
272
287
  };
273
- var BLUE = "blue";
274
- var GREEN = "green";
275
- var GREY = "grey";
276
- var PURPLE = "purple";
277
- var RED = "red";
278
- var WHITE = "white";
279
- var YELLOW = "yellow";
280
- var nanolight_ts = /* @__PURE__ */ new_tokenizer((chunk, name) => name != null ? ["span", { class: name }, chunk] : chunk, [YELLOW, /".*?"/], [YELLOW, /'.*?'/], [YELLOW, /`[\s\S]*?`/], [RED, "?"], [RED, /(?<=\s):/], [GREY, "("], [GREY, ")"], [GREY, ","], [GREY, "."], [GREY, ":"], [GREY, ";"], [GREY, "?."], [GREY, "["], [GREY, "]"], [GREY, "{"], [GREY, "}"], [GREY, /\/\*[\s\S]*?\*\//], [GREY, /(?<!\\)\/\/.*?(?=\n)/], [RED, "!"], [RED, "!="], [RED, "!=="], [RED, "%"], [RED, "%="], [RED, "&&"], [RED, "&&="], [RED, "&"], [RED, "&="], [RED, "*"], [RED, "**"], [RED, "**="], [RED, "*="], [RED, "+"], [RED, "++"], [RED, "+="], [RED, "-"], [RED, "--"], [RED, "-="], [RED, "..."], [RED, "/"], [RED, "/="], [RED, ":"], [RED, "<"], [RED, "<<"], [RED, "<<="], [RED, "<="], [RED, "="], [RED, "=="], [RED, "==="], [RED, "=>"], [RED, ">"], [RED, ">="], [RED, ">>"], [RED, ">>="], [RED, ">>>"], [RED, ">>>="], [RED, "?"], [RED, "??"], [RED, "??="], [RED, "^"], [RED, "^="], [RED, "as"], [RED, "async"], [RED, "await"], [RED, "break"], [RED, "case"], [RED, "catch"], [RED, "class"], [RED, "const"], [RED, "continue"], [RED, "debugger"], [RED, "default"], [RED, "delete"], [RED, "do"], [RED, "else"], [RED, "export"], [RED, "extends"], [RED, "finally"], [RED, "for"], [RED, "from"], [RED, "function"], [RED, "function*"], [RED, "goto"], [RED, "if"], [RED, "import"], [RED, "in"], [RED, "instanceof"], [RED, "is"], [RED, "keyof"], [RED, "let"], [RED, "new"], [RED, "of"], [RED, "package"], [RED, "return"], [RED, "super"], [RED, "switch"], [RED, "this"], [RED, "throw"], [RED, "try"], [RED, "type"], [RED, "typeof"], [RED, "var"], [RED, "void"], [RED, "while"], [RED, "with"], [RED, "yield"], [RED, "yield*"], [RED, "|"], [RED, "|="], [RED, "||"], [RED, "||="], [RED, "~"], [RED, "~="], [PURPLE, "false"], [PURPLE, "Infinity"], [PURPLE, "NaN"], [PURPLE, "null"], [PURPLE, "true"], [PURPLE, "undefined"], [PURPLE, /0b[01_]+/], [PURPLE, /0o[01234567_]+/], [PURPLE, /0x[\dabcdef_]+/], [PURPLE, /[\p{Lu}_$][\p{Lu}\d_$]+/u], [PURPLE, /\d[\d_]*(\.[\d_]+)?(e[+-]?[\d_]+)?/], [BLUE, "any"], [BLUE, "bigint"], [BLUE, "boolean"], [BLUE, "eval"], [BLUE, "number"], [BLUE, "string"], [BLUE, "symbol"], [BLUE, "unknown"], [GREEN, /[\p{L}_$][\p{L}\d_$]+(?=\()/u], [BLUE, /\p{Lu}[\p{L}\d_$]+/u], [WHITE, /[\p{L}_$][\p{L}\d_$]+/u]);
281
- var monokai = {
282
- ":root": {
283
- __black: "$faf4f2",
284
- __blue: "#1c8ca8",
285
- __green: "#269d69",
286
- __grey: "#918c8e",
287
- __purple: "#7058be",
288
- __red: "#e14775",
289
- __white: "#29242a",
290
- __yellow: "#cc7a0a"
291
- },
292
- body$$monokai: {
293
- backgroundColor: "var(--black)",
294
- color: "var(--white)"
295
- },
296
- "code>span.": {
297
- black: { color: "var(--black)" },
298
- blue: { color: "var(--blue)" },
299
- green: { color: "var(--green)" },
300
- grey: { color: "var(--grey)" },
301
- purple: { color: "var(--purple)" },
302
- red: { color: "var(--red)" },
303
- white: { color: "var(--white)" },
304
- yellow: { color: "var(--yellow)" }
305
- },
306
- pre: {
307
- margin: 0,
308
- overflow: "visible",
309
- " code": {
310
- fontFamily: '"Source Code Pro"',
311
- padding: 0
288
+
289
+ // src/nnn/nanolight_ts.ts
290
+ var COMMENT = "comment";
291
+ var IDENTIFIER_1 = "identifier-1";
292
+ var IDENTIFIER_2 = "identifier-2";
293
+ var IDENTIFIER_3 = "identifier-3";
294
+ var IDENTIFIER_4 = "identifier-4";
295
+ var KEYWORD_1 = "keyword-1";
296
+ var KEYWORD_2 = "keyword-2";
297
+ var KEYWORD_3 = "keyword-3";
298
+ var NUMBER = "number";
299
+ var OPERATOR = "operator";
300
+ var PUNCTUATION = "punctuation";
301
+ var STRING = "string";
302
+ var nanolight_ts = /* @__PURE__ */ new_tokenizer((chunk, name) => name != null ? ["span", { class: name }, chunk] : chunk, [STRING, /".*?"/], [STRING, /'.*?'/], [STRING, /`[\s\S]*?`/], [COMMENT, /\/\*[\s\S]*?\*\//], [COMMENT, /(?<!\\)\/\/.*?(?=\n)/], [NUMBER, /0b[01_]+/], [NUMBER, /0o[01234567_]+/], [NUMBER, /0x[\dabcdef_]+/], [NUMBER, /\d[\d_]*(\.[\d_]+)?(e[+-]?[\d_]+)?/], [OPERATOR, "!"], [OPERATOR, "!="], [OPERATOR, "!=="], [OPERATOR, "%"], [OPERATOR, "%="], [OPERATOR, "&&"], [OPERATOR, "&&="], [OPERATOR, "&"], [OPERATOR, "&="], [OPERATOR, "*"], [OPERATOR, "**"], [OPERATOR, "**="], [OPERATOR, "*="], [OPERATOR, "+"], [OPERATOR, "++"], [OPERATOR, "+="], [OPERATOR, "-"], [OPERATOR, "--"], [OPERATOR, "-="], [OPERATOR, "..."], [OPERATOR, "/"], [OPERATOR, "/="], [OPERATOR, ":"], [OPERATOR, "<"], [OPERATOR, "<<"], [OPERATOR, "<<="], [OPERATOR, "<="], [OPERATOR, "="], [OPERATOR, "=="], [OPERATOR, "==="], [OPERATOR, "=>"], [OPERATOR, ">"], [OPERATOR, ">="], [OPERATOR, ">>"], [OPERATOR, ">>="], [OPERATOR, ">>>"], [OPERATOR, ">>>="], [OPERATOR, "?"], [OPERATOR, "?"], [OPERATOR, "??"], [OPERATOR, "??="], [OPERATOR, "^"], [OPERATOR, "^="], [OPERATOR, "|"], [OPERATOR, "|="], [OPERATOR, "||"], [OPERATOR, "||="], [OPERATOR, "~"], [OPERATOR, "~="], [OPERATOR, /(?<=\s):/], [PUNCTUATION, "("], [PUNCTUATION, ")"], [PUNCTUATION, ","], [PUNCTUATION, "."], [PUNCTUATION, ":"], [PUNCTUATION, ";"], [PUNCTUATION, "?."], [PUNCTUATION, "["], [PUNCTUATION, "]"], [PUNCTUATION, "{"], [PUNCTUATION, "}"], [KEYWORD_1, "as"], [KEYWORD_1, "async"], [KEYWORD_1, "await"], [KEYWORD_1, "break"], [KEYWORD_1, "case"], [KEYWORD_1, "catch"], [KEYWORD_1, "class"], [KEYWORD_1, "const"], [KEYWORD_1, "continue"], [KEYWORD_1, "debugger"], [KEYWORD_1, "default"], [KEYWORD_1, "delete"], [KEYWORD_1, "do"], [KEYWORD_1, "else"], [KEYWORD_1, "export"], [KEYWORD_1, "extends"], [KEYWORD_1, "finally"], [KEYWORD_1, "for"], [KEYWORD_1, "from"], [KEYWORD_1, "function"], [KEYWORD_1, "function*"], [KEYWORD_1, "goto"], [KEYWORD_1, "if"], [KEYWORD_1, "import"], [KEYWORD_1, "in"], [KEYWORD_1, "instanceof"], [KEYWORD_1, "is"], [KEYWORD_1, "keyof"], [KEYWORD_1, "let"], [KEYWORD_1, "new"], [KEYWORD_1, "of"], [KEYWORD_1, "package"], [KEYWORD_1, "return"], [KEYWORD_1, "super"], [KEYWORD_1, "switch"], [KEYWORD_1, "this"], [KEYWORD_1, "throw"], [KEYWORD_1, "try"], [KEYWORD_1, "type"], [KEYWORD_1, "typeof"], [KEYWORD_1, "var"], [KEYWORD_1, "void"], [KEYWORD_1, "while"], [KEYWORD_1, "with"], [KEYWORD_1, "yield"], [KEYWORD_1, "yield*"], [KEYWORD_2, "false"], [KEYWORD_2, "Infinity"], [KEYWORD_2, "NaN"], [KEYWORD_2, "null"], [KEYWORD_2, "true"], [KEYWORD_2, "undefined"], [KEYWORD_3, "any"], [KEYWORD_3, "bigint"], [KEYWORD_3, "boolean"], [KEYWORD_3, "eval"], [KEYWORD_3, "number"], [KEYWORD_3, "string"], [KEYWORD_3, "symbol"], [KEYWORD_3, "unknown"], [IDENTIFIER_1, /[\p{Lu}_$][\p{Lu}\d_$]*/u], [IDENTIFIER_2, /[\p{L}_$][\p{L}\d_$]*(?=[(`])/u], [IDENTIFIER_3, /\p{Lu}[\p{L}\d_$]*/u], [IDENTIFIER_4, /[\p{L}_$][\p{L}\d_$]*/u]);
303
+ // src/nnn/new_escape.ts
304
+ var escape_values = (escape_map, values) => values.map((value) => (value == null ? escape_map.get(value) : escape_map.get(value?.constructor))?.(value) ?? "");
305
+ var new_escape = (escape_map) => (template, ...values) => String.raw(template, ...escape_values(escape_map, values));
306
+ // src/nnn/new_noun_form.ts
307
+ var PLURAL_RULES = {};
308
+ var new_noun_form = (locale, forms) => (value) => forms[(PLURAL_RULES[locale] ??= new Intl.PluralRules(locale)).select(value)] ?? forms.other ?? "";
309
+ // src/nnn/omit.ts
310
+ var omit = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => !keys.includes(key)));
311
+ // src/nnn/pick.ts
312
+ var pick = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => keys.includes(key)));
313
+ // src/nnn/vivify.ts
314
+ var ARRAY_INDEX_REGEXP = /^(0|[1-9]\d*)$/;
315
+ var is_object = (ref) => typeof ref === "object";
316
+ var get_target = (parent, parent_key, key) => parent[parent_key] ??= is_string(key) && ARRAY_INDEX_REGEXP.test(key) ? [] : {};
317
+ var _vivify = (parent, parent_key) => {
318
+ return parent != null && is_object(parent) ? new Proxy(parent, {
319
+ get(_, key) {
320
+ const target = get_target(parent, parent_key, key);
321
+ const value = target[key];
322
+ return is_string(key) && is_object(target) && (value == null || is_object(value)) ? _vivify(target, key) : value;
323
+ },
324
+ set(_, key, value) {
325
+ const target = get_target(parent, parent_key, key);
326
+ target[key] = value;
327
+ return true;
328
+ },
329
+ deleteProperty(_, key) {
330
+ const target = get_target(parent, parent_key, key);
331
+ return delete target[key];
312
332
  }
313
- },
314
- "@media only screen and (prefers-color-scheme: dark)": {
315
- ":root": {
316
- __black: "#2d2a2e",
317
- __blue: "#66d9ef",
318
- __green: "#a9dc76",
319
- __grey: "#727072",
320
- __purple: "#ae81ff",
321
- __red: "#ff6188",
322
- __white: "#fcfcfa",
323
- __yellow: "#ffd866"
333
+ }) : parent?.[parent_key];
334
+ };
335
+ var vivify = (ref) => _vivify({ _: ref }, "_");
336
+
337
+ // src/nnn/rwd.ts
338
+ var rwd = (root, selector, cell_width_px, cell_height_px, ...specs) => {
339
+ const main = vivify(root)[selector];
340
+ main.boxSizing = "border-box";
341
+ main.display = "block";
342
+ main.float = "left";
343
+ main.width = "100%";
344
+ main.height = `${cell_height_px}px`;
345
+ specs.sort(([a], [b]) => a - b);
346
+ for (let [max_width, width, height] of specs) {
347
+ const node = max_width === 1 ? main : vivify(root)[`@media(min-width:${cell_width_px * max_width}px)`][selector];
348
+ width ??= 1;
349
+ height ??= 1;
350
+ let gcd = 100 * width;
351
+ let tmp = max_width;
352
+ while (tmp > 0) {
353
+ [gcd, tmp] = [tmp, gcd % tmp];
324
354
  }
355
+ const w_100_per_gcd = 100 * width / gcd;
356
+ node.width = max_width === gcd ? `${w_100_per_gcd}%` : `calc(${w_100_per_gcd}% / ${max_width / gcd})`;
357
+ node.height = `${cell_height_px * height}px`;
325
358
  }
326
359
  };
360
+ // src/nnn/svg_use.ts
361
+ var svg_use = (id, ...args) => s("svg", ["use", { "xlink:href": "#" + id }], ...args);
327
362
  // src/nnn/uuid_v1.ts
328
363
  var ZEROS = /* @__PURE__ */ "0".repeat(16);
329
364
  var counter = 0;
package/package.json CHANGED
@@ -43,5 +43,5 @@
43
43
  "name": "@jackens/nnn",
44
44
  "type": "module",
45
45
  "types": "nnn.d.ts",
46
- "version": "2026.2.13"
46
+ "version": "2026.2.19"
47
47
  }
package/readme.md CHANGED
@@ -1,22 +1,23 @@
1
1
  # nnn
2
2
 
3
- A collection of Jackens’ JavaScript helper utilities.
3
+ A collection of Jackens’ JavaScript helper utilities (version: `2026.2.19`).
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```sh
8
- bun i @jackens/nnn # npm i @jackens/nnn
8
+ bun i @jackens/nnn
9
+ ```
10
+
11
+ or
12
+
13
+ ```sh
14
+ npm i @jackens/nnn
9
15
  ```
10
16
 
11
17
  ## Usage
12
18
 
13
19
  ```js
14
20
  import {
15
- CNode,
16
- CRoot,
17
- EscapeMap,
18
- HArgs,
19
- HArgs1,
20
21
  c,
21
22
  csv_parse,
22
23
  escape_values,
@@ -41,7 +42,7 @@ import {
41
42
  svg_use,
42
43
  uuid_v1,
43
44
  vivify
44
- } from '@jackens/nnn' // './node_modules/@jackens/nnn/nnn.js'
45
+ } from '@jackens/nnn' // or './node_modules/@jackens/nnn/nnn.js'
45
46
  ```
46
47
 
47
48
  ## Exports
@@ -55,8 +56,7 @@ import {
55
56
  - [`csv_parse`](#csv_parse): Parses a CSV string into a two-dimensional array of strings.
56
57
  - [`escape_values`](#escape_values): Escapes an array of values using the provided escape map.
57
58
  - [`fix_pl_typography`](#fix_pl_typography): Applies Polish-specific typographic corrections to a DOM subtree.
58
- - [`h`](#h): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
59
- for creating and modifying `HTMLElement`s (see also [`s`](#s)).
59
+ - [`h`](#h): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
60
60
  - [`has_own`](#has_own): Checks whether an object has the specified key as its own property.
61
61
  - [`is_array`](#is_array): Checks whether the argument is an array.
62
62
  - [`is_finite_number`](#is_finite_number): Checks whether the argument is a finite number (excludes `±Infinity` and `NaN`).
@@ -72,8 +72,7 @@ import {
72
72
  - [`omit`](#omit): Creates a new object excluding the specified keys from the source object.
73
73
  - [`pick`](#pick): Creates a new object containing only the specified keys from the source object.
74
74
  - [`rwd`](#rwd): A responsive web design helper that generates CSS rules for a grid-like layout.
75
- - [`s`](#s): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
76
- for creating and modifying `SVGElement`s (see also [`h`](#h)).
75
+ - [`s`](#s): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also [`h`](#h)).
77
76
  - [`svg_use`](#svg_use): Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
78
77
  - [`uuid_v1`](#uuid_v1): Generates a UUID v1 (time-based) identifier.
79
78
  - [`vivify`](#vivify): A Proxy-based helper for auto-vivification of nested object structures.
@@ -156,7 +155,7 @@ A CSS string representing the compiled rules.
156
155
  #### Usage Examples
157
156
 
158
157
  ```ts
159
- const actual = c({
158
+ const actual_1 = c({
160
159
  a: {
161
160
  color: 'red',
162
161
  margin: 1,
@@ -165,7 +164,7 @@ const actual = c({
165
164
  }
166
165
  })
167
166
 
168
- const expected = `
167
+ const expected_1 = `
169
168
  a{
170
169
  color:red;
171
170
  margin:1
@@ -178,11 +177,9 @@ a{
178
177
  padding:1
179
178
  }`.replace(/\n\s*/g, '')
180
179
 
181
- expect(actual).to.deep.equal(expected)
182
- ```
180
+ expect(actual_1).to.equal(expected_1)
183
181
 
184
- ```ts
185
- const actual = c({
182
+ const actual_2 = c({
186
183
  a: {
187
184
  '.b': {
188
185
  color: 'red',
@@ -193,7 +190,7 @@ const actual = c({
193
190
  }
194
191
  })
195
192
 
196
- const expected = `
193
+ const expected_2 = `
197
194
  a.b{
198
195
  color:red;
199
196
  margin:1
@@ -206,11 +203,9 @@ a.b{
206
203
  padding:1
207
204
  }`.replace(/\n\s*/g, '')
208
205
 
209
- expect(actual).to.deep.equal(expected)
210
- ```
206
+ expect(actual_2).to.equal(expected_2)
211
207
 
212
- ```ts
213
- const actual = c({
208
+ const actual_3 = c({
214
209
  '@font-face$$1': {
215
210
  fontFamily: 'Jackens',
216
211
  src$$1: 'url(otf/jackens.otf)',
@@ -239,7 +234,7 @@ const actual = c({
239
234
  }
240
235
  })
241
236
 
242
- const expected = `
237
+ const expected_3 = `
243
238
  @font-face{
244
239
  font-family:Jackens;
245
240
  src:url(otf/jackens.otf);
@@ -281,11 +276,9 @@ div.c2{
281
276
  }
282
277
  }`.replace(/\n\s*/g, '')
283
278
 
284
- expect(actual).to.deep.equal(expected)
285
- ```
279
+ expect(actual_3).to.equal(expected_3)
286
280
 
287
- ```ts
288
- const actual = c({
281
+ const actual_4 = c({
289
282
  a: {
290
283
  '.b,.c': {
291
284
  margin: 1,
@@ -296,7 +289,7 @@ const actual = c({
296
289
  }
297
290
  })
298
291
 
299
- const expected = `
292
+ const expected_4 = `
300
293
  a.b,a.c{
301
294
  margin:1
302
295
  }
@@ -304,11 +297,9 @@ a.b.d,a.c.d{
304
297
  margin:2
305
298
  }`.replace(/\n\s*/g, '')
306
299
 
307
- expect(actual).to.deep.equal(expected)
308
- ```
300
+ expect(actual_4).to.equal(expected_4)
309
301
 
310
- ```ts
311
- const actual = c({
302
+ const actual_5 = c({
312
303
  '.b,.c': {
313
304
  margin: 1,
314
305
  '.d': {
@@ -317,7 +308,7 @@ const actual = c({
317
308
  }
318
309
  })
319
310
 
320
- const expected = `
311
+ const expected_5 = `
321
312
  .b,.c{
322
313
  margin:1
323
314
  }
@@ -325,11 +316,9 @@ const expected = `
325
316
  margin:2
326
317
  }`.replace(/\n\s*/g, '')
327
318
 
328
- expect(actual).to.deep.equal(expected)
329
- ```
319
+ expect(actual_5).to.equal(expected_5)
330
320
 
331
- ```ts
332
- const actual = c({
321
+ const actual_6 = c({
333
322
  '.a,.b': {
334
323
  margin: 1,
335
324
  '.c,.d': {
@@ -338,7 +327,7 @@ const actual = c({
338
327
  }
339
328
  })
340
329
 
341
- const expected = `
330
+ const expected_6 = `
342
331
  .a,.b{
343
332
  margin:1
344
333
  }
@@ -346,7 +335,7 @@ const expected = `
346
335
  margin:2
347
336
  }`.replace(/\n\s*/g, '')
348
337
 
349
- expect(actual).to.deep.equal(expected)
338
+ expect(actual_6).to.equal(expected_6)
350
339
  ```
351
340
 
352
341
  ### csv_parse
@@ -382,6 +371,7 @@ yyy",zzz
382
371
  42 , "42" , 17
383
372
 
384
373
  `
374
+
385
375
  expect(csv_parse(text)).to.deep.equal([
386
376
  ['aaa\n"aaa"\naaa', 'bbb', 'ccc,ccc'],
387
377
  ['xxx,xxx', 'yyy\nyyy', 'zzz'],
@@ -448,8 +438,7 @@ const h: {
448
438
  };
449
439
  ```
450
440
 
451
- A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
452
- for creating and modifying `HTMLElement`s (see also [`s`](#s)).
441
+ A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
453
442
 
454
443
  #### tag_or_node
455
444
 
@@ -475,49 +464,41 @@ The created or modified `HTMLElement`.
475
464
  ```ts
476
465
  const b = h('b')
477
466
 
478
- expect(b.outerHTML).to.deep.equal('<b></b>')
467
+ expect(b.outerHTML).to.equal('<b></b>')
479
468
 
480
469
  const i = h('i', 'text')
481
470
 
482
471
  h(b, i)
483
472
 
484
- expect(i.outerHTML).to.deep.equal('<i>text</i>')
485
- expect(b.outerHTML).to.deep.equal('<b><i>text</i></b>')
473
+ expect(i.outerHTML).to.equal('<i>text</i>')
474
+ expect(b.outerHTML).to.equal('<b><i>text</i></b>')
486
475
 
487
476
  h(i, { $className: 'some class' })
488
477
 
489
- expect(i.outerHTML).to.deep.equal('<i class="some class">text</i>')
490
- expect(b.outerHTML).to.deep.equal('<b><i class="some class">text</i></b>')
491
- ```
478
+ expect(i.outerHTML).to.equal('<i class="some class">text</i>')
479
+ expect(b.outerHTML).to.equal('<b><i class="some class">text</i></b>')
492
480
 
493
- ```ts
494
- expect(h('span', 'text').outerHTML).to.deep.equal('<span>text</span>')
495
- expect(h('span', { $innerText: 'text' }).outerHTML).to.deep.equal('<span>text</span>')
496
- ```
481
+ expect(h('span', 'text').outerHTML).to.equal('<span>text</span>')
482
+ expect(h('span', { $innerText: 'text' }).outerHTML).to.equal('<span>text</span>')
497
483
 
498
- ```ts
499
- expect(h('span', '42').outerHTML).to.deep.equal('<span>42</span>')
500
- expect(h('span', 42).outerHTML).to.deep.equal('<span>42</span>')
501
- ```
484
+ expect(h('span', '42').outerHTML).to.equal('<span>42</span>')
485
+ expect(h('span', 42).outerHTML).to.equal('<span>42</span>')
502
486
 
503
- ```ts
504
487
  expect(h('div', { style: 'margin:0;padding:0' }).outerHTML)
505
- .to.deep.equal('<div style="margin:0;padding:0"></div>')
488
+ .to.equal('<div style="margin:0;padding:0"></div>')
506
489
  expect(h('div', { $style: 'margin:0;padding:0' }).outerHTML)
507
- .to.deep.equal('<div style="margin: 0px; padding: 0px;"></div>')
490
+ .to.equal('<div style="margin: 0px; padding: 0px;"></div>')
508
491
  expect(h('div', { $style: { margin: 0, padding: 0 } }).outerHTML)
509
- .to.deep.equal('<div style="margin: 0px; padding: 0px;"></div>')
510
- ```
492
+ .to.equal('<div style="margin: 0px; padding: 0px;"></div>')
511
493
 
512
- ```ts
513
494
  const input1 = h('input', { value: 42 })
514
495
  const input2 = h('input', { $value: '42' })
515
496
 
516
- expect(input1.value).to.deep.equal('42')
517
- expect(input2.value).to.deep.equal('42')
497
+ expect(input1.value).to.equal('42')
498
+ expect(input2.value).to.equal('42')
518
499
 
519
- expect(input1.outerHTML).to.deep.equal('<input value="42">')
520
- expect(input2.outerHTML).to.deep.equal('<input>')
500
+ expect(input1.outerHTML).to.equal('<input value="42">')
501
+ expect(input2.outerHTML).to.equal('<input>')
521
502
 
522
503
  const checkbox1 = h('input', { type: 'checkbox', checked: true })
523
504
  const checkbox2 = h('input', { type: 'checkbox', $checked: true })
@@ -525,11 +506,9 @@ const checkbox2 = h('input', { type: 'checkbox', $checked: true })
525
506
  expect(checkbox1.checked).to.be.true
526
507
  expect(checkbox2.checked).to.be.true
527
508
 
528
- expect(checkbox1.outerHTML).to.deep.equal('<input type="checkbox" checked="">')
529
- expect(checkbox2.outerHTML).to.deep.equal('<input type="checkbox">')
530
- ```
509
+ expect(checkbox1.outerHTML).to.equal('<input type="checkbox" checked="">')
510
+ expect(checkbox2.outerHTML).to.equal('<input type="checkbox">')
531
511
 
532
- ```ts
533
512
  const div = h('div')
534
513
 
535
514
  expect(div.key).to.be.undefined
@@ -618,6 +597,8 @@ The value to check.
618
597
 
619
598
  ```ts
620
599
  expect(is_array([])).to.be.true
600
+ expect(is_array(Object.create({ constructor: Array }))).to.be.false
601
+ expect(is_array(Object.create({ [Symbol.toStringTag]: Array.name }))).to.be.false
621
602
  ```
622
603
 
623
604
  ### is_finite_number
@@ -700,6 +681,7 @@ expect(is_record(new Number(42))).to.be.true
700
681
  expect(is_record(new String('42'))).to.be.true
701
682
 
702
683
  class Foo_Bar { }
684
+
703
685
  expect(is_record(new Foo_Bar())).to.be.true
704
686
  ```
705
687
 
@@ -810,15 +792,6 @@ const monokai: CRoot;
810
792
 
811
793
  A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolight_ts`](#nanolight_ts) tokenizer.
812
794
 
813
- Defines CSS custom properties for eight colors (`--black`, `--blue`, `--green`, `--grey`,
814
- `--purple`, `--red`, `--white`, `--yellow`) with both light and dark mode variants.
815
-
816
- #### Remarks
817
-
818
- - In light mode, `--black` is a light background and `--white` is dark text.
819
- - In dark mode (via `prefers-color-scheme: dark`), the palette inverts to a dark background with light text.
820
- - Includes base styles for `body`, `pre` and `code>span.*` (syntax highlighting).
821
-
822
795
  ### nanolight_ts
823
796
 
824
797
  ```ts
@@ -841,25 +814,25 @@ An array of [`HArgs1`](#HArgs1) elements suitable for rendering with [`h`](#h).
841
814
  const code_js = "const answer_to_life_the_universe_and_everything = { 42: 42 }['42'] /* 42 */"
842
815
 
843
816
  expect(nanolight_ts(code_js)).to.deep.equal([
844
- ['span', { class: 'red' }, 'const'],
817
+ ['span', { class: 'keyword-1' }, 'const'],
845
818
  ' ',
846
- ['span', { class: 'white' }, 'answer_to_life_the_universe_and_everything'],
819
+ ['span', { class: 'identifier-4' }, 'answer_to_life_the_universe_and_everything'],
847
820
  ' ',
848
- ['span', { class: 'red' }, '='],
821
+ ['span', { class: 'operator' }, '='],
849
822
  ' ',
850
- ['span', { class: 'grey' }, '{'],
823
+ ['span', { class: 'punctuation' }, '{'],
851
824
  ' ',
852
- ['span', { class: 'purple' }, '42'],
853
- ['span', { class: 'grey' }, ':'],
825
+ ['span', { class: 'number' }, '42'],
826
+ ['span', { class: 'operator' }, ':'],
854
827
  ' ',
855
- ['span', { class: 'purple' }, '42'],
828
+ ['span', { class: 'number' }, '42'],
856
829
  ' ',
857
- ['span', { class: 'grey' }, '}'],
858
- ['span', { class: 'grey' }, '['],
859
- ['span', { class: 'yellow' }, "'42'"],
860
- ['span', { class: 'grey' }, ']'],
830
+ ['span', { class: 'punctuation' }, '}'],
831
+ ['span', { class: 'punctuation' }, '['],
832
+ ['span', { class: 'string' }, "'42'"],
833
+ ['span', { class: 'punctuation' }, ']'],
861
834
  ' ',
862
- ['span', { class: 'grey' }, '/* 42 */']
835
+ ['span', { class: 'comment' }, '/* 42 */']
863
836
  ])
864
837
  ```
865
838
 
@@ -904,7 +877,7 @@ const expected = `
904
877
  FROM table_name
905
878
  WHERE column_name IN (b'1', NULL, NULL, 42, '42', '4''2', '1980-03-31 04:30:00')`
906
879
 
907
- expect(actual).to.deep.equal(expected)
880
+ expect(actual).to.equal(expected)
908
881
  ```
909
882
 
910
883
  ### new_noun_form
@@ -915,8 +888,8 @@ const new_noun_form: (locale: string, forms: Partial<Record<Intl.LDMLPluralRule,
915
888
 
916
889
  Creates a function that returns the appropriate noun form based on a numeric value using `Intl.PluralRules`.
917
890
 
918
- Different languages have different plural rules. The `Intl.PluralRules` API provides
919
- locale-aware plural category selection. Possible categories are:
891
+ Different languages have different plural rules. The `Intl.PluralRules` API provides locale-aware plural category selection.
892
+ Possible categories are:
920
893
 
921
894
  - `zero`: for zero items (used in some languages like Arabic, Latvian)
922
895
  - `one`: for singular (e.g., 1 item)
@@ -943,17 +916,17 @@ A function that takes a numeric value and returns the appropriate noun form.
943
916
  ```ts
944
917
  const auto = new_noun_form('pl', { one: 'auto', few: 'auta', other: 'aut' })
945
918
 
946
- expect(auto(0)).to.deep.equal('aut')
947
- expect(auto(1)).to.deep.equal('auto')
948
- expect(auto(17)).to.deep.equal('aut')
949
- expect(auto(42)).to.deep.equal('auta')
919
+ expect(auto(0)).to.equal('aut')
920
+ expect(auto(1)).to.equal('auto')
921
+ expect(auto(17)).to.equal('aut')
922
+ expect(auto(42)).to.equal('auta')
950
923
 
951
924
  const car = new_noun_form('en', { one: 'car', other: 'cars' })
952
925
 
953
- expect(car(0)).to.deep.equal('cars')
954
- expect(car(1)).to.deep.equal('car')
955
- expect(car(17)).to.deep.equal('cars')
956
- expect(car(42)).to.deep.equal('cars')
926
+ expect(car(0)).to.equal('cars')
927
+ expect(car(1)).to.equal('car')
928
+ expect(car(17)).to.equal('cars')
929
+ expect(car(42)).to.equal('cars')
957
930
  ```
958
931
 
959
932
  ### new_tokenizer
@@ -1011,9 +984,9 @@ A new object without the specified keys.
1011
984
  #### Usage Examples
1012
985
 
1013
986
  ```ts
1014
- const obj = { a: 42, b: '42', c: 17 }
987
+ const ref = { a: 42, b: '42', c: 17 }
1015
988
 
1016
- expect(omit(obj, ['c'])).to.deep.equal({ a: 42, b: '42' })
989
+ expect(omit(ref, ['c'])).to.deep.equal({ a: 42, b: '42' })
1017
990
  ```
1018
991
 
1019
992
  ### pick
@@ -1041,9 +1014,9 @@ A new object with only the specified keys.
1041
1014
  #### Usage Examples
1042
1015
 
1043
1016
  ```ts
1044
- const obj = { a: 42, b: '42', c: 17 }
1017
+ const ref = { a: 42, b: '42', c: 17 }
1045
1018
 
1046
- expect(pick(obj, ['a', 'b'])).to.deep.equal({ a: 42, b: '42' })
1019
+ expect(pick(ref, ['a', 'b'])).to.deep.equal({ a: 42, b: '42' })
1047
1020
  ```
1048
1021
 
1049
1022
  ### rwd
@@ -1134,8 +1107,7 @@ const s: {
1134
1107
  };
1135
1108
  ```
1136
1109
 
1137
- A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
1138
- for creating and modifying `SVGElement`s (see also [`h`](#h)).
1110
+ A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also [`h`](#h)).
1139
1111
 
1140
1112
  #### tag_or_node
1141
1113
 
@@ -1206,41 +1178,37 @@ for (let i = 1; i <= 22136; ++i) {
1206
1178
  const uuid = uuid_v1()
1207
1179
 
1208
1180
  if (i === 1) {
1209
- expect(uuid.split('-')[3]).to.deep.equal('8001')
1181
+ expect(uuid.split('-')[3]).to.equal('8001')
1210
1182
  }
1211
1183
 
1212
1184
  if (i === 4095) {
1213
- expect(uuid.split('-')[3]).to.deep.equal('8fff')
1185
+ expect(uuid.split('-')[3]).to.equal('8fff')
1214
1186
  }
1215
1187
 
1216
1188
  if (i === 4096) {
1217
- expect(uuid.split('-')[3]).to.deep.equal('9000')
1189
+ expect(uuid.split('-')[3]).to.equal('9000')
1218
1190
  }
1219
1191
 
1220
1192
  if (i === 9029) {
1221
- expect(uuid.split('-')[3]).to.deep.equal('a345')
1193
+ expect(uuid.split('-')[3]).to.equal('a345')
1222
1194
  }
1223
1195
 
1224
1196
  if (i === 13398) {
1225
- expect(uuid.split('-')[3]).to.deep.equal('b456')
1197
+ expect(uuid.split('-')[3]).to.equal('b456')
1226
1198
  }
1227
1199
 
1228
1200
  if (i === 16384) {
1229
- expect(uuid.split('-')[3]).to.deep.equal('8000')
1201
+ expect(uuid.split('-')[3]).to.equal('8000')
1230
1202
  }
1231
1203
 
1232
1204
  if (i === 17767) {
1233
- expect(uuid.split('-')[3]).to.deep.equal('8567')
1205
+ expect(uuid.split('-')[3]).to.equal('8567')
1234
1206
  }
1235
1207
  }
1236
- ```
1237
1208
 
1238
- ```ts
1239
- expect(uuid_v1(new Date(), '000123456789abc').split('-')[4]).to.deep.equal('123456789abc')
1240
- expect(uuid_v1(new Date(), '123456789').split('-')[4]).to.deep.equal('000123456789')
1241
- ```
1209
+ expect(uuid_v1(new Date(), '000123456789abc').split('-')[4]).to.equal('123456789abc')
1210
+ expect(uuid_v1(new Date(), '123456789').split('-')[4]).to.equal('000123456789')
1242
1211
 
1243
- ```ts
1244
1212
  expect(uuid_v1(new Date(323325000000)).startsWith('c1399400-9a71-11bd')).to.be.true
1245
1213
  ```
1246
1214
 
@@ -1258,14 +1226,14 @@ Accessing, assigning, or deleting any nested property on the returned proxy auto
1258
1226
  Intermediates of the last level in a get-only property chain are NOT auto-created.
1259
1227
  For example, `vivify(ref).one.two` will create `ref.one` as `{}`, but will NOT create `ref.one.two`.
1260
1228
  Only when a deeper access, assignment, or deletion occurs
1261
- (e.g. `vivify(ref).one.two[3]` or `vivify(ref).one.two.three = 42`) will `ref.one.two` be materialized.
1229
+ (e.g. `delete vivify(ref).one.two.three` or `vivify(ref).one.two.three = 4`) will `ref.one.two` be materialized.
1262
1230
 
1263
1231
  When traversal reaches a primitive value, no auto-creation happens;
1264
1232
  the primitive’s own property is returned instead
1265
1233
  (e.g. accessing `.toString.name` on a number yields `'toString'` without modifying the underlying structure).
1266
1234
 
1267
1235
  Deletion on a non-existing intermediate path will auto-create intermediates up to the parent of the deleted key
1268
- (e.g. `delete vivify(ref).a.b.c` will create `ref.a.b` as `{}` if it does not exist).
1236
+ (e.g. `delete vivify(ref).one.two.three` will create `ref.one.two` as `{}` if it does not exist).
1269
1237
 
1270
1238
  #### ref
1271
1239
 
@@ -1280,33 +1248,49 @@ A proxy that auto-creates nested objects/arrays on property access.
1280
1248
  ```ts
1281
1249
  const ref: any = {}
1282
1250
 
1283
- vivify(ref).one.two[1][2] = 42
1284
-
1285
- expect(ref).to.deep.equal({ one: { two: [undefined, [undefined, undefined, 42]] } })
1286
-
1287
- vivify(ref).one.two[1][3] = 42
1251
+ vivify(ref).one.two[3][4]
1288
1252
 
1289
- expect(ref).to.deep.equal({ one: { two: [undefined, [undefined, undefined, 42, 42]] } })
1253
+ expect(ref).to.deep.equal({
1254
+ one: {
1255
+ two: [undefined, undefined, undefined, []]
1256
+ }
1257
+ })
1290
1258
 
1291
- vivify(ref).one.two[1] = 42
1259
+ vivify(ref).one.two[3][4] = 5
1292
1260
 
1293
- expect(ref).to.deep.equal({ one: { two: [undefined, 42] } })
1261
+ expect(ref).to.deep.equal({
1262
+ one: {
1263
+ two: [undefined, undefined, undefined, [undefined, undefined, undefined, undefined, 5]]
1264
+ }
1265
+ })
1294
1266
 
1295
- vivify(ref).one.two[3][0] = 42
1267
+ vivify(ref).one.two[3].length = 1
1296
1268
 
1297
- expect(ref).to.deep.equal({ one: { two: [undefined, 42, undefined, [42]] } })
1269
+ expect(ref).to.deep.equal({
1270
+ one: {
1271
+ two: [undefined, undefined, undefined, [undefined]]
1272
+ }
1273
+ })
1298
1274
 
1299
- vivify(ref).one.two[3].length = 2
1275
+ vivify(ref).one.two[3] = 4
1300
1276
 
1301
- expect(ref).to.deep.equal({ one: { two: [undefined, 42, undefined, [42, undefined]] } })
1277
+ expect(ref).to.deep.equal({
1278
+ one: {
1279
+ two: [undefined, undefined, undefined, 4]
1280
+ }
1281
+ })
1302
1282
 
1303
- expect(vivify(ref).one.two.length).to.deep.equal(4)
1283
+ expect(vivify(ref).one.two.length).to.equal(4)
1304
1284
 
1305
- expect(ref).to.deep.equal({ one: { two: [undefined, 42, undefined, [42, undefined]] } })
1285
+ expect(ref).to.deep.equal({
1286
+ one: {
1287
+ two: [undefined, undefined, undefined, 4]
1288
+ }
1289
+ })
1306
1290
 
1307
- vivify(ref).one.two = 12
1291
+ vivify(ref).one.two = 3
1308
1292
 
1309
- expect(ref).to.deep.equal({ one: { two: 12 } })
1293
+ expect(ref).to.deep.equal({ one: { two: 3 } })
1310
1294
 
1311
1295
  vivify(ref).one.two = undefined
1312
1296
 
@@ -1334,13 +1318,13 @@ vivify(ref).one.two[3]
1334
1318
 
1335
1319
  expect(ref).to.deep.equal({ one: { two: { three: {} } } })
1336
1320
 
1337
- vivify(ref).one.two.three.four = 42
1321
+ vivify(ref).one.two.three.four = 5
1338
1322
 
1339
- expect(ref).to.deep.equal({ one: { two: { three: { four: 42 } } } })
1323
+ expect(ref).to.deep.equal({ one: { two: { three: { four: 5 } } } })
1340
1324
 
1341
- expect(vivify(ref).one.two.three.four.toString.name).to.deep.equal('toString')
1325
+ expect(vivify(ref).one.two.three.four.toString.name).to.equal('toString')
1342
1326
 
1343
- expect(ref).to.deep.equal({ one: { two: { three: { four: 42 } } } })
1327
+ expect(ref).to.deep.equal({ one: { two: { three: { four: 5 } } } })
1344
1328
  ```
1345
1329
 
1346
1330
  ## License