@jackens/nnn 2023.10.13 → 2023.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/nnn.js +417 -1423
- package/package.json +1 -1
package/nnn.js
CHANGED
|
@@ -1,132 +1,111 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
// src/eq.js
|
|
2
|
+
var eq = (
|
|
3
|
+
/** @return {boolean} */
|
|
4
|
+
(x, y) => {
|
|
5
|
+
if (x === y) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
const xConstructor = x?.constructor;
|
|
9
|
+
if (xConstructor === y?.constructor) {
|
|
10
|
+
if (xConstructor === Number) {
|
|
11
|
+
return isNaN(x) && isNaN(y) || +x === +y;
|
|
12
|
+
}
|
|
13
|
+
if (xConstructor === Date) {
|
|
14
|
+
return +x === +y;
|
|
15
|
+
}
|
|
16
|
+
if (xConstructor === String || xConstructor === RegExp) {
|
|
17
|
+
return "" + x === "" + y;
|
|
18
|
+
}
|
|
19
|
+
if (xConstructor === Array) {
|
|
20
|
+
return x.length === y.length && x.every((item, index) => eq(item, y[index]));
|
|
21
|
+
}
|
|
22
|
+
if (xConstructor === Object) {
|
|
23
|
+
const keysOfX = Object.keys(x);
|
|
24
|
+
return keysOfX.length === Object.keys(y).length && keysOfX.every((key) => eq(x[key], y[key]));
|
|
25
|
+
}
|
|
26
|
+
if (xConstructor === Set || xConstructor === Map) {
|
|
27
|
+
if (x.size !== y.size) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const xa = [...x];
|
|
31
|
+
const ya = [...y];
|
|
32
|
+
return xa.every((xv) => ya.find((yv) => eq(xv, yv)));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// src/is.js
|
|
40
|
+
var is = (type, arg) => arg?.constructor === type;
|
|
41
|
+
|
|
42
|
+
// src/h.js
|
|
43
|
+
var _NS = {
|
|
44
|
+
xlink: "http://www.w3.org/1999/xlink"
|
|
45
|
+
};
|
|
46
|
+
var _h = (namespaceURI) => {
|
|
47
|
+
const createElement = namespaceURI == null ? (tag) => document.createElement(tag) : (tag) => document.createElementNS(namespaceURI, tag);
|
|
48
|
+
const h2 = (tagOrNode, ...args) => {
|
|
49
|
+
const node = is(String, tagOrNode) ? createElement(tagOrNode) : tagOrNode;
|
|
50
|
+
for (const arg of args) {
|
|
51
|
+
let child = null;
|
|
52
|
+
if (arg instanceof Node) {
|
|
53
|
+
child = arg;
|
|
54
|
+
} else if (is(String, arg) || is(Number, arg)) {
|
|
55
|
+
child = new Text(arg);
|
|
56
|
+
} else if (is(Array, arg)) {
|
|
57
|
+
child = h2(...arg);
|
|
58
|
+
} else if (arg != null) {
|
|
59
|
+
for (const name in arg) {
|
|
60
|
+
const value = arg[name];
|
|
61
|
+
if (name[0] === "$") {
|
|
62
|
+
const name1 = name.slice(1);
|
|
63
|
+
if (is(Object, value)) {
|
|
64
|
+
node[name1] = node[name1] ?? {};
|
|
65
|
+
Object.assign(node[name1], value);
|
|
66
|
+
} else {
|
|
67
|
+
node[name1] = value;
|
|
68
|
+
}
|
|
69
|
+
} else if (node instanceof Element) {
|
|
70
|
+
const indexOfColon = name.indexOf(":");
|
|
71
|
+
if (indexOfColon >= 0) {
|
|
72
|
+
const ns = _NS[name.slice(0, indexOfColon)];
|
|
73
|
+
if (ns != null) {
|
|
74
|
+
const basename = name.slice(indexOfColon + 1);
|
|
75
|
+
if (value === true) {
|
|
76
|
+
node.setAttributeNS(ns, basename, "");
|
|
77
|
+
} else if (value === false) {
|
|
78
|
+
node.removeAttributeNS(ns, basename);
|
|
79
|
+
} else {
|
|
80
|
+
node.setAttributeNS(ns, basename, is(String, value) ? value : "" + value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
if (value === true) {
|
|
85
|
+
node.setAttribute(name, "");
|
|
86
|
+
} else if (value === false) {
|
|
87
|
+
node.removeAttribute(name);
|
|
88
|
+
} else {
|
|
89
|
+
node.setAttribute(name, is(String, value) ? value : "" + value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (child != null) {
|
|
96
|
+
node.appendChild(child);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return node;
|
|
100
|
+
};
|
|
101
|
+
return h2;
|
|
102
|
+
};
|
|
103
|
+
var h = _h();
|
|
104
|
+
var s = _h("http://www.w3.org/2000/svg");
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* Options:
|
|
93
|
-
* - `bottom`: bottom padding (for X axis labels)
|
|
94
|
-
* - `gapX`: X axis spacing
|
|
95
|
-
* - `gapY`: Y axis spacing
|
|
96
|
-
* - `headerColumn`: flag indicating that `table` has a header column (with X axis labels)
|
|
97
|
-
* - `headerRow`: flag indicating that `table` has a header row (with data series labels)
|
|
98
|
-
* - `id`: chart id
|
|
99
|
-
* - `left`: left padding (for data series labels)
|
|
100
|
-
* - `maxY`: number of Y axis lines
|
|
101
|
-
* - `right`: right padding (for data series labels)
|
|
102
|
-
* - `singleScale`: flag to force single scale
|
|
103
|
-
* - `table`: `HTMLTableElement` to extract data, data series labels and X axis labels
|
|
104
|
-
* - `title`: chart title
|
|
105
|
-
* - `top`: top padding (for the title)
|
|
106
|
-
* - `xLabels`: X axis labels
|
|
107
|
-
* - `zLabels`: data series labels
|
|
108
|
-
* - `zxY`: chart data
|
|
109
|
-
*
|
|
110
|
-
* @param {{
|
|
111
|
-
* bottom?: number;
|
|
112
|
-
* gapX?: number;
|
|
113
|
-
* gapY?: number;
|
|
114
|
-
* headerColumn?: boolean;
|
|
115
|
-
* headerRow?: boolean;
|
|
116
|
-
* id?: string;
|
|
117
|
-
* left?: number;
|
|
118
|
-
* maxY?: number;
|
|
119
|
-
* right?: number;
|
|
120
|
-
* singleScale?: boolean;
|
|
121
|
-
* table?: HTMLTableElement;
|
|
122
|
-
* title?: string;
|
|
123
|
-
* top?: number;
|
|
124
|
-
* xLabels?: string[];
|
|
125
|
-
* zLabels?: string[];
|
|
126
|
-
* zxY?: number[][];
|
|
127
|
-
* }} options
|
|
128
|
-
*/
|
|
129
|
-
export const chartable = ({
|
|
106
|
+
// src/chartable.js
|
|
107
|
+
var _COLORS = ["#e22", "#e73", "#fc3", "#ad4", "#4d9", "#3be", "#45d", "#c3e"];
|
|
108
|
+
var chartable = ({
|
|
130
109
|
bottom = 50,
|
|
131
110
|
gapX = 70,
|
|
132
111
|
gapY = 30,
|
|
@@ -145,1390 +124,405 @@ export const chartable = ({
|
|
|
145
124
|
zxY = []
|
|
146
125
|
} = {}) => {
|
|
147
126
|
if (table != null) {
|
|
148
|
-
const zxYNotPassed = !zxY.length
|
|
149
|
-
const xLabelsNotPassed = !xLabels.length
|
|
150
|
-
const zLabelsNotPassed = !zLabels.length
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
// @ts-expect-error
|
|
156
|
-
const value = col.innerText
|
|
157
|
-
|
|
127
|
+
const zxYNotPassed = !zxY.length;
|
|
128
|
+
const xLabelsNotPassed = !xLabels.length;
|
|
129
|
+
const zLabelsNotPassed = !zLabels.length;
|
|
130
|
+
table.querySelectorAll("tr").forEach((row, r) => row.querySelectorAll("td,th").forEach((col, c) => {
|
|
131
|
+
const x = r - +headerRow;
|
|
132
|
+
const z = c - +headerColumn;
|
|
133
|
+
const value = col.innerText;
|
|
158
134
|
if (x >= 0 && z >= 0 && zxYNotPassed) {
|
|
159
|
-
zxY[z] = zxY[z] ?? []
|
|
160
|
-
zxY[z][x] = parseFloat(value)
|
|
135
|
+
zxY[z] = zxY[z] ?? [];
|
|
136
|
+
zxY[z][x] = parseFloat(value);
|
|
161
137
|
} else if (x >= 0 && z < 0 && xLabelsNotPassed) {
|
|
162
|
-
xLabels[x] = value
|
|
138
|
+
xLabels[x] = value;
|
|
163
139
|
} else if (x < 0 && z >= 0 && zLabelsNotPassed) {
|
|
164
|
-
zLabels[z] = value
|
|
140
|
+
zLabels[z] = value;
|
|
165
141
|
}
|
|
166
|
-
}))
|
|
142
|
+
}));
|
|
167
143
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return [Math.min(...xY), Math.max(...xY)]
|
|
174
|
-
})
|
|
175
|
-
|
|
144
|
+
let bestScales = [Infinity, -Infinity, Infinity, -Infinity, Infinity];
|
|
145
|
+
const ranges = zxY.map((xY) => {
|
|
146
|
+
xY = xY.filter((y) => !isNaN(y));
|
|
147
|
+
return [Math.min(...xY), Math.max(...xY)];
|
|
148
|
+
});
|
|
176
149
|
if (singleScale) {
|
|
177
|
-
bestScales = ranges.reduce(([totalMin, totalMax], [min, max]) =>
|
|
178
|
-
[Math.min(totalMin, min), Math.max(totalMax, max)], bestScales)
|
|
150
|
+
bestScales = ranges.reduce(([totalMin, totalMax], [min, max]) => [Math.min(totalMin, min), Math.max(totalMax, max)], bestScales);
|
|
179
151
|
} else {
|
|
180
|
-
ranges.forEach(([
|
|
181
|
-
if (min0 >=
|
|
182
|
-
let
|
|
183
|
-
let
|
|
184
|
-
|
|
152
|
+
ranges.forEach(([min12, max0]) => ranges.forEach(([min0, max12]) => {
|
|
153
|
+
if (min0 >= min12 && max12 >= max0) {
|
|
154
|
+
let min22 = Infinity;
|
|
155
|
+
let max22 = -Infinity;
|
|
185
156
|
ranges.forEach(([min, max]) => {
|
|
186
|
-
if (min <
|
|
187
|
-
if (
|
|
188
|
-
|
|
157
|
+
if (min < min12 || max12 < max) {
|
|
158
|
+
if (min22 > min) {
|
|
159
|
+
min22 = min;
|
|
189
160
|
}
|
|
190
|
-
if (
|
|
191
|
-
|
|
161
|
+
if (max22 < max) {
|
|
162
|
+
max22 = max;
|
|
192
163
|
}
|
|
193
164
|
}
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
let cost = 0
|
|
197
|
-
|
|
165
|
+
});
|
|
166
|
+
let cost = 0;
|
|
198
167
|
ranges.forEach(([min, max]) => {
|
|
199
168
|
if (min < max) {
|
|
200
|
-
cost += 1 - (max - min) / (
|
|
169
|
+
cost += 1 - (max - min) / (min12 <= min && max <= max12 ? max12 - min12 : max22 - min22);
|
|
201
170
|
}
|
|
202
|
-
})
|
|
203
|
-
|
|
171
|
+
});
|
|
204
172
|
if (bestScales[4] > cost) {
|
|
205
|
-
bestScales = [
|
|
173
|
+
bestScales = [min12, max12, min22, max22, cost];
|
|
206
174
|
}
|
|
207
175
|
}
|
|
208
|
-
}))
|
|
176
|
+
}));
|
|
209
177
|
}
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
...
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
178
|
+
const [min1, max1, min2, max2] = bestScales;
|
|
179
|
+
const maxX = zxY[0].length;
|
|
180
|
+
const xi = Array.from({ length: maxX }, (_, x) => x * gapX);
|
|
181
|
+
const yi = Array.from({ length: maxY }, (_, y) => y * gapY);
|
|
182
|
+
const w = gapX * (maxX - 1);
|
|
183
|
+
const h2 = gapY * (maxY - 1);
|
|
184
|
+
const svgW = left + w + right;
|
|
185
|
+
const svgH = top + h2 + bottom;
|
|
186
|
+
const graph = [
|
|
187
|
+
"g",
|
|
188
|
+
{ transform: `translate(${left} ${top})` },
|
|
189
|
+
...yi.map((y) => ["line", { x1: 0, x2: w, y1: y, y2: y, stroke: "#8888" }]),
|
|
190
|
+
...xi.map((x) => ["line", { x1: x, x2: x, y1: 0, y2: h2, stroke: "#8888" }])
|
|
191
|
+
];
|
|
192
|
+
const lColors = ["g", { class: "chartable-z-colors", transform: "translate(-80 0)" }];
|
|
193
|
+
const lLabels = ["g", { class: "chartable-z-labels", transform: "translate(-95 0)" }];
|
|
194
|
+
const rColors = ["g", { class: "chartable-z-colors", transform: "translate(80 0)" }];
|
|
195
|
+
const rLabels = ["g", { class: "chartable-z-labels", transform: "translate(95 0)" }];
|
|
196
|
+
const yLabel = (min, max, y) => (min + (max - min) * (maxY - 1 - y) / (maxY - 1)).toFixed(2).replace(/\.?0+$/, "");
|
|
229
197
|
zxY.forEach((xa, z) => {
|
|
230
|
-
const cls = `chartable-z-${z + 1}
|
|
231
|
-
const fill = _COLORS[z % _COLORS.length]
|
|
232
|
-
let [min, max] = ranges[z]
|
|
233
|
-
|
|
198
|
+
const cls = `chartable-z-${z + 1}`;
|
|
199
|
+
const fill = _COLORS[z % _COLORS.length];
|
|
200
|
+
let [min, max] = ranges[z];
|
|
234
201
|
if (min1 <= min && max <= max1) {
|
|
235
|
-
min = min1
|
|
236
|
-
max = max1
|
|
237
|
-
lColors.push([
|
|
238
|
-
lLabels.push([
|
|
239
|
-
|
|
240
|
-
|
|
202
|
+
min = min1;
|
|
203
|
+
max = max1;
|
|
204
|
+
lColors.push(["circle", { class: cls, cx: 0, cy: gapY * (lColors.length - 2), r: 5, fill }]);
|
|
205
|
+
lLabels.push([
|
|
206
|
+
"text",
|
|
207
|
+
{ x: 0, y: gapY * (lLabels.length - 2), "text-anchor": "end", "alignment-baseline": "middle" },
|
|
208
|
+
zLabels[z]
|
|
209
|
+
]);
|
|
241
210
|
} else {
|
|
242
|
-
min = min2
|
|
243
|
-
max = max2
|
|
244
|
-
rColors.push([
|
|
245
|
-
rLabels.push([
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
211
|
+
min = min2;
|
|
212
|
+
max = max2;
|
|
213
|
+
rColors.push(["circle", { class: cls, cx: 0, cy: gapY * (rColors.length - 2), r: 5, fill }]);
|
|
214
|
+
rLabels.push([
|
|
215
|
+
"text",
|
|
216
|
+
{ x: 0, y: gapY * (rLabels.length - 2), "text-anchor": "start", "alignment-baseline": "middle" },
|
|
217
|
+
zLabels[z]
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
const y = xa.map((x) => isNaN(x) ? x : h2 * (max - x) / (max - min));
|
|
252
221
|
xi.forEach((x, i) => {
|
|
253
222
|
if (!isNaN(y[i])) {
|
|
254
|
-
graph.push([
|
|
255
|
-
|
|
223
|
+
graph.push(["circle", { class: cls, cx: x, cy: y[i], r: 5, fill }]);
|
|
256
224
|
if (!isNaN(y[i - 1])) {
|
|
257
|
-
graph.push([
|
|
225
|
+
graph.push(["line", { class: cls, x1: x, x2: xi[i - 1], y1: y[i], y2: y[i - 1], stroke: fill }]);
|
|
258
226
|
}
|
|
259
227
|
}
|
|
260
|
-
})
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const keysOfX = Object.keys(x)
|
|
335
|
-
return keysOfX.length === Object.keys(y).length && keysOfX.every(key => eq(x[key], y[key]))
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (xConstructor === Set || xConstructor === Map) {
|
|
339
|
-
if (x.size !== y.size) {
|
|
340
|
-
return false
|
|
341
|
-
}
|
|
342
|
-
const xa = [...x]
|
|
343
|
-
const ya = [...y]
|
|
344
|
-
return xa.every(xv => ya.find(yv => eq(xv, yv)))
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
return false
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
_test.eq = () => {
|
|
352
|
-
console.assert(eq(true, true))
|
|
353
|
-
console.assert(eq(NaN, NaN))
|
|
354
|
-
console.assert(!eq(null, undefined))
|
|
355
|
-
console.assert(eq(42, 42))
|
|
356
|
-
// eslint-disable-next-line no-new-wrappers
|
|
357
|
-
console.assert(eq(42, new Number(42)))
|
|
358
|
-
console.assert(eq(42, Number(42)))
|
|
359
|
-
// eslint-disable-next-line no-new-wrappers
|
|
360
|
-
console.assert(eq(new Number(42), Number(42)))
|
|
361
|
-
console.assert(!eq(42, '42'))
|
|
362
|
-
console.assert(eq('42', '42'))
|
|
363
|
-
// eslint-disable-next-line no-new-wrappers
|
|
364
|
-
console.assert(eq('42', new String('42')))
|
|
365
|
-
console.assert(eq('42', String('42')))
|
|
366
|
-
// eslint-disable-next-line no-new-wrappers
|
|
367
|
-
console.assert(eq(String('42'), new String('42')))
|
|
368
|
-
console.assert(eq(/42/, /42/))
|
|
369
|
-
console.assert(!eq(/42/, /42/g))
|
|
370
|
-
console.assert(eq(new Date(42), new Date(42)))
|
|
371
|
-
console.assert(!eq(new Date(), new Date(42)))
|
|
372
|
-
console.assert(eq({ j: '42', c: 42 }, { c: 42, j: '42' }))
|
|
373
|
-
console.assert(eq([42, '42'], [42, '42']))
|
|
374
|
-
console.assert(eq(new Set(['42', 42]), new Set([42, '42'])))
|
|
375
|
-
console.assert(!eq(new Set(['42', 42]), new Set([42])))
|
|
376
|
-
console.assert(!eq(new Set([42, undefined]), new Set([42])))
|
|
377
|
-
console.assert(eq(
|
|
378
|
-
new Map([[{ j: 42 }, { J: '42' }], [{ c: 42 }, { C: '42' }]]),
|
|
379
|
-
new Map([[{ c: 42 }, { C: '42' }], [{ j: 42 }, { J: '42' }]])
|
|
380
|
-
))
|
|
381
|
-
console.assert(!eq(
|
|
382
|
-
new Map([[{ j: 42 }, { J: '42' }], [{ c: 42 }, { C: '42' }]]),
|
|
383
|
-
new Map([[{ j: '42' }, { J: 42 }], [{ c: '42' }, { C: 42 }]])
|
|
384
|
-
))
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* ```ts
|
|
389
|
-
* export function escape(escapeMap: EscapeMap, template: TemplateStringsArray, ...values: any[]): string;
|
|
390
|
-
* ```
|
|
391
|
-
*/
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* A generic helper for escaping `values` by given `escapeMap` (in *TemplateStrings* flavor).
|
|
395
|
-
*/
|
|
396
|
-
export const escape = (
|
|
397
|
-
/** @type {EscapeMap} */ escapeMap,
|
|
398
|
-
/** @type {TemplateStringsArray} */ template,
|
|
399
|
-
/** @type {any[]} */ ...values
|
|
400
|
-
) => String.raw(template, ...escapeValues(escapeMap, values))
|
|
401
|
-
|
|
402
|
-
_test.escape = () => {
|
|
403
|
-
// @ts-expect-error
|
|
404
|
-
const /** @type {EscapeMap} */ escapeMap = new Map([
|
|
405
|
-
[undefined, () => 'NULL'],
|
|
406
|
-
[Array, (/** @type {any[]} */ values) => escapeValues(escapeMap, values).join(', ')],
|
|
407
|
-
[Boolean, (/** @type {boolean} */ value) => `b'${+value}'`],
|
|
408
|
-
[Date, (/** @type {Date} */ value) => `'${value.toISOString().replace(/^(.+)T(.+)\..*$/, '$1 $2')}'`],
|
|
409
|
-
[Number, (/** @type {number} */ value) => `${value}`],
|
|
410
|
-
[String, (/** @type {string} */ value) => `'${value.replace(/'/g, "''")}'`]
|
|
411
|
-
])
|
|
412
|
-
|
|
413
|
-
// @ts-expect-error
|
|
414
|
-
const sql = escape.bind(null, escapeMap)
|
|
415
|
-
|
|
416
|
-
const actual = sql`
|
|
417
|
-
SELECT *
|
|
418
|
-
FROM table_name
|
|
419
|
-
WHERE column_name IN (${[true, null, undefined, 42, '42', "4'2", /42/, new Date(323325e6)]})`
|
|
420
|
-
|
|
421
|
-
const expected = `
|
|
422
|
-
SELECT *
|
|
423
|
-
FROM table_name
|
|
424
|
-
WHERE column_name IN (b'1', NULL, NULL, 42, '42', '4''2', NULL, '1980-03-31 04:30:00')`
|
|
425
|
-
|
|
426
|
-
console.assert(actual === expected)
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* ```ts
|
|
431
|
-
* export function escapeValues(escapeMap: EscapeMap, values: any[]): string[];
|
|
432
|
-
* ```
|
|
433
|
-
*/
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* A generic helper for escaping `values` by given `escapeMap`.
|
|
437
|
-
*/
|
|
438
|
-
export const escapeValues = (
|
|
439
|
-
/** @type {EscapeMap} */ escapeMap,
|
|
440
|
-
/** @type {any[]} */ values
|
|
441
|
-
) => values.map(value => (escapeMap.get(value?.constructor) ?? escapeMap.get(undefined))?.(value) ?? '')
|
|
442
|
-
|
|
443
|
-
const _TAGS_TO_SKIP = { IFRAME: 1, NOSCRIPT: 1, PRE: 1, SCRIPT: 1, STYLE: 1, TEXTAREA: 1 }
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* ```ts
|
|
447
|
-
* export function fixTypography(node: Node): void;
|
|
448
|
-
* ```
|
|
449
|
-
*/
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* A helper that implements typographic corrections specific to Polish typography.
|
|
453
|
-
*/
|
|
454
|
-
export const fixTypography = (/** @type {Node} */ node) => {
|
|
455
|
-
const /** @type {Node[]} */ queue = [node]
|
|
456
|
-
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
return s(
|
|
231
|
+
"svg",
|
|
232
|
+
{
|
|
233
|
+
viewBox: `0 0 ${svgW} ${svgH}`,
|
|
234
|
+
width: `${svgW}px`,
|
|
235
|
+
height: `${svgH}px`,
|
|
236
|
+
class: "chartable"
|
|
237
|
+
},
|
|
238
|
+
id != null ? { id } : null,
|
|
239
|
+
title != null ? [
|
|
240
|
+
"text",
|
|
241
|
+
{ class: "chartable-title", x: svgW / 2, y: top / 2, "text-anchor": "middle", "alignment-baseline": "middle" },
|
|
242
|
+
title
|
|
243
|
+
] : null,
|
|
244
|
+
graph,
|
|
245
|
+
min1 < Infinity ? [
|
|
246
|
+
"g",
|
|
247
|
+
{ class: "chartable-left", transform: `translate(${left} ${top})` },
|
|
248
|
+
[
|
|
249
|
+
"g",
|
|
250
|
+
{ class: "chartable-y-labels", transform: "translate(-15 0)" },
|
|
251
|
+
...yi.map((y, i) => [
|
|
252
|
+
"text",
|
|
253
|
+
{ x: 0, y, "text-anchor": "end", "alignment-baseline": "middle" },
|
|
254
|
+
yLabel(min1, max1, i)
|
|
255
|
+
])
|
|
256
|
+
],
|
|
257
|
+
lColors,
|
|
258
|
+
lLabels
|
|
259
|
+
] : null,
|
|
260
|
+
min2 < Infinity ? [
|
|
261
|
+
"g",
|
|
262
|
+
{ class: "chartable-right", transform: `translate(${left + w} ${top})` },
|
|
263
|
+
[
|
|
264
|
+
"g",
|
|
265
|
+
{ class: "chartable-y-labels", transform: "translate(15 0)" },
|
|
266
|
+
...yi.map((y, i) => [
|
|
267
|
+
"text",
|
|
268
|
+
{ x: 0, y, "text-anchor": "start", "alignment-baseline": "middle" },
|
|
269
|
+
yLabel(min2, max2, i)
|
|
270
|
+
])
|
|
271
|
+
],
|
|
272
|
+
rColors,
|
|
273
|
+
rLabels
|
|
274
|
+
] : null,
|
|
275
|
+
xLabels.length > 0 ? [
|
|
276
|
+
"g",
|
|
277
|
+
{ transform: `translate(${left} ${top + h2})` },
|
|
278
|
+
[
|
|
279
|
+
"g",
|
|
280
|
+
{ class: "chartable-x-labels", transform: "translate(0 15)" },
|
|
281
|
+
...xi.map((x, i) => [
|
|
282
|
+
"text",
|
|
283
|
+
{ x, y: 0, "text-anchor": "middle", "alignment-baseline": "hanging" },
|
|
284
|
+
xLabels[i]
|
|
285
|
+
])
|
|
286
|
+
]
|
|
287
|
+
] : null
|
|
288
|
+
);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/escape.js
|
|
292
|
+
var escapeValues = (escapeMap, values) => values.map((value) => (escapeMap.get(value?.constructor) ?? escapeMap.get(void 0))?.(value) ?? "");
|
|
293
|
+
var escape = (escapeMap, template, ...values) => String.raw(template, ...escapeValues(escapeMap, values));
|
|
294
|
+
|
|
295
|
+
// src/has.js
|
|
296
|
+
var has = (key, ref) => (is(String, key) || is(Number, key) || is(Symbol, key)) && Object.hasOwnProperty.call(ref ?? Object, key);
|
|
297
|
+
|
|
298
|
+
// src/fixTypography.js
|
|
299
|
+
var _TAGS_TO_SKIP = { IFRAME: 1, NOSCRIPT: 1, PRE: 1, SCRIPT: 1, STYLE: 1, TEXTAREA: 1 };
|
|
300
|
+
var fixTypography = (node) => {
|
|
301
|
+
const queue = [node];
|
|
457
302
|
while (queue.length > 0) {
|
|
458
|
-
const node0 = queue.shift()
|
|
459
|
-
|
|
303
|
+
const node0 = queue.shift();
|
|
460
304
|
if (node0 instanceof Element) {
|
|
461
305
|
for (let i = 0; i < node0.childNodes.length; ++i) {
|
|
462
|
-
const childNode = node0.childNodes[i]
|
|
463
|
-
|
|
306
|
+
const childNode = node0.childNodes[i];
|
|
464
307
|
if (childNode instanceof Text) {
|
|
465
|
-
queue.push(childNode)
|
|
308
|
+
queue.push(childNode);
|
|
466
309
|
} else if (childNode instanceof Element && !has(childNode.tagName, _TAGS_TO_SKIP)) {
|
|
467
|
-
queue.push(childNode)
|
|
310
|
+
queue.push(childNode);
|
|
468
311
|
}
|
|
469
312
|
}
|
|
470
|
-
} else if (node0 instanceof Text && node0.nodeValue != null && node0.nodeValue.trim() !==
|
|
471
|
-
let
|
|
472
|
-
|
|
313
|
+
} else if (node0 instanceof Text && node0.nodeValue != null && node0.nodeValue.trim() !== "") {
|
|
314
|
+
let previousNode = node0;
|
|
473
315
|
node0.nodeValue.split(/(\s|\(|„)([aiouwz—]\s)/gi).forEach((chunk, i) => {
|
|
474
|
-
i %= 3
|
|
475
|
-
|
|
476
|
-
const currentNode = i === 2
|
|
477
|
-
? h('span', { style: 'white-space:nowrap' }, chunk)
|
|
478
|
-
: i === 1
|
|
479
|
-
? document.createTextNode(chunk)
|
|
480
|
-
: document.createTextNode(chunk.replace(/(\/(?=[^/\s])|\.(?=[^\s]))/g, '$1\u200B'))
|
|
481
|
-
|
|
316
|
+
i %= 3;
|
|
317
|
+
const currentNode = i === 2 ? h("span", { style: "white-space:nowrap" }, chunk) : i === 1 ? document.createTextNode(chunk) : document.createTextNode(chunk.replace(/(\/(?=[^/\s])|\.(?=[^\s]))/g, "$1\u200B"));
|
|
482
318
|
if (node0.parentNode != null) {
|
|
483
|
-
node0.parentNode.insertBefore(currentNode, previousNode.nextSibling)
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
previousNode = currentNode
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
node0.parentNode?.removeChild(node0)
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
_test.fixTypography = () => {
|
|
495
|
-
const p = h('p', 'Pchnąć w tę łódź jeża lub ośm skrzyń fig (zob. https://pl.wikipedia.org/wiki/Pangram).')
|
|
496
|
-
|
|
497
|
-
fixTypography(p)
|
|
498
|
-
|
|
499
|
-
console.assert(p.innerHTML ===
|
|
500
|
-
'Pchnąć <span style="white-space:nowrap">w </span>tę łódź jeża lub ośm skrzyń fig ' +
|
|
501
|
-
'(zob. https://\u200Bpl.\u200Bwikipedia.\u200Borg/\u200Bwiki/\u200BPangram).')
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
const /** @type {Record<string, string>} */ _NS = {
|
|
505
|
-
xlink: 'http://www.w3.org/1999/xlink'
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/**
|
|
509
|
-
* @type {{
|
|
510
|
-
* (namespaceURI?: null | undefined): {
|
|
511
|
-
* <T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs[1][]): HTMLElementTagNameMap[T];
|
|
512
|
-
* <N extends Node> (node: N, ...args: HArgs[1][]): N;
|
|
513
|
-
* (...args: HArgs): Node;
|
|
514
|
-
* };
|
|
515
|
-
* (namespaceURI: 'http://www.w3.org/2000/svg'): {
|
|
516
|
-
* <T extends keyof SVGElementTagNameMap> (tag: T, ...args: HArgs[1][]): SVGElementTagNameMap[T];
|
|
517
|
-
* <N extends Node> (node: N, ...args: HArgs[1][]): N;
|
|
518
|
-
* (...args: HArgs): Node;
|
|
519
|
-
* };
|
|
520
|
-
* }}
|
|
521
|
-
*/
|
|
522
|
-
const _h = (/** @type {string?=} */ namespaceURI) => {
|
|
523
|
-
const createElement = namespaceURI == null
|
|
524
|
-
? (/** @type {string } */ tag) => document.createElement(tag)
|
|
525
|
-
: (/** @type {string } */ tag) => document.createElementNS(namespaceURI, tag)
|
|
526
|
-
|
|
527
|
-
const h = (/** @type {HArgs[0]} */ tagOrNode, /** @type {HArgs[1][]} */ ...args) => {
|
|
528
|
-
const node = is(String, tagOrNode) ? createElement(tagOrNode) : tagOrNode
|
|
529
|
-
|
|
530
|
-
for (const arg of args) {
|
|
531
|
-
let child = null
|
|
532
|
-
|
|
533
|
-
if (arg instanceof Node) {
|
|
534
|
-
child = arg
|
|
535
|
-
} else if (is(String, arg) || is(Number, arg)) {
|
|
536
|
-
// @ts-expect-error
|
|
537
|
-
child = new Text(arg)
|
|
538
|
-
} else if (is(Array, arg)) {
|
|
539
|
-
// @ts-expect-error
|
|
540
|
-
child = h(...arg)
|
|
541
|
-
} else if (arg != null) {
|
|
542
|
-
for (const name in arg) {
|
|
543
|
-
const value = arg[name]
|
|
544
|
-
|
|
545
|
-
if (name[0] === '$') {
|
|
546
|
-
const name1 = name.slice(1)
|
|
547
|
-
|
|
548
|
-
if (is(Object, value)) {
|
|
549
|
-
// @ts-expect-error
|
|
550
|
-
node[name1] = node[name1] ?? {}
|
|
551
|
-
// @ts-expect-error
|
|
552
|
-
Object.assign(node[name1], value)
|
|
553
|
-
} else {
|
|
554
|
-
// @ts-expect-error
|
|
555
|
-
node[name1] = value
|
|
556
|
-
}
|
|
557
|
-
} else if (node instanceof Element) {
|
|
558
|
-
const indexOfColon = name.indexOf(':')
|
|
559
|
-
|
|
560
|
-
if (indexOfColon >= 0) {
|
|
561
|
-
const /** @type {string=} */ ns = _NS[name.slice(0, indexOfColon)]
|
|
562
|
-
|
|
563
|
-
if (ns != null) {
|
|
564
|
-
const basename = name.slice(indexOfColon + 1)
|
|
565
|
-
|
|
566
|
-
if (value === true) {
|
|
567
|
-
node.setAttributeNS(ns, basename, '')
|
|
568
|
-
} else if (value === false) {
|
|
569
|
-
node.removeAttributeNS(ns, basename)
|
|
570
|
-
} else {
|
|
571
|
-
node.setAttributeNS(ns, basename, is(String, value) ? value : '' + value)
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
} else {
|
|
575
|
-
if (value === true) {
|
|
576
|
-
node.setAttribute(name, '')
|
|
577
|
-
} else if (value === false) {
|
|
578
|
-
node.removeAttribute(name)
|
|
579
|
-
} else {
|
|
580
|
-
node.setAttribute(name, is(String, value) ? value : '' + value)
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
319
|
+
node0.parentNode.insertBefore(currentNode, previousNode.nextSibling);
|
|
584
320
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
node.appendChild(child)
|
|
589
|
-
}
|
|
321
|
+
previousNode = currentNode;
|
|
322
|
+
});
|
|
323
|
+
node0.parentNode?.removeChild(node0);
|
|
590
324
|
}
|
|
591
|
-
|
|
592
|
-
return node
|
|
593
325
|
}
|
|
326
|
+
};
|
|
594
327
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* ```ts
|
|
600
|
-
* export function h<T extends keyof HTMLElementTagNameMap>(tag: T, ...args: HArgs[1][]): HTMLElementTagNameMap[T];
|
|
601
|
-
* export function h<N extends Node>(node: N, ...args: HArgs[1][]): N;
|
|
602
|
-
* export function h(...args: HArgs): Node;
|
|
603
|
-
* ```
|
|
604
|
-
*/
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `HTMLElement`s (see also `s`).
|
|
608
|
-
*
|
|
609
|
-
* - The first argument of type `string` specifies the tag of the element to be created.
|
|
610
|
-
* - The first argument of type `Node` specifies the element to be modified.
|
|
611
|
-
* - All other arguments of type `Record<string, any>` are mappings of attributes and properties.
|
|
612
|
-
* Keys starting with `$` specify *properties* (without the leading `$`) to be set on the element being created or modified.
|
|
613
|
-
* (Note that `$` is not a valid attribute name character.)
|
|
614
|
-
* All other keys specify *attributes* to be set by `setAttribute`.
|
|
615
|
-
* An attribute equal to `false` causes the attribute to be removed by `removeAttribute`.
|
|
616
|
-
* - All other arguments of type `null` or `undefined` are simply ignored.
|
|
617
|
-
* - All other arguments of type `Node` are appended to the element being created or modified.
|
|
618
|
-
* - All other arguments of type `string`/`number` are converted to `Text` nodes and appended to the element being created or modified.
|
|
619
|
-
* - All other arguments of type `HArgs` are passed to `h` and the results are appended to the element being created or modified.
|
|
620
|
-
*/
|
|
621
|
-
export const h = _h()
|
|
622
|
-
|
|
623
|
-
_test.h = () => {
|
|
624
|
-
const b = h('b')
|
|
625
|
-
|
|
626
|
-
console.assert(b.outerHTML === '<b></b>')
|
|
627
|
-
|
|
628
|
-
const i = h('i', 'text')
|
|
629
|
-
|
|
630
|
-
h(b, i)
|
|
631
|
-
|
|
632
|
-
console.assert(i.outerHTML === '<i>text</i>')
|
|
633
|
-
console.assert(b.outerHTML === '<b><i>text</i></b>')
|
|
634
|
-
|
|
635
|
-
h(i, { $className: 'some class' })
|
|
636
|
-
|
|
637
|
-
console.assert(i.outerHTML === '<i class="some class">text</i>')
|
|
638
|
-
console.assert(b.outerHTML === '<b><i class="some class">text</i></b>')
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
_test['h: innerText vs items'] = () => {
|
|
642
|
-
console.assert(h('span', 'text').outerHTML === '<span>text</span>')
|
|
643
|
-
console.assert(h('span', { $innerText: 'text' }).outerHTML === '<span>text</span>')
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
_test['h: style'] = () => {
|
|
647
|
-
console.assert(h('div', { style: 'margin:0;padding:0' }).outerHTML ===
|
|
648
|
-
'<div style="margin:0;padding:0"></div>')
|
|
649
|
-
console.assert(h('div', { $style: 'margin:0;padding:0' }).outerHTML ===
|
|
650
|
-
'<div style="margin: 0px; padding: 0px;"></div>')
|
|
651
|
-
console.assert(h('div', { $style: { margin: 0, padding: 0 } }).outerHTML ===
|
|
652
|
-
'<div style="margin: 0px; padding: 0px;"></div>')
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
_test['h: attributes vs properties'] = () => {
|
|
656
|
-
const input1 = h('input', { value: 42 })
|
|
657
|
-
const input2 = h('input', { $value: '42' })
|
|
658
|
-
|
|
659
|
-
console.assert(input1.value === '42')
|
|
660
|
-
console.assert(input2.value === '42')
|
|
661
|
-
|
|
662
|
-
console.assert(input1.outerHTML === '<input value="42">')
|
|
663
|
-
console.assert(input2.outerHTML === '<input>')
|
|
664
|
-
|
|
665
|
-
const checkbox1 = h('input', { type: 'checkbox', checked: true })
|
|
666
|
-
const checkbox2 = h('input', { type: 'checkbox', $checked: true })
|
|
667
|
-
|
|
668
|
-
console.assert(checkbox1.checked === true)
|
|
669
|
-
console.assert(checkbox2.checked === true)
|
|
670
|
-
|
|
671
|
-
console.assert(checkbox1.outerHTML === '<input type="checkbox" checked="">')
|
|
672
|
-
console.assert(checkbox2.outerHTML === '<input type="checkbox">')
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
_test['h: nested properties'] = () => {
|
|
676
|
-
const div = h('div')
|
|
677
|
-
|
|
678
|
-
// @ts-expect-error
|
|
679
|
-
console.assert(div.key === undefined)
|
|
680
|
-
|
|
681
|
-
h(div, { $key: { one: 1 } })
|
|
682
|
-
|
|
683
|
-
// @ts-expect-error
|
|
684
|
-
console.assert(eq(div.key, { one: 1 }))
|
|
685
|
-
|
|
686
|
-
h(div, { $key: { two: 2 } })
|
|
687
|
-
|
|
688
|
-
// @ts-expect-error
|
|
689
|
-
console.assert(eq(div.key, { one: 1, two: 2 }))
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* ```ts
|
|
694
|
-
* export function has(key: any, ref: any): boolean;
|
|
695
|
-
* ```
|
|
696
|
-
*/
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* A replacement for the `in` operator (not to be confused with the `for-in` loop) that works properly.
|
|
700
|
-
*/
|
|
701
|
-
export const has = (/** @type {any} */ key, /** @type {any} */ ref) =>
|
|
702
|
-
(is(String, key) || is(Number, key) || is(Symbol, key)) && Object.hasOwnProperty.call(ref ?? Object, key)
|
|
703
|
-
|
|
704
|
-
_test.has = () => {
|
|
705
|
-
const obj = { key: 'K', null: 'N' }
|
|
706
|
-
|
|
707
|
-
console.assert('key' in obj)
|
|
708
|
-
console.assert(has('key', obj))
|
|
709
|
-
|
|
710
|
-
console.assert('null' in obj)
|
|
711
|
-
console.assert(has('null', obj))
|
|
712
|
-
|
|
713
|
-
// @ts-expect-error
|
|
714
|
-
console.assert(null in obj)
|
|
715
|
-
console.assert(!has(null, obj))
|
|
716
|
-
|
|
717
|
-
console.assert('toString' in obj)
|
|
718
|
-
console.assert(!has('toString', obj))
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
_test['has: null'] = () => {
|
|
722
|
-
let typeError
|
|
723
|
-
|
|
724
|
-
try {
|
|
725
|
-
// @ts-expect-error
|
|
726
|
-
console.assert('key' in null)
|
|
727
|
-
} catch (error) {
|
|
728
|
-
typeError = error
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
console.assert(typeError instanceof TypeError) // Cannot use 'in' operator to search for 'key' in null
|
|
732
|
-
console.assert(!has('key', null))
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* ```ts
|
|
737
|
-
* export function is(type: BigIntConstructor, arg: any): arg is bigint;
|
|
738
|
-
* export function is(type: BooleanConstructor, arg: any): arg is boolean;
|
|
739
|
-
* export function is(type: NumberConstructor, arg: any): arg is number;
|
|
740
|
-
* export function is(type: StringConstructor, arg: any): arg is string;
|
|
741
|
-
* export function is(type: SymbolConstructor, arg: any): arg is symbol;
|
|
742
|
-
* export function is(type: undefined, arg: any): arg is null | undefined;
|
|
743
|
-
* export function is<T extends abstract new (...args: any[]) => any>(type: T, arg: any): arg is InstanceType<T>;
|
|
744
|
-
* ```
|
|
745
|
-
*/
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* A helper that checks if the given argument is of a certain type.
|
|
749
|
-
*
|
|
750
|
-
* @template {abstract new (...args: any[]) => any} T
|
|
751
|
-
*
|
|
752
|
-
* @type {{
|
|
753
|
-
* (type: BigIntConstructor, arg: any): arg is bigint;
|
|
754
|
-
* (type: BooleanConstructor, arg: any): arg is boolean;
|
|
755
|
-
* (type: NumberConstructor, arg: any): arg is number;
|
|
756
|
-
* (type: StringConstructor, arg: any): arg is string;
|
|
757
|
-
* (type: SymbolConstructor, arg: any): arg is symbol;
|
|
758
|
-
* (type: undefined, arg: any): arg is undefined | null;
|
|
759
|
-
* <T extends abstract new (...args: any[]) => any>(type: T, arg: any): arg is InstanceType<T>;
|
|
760
|
-
* }}
|
|
761
|
-
*
|
|
762
|
-
* @returns {arg is bigint | boolean | number | string | symbol | undefined | null | InstanceType<T>}
|
|
763
|
-
*/
|
|
764
|
-
export const is = (/** @type {T} */ type, /** @type {any} */ arg) => arg?.constructor === type
|
|
765
|
-
|
|
766
|
-
_test.is = () => {
|
|
767
|
-
console.assert(is(Number, 42))
|
|
768
|
-
console.assert(is(Number, Number(42)))
|
|
769
|
-
// eslint-disable-next-line no-new-wrappers
|
|
770
|
-
console.assert(is(Number, new Number(42)))
|
|
771
|
-
console.assert(is(Number, NaN))
|
|
772
|
-
console.assert(is(String, '42'))
|
|
773
|
-
console.assert(is(String, String('42')))
|
|
774
|
-
// eslint-disable-next-line no-new-wrappers
|
|
775
|
-
console.assert(is(String, new String('42')))
|
|
776
|
-
console.assert(is(Symbol, Symbol('42')))
|
|
777
|
-
console.assert(is(Symbol, Object(Symbol('42'))))
|
|
778
|
-
console.assert(is(undefined, undefined))
|
|
779
|
-
console.assert(is(undefined, null))
|
|
780
|
-
console.assert(is(Object, {}))
|
|
781
|
-
console.assert(is(Array, []))
|
|
782
|
-
console.assert(is(RegExp, /42/))
|
|
783
|
-
console.assert(is(Date, new Date(42)))
|
|
784
|
-
console.assert(is(Set, new Set(['42', 42])))
|
|
785
|
-
console.assert(is(Map, new Map([[{ j: 42 }, { J: '42' }], [{ c: 42 }, { C: '42' }]])))
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
_test['is vs ‘toString.call’'] = () => {
|
|
789
|
-
class FooBar { }
|
|
790
|
-
|
|
791
|
-
console.assert(is(FooBar, new FooBar()))
|
|
792
|
-
|
|
793
|
-
const fakeFooBar = { [Symbol.toStringTag]: 'FooBar' }
|
|
794
|
-
|
|
795
|
-
console.assert(({}).toString.call(new FooBar()) === '[object Object]')
|
|
796
|
-
console.assert(({}).toString.call(fakeFooBar) === '[object FooBar]')
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
const _jcss = (
|
|
800
|
-
/** @type {JcssNode} */ node,
|
|
801
|
-
/** @type {string} */ prefix,
|
|
802
|
-
/** @type {string[]} */ result,
|
|
803
|
-
/** @type {(text: string) => string} */ split
|
|
804
|
-
) => {
|
|
805
|
-
const /** @type {[JcssNode | string[], string][]} */ queue = [[node, prefix]]
|
|
806
|
-
|
|
328
|
+
// src/jcss.js
|
|
329
|
+
var _jcss = (node, prefix, result, split) => {
|
|
330
|
+
const queue = [[node, prefix]];
|
|
807
331
|
while (queue.length) {
|
|
808
|
-
const [style2, prefix2] = queue.shift() ?? []
|
|
809
|
-
|
|
332
|
+
const [style2, prefix2] = queue.shift() ?? [];
|
|
810
333
|
if (style2 == null || prefix2 == null) {
|
|
811
|
-
continue
|
|
334
|
+
continue;
|
|
812
335
|
}
|
|
813
|
-
|
|
814
336
|
if (is(Array, style2)) {
|
|
815
|
-
result.push(prefix2, prefix2 !==
|
|
337
|
+
result.push(prefix2, prefix2 !== "" ? "{" : "", style2.join(";"), prefix2 !== "" ? "}" : "");
|
|
816
338
|
} else {
|
|
817
|
-
const
|
|
818
|
-
let
|
|
819
|
-
let attributesPushed = false
|
|
820
|
-
|
|
339
|
+
const todo = [];
|
|
340
|
+
let attributes = [];
|
|
341
|
+
let attributesPushed = false;
|
|
821
342
|
for (const key in style2) {
|
|
822
|
-
const value = style2[key]
|
|
823
|
-
|
|
343
|
+
const value = style2[key];
|
|
824
344
|
if (is(String, value) || is(Number, value)) {
|
|
825
345
|
if (!attributesPushed) {
|
|
826
|
-
attributesPushed = true
|
|
827
|
-
attributes = []
|
|
828
|
-
todo.push([attributes, prefix2])
|
|
346
|
+
attributesPushed = true;
|
|
347
|
+
attributes = [];
|
|
348
|
+
todo.push([attributes, prefix2]);
|
|
829
349
|
}
|
|
830
|
-
|
|
831
|
-
attributes.push(`${split(key).replace(/([A-Z])/g, (_, letter) => '-' + letter.toLowerCase())}:${value}`)
|
|
350
|
+
attributes.push(`${split(key).replace(/([A-Z])/g, (_, letter) => "-" + letter.toLowerCase())}:${value}`);
|
|
832
351
|
} else {
|
|
833
|
-
attributesPushed = false
|
|
834
|
-
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
for (const prefixItem of prefix2.split(',')) {
|
|
352
|
+
attributesPushed = false;
|
|
353
|
+
const newPrefix = [];
|
|
354
|
+
const keySplitted = key.split(",");
|
|
355
|
+
for (const prefixItem of prefix2.split(",")) {
|
|
839
356
|
for (const keyItem of keySplitted) {
|
|
840
|
-
newPrefix.push(prefixItem + keyItem)
|
|
357
|
+
newPrefix.push(prefixItem + keyItem);
|
|
841
358
|
}
|
|
842
359
|
}
|
|
843
|
-
|
|
844
|
-
todo.push([value, newPrefix.join(',')])
|
|
360
|
+
todo.push([value, newPrefix.join(",")]);
|
|
845
361
|
}
|
|
846
362
|
}
|
|
847
|
-
|
|
848
|
-
queue.unshift(...todo)
|
|
363
|
+
queue.unshift(...todo);
|
|
849
364
|
}
|
|
850
365
|
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
* export function jcss(root: JcssRoot, splitter?: string): string;
|
|
856
|
-
* ```
|
|
857
|
-
*/
|
|
858
|
-
|
|
859
|
-
/**
|
|
860
|
-
* A simple CSS-in-JS helper.
|
|
861
|
-
*
|
|
862
|
-
* The `root` parameter provides a hierarchical description of CSS rules.
|
|
863
|
-
*
|
|
864
|
-
* - Keys of sub-objects whose values are NOT objects are treated as CSS attribute, and values are treated as values of those CSS attributes; the concatenation of keys of all parent objects is a CSS rule.
|
|
865
|
-
* - All keys ignore the part starting with a splitter (default: `$$`) sign until the end of the key (e.g. `src$$1` → `src`, `@font-face$$1` → `@font-face`).
|
|
866
|
-
* - In keys specifying CSS attribute, all uppercase letters are replaced by lowercase letters with an additional `-` character preceding them (e.g. `fontFamily` → `font-family`).
|
|
867
|
-
* - Commas in keys that makes a CSS rule cause it to “split” and create separate rules for each part (e.g. `{div:{margin:1,'.a,.b,.c':{margin:2}}}` → `div{margin:1}div.a,div.b,div.c{margin:2}`).
|
|
868
|
-
* - Top-level keys that begin with `@` are not concatenated with sub-object keys.
|
|
869
|
-
*/
|
|
870
|
-
export const jcss = (/** @type {JcssRoot} */ root, splitter = '$$') => {
|
|
871
|
-
const split = (/** @type {string} */ text) => text.split(splitter)[0]
|
|
872
|
-
const /** @type {string[]} */ result = []
|
|
873
|
-
|
|
366
|
+
};
|
|
367
|
+
var jcss = (root, splitter = "$$") => {
|
|
368
|
+
const split = (text) => text.split(splitter)[0];
|
|
369
|
+
const result = [];
|
|
874
370
|
for (const key in root) {
|
|
875
|
-
const value = root[key]
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
result.push('}')
|
|
371
|
+
const value = root[key];
|
|
372
|
+
if (key[0] === "@") {
|
|
373
|
+
result.push(split(key) + "{");
|
|
374
|
+
_jcss(value, "", result, split);
|
|
375
|
+
result.push("}");
|
|
881
376
|
} else {
|
|
882
|
-
_jcss(value, split(key), result, split)
|
|
377
|
+
_jcss(value, split(key), result, split);
|
|
883
378
|
}
|
|
884
379
|
}
|
|
380
|
+
return result.join("");
|
|
381
|
+
};
|
|
885
382
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
_test['jcss: #1'] = () => {
|
|
890
|
-
const actual = jcss({
|
|
891
|
-
a: {
|
|
892
|
-
color: 'red',
|
|
893
|
-
margin: 1,
|
|
894
|
-
'.c': { margin: 2, padding: 2 },
|
|
895
|
-
padding: 1
|
|
896
|
-
}
|
|
897
|
-
})
|
|
898
|
-
|
|
899
|
-
const expected = `
|
|
900
|
-
a{
|
|
901
|
-
color:red;
|
|
902
|
-
margin:1
|
|
903
|
-
}
|
|
904
|
-
a.c{
|
|
905
|
-
margin:2;
|
|
906
|
-
padding:2
|
|
907
|
-
}
|
|
908
|
-
a{
|
|
909
|
-
padding:1
|
|
910
|
-
}`.replace(/\n\s*/g, '')
|
|
911
|
-
|
|
912
|
-
console.assert(actual === expected)
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
_test['jcss: #2'] = () => {
|
|
916
|
-
const actual = jcss({
|
|
917
|
-
a: {
|
|
918
|
-
'.b': {
|
|
919
|
-
color: 'red',
|
|
920
|
-
margin: 1,
|
|
921
|
-
'.c': { margin: 2, padding: 2 },
|
|
922
|
-
padding: 1
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
})
|
|
926
|
-
|
|
927
|
-
const expected = `
|
|
928
|
-
a.b{
|
|
929
|
-
color:red;
|
|
930
|
-
margin:1
|
|
931
|
-
}
|
|
932
|
-
a.b.c{
|
|
933
|
-
margin:2;
|
|
934
|
-
padding:2
|
|
935
|
-
}
|
|
936
|
-
a.b{
|
|
937
|
-
padding:1
|
|
938
|
-
}`.replace(/\n\s*/g, '')
|
|
939
|
-
|
|
940
|
-
console.assert(actual === expected)
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
_test['jcss: #3'] = () => {
|
|
944
|
-
const actual = jcss({
|
|
945
|
-
'@font-face$$1': {
|
|
946
|
-
fontFamily: 'Jackens',
|
|
947
|
-
src$$1: 'url(otf/jackens.otf)',
|
|
948
|
-
src$$2: "url(otf/jackens.otf) format('opentype')," +
|
|
949
|
-
"url(svg/jackens.svg) format('svg')",
|
|
950
|
-
fontWeight: 'normal',
|
|
951
|
-
fontStyle: 'normal'
|
|
952
|
-
},
|
|
953
|
-
'@font-face$$2': {
|
|
954
|
-
fontFamily: 'C64',
|
|
955
|
-
src: 'url(fonts/C64_Pro_Mono-STYLE.woff)'
|
|
956
|
-
},
|
|
957
|
-
'@keyframes spin': {
|
|
958
|
-
'0%': { transform: 'rotate(0deg)' },
|
|
959
|
-
'100%': { transform: 'rotate(360deg)' }
|
|
960
|
-
},
|
|
961
|
-
div: {
|
|
962
|
-
border: 'solid red 1px',
|
|
963
|
-
'.c1': { 'background-color': '#000' },
|
|
964
|
-
' .c1': { backgroundColor: 'black' },
|
|
965
|
-
'.c2': { backgroundColor: 'rgb(0,0,0)' }
|
|
966
|
-
},
|
|
967
|
-
'@media(min-width:200px)': {
|
|
968
|
-
div: { margin: 0, padding: 0 },
|
|
969
|
-
span: { color: '#000' }
|
|
970
|
-
}
|
|
971
|
-
})
|
|
972
|
-
|
|
973
|
-
const expected = `
|
|
974
|
-
@font-face{
|
|
975
|
-
font-family:Jackens;
|
|
976
|
-
src:url(otf/jackens.otf);
|
|
977
|
-
src:url(otf/jackens.otf) format('opentype'),url(svg/jackens.svg) format('svg');
|
|
978
|
-
font-weight:normal;
|
|
979
|
-
font-style:normal
|
|
980
|
-
}
|
|
981
|
-
@font-face{
|
|
982
|
-
font-family:C64;
|
|
983
|
-
src:url(fonts/C64_Pro_Mono-STYLE.woff)
|
|
984
|
-
}
|
|
985
|
-
@keyframes spin{
|
|
986
|
-
0%{
|
|
987
|
-
transform:rotate(0deg)
|
|
988
|
-
}
|
|
989
|
-
100%{
|
|
990
|
-
transform:rotate(360deg)
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
div{
|
|
994
|
-
border:solid red 1px
|
|
995
|
-
}
|
|
996
|
-
div.c1{
|
|
997
|
-
background-color:#000
|
|
998
|
-
}
|
|
999
|
-
div .c1{
|
|
1000
|
-
background-color:black
|
|
1001
|
-
}
|
|
1002
|
-
div.c2{
|
|
1003
|
-
background-color:rgb(0,0,0)
|
|
1004
|
-
}
|
|
1005
|
-
@media(min-width:200px){
|
|
1006
|
-
div{
|
|
1007
|
-
margin:0;
|
|
1008
|
-
padding:0
|
|
1009
|
-
}
|
|
1010
|
-
span{
|
|
1011
|
-
color:#000
|
|
1012
|
-
}
|
|
1013
|
-
}`.replace(/\n\s*/g, '')
|
|
1014
|
-
|
|
1015
|
-
console.assert(actual === expected)
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
_test['jcss: #4'] = () => {
|
|
1019
|
-
const actual = jcss({
|
|
1020
|
-
a: {
|
|
1021
|
-
'.b,.c': {
|
|
1022
|
-
margin: 1,
|
|
1023
|
-
'.d': {
|
|
1024
|
-
margin: 2
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
})
|
|
1029
|
-
|
|
1030
|
-
const expected = `
|
|
1031
|
-
a.b,a.c{
|
|
1032
|
-
margin:1
|
|
1033
|
-
}
|
|
1034
|
-
a.b.d,a.c.d{
|
|
1035
|
-
margin:2
|
|
1036
|
-
}`.replace(/\n\s*/g, '')
|
|
1037
|
-
|
|
1038
|
-
console.assert(actual === expected)
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
_test['jcss: #5'] = () => {
|
|
1042
|
-
const actual = jcss({
|
|
1043
|
-
'.b,.c': {
|
|
1044
|
-
margin: 1,
|
|
1045
|
-
'.d': {
|
|
1046
|
-
margin: 2
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
})
|
|
1050
|
-
|
|
1051
|
-
const expected = `
|
|
1052
|
-
.b,.c{
|
|
1053
|
-
margin:1
|
|
1054
|
-
}
|
|
1055
|
-
.b.d,.c.d{
|
|
1056
|
-
margin:2
|
|
1057
|
-
}`.replace(/\n\s*/g, '')
|
|
1058
|
-
|
|
1059
|
-
console.assert(actual === expected)
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
_test['jcss: #6'] = () => {
|
|
1063
|
-
const actual = jcss({
|
|
1064
|
-
'.a,.b': {
|
|
1065
|
-
margin: 1,
|
|
1066
|
-
'.c,.d': {
|
|
1067
|
-
margin: 2
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
})
|
|
1071
|
-
|
|
1072
|
-
const expected = `
|
|
1073
|
-
.a,.b{
|
|
1074
|
-
margin:1
|
|
1075
|
-
}
|
|
1076
|
-
.a.c,.a.d,.b.c,.b.d{
|
|
1077
|
-
margin:2
|
|
1078
|
-
}`.replace(/\n\s*/g, '')
|
|
1079
|
-
|
|
1080
|
-
console.assert(actual === expected)
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
/**
|
|
1084
|
-
* ```ts
|
|
1085
|
-
* export function jsOnParse(handlers: Record<string, Function>, text: string): any;
|
|
1086
|
-
* ```
|
|
1087
|
-
*/
|
|
1088
|
-
|
|
1089
|
-
/**
|
|
1090
|
-
* `JSON.parse` with “JavaScript turned on”.
|
|
1091
|
-
*
|
|
1092
|
-
* Objects having *exactly* one property which is present in the `handlers` map, i.e. objects of the form:
|
|
1093
|
-
*
|
|
1094
|
-
* ```js
|
|
1095
|
-
* { "«handlerName»": [«params»] }
|
|
1096
|
-
* ```
|
|
1097
|
-
*
|
|
1098
|
-
* are replaced by the result of call
|
|
1099
|
-
*
|
|
1100
|
-
* ```js
|
|
1101
|
-
* handlers['«handlerName»'](...«params»)
|
|
1102
|
-
* ```
|
|
1103
|
-
*/
|
|
1104
|
-
export const jsOnParse = (
|
|
1105
|
-
/** @type {Record<string, Function>} */ handlers,
|
|
1106
|
-
/** @type {string} */ text
|
|
1107
|
-
) => JSON.parse(text, (key, value) => {
|
|
383
|
+
// src/jsOnParse.js
|
|
384
|
+
var jsOnParse = (handlers, text) => JSON.parse(text, (key, value) => {
|
|
1108
385
|
if (is(Object, value)) {
|
|
1109
|
-
let isSecondKey = false
|
|
1110
|
-
|
|
386
|
+
let isSecondKey = false;
|
|
1111
387
|
for (key in value) {
|
|
1112
388
|
if (isSecondKey) {
|
|
1113
|
-
return value
|
|
389
|
+
return value;
|
|
1114
390
|
}
|
|
1115
|
-
isSecondKey = true
|
|
391
|
+
isSecondKey = true;
|
|
1116
392
|
}
|
|
1117
|
-
|
|
1118
393
|
if (has(key, handlers) && is(Array, value[key])) {
|
|
1119
|
-
return handlers[key](...value[key])
|
|
394
|
+
return handlers[key](...value[key]);
|
|
1120
395
|
}
|
|
1121
396
|
}
|
|
397
|
+
return value;
|
|
398
|
+
});
|
|
1122
399
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
_test.jsOnParse = () => {
|
|
1127
|
-
const handlers = {
|
|
1128
|
-
$hello: (/** @type {string} */ name) => `Hello ${name}!`,
|
|
1129
|
-
$foo: () => 'bar'
|
|
1130
|
-
}
|
|
1131
|
-
const actual = jsOnParse(handlers, `
|
|
1132
|
-
[
|
|
1133
|
-
{
|
|
1134
|
-
"$hello": ["World"]
|
|
1135
|
-
},
|
|
1136
|
-
{
|
|
1137
|
-
"nested": {
|
|
1138
|
-
"$hello": ["nested World"]
|
|
1139
|
-
},
|
|
1140
|
-
"one": 1,
|
|
1141
|
-
"two": 2
|
|
1142
|
-
},
|
|
1143
|
-
{
|
|
1144
|
-
"$foo": []
|
|
1145
|
-
},
|
|
1146
|
-
{
|
|
1147
|
-
"$foo": ["The parent object does not have exactly one property!"],
|
|
1148
|
-
"one": 1,
|
|
1149
|
-
"two": 2
|
|
1150
|
-
}
|
|
1151
|
-
]`)
|
|
1152
|
-
const expected = [
|
|
1153
|
-
'Hello World!',
|
|
1154
|
-
{
|
|
1155
|
-
nested: 'Hello nested World!',
|
|
1156
|
-
one: 1,
|
|
1157
|
-
two: 2
|
|
1158
|
-
},
|
|
1159
|
-
'bar',
|
|
1160
|
-
{
|
|
1161
|
-
$foo: ['The parent object does not have exactly one property!'],
|
|
1162
|
-
one: 1,
|
|
1163
|
-
two: 2
|
|
1164
|
-
}]
|
|
1165
|
-
|
|
1166
|
-
console.assert(eq(actual, expected))
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
const _locale = (
|
|
1170
|
-
/** @type {Record<string, Record<string, string | Record<string, string>>>} */ locales,
|
|
1171
|
-
/** @type {string} */ language,
|
|
1172
|
-
/** @type {string} */ text,
|
|
1173
|
-
/** @type {string} */ version
|
|
1174
|
-
) => {
|
|
1175
|
-
// @ts-expect-error
|
|
1176
|
-
const v = locales?.[language]?.[version]?.[text]
|
|
1177
|
-
|
|
400
|
+
// src/locale.js
|
|
401
|
+
var _locale = (locales, language, text, version) => {
|
|
402
|
+
const v = locales?.[language]?.[version]?.[text];
|
|
1178
403
|
if (is(String, v)) {
|
|
1179
|
-
return v
|
|
404
|
+
return v;
|
|
1180
405
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
/**
|
|
1188
|
-
* ```ts
|
|
1189
|
-
* export function locale(
|
|
1190
|
-
* locales: Record<string, Record<string, string | Record<string, string>>>,
|
|
1191
|
-
* defaultLanguage: string,
|
|
1192
|
-
* languages?: readonly string[]
|
|
1193
|
-
* ): (text?: string, version?: string) => string;
|
|
1194
|
-
* ```
|
|
1195
|
-
*/
|
|
1196
|
-
|
|
1197
|
-
/**
|
|
1198
|
-
* Language translations helper.
|
|
1199
|
-
*/
|
|
1200
|
-
export const locale = (
|
|
1201
|
-
/** @type {Record<string, Record<string, string | Record<string, string>>>} */ locales,
|
|
1202
|
-
/** @type {string} */ defaultLanguage,
|
|
1203
|
-
languages = navigator.languages
|
|
1204
|
-
) => {
|
|
406
|
+
const t = locales?.[language]?.[text];
|
|
407
|
+
return is(String, t) ? t : text;
|
|
408
|
+
};
|
|
409
|
+
var locale = (locales, defaultLanguage, languages = navigator.languages) => {
|
|
1205
410
|
for (const language of languages) {
|
|
1206
411
|
if (has(language, locales)) {
|
|
1207
|
-
|
|
1208
|
-
return _locale.bind(0, locales, language)
|
|
412
|
+
return _locale.bind(0, locales, language);
|
|
1209
413
|
}
|
|
1210
414
|
}
|
|
415
|
+
return _locale.bind(0, locales, defaultLanguage);
|
|
416
|
+
};
|
|
1211
417
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
_test.locale = () => {
|
|
1217
|
-
const locales = {
|
|
1218
|
-
pl: {
|
|
1219
|
-
Password: 'Hasło',
|
|
1220
|
-
button: { Login: 'Zaloguj' }
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
const _ = locale(locales, 'pl', [])
|
|
1224
|
-
|
|
1225
|
-
console.assert(_('Login') === 'Login')
|
|
1226
|
-
console.assert(_('Password') === 'Hasło')
|
|
1227
|
-
|
|
1228
|
-
console.assert(_('Undefined text') === 'Undefined text')
|
|
1229
|
-
|
|
1230
|
-
console.assert(_('Login', 'button') === 'Zaloguj')
|
|
1231
|
-
|
|
1232
|
-
console.assert(_('Password', 'undefined_version') === 'Hasło')
|
|
1233
|
-
console.assert(_('Undefined text', 'undefined_version') === 'Undefined text')
|
|
1234
|
-
|
|
1235
|
-
console.assert(_('toString') === 'toString')
|
|
1236
|
-
console.assert(_('toString', 'undefined_version') === 'toString')
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
/**
|
|
1240
|
-
* ```ts
|
|
1241
|
-
* export function nanolight(
|
|
1242
|
-
* pattern: RegExp,
|
|
1243
|
-
* highlighters: ((chunk: string, index: number) => HArgs[1])[],
|
|
1244
|
-
* code: string
|
|
1245
|
-
* ): HArgs[1][];
|
|
1246
|
-
* ```
|
|
1247
|
-
*/
|
|
1248
|
-
|
|
1249
|
-
/**
|
|
1250
|
-
* A generic helper for syntax highlighting (see also `nanolightJs`).
|
|
1251
|
-
*/
|
|
1252
|
-
export const nanolight = (
|
|
1253
|
-
/** @type {RegExp} */ pattern,
|
|
1254
|
-
/** @type {((chunk: string, index: number) => HArgs[1])[]} */ highlighters,
|
|
1255
|
-
/** @type {string} */ code
|
|
1256
|
-
) => {
|
|
1257
|
-
const /** @type {HArgs[1][]} */ result = []
|
|
1258
|
-
|
|
418
|
+
// src/nanolight.js
|
|
419
|
+
var nanolight = (pattern, highlighters, code) => {
|
|
420
|
+
const result = [];
|
|
1259
421
|
code.split(pattern).forEach((chunk, index) => {
|
|
1260
|
-
index %= highlighters.length
|
|
422
|
+
index %= highlighters.length;
|
|
1261
423
|
if (chunk != null) {
|
|
1262
|
-
result.push(highlighters[index](chunk, index))
|
|
424
|
+
result.push(highlighters[index](chunk, index));
|
|
1263
425
|
}
|
|
1264
|
-
})
|
|
426
|
+
});
|
|
427
|
+
return result;
|
|
428
|
+
};
|
|
1265
429
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
/**
|
|
1270
|
-
* ```ts
|
|
1271
|
-
* export function nanolightJs(codeJs: string): HArgs[1][];
|
|
1272
|
-
* ```
|
|
1273
|
-
*/
|
|
1274
|
-
|
|
1275
|
-
/**
|
|
1276
|
-
* A helper for highlighting JavaScript.
|
|
1277
|
-
*/
|
|
1278
|
-
// @ts-expect-error
|
|
1279
|
-
export const nanolightJs = nanolight.bind(0,
|
|
430
|
+
// src/nanolightJs.js
|
|
431
|
+
var nanolightJs = nanolight.bind(
|
|
432
|
+
0,
|
|
1280
433
|
/('.*?'|".*?"|`[\s\S]*?`)|(\/\/.*?\n|\/\*[\s\S]*?\*\/)|(break|case|catch|const|continue|debugger|default|delete|do|else|eval|export\s+type|export|extends|false|finally|for|from|function|goto|if|import|in|instanceof|is|keyof|let|NaN|new|null|package|return|super|switch|this|throw|true|try|typeof|undefined|var|void|while|with|yield)(?!\w)|([<>=.?:&|!~*/%+-])|(0x[\dabcdef]+|0o[01234567]+|0b[01]+|\d+(?:\.[\d_]+)?(?:e[+-]?[\d_]+)?)|([$\w]+)(?=\()|([$\wąćęłńóśżźĄĆĘŁŃÓŚŻŹ]+)/,
|
|
1281
434
|
[
|
|
1282
|
-
chunk => chunk,
|
|
1283
|
-
|
|
1284
|
-
chunk => [
|
|
1285
|
-
|
|
1286
|
-
chunk => [
|
|
1287
|
-
|
|
1288
|
-
chunk => [
|
|
1289
|
-
|
|
435
|
+
(chunk) => chunk,
|
|
436
|
+
// no match
|
|
437
|
+
(chunk) => ["u", chunk],
|
|
438
|
+
// string literals
|
|
439
|
+
(chunk) => ["em", chunk],
|
|
440
|
+
// comments
|
|
441
|
+
(chunk) => ["b", chunk],
|
|
442
|
+
// keywords
|
|
443
|
+
(chunk) => ["b", chunk],
|
|
444
|
+
// operators
|
|
445
|
+
(chunk) => ["u", chunk],
|
|
446
|
+
// number literals
|
|
447
|
+
(chunk) => ["ins", chunk],
|
|
448
|
+
// function calls
|
|
449
|
+
(chunk) => ["i", chunk]
|
|
450
|
+
// literals
|
|
1290
451
|
]
|
|
1291
|
-
)
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
*/
|
|
1305
|
-
|
|
1306
|
-
/**
|
|
1307
|
-
* A helper for choosing the correct singular and plural.
|
|
1308
|
-
*/
|
|
1309
|
-
export const plUral = (
|
|
1310
|
-
/** @type {string} */ singular,
|
|
1311
|
-
/** @type {string} */ plural2,
|
|
1312
|
-
/** @type {string} */ plural5,
|
|
1313
|
-
/** @type {number} */ value
|
|
1314
|
-
) => {
|
|
1315
|
-
const absValue = Math.abs(value)
|
|
1316
|
-
const absValueMod10 = absValue % 10
|
|
1317
|
-
|
|
1318
|
-
return value === 1
|
|
1319
|
-
? singular
|
|
1320
|
-
: absValue === 12 || absValue === 13 || absValue === 14
|
|
1321
|
-
? plural5
|
|
1322
|
-
: absValueMod10 === 2 || absValueMod10 === 3 || absValueMod10 === 4
|
|
1323
|
-
? plural2
|
|
1324
|
-
: plural5
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
_test.plUral = () => {
|
|
1328
|
-
// @ts-expect-error
|
|
1329
|
-
const auto = plUral.bind(null, 'auto', 'auta', 'aut')
|
|
1330
|
-
|
|
1331
|
-
console.assert(auto(0) === 'aut')
|
|
1332
|
-
console.assert(auto(1) === 'auto')
|
|
1333
|
-
console.assert(auto(17) === 'aut')
|
|
1334
|
-
console.assert(auto(42) === 'auta')
|
|
1335
|
-
|
|
1336
|
-
// @ts-expect-error
|
|
1337
|
-
const car = plUral.bind(null, 'car', 'cars', 'cars')
|
|
1338
|
-
|
|
1339
|
-
console.assert(car(0) === 'cars')
|
|
1340
|
-
console.assert(car(1) === 'car')
|
|
1341
|
-
console.assert(car(17) === 'cars')
|
|
1342
|
-
console.assert(car(42) === 'cars')
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
/**
|
|
1346
|
-
* ```ts
|
|
1347
|
-
* export function pro(ref: any): any;
|
|
1348
|
-
* ```
|
|
1349
|
-
*/
|
|
1350
|
-
|
|
1351
|
-
/**
|
|
1352
|
-
* A helper that protect calls to nested properties by `Proxy` which initializes non-existent values with an empty object.
|
|
1353
|
-
*/
|
|
1354
|
-
// @ts-expect-error
|
|
1355
|
-
export const pro = (/** @type {any} */ ref) => new Proxy(ref, {
|
|
1356
|
-
get (target, key) {
|
|
1357
|
-
return pro(target[key] = target[key] ?? {})
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
// src/plUral.js
|
|
455
|
+
var plUral = (singular, plural2, plural5, value) => {
|
|
456
|
+
const absValue = Math.abs(value);
|
|
457
|
+
const absValueMod10 = absValue % 10;
|
|
458
|
+
return value === 1 ? singular : absValue === 12 || absValue === 13 || absValue === 14 ? plural5 : absValueMod10 === 2 || absValueMod10 === 3 || absValueMod10 === 4 ? plural2 : plural5;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/pro.js
|
|
462
|
+
var pro = (ref) => new Proxy(ref, {
|
|
463
|
+
get(target, key) {
|
|
464
|
+
return pro(target[key] = target[key] ?? {});
|
|
1358
465
|
}
|
|
1359
|
-
})
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
console.assert(eq(ref, { one: { two: { three: { four: 1234 } } } }))
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
/**
|
|
1390
|
-
* ```ts
|
|
1391
|
-
* export function refsInfo(...refs: any[]): [string, string, string[]][];
|
|
1392
|
-
* ```
|
|
1393
|
-
*/
|
|
1394
|
-
|
|
1395
|
-
/**
|
|
1396
|
-
* A helper that provides information about the given `refs`.
|
|
1397
|
-
*
|
|
1398
|
-
* It returns an array of triples: `[«name», «prototype-name», «array-of-own-property-names»]`.
|
|
1399
|
-
*/
|
|
1400
|
-
export const refsInfo = (/** @type {any[]} */ ...refs) => {
|
|
1401
|
-
const /** @type {Set<Function>} */ fns = new Set()
|
|
1402
|
-
|
|
1403
|
-
refs.forEach(ref => {
|
|
1404
|
-
while (is(Function, ref) && !fns.has(ref) && ref.toString?.()?.includes?.('[native code]')) {
|
|
1405
|
-
fns.add(ref)
|
|
1406
|
-
ref = Object.getPrototypeOf(ref)
|
|
1407
|
-
}
|
|
1408
|
-
})
|
|
1409
|
-
|
|
1410
|
-
return Array.from(fns.values()).map(/** @returns {[string, string, string[]]} */ fn => [
|
|
1411
|
-
fn.name,
|
|
1412
|
-
Object.getPrototypeOf(fn)?.name ?? '',
|
|
1413
|
-
Object.getOwnPropertyNames(fn.prototype ?? Object.create(null)).sort()
|
|
1414
|
-
]).sort((a, b) => -(a[0] < b[0]))
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
_test.refsInfo = () => {
|
|
1418
|
-
const info = refsInfo(Array, Function)
|
|
1419
|
-
|
|
1420
|
-
console.assert(info.find(([name]) => name === 'Array')?.[2]?.includes?.('length'))
|
|
1421
|
-
console.assert(info.find(([name]) => name === 'Function')?.[2]?.includes?.('length'))
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
_test['refsInfo: browserFingerprint'] = () => {
|
|
1425
|
-
const browserFingerprint = () => {
|
|
1426
|
-
// @ts-expect-error
|
|
1427
|
-
const refs = Object.getOwnPropertyNames(window).map(name => window[name])
|
|
1428
|
-
const info = refsInfo(...refs)
|
|
1429
|
-
const json = JSON.stringify(info)
|
|
1430
|
-
const hash = Array(32).fill(0)
|
|
1431
|
-
let j = 0
|
|
1432
|
-
|
|
1433
|
-
for (let i = 0; i < json.length; i++) {
|
|
1434
|
-
let charCode = json.charCodeAt(i)
|
|
1435
|
-
|
|
1436
|
-
while (charCode > 0) {
|
|
1437
|
-
hash[j] = hash[j] ^ (charCode & 15)
|
|
1438
|
-
charCode >>= 4
|
|
1439
|
-
j = (j + 1) & 31
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
return hash.map(x => x.toString(16)).join('')
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
console.log(browserFingerprint())
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* ```ts
|
|
1451
|
-
* export function s<T extends keyof SVGElementTagNameMap>(tag: T, ...args: HArgs[1][]): SVGElementTagNameMap[T];
|
|
1452
|
-
* export function s<N extends Node>(node: N, ...args: HArgs[1][]): N;
|
|
1453
|
-
* export function s(...args: HArgs): Node;
|
|
1454
|
-
* ```
|
|
1455
|
-
*/
|
|
1456
|
-
|
|
1457
|
-
/**
|
|
1458
|
-
* A lightweight [HyperScript](https://github.com/hyperhype/hyperscript)-style helper for creating and modifying `SVGElement`s (see also `h`).
|
|
1459
|
-
*
|
|
1460
|
-
* - The first argument of type `string` specifies the tag of the element to be created.
|
|
1461
|
-
* - The first argument of type `Node` specifies the element to be modified.
|
|
1462
|
-
* - All other arguments of type `Record<string, any>` are mappings of attributes and properties.
|
|
1463
|
-
* Keys starting with `$` specify *properties* (without the leading `$`) to be set on the element being created or modified.
|
|
1464
|
-
* (Note that `$` is not a valid attribute name character.)
|
|
1465
|
-
* All other keys specify *attributes* to be set by `setAttributeNS`.
|
|
1466
|
-
* An attribute equal to `false` causes the attribute to be removed by `removeAttributeNS`.
|
|
1467
|
-
* - All other arguments of type `null` or `undefined` are simply ignored.
|
|
1468
|
-
* - All other arguments of type `Node` are appended to the element being created or modified.
|
|
1469
|
-
* - All other arguments of type `string`/`number` are converted to `Text` nodes and appended to the element being created or modified.
|
|
1470
|
-
* - All other arguments of type `HArgs` are passed to `s` and the results are appended to the element being created or modified.
|
|
1471
|
-
*/
|
|
1472
|
-
export const s = _h('http://www.w3.org/2000/svg')
|
|
1473
|
-
|
|
1474
|
-
const _ZEROS = '0'.repeat(16)
|
|
1475
|
-
let _counter = 0
|
|
1476
|
-
|
|
1477
|
-
/**
|
|
1478
|
-
* ```ts
|
|
1479
|
-
* export function uuid1(options?: {
|
|
1480
|
-
* date?: Date;
|
|
1481
|
-
* node?: string;
|
|
1482
|
-
* }): string;
|
|
1483
|
-
* ```
|
|
1484
|
-
*/
|
|
1485
|
-
|
|
1486
|
-
/**
|
|
1487
|
-
* A helper that generates a UUID v1 identifier (with a creation timestamp).
|
|
1488
|
-
*
|
|
1489
|
-
* - The optional `node` parameter should have the format `/^[0123456789abcdef]+$/`.
|
|
1490
|
-
* Its value will be trimmed to last 12 characters and left padded with zeros.
|
|
1491
|
-
*/
|
|
1492
|
-
export const uuid1 = ({
|
|
1493
|
-
date = new Date(),
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// src/refsInfo.js
|
|
469
|
+
var refsInfo = (...refs) => {
|
|
470
|
+
const fns = /* @__PURE__ */ new Set();
|
|
471
|
+
refs.forEach((ref) => {
|
|
472
|
+
while (is(Function, ref) && !fns.has(ref) && ref.toString?.()?.includes?.("[native code]")) {
|
|
473
|
+
fns.add(ref);
|
|
474
|
+
ref = Object.getPrototypeOf(ref);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
return Array.from(fns.values()).map(
|
|
478
|
+
/** @return {[string, string, string[]]} */
|
|
479
|
+
(fn) => [
|
|
480
|
+
fn.name,
|
|
481
|
+
Object.getPrototypeOf(fn)?.name ?? "",
|
|
482
|
+
Object.getOwnPropertyNames(fn.prototype ?? /* @__PURE__ */ Object.create(null)).sort()
|
|
483
|
+
]
|
|
484
|
+
).sort((a, b) => -(a[0] < b[0]));
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/uuid1.js
|
|
488
|
+
var ZEROS = "0".repeat(16);
|
|
489
|
+
var counter = 0;
|
|
490
|
+
var uuid1 = ({
|
|
491
|
+
date = /* @__PURE__ */ new Date(),
|
|
1494
492
|
node = Math.random().toString(16).slice(2)
|
|
1495
493
|
} = {}) => {
|
|
1496
|
-
const time =
|
|
1497
|
-
|
|
1498
|
-
_counter = (_counter + 1) & 0x3fff
|
|
1499
|
-
|
|
494
|
+
const time = ZEROS + (1e4 * (+date + 122192928e5)).toString(16);
|
|
495
|
+
counter = counter + 1 & 16383;
|
|
1500
496
|
return time.slice(-8).concat(
|
|
1501
|
-
|
|
497
|
+
"-",
|
|
1502
498
|
time.slice(-12, -8),
|
|
1503
499
|
// @ts-expect-error
|
|
1504
500
|
-1,
|
|
1505
501
|
time.slice(-15, -12),
|
|
1506
|
-
|
|
1507
|
-
(8 |
|
|
1508
|
-
(
|
|
1509
|
-
|
|
1510
|
-
(
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
console.assert(uuid1({ date: new Date(323325e6) }).startsWith('c1399400-9a71-11bd'))
|
|
1534
|
-
}
|
|
502
|
+
"-",
|
|
503
|
+
(8 | counter >> 12).toString(16),
|
|
504
|
+
(ZEROS + (counter & 4095).toString(16)).slice(-3),
|
|
505
|
+
"-",
|
|
506
|
+
(ZEROS + node).slice(-12)
|
|
507
|
+
);
|
|
508
|
+
};
|
|
509
|
+
export {
|
|
510
|
+
chartable,
|
|
511
|
+
eq,
|
|
512
|
+
escape,
|
|
513
|
+
escapeValues,
|
|
514
|
+
fixTypography,
|
|
515
|
+
h,
|
|
516
|
+
has,
|
|
517
|
+
is,
|
|
518
|
+
jcss,
|
|
519
|
+
jsOnParse,
|
|
520
|
+
locale,
|
|
521
|
+
nanolight,
|
|
522
|
+
nanolightJs,
|
|
523
|
+
plUral,
|
|
524
|
+
pro,
|
|
525
|
+
refsInfo,
|
|
526
|
+
s,
|
|
527
|
+
uuid1
|
|
528
|
+
};
|