@jackens/nnn 2025.12.23 → 2026.2.6

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 +60 -54
  2. package/nnn.js +75 -65
  3. package/package.json +4 -2
  4. package/readme.md +120 -86
package/nnn.d.ts CHANGED
@@ -126,7 +126,7 @@ export declare const new_escape: (escape_map: EscapeMap) => (template: TemplateS
126
126
  * The root DOM node to process. All descendant text nodes are corrected recursively,
127
127
  * except those inside `IFRAME`, `NOSCRIPT`, `PRE`, `SCRIPT`, `STYLE`, or `TEXTAREA` elements.
128
128
  */
129
- export declare const fix_typography: (node: Node) => void;
129
+ export declare const fix_pl_typography: (node: Node) => void;
130
130
  /**
131
131
  * Single argument type for the {@link h} and {@link s} helpers.
132
132
  */
@@ -307,55 +307,32 @@ export declare const is_string: (arg: unknown) => arg is string;
307
307
  */
308
308
  export declare const js_on_parse: (handlers: Record<PropertyKey, Function>, text: string) => any;
309
309
  /**
310
- * A Monokai-inspired color scheme for use with the {@link c} helper and {@link nanolight_ts} tokenizer.
311
- *
312
- * Defines CSS custom properties for eight colors (`--black`, `--blue`, `--green`, `--grey`,
313
- * `--purple`, `--red`, `--white`, `--yellow`) with both light and dark mode variants.
314
- *
315
- * @remarks
316
- *
317
- * - In light mode, `--black` is a light background and `--white` is dark text.
318
- * - In dark mode (via `prefers-color-scheme: dark`), the palette inverts to a dark background with light text.
319
- * - Includes base styles for `body`, `pre` and `code>span.*` (syntax highlighting).
320
- */
321
- export declare const monokai: CRoot;
322
- /**
323
- * A TypeScript/JavaScript syntax highlighting tokenizer built using {@link new_tokenizer}.
324
- *
325
- * @param code
326
- *
327
- * The source code string to tokenize.
328
- *
329
- * @returns
330
- *
331
- * An array of {@link HArgs1} elements suitable for rendering with {@link h}.
332
- */
333
- export declare const nanolight_ts: (code: string) => HArgs1[];
334
- /**
335
- * Creates a function that returns the appropriate Polish noun form based on a numeric value.
336
- *
337
- * Polish has three plural forms depending on the number:
338
- * - Singular: used for exactly 1.
339
- * - “Plural 2-4”: used for 2, 3, 4, 22, 23, 24, etc. (but not 12, 13, 14).
340
- * - “Plural 5+”: used for 0, 5–21, 25–31, etc.
310
+ * Creates a function that returns the appropriate noun form based on a numeric value using `Intl.PluralRules`.
341
311
  *
342
- * @param singular
312
+ * Different languages have different plural rules. The `Intl.PluralRules` API provides
313
+ * locale-aware plural category selection. Possible categories are:
343
314
  *
344
- * The singular form (e.g., “auto” for “car”).
315
+ * - `zero` - for zero items (used in some languages like Arabic, Latvian)
316
+ * - `one` - for singular (e.g., 1 item)
317
+ * - `two` - for dual (used in some languages like Arabic, Hebrew)
318
+ * - `few` - for small plurals (e.g., 2-4 in Polish)
319
+ * - `many` - for larger plurals (e.g., 5-21 in Polish)
320
+ * - `other` - fallback category (used by all languages)
345
321
  *
346
- * @param plural_2
322
+ * @param locale
347
323
  *
348
- * The form for 2, 3, 4 (e.g., “auta”).
324
+ * A BCP 47 language tag (e.g., "pl", "en").
349
325
  *
350
- * @param plural_5
326
+ * @param forms
351
327
  *
352
- * The form for 5+ (e.g., “aut”).
328
+ * An object mapping plural categories to noun forms. Not all categories need to be provided;
329
+ * if a category is missing, the function falls back to `other`, then to an empty string.
353
330
  *
354
331
  * @returns
355
332
  *
356
333
  * A function that takes a numeric value and returns the appropriate noun form.
357
334
  */
358
- export declare const new_noun_form: (singular: string, plural_2: string, plural_5: string) => (value: number) => string;
335
+ export declare const new_noun_form: (locale: string, forms: Partial<Record<Intl.LDMLPluralRule, string>>) => (value: number) => string;
359
336
  /**
360
337
  * Creates a new object containing only the specified keys from the source object.
361
338
  *
@@ -392,21 +369,6 @@ export declare const pick: <T, K extends keyof T>(ref: T, keys: K[]) => Pick<T,
392
369
  * A new object without the specified keys.
393
370
  */
394
371
  export declare const omit: <T, K extends keyof T>(ref: T, keys: unknown[]) => Omit<T, K>;
395
- /**
396
- * A Proxy-based helper for auto-vivification of nested object structures.
397
- *
398
- * Accessing any property on the returned proxy automatically creates an empty object if the property does not exist,
399
- * allowing deep assignments without explicit null checks.
400
- *
401
- * @param ref
402
- *
403
- * The root object to wrap.
404
- *
405
- * @returns
406
- *
407
- * A proxy that auto-creates nested objects on property access.
408
- */
409
- export declare const pro: (ref: unknown) => any;
410
372
  /**
411
373
  * A helper for building simple tokenizers (see also {@link nanolight_ts}).
412
374
  *
@@ -433,6 +395,31 @@ export declare const pro: (ref: unknown) => any;
433
395
  * 3. Among matches of the same position and length, the one defined earlier wins.
434
396
  */
435
397
  export declare const new_tokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
398
+ /**
399
+ * A TypeScript/JavaScript syntax highlighting tokenizer built using {@link new_tokenizer}.
400
+ *
401
+ * @param code
402
+ *
403
+ * The source code string to tokenize.
404
+ *
405
+ * @returns
406
+ *
407
+ * An array of {@link HArgs1} elements suitable for rendering with {@link h}.
408
+ */
409
+ export declare const nanolight_ts: (code: string) => HArgs1[];
410
+ /**
411
+ * A Monokai-inspired color scheme for use with the {@link c} helper and {@link nanolight_ts} tokenizer.
412
+ *
413
+ * Defines CSS custom properties for eight colors (`--black`, `--blue`, `--green`, `--grey`,
414
+ * `--purple`, `--red`, `--white`, `--yellow`) with both light and dark mode variants.
415
+ *
416
+ * @remarks
417
+ *
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).
421
+ */
422
+ export declare const monokai: CRoot;
436
423
  /**
437
424
  * Generates a UUID v1 (time-based) identifier.
438
425
  *
@@ -450,3 +437,22 @@ export declare const new_tokenizer: <M, T>(decorator: (chunk: string, metadata?:
450
437
  * A UUID v1 `string` in the standard format `xxxxxxxx-xxxx-1xxx-xxxx-xxxxxxxxxxxx`.
451
438
  */
452
439
  export declare const uuid_v1: (date?: Date, node?: string) => string;
440
+ /**
441
+ * A Proxy-based helper for auto-vivification of nested object structures.
442
+ *
443
+ * Accessing any property on the returned proxy automatically creates an empty object
444
+ * (or array for numeric keys) if the property does not exist,
445
+ * allowing deep assignments without explicit null checks.
446
+ *
447
+ * When a numeric key (like `[42]`) is accessed, the parent structure is
448
+ * automatically converted to an array if it isn’t one already.
449
+ *
450
+ * @param ref
451
+ *
452
+ * The root object to wrap.
453
+ *
454
+ * @returns
455
+ *
456
+ * A proxy that auto-creates nested objects/arrays on property access.
457
+ */
458
+ export declare const vivify: (ref: unknown) => any;
package/nnn.js CHANGED
@@ -5,12 +5,26 @@ var is_number = (arg) => typeof arg === "number";
5
5
  var is_record = (arg) => typeof arg === "object" && arg != null && !is_array(arg);
6
6
  var is_string = (arg) => typeof arg === "string";
7
7
 
8
- // src/nnn/pro.ts
9
- var pro = (ref) => new Proxy(ref, {
8
+ // src/nnn/vivify.ts
9
+ var ARRAY_INDEX_REGEXP = /^(0|[1-9]\d*)$/;
10
+ var should_convert_target_to_array = (target, key, parent) => parent != null && typeof key === "string" && ARRAY_INDEX_REGEXP.test(key) && !is_array(target);
11
+ var _vivify = (ref, parent, parentKey) => new Proxy(ref, {
10
12
  get(target, key) {
11
- return pro(target[key] ??= {});
13
+ if (should_convert_target_to_array(target, key, parent)) {
14
+ target = parent[parentKey] = [];
15
+ }
16
+ target[key] ??= {};
17
+ return _vivify(target[key], target, key);
18
+ },
19
+ set(target, key, value) {
20
+ if (should_convert_target_to_array(target, key, parent)) {
21
+ target = parent[parentKey] = [];
22
+ }
23
+ target[key] = value;
24
+ return true;
12
25
  }
13
26
  });
27
+ var vivify = (ref) => _vivify(ref);
14
28
 
15
29
  // src/nnn/c.ts
16
30
  var _c = (node, prefix, result, splitter) => {
@@ -65,7 +79,7 @@ var c = (root, splitter = "$$") => {
65
79
  return chunks.join("");
66
80
  };
67
81
  var rwd = (root, selector, cell_width_px, cell_height_px, ...specs) => {
68
- const main = pro(root)[selector];
82
+ const main = vivify(root)[selector];
69
83
  main.boxSizing = "border-box";
70
84
  main.display = "block";
71
85
  main.float = "left";
@@ -73,7 +87,7 @@ var rwd = (root, selector, cell_width_px, cell_height_px, ...specs) => {
73
87
  main.height = `${cell_height_px}px`;
74
88
  specs.sort(([a], [b]) => a - b);
75
89
  for (let [max_width, width, height] of specs) {
76
- const node = max_width === 1 ? main : pro(root)[`@media(min-width:${cell_width_px * max_width}px)`][selector];
90
+ const node = max_width === 1 ? main : vivify(root)[`@media(min-width:${cell_width_px * max_width}px)`][selector];
77
91
  width ??= 1;
78
92
  height ??= 1;
79
93
  let gcd = 100 * width;
@@ -158,9 +172,9 @@ var h = /* @__PURE__ */ _h();
158
172
  var s = /* @__PURE__ */ _h("http://www.w3.org/2000/svg");
159
173
  var svg_use = (id, ...args) => s("svg", ["use", { "xlink:href": "#" + id }], ...args);
160
174
 
161
- // src/nnn/fix_typography.ts
175
+ // src/nnn/fix_pl_typography.ts
162
176
  var TAGS_TO_SKIP = ["IFRAME", "NOSCRIPT", "PRE", "SCRIPT", "STYLE", "TEXTAREA"];
163
- var fix_typography = (node) => {
177
+ var fix_pl_typography = (node) => {
164
178
  const queue = [node];
165
179
  while (queue.length > 0) {
166
180
  const node_0 = queue.shift();
@@ -209,53 +223,14 @@ var js_on_parse = (handlers, text) => JSON.parse(text, (key, value) => {
209
223
  }
210
224
  return value;
211
225
  });
212
- // src/nnn/monokai.ts
213
- var monokai = {
214
- ":root": {
215
- __black: "$faf4f2",
216
- __blue: "#1c8ca8",
217
- __green: "#269d69",
218
- __grey: "#918c8e",
219
- __purple: "#7058be",
220
- __red: "#e14775",
221
- __white: "#29242a",
222
- __yellow: "#cc7a0a"
223
- },
224
- body$$monokai: {
225
- backgroundColor: "var(--black)",
226
- color: "var(--white)"
227
- },
228
- "code>span.": {
229
- black: { color: "var(--black)" },
230
- blue: { color: "var(--blue)" },
231
- green: { color: "var(--green)" },
232
- grey: { color: "var(--grey)" },
233
- purple: { color: "var(--purple)" },
234
- red: { color: "var(--red)" },
235
- white: { color: "var(--white)" },
236
- yellow: { color: "var(--yellow)" }
237
- },
238
- pre: {
239
- margin: 0,
240
- overflow: "visible",
241
- " code": {
242
- fontFamily: '"Source Code Pro"',
243
- padding: 0
244
- }
245
- },
246
- "@media only screen and (prefers-color-scheme: dark)": {
247
- ":root": {
248
- __black: "#2d2a2e",
249
- __blue: "#66d9ef",
250
- __green: "#a9dc76",
251
- __grey: "#727072",
252
- __purple: "#ae81ff",
253
- __red: "#ff6188",
254
- __white: "#fcfcfa",
255
- __yellow: "#ffd866"
256
- }
257
- }
226
+ // src/nnn/new_noun_form.ts
227
+ var new_noun_form = (locale, forms) => {
228
+ const plural_rules = new Intl.PluralRules(locale);
229
+ return (value) => forms[plural_rules.select(value)] ?? forms.other ?? "";
258
230
  };
231
+ // src/nnn/omit_pick.ts
232
+ var pick = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => keys.includes(key)));
233
+ var omit = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => !keys.includes(key)));
259
234
  // src/nnn/tokenizer.ts
260
235
  var new_tokenizer = (decorator, ...specs) => (code) => {
261
236
  const result = [];
@@ -294,8 +269,6 @@ var new_tokenizer = (decorator, ...specs) => (code) => {
294
269
  }
295
270
  return result;
296
271
  };
297
-
298
- // src/nnn/nanolight.ts
299
272
  var BLUE = "blue";
300
273
  var GREEN = "green";
301
274
  var GREY = "grey";
@@ -304,15 +277,52 @@ var RED = "red";
304
277
  var WHITE = "white";
305
278
  var YELLOW = "yellow";
306
279
  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]);
307
- // src/nnn/noun_form.ts
308
- var new_noun_form = (singular, plural_2, plural_5) => (value) => {
309
- const abs_value = Math.abs(value);
310
- const abs_value_mod_10 = abs_value % 10;
311
- return value === 1 ? singular : (abs_value_mod_10 === 2 || abs_value_mod_10 === 3 || abs_value_mod_10 === 4) && abs_value !== 12 && abs_value !== 13 && abs_value !== 14 ? plural_2 : plural_5;
280
+ var monokai = {
281
+ ":root": {
282
+ __black: "$faf4f2",
283
+ __blue: "#1c8ca8",
284
+ __green: "#269d69",
285
+ __grey: "#918c8e",
286
+ __purple: "#7058be",
287
+ __red: "#e14775",
288
+ __white: "#29242a",
289
+ __yellow: "#cc7a0a"
290
+ },
291
+ body$$monokai: {
292
+ backgroundColor: "var(--black)",
293
+ color: "var(--white)"
294
+ },
295
+ "code>span.": {
296
+ black: { color: "var(--black)" },
297
+ blue: { color: "var(--blue)" },
298
+ green: { color: "var(--green)" },
299
+ grey: { color: "var(--grey)" },
300
+ purple: { color: "var(--purple)" },
301
+ red: { color: "var(--red)" },
302
+ white: { color: "var(--white)" },
303
+ yellow: { color: "var(--yellow)" }
304
+ },
305
+ pre: {
306
+ margin: 0,
307
+ overflow: "visible",
308
+ " code": {
309
+ fontFamily: '"Source Code Pro"',
310
+ padding: 0
311
+ }
312
+ },
313
+ "@media only screen and (prefers-color-scheme: dark)": {
314
+ ":root": {
315
+ __black: "#2d2a2e",
316
+ __blue: "#66d9ef",
317
+ __green: "#a9dc76",
318
+ __grey: "#727072",
319
+ __purple: "#ae81ff",
320
+ __red: "#ff6188",
321
+ __white: "#fcfcfa",
322
+ __yellow: "#ffd866"
323
+ }
324
+ }
312
325
  };
313
- // src/nnn/omit_pick.ts
314
- var pick = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => keys.includes(key)));
315
- var omit = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => !keys.includes(key)));
316
326
  // src/nnn/uuid_v1.ts
317
327
  var ZEROS = /* @__PURE__ */ "0".repeat(16);
318
328
  var counter = 0;
@@ -322,11 +332,11 @@ var uuid_v1 = (date = new Date, node = Math.random().toString(16).slice(2)) => {
322
332
  return time.slice(-8).concat("-", time.slice(-12, -8), -1, time.slice(-15, -12), "-", (8 | counter >> 12).toString(16), (ZEROS + (counter & 4095).toString(16)).slice(-3), "-", (ZEROS + node).slice(-12));
323
333
  };
324
334
  export {
335
+ vivify,
325
336
  uuid_v1,
326
337
  svg_use,
327
338
  s,
328
339
  rwd,
329
- pro,
330
340
  pick,
331
341
  omit,
332
342
  new_tokenizer,
@@ -342,7 +352,7 @@ export {
342
352
  is_array,
343
353
  has_own,
344
354
  h,
345
- fix_typography,
355
+ fix_pl_typography,
346
356
  escape_values,
347
357
  csv_parse,
348
358
  c
package/package.json CHANGED
@@ -2,6 +2,7 @@
2
2
  "author": "Jackens",
3
3
  "description": "Jackens’ JavaScript helpers.",
4
4
  "keywords": [
5
+ "autovivify",
5
6
  "c",
6
7
  "CSS-in-JS",
7
8
  "CSV parse",
@@ -34,12 +35,13 @@
34
35
  "tokenizer",
35
36
  "typography",
36
37
  "uuid",
37
- "uuidv1"
38
+ "uuidv1",
39
+ "vivify"
38
40
  ],
39
41
  "license": "MIT",
40
42
  "main": "nnn.js",
41
43
  "name": "@jackens/nnn",
42
44
  "type": "module",
43
45
  "types": "nnn.d.ts",
44
- "version": "2025.12.23"
46
+ "version": "2026.2.6"
45
47
  }
package/readme.md CHANGED
@@ -5,25 +5,43 @@ A collection of Jackens’ JavaScript helper utilities.
5
5
  ## Installation
6
6
 
7
7
  ```sh
8
- bun i @jackens/nnn
9
- ```
10
-
11
- or
12
-
13
- ```sh
14
- npm i @jackens/nnn
8
+ bun i @jackens/nnn # npm i @jackens/nnn
15
9
  ```
16
10
 
17
11
  ## Usage
18
12
 
19
13
  ```js
20
- import { «something» } from '@jackens/nnn'
21
- ```
22
-
23
- or
24
-
25
- ```js
26
- import { «something» } from './node_modules/@jackens/nnn/nnn.js'
14
+ import {
15
+ CNode,
16
+ CRoot,
17
+ EscapeMap,
18
+ HArgs,
19
+ HArgs1,
20
+ c,
21
+ csv_parse,
22
+ escape_values,
23
+ fix_pl_typography,
24
+ h,
25
+ has_own,
26
+ is_array,
27
+ is_finite_number,
28
+ is_number,
29
+ is_record,
30
+ is_string,
31
+ js_on_parse,
32
+ monokai,
33
+ nanolight_ts,
34
+ new_escape,
35
+ new_noun_form,
36
+ new_tokenizer,
37
+ omit,
38
+ pick,
39
+ rwd,
40
+ s,
41
+ svg_use,
42
+ uuid_v1,
43
+ vivify
44
+ } from '@jackens/nnn' // './node_modules/@jackens/nnn/nnn.js'
27
45
  ```
28
46
 
29
47
  ## Exports
@@ -36,8 +54,9 @@ import { «something» } from './node_modules/@jackens/nnn/nnn.js'
36
54
  - [`c`](#c): A minimal CSS-in-JS helper that converts a JavaScript object hierarchy into a CSS string.
37
55
  - [`csv_parse`](#csv_parse): Parses a CSV string into a two-dimensional array of strings.
38
56
  - [`escape_values`](#escape_values): Escapes an array of values using the provided escape map.
39
- - [`fix_typography`](#fix_typography): Applies Polish-specific typographic corrections to a DOM subtree.
57
+ - [`fix_pl_typography`](#fix_pl_typography): Applies Polish-specific typographic corrections to a DOM subtree.
40
58
  - [`h`](#h): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
59
+ for creating and modifying `HTMLElement`s (see also [`s`](#s)).
41
60
  - [`has_own`](#has_own): Checks whether an object has the specified key as its own property.
42
61
  - [`is_array`](#is_array): Checks whether the argument is an array.
43
62
  - [`is_finite_number`](#is_finite_number): Checks whether the argument is a finite number (excludes `±Infinity` and `NaN`).
@@ -48,15 +67,16 @@ import { «something» } from './node_modules/@jackens/nnn/nnn.js'
48
67
  - [`monokai`](#monokai): A Monokai-inspired color scheme for use with the [`c`](#c) helper and [`nanolight_ts`](#nanolight_ts) tokenizer.
49
68
  - [`nanolight_ts`](#nanolight_ts): A TypeScript/JavaScript syntax highlighting tokenizer built using [`new_tokenizer`](#new_tokenizer).
50
69
  - [`new_escape`](#new_escape): Creates a tag function for escaping interpolated values in template literals.
51
- - [`new_noun_form`](#new_noun_form): Creates a function that returns the appropriate Polish noun form based on a numeric value.
70
+ - [`new_noun_form`](#new_noun_form): Creates a function that returns the appropriate noun form based on a numeric value using `Intl.PluralRules`.
52
71
  - [`new_tokenizer`](#new_tokenizer): A helper for building simple tokenizers (see also [`nanolight_ts`](#nanolight_ts)).
53
72
  - [`omit`](#omit): Creates a new object excluding the specified keys from the source object.
54
73
  - [`pick`](#pick): Creates a new object containing only the specified keys from the source object.
55
- - [`pro`](#pro): A Proxy-based helper for auto-vivification of nested object structures.
56
74
  - [`rwd`](#rwd): A responsive web design helper that generates CSS rules for a grid-like layout.
57
75
  - [`s`](#s): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper
76
+ for creating and modifying `SVGElement`s (see also [`h`](#h)).
58
77
  - [`svg_use`](#svg_use): Shorthand for creating an SVG element with a `<use>` child referencing an icon by ID.
59
78
  - [`uuid_v1`](#uuid_v1): Generates a UUID v1 (time-based) identifier.
79
+ - [`vivify`](#vivify): A Proxy-based helper for auto-vivification of nested object structures.
60
80
 
61
81
  ### CNode
62
82
 
@@ -389,10 +409,10 @@ The array of values to escape.
389
409
 
390
410
  An array of escaped strings.
391
411
 
392
- ### fix_typography
412
+ ### fix_pl_typography
393
413
 
394
414
  ```ts
395
- const fix_typography: (node: Node) => void;
415
+ const fix_pl_typography: (node: Node) => void;
396
416
  ```
397
417
 
398
418
  Applies Polish-specific typographic corrections to a DOM subtree.
@@ -411,7 +431,7 @@ except those inside `IFRAME`, `NOSCRIPT`, `PRE`, `SCRIPT`, `STYLE`, or `TEXTAREA
411
431
  ```ts
412
432
  const p = h('p', 'Pchnąć w tę łódź jeża lub ośm skrzyń fig (zob. https://pl.wikipedia.org/wiki/Pangram).')
413
433
 
414
- fix_typography(p)
434
+ fix_pl_typography(p)
415
435
 
416
436
  expect(p.innerHTML).to.deep.equal(
417
437
  'Pchnąć <span style="white-space:nowrap">w </span>tę łódź jeża lub ośm skrzyń fig ' +
@@ -877,12 +897,12 @@ const sql = new_escape(escape_map)
877
897
  const actual = sql`
878
898
  SELECT *
879
899
  FROM table_name
880
- WHERE column_name IN (${[true, null, undefined, 42, '42', "4'2", /42/, new Date(323325000000)]})`
900
+ WHERE column_name IN (${[true, null, undefined, 42, '42', "4'2", new Date(323325000000)]})`
881
901
 
882
902
  const expected = `
883
903
  SELECT *
884
904
  FROM table_name
885
- WHERE column_name IN (b'1', NULL, NULL, 42, '42', '4''2', NULL, '1980-03-31 04:30:00')`
905
+ WHERE column_name IN (b'1', NULL, NULL, 42, '42', '4''2', '1980-03-31 04:30:00')`
886
906
 
887
907
  expect(actual).to.deep.equal(expected)
888
908
  ```
@@ -890,27 +910,29 @@ expect(actual).to.deep.equal(expected)
890
910
  ### new_noun_form
891
911
 
892
912
  ```ts
893
- const new_noun_form: (singular: string, plural_2: string, plural_5: string) => (value: number) => string;
913
+ const new_noun_form: (locale: string, forms: Partial<Record<Intl.LDMLPluralRule, string>>) => (value: number) => string;
894
914
  ```
895
915
 
896
- Creates a function that returns the appropriate Polish noun form based on a numeric value.
897
-
898
- Polish has three plural forms depending on the number:
899
- - Singular: used for exactly 1.
900
- - “Plural 2-4”: used for 2, 3, 4, 22, 23, 24, etc. (but not 12, 13, 14).
901
- - “Plural 5+”: used for 0, 5–21, 25–31, etc.
916
+ Creates a function that returns the appropriate noun form based on a numeric value using `Intl.PluralRules`.
902
917
 
903
- #### singular
918
+ Different languages have different plural rules. The `Intl.PluralRules` API provides
919
+ locale-aware plural category selection. Possible categories are:
904
920
 
905
- The singular form (e.g., “auto” for “car”).
921
+ - `zero` - for zero items (used in some languages like Arabic, Latvian)
922
+ - `one` - for singular (e.g., 1 item)
923
+ - `two` - for dual (used in some languages like Arabic, Hebrew)
924
+ - `few` - for small plurals (e.g., 2-4 in Polish)
925
+ - `many` - for larger plurals (e.g., 5-21 in Polish)
926
+ - `other` - fallback category (used by all languages)
906
927
 
907
- #### plural_2
928
+ #### locale
908
929
 
909
- The form for 2, 3, 4 (e.g., “auta”).
930
+ A BCP 47 language tag (e.g., "pl", "en").
910
931
 
911
- #### plural_5
932
+ #### forms
912
933
 
913
- The form for 5+ (e.g., “aut”).
934
+ An object mapping plural categories to noun forms. Not all categories need to be provided;
935
+ if a category is missing, the function falls back to `other`, then to an empty string.
914
936
 
915
937
  #### Returns
916
938
 
@@ -919,14 +941,14 @@ A function that takes a numeric value and returns the appropriate noun form.
919
941
  #### Usage Examples
920
942
 
921
943
  ```ts
922
- const auto = new_noun_form('auto', 'auta', 'aut')
944
+ const auto = new_noun_form('pl', { one: 'auto', few: 'auta', other: 'aut' })
923
945
 
924
946
  expect(auto(0)).to.deep.equal('aut')
925
947
  expect(auto(1)).to.deep.equal('auto')
926
948
  expect(auto(17)).to.deep.equal('aut')
927
949
  expect(auto(42)).to.deep.equal('auta')
928
950
 
929
- const car = new_noun_form('car', 'cars', 'cars')
951
+ const car = new_noun_form('en', { one: 'car', other: 'cars' })
930
952
 
931
953
  expect(car(0)).to.deep.equal('cars')
932
954
  expect(car(1)).to.deep.equal('car')
@@ -1024,55 +1046,6 @@ const obj = { a: 42, b: '42', c: 17 }
1024
1046
  expect(pick(obj, ['a', 'b'])).to.deep.equal({ a: 42, b: '42' })
1025
1047
  ```
1026
1048
 
1027
- ### pro
1028
-
1029
- ```ts
1030
- const pro: (ref: unknown) => any;
1031
- ```
1032
-
1033
- A Proxy-based helper for auto-vivification of nested object structures.
1034
-
1035
- Accessing any property on the returned proxy automatically creates an empty object if the property does not exist,
1036
- allowing deep assignments without explicit null checks.
1037
-
1038
- #### ref
1039
-
1040
- The root object to wrap.
1041
-
1042
- #### Returns
1043
-
1044
- A proxy that auto-creates nested objects on property access.
1045
-
1046
- #### Usage Examples
1047
-
1048
- ```ts
1049
- const ref = {}
1050
-
1051
- pro(ref).one.two[3][4] = 1234
1052
-
1053
- expect(ref).to.deep.equal({ one: { two: { 3: { 4: 1234 } } } })
1054
-
1055
- pro(ref).one.two.tree = 123
1056
-
1057
- expect(ref).to.deep.equal({ one: { two: { 3: { 4: 1234 }, tree: 123 } } })
1058
-
1059
- pro(ref).one.two = undefined
1060
-
1061
- expect(ref).to.deep.equal({ one: { two: undefined } })
1062
-
1063
- delete pro(ref).one.two
1064
-
1065
- expect(ref).to.deep.equal({ one: {} })
1066
-
1067
- pro(ref).one.two.three.four
1068
-
1069
- expect(ref).to.deep.equal({ one: { two: { three: { four: {} } } } })
1070
-
1071
- pro(ref).one.two.three.four = 1234
1072
-
1073
- expect(ref).to.deep.equal({ one: { two: { three: { four: 1234 } } } })
1074
- ```
1075
-
1076
1049
  ### rwd
1077
1050
 
1078
1051
  ```ts
@@ -1271,6 +1244,67 @@ expect(uuid_v1(new Date(), '123456789').split('-')[4]).to.deep.equal('0001234567
1271
1244
  expect(uuid_v1(new Date(323325000000)).startsWith('c1399400-9a71-11bd')).to.be.true
1272
1245
  ```
1273
1246
 
1247
+ ### vivify
1248
+
1249
+ ```ts
1250
+ const vivify: (ref: unknown) => any;
1251
+ ```
1252
+
1253
+ A Proxy-based helper for auto-vivification of nested object structures.
1254
+
1255
+ Accessing any property on the returned proxy automatically creates an empty object
1256
+ (or array for numeric keys) if the property does not exist,
1257
+ allowing deep assignments without explicit null checks.
1258
+
1259
+ When a numeric key (like `[42]`) is accessed, the parent structure is
1260
+ automatically converted to an array if it isn’t one already.
1261
+
1262
+ #### ref
1263
+
1264
+ The root object to wrap.
1265
+
1266
+ #### Returns
1267
+
1268
+ A proxy that auto-creates nested objects/arrays on property access.
1269
+
1270
+ #### Usage Examples
1271
+
1272
+ ```ts
1273
+ const ref: any = {}
1274
+
1275
+ vivify(ref).one.two[3][4] = 1234
1276
+
1277
+ expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234]] } })
1278
+
1279
+ vivify(ref).one.two[3][5] = 12345
1280
+
1281
+ expect(ref).to.deep.equal({ one: { two: [, , , [, , , , 1234, 12345]] } })
1282
+
1283
+ vivify(ref).one.two[1] = 'one'
1284
+
1285
+ expect(ref).to.deep.equal({ one: { two: [, 'one', , [, , , , 1234, 12345]] } })
1286
+
1287
+ vivify(ref).one.two = 'tree'
1288
+
1289
+ expect(ref).to.deep.equal({ one: { two: 'tree' } })
1290
+
1291
+ vivify(ref).one.two = undefined
1292
+
1293
+ expect(ref).to.deep.equal({ one: { two: undefined } })
1294
+
1295
+ delete vivify(ref).one.two
1296
+
1297
+ expect(ref).to.deep.equal({ one: {} })
1298
+
1299
+ vivify(ref).one.two.three.four
1300
+
1301
+ expect(ref).to.deep.equal({ one: { two: { three: { four: {} } } } })
1302
+
1303
+ vivify(ref).one.two.three.four = 1234
1304
+
1305
+ expect(ref).to.deep.equal({ one: { two: { three: { four: 1234 } } } })
1306
+ ```
1307
+
1274
1308
  ## License
1275
1309
 
1276
1310
  The MIT License (MIT)