blessed-components 0.0.1 → 1.1.0
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/README.md +30 -44
- package/ROADMAP.md +695 -302
- package/dist/index.cjs +132 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +124 -0
- package/dist/index.js.map +1 -1
- package/dist/progress-bar/blessed.cjs +60 -0
- package/dist/progress-bar/blessed.cjs.map +1 -0
- package/dist/progress-bar/blessed.d.cts +129 -0
- package/dist/progress-bar/blessed.d.ts +129 -0
- package/dist/progress-bar/blessed.js +54 -0
- package/dist/progress-bar/blessed.js.map +1 -0
- package/dist/progress-bar/index.cjs +33 -0
- package/dist/progress-bar/index.cjs.map +1 -0
- package/dist/progress-bar/index.d.cts +181 -0
- package/dist/progress-bar/index.d.ts +181 -0
- package/dist/progress-bar/index.js +31 -0
- package/dist/progress-bar/index.js.map +1 -0
- package/dist/sparkline/blessed.cjs +86 -0
- package/dist/sparkline/blessed.cjs.map +1 -0
- package/dist/sparkline/blessed.d.cts +90 -0
- package/dist/sparkline/blessed.d.ts +90 -0
- package/dist/sparkline/blessed.js +80 -0
- package/dist/sparkline/blessed.js.map +1 -0
- package/dist/sparkline/index.cjs +59 -0
- package/dist/sparkline/index.cjs.map +1 -0
- package/dist/sparkline/index.d.cts +123 -0
- package/dist/sparkline/index.d.ts +123 -0
- package/dist/sparkline/index.js +57 -0
- package/dist/sparkline/index.js.map +1 -0
- package/package.json +40 -12
- package/src/components/progress-bar/README.md +193 -0
- package/src/components/sparkline/README.md +171 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options accepted by {@link renderSparkline}.
|
|
3
|
+
*
|
|
4
|
+
* Samples are kept in input order, scaled into `characters`, and reduced to at
|
|
5
|
+
* most `width` cells. Values outside an explicit domain are clamped.
|
|
6
|
+
*/
|
|
7
|
+
interface RenderSparklineOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Ordered glyph scale from lowest to highest intensity.
|
|
10
|
+
*
|
|
11
|
+
* At least two non-empty strings are required. Glyphs should occupy one
|
|
12
|
+
* terminal cell to keep output width predictable.
|
|
13
|
+
*
|
|
14
|
+
* @defaultValue `['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']`
|
|
15
|
+
*/
|
|
16
|
+
characters?: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* Text returned when `values` is empty.
|
|
19
|
+
*
|
|
20
|
+
* Metadata is not rendered for an empty series.
|
|
21
|
+
*
|
|
22
|
+
* @defaultValue `'No data'`
|
|
23
|
+
*/
|
|
24
|
+
emptyText?: string;
|
|
25
|
+
/** Optional heading label rendered above the graph. */
|
|
26
|
+
label?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Explicit upper domain bound.
|
|
29
|
+
*
|
|
30
|
+
* When omitted, the largest input value is used. If either explicit bound is
|
|
31
|
+
* supplied, the resolved `max` must be greater than the resolved `min`.
|
|
32
|
+
*/
|
|
33
|
+
max?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Explicit lower domain bound.
|
|
36
|
+
*
|
|
37
|
+
* When omitted, the smallest input value is used.
|
|
38
|
+
*/
|
|
39
|
+
min?: number;
|
|
40
|
+
/** Optional text appended to the graph line after one space. */
|
|
41
|
+
summary?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Optional primary value rendered next to `label`.
|
|
44
|
+
*
|
|
45
|
+
* This value is presentation metadata and does not affect graph scaling.
|
|
46
|
+
*/
|
|
47
|
+
value?: number | string;
|
|
48
|
+
/**
|
|
49
|
+
* Ordered finite numeric samples.
|
|
50
|
+
*
|
|
51
|
+
* The input array is never mutated.
|
|
52
|
+
*/
|
|
53
|
+
values: readonly number[];
|
|
54
|
+
/**
|
|
55
|
+
* Positive integer maximum number of graph cells.
|
|
56
|
+
*
|
|
57
|
+
* Longer series are downsampled. Shorter series are not padded.
|
|
58
|
+
*/
|
|
59
|
+
width: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Renders a deterministic terminal sparkline.
|
|
63
|
+
*
|
|
64
|
+
* This pure renderer does not import Blessed. Use the
|
|
65
|
+
* `blessed-components/sparkline` subpath to keep the Blessed adapter outside
|
|
66
|
+
* the module graph.
|
|
67
|
+
*
|
|
68
|
+
* When downsampling is required, the series is divided into ordered buckets
|
|
69
|
+
* and each bucket keeps its maximum value. This preserves narrow peaks. A
|
|
70
|
+
* constant series uses the middle glyph to avoid division by zero.
|
|
71
|
+
*
|
|
72
|
+
* Output shape:
|
|
73
|
+
*
|
|
74
|
+
* ```text
|
|
75
|
+
* [optional label and value]
|
|
76
|
+
* [graph][optional summary]
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* With no metadata, only the graph line is returned.
|
|
80
|
+
*
|
|
81
|
+
* @param options - Series, domain, width, glyphs, and optional metadata.
|
|
82
|
+
* @returns Plain text without ANSI sequences or Blessed tags.
|
|
83
|
+
*
|
|
84
|
+
* @throws `RangeError`
|
|
85
|
+
* Thrown when `width` is not a positive integer.
|
|
86
|
+
*
|
|
87
|
+
* @throws `RangeError`
|
|
88
|
+
* Thrown when fewer than two non-empty characters are provided.
|
|
89
|
+
*
|
|
90
|
+
* @throws `RangeError`
|
|
91
|
+
* Thrown when any sample or domain bound is not finite.
|
|
92
|
+
*
|
|
93
|
+
* @throws `RangeError`
|
|
94
|
+
* Thrown when an explicit domain does not have `max > min`.
|
|
95
|
+
*
|
|
96
|
+
* @example Basic series
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* import { renderSparkline } from 'blessed-components/sparkline';
|
|
100
|
+
*
|
|
101
|
+
* renderSparkline({
|
|
102
|
+
* values: [1, 2, 3, 4, 5, 6, 7, 8],
|
|
103
|
+
* width: 8,
|
|
104
|
+
* });
|
|
105
|
+
* // "▁▂▃▄▅▆▇█"
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @example Metadata and summary
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* renderSparkline({
|
|
112
|
+
* label: 'Downloads',
|
|
113
|
+
* value: '25.2M',
|
|
114
|
+
* values: [1, 3, 2, 8],
|
|
115
|
+
* width: 4,
|
|
116
|
+
* summary: 'peak: 8M',
|
|
117
|
+
* });
|
|
118
|
+
* // "Downloads 25.2M\n▁▃▂█ peak: 8M"
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
declare function renderSparkline({ characters, emptyText, label, max: configuredMax, min: configuredMin, summary, value: displayValue, values, width, }: RenderSparklineOptions): string;
|
|
122
|
+
|
|
123
|
+
export { type RenderSparklineOptions, renderSparkline };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/components/sparkline/index.ts
|
|
2
|
+
var DEFAULT_CHARACTERS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
3
|
+
function renderSparkline({
|
|
4
|
+
characters = DEFAULT_CHARACTERS,
|
|
5
|
+
emptyText = "No data",
|
|
6
|
+
label,
|
|
7
|
+
max: configuredMax,
|
|
8
|
+
min: configuredMin,
|
|
9
|
+
summary,
|
|
10
|
+
value: displayValue,
|
|
11
|
+
values,
|
|
12
|
+
width
|
|
13
|
+
}) {
|
|
14
|
+
if (!Number.isInteger(width) || width < 1) {
|
|
15
|
+
throw new RangeError("Sparkline width must be a positive integer.");
|
|
16
|
+
}
|
|
17
|
+
if (characters.length < 2 || characters.some((character) => character.length === 0)) {
|
|
18
|
+
throw new RangeError("Sparkline characters must contain at least two non-empty strings.");
|
|
19
|
+
}
|
|
20
|
+
if (values.some((value) => !Number.isFinite(value))) {
|
|
21
|
+
throw new RangeError("Sparkline values must be finite.");
|
|
22
|
+
}
|
|
23
|
+
if (values.length === 0) {
|
|
24
|
+
return emptyText;
|
|
25
|
+
}
|
|
26
|
+
const min = configuredMin ?? Math.min(...values);
|
|
27
|
+
const max = configuredMax ?? Math.max(...values);
|
|
28
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max < min) {
|
|
29
|
+
throw new RangeError("Sparkline max must be greater than or equal to min.");
|
|
30
|
+
}
|
|
31
|
+
if ((configuredMin !== void 0 || configuredMax !== void 0) && max === min) {
|
|
32
|
+
throw new RangeError("Sparkline explicit max must be greater than min.");
|
|
33
|
+
}
|
|
34
|
+
const constantIndex = Math.floor((characters.length - 1) / 2);
|
|
35
|
+
const samples = values.length <= width ? values : Array.from({ length: width }, (_, index) => {
|
|
36
|
+
const start = Math.floor(index * values.length / width);
|
|
37
|
+
const end = Math.floor((index + 1) * values.length / width);
|
|
38
|
+
return Math.max(...values.slice(start, end));
|
|
39
|
+
});
|
|
40
|
+
const sparkline = samples.map((value) => {
|
|
41
|
+
if (min === max) {
|
|
42
|
+
return characters[constantIndex];
|
|
43
|
+
}
|
|
44
|
+
const clampedValue = Math.min(max, Math.max(min, value));
|
|
45
|
+
const ratio = (clampedValue - min) / (max - min);
|
|
46
|
+
const index = clampedValue >= max ? characters.length - 1 : Math.floor(ratio * (characters.length - 1));
|
|
47
|
+
return characters[index];
|
|
48
|
+
}).join("");
|
|
49
|
+
const heading = [label, displayValue].filter((part) => part !== void 0).join(" ");
|
|
50
|
+
const chartLine = summary === void 0 ? sparkline : `${sparkline} ${summary}`;
|
|
51
|
+
return heading.length === 0 ? chartLine : `${heading}
|
|
52
|
+
${chartLine}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { renderSparkline };
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
57
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/sparkline/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["const DEFAULT_CHARACTERS = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'] as const;\n\n/**\n * Options accepted by {@link renderSparkline}.\n *\n * Samples are kept in input order, scaled into `characters`, and reduced to at\n * most `width` cells. Values outside an explicit domain are clamped.\n */\nexport interface RenderSparklineOptions {\n /**\n * Ordered glyph scale from lowest to highest intensity.\n *\n * At least two non-empty strings are required. Glyphs should occupy one\n * terminal cell to keep output width predictable.\n *\n * @defaultValue `['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█']`\n */\n characters?: readonly string[];\n\n /**\n * Text returned when `values` is empty.\n *\n * Metadata is not rendered for an empty series.\n *\n * @defaultValue `'No data'`\n */\n emptyText?: string;\n\n /** Optional heading label rendered above the graph. */\n label?: string;\n\n /**\n * Explicit upper domain bound.\n *\n * When omitted, the largest input value is used. If either explicit bound is\n * supplied, the resolved `max` must be greater than the resolved `min`.\n */\n max?: number;\n\n /**\n * Explicit lower domain bound.\n *\n * When omitted, the smallest input value is used.\n */\n min?: number;\n\n /** Optional text appended to the graph line after one space. */\n summary?: string;\n\n /**\n * Optional primary value rendered next to `label`.\n *\n * This value is presentation metadata and does not affect graph scaling.\n */\n value?: number | string;\n\n /**\n * Ordered finite numeric samples.\n *\n * The input array is never mutated.\n */\n values: readonly number[];\n\n /**\n * Positive integer maximum number of graph cells.\n *\n * Longer series are downsampled. Shorter series are not padded.\n */\n width: number;\n}\n\n/**\n * Renders a deterministic terminal sparkline.\n *\n * This pure renderer does not import Blessed. Use the\n * `blessed-components/sparkline` subpath to keep the Blessed adapter outside\n * the module graph.\n *\n * When downsampling is required, the series is divided into ordered buckets\n * and each bucket keeps its maximum value. This preserves narrow peaks. A\n * constant series uses the middle glyph to avoid division by zero.\n *\n * Output shape:\n *\n * ```text\n * [optional label and value]\n * [graph][optional summary]\n * ```\n *\n * With no metadata, only the graph line is returned.\n *\n * @param options - Series, domain, width, glyphs, and optional metadata.\n * @returns Plain text without ANSI sequences or Blessed tags.\n *\n * @throws `RangeError`\n * Thrown when `width` is not a positive integer.\n *\n * @throws `RangeError`\n * Thrown when fewer than two non-empty characters are provided.\n *\n * @throws `RangeError`\n * Thrown when any sample or domain bound is not finite.\n *\n * @throws `RangeError`\n * Thrown when an explicit domain does not have `max > min`.\n *\n * @example Basic series\n *\n * ```ts\n * import { renderSparkline } from 'blessed-components/sparkline';\n *\n * renderSparkline({\n * values: [1, 2, 3, 4, 5, 6, 7, 8],\n * width: 8,\n * });\n * // \"▁▂▃▄▅▆▇█\"\n * ```\n *\n * @example Metadata and summary\n *\n * ```ts\n * renderSparkline({\n * label: 'Downloads',\n * value: '25.2M',\n * values: [1, 3, 2, 8],\n * width: 4,\n * summary: 'peak: 8M',\n * });\n * // \"Downloads 25.2M\\n▁▃▂█ peak: 8M\"\n * ```\n */\nexport function renderSparkline({\n characters = DEFAULT_CHARACTERS,\n emptyText = 'No data',\n label,\n max: configuredMax,\n min: configuredMin,\n summary,\n value: displayValue,\n values,\n width,\n}: RenderSparklineOptions): string {\n if (!Number.isInteger(width) || width < 1) {\n throw new RangeError('Sparkline width must be a positive integer.');\n }\n\n if (characters.length < 2 || characters.some((character) => character.length === 0)) {\n throw new RangeError('Sparkline characters must contain at least two non-empty strings.');\n }\n\n if (values.some((value) => !Number.isFinite(value))) {\n throw new RangeError('Sparkline values must be finite.');\n }\n\n if (values.length === 0) {\n return emptyText;\n }\n\n const min = configuredMin ?? Math.min(...values);\n const max = configuredMax ?? Math.max(...values);\n\n if (!Number.isFinite(min) || !Number.isFinite(max) || max < min) {\n throw new RangeError('Sparkline max must be greater than or equal to min.');\n }\n\n if ((configuredMin !== undefined || configuredMax !== undefined) && max === min) {\n throw new RangeError('Sparkline explicit max must be greater than min.');\n }\n\n const constantIndex = Math.floor((characters.length - 1) / 2);\n const samples =\n values.length <= width\n ? values\n : Array.from({ length: width }, (_, index) => {\n const start = Math.floor((index * values.length) / width);\n const end = Math.floor(((index + 1) * values.length) / width);\n\n return Math.max(...values.slice(start, end));\n });\n const sparkline = samples\n .map((value) => {\n if (min === max) {\n return characters[constantIndex];\n }\n\n const clampedValue = Math.min(max, Math.max(min, value));\n const ratio = (clampedValue - min) / (max - min);\n const index =\n clampedValue >= max ? characters.length - 1 : Math.floor(ratio * (characters.length - 1));\n\n return characters[index];\n })\n .join('');\n const heading = [label, displayValue].filter((part) => part !== undefined).join(' ');\n const chartLine = summary === undefined ? sparkline : `${sparkline} ${summary}`;\n\n return heading.length === 0 ? chartLine : `${heading}\\n${chartLine}`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blessed-components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Composable, typed terminal UI components for Blessed.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"blessed",
|
|
@@ -28,13 +28,34 @@
|
|
|
28
28
|
"types": "./dist/index.d.ts",
|
|
29
29
|
"import": "./dist/index.js",
|
|
30
30
|
"require": "./dist/index.cjs"
|
|
31
|
+
},
|
|
32
|
+
"./progress-bar": {
|
|
33
|
+
"types": "./dist/progress-bar/index.d.ts",
|
|
34
|
+
"import": "./dist/progress-bar/index.js",
|
|
35
|
+
"require": "./dist/progress-bar/index.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./progress-bar/blessed": {
|
|
38
|
+
"types": "./dist/progress-bar/blessed.d.ts",
|
|
39
|
+
"import": "./dist/progress-bar/blessed.js",
|
|
40
|
+
"require": "./dist/progress-bar/blessed.cjs"
|
|
41
|
+
},
|
|
42
|
+
"./sparkline": {
|
|
43
|
+
"types": "./dist/sparkline/index.d.ts",
|
|
44
|
+
"import": "./dist/sparkline/index.js",
|
|
45
|
+
"require": "./dist/sparkline/index.cjs"
|
|
46
|
+
},
|
|
47
|
+
"./sparkline/blessed": {
|
|
48
|
+
"types": "./dist/sparkline/blessed.d.ts",
|
|
49
|
+
"import": "./dist/sparkline/blessed.js",
|
|
50
|
+
"require": "./dist/sparkline/blessed.cjs"
|
|
31
51
|
}
|
|
32
52
|
},
|
|
33
53
|
"files": [
|
|
34
54
|
"dist",
|
|
35
55
|
"LICENSE",
|
|
36
56
|
"README.md",
|
|
37
|
-
"ROADMAP.md"
|
|
57
|
+
"ROADMAP.md",
|
|
58
|
+
"src/components/*/README.md"
|
|
38
59
|
],
|
|
39
60
|
"sideEffects": false,
|
|
40
61
|
"engines": {
|
|
@@ -47,35 +68,42 @@
|
|
|
47
68
|
"scripts": {
|
|
48
69
|
"build": "tsup",
|
|
49
70
|
"clean": "rm -rf coverage dist",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
71
|
+
"docs": "typedoc --options typedoc.json",
|
|
72
|
+
"docs:check": "typedoc --options typedoc.json --emit none",
|
|
73
|
+
"biome:check": "biome check .",
|
|
74
|
+
"format": "biome check --write .",
|
|
75
|
+
"format:check": "biome check .",
|
|
52
76
|
"lint": "eslint .",
|
|
77
|
+
"lint:fix": "eslint . --fix",
|
|
78
|
+
"preview": "tsx examples/preview-prototype/app.ts",
|
|
53
79
|
"release": "semantic-release",
|
|
54
|
-
"test": "vitest run
|
|
80
|
+
"test": "vitest run",
|
|
81
|
+
"test:package": "node tests/package-exports.mjs",
|
|
55
82
|
"test:watch": "vitest",
|
|
56
83
|
"typecheck": "tsc --noEmit",
|
|
57
|
-
"validate": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run build",
|
|
84
|
+
"validate": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run docs:check && npm run build && npm run test:package",
|
|
58
85
|
"prepublishOnly": "npm run validate"
|
|
59
86
|
},
|
|
60
87
|
"peerDependencies": {
|
|
61
88
|
"blessed": "^0.1.81"
|
|
62
89
|
},
|
|
63
90
|
"devDependencies": {
|
|
64
|
-
"@
|
|
91
|
+
"@biomejs/biome": "^2.5.0",
|
|
65
92
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
93
|
+
"@semantic-release/exec": "^7.1.0",
|
|
66
94
|
"@semantic-release/github": "^12.0.8",
|
|
67
95
|
"@semantic-release/npm": "^13.1.5",
|
|
68
96
|
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
69
97
|
"@types/blessed": "^0.1.27",
|
|
70
98
|
"@types/node": "^22.20.0",
|
|
71
99
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
72
|
-
"eslint": "^
|
|
73
|
-
"eslint-config-prettier": "^10.1.8",
|
|
74
|
-
"prettier": "^3.8.4",
|
|
100
|
+
"eslint": "^9.39.4",
|
|
75
101
|
"semantic-release": "^25.0.5",
|
|
102
|
+
"super-configs": "^1.15.0",
|
|
103
|
+
"tsx": "^4.21.0",
|
|
76
104
|
"tsup": "^8.5.1",
|
|
105
|
+
"typedoc": "^0.28.19",
|
|
77
106
|
"typescript": "^6.0.3",
|
|
78
|
-
"typescript-eslint": "^8.61.1",
|
|
79
107
|
"vitest": "^4.1.9"
|
|
80
108
|
}
|
|
81
|
-
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# ProgressBar
|
|
2
|
+
|
|
3
|
+
Render a bounded numeric value as a fixed-width terminal progress track.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Pure renderer usable without a live terminal.
|
|
8
|
+
- Blessed adapter with explicit update and destroy methods.
|
|
9
|
+
- Custom numeric ranges.
|
|
10
|
+
- Automatic clamping.
|
|
11
|
+
- Unicode defaults and custom ASCII characters.
|
|
12
|
+
- Custom labels and value formatting.
|
|
13
|
+
- Dedicated subpath exports for tree shaking.
|
|
14
|
+
- No automatic `screen.render()` calls.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install blessed blessed-components
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`blessed` is a peer dependency.
|
|
23
|
+
|
|
24
|
+
## Pure renderer
|
|
25
|
+
|
|
26
|
+
Importing this entry does not load Blessed:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { renderProgressBar } from 'blessed-components/progress-bar';
|
|
30
|
+
|
|
31
|
+
const output = renderProgressBar({
|
|
32
|
+
label: 'Quality',
|
|
33
|
+
value: 78,
|
|
34
|
+
width: 16,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Quality ████████████░░░░ 78%
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Custom range
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
renderProgressBar({
|
|
44
|
+
min: 10,
|
|
45
|
+
max: 20,
|
|
46
|
+
value: 15,
|
|
47
|
+
width: 10,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// █████░░░░░ 50%
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### ASCII mode
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
renderProgressBar({
|
|
57
|
+
characters: {
|
|
58
|
+
filled: '#',
|
|
59
|
+
empty: '-',
|
|
60
|
+
},
|
|
61
|
+
value: 50,
|
|
62
|
+
width: 10,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// #####----- 50%
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Custom value
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
renderProgressBar({
|
|
72
|
+
formatValue: ({ value }) => `${value} files`,
|
|
73
|
+
label: 'Uploaded',
|
|
74
|
+
value: 25,
|
|
75
|
+
width: 8,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Uploaded ██░░░░░░ 25 files
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Blessed adapter
|
|
82
|
+
|
|
83
|
+
Use the separate Blessed entry when an element is needed:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import blessed from 'blessed';
|
|
87
|
+
import { progressBar } from 'blessed-components/progress-bar/blessed';
|
|
88
|
+
|
|
89
|
+
const screen = blessed.screen({ smartCSR: true });
|
|
90
|
+
|
|
91
|
+
const upload = progressBar({
|
|
92
|
+
parent: screen,
|
|
93
|
+
box: {
|
|
94
|
+
top: 0,
|
|
95
|
+
left: 0,
|
|
96
|
+
width: 40,
|
|
97
|
+
height: 1,
|
|
98
|
+
},
|
|
99
|
+
data: {
|
|
100
|
+
label: 'Uploaded',
|
|
101
|
+
value: 25,
|
|
102
|
+
width: 20,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
screen.render();
|
|
107
|
+
|
|
108
|
+
upload.setData({
|
|
109
|
+
label: 'Uploaded',
|
|
110
|
+
value: 75,
|
|
111
|
+
width: 20,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
screen.render();
|
|
115
|
+
|
|
116
|
+
upload.destroy();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The adapter owns only its `BoxElement`. It updates content through `setData`
|
|
120
|
+
and detaches the element through `destroy`. Rendering remains the caller's
|
|
121
|
+
responsibility, allowing multiple updates to be batched.
|
|
122
|
+
|
|
123
|
+
## Renderer API
|
|
124
|
+
|
|
125
|
+
### `renderProgressBar(options)`
|
|
126
|
+
|
|
127
|
+
Returns a plain string.
|
|
128
|
+
|
|
129
|
+
| Option | Type | Default | Description |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| `value` | `number` | Required | Current numeric value. |
|
|
132
|
+
| `width` | `number` | Required | Positive integer track width in characters. |
|
|
133
|
+
| `min` | `number` | `0` | Lower bound. |
|
|
134
|
+
| `max` | `number` | `100` | Upper bound. |
|
|
135
|
+
| `label` | `string` | — | Text rendered before the track. |
|
|
136
|
+
| `characters` | `{ filled: string; empty: string }` | `█`, `░` | Single-cell track characters. |
|
|
137
|
+
| `formatValue` | `(context) => string` | Percentage | Formats trailing value text. |
|
|
138
|
+
|
|
139
|
+
`value` is clamped to `[min, max]`. `max` must be greater than `min`.
|
|
140
|
+
|
|
141
|
+
`formatValue` receives:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
interface ProgressBarValueContext {
|
|
145
|
+
percentage: number;
|
|
146
|
+
value: number;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`value` in this context is the clamped value.
|
|
151
|
+
|
|
152
|
+
## Adapter API
|
|
153
|
+
|
|
154
|
+
### `progressBar(options)`
|
|
155
|
+
|
|
156
|
+
| Option | Type | Description |
|
|
157
|
+
| --- | --- | --- |
|
|
158
|
+
| `parent` | `blessed.Widgets.Node` | Parent Blessed node. |
|
|
159
|
+
| `data` | `RenderProgressBarOptions` | Renderer data. |
|
|
160
|
+
| `box` | `ProgressBarBoxOptions` | Blessed box options except `parent`, `content`, and `tags`. |
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
interface ProgressBarHandle {
|
|
166
|
+
readonly element: blessed.Widgets.BoxElement;
|
|
167
|
+
setData(data: RenderProgressBarOptions): void;
|
|
168
|
+
destroy(): void;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Accessibility and terminal compatibility
|
|
173
|
+
|
|
174
|
+
- Do not communicate state through color alone; percentage text is enabled by
|
|
175
|
+
default.
|
|
176
|
+
- Use ASCII characters when Unicode block glyphs are unavailable.
|
|
177
|
+
- Keep labels concise for narrow terminals.
|
|
178
|
+
- The component is display-only and does not receive focus or keyboard input.
|
|
179
|
+
- Call `screen.render()` after updates when visible output should be flushed.
|
|
180
|
+
|
|
181
|
+
## Tree shaking
|
|
182
|
+
|
|
183
|
+
Prefer the narrowest import:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { renderProgressBar } from 'blessed-components/progress-bar';
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The pure entry contains no Blessed import. Import the adapter only when needed:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { progressBar } from 'blessed-components/progress-bar/blessed';
|
|
193
|
+
```
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Sparkline
|
|
2
|
+
|
|
3
|
+
Render compact numeric trends with Unicode or custom terminal glyphs.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Pure renderer with no Blessed import.
|
|
8
|
+
- Blessed adapter with `setData()` and `destroy()`.
|
|
9
|
+
- Automatic or explicit numeric domains.
|
|
10
|
+
- Peak-preserving downsampling for narrow terminals.
|
|
11
|
+
- Stable rendering for constant series.
|
|
12
|
+
- Empty-state text.
|
|
13
|
+
- Optional label, primary value, and summary.
|
|
14
|
+
- Custom glyph scales for Unicode or ASCII terminals.
|
|
15
|
+
- Dedicated tree-shakable subpath exports.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install blessed blessed-components
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Pure renderer
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { renderSparkline } from 'blessed-components/sparkline';
|
|
27
|
+
|
|
28
|
+
renderSparkline({
|
|
29
|
+
values: [1, 2, 3, 4, 5, 6, 7, 8],
|
|
30
|
+
width: 8,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ▁▂▃▄▅▆▇█
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Metadata
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
renderSparkline({
|
|
40
|
+
label: 'Weekly downloads',
|
|
41
|
+
value: '25.2M',
|
|
42
|
+
values: [1, 3, 2, 5, 8, 6],
|
|
43
|
+
width: 6,
|
|
44
|
+
summary: 'peak: 8M',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Weekly downloads 25.2M
|
|
48
|
+
// ▁▃▂▅█▆ peak: 8M
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Explicit domain
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
renderSparkline({
|
|
55
|
+
min: 0,
|
|
56
|
+
max: 100,
|
|
57
|
+
values: [0, 50, 100],
|
|
58
|
+
width: 3,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ▁▄█
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### ASCII mode
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
renderSparkline({
|
|
68
|
+
characters: ['.', ':', '*', '#'],
|
|
69
|
+
values: [0, 30, 60, 100],
|
|
70
|
+
width: 4,
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Downsampling
|
|
75
|
+
|
|
76
|
+
`width` is the maximum number of samples rendered. When the source series is
|
|
77
|
+
wider, values are divided into ordered buckets and the maximum value from each
|
|
78
|
+
bucket is retained. This preserves short peaks that uniform point sampling can
|
|
79
|
+
hide.
|
|
80
|
+
|
|
81
|
+
Series shorter than `width` are not padded or interpolated.
|
|
82
|
+
|
|
83
|
+
## Empty and constant series
|
|
84
|
+
|
|
85
|
+
Empty arrays render `No data` by default:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
renderSparkline({ values: [], width: 10 });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Override with `emptyText`. Constant series use the middle glyph instead of
|
|
92
|
+
dividing by zero:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
renderSparkline({ values: [4, 4, 4], width: 3 });
|
|
96
|
+
// ▄▄▄
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Blessed adapter
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import blessed from 'blessed';
|
|
103
|
+
import { sparkline } from 'blessed-components/sparkline/blessed';
|
|
104
|
+
|
|
105
|
+
const screen = blessed.screen({ smartCSR: true });
|
|
106
|
+
const downloads = sparkline({
|
|
107
|
+
parent: screen,
|
|
108
|
+
box: {
|
|
109
|
+
top: 0,
|
|
110
|
+
left: 0,
|
|
111
|
+
width: 40,
|
|
112
|
+
height: 2,
|
|
113
|
+
},
|
|
114
|
+
data: {
|
|
115
|
+
label: 'Downloads',
|
|
116
|
+
values: [1, 3, 2, 5, 8, 6],
|
|
117
|
+
width: 20,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
screen.render();
|
|
122
|
+
|
|
123
|
+
downloads.setData({
|
|
124
|
+
label: 'Downloads',
|
|
125
|
+
values: [2, 4, 6, 8],
|
|
126
|
+
width: 20,
|
|
127
|
+
});
|
|
128
|
+
screen.render();
|
|
129
|
+
|
|
130
|
+
downloads.destroy();
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The adapter never calls `screen.render()`. This lets applications batch
|
|
134
|
+
multiple component updates.
|
|
135
|
+
|
|
136
|
+
## Renderer API
|
|
137
|
+
|
|
138
|
+
| Option | Type | Default | Description |
|
|
139
|
+
| --- | --- | --- | --- |
|
|
140
|
+
| `values` | `readonly number[]` | Required | Ordered finite numeric samples. |
|
|
141
|
+
| `width` | `number` | Required | Positive integer maximum sample count. |
|
|
142
|
+
| `min` | `number` | Series minimum | Lower domain bound. |
|
|
143
|
+
| `max` | `number` | Series maximum | Upper domain bound. |
|
|
144
|
+
| `characters` | `readonly string[]` | `▁▂▃▄▅▆▇█` | Ordered low-to-high glyph scale. |
|
|
145
|
+
| `emptyText` | `string` | `No data` | Output for an empty series. |
|
|
146
|
+
| `label` | `string` | — | Heading label. |
|
|
147
|
+
| `value` | `string \| number` | — | Primary heading value. |
|
|
148
|
+
| `summary` | `string` | — | Text appended after the graph. |
|
|
149
|
+
|
|
150
|
+
## Terminal compatibility
|
|
151
|
+
|
|
152
|
+
- Glyphs should occupy one terminal cell.
|
|
153
|
+
- Use custom ASCII glyphs when Unicode support is unavailable.
|
|
154
|
+
- Text remains literal because the Blessed adapter disables tags.
|
|
155
|
+
- The component is display-only and never receives focus.
|
|
156
|
+
- Do not communicate meaning through glyph height or color alone; include
|
|
157
|
+
labels, values, or summaries when context matters.
|
|
158
|
+
|
|
159
|
+
## Tree shaking
|
|
160
|
+
|
|
161
|
+
Use the pure entry when no Blessed element is needed:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { renderSparkline } from 'blessed-components/sparkline';
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Import the adapter separately:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { sparkline } from 'blessed-components/sparkline/blessed';
|
|
171
|
+
```
|