@jackens/nnn 2024.2.12 → 2024.2.17

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/chartable.d.ts CHANGED
@@ -6,32 +6,26 @@
6
6
  * - `gapX`: X axis spacing
7
7
  * - `gapY`: Y axis spacing
8
8
  * - `headerColumn`: flag indicating that `table` has a header column (with X axis labels)
9
- * - `id`: chart id
10
- * - `left`: left padding (for data series labels)
9
+ * - `left`: left padding
11
10
  * - `maxY`: number of Y axis lines
12
11
  * - `reverse`: flag to reverse all data series
13
12
  * - `right`: right padding (for data series labels)
14
13
  * - `rotate`: flag to rotate X axis labels
15
- * - `singleScale`: flag to force single scale
16
14
  * - `table`: `HTMLTableElement` to extract data, data series labels and X axis labels
17
- * - `title`: chart title
18
- * - `top`: top padding (for the title)
15
+ * - `top`: top padding
19
16
  */
20
- export function chartable({ bottom, gapX, gapY, headerColumn, id, left, maxY, reverse, right, rotate, singleScale, table, title, top }: {
17
+ export function chartable({ bottom, gapX, gapY, headerColumn, left, maxY, reverse, right, rotate, table, top }: {
21
18
  bottom?: number;
22
19
  gapX?: number;
23
20
  gapY?: number;
24
21
  headerColumn?: boolean;
25
- id?: string;
26
22
  left?: number;
27
23
  maxY?: number;
28
24
  reverse?: boolean;
29
25
  right?: number;
30
26
  rotate?: boolean;
31
- singleScale?: boolean;
32
27
  table: HTMLTableElement;
33
- title?: string;
34
28
  top?: number;
35
- }): SVGSVGElement;
29
+ }): Node;
36
30
 
37
31
  export const tests: {};
package/chartable.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { s } from './h.js'
2
2
 
3
3
  const COLORS = ['#e22', '#e73', '#fc3', '#ad4', '#4d9', '#3be', '#45d', '#c3e']
4
+ const PADDING = 15
4
5
 
5
6
  /**
6
7
  * A helper for creating a chart based on a table (conf. <https://jackens.github.io/nnn/chartable/>).
@@ -10,31 +11,25 @@ const COLORS = ['#e22', '#e73', '#fc3', '#ad4', '#4d9', '#3be', '#45d', '#c3e']
10
11
  * - `gapX`: X axis spacing
11
12
  * - `gapY`: Y axis spacing
12
13
  * - `headerColumn`: flag indicating that `table` has a header column (with X axis labels)
13
- * - `id`: chart id
14
- * - `left`: left padding (for data series labels)
14
+ * - `left`: left padding
15
15
  * - `maxY`: number of Y axis lines
16
16
  * - `reverse`: flag to reverse all data series
17
17
  * - `right`: right padding (for data series labels)
18
18
  * - `rotate`: flag to rotate X axis labels
19
- * - `singleScale`: flag to force single scale
20
19
  * - `table`: `HTMLTableElement` to extract data, data series labels and X axis labels
21
- * - `title`: chart title
22
- * - `top`: top padding (for the title)
20
+ * - `top`: top padding
23
21
  *
24
22
  * @param {{
25
23
  * bottom?: number;
26
24
  * gapX?: number;
27
25
  * gapY?: number;
28
26
  * headerColumn?: boolean;
29
- * id?: string;
30
27
  * left?: number;
31
28
  * maxY?: number;
32
29
  * reverse?: boolean;
33
30
  * right?: number;
34
31
  * rotate?: boolean;
35
- * singleScale?: boolean;
36
32
  * table: HTMLTableElement;
37
- * title?: string;
38
33
  * top?: number;
39
34
  * }} options
40
35
  */
@@ -43,31 +38,28 @@ export const chartable = ({
43
38
  gapX = 70,
44
39
  gapY = 30,
45
40
  headerColumn = false,
46
- id,
47
- left = 200,
41
+ left = 300,
48
42
  maxY = 10,
49
43
  reverse = false,
50
- right = 200,
44
+ right = 100,
51
45
  rotate = false,
52
- singleScale = false,
53
46
  table,
54
- title,
55
- top = 70
47
+ top = PADDING
56
48
  }) => {
57
- const /** @type {number[][]} */ zxY = []
49
+ const /** @type {number[][]} */ zxValues = []
58
50
  const /** @type {string[]} */ xLabels = []
59
51
  const /** @type {string[]} */ zLabels = []
60
52
 
61
53
  table.querySelectorAll('tr').forEach((row, r) =>
62
- // @ts-ignore
54
+ // @ts-expect-error
63
55
  row.querySelectorAll('td,th').forEach((/** @type {HTMLTableCellElement} */ col, c) => {
64
56
  const x = r - 1
65
57
  const z = c - +headerColumn
66
58
  const value = col.innerText
67
59
 
68
60
  if (x >= 0 && z >= 0) {
69
- zxY[z] = zxY[z] ?? []
70
- zxY[z][x] = parseFloat(value)
61
+ zxValues[z] = zxValues[z] ?? []
62
+ zxValues[z][x] = parseFloat(value)
71
63
  } else if (x >= 0 && z < 0) {
72
64
  xLabels[x] = value
73
65
  } else if (x < 0 && z >= 0) {
@@ -77,141 +69,90 @@ export const chartable = ({
77
69
 
78
70
  if (reverse) {
79
71
  xLabels.reverse()
80
- zxY.forEach(xY => xY.reverse())
72
+ zxValues.forEach(xValues => xValues.reverse())
81
73
  }
82
74
 
83
- let bestScales = [Infinity, -Infinity, Infinity, -Infinity, Infinity]
75
+ const maxX = zxValues[0].length
76
+ const xPx = Array.from({ length: maxX }, (_, x) => x * gapX)
77
+ const yPx = Array.from({ length: maxY }, (_, y) => y * gapY)
78
+ const width = gapX * (maxX - 1)
79
+ const height = gapY * (maxY - 1)
80
+ const svgW = left + width + right
81
+ const svgH = top + height + bottom
82
+
83
+ const /** @type {import('./h.js').HArgs1} */ gGraph = ['g', { transform: `translate(${left} ${top})` },
84
+ ...yPx.map(px => ['line', { x1: 0, x2: width, y1: px, y2: px, stroke: '#8888' }]),
85
+ ...xPx.map(px => ['line', { x1: px, x2: px, y1: 0, y2: height, stroke: '#8888' }])]
86
+ const /** @type {import('./h.js').HArgs1} */ gZColors = ['g', { transform: `translate(${PADDING} ${top})` }]
87
+ const /** @type {import('./h.js').HArgs1} */ gZLabels = ['g', { transform: `translate(${2 * PADDING} ${top})` }]
88
+ const /** @type {import('./h.js').HArgs} */ svg = ['svg',
89
+ { viewBox: `0 0 ${svgW} ${svgH}`, width: `${svgW}px`, height: `${svgH}px`, class: 'chartable' },
90
+ gGraph, gZColors, gZLabels]
91
+
92
+ if (xLabels.length > 0) {
93
+ svg.push(['g', { transform: `translate(${left} ${top + height + PADDING})` },
94
+ ...xPx.slice(0).map((xp, x) => ['text',
95
+ rotate
96
+ ? { x: 0, y: -xp, 'text-anchor': 'start', 'alignment-baseline': 'middle', transform: 'rotate(90)' }
97
+ : { x: xp, y: PADDING, 'text-anchor': 'middle', 'alignment-baseline': 'middle' },
98
+ xLabels[x]])])
99
+ }
84
100
 
85
- const ranges = zxY.map(xY => {
86
- xY = xY.filter(y => !isNaN(y))
87
- return [Math.min(...xY), Math.max(...xY)]
88
- })
101
+ const /** @type {[SVGElement, SVGElement, SVGTextElement][]} */ toUpdate = []
89
102
 
90
- if (singleScale) {
91
- bestScales = ranges.reduce(
92
- ([totalMin, totalMax], [min, max]) => [Math.min(totalMin, min), Math.max(totalMax, max)],
93
- bestScales
94
- )
95
- } else {
96
- ranges.forEach(([min1, max0]) => ranges.forEach(([min0, max1]) => {
97
- if (min0 >= min1 && max1 >= max0) {
98
- let min2 = Infinity
99
- let max2 = -Infinity
100
-
101
- ranges.forEach(([min, max]) => {
102
- if (min < min1 || max1 < max) {
103
- if (min2 > min) {
104
- min2 = min
105
- }
106
- if (max2 < max) {
107
- max2 = max
108
- }
109
- }
110
- })
111
-
112
- let cost = 0
113
-
114
- ranges.forEach(([min, max]) => {
115
- if (min < max) {
116
- cost += 1 - (max - min) / (min1 <= min && max <= max1 ? max1 - min1 : max2 - min2)
117
- }
118
- })
119
-
120
- if (bestScales[4] > cost) {
121
- bestScales = [min1, max1, min2, max2, cost]
122
- }
123
- }
124
- }))
125
- }
103
+ const onclick = (/** @type {number} */ z) => toUpdate.forEach(([gLeftYLabels, gRightYLabels, zLabel], z2) => {
104
+ s(gLeftYLabels, { $style: { display: z2 === z ? 'block' : 'none' } })
105
+ s(gRightYLabels, { $style: { display: z2 === z ? 'block' : 'none' } })
106
+ s(zLabel, { $style: { textDecoration: z2 === z ? 'underline' : 'none', cursor: 'pointer' } })
107
+ })
126
108
 
127
- const [min1, max1, min2, max2] = bestScales
128
- const maxX = zxY[0].length
129
- const xi = Array.from({ length: maxX }, (_, x) => x * gapX)
130
- const yi = Array.from({ length: maxY }, (_, y) => y * gapY)
131
- const w = gapX * (maxX - 1)
132
- const h = gapY * (maxY - 1)
133
- const svgW = left + w + right
134
- const svgH = top + h + bottom
135
- const /** @type {import('./h.js').HArgs1} */ graph = ['g', { transform: `translate(${left} ${top})` },
136
- ...yi.map(y => ['line', { x1: 0, x2: w, y1: y, y2: y, stroke: '#8888' }]),
137
- ...xi.map(x => ['line', { x1: x, x2: x, y1: 0, y2: h, stroke: '#8888' }])]
138
- const /** @type {import('./h.js').HArgs1} */ lColors = ['g', { class: 'chartable-z-colors', transform: 'translate(-80 0)' }]
139
- const /** @type {import('./h.js').HArgs1} */ lLabels = ['g', { class: 'chartable-z-labels', transform: 'translate(-95 0)' }]
140
- const /** @type {import('./h.js').HArgs1} */ rColors = ['g', { class: 'chartable-z-colors', transform: 'translate(80 0)' }]
141
- const /** @type {import('./h.js').HArgs1} */ rLabels = ['g', { class: 'chartable-z-labels', transform: 'translate(95 0)' }]
142
- const yLabel = (/** @type {number} */ min, /** @type {number} */ max, /** @type {number} */ y) =>
143
- (min + (max - min) * (maxY - 1 - y) / (maxY - 1)).toFixed(2).replace(/\.?0+$/, '')
144
-
145
- zxY.forEach((xa, z) => {
146
- const cls = `chartable-z-${z + 1}`
109
+ zxValues.forEach((values, z) => {
147
110
  const fill = COLORS[z % COLORS.length]
148
- let [min, max] = ranges[z]
149
-
150
- if (min1 <= min && max <= max1) {
151
- min = min1
152
- max = max1
153
- lColors.push(['circle', { class: cls, cx: 0, cy: gapY * (lColors.length - 2), r: 5, fill }])
154
- lLabels.push(['text',
155
- { x: 0, y: gapY * (lLabels.length - 2), 'text-anchor': 'end', 'alignment-baseline': 'middle' },
156
- zLabels[z]])
157
- } else {
158
- min = min2
159
- max = max2
160
- rColors.push(['circle', { class: cls, cx: 0, cy: gapY * (rColors.length - 2), r: 5, fill }])
161
- rLabels.push(['text',
162
- { x: 0, y: gapY * (rLabels.length - 2), 'text-anchor': 'start', 'alignment-baseline': 'middle' },
163
- zLabels[z]])
164
- }
165
-
166
- const y = xa.map(x => isNaN(x) ? x : h * (max - x) / (max - min))
167
-
168
- xi.forEach((x, i) => {
169
- if (!isNaN(y[i])) {
170
- graph.push(['circle', { class: cls, cx: x, cy: y[i], r: 5, fill }])
171
-
172
- if (!isNaN(y[i - 1])) {
173
- graph.push(['line', { class: cls, x1: x, x2: xi[i - 1], y1: y[i], y2: y[i - 1], stroke: fill }])
111
+ const minValue = Math.min(...values.filter(value => !isNaN(value)))
112
+ const maxValue = Math.max(...values.filter(value => !isNaN(value)))
113
+
114
+ const valuesPx = values.map(value => isNaN(value) ? value : height * (maxValue - value) / (maxValue - minValue))
115
+
116
+ const gLeftYLabels = s('g', { transform: `translate(${left - PADDING} ${top})` },
117
+ ...yPx.map((yp, y) => ['text',
118
+ { x: 0, y: yp, 'text-anchor': 'end', 'alignment-baseline': 'middle' },
119
+ (maxValue - (maxValue - minValue) / maxY * (y + 1)).toFixed(2)]))
120
+
121
+ const gRightYLabels = s('g', { transform: `translate(${left + width + PADDING} ${top})` },
122
+ ...yPx.map((yp, y) => ['text',
123
+ { x: 0, y: yp, 'text-anchor': 'start', 'alignment-baseline': 'middle' },
124
+ (maxValue - (maxValue - minValue) / maxY * (y + 1)).toFixed(2)]))
125
+
126
+ svg.push(gLeftYLabels, gRightYLabels)
127
+
128
+ const zLabel = s('text', {
129
+ x: 0,
130
+ y: gapY * (gZLabels.length - 2),
131
+ 'text-anchor': 'start',
132
+ 'alignment-baseline': 'middle',
133
+ $onclick: () => onclick(z)
134
+ },
135
+ zLabels[z])
136
+
137
+ gZLabels.push(zLabel)
138
+ gZColors.push(['circle', { cx: 0, cy: gapY * (gZColors.length - 2), r: 5, fill }])
139
+
140
+ toUpdate.push([gLeftYLabels, gRightYLabels, zLabel])
141
+
142
+ xPx.forEach((px, x) => {
143
+ if (!isNaN(valuesPx[x])) {
144
+ gGraph.push(['g', ['circle', { cx: px, cy: valuesPx[x], r: 5, fill }]])
145
+
146
+ if (!isNaN(valuesPx[x - 1])) {
147
+ gGraph.push(['line', { x1: px, x2: xPx[x - 1], y1: valuesPx[x], y2: valuesPx[x - 1], stroke: fill }])
174
148
  }
175
149
  }
176
150
  })
177
151
  })
178
152
 
179
- return s('svg', {
180
- viewBox: `0 0 ${svgW} ${svgH}`,
181
- width: `${svgW}px`,
182
- height: `${svgH}px`,
183
- class: 'chartable'
184
- }, id != null ? { id } : null, title != null
185
- ? ['text',
186
- { class: 'chartable-title', x: svgW / 2, y: top / 2, 'text-anchor': 'middle', 'alignment-baseline': 'middle' },
187
- title]
188
- : null,
189
- graph,
190
- min1 < Infinity
191
- ? ['g', { class: 'chartable-left', transform: `translate(${left} ${top})` },
192
- ['g', { class: 'chartable-y-labels', transform: 'translate(-15 0)' },
193
- ...yi.map((y, i) => ['text',
194
- { x: 0, y, 'text-anchor': 'end', 'alignment-baseline': 'middle' },
195
- yLabel(min1, max1, i)])],
196
- lColors, lLabels]
197
- : null,
198
- min2 < Infinity
199
- ? ['g', { class: 'chartable-right', transform: `translate(${left + w} ${top})` },
200
- ['g', { class: 'chartable-y-labels', transform: 'translate(15 0)' },
201
- ...yi.map((y, i) => ['text',
202
- { x: 0, y, 'text-anchor': 'start', 'alignment-baseline': 'middle' },
203
- yLabel(min2, max2, i)])],
204
- rColors, rLabels]
205
- : null,
206
- xLabels.length > 0
207
- ? ['g', { transform: `translate(${left} ${top + h})` },
208
- ['g', { class: 'chartable-x-labels', transform: 'translate(0 15)' },
209
- ...xi.slice(0).map((x, i) => ['text', rotate
210
- ? { x: 0, y: -x, 'text-anchor': 'start', 'alignment-baseline': 'hanging', transform: 'rotate(90)' }
211
- : { x, y: 0, 'text-anchor': 'middle', 'alignment-baseline': 'hanging' },
212
- xLabels[i]])]]
213
- : null
214
- )
153
+ onclick(0)
154
+
155
+ return s(...svg)
215
156
  }
216
157
 
217
158
  export const tests = {}
package/escape.js CHANGED
@@ -23,7 +23,7 @@ export const escape = (
23
23
 
24
24
  export const tests = {
25
25
  escape: (/** @type {import('bun:test').Expect} */ expect) => {
26
- // @ts-ignore
26
+ // @ts-expect-error
27
27
  const /** @type {EscapeMap} */ escapeMap = new Map([
28
28
  [undefined, () => 'NULL'],
29
29
  [Array, (/** @type {any[]} */ values) => escapeValues(escapeMap, values).join(', ')],
@@ -33,7 +33,7 @@ export const tests = {
33
33
  [String, (/** @type {string} */ value) => `'${value.replace(/'/g, "''")}'`]
34
34
  ])
35
35
 
36
- // @ts-ignore
36
+ // @ts-expect-error
37
37
  const sql = escape.bind(null, escapeMap)
38
38
 
39
39
  const actual = sql`
package/h.js CHANGED
@@ -44,10 +44,10 @@ const _h = (/** @type {string?=} */ namespaceURI) => {
44
44
  if (arg instanceof Node) {
45
45
  child = arg
46
46
  } else if (is(String, arg) || is(Number, arg)) {
47
- // @ts-ignore
47
+ // @ts-expect-error
48
48
  child = new Text(arg)
49
49
  } else if (is(Array, arg)) {
50
- // @ts-ignore
50
+ // @ts-expect-error
51
51
  child = h(...arg)
52
52
  } else if (arg != null) {
53
53
  for (const name in arg) {
@@ -57,19 +57,19 @@ const _h = (/** @type {string?=} */ namespaceURI) => {
57
57
  const name1 = name.slice(1)
58
58
 
59
59
  if (is(Object, value)) {
60
- // @ts-ignore
60
+ // @ts-expect-error
61
61
  node[name1] = node[name1] ?? {}
62
- // @ts-ignore
62
+ // @ts-expect-error
63
63
  Object.assign(node[name1], value)
64
64
  } else {
65
- // @ts-ignore
65
+ // @ts-expect-error
66
66
  node[name1] = value
67
67
  }
68
68
  } else if (node instanceof Element) {
69
69
  const indexOfColon = name.indexOf(':')
70
70
 
71
71
  if (indexOfColon >= 0) {
72
- // @ts-ignore
72
+ // @ts-expect-error
73
73
  const /** @type {string=} */ ns = NS[name.slice(0, indexOfColon)]
74
74
 
75
75
  if (ns != null) {
@@ -197,17 +197,17 @@ export const tests = {
197
197
  'h: nested properties': (/** @type {import('bun:test').Expect} */ expect) => {
198
198
  const div = h('div')
199
199
 
200
- // @ts-ignore
200
+ // @ts-expect-error
201
201
  expect(div.key).toBeUndefined()
202
202
 
203
203
  h(div, { $key: { one: 1 } })
204
204
 
205
- // @ts-ignore
205
+ // @ts-expect-error
206
206
  expect(div.key).toEqual({ one: 1 })
207
207
 
208
208
  h(div, { $key: { two: 2 } })
209
209
 
210
- // @ts-ignore
210
+ // @ts-expect-error
211
211
  expect(div.key).toEqual({ one: 1, two: 2 })
212
212
  }
213
213
  }
package/has.js CHANGED
@@ -16,7 +16,7 @@ export const tests = {
16
16
  expect('null' in obj).toBe(true)
17
17
  expect(has('null', obj)).toBe(true)
18
18
 
19
- // @ts-ignore
19
+ // @ts-expect-error
20
20
  expect(null in obj).toBe(true)
21
21
  expect(has(null, obj)).toBe(false)
22
22
 
@@ -28,7 +28,7 @@ export const tests = {
28
28
  let typeError
29
29
 
30
30
  try {
31
- // @ts-ignore
31
+ // @ts-expect-error
32
32
  'key' in null
33
33
  } catch (error) {
34
34
  typeError = error
package/is.js CHANGED
@@ -53,7 +53,7 @@ export const tests = {
53
53
 
54
54
  const fakeFooBar = {}
55
55
 
56
- // @ts-ignore
56
+ // @ts-expect-error
57
57
  fakeFooBar[Symbol.toStringTag] = FooBar.name
58
58
 
59
59
  expect(is(FooBar, fakeFooBar)).toBe(false)
package/jsOnParse.js CHANGED
@@ -31,7 +31,7 @@ export const jsOnParse = (
31
31
  }
32
32
 
33
33
  if (has(key, handlers) && is(Array, value[key])) {
34
- // @ts-ignore
34
+ // @ts-expect-error
35
35
  return handlers[key](...value[key])
36
36
  }
37
37
  }
package/nanolightJs.js CHANGED
@@ -4,7 +4,7 @@ import { nanolight } from './nanolight.js'
4
4
  /**
5
5
  * A helper for highlighting JavaScript.
6
6
  */
7
- // @ts-ignore
7
+ // @ts-expect-error
8
8
  export const nanolightJs = nanolight.bind(0,
9
9
  /('.*?'|".*?"|`[\s\S]*?`)|(\/\/.*?\n|\/\*[\s\S]*?\*\/)|(any|bigint|break|boolean|case|catch|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_]+)?(?:e[+-]?[\d_]+)?)|([$\w]+)(?=\()|([$\wąćęłńóśżźĄĆĘŁŃÓŚŻŹ]+)/,
10
10
  [
package/package.json CHANGED
@@ -40,5 +40,5 @@
40
40
  "types": "nnn.d.ts",
41
41
  "name": "@jackens/nnn",
42
42
  "type": "module",
43
- "version": "2024.2.12"
43
+ "version": "2024.2.17"
44
44
  }
package/pick.js CHANGED
@@ -2,45 +2,19 @@
2
2
  * A helper that implements TypeScript’s `Pick` utility type.
3
3
  *
4
4
  * @type {<T extends Partial<Record<PropertyKey, any>>, K extends (keyof T)[]>(obj: T, keys: K) => Pick<T, K[number]>}
5
- * @template {Partial<Record<PropertyKey, any>>} T
6
- * @template {keyof T} K
7
5
  */
8
- export const pick = (/** @type {T} */ obj, /** @type {K[]} */ keys) => {
9
- const result = {}
10
-
11
- for (const key in obj) {
12
- // @ts-ignore
13
- if (keys.includes(key)) {
14
- // @ts-ignore
15
- result[key] = obj[key]
16
- }
17
- }
18
-
19
- // @ts-ignore
20
- return result
21
- }
6
+ export const pick = (/** @type {Partial<Record<PropertyKey, any>>} */ obj, /** @type {any[]} */ keys) =>
7
+ // @ts-expect-error
8
+ Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)))
22
9
 
23
10
  /**
24
11
  * A helper that implements TypeScript’s `Omit` utility type.
25
12
  *
26
13
  * @type {<T extends Partial<Record<PropertyKey, any>>, K extends (keyof T)[]>(obj: T, keys: K) => Omit<T, K[number]>}
27
- * @template {Partial<Record<PropertyKey, any>>} T
28
- * @template {keyof T} K
29
14
  */
30
- export const omit = (/** @type {T} */ obj, /** @type {K[]} */ keys) => {
31
- const result = {}
32
-
33
- for (const key in obj) {
34
- // @ts-ignore
35
- if (!keys.includes(key)) {
36
- // @ts-ignore
37
- result[key] = obj[key]
38
- }
39
- }
40
-
41
- // @ts-ignore
42
- return result
43
- }
15
+ export const omit = (/** @type {Partial<Record<PropertyKey, any>>} */ obj, /** @type {any[]} */ keys) =>
16
+ // @ts-expect-error
17
+ Object.fromEntries(Object.entries(obj).filter(([key]) => !keys.includes(key)))
44
18
 
45
19
  export const tests = {
46
20
  pick: (/** @type {import('bun:test').Expect} */ expect) => {
package/plUral.js CHANGED
@@ -20,7 +20,7 @@ export const plUral = (
20
20
 
21
21
  export const tests = {
22
22
  plUral: (/** @type {import('bun:test').Expect} */ expect) => {
23
- // @ts-ignore
23
+ // @ts-expect-error
24
24
  const auto = plUral.bind(null, 'auto', 'auta', 'aut')
25
25
 
26
26
  expect(auto(0)).toEqual('aut')
@@ -28,7 +28,7 @@ export const tests = {
28
28
  expect(auto(17)).toEqual('aut')
29
29
  expect(auto(42)).toEqual('auta')
30
30
 
31
- // @ts-ignore
31
+ // @ts-expect-error
32
32
  const car = plUral.bind(null, 'car', 'cars', 'cars')
33
33
 
34
34
  expect(car(0)).toEqual('cars')
package/pro.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * A helper that protects calls to nested properties by a `Proxy` that initializes non-existent values with an empty object.
3
3
  */
4
- // @ts-ignore
4
+ // @ts-expect-error
5
5
  export const pro = (/** @type {any} */ ref) => new Proxy(ref, {
6
6
  get (target, key) {
7
7
  return pro(target[key] = target[key] ?? {})
package/readme.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Jackens’ JavaScript helpers.
4
4
 
5
- <sub>Version: <code class="version">2024.2.12</code></sub>
5
+ <sub>Version: <code class="version">2024.2.17</code></sub>
6
6
 
7
7
  ## Installation
8
8
 
@@ -103,22 +103,19 @@ The type of arguments of the `jcss` helper.
103
103
  ### chartable
104
104
 
105
105
  ```ts
106
- export function chartable({ bottom, gapX, gapY, headerColumn, id, left, maxY, reverse, right, rotate, singleScale, table, title, top }: {
106
+ export function chartable({ bottom, gapX, gapY, headerColumn, left, maxY, reverse, right, rotate, table, top }: {
107
107
  bottom?: number;
108
108
  gapX?: number;
109
109
  gapY?: number;
110
110
  headerColumn?: boolean;
111
- id?: string;
112
111
  left?: number;
113
112
  maxY?: number;
114
113
  reverse?: boolean;
115
114
  right?: number;
116
115
  rotate?: boolean;
117
- singleScale?: boolean;
118
116
  table: HTMLTableElement;
119
- title?: string;
120
117
  top?: number;
121
- }): SVGSVGElement;
118
+ }): Node;
122
119
  ```
123
120
 
124
121
  A helper for creating a chart based on a table (conf. <https://jackens.github.io/nnn/chartable/>).
@@ -128,16 +125,13 @@ Options:
128
125
  - `gapX`: X axis spacing
129
126
  - `gapY`: Y axis spacing
130
127
  - `headerColumn`: flag indicating that `table` has a header column (with X axis labels)
131
- - `id`: chart id
132
- - `left`: left padding (for data series labels)
128
+ - `left`: left padding
133
129
  - `maxY`: number of Y axis lines
134
130
  - `reverse`: flag to reverse all data series
135
131
  - `right`: right padding (for data series labels)
136
132
  - `rotate`: flag to rotate X axis labels
137
- - `singleScale`: flag to force single scale
138
133
  - `table`: `HTMLTableElement` to extract data, data series labels and X axis labels
139
- - `title`: chart title
140
- - `top`: top padding (for the title)
134
+ - `top`: top padding
141
135
 
142
136
  ### eq
143
137
 
package/refsInfo.js CHANGED
@@ -32,7 +32,7 @@ export const tests = {
32
32
 
33
33
  'refsInfo: browserFingerprint': (/** @type {import('bun:test').Expect} */ expect) => {
34
34
  const browserFingerprint = () => {
35
- // @ts-ignore
35
+ // @ts-expect-error
36
36
  const refs = Object.getOwnPropertyNames(window).map(name => window[name])
37
37
  const info = refsInfo(...refs)
38
38
  const json = JSON.stringify(info)
package/uuid1.js CHANGED
@@ -18,7 +18,7 @@ export const uuid1 = ({
18
18
  return time.slice(-8).concat(
19
19
  '-',
20
20
  time.slice(-12, -8),
21
- // @ts-ignore
21
+ // @ts-expect-error
22
22
  -1,
23
23
  time.slice(-15, -12),
24
24
  '-',