@linxiraos/pi-utils 1.0.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/CHANGELOG.md +449 -0
- package/README.md +36 -0
- package/dist/types/abortable.d.ts +32 -0
- package/dist/types/acp/connection.d.ts +118 -0
- package/dist/types/acp/protocol.d.ts +526 -0
- package/dist/types/acp/schema.d.ts +41 -0
- package/dist/types/acp/stream.d.ts +8 -0
- package/dist/types/acp/transport.d.ts +81 -0
- package/dist/types/acp.d.ts +6 -0
- package/dist/types/async.d.ts +20 -0
- package/dist/types/binary.d.ts +17 -0
- package/dist/types/browsers.d.ts +68 -0
- package/dist/types/chalk.d.ts +125 -0
- package/dist/types/cli.d.ts +134 -0
- package/dist/types/color.d.ts +102 -0
- package/dist/types/dates.d.ts +7 -0
- package/dist/types/dirs.d.ts +282 -0
- package/dist/types/docx/converter.d.ts +46 -0
- package/dist/types/docx/xml.d.ts +26 -0
- package/dist/types/docx/zip.d.ts +6 -0
- package/dist/types/docx.d.ts +11 -0
- package/dist/types/dom/core.d.ts +431 -0
- package/dist/types/dom/parser.d.ts +7 -0
- package/dist/types/dom/selector.d.ts +5 -0
- package/dist/types/dom.d.ts +5 -0
- package/dist/types/env.d.ts +124 -0
- package/dist/types/fetch-retry.d.ts +96 -0
- package/dist/types/file-lock.d.ts +23 -0
- package/dist/types/format.d.ts +37 -0
- package/dist/types/frontmatter.d.ts +46 -0
- package/dist/types/fs-error.d.ts +31 -0
- package/dist/types/glob.d.ts +28 -0
- package/dist/types/headers.d.ts +34 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/json-parse.d.ts +74 -0
- package/dist/types/json.d.ts +14 -0
- package/dist/types/logger/rotating-file.d.ts +18 -0
- package/dist/types/logger.d.ts +96 -0
- package/dist/types/loop-phase.d.ts +10 -0
- package/dist/types/lru.d.ts +46 -0
- package/dist/types/marked/core.d.ts +445 -0
- package/dist/types/marked.d.ts +2 -0
- package/dist/types/mermaid-ascii.d.ts +11 -0
- package/dist/types/mime.d.ts +29 -0
- package/dist/types/module-timer.d.ts +1 -0
- package/dist/types/path-tree.d.ts +76 -0
- package/dist/types/path.d.ts +2 -0
- package/dist/types/peek-file.d.ts +29 -0
- package/dist/types/postmortem.d.ts +104 -0
- package/dist/types/process-name.d.ts +7 -0
- package/dist/types/procmgr.d.ts +74 -0
- package/dist/types/prompt.d.ts +18 -0
- package/dist/types/ptree.d.ts +110 -0
- package/dist/types/readability/readability.d.ts +9 -0
- package/dist/types/readability/readerable.d.ts +10 -0
- package/dist/types/readability/types.d.ts +70 -0
- package/dist/types/readability.d.ts +4 -0
- package/dist/types/ring.d.ts +93 -0
- package/dist/types/runtime-install.d.ts +78 -0
- package/dist/types/sanitize-text.d.ts +29 -0
- package/dist/types/snowflake.d.ts +25 -0
- package/dist/types/stderr-guard.d.ts +22 -0
- package/dist/types/stream.d.ts +67 -0
- package/dist/types/tab-spacing.d.ts +24 -0
- package/dist/types/temp.d.ts +17 -0
- package/dist/types/template.d.ts +62 -0
- package/dist/types/timing-buffer.d.ts +22 -0
- package/dist/types/tls-fetch.d.ts +37 -0
- package/dist/types/turndown/gfm.d.ts +11 -0
- package/dist/types/turndown/html.d.ts +5 -0
- package/dist/types/turndown/service.d.ts +21 -0
- package/dist/types/turndown/types.d.ts +70 -0
- package/dist/types/turndown.d.ts +4 -0
- package/dist/types/type-guards.d.ts +5 -0
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
- package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
- package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
- package/dist/types/version.d.ts +18 -0
- package/dist/types/vterm/buffer.d.ts +99 -0
- package/dist/types/vterm/terminal.d.ts +44 -0
- package/dist/types/vterm.d.ts +8 -0
- package/dist/types/which.d.ts +37 -0
- package/dist/types/worker-host.d.ts +51 -0
- package/dist/types/xml.d.ts +31 -0
- package/package.json +59 -0
- package/src/abortable.ts +109 -0
- package/src/acp/connection.ts +344 -0
- package/src/acp/protocol.ts +466 -0
- package/src/acp/schema.ts +160 -0
- package/src/acp/stream.ts +82 -0
- package/src/acp/transport.ts +213 -0
- package/src/acp.ts +6 -0
- package/src/async.ts +91 -0
- package/src/binary.ts +50 -0
- package/src/browsers.ts +501 -0
- package/src/chalk.ts +312 -0
- package/src/cli.ts +529 -0
- package/src/color.ts +302 -0
- package/src/dates.ts +194 -0
- package/src/dirs.ts +1026 -0
- package/src/docx/converter.ts +681 -0
- package/src/docx/xml.ts +166 -0
- package/src/docx/zip.ts +87 -0
- package/src/docx.ts +20 -0
- package/src/dom/core.ts +1254 -0
- package/src/dom/parser.ts +370 -0
- package/src/dom/selector.ts +290 -0
- package/src/dom.ts +33 -0
- package/src/env.ts +400 -0
- package/src/fetch-retry.ts +379 -0
- package/src/file-lock.ts +69 -0
- package/src/format.ts +113 -0
- package/src/frontmatter.ts +205 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/headers.ts +167 -0
- package/src/index.ts +59 -0
- package/src/json-parse.ts +821 -0
- package/src/json.ts +23 -0
- package/src/logger/rotating-file.ts +149 -0
- package/src/logger.ts +798 -0
- package/src/loop-phase.ts +49 -0
- package/src/lru.ts +185 -0
- package/src/marked/core.ts +1576 -0
- package/src/marked.ts +2 -0
- package/src/mermaid-ascii.ts +31 -0
- package/src/mime.ts +159 -0
- package/src/module-timer.ts +148 -0
- package/src/path-tree.ts +147 -0
- package/src/path.ts +28 -0
- package/src/peek-file.ts +188 -0
- package/src/postmortem.ts +433 -0
- package/src/process-name.ts +57 -0
- package/src/procmgr.ts +247 -0
- package/src/prompt.ts +545 -0
- package/src/ptree.ts +412 -0
- package/src/readability/readability.ts +533 -0
- package/src/readability/readerable.ts +51 -0
- package/src/readability/types.ts +72 -0
- package/src/readability.ts +11 -0
- package/src/ring.ts +169 -0
- package/src/runtime-install.ts +393 -0
- package/src/sanitize-text.ts +96 -0
- package/src/snowflake.ts +121 -0
- package/src/stderr-guard.ts +155 -0
- package/src/stream.ts +454 -0
- package/src/tab-spacing.ts +336 -0
- package/src/temp.ts +134 -0
- package/src/template.ts +586 -0
- package/src/timing-buffer.ts +47 -0
- package/src/tls-fetch.ts +178 -0
- package/src/turndown/gfm.ts +106 -0
- package/src/turndown/html.ts +257 -0
- package/src/turndown/service.ts +334 -0
- package/src/turndown/types.ts +81 -0
- package/src/turndown.ts +5 -0
- package/src/type-guards.ts +17 -0
- package/src/vendor/mermaid-ascii/NOTICE +33 -0
- package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
- package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
- package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
- package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
- package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
- package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
- package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
- package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
- package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
- package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
- package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +277 -0
- package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
- package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
- package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
- package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
- package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
- package/src/vendor/mermaid-ascii/class/types.ts +121 -0
- package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
- package/src/vendor/mermaid-ascii/er/types.ts +91 -0
- package/src/vendor/mermaid-ascii/index.ts +14 -0
- package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
- package/src/vendor/mermaid-ascii/parser.ts +645 -0
- package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
- package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
- package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
- package/src/vendor/mermaid-ascii/types.ts +164 -0
- package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
- package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
- package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
- package/src/version.ts +99 -0
- package/src/vterm/buffer.ts +218 -0
- package/src/vterm/terminal.ts +773 -0
- package/src/vterm.ts +8 -0
- package/src/which.ts +234 -0
- package/src/worker-host.ts +100 -0
- package/src/xml.ts +313 -0
package/src/color.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color manipulation utilities for hex colors.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { hexToHsv, hsvToHex } from "@linxiraos/pi-utils";
|
|
7
|
+
*
|
|
8
|
+
* // Rotate the hue by 90°
|
|
9
|
+
* const hsv = hexToHsv("#4ade80");
|
|
10
|
+
* hsv.h = (hsv.h + 90) % 360;
|
|
11
|
+
* const newHex = hsvToHex(hsv);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface HSV {
|
|
16
|
+
/** Hue in degrees (0-360) */
|
|
17
|
+
h: number;
|
|
18
|
+
/** Saturation (0-1) */
|
|
19
|
+
s: number;
|
|
20
|
+
/** Value/brightness (0-1) */
|
|
21
|
+
v: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RGB {
|
|
25
|
+
/** Red (0-255) */
|
|
26
|
+
r: number;
|
|
27
|
+
/** Green (0-255) */
|
|
28
|
+
g: number;
|
|
29
|
+
/** Blue (0-255) */
|
|
30
|
+
b: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse a hex color string to RGB.
|
|
35
|
+
* Supports #RGB, #RRGGBB formats.
|
|
36
|
+
*/
|
|
37
|
+
export function hexToRgb(hex: string): RGB {
|
|
38
|
+
const h = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
39
|
+
if (h.length === 3) {
|
|
40
|
+
return {
|
|
41
|
+
r: parseInt(h[0] + h[0], 16),
|
|
42
|
+
g: parseInt(h[1] + h[1], 16),
|
|
43
|
+
b: parseInt(h[2] + h[2], 16),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
r: parseInt(h.slice(0, 2), 16),
|
|
48
|
+
g: parseInt(h.slice(2, 4), 16),
|
|
49
|
+
b: parseInt(h.slice(4, 6), 16),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert RGB to hex color string.
|
|
55
|
+
*/
|
|
56
|
+
export function rgbToHex(rgb: RGB): string {
|
|
57
|
+
const toHex = (n: number) =>
|
|
58
|
+
Math.max(0, Math.min(255, Math.round(n)))
|
|
59
|
+
.toString(16)
|
|
60
|
+
.padStart(2, "0");
|
|
61
|
+
return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Convert RGB to HSV.
|
|
66
|
+
*/
|
|
67
|
+
export function rgbToHsv(rgb: RGB): HSV {
|
|
68
|
+
const r = rgb.r / 255;
|
|
69
|
+
const g = rgb.g / 255;
|
|
70
|
+
const b = rgb.b / 255;
|
|
71
|
+
|
|
72
|
+
const max = Math.max(r, g, b);
|
|
73
|
+
const min = Math.min(r, g, b);
|
|
74
|
+
const d = max - min;
|
|
75
|
+
|
|
76
|
+
let h = 0;
|
|
77
|
+
if (d !== 0) {
|
|
78
|
+
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
79
|
+
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
80
|
+
else h = ((r - g) / d + 4) / 6;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
h: h * 360,
|
|
85
|
+
s: max === 0 ? 0 : d / max,
|
|
86
|
+
v: max,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Convert HSV to RGB.
|
|
92
|
+
*/
|
|
93
|
+
export function hsvToRgb(hsv: HSV): RGB {
|
|
94
|
+
const { s, v } = hsv;
|
|
95
|
+
const h = ((hsv.h % 360) + 360) % 360; // Normalize to 0-360
|
|
96
|
+
|
|
97
|
+
const i = Math.floor(h / 60);
|
|
98
|
+
const f = h / 60 - i;
|
|
99
|
+
const p = v * (1 - s);
|
|
100
|
+
const q = v * (1 - f * s);
|
|
101
|
+
const t = v * (1 - (1 - f) * s);
|
|
102
|
+
|
|
103
|
+
let r: number, g: number, b: number;
|
|
104
|
+
switch (i % 6) {
|
|
105
|
+
case 0:
|
|
106
|
+
r = v;
|
|
107
|
+
g = t;
|
|
108
|
+
b = p;
|
|
109
|
+
break;
|
|
110
|
+
case 1:
|
|
111
|
+
r = q;
|
|
112
|
+
g = v;
|
|
113
|
+
b = p;
|
|
114
|
+
break;
|
|
115
|
+
case 2:
|
|
116
|
+
r = p;
|
|
117
|
+
g = v;
|
|
118
|
+
b = t;
|
|
119
|
+
break;
|
|
120
|
+
case 3:
|
|
121
|
+
r = p;
|
|
122
|
+
g = q;
|
|
123
|
+
b = v;
|
|
124
|
+
break;
|
|
125
|
+
case 4:
|
|
126
|
+
r = t;
|
|
127
|
+
g = p;
|
|
128
|
+
b = v;
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
r = v;
|
|
132
|
+
g = p;
|
|
133
|
+
b = q;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
r: Math.round(r * 255),
|
|
139
|
+
g: Math.round(g * 255),
|
|
140
|
+
b: Math.round(b * 255),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Convert hex color to HSV.
|
|
146
|
+
*/
|
|
147
|
+
export function hexToHsv(hex: string): HSV {
|
|
148
|
+
return rgbToHsv(hexToRgb(hex));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Convert HSV to hex color.
|
|
153
|
+
*/
|
|
154
|
+
export function hsvToHex(hsv: HSV): string {
|
|
155
|
+
return rgbToHex(hsvToRgb(hsv));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Shift the hue of a hex color by a given number of degrees.
|
|
160
|
+
*/
|
|
161
|
+
export function shiftHue(hex: string, degrees: number): string {
|
|
162
|
+
const hsv = hexToHsv(hex);
|
|
163
|
+
hsv.h = (hsv.h + degrees) % 360;
|
|
164
|
+
if (hsv.h < 0) hsv.h += 360;
|
|
165
|
+
return hsvToHex(hsv);
|
|
166
|
+
}
|
|
167
|
+
export interface HSVAdjustment {
|
|
168
|
+
/** Hue shift in degrees (additive) */
|
|
169
|
+
h?: number;
|
|
170
|
+
/** Saturation multiplier */
|
|
171
|
+
s?: number;
|
|
172
|
+
/** Value/brightness multiplier */
|
|
173
|
+
v?: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Adjust HSV components of a hex color.
|
|
178
|
+
*
|
|
179
|
+
* @param hex - Hex color string (#RGB or #RRGGBB)
|
|
180
|
+
* @param adj - Adjustments: h is additive degrees, s and v are multipliers
|
|
181
|
+
* @returns New hex color string
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* // Shift hue +60°, reduce saturation to 71%
|
|
186
|
+
* adjustHsv("#00ff88", { h: 60, s: 0.71 }) // "#4a9eff"
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export function adjustHsv(hex: string, adj: HSVAdjustment): string {
|
|
190
|
+
const hsv = hexToHsv(hex);
|
|
191
|
+
if (adj.h !== undefined) {
|
|
192
|
+
hsv.h = (hsv.h + adj.h) % 360;
|
|
193
|
+
if (hsv.h < 0) hsv.h += 360;
|
|
194
|
+
}
|
|
195
|
+
if (adj.s !== undefined) {
|
|
196
|
+
hsv.s = Math.max(0, Math.min(1, hsv.s * adj.s));
|
|
197
|
+
}
|
|
198
|
+
if (adj.v !== undefined) {
|
|
199
|
+
hsv.v = Math.max(0, Math.min(1, hsv.v * adj.v));
|
|
200
|
+
}
|
|
201
|
+
return hsvToHex(hsv);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Convert HSL (h: 0-360, s: 0-1, l: 0-1) to a CSS hex string.
|
|
206
|
+
*/
|
|
207
|
+
export function hslToHex(h: number, s: number, l: number): string {
|
|
208
|
+
const a = s * Math.min(l, 1 - l);
|
|
209
|
+
const f = (n: number) => {
|
|
210
|
+
const k = (n + h / 30) % 12;
|
|
211
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
212
|
+
return Math.round(255 * color)
|
|
213
|
+
.toString(16)
|
|
214
|
+
.padStart(2, "0");
|
|
215
|
+
};
|
|
216
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Conventional xterm RGB for the 16 base ANSI colors. Terminals may remap these,
|
|
220
|
+
// so they're a best-effort approximation for light/dark classification.
|
|
221
|
+
const ANSI_16: readonly (readonly [number, number, number])[] = [
|
|
222
|
+
[0, 0, 0],
|
|
223
|
+
[128, 0, 0],
|
|
224
|
+
[0, 128, 0],
|
|
225
|
+
[128, 128, 0],
|
|
226
|
+
[0, 0, 128],
|
|
227
|
+
[128, 0, 128],
|
|
228
|
+
[0, 128, 128],
|
|
229
|
+
[192, 192, 192],
|
|
230
|
+
[128, 128, 128],
|
|
231
|
+
[255, 0, 0],
|
|
232
|
+
[0, 255, 0],
|
|
233
|
+
[255, 255, 0],
|
|
234
|
+
[0, 0, 255],
|
|
235
|
+
[255, 0, 255],
|
|
236
|
+
[0, 255, 255],
|
|
237
|
+
[255, 255, 255],
|
|
238
|
+
];
|
|
239
|
+
const CUBE_STEPS = [0, 95, 135, 175, 215, 255] as const;
|
|
240
|
+
|
|
241
|
+
/** Parse a 256-color palette index (0–255) to RGB (0..255). */
|
|
242
|
+
function paletteToRgb(index: number): RGB | undefined {
|
|
243
|
+
if (!Number.isInteger(index) || index < 0 || index > 255) return undefined;
|
|
244
|
+
if (index < 16) {
|
|
245
|
+
const rgb = ANSI_16[index];
|
|
246
|
+
return rgb ? { r: rgb[0], g: rgb[1], b: rgb[2] } : undefined;
|
|
247
|
+
}
|
|
248
|
+
if (index < 232) {
|
|
249
|
+
const n = index - 16;
|
|
250
|
+
return {
|
|
251
|
+
r: CUBE_STEPS[Math.floor(n / 36) % 6] ?? 0,
|
|
252
|
+
g: CUBE_STEPS[Math.floor(n / 6) % 6] ?? 0,
|
|
253
|
+
b: CUBE_STEPS[n % 6] ?? 0,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const gray = 8 + (index - 232) * 10;
|
|
257
|
+
return { r: gray, g: gray, b: gray };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Parse a theme color value — `#rgb`/`#rrggbb` hex or 256-color palette index — to RGB (0..255). */
|
|
261
|
+
function toRgb(value: string | number): RGB | undefined {
|
|
262
|
+
if (typeof value === "number") return paletteToRgb(value);
|
|
263
|
+
if (typeof value !== "string" || value[0] !== "#") return undefined;
|
|
264
|
+
if (value.length !== 4 && value.length !== 7) return undefined;
|
|
265
|
+
const rgb = hexToRgb(value);
|
|
266
|
+
if (Number.isNaN(rgb.r) || Number.isNaN(rgb.g) || Number.isNaN(rgb.b)) return undefined;
|
|
267
|
+
return rgb;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Gamma-decode a single 0..255 sRGB channel to linear 0..1. */
|
|
271
|
+
function linearizeChannel(channel: number): number {
|
|
272
|
+
const c = channel / 255;
|
|
273
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Perceptual luma (gamma-encoded BT.709 weights over raw sRGB), normalized to 0..1.
|
|
278
|
+
*
|
|
279
|
+
* Accepts a hex string (`#rgb` / `#rrggbb`) or a 256-color palette index; returns
|
|
280
|
+
* `undefined` for var refs, empty strings, or anything unparseable.
|
|
281
|
+
*
|
|
282
|
+
* Cheap and good enough for a light/dark *classification* threshold. NOT suitable
|
|
283
|
+
* for contrast ratios — use {@link relativeLuminance} for those.
|
|
284
|
+
*/
|
|
285
|
+
export function colorLuma(value: string | number): number | undefined {
|
|
286
|
+
const rgb = toRgb(value);
|
|
287
|
+
if (!rgb) return undefined;
|
|
288
|
+
return (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* WCAG 2.x relative luminance (BT.709 weights over linearized sRGB), normalized to
|
|
293
|
+
* 0..1. This is the value the WCAG contrast-ratio formula expects.
|
|
294
|
+
*
|
|
295
|
+
* Accepts a hex string (`#rgb` / `#rrggbb`) or a 256-color palette index; returns
|
|
296
|
+
* `undefined` for var refs, empty strings, or anything unparseable.
|
|
297
|
+
*/
|
|
298
|
+
export function relativeLuminance(value: string | number): number | undefined {
|
|
299
|
+
const rgb = toRgb(value);
|
|
300
|
+
if (!rgb) return undefined;
|
|
301
|
+
return 0.2126 * linearizeChannel(rgb.r) + 0.7152 * linearizeChannel(rgb.g) + 0.0722 * linearizeChannel(rgb.b);
|
|
302
|
+
}
|
package/src/dates.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/** Behavior-compatible reimplementation of date-fns's used surface. */
|
|
2
|
+
|
|
3
|
+
const MONTHS = [
|
|
4
|
+
"January",
|
|
5
|
+
"February",
|
|
6
|
+
"March",
|
|
7
|
+
"April",
|
|
8
|
+
"May",
|
|
9
|
+
"June",
|
|
10
|
+
"July",
|
|
11
|
+
"August",
|
|
12
|
+
"September",
|
|
13
|
+
"October",
|
|
14
|
+
"November",
|
|
15
|
+
"December",
|
|
16
|
+
] as const;
|
|
17
|
+
const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] as const;
|
|
18
|
+
const TOKENS = [
|
|
19
|
+
"MMMM",
|
|
20
|
+
"yyyy",
|
|
21
|
+
"EEEE",
|
|
22
|
+
"MMM",
|
|
23
|
+
"EEE",
|
|
24
|
+
"yy",
|
|
25
|
+
"MM",
|
|
26
|
+
"dd",
|
|
27
|
+
"HH",
|
|
28
|
+
"hh",
|
|
29
|
+
"mm",
|
|
30
|
+
"ss",
|
|
31
|
+
"M",
|
|
32
|
+
"d",
|
|
33
|
+
"H",
|
|
34
|
+
"h",
|
|
35
|
+
"m",
|
|
36
|
+
"s",
|
|
37
|
+
"a",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
40
|
+
function pad(value: number, length = 2): string {
|
|
41
|
+
return String(value).padStart(length, "0");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function tokenValue(date: Date, token: (typeof TOKENS)[number]): string {
|
|
45
|
+
const month = date.getMonth();
|
|
46
|
+
const weekday = date.getDay();
|
|
47
|
+
const hours = date.getHours();
|
|
48
|
+
const hours12 = hours % 12 || 12;
|
|
49
|
+
|
|
50
|
+
switch (token) {
|
|
51
|
+
case "yyyy":
|
|
52
|
+
return pad(date.getFullYear(), 4);
|
|
53
|
+
case "yy":
|
|
54
|
+
return pad(date.getFullYear() % 100);
|
|
55
|
+
case "MMMM":
|
|
56
|
+
return MONTHS[month];
|
|
57
|
+
case "MMM":
|
|
58
|
+
return MONTHS[month].slice(0, 3);
|
|
59
|
+
case "MM":
|
|
60
|
+
return pad(month + 1);
|
|
61
|
+
case "M":
|
|
62
|
+
return String(month + 1);
|
|
63
|
+
case "dd":
|
|
64
|
+
return pad(date.getDate());
|
|
65
|
+
case "d":
|
|
66
|
+
return String(date.getDate());
|
|
67
|
+
case "EEEE":
|
|
68
|
+
return WEEKDAYS[weekday];
|
|
69
|
+
case "EEE":
|
|
70
|
+
return WEEKDAYS[weekday].slice(0, 3);
|
|
71
|
+
case "HH":
|
|
72
|
+
return pad(hours);
|
|
73
|
+
case "H":
|
|
74
|
+
return String(hours);
|
|
75
|
+
case "hh":
|
|
76
|
+
return pad(hours12);
|
|
77
|
+
case "h":
|
|
78
|
+
return String(hours12);
|
|
79
|
+
case "mm":
|
|
80
|
+
return pad(date.getMinutes());
|
|
81
|
+
case "m":
|
|
82
|
+
return String(date.getMinutes());
|
|
83
|
+
case "ss":
|
|
84
|
+
return pad(date.getSeconds());
|
|
85
|
+
case "s":
|
|
86
|
+
return String(date.getSeconds());
|
|
87
|
+
case "a":
|
|
88
|
+
return hours < 12 ? "AM" : "PM";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function asDate(value: Date | number): Date {
|
|
93
|
+
const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
|
|
94
|
+
if (Number.isNaN(date.getTime())) throw new RangeError("Invalid time value");
|
|
95
|
+
return date;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Format a date with the supported date-fns v4 tokens and quoted literals. */
|
|
99
|
+
export function format(value: Date | number, pattern: string): string {
|
|
100
|
+
const date = asDate(value);
|
|
101
|
+
let result = "";
|
|
102
|
+
|
|
103
|
+
for (let index = 0; index < pattern.length; ) {
|
|
104
|
+
if (pattern[index] === "'") {
|
|
105
|
+
if (pattern[index + 1] === "'") {
|
|
106
|
+
result += "'";
|
|
107
|
+
index += 2;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
index++;
|
|
112
|
+
while (index < pattern.length) {
|
|
113
|
+
if (pattern[index] !== "'") {
|
|
114
|
+
result += pattern[index++];
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (pattern[index + 1] === "'") {
|
|
118
|
+
result += "'";
|
|
119
|
+
index += 2;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
index++;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const rest = pattern.slice(index);
|
|
129
|
+
const token = TOKENS.find(candidate => rest.startsWith(candidate));
|
|
130
|
+
if (token) {
|
|
131
|
+
result += tokenValue(date, token);
|
|
132
|
+
index += token.length;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const character = pattern[index++];
|
|
137
|
+
if (/[A-Za-z]/.test(character)) {
|
|
138
|
+
throw new RangeError(`Format string contains an unescaped latin alphabet character \`${character}\``);
|
|
139
|
+
}
|
|
140
|
+
result += character;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function plural(count: number, singular: string): string {
|
|
147
|
+
return `${count} ${count === 1 ? singular : `${singular}s`}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function completedMonths(earlier: Date, later: Date): number {
|
|
151
|
+
let months = (later.getFullYear() - earlier.getFullYear()) * 12 + later.getMonth() - earlier.getMonth();
|
|
152
|
+
if (months === 0) return 0;
|
|
153
|
+
|
|
154
|
+
const candidate = new Date(earlier);
|
|
155
|
+
candidate.setDate(1);
|
|
156
|
+
candidate.setFullYear(earlier.getFullYear(), earlier.getMonth() + months, 1);
|
|
157
|
+
const lastDay = new Date(candidate.getFullYear(), candidate.getMonth() + 1, 0).getDate();
|
|
158
|
+
candidate.setDate(Math.min(earlier.getDate(), lastDay));
|
|
159
|
+
if (candidate.getTime() > later.getTime()) months--;
|
|
160
|
+
return months;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function distanceWords(earlier: Date, later: Date): string {
|
|
164
|
+
const seconds = (later.getTime() - earlier.getTime()) / 1_000;
|
|
165
|
+
const timezoneOffset = (later.getTimezoneOffset() - earlier.getTimezoneOffset()) * 60;
|
|
166
|
+
const minutes = Math.round((seconds - timezoneOffset) / 60);
|
|
167
|
+
|
|
168
|
+
if (minutes < 2) return minutes === 0 ? "less than a minute" : "1 minute";
|
|
169
|
+
if (minutes < 45) return plural(minutes, "minute");
|
|
170
|
+
if (minutes < 90) return "about 1 hour";
|
|
171
|
+
if (minutes < 1_440) return `about ${plural(Math.round(minutes / 60), "hour")}`;
|
|
172
|
+
if (minutes < 2_520) return "1 day";
|
|
173
|
+
if (minutes < 43_200) return plural(Math.round(minutes / 1_440), "day");
|
|
174
|
+
if (minutes < 86_400) return `about ${plural(Math.round(minutes / 43_200), "month")}`;
|
|
175
|
+
|
|
176
|
+
const months = completedMonths(earlier, later);
|
|
177
|
+
if (months < 12) return plural(Math.round(minutes / 43_200), "month");
|
|
178
|
+
|
|
179
|
+
const years = Math.trunc(months / 12);
|
|
180
|
+
const remainingMonths = months % 12;
|
|
181
|
+
if (remainingMonths < 3) return `about ${plural(years, "year")}`;
|
|
182
|
+
if (remainingMonths < 9) return `over ${plural(years, "year")}`;
|
|
183
|
+
return `almost ${plural(years + 1, "year")}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Describe the distance from a date to now using date-fns's English thresholds. */
|
|
187
|
+
export function formatDistanceToNow(value: Date | number, options: { addSuffix?: boolean } = {}): string {
|
|
188
|
+
const date = asDate(value);
|
|
189
|
+
const now = new Date(Date.now());
|
|
190
|
+
const isFuture = date.getTime() > now.getTime();
|
|
191
|
+
const words = isFuture ? distanceWords(now, date) : distanceWords(date, now);
|
|
192
|
+
if (!options.addSuffix) return words;
|
|
193
|
+
return isFuture ? `in ${words}` : `${words} ago`;
|
|
194
|
+
}
|