@jackens/nnn 2025.11.11 → 2025.12.8

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 +73 -65
  2. package/nnn.js +307 -134
  3. package/package.json +1 -1
  4. package/readme.md +53 -42
package/nnn.d.ts CHANGED
@@ -1,42 +1,3 @@
1
- /** Argument type for the `c` helper. */
2
- export type CNode = {
3
- [attribute_or_selector: string]: string | number | CNode | undefined;
4
- };
5
- /** Argument type for the `c` helper. */
6
- export type CRoot = Record<PropertyKey, CNode>;
7
- /**
8
- * A minimal JS-to-CSS (CSS‑in‑JS) helper.
9
- *
10
- * The `root` object describes a hierarchy of CSS rules.
11
- *
12
- * - Keys whose values are not objects are treated as CSS property names; their values become property values. The concatenation of parent keys produces a selector.
13
- * - For every key, the substring starting with the splitter (default: `$$`) to the end is ignored (e.g. `src$$1` → `src`, `@font-face$$1` → `@font-face`).
14
- * - In property keys, uppercase letters are converted to lowercase and prefixed with `-` (e.g. `fontFamily` → `font-family`); underscores are converted to `-` (e.g. `font_family` → `font-family`).
15
- * - Commas inside selector keys cause them to expand into multiple selectors (e.g. `{div:{margin:1,'.a,.b,.c':{margin:2}}}` → `div{margin:1}div.a,div.b,div.c{margin:2}`).
16
- * - Top-level keys beginning with `@` (at-rules) are not concatenated with parent keys.
17
- */
18
- export declare const c: (root: CRoot, splitter?: string) => string;
19
- /**
20
- * A responsive‑web‑design helper that generates CSS rules for a grid layout.
21
- *
22
- * - `root` and `selector` specify where to apply the generated rules (see `c`).
23
- * - `cell_width_px` and `cell_height_px` specify the base cell dimensions in pixels.
24
- * - Each entry in `specs` is a tuple of:
25
- * - `max_width`: maximum number of cells in a row (viewport width breakpoint).
26
- * - `width` (optional, default: `1`): number of cells occupied by the element.
27
- * - `height` (optional, default: `1`): number of cells in height occupied by the element.
28
- */
29
- export declare const rwd: (root: CRoot, selector: string, cell_width_px: number, cell_height_px: number, ...specs: [number, number?, number?][]) => void;
30
- /** A tiny CSV parsing helper. */
31
- export declare const csv_parse: (csv: string, separator?: string) => string[][];
32
- /** Argument type accepted by the `escape_values` and `escape` helpers. */
33
- export type EscapeMap = Map<unknown, (value?: unknown) => string>;
34
- /** Escapes array `values` using the provided `escape_map`. */
35
- export declare const escape_values: (escape_map: EscapeMap, values: unknown[]) => string[];
36
- /** Escapes interpolated template `values` using the provided `escape_map`. */
37
- export declare const escape: (escape_map: EscapeMap, template: TemplateStringsArray, ...values: unknown[]) => string;
38
- /** Applies Polish‑specific typographic corrections. */
39
- export declare const fix_typography: (node: Node) => void;
40
1
  /** Argument type for the `h` and `s` helpers. */
41
2
  export type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string | number | HArgs;
42
3
  /** Argument type for the `h` and `s` helpers. */
@@ -81,18 +42,63 @@ export declare const s: {
81
42
  };
82
43
  /** Shorthand for: `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`. */
83
44
  export declare const svg_use: (id: string, ...args: HArgs1[]) => SVGSVGElement;
84
- /** A replacement to the `in` operator (not to be confused with `for-in`). */
85
- export declare const has_own: (ref: unknown, key: unknown) => boolean;
86
- /** Checks whether the argument is an array. */
87
- export declare const is_array: (arg: unknown) => arg is unknown[];
88
- /** Checks whether the argument is a finite number (excluding `±Infinity` and `NaN`). */
89
- export declare const is_finite_number: (arg: unknown) => arg is number;
90
- /** Checks whether the argument is a number. */
91
- export declare const is_number: (arg: unknown) => arg is number;
92
- /** Checks whether the argument is a plain object record. */
93
- export declare const is_record: (arg: unknown) => arg is Record<PropertyKey, unknown>;
94
- /** Checks whether the argument is a string. */
95
- export declare const is_string: (arg: unknown) => arg is string;
45
+ /**
46
+ * Generic syntax highlighting helper (see also `nanolight_ts`).
47
+ *
48
+ * Each `config` argument is a mapping of CSS class names to arrays of string or RegExp patterns.
49
+ *
50
+ * - For any two patterns that match at different positions, the one matching earlier takes precedence.
51
+ * - For any two patterns that match at the same position, the longer one takes precedence.
52
+ * - For any two patterns that match at the same position and have the same length, the one defined earlier takes precedence.
53
+ */
54
+ export declare const nanolight: (...configs: Record<string, (string | RegExp)[]>[]) => (code: string) => HArgs1[];
55
+ /** TypeScript syntax highlighting helper (built on `nanolight`). */
56
+ export declare const nanolight_ts: (code: string) => HArgs1[];
57
+ /** Applies Polish‑specific typographic corrections. */
58
+ export declare const fix_typography: (node: Node) => void;
59
+ /** Argument type for the `c` helper. */
60
+ export type CNode = {
61
+ [attribute_or_selector: string]: string | number | CNode | undefined;
62
+ };
63
+ /** Argument type for the `c` helper. */
64
+ export type CRoot = Record<PropertyKey, CNode>;
65
+ /**
66
+ * A minimal JS-to-CSS (CSS‑in‑JS) helper.
67
+ *
68
+ * The `root` object describes a hierarchy of CSS rules.
69
+ *
70
+ * - Keys whose values are not objects are treated as CSS property names; their values become property values. The concatenation of parent keys produces a selector.
71
+ * - For every key, the substring starting with the splitter (default: `$$`) to the end is ignored (e.g. `src$$1` → `src`, `@font-face$$1` → `@font-face`).
72
+ * - In property keys, uppercase letters are converted to lowercase and prefixed with `-` (e.g. `fontFamily` → `font-family`); underscores are converted to `-` (e.g. `font_family` → `font-family`).
73
+ * - Commas inside selector keys cause them to expand into multiple selectors (e.g. `{div:{margin:1,'.a,.b,.c':{margin:2}}}` → `div{margin:1}div.a,div.b,div.c{margin:2}`).
74
+ * - Top-level keys beginning with `@` (at-rules) are not concatenated with parent keys.
75
+ */
76
+ export declare const c: (root: CRoot, splitter?: string) => string;
77
+ /**
78
+ * A responsive‑web‑design helper that generates CSS rules for a grid layout.
79
+ *
80
+ * - `root` and `selector` specify where to apply the generated rules (see `c`).
81
+ * - `cell_width_px` and `cell_height_px` specify the base cell dimensions in pixels.
82
+ * - Each entry in `specs` is a tuple of:
83
+ * - `max_width`: maximum number of cells in a row (viewport width breakpoint).
84
+ * - `width` (optional, default: `1`): number of cells occupied by the element.
85
+ * - `height` (optional, default: `1`): number of cells in height occupied by the element.
86
+ */
87
+ export declare const rwd: (root: CRoot, selector: string, cell_width_px: number, cell_height_px: number, ...specs: [number, number?, number?][]) => void;
88
+ /** Argument type accepted by the `escape_values` and `escape` helpers. */
89
+ export type EscapeMap = Map<unknown, (value?: unknown) => string>;
90
+ /** Escapes array `values` using the provided `escape_map`. */
91
+ export declare const escape_values: (escape_map: EscapeMap, values: unknown[]) => string[];
92
+ /** Escapes interpolated template `values` using the provided `escape_map`. */
93
+ export declare const escape: (escape_map: EscapeMap, template: TemplateStringsArray, ...values: unknown[]) => string;
94
+ /** Chooses the appropriate Polish noun form based on a numeric value. */
95
+ export declare const pl_ural: (singular: string, plural_2: string, plural_5: string, value: number) => string;
96
+ /**
97
+ * Generates a UUID v1 (time-based) identifier.
98
+ *
99
+ * - Optional `node` must match `/^[0-9a-f]*$/`; it is trimmed to the last 12 characters and left-padded with zeros.
100
+ */
101
+ export declare const uuid_v1: (date?: Date, node?: string) => string;
96
102
  /**
97
103
  * `JSON.parse` with “JavaScript turned on”.
98
104
  *
@@ -109,21 +115,23 @@ export declare const is_string: (arg: unknown) => arg is string;
109
115
  * ```
110
116
  */
111
117
  export declare const js_on_parse: (handlers: Record<PropertyKey, Function>, text: string) => any;
112
- /** Generic syntax highlighting helper (see also `nanolight_js`). */
113
- export declare const nanolight: (pattern: RegExp, highlighters: ((chunk: string, index: number) => HArgs1)[], code: string) => HArgs1[];
114
- /** JavaScript syntax highlighting helper (built on `nanolight`). */
115
- export declare const nanolight_js: (code: string) => HArgs1[];
118
+ /** Checks whether the argument is an array. */
119
+ export declare const is_array: (arg: unknown) => arg is unknown[];
120
+ /** Checks whether the argument is a finite number (excluding `±Infinity` and `NaN`). */
121
+ export declare const is_finite_number: (arg: unknown) => arg is number;
122
+ /** Checks whether the argument is a number. */
123
+ export declare const is_number: (arg: unknown) => arg is number;
124
+ /** Checks whether the argument is a plain object record. */
125
+ export declare const is_record: (arg: unknown) => arg is Record<PropertyKey, unknown>;
126
+ /** Checks whether the argument is a string. */
127
+ export declare const is_string: (arg: unknown) => arg is string;
128
+ /** A replacement to the `in` operator (not to be confused with `for-in`). */
129
+ export declare const has_own: (ref: unknown, key: unknown) => boolean;
130
+ /** A `Proxy`-based helper that safely creates nested structures on access and allows deep assignment without guards. */
131
+ export declare const pro: (ref: unknown) => any;
132
+ /** A tiny CSV parsing helper. */
133
+ export declare const csv_parse: (csv: string, separator?: string) => string[][];
116
134
  /** Runtime implementation of TypeScript’s `Pick` (see also `omit`). */
117
135
  export declare const pick: <T, K extends keyof T>(ref: T, keys: K[]) => Pick<T, K>;
118
136
  /** Runtime implementation of TypeScript’s `Omit` (see also `pick`). */
119
137
  export declare const omit: <T, K extends keyof T>(ref: T, keys: unknown[]) => Omit<T, K>;
120
- /** Chooses the appropriate Polish noun form based on a numeric value. */
121
- export declare const pl_ural: (singular: string, plural_2: string, plural_5: string, value: number) => string;
122
- /** A `Proxy`-based helper that safely creates nested structures on access and allows deep assignment without guards. */
123
- export declare const pro: (ref: unknown) => any;
124
- /**
125
- * Generates a UUID v1 (time-based) identifier.
126
- *
127
- * - Optional `node` must match `/^[0-9a-f]*$/`; it is trimmed to the last 12 characters and left-padded with zeros.
128
- */
129
- export declare const uuid_v1: (date?: Date, node?: string) => string;
package/nnn.js CHANGED
@@ -1,4 +1,288 @@
1
1
  // src/nnn.ts
2
+ var _h = (namespace_uri) => {
3
+ const create_element = namespace_uri == null ? (tag) => document.createElement(tag) : (tag) => document.createElementNS(namespace_uri, tag);
4
+ const h = (tag_or_node, ...args) => {
5
+ const node = is_string(tag_or_node) ? create_element(tag_or_node) : tag_or_node;
6
+ args.forEach((arg) => {
7
+ let child = null;
8
+ if (arg instanceof Node) {
9
+ child = arg;
10
+ } else if (is_array(arg)) {
11
+ child = h(...arg);
12
+ } else if (is_record(arg)) {
13
+ for (const name in arg) {
14
+ const value = arg[name];
15
+ if (name[0] === "$") {
16
+ const name1 = name.slice(1);
17
+ if (is_record(value)) {
18
+ node[name1] ??= {};
19
+ Object.assign(node[name1], value);
20
+ } else {
21
+ node[name1] = value;
22
+ }
23
+ } else if (node instanceof Element) {
24
+ const index_of_colon = name.indexOf(":");
25
+ if (index_of_colon >= 0) {
26
+ const ns_key = name.slice(0, index_of_colon);
27
+ if (ns_key === "xlink") {
28
+ const ns = "http://www.w3.org/1999/xlink";
29
+ const basename = name.slice(index_of_colon + 1);
30
+ if (value === true) {
31
+ node.setAttributeNS(ns, basename, "");
32
+ } else if (value === false) {
33
+ node.removeAttributeNS(ns, basename);
34
+ } else {
35
+ node.setAttributeNS(ns, basename, "" + value);
36
+ }
37
+ }
38
+ } else {
39
+ if (value === true) {
40
+ node.setAttribute(name, "");
41
+ } else if (value === false) {
42
+ node.removeAttribute(name);
43
+ } else {
44
+ node.setAttribute(name, "" + value);
45
+ }
46
+ }
47
+ }
48
+ }
49
+ } else if (is_string(arg) || is_number(arg)) {
50
+ child = document.createTextNode(arg);
51
+ }
52
+ if (child != null) {
53
+ node.appendChild(child);
54
+ }
55
+ });
56
+ return node;
57
+ };
58
+ return h;
59
+ };
60
+ var h = _h();
61
+ var s = _h("http://www.w3.org/2000/svg");
62
+ var svg_use = (id, ...args) => s("svg", ["use", { "xlink:href": "#" + id }], ...args);
63
+ var nanolight = (...configs) => (code) => {
64
+ const result = [];
65
+ while (code.length > 0) {
66
+ let best_name;
67
+ let best_index = Infinity;
68
+ let best_chunk = "";
69
+ for (const config of configs) {
70
+ for (const name in config) {
71
+ for (const pattern of config[name]) {
72
+ let index = -1;
73
+ let chunk = "";
74
+ if (is_string(pattern) && pattern.length > 0) {
75
+ index = code.indexOf(pattern);
76
+ chunk = code.slice(index, index + pattern.length);
77
+ }
78
+ if (pattern instanceof RegExp) {
79
+ const match = code.match(pattern);
80
+ index = match?.index ?? -1;
81
+ chunk = match?.[0] ?? "";
82
+ }
83
+ if (index >= 0 && chunk.length > 0 && (index < best_index || index === best_index && chunk.length > best_chunk.length)) {
84
+ best_name = name;
85
+ best_index = index;
86
+ best_chunk = chunk;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ if (best_name != null) {
92
+ if (best_index > 0) {
93
+ result.push(code.slice(0, best_index));
94
+ }
95
+ result.push(["span", { class: best_name }, best_chunk]);
96
+ code = code.slice(best_index + best_chunk.length);
97
+ } else {
98
+ result.push(code);
99
+ break;
100
+ }
101
+ }
102
+ return result;
103
+ };
104
+ var nanolight_ts = nanolight({
105
+ yellow: [
106
+ /".*?"/,
107
+ /'.*?'/,
108
+ /`[\s\S]*?`/
109
+ ],
110
+ grey: [
111
+ "(",
112
+ ")",
113
+ ",",
114
+ ".",
115
+ ":",
116
+ ";",
117
+ "?",
118
+ "[",
119
+ "]",
120
+ "{",
121
+ "}",
122
+ /\/\*[\s\S]*?\*\//,
123
+ /\/\/.*?(?=\n)/
124
+ ],
125
+ red: [
126
+ "!",
127
+ "!=",
128
+ "!==",
129
+ "%",
130
+ "%=",
131
+ "&&",
132
+ "&&=",
133
+ "&",
134
+ "&=",
135
+ "*",
136
+ "**",
137
+ "**=",
138
+ "*=",
139
+ "+",
140
+ "++",
141
+ "+=",
142
+ "-",
143
+ "--",
144
+ "-=",
145
+ "/",
146
+ "/=",
147
+ ":",
148
+ "<",
149
+ "<<",
150
+ "<<=",
151
+ "<=",
152
+ "=",
153
+ "==",
154
+ "===",
155
+ "=>",
156
+ ">",
157
+ ">=",
158
+ ">>",
159
+ ">>=",
160
+ ">>>",
161
+ ">>>=",
162
+ "?",
163
+ "??",
164
+ "??=",
165
+ "^",
166
+ "^=",
167
+ "as",
168
+ "async",
169
+ "await",
170
+ "break",
171
+ "case",
172
+ "catch",
173
+ "class",
174
+ "const",
175
+ "continue",
176
+ "debugger",
177
+ "default",
178
+ "delete",
179
+ "do",
180
+ "else",
181
+ "export",
182
+ "extends",
183
+ "finally",
184
+ "for",
185
+ "from",
186
+ "function",
187
+ "function*",
188
+ "goto",
189
+ "if",
190
+ "import",
191
+ "in",
192
+ "instanceof",
193
+ "is",
194
+ "keyof",
195
+ "let",
196
+ "new",
197
+ "of",
198
+ "package",
199
+ "return",
200
+ "super",
201
+ "switch",
202
+ "this",
203
+ "throw",
204
+ "try",
205
+ "type",
206
+ "typeof",
207
+ "var",
208
+ "void",
209
+ "while",
210
+ "with",
211
+ "yield",
212
+ "yield*",
213
+ "|",
214
+ "|=",
215
+ "||",
216
+ "||=",
217
+ "~",
218
+ "~="
219
+ ],
220
+ purple: [
221
+ "false",
222
+ "Infinity",
223
+ "NaN",
224
+ "null",
225
+ "true",
226
+ "undefined",
227
+ /0b[01_]+/,
228
+ /0o[01234567_]+/,
229
+ /0x[\dabcdef_]+/,
230
+ /[A-Z_$][A-Z0-9_$]+/,
231
+ /\d[\d_]*(\.[\d_]+)?(e[+-]?[\d_]+)?/
232
+ ],
233
+ blue: [
234
+ "any",
235
+ "bigint",
236
+ "boolean",
237
+ "eval",
238
+ "number",
239
+ "string",
240
+ "symbol",
241
+ "unknown"
242
+ ]
243
+ }, {
244
+ green: [
245
+ /[$\w]+(?=\()/
246
+ ]
247
+ }, {
248
+ blue: [
249
+ /[A-Z][$\w]+/
250
+ ]
251
+ }, {
252
+ white: [
253
+ /[$\w]+/
254
+ ]
255
+ });
256
+ var TAGS_TO_SKIP = ["IFRAME", "NOSCRIPT", "PRE", "SCRIPT", "STYLE", "TEXTAREA"];
257
+ var fix_typography = (node) => {
258
+ const queue = [node];
259
+ while (queue.length > 0) {
260
+ const node_0 = queue.shift();
261
+ if (node_0 instanceof Element) {
262
+ node_0.childNodes.forEach((child_node) => {
263
+ if (child_node instanceof Text) {
264
+ queue.push(child_node);
265
+ } else if (child_node instanceof Element && !TAGS_TO_SKIP.includes(child_node.tagName)) {
266
+ queue.push(child_node);
267
+ }
268
+ });
269
+ } else if (node_0 instanceof Text) {
270
+ const node_value = node_0.nodeValue?.trim?.();
271
+ if (node_value != null) {
272
+ let previous_node = node_0;
273
+ node_value.split(/(\s|\(|„)([aiouwz—]\s)/gi).forEach((chunk, i) => {
274
+ i %= 3;
275
+ const current_node = i === 2 ? h("span", { style: "white-space:nowrap" }, chunk) : i === 1 ? document.createTextNode(chunk) : document.createTextNode(chunk.replace(/(\/(?=[^/\s])|\.(?=[^\s]))/g, "$1​"));
276
+ if (node_0.parentNode != null) {
277
+ node_0.parentNode.insertBefore(current_node, previous_node.nextSibling);
278
+ }
279
+ previous_node = current_node;
280
+ });
281
+ node_0.parentNode?.removeChild(node_0);
282
+ }
283
+ }
284
+ }
285
+ };
2
286
  var _c = (node, prefix, result, splitter) => {
3
287
  const queue = [[node, prefix]];
4
288
  while (queue.length > 0) {
@@ -72,110 +356,20 @@ var rwd = (root, selector, cell_width_px, cell_height_px, ...specs) => {
72
356
  node.height = `${cell_height_px * height}px`;
73
357
  }
74
358
  };
75
- var csv_parse = (csv, separator = ",") => {
76
- const main_pattern = /\n|(?<!")("(?:[^"]|"")*")(?!")/g;
77
- const line_pattern = new RegExp(`${separator}|(?<!")\\s*"((?:[^"]|"")*)"\\s*(?!")`, "g");
78
- 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"));
79
- };
80
359
  var escape_values = (escape_map, values) => values.map((value) => (escape_map.get(value?.constructor) ?? escape_map.get(undefined))?.(value) ?? "");
81
360
  var escape = (escape_map, template, ...values) => String.raw(template, ...escape_values(escape_map, values));
82
- var TAGS_TO_SKIP = ["IFRAME", "NOSCRIPT", "PRE", "SCRIPT", "STYLE", "TEXTAREA"];
83
- var fix_typography = (node) => {
84
- const queue = [node];
85
- while (queue.length > 0) {
86
- const node_0 = queue.shift();
87
- if (node_0 instanceof Element) {
88
- node_0.childNodes.forEach((child_node) => {
89
- if (child_node instanceof Text) {
90
- queue.push(child_node);
91
- } else if (child_node instanceof Element && !TAGS_TO_SKIP.includes(child_node.tagName)) {
92
- queue.push(child_node);
93
- }
94
- });
95
- } else if (node_0 instanceof Text) {
96
- const node_value = node_0.nodeValue?.trim?.();
97
- if (node_value != null) {
98
- let previous_node = node_0;
99
- node_value.split(/(\s|\(|„)([aiouwz—]\s)/gi).forEach((chunk, i) => {
100
- i %= 3;
101
- const current_node = i === 2 ? h("span", { style: "white-space:nowrap" }, chunk) : i === 1 ? document.createTextNode(chunk) : document.createTextNode(chunk.replace(/(\/(?=[^/\s])|\.(?=[^\s]))/g, "$1​"));
102
- if (node_0.parentNode != null) {
103
- node_0.parentNode.insertBefore(current_node, previous_node.nextSibling);
104
- }
105
- previous_node = current_node;
106
- });
107
- node_0.parentNode?.removeChild(node_0);
108
- }
109
- }
110
- }
361
+ var pl_ural = (singular, plural_2, plural_5, value) => {
362
+ const abs_value = Math.abs(value);
363
+ const abs_value_mod_10 = abs_value % 10;
364
+ 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;
111
365
  };
112
- var _h = (namespace_uri) => {
113
- const create_element = namespace_uri == null ? (tag) => document.createElement(tag) : (tag) => document.createElementNS(namespace_uri, tag);
114
- const h = (tag_or_node, ...args) => {
115
- const node = is_string(tag_or_node) ? create_element(tag_or_node) : tag_or_node;
116
- args.forEach((arg) => {
117
- let child = null;
118
- if (arg instanceof Node) {
119
- child = arg;
120
- } else if (is_array(arg)) {
121
- child = h(...arg);
122
- } else if (is_record(arg)) {
123
- for (const name in arg) {
124
- const value = arg[name];
125
- if (name[0] === "$") {
126
- const name1 = name.slice(1);
127
- if (is_record(value)) {
128
- node[name1] ??= {};
129
- Object.assign(node[name1], value);
130
- } else {
131
- node[name1] = value;
132
- }
133
- } else if (node instanceof Element) {
134
- const index_of_colon = name.indexOf(":");
135
- if (index_of_colon >= 0) {
136
- const ns_key = name.slice(0, index_of_colon);
137
- if (ns_key === "xlink") {
138
- const ns = "http://www.w3.org/1999/xlink";
139
- const basename = name.slice(index_of_colon + 1);
140
- if (value === true) {
141
- node.setAttributeNS(ns, basename, "");
142
- } else if (value === false) {
143
- node.removeAttributeNS(ns, basename);
144
- } else {
145
- node.setAttributeNS(ns, basename, "" + value);
146
- }
147
- }
148
- } else {
149
- if (value === true) {
150
- node.setAttribute(name, "");
151
- } else if (value === false) {
152
- node.removeAttribute(name);
153
- } else {
154
- node.setAttribute(name, "" + value);
155
- }
156
- }
157
- }
158
- }
159
- } else if (is_string(arg) || is_number(arg)) {
160
- child = document.createTextNode(arg);
161
- }
162
- if (child != null) {
163
- node.appendChild(child);
164
- }
165
- });
166
- return node;
167
- };
168
- return h;
366
+ var ZEROS = "0".repeat(16);
367
+ var counter = 0;
368
+ var uuid_v1 = (date = new Date, node = Math.random().toString(16).slice(2)) => {
369
+ const time = ZEROS + (1e4 * (+date + 12219292800000)).toString(16);
370
+ counter = counter + 1 & 16383;
371
+ 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));
169
372
  };
170
- var h = _h();
171
- var s = _h("http://www.w3.org/2000/svg");
172
- var svg_use = (id, ...args) => s("svg", ["use", { "xlink:href": "#" + id }], ...args);
173
- var has_own = (ref, key) => ref != null && Object.hasOwn(ref, key);
174
- var is_array = Array.isArray;
175
- var is_finite_number = Number.isFinite;
176
- var is_number = (arg) => typeof arg === "number";
177
- var is_record = (arg) => typeof arg === "object" && arg != null && !is_array(arg);
178
- var is_string = (arg) => typeof arg === "string";
179
373
  var js_on_parse = (handlers, text) => JSON.parse(text, (key, value) => {
180
374
  if (is_record(value)) {
181
375
  let is_second_key = false;
@@ -193,45 +387,24 @@ var js_on_parse = (handlers, text) => JSON.parse(text, (key, value) => {
193
387
  }
194
388
  return value;
195
389
  });
196
- var nanolight = (pattern, highlighters, code) => {
197
- const result = [];
198
- code.split(pattern).forEach((chunk, index) => {
199
- if (chunk != null && chunk !== "") {
200
- index %= highlighters.length;
201
- result.push(highlighters[index](chunk, index));
202
- }
203
- });
204
- return result;
205
- };
206
- var nanolight_js = nanolight.bind(0, /('.*?'|".*?"|`[\s\S]*?`)|(\/\/.*?\n|\/\*[\s\S]*?\*\/)|(any|bigint|break|boolean|case|catch|class|const|continue|debugger|default|delete|do|else|eval|export|extends|false|finally|for|from|function|goto|if|import|in|instanceof|is|keyof|let|NaN|new|number|null|package|return|string|super|switch|symbol|this|throw|true|try|type|typeof|undefined|unknown|var|void|while|with|yield)(?!\w)|([<>=.?:&|!^~*/%+-])|(0x[\dabcdef_]+|0o[01234567_]+|0b[01_]+|\d[\d_]*(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?)|([$\w]+)(?=\()|([$\wąćęłńóśżźĄĆĘŁŃÓŚŻŹ]+)/, [
207
- (chunk) => chunk,
208
- (chunk) => ["span", { class: "string" }, chunk],
209
- (chunk) => ["span", { class: "comment" }, chunk],
210
- (chunk) => ["span", { class: "keyword" }, chunk],
211
- (chunk) => ["span", { class: "operator" }, chunk],
212
- (chunk) => ["span", { class: "number" }, chunk],
213
- (chunk) => ["span", { class: "function" }, chunk],
214
- (chunk) => ["span", { class: "literal" }, chunk]
215
- ]);
216
- var pick = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => keys.includes(key)));
217
- var omit = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => !keys.includes(key)));
218
- var pl_ural = (singular, plural_2, plural_5, value) => {
219
- const abs_value = Math.abs(value);
220
- const abs_value_mod_10 = abs_value % 10;
221
- 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;
222
- };
390
+ var is_array = Array.isArray;
391
+ var is_finite_number = Number.isFinite;
392
+ var is_number = (arg) => typeof arg === "number";
393
+ var is_record = (arg) => typeof arg === "object" && arg != null && !is_array(arg);
394
+ var is_string = (arg) => typeof arg === "string";
395
+ var has_own = (ref, key) => ref != null && Object.hasOwn(ref, key);
223
396
  var pro = (ref) => new Proxy(ref, {
224
397
  get(target, key) {
225
398
  return pro(target[key] ??= {});
226
399
  }
227
400
  });
228
- var ZEROS = "0".repeat(16);
229
- var counter = 0;
230
- var uuid_v1 = (date = new Date, node = Math.random().toString(16).slice(2)) => {
231
- const time = ZEROS + (1e4 * (+date + 12219292800000)).toString(16);
232
- counter = counter + 1 & 16383;
233
- 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));
401
+ var csv_parse = (csv, separator = ",") => {
402
+ const main_pattern = /\n|(?<!")("(?:[^"]|"")*")(?!")/g;
403
+ const line_pattern = new RegExp(`${separator}|(?<!")\\s*"((?:[^"]|"")*)"\\s*(?!")`, "g");
404
+ 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"));
234
405
  };
406
+ var pick = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => keys.includes(key)));
407
+ var omit = (ref, keys) => Object.fromEntries(Object.entries(ref).filter(([key]) => !keys.includes(key)));
235
408
  export {
236
409
  uuid_v1,
237
410
  svg_use,
@@ -241,7 +414,7 @@ export {
241
414
  pl_ural,
242
415
  pick,
243
416
  omit,
244
- nanolight_js,
417
+ nanolight_ts,
245
418
  nanolight,
246
419
  js_on_parse,
247
420
  is_string,
package/package.json CHANGED
@@ -38,5 +38,5 @@
38
38
  "name": "@jackens/nnn",
39
39
  "type": "module",
40
40
  "types": "nnn.d.ts",
41
- "version": "2025.11.11"
41
+ "version": "2025.12.8"
42
42
  }
package/readme.md CHANGED
@@ -28,17 +28,17 @@ import { «something» } from './node_modules/@jackens/nnn/nnn.js'
28
28
 
29
29
  ## Exports
30
30
 
31
- - [`CNode`](#CNode): Argument type for the `c` helper.
32
- - [`CRoot`](#CRoot): Argument type for the `c` helper.
33
- - [`EscapeMap`](#EscapeMap): Argument type accepted by the `escape_values` and `escape` helpers.
34
- - [`HArgs`](#HArgs): Argument type for the `h` and `s` helpers.
35
- - [`HArgs1`](#HArgs1): Argument type for the `h` and `s` helpers.
31
+ - [`CNode`](#CNode): Argument type for the [`c`](#c) helper.
32
+ - [`CRoot`](#CRoot): Argument type for the [`c`](#c) helper.
33
+ - [`EscapeMap`](#EscapeMap): Argument type accepted by the [`escape_values`](#escape_values) and [`escape`](#escape) helpers.
34
+ - [`HArgs`](#HArgs): Argument type for the [`h`](#h) and [`s`](#s) helpers.
35
+ - [`HArgs1`](#HArgs1): Argument type for the [`h`](#h) and [`s`](#s) helpers.
36
36
  - [`c`](#c): A minimal JS-to-CSS (CSS‑in‑JS) helper.
37
37
  - [`csv_parse`](#csv_parse): A tiny CSV parsing helper.
38
38
  - [`escape`](#escape): Escapes interpolated template `values` using the provided `escape_map`.
39
39
  - [`escape_values`](#escape_values): Escapes array `values` using the provided `escape_map`.
40
40
  - [`fix_typography`](#fix_typography): Applies Polish‑specific typographic corrections.
41
- - [`h`](#h): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also `s`).
41
+ - [`h`](#h): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
42
42
  - [`has_own`](#has_own): A replacement to the `in` operator (not to be confused with `for-in`).
43
43
  - [`is_array`](#is_array): Checks whether the argument is an array.
44
44
  - [`is_finite_number`](#is_finite_number): Checks whether the argument is a finite number (excluding `±Infinity` and `NaN`).
@@ -46,14 +46,14 @@ import { «something» } from './node_modules/@jackens/nnn/nnn.js'
46
46
  - [`is_record`](#is_record): Checks whether the argument is a plain object record.
47
47
  - [`is_string`](#is_string): Checks whether the argument is a string.
48
48
  - [`js_on_parse`](#js_on_parse): `JSON.parse` with “JavaScript turned on”.
49
- - [`nanolight`](#nanolight): Generic syntax highlighting helper (see also `nanolight_js`).
50
- - [`nanolight_js`](#nanolight_js): JavaScript syntax highlighting helper (built on `nanolight`).
51
- - [`omit`](#omit): Runtime implementation of TypeScript’s `Omit` (see also `pick`).
52
- - [`pick`](#pick): Runtime implementation of TypeScript’s `Pick` (see also `omit`).
49
+ - [`nanolight`](#nanolight): Generic syntax highlighting helper (see also [`nanolight_ts`](#nanolight_ts)).
50
+ - [`nanolight_ts`](#nanolight_ts): TypeScript syntax highlighting helper (built on [`nanolight`](#nanolight)).
51
+ - [`omit`](#omit): Runtime implementation of TypeScript’s `Omit` (see also [`pick`](#pick)).
52
+ - [`pick`](#pick): Runtime implementation of TypeScript’s `Pick` (see also [`omit`](#omit)).
53
53
  - [`pl_ural`](#pl_ural): Chooses the appropriate Polish noun form based on a numeric value.
54
54
  - [`pro`](#pro): A `Proxy`-based helper that safely creates nested structures on access and allows deep assignment without guards.
55
55
  - [`rwd`](#rwd): A responsive‑web‑design helper that generates CSS rules for a grid layout.
56
- - [`s`](#s): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also `h`).
56
+ - [`s`](#s): A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also [`h`](#h)).
57
57
  - [`svg_use`](#svg_use): Shorthand for: `s('svg', ['use', { 'xlink:href': '#' + id }], ...args)`.
58
58
  - [`uuid_v1`](#uuid_v1): Generates a UUID v1 (time-based) identifier.
59
59
 
@@ -65,7 +65,7 @@ type CNode = {
65
65
  };
66
66
  ```
67
67
 
68
- Argument type for the `c` helper.
68
+ Argument type for the [`c`](#c) helper.
69
69
 
70
70
  ### CRoot
71
71
 
@@ -73,7 +73,7 @@ Argument type for the `c` helper.
73
73
  type CRoot = Record<PropertyKey, CNode>;
74
74
  ```
75
75
 
76
- Argument type for the `c` helper.
76
+ Argument type for the [`c`](#c) helper.
77
77
 
78
78
  ### EscapeMap
79
79
 
@@ -81,7 +81,7 @@ Argument type for the `c` helper.
81
81
  type EscapeMap = Map<unknown, (value?: unknown) => string>;
82
82
  ```
83
83
 
84
- Argument type accepted by the `escape_values` and `escape` helpers.
84
+ Argument type accepted by the [`escape_values`](#escape_values) and [`escape`](#escape) helpers.
85
85
 
86
86
  ### HArgs
87
87
 
@@ -89,7 +89,7 @@ Argument type accepted by the `escape_values` and `escape` helpers.
89
89
  type HArgs = [string | Node, ...HArgs1[]];
90
90
  ```
91
91
 
92
- Argument type for the `h` and `s` helpers.
92
+ Argument type for the [`h`](#h) and [`s`](#s) helpers.
93
93
 
94
94
  ### HArgs1
95
95
 
@@ -97,7 +97,7 @@ Argument type for the `h` and `s` helpers.
97
97
  type HArgs1 = Record<PropertyKey, unknown> | null | undefined | Node | string | number | HArgs;
98
98
  ```
99
99
 
100
- Argument type for the `h` and `s` helpers.
100
+ Argument type for the [`h`](#h) and [`s`](#s) helpers.
101
101
 
102
102
  ### c
103
103
 
@@ -410,7 +410,7 @@ const h: {
410
410
  };
411
411
  ```
412
412
 
413
- A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also `s`).
413
+ A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also [`s`](#s)).
414
414
 
415
415
  - If the first argument is a `string`, it is treated as the tag name to create.
416
416
  - If the first argument is a `Node`, that node is modified.
@@ -584,7 +584,7 @@ expect(is_number(42)).to.be.true
584
584
  expect(is_number(Number(42))).to.be.true
585
585
  expect(is_number(new Number(42))).to.be.false
586
586
  expect(is_number(NaN)).to.be.true
587
- expect(is_finite_number(Infinity)).to.be.true
587
+ expect(is_number(Infinity)).to.be.true
588
588
  ```
589
589
 
590
590
  ### is_record
@@ -696,39 +696,50 @@ expect(actual).to.deep.equal(expected)
696
696
  ### nanolight
697
697
 
698
698
  ```ts
699
- const nanolight: (pattern: RegExp, highlighters: ((chunk: string, index: number) => HArgs1)[], code: string) => HArgs1[];
699
+ const nanolight: (...configs: Record<string, (string | RegExp)[]>[]) => (code: string) => HArgs1[];
700
700
  ```
701
701
 
702
- Generic syntax highlighting helper (see also `nanolight_js`).
702
+ Generic syntax highlighting helper (see also [`nanolight_ts`](#nanolight_ts)).
703
703
 
704
- ### nanolight_js
704
+ Each `config` argument is a mapping of CSS class names to arrays of string or RegExp patterns.
705
+
706
+ - For any two patterns that match at different positions, the one matching earlier takes precedence.
707
+ - For any two patterns that match at the same position, the longer one takes precedence.
708
+ - For any two patterns that match at the same position and have the same length, the one defined earlier takes precedence.
709
+
710
+ ### nanolight_ts
705
711
 
706
712
  ```ts
707
- const nanolight_js: (code: string) => HArgs1[];
713
+ const nanolight_ts: (code: string) => HArgs1[];
708
714
  ```
709
715
 
710
- JavaScript syntax highlighting helper (built on `nanolight`).
716
+ TypeScript syntax highlighting helper (built on [`nanolight`](#nanolight)).
711
717
 
712
718
  #### Usage Examples
713
719
 
714
720
  ```ts
715
721
  const code_js = "const answer_to_life_the_universe_and_everything = { 42: 42 }['42'] /* 42 */"
716
722
 
717
- expect(nanolight_js(code_js)).to.deep.equal([
718
- ['span', { class: 'keyword' }, 'const'],
723
+ expect(nanolight_ts(code_js)).to.deep.equal([
724
+ ['span', { class: 'red' }, 'const'],
725
+ ' ',
726
+ ['span', { class: 'white' }, 'answer_to_life_the_universe_and_everything'],
727
+ ' ',
728
+ ['span', { class: 'red' }, '='],
729
+ ' ',
730
+ ['span', { class: 'grey' }, '{'],
731
+ ' ',
732
+ ['span', { class: 'purple' }, '42'],
733
+ ['span', { class: 'grey' }, ':'],
719
734
  ' ',
720
- ['span', { class: 'literal' }, 'answer_to_life_the_universe_and_everything'],
735
+ ['span', { class: 'purple' }, '42'],
721
736
  ' ',
722
- ['span', { class: 'operator' }, '='],
723
- ' { ',
724
- ['span', { class: 'number' }, '42'],
725
- ['span', { class: 'operator' }, ':'],
737
+ ['span', { class: 'grey' }, '}'],
738
+ ['span', { class: 'grey' }, '['],
739
+ ['span', { class: 'yellow' }, "'42'"],
740
+ ['span', { class: 'grey' }, ']'],
726
741
  ' ',
727
- ['span', { class: 'number' }, '42'],
728
- ' }[',
729
- ['span', { class: 'string' }, "'42'"],
730
- '] ',
731
- ['span', { class: 'comment' }, '/* 42 */']
742
+ ['span', { class: 'grey' }, '/* 42 */']
732
743
  ])
733
744
  ```
734
745
 
@@ -738,7 +749,7 @@ expect(nanolight_js(code_js)).to.deep.equal([
738
749
  const omit: <T, K extends keyof T>(ref: T, keys: unknown[]) => Omit<T, K>;
739
750
  ```
740
751
 
741
- Runtime implementation of TypeScript’s `Omit` (see also `pick`).
752
+ Runtime implementation of TypeScript’s `Omit` (see also [`pick`](#pick)).
742
753
 
743
754
  #### Usage Examples
744
755
 
@@ -754,7 +765,7 @@ expect(omit(obj, ['c'])).to.deep.equal({ a: 42, b: '42' })
754
765
  const pick: <T, K extends keyof T>(ref: T, keys: K[]) => Pick<T, K>;
755
766
  ```
756
767
 
757
- Runtime implementation of TypeScript’s `Pick` (see also `omit`).
768
+ Runtime implementation of TypeScript’s `Pick` (see also [`omit`](#omit)).
758
769
 
759
770
  #### Usage Examples
760
771
 
@@ -836,7 +847,7 @@ const rwd: (root: CRoot, selector: string, cell_width_px: number, cell_height_px
836
847
 
837
848
  A responsive‑web‑design helper that generates CSS rules for a grid layout.
838
849
 
839
- - `root` and `selector` specify where to apply the generated rules (see `c`).
850
+ - `root` and `selector` specify where to apply the generated rules (see [`c`](#c)).
840
851
  - `cell_width_px` and `cell_height_px` specify the base cell dimensions in pixels.
841
852
  - Each entry in `specs` is a tuple of:
842
853
  - `max_width`: maximum number of cells in a row (viewport width breakpoint).
@@ -846,7 +857,7 @@ A responsive‑web‑design helper that generates CSS rules for a grid layout.
846
857
  #### Usage Examples
847
858
 
848
859
  ```ts
849
- const css: CRoot = {
860
+ const style: CRoot = {
850
861
  body: {
851
862
  margin: 0
852
863
  },
@@ -858,9 +869,9 @@ const css: CRoot = {
858
869
  }
859
870
  }
860
871
 
861
- rwd(css, '.r6', 200, 50, [6], [3], [1, 1, 2])
872
+ rwd(style, '.r6', 200, 50, [6], [3], [1, 1, 2])
862
873
 
863
- expect(css).to.deep.equal({
874
+ expect(style).to.deep.equal({
864
875
  body: {
865
876
  margin: 0
866
877
  },
@@ -900,7 +911,7 @@ const s: {
900
911
  };
901
912
  ```
902
913
 
903
- A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also `h`).
914
+ A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also [`h`](#h)).
904
915
 
905
916
  - If the first argument is a `string`, it is treated as the tag name to create.
906
917
  - If the first argument is a `Node`, that node is modified.