@reliverse/relico 1.4.2 → 2.2.7

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 CHANGED
@@ -1,12 +1,8 @@
1
1
  # Reliverse Relico
2
2
 
3
- > @reliverse/dler-colors is a themeable, chainable, typed, truecolor-powered terminal styling toolkit — built for humans, not just terminals. It makes your CLI output beautiful, accessible, and expressive — with developer-first ergonomics, smart config, and blazing-fast performance.
3
+ > @reliverse/relico is a themeable, chainable, typed, truecolor-powered terminal styling toolkit — built for humans, not just terminals. It makes your CLI output beautiful, accessible, and expressive — with developer-first ergonomics, smart config, and blazing-fast performance.
4
4
 
5
- [sponsor](https://github.com/sponsors/blefnk) — [discord](https://discord.gg/Pb8uKbwpsJ) — [repo](https://github.com/reliverse/relico) — [npm](https://npmjs.com/@reliverse/dler-colors)
6
-
7
- ---
8
-
9
- ‼️Attention! The latest implementation of `@reliverse/relico` now lives in the framework repository for building libraries — `@reliverse/dler`. Please use [@reliverse/dler-colors](https://github.com/reliverse/dler/tree/main/packages/colors) instead of `@reliverse/relico`.
5
+ [sponsor](https://github.com/sponsors/blefnk) — [discord](https://discord.gg/Pb8uKbwpsJ) — [repo](https://github.com/reliverse/relico) — [npm](https://npmjs.com/@reliverse/relico)
10
6
 
11
7
  ## Why Relico?
12
8
 
@@ -28,7 +24,7 @@ Because terminal styling shouldn't feel like duct tape. **Relico** brings design
28
24
  ## Installation
29
25
 
30
26
  ```bash
31
- bun add @reliverse/dler-colors
27
+ bun add @reliverse/relico
32
28
  # bun • pnpm • yarn • npm
33
29
  ```
34
30
 
@@ -95,7 +91,7 @@ The readme will be updated soon.
95
91
  **If you're end-user OR developer, create `relico.config.ts` in your root**:
96
92
 
97
93
  ```ts
98
- import { defineConfig } from "@reliverse/dler-colors";
94
+ import { defineConfig } from "@reliverse/relico";
99
95
 
100
96
  export default defineConfig({
101
97
  colorLevel: 3, // 0 = off, 1 = basic, 2 = 256, 3 = truecolor
@@ -112,7 +108,7 @@ export default defineConfig({
112
108
  **If you're developer, initialize in your app (optional)**:
113
109
 
114
110
  ```ts
115
- import { initUserConfig, re } from "@reliverse/dler-colors";
111
+ import { initUserConfig, re } from "@reliverse/relico";
116
112
 
117
113
  // Use this to override Relico's
118
114
  // default settings for your app
@@ -124,7 +120,7 @@ console.log(re.info("Custom config loaded!"));
124
120
  ## API Sneak Peek
125
121
 
126
122
  ```ts
127
- import { re, rgb } from "@reliverse/dler-colors";
123
+ import { re, rgb } from "@reliverse/relico";
128
124
 
129
125
  console.log(re.red("Red!"));
130
126
  console.log(re.bold(re.green("Bold green")));
@@ -175,7 +171,7 @@ console.log(boldRed("This text is bold and red"));
175
171
  ### Want to Get Only Certain Colors?
176
172
 
177
173
  ```ts
178
- import type { DefaultColorKeys } from "@reliverse/dler-colors";
174
+ import type { DefaultColorKeys } from "@reliverse/relico";
179
175
  const brandColors: DefaultColorKeys[] = ["magentaBright", "maroon"];
180
176
  ```
181
177
 
@@ -184,7 +180,7 @@ const brandColors: DefaultColorKeys[] = ["magentaBright", "maroon"];
184
180
  Relico detects your terminal's capability:
185
181
 
186
182
  ```ts
187
- import { colorSupport } from "@reliverse/dler-colors";
183
+ import { colorSupport } from "@reliverse/relico";
188
184
 
189
185
  console.log(colorSupport.terminalName); // iTerm2, Windows Terminal, etc.
190
186
  console.log(colorSupport.level); // 0, 1, 2, or 3
@@ -200,7 +196,7 @@ console.log(colorSupport.level); // 0, 1, 2, or 3
200
196
  ### Custom RGB + Hex
201
197
 
202
198
  ```ts
203
- import { rgb, bgHex, hex } from "@reliverse/dler-colors";
199
+ import { rgb, bgHex, hex } from "@reliverse/relico";
204
200
 
205
201
  console.log(rgb(255, 105, 180)("Hot pink"));
206
202
  console.log(bgHex("#1e90ff")("Dodger blue background"));
@@ -209,7 +205,7 @@ console.log(bgHex("#1e90ff")("Dodger blue background"));
209
205
  ### Gradients & Rainbow
210
206
 
211
207
  ```ts
212
- import { gradient, multiGradient, rainbow } from "@reliverse/dler-colors";
208
+ import { gradient, multiGradient, rainbow } from "@reliverse/relico";
213
209
 
214
210
  console.log(rainbow("🎉 Woohoo!"));
215
211
  console.log(gradient("From red to blue", "#ff0000", "#0000ff"));
@@ -222,7 +218,7 @@ This function allows you to combine multiple color formatters into a single form
222
218
  ## Basic Usage
223
219
 
224
220
  ```typescript
225
- import { re, chain } from "@reliverse/dler-colors";
221
+ import { re, chain } from "@reliverse/relico";
226
222
 
227
223
  // Create a custom style that combines bold and red text
228
224
  const boldRed = chain(re.bold, re.red);
@@ -240,7 +236,7 @@ console.log(importantError("CRITICAL ERROR: System failure"));
240
236
  ## Creating Theme Combinations
241
237
 
242
238
  ```typescript
243
- import { re, chain } from "@reliverse/dler-colors";
239
+ import { re, chain } from "@reliverse/relico";
244
240
 
245
241
  // Create themed message styles
246
242
  const successStyle = chain(re.bold, re.green);
@@ -258,7 +254,7 @@ console.log(warnStyle("⚠ API rate limit approaching"));
258
254
  ## Custom RGB Combinations
259
255
 
260
256
  ```typescript
261
- import { re, rgb, bgRgb, chain } from "@reliverse/dler-colors";
257
+ import { re, rgb, bgRgb, chain } from "@reliverse/relico";
262
258
 
263
259
  // Create a custom color scheme with RGB values
264
260
  const customHeader = chain(
@@ -282,7 +278,7 @@ console.log(danger("Danger: High voltage detected!"));
282
278
  The `chain()` function automatically handles multiline text to prevent style leakage:
283
279
 
284
280
  ```typescript
285
- import { re, chain } from "@reliverse/dler-colors";
281
+ import { re, chain } from "@reliverse/relico";
286
282
 
287
283
  const highlight = chain(re.bgYellow, re.black, re.bold);
288
284
 
@@ -297,7 +293,7 @@ console.log(highlight(multilineText));
297
293
  ## Creating a Simple Logger
298
294
 
299
295
  ```typescript
300
- import { re, chain } from "@reliverse/dler-colors";
296
+ import { re, chain } from "@reliverse/relico";
301
297
 
302
298
  // Create logger styles
303
299
  const styles = {
@@ -320,7 +316,7 @@ const logger = {
320
316
  // Usage
321
317
  logger.info("Application started");
322
318
  logger.success("Data loaded successfully");
323
- logger.warning("Cache expired, refreshing data");
319
+ logger.warn("Cache expired, refreshing data");
324
320
  logger.error("Failed to connect to database");
325
321
  logger.debug("Request payload: " + JSON.stringify({id: 123}));
326
322
  ```
package/dist/mod.d.ts CHANGED
@@ -48,4 +48,13 @@ declare const OP_SYMBOL: unique symbol;
48
48
  export declare const setColorLevel: (level: ColorLevel) => void;
49
49
  export declare const re: Re;
50
50
  export declare const chain: (...parts: FormatCallable[]) => Re;
51
+ type BunColorInput = {
52
+ r: number;
53
+ g: number;
54
+ b: number;
55
+ a?: number;
56
+ } | [number, number, number] | [number, number, number, number] | string | number | {
57
+ toString(): string;
58
+ };
59
+ export declare const color: (input: BunColorInput, isBg?: boolean) => Re;
51
60
  export {};
package/dist/mod.js CHANGED
@@ -1,6 +1,5 @@
1
- const ESC = "\x1B[";
2
- const RESET = `${ESC}0m`;
3
- const OP_SYMBOL = Symbol("re.ops");
1
+ const RESET = "\x1B[0m";
2
+ const OP_SYMBOL = /* @__PURE__ */ Symbol("re.ops");
4
3
  const COLOR_LEVEL_OFF = 0;
5
4
  const COLOR_LEVEL_BASIC = 1;
6
5
  const COLOR_LEVEL_256 = 2;
@@ -30,17 +29,12 @@ const SGR_UNDERLINE = 4;
30
29
  const SGR_INVERSE = 7;
31
30
  const SGR_HIDDEN = 8;
32
31
  const SGR_STRIKETHROUGH = 9;
33
- const HEX_BYTE_LENGTH = 2;
34
- const HEX_RED_START = 0;
35
- const HEX_GREEN_START = 2;
36
- const HEX_BLUE_START = 4;
37
- const HEX_BLUE_END = 6;
38
32
  const HEX_RADIX = 16;
39
33
  const BRIGHT_SUFFIX_LENGTH = 6;
40
34
  const BG_PREFIX_LENGTH = 2;
41
- const BG_COLOR_START = 3;
42
35
  const BRIGHT_MIX_FACTOR = 0.25;
43
- const BRIGHT_SUFFIX_REGEX = /Bright$/u;
36
+ const BRIGHT_SUFFIX = "Bright";
37
+ const IS_BUN = typeof process !== "undefined" && process.versions?.bun !== void 0 && typeof Bun !== "undefined" && typeof Bun.color === "function";
44
38
  let CURRENT_LEVEL = COLOR_LEVEL_TRUECOLOR;
45
39
  export const setColorLevel = (level) => {
46
40
  if (level !== COLOR_LEVEL_OFF && level !== COLOR_LEVEL_BASIC && level !== COLOR_LEVEL_256 && level !== COLOR_LEVEL_TRUECOLOR) {
@@ -49,44 +43,55 @@ export const setColorLevel = (level) => {
49
43
  CURRENT_LEVEL = level;
50
44
  };
51
45
  const clampByte = (n) => {
52
- if (!Number.isFinite(n)) {
53
- return MIN_BYTE;
54
- }
55
- if (n < MIN_BYTE) {
56
- return MIN_BYTE;
57
- }
58
- if (n > MAX_BYTE) {
59
- return MAX_BYTE;
60
- }
46
+ if (n <= MIN_BYTE || !Number.isFinite(n)) return MIN_BYTE;
47
+ if (n >= MAX_BYTE) return MAX_BYTE;
61
48
  return Math.round(n);
62
49
  };
63
- const BASIC8 = [
64
- { r: 0, g: 0, b: 0 },
65
- // black
66
- { r: 205, g: 0, b: 0 },
67
- // red
68
- { r: 0, g: 205, b: 0 },
69
- // green
70
- { r: 205, g: 205, b: 0 },
71
- // yellow
72
- { r: 0, g: 0, b: 238 },
73
- // blue
74
- { r: 205, g: 0, b: 205 },
75
- // magenta
76
- { r: 0, g: 205, b: 205 },
77
- // cyan
78
- { r: 229, g: 229, b: 229 }
79
- // white (light gray)
80
- ];
81
- const sgr = (codes) => `${ESC}${codes.join(";")}m`;
50
+ const BASIC8 = Object.freeze([
51
+ Object.freeze({ r: 0, g: 0, b: 0 }),
52
+ Object.freeze({ r: 205, g: 0, b: 0 }),
53
+ Object.freeze({ r: 0, g: 205, b: 0 }),
54
+ Object.freeze({ r: 205, g: 205, b: 0 }),
55
+ Object.freeze({ r: 0, g: 0, b: 238 }),
56
+ Object.freeze({ r: 205, g: 0, b: 205 }),
57
+ Object.freeze({ r: 0, g: 205, b: 205 }),
58
+ Object.freeze({ r: 229, g: 229, b: 229 })
59
+ ]);
60
+ const sgrCache = /* @__PURE__ */ new Map();
61
+ const sgr = (codes) => {
62
+ if (codes.length === 1) {
63
+ const code = codes[0];
64
+ const cached2 = sgrCache.get(String(code));
65
+ if (cached2) return cached2;
66
+ const seq2 = `\x1B[${code}m`;
67
+ sgrCache.set(String(code), seq2);
68
+ return seq2;
69
+ }
70
+ const key = codes.join(";");
71
+ const cached = sgrCache.get(key);
72
+ if (cached) return cached;
73
+ const seq = `\x1B[${key}m`;
74
+ sgrCache.set(key, seq);
75
+ return seq;
76
+ };
82
77
  const nearestBasicIndex = (rgb) => {
78
+ if (IS_BUN) {
79
+ const ansiStr = Bun.color(rgb, "ansi-16");
80
+ if (ansiStr) {
81
+ const match = ansiStr.match(/38;5;(\d+)/);
82
+ if (match?.[1]) {
83
+ return Number.parseInt(match[1], 10) & 7;
84
+ }
85
+ }
86
+ }
83
87
  let best = 0;
84
88
  let bestDist = Number.POSITIVE_INFINITY;
85
- for (let i = 0; i < BASIC8.length; i++) {
89
+ for (let i = 0; i < 8; i++) {
86
90
  const c = BASIC8[i];
87
91
  const dr = c.r - rgb.r;
88
92
  const dg = c.g - rgb.g;
89
93
  const db = c.b - rgb.b;
94
+ if (dr === 0 && dg === 0 && db === 0) return i;
90
95
  const d = dr * dr + dg * dg + db * db;
91
96
  if (d < bestDist) {
92
97
  bestDist = d;
@@ -96,22 +101,27 @@ const nearestBasicIndex = (rgb) => {
96
101
  return best;
97
102
  };
98
103
  const rgbToAnsi256 = (rgb) => {
99
- if (rgb.r === rgb.g && rgb.g === rgb.b) {
100
- if (rgb.r < ANSI_256_GRAYSCALE_MIN) {
101
- return ANSI_256_BASE_OFFSET;
102
- }
103
- if (rgb.r > ANSI_256_GRAYSCALE_MAX) {
104
- return ANSI_256_BRIGHT_THRESHOLD;
104
+ if (IS_BUN) {
105
+ const ansiStr = Bun.color(rgb, "ansi-256");
106
+ if (ansiStr) {
107
+ const match = ansiStr.match(/38;5;(\d+)/);
108
+ if (match?.[1]) {
109
+ return Number.parseInt(match[1], 10);
110
+ }
105
111
  }
112
+ }
113
+ if (rgb.r === rgb.g && rgb.g === rgb.b) {
114
+ if (rgb.r < ANSI_256_GRAYSCALE_MIN) return ANSI_256_BASE_OFFSET;
115
+ if (rgb.r > ANSI_256_GRAYSCALE_MAX) return ANSI_256_BRIGHT_THRESHOLD;
106
116
  const step = Math.round(
107
117
  (rgb.r - ANSI_256_GRAYSCALE_MIN) / ANSI_256_GRAYSCALE_RANGE * ANSI_256_GRAYSCALE_STEPS
108
118
  );
109
119
  return ANSI_256_GRAYSCALE_BASE + step;
110
120
  }
111
- const r = Math.round(rgb.r / MAX_BYTE * ANSI_256_RGB_LEVELS);
112
- const g = Math.round(rgb.g / MAX_BYTE * ANSI_256_RGB_LEVELS);
113
- const b = Math.round(rgb.b / MAX_BYTE * ANSI_256_RGB_LEVELS);
114
- return ANSI_256_BASE_OFFSET + ANSI_256_RGB_RED_MULTIPLIER * r + ANSI_256_RGB_GREEN_MULTIPLIER * g + b;
121
+ const r = (rgb.r * ANSI_256_RGB_LEVELS + 127) / MAX_BYTE | 0;
122
+ const g = (rgb.g * ANSI_256_RGB_LEVELS + 127) / MAX_BYTE | 0;
123
+ const b = (rgb.b * ANSI_256_RGB_LEVELS + 127) / MAX_BYTE | 0;
124
+ return ANSI_256_BASE_OFFSET + r * ANSI_256_RGB_RED_MULTIPLIER + g * ANSI_256_RGB_GREEN_MULTIPLIER + b;
115
125
  };
116
126
  const NAMED_COLORS = {
117
127
  black: "#000000",
@@ -134,57 +144,70 @@ const NAMED_COLORS = {
134
144
  olive: "#808000",
135
145
  silver: "#c0c0c0"
136
146
  };
147
+ const rgbCache = /* @__PURE__ */ new Map();
148
+ for (const name of ["black", "white", "red", "green", "blue"]) {
149
+ const hex = NAMED_COLORS[name];
150
+ if (hex) {
151
+ const clean = hex.slice(1);
152
+ const r = Number.parseInt(clean.slice(0, 2), HEX_RADIX);
153
+ const g = Number.parseInt(clean.slice(2, 4), HEX_RADIX);
154
+ const b = Number.parseInt(clean.slice(4, 6), HEX_RADIX);
155
+ rgbCache.set(name, Object.freeze({ r, g, b }));
156
+ }
157
+ }
137
158
  const mixWithWhite = (rgb, factor) => {
138
- const t = factor;
159
+ const invFactor = 1 - factor;
139
160
  return {
140
- r: clampByte(rgb.r * (1 - t) + WHITE_RGB * t),
141
- g: clampByte(rgb.g * (1 - t) + WHITE_RGB * t),
142
- b: clampByte(rgb.b * (1 - t) + WHITE_RGB * t)
161
+ r: clampByte(rgb.r * invFactor + WHITE_RGB * factor),
162
+ g: clampByte(rgb.g * invFactor + WHITE_RGB * factor),
163
+ b: clampByte(rgb.b * invFactor + WHITE_RGB * factor)
143
164
  };
144
165
  };
145
166
  const fromNamed = (name) => {
167
+ const cached = rgbCache.get(name);
168
+ if (cached) return cached;
146
169
  const hex = NAMED_COLORS[name];
147
- if (!hex || typeof hex !== "string") {
148
- return { r: 0, g: 0, b: 0 };
149
- }
150
- const clean = hex.startsWith("#") ? hex.slice(1) : hex;
151
- if (clean.length !== HEX_BLUE_END && clean.length !== 3) {
152
- return { r: 0, g: 0, b: 0 };
170
+ if (!hex) return { r: 0, g: 0, b: 0 };
171
+ if (IS_BUN) {
172
+ const rgb = Bun.color(hex, "{rgb}");
173
+ if (rgb) {
174
+ rgbCache.set(name, rgb);
175
+ return rgb;
176
+ }
153
177
  }
154
- let rHex, gHex, bHex;
155
- if (clean.length === 3) {
156
- rHex = clean[0].repeat(2);
157
- gHex = clean[1].repeat(2);
158
- bHex = clean[2].repeat(2);
178
+ const clean = hex[0] === "#" ? hex.slice(1) : hex;
179
+ const len = clean.length;
180
+ let r, g, b;
181
+ if (len === 3) {
182
+ const rv = Number.parseInt(clean[0], HEX_RADIX);
183
+ const gv = Number.parseInt(clean[1], HEX_RADIX);
184
+ const bv = Number.parseInt(clean[2], HEX_RADIX);
185
+ r = rv << 4 | rv;
186
+ g = gv << 4 | gv;
187
+ b = bv << 4 | bv;
188
+ } else if (len === 6) {
189
+ r = Number.parseInt(clean.slice(0, 2), HEX_RADIX);
190
+ g = Number.parseInt(clean.slice(2, 4), HEX_RADIX);
191
+ b = Number.parseInt(clean.slice(4, 6), HEX_RADIX);
159
192
  } else {
160
- rHex = clean.slice(HEX_RED_START, HEX_BYTE_LENGTH);
161
- gHex = clean.slice(HEX_GREEN_START, HEX_BLUE_START);
162
- bHex = clean.slice(HEX_BLUE_START, HEX_BLUE_END);
193
+ return { r: 0, g: 0, b: 0 };
163
194
  }
164
- const r = Number.parseInt(rHex, HEX_RADIX);
165
- const g = Number.parseInt(gHex, HEX_RADIX);
166
- const b = Number.parseInt(bHex, HEX_RADIX);
167
195
  if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) {
168
196
  return { r: 0, g: 0, b: 0 };
169
197
  }
170
- return { r, g, b };
198
+ const result = Object.freeze({ r, g, b });
199
+ rgbCache.set(name, result);
200
+ return result;
171
201
  };
172
202
  const toBaseName = (compound) => {
173
- if (!compound || typeof compound !== "string") {
174
- return "black";
175
- }
176
- const base = compound.replace(BRIGHT_SUFFIX_REGEX, "");
177
- if (!base) {
178
- return "black";
179
- }
180
- const key = base.charAt(0).toLowerCase() + base.slice(1);
181
- return key;
203
+ if (!compound) return "black";
204
+ const base = compound.slice(0, -BRIGHT_SUFFIX_LENGTH);
205
+ if (!base) return "black";
206
+ return base[0]?.toLowerCase() + base.slice(1);
182
207
  };
183
208
  const parseColorName = (name) => {
184
- if (!name || typeof name !== "string") {
185
- return { rgb: { r: 0, g: 0, b: 0 }, wantBright: false };
186
- }
187
- if (name.endsWith("Bright")) {
209
+ if (!name) return { rgb: { r: 0, g: 0, b: 0 }, wantBright: false };
210
+ if (name.endsWith(BRIGHT_SUFFIX)) {
188
211
  const base = toBaseName(name);
189
212
  const rgb = fromNamed(base);
190
213
  const rgbAdj = mixWithWhite(rgb, BRIGHT_MIX_FACTOR);
@@ -193,63 +216,110 @@ const parseColorName = (name) => {
193
216
  return { rgb: fromNamed(name), wantBright: false };
194
217
  };
195
218
  const openForOp = (op) => {
196
- if (CURRENT_LEVEL === COLOR_LEVEL_OFF) {
197
- return "";
198
- }
219
+ if (CURRENT_LEVEL === COLOR_LEVEL_OFF) return "";
199
220
  switch (op.kind) {
200
221
  case "style":
201
222
  return sgr(op.open);
202
- case "fg-basic":
203
- return sgr([(op.bright ? SGR_FG_BRIGHT_BASE : SGR_FG_BASE) + op.idx]);
204
- case "bg-basic":
205
- return sgr([(op.bright ? SGR_BG_BRIGHT_BASE : SGR_BG_BASE) + op.idx]);
206
- case "fg-256":
207
- return `${ESC}38;5;${op.code}m`;
208
- case "bg-256":
209
- return `${ESC}48;5;${op.code}m`;
210
- case "fg-true":
211
- return `${ESC}38;2;${op.rgb.r};${op.rgb.g};${op.rgb.b}m`;
212
- case "bg-true":
213
- return `${ESC}48;2;${op.rgb.r};${op.rgb.g};${op.rgb.b}m`;
223
+ case "fg-basic": {
224
+ const code = (op.bright ? SGR_FG_BRIGHT_BASE : SGR_FG_BASE) + op.idx;
225
+ return sgr([code]);
226
+ }
227
+ case "bg-basic": {
228
+ const code = (op.bright ? SGR_BG_BRIGHT_BASE : SGR_BG_BASE) + op.idx;
229
+ return sgr([code]);
230
+ }
231
+ case "fg-256": {
232
+ if (IS_BUN) {
233
+ const ansi = Bun.color(op.code, "ansi-256");
234
+ if (ansi) return ansi;
235
+ }
236
+ return `\x1B[38;5;${op.code}m`;
237
+ }
238
+ case "bg-256": {
239
+ if (IS_BUN) {
240
+ const fgAnsi = Bun.color(op.code, "ansi-256");
241
+ if (fgAnsi) return fgAnsi.replace("38;5;", "48;5;");
242
+ }
243
+ return `\x1B[48;5;${op.code}m`;
244
+ }
245
+ case "fg-true": {
246
+ if (IS_BUN) {
247
+ const ansi = Bun.color(op.rgb, "ansi-16m");
248
+ if (ansi) return ansi;
249
+ }
250
+ const { r, g, b } = op.rgb;
251
+ return `\x1B[38;2;${r};${g};${b}m`;
252
+ }
253
+ case "bg-true": {
254
+ if (IS_BUN) {
255
+ const fgAnsi = Bun.color(op.rgb, "ansi-16m");
256
+ if (fgAnsi) return fgAnsi.replace("38;2;", "48;2;");
257
+ }
258
+ const { r, g, b } = op.rgb;
259
+ return `\x1B[48;2;${r};${g};${b}m`;
260
+ }
214
261
  default:
215
262
  return "";
216
263
  }
217
264
  };
218
265
  const opsToOpen = (ops) => {
219
- if (CURRENT_LEVEL === COLOR_LEVEL_OFF) {
220
- return "";
221
- }
222
- let out = "";
223
- for (const op of ops) {
224
- out += openForOp(op);
266
+ if (CURRENT_LEVEL === COLOR_LEVEL_OFF) return "";
267
+ const len = ops.length;
268
+ if (len === 0) return "";
269
+ if (len === 1) return openForOp(ops[0]);
270
+ let result = "";
271
+ for (let i = 0; i < len; i++) {
272
+ result += openForOp(ops[i]);
225
273
  }
226
- return out;
274
+ return result;
227
275
  };
228
276
  const applyOpsToText = (ops, input) => {
229
277
  const text = String(input);
230
- if (CURRENT_LEVEL === COLOR_LEVEL_OFF || ops.length === 0 || text.length === 0) {
278
+ const textLen = text.length;
279
+ if (CURRENT_LEVEL === COLOR_LEVEL_OFF || ops.length === 0 || textLen === 0) {
231
280
  return text;
232
281
  }
233
282
  const open = opsToOpen(ops);
234
- if (!text.includes("\n")) {
283
+ if (!open) return text;
284
+ const nlIdx = text.indexOf("\n");
285
+ if (nlIdx === -1) {
235
286
  return `${open}${text}${RESET}`;
236
287
  }
237
- const lines = text.split("\n");
238
- const result = new Array(lines.length);
239
- for (let i = 0; i < lines.length; i++) {
240
- const line = lines[i];
241
- if (line.endsWith("\r")) {
242
- result[i] = `${open}${line.slice(0, -1)}\r${RESET}`;
288
+ let result = "";
289
+ let start = 0;
290
+ while (start < textLen) {
291
+ const end = text.indexOf("\n", start);
292
+ if (end === -1) {
293
+ const line2 = text.slice(start);
294
+ if (line2) {
295
+ if (line2.endsWith("\r")) {
296
+ result += `${open}${line2.slice(0, -1)}\r${RESET}`;
297
+ } else {
298
+ result += `${open}${line2}${RESET}`;
299
+ }
300
+ }
301
+ break;
302
+ }
303
+ if (start > 0) result += "\n";
304
+ const line = text.slice(start, end);
305
+ if (line) {
306
+ if (line.endsWith("\r")) {
307
+ result += `${open}${line.slice(0, -1)}\r${RESET}`;
308
+ } else {
309
+ result += `${open}${line}${RESET}`;
310
+ }
243
311
  } else {
244
- result[i] = `${open}${line}${RESET}`;
312
+ result += `${open}${RESET}`;
245
313
  }
314
+ start = end + 1;
246
315
  }
247
- return result.join("\n");
316
+ return result;
248
317
  };
249
318
  const mkFgOpsFromRgb = (rgb, wantBright = false) => {
250
319
  if (CURRENT_LEVEL === COLOR_LEVEL_BASIC) {
251
- const idx = nearestBasicIndex(rgb);
252
- return [{ kind: "fg-basic", idx, bright: wantBright }];
320
+ return [
321
+ { kind: "fg-basic", idx: nearestBasicIndex(rgb), bright: wantBright }
322
+ ];
253
323
  }
254
324
  if (CURRENT_LEVEL === COLOR_LEVEL_256) {
255
325
  return [{ kind: "fg-256", code: rgbToAnsi256(rgb) }];
@@ -258,8 +328,9 @@ const mkFgOpsFromRgb = (rgb, wantBright = false) => {
258
328
  };
259
329
  const mkBgOpsFromRgb = (rgb, wantBright = false) => {
260
330
  if (CURRENT_LEVEL === COLOR_LEVEL_BASIC) {
261
- const idx = nearestBasicIndex(rgb);
262
- return [{ kind: "bg-basic", idx, bright: wantBright }];
331
+ return [
332
+ { kind: "bg-basic", idx: nearestBasicIndex(rgb), bright: wantBright }
333
+ ];
263
334
  }
264
335
  if (CURRENT_LEVEL === COLOR_LEVEL_256) {
265
336
  return [{ kind: "bg-256", code: rgbToAnsi256(rgb) }];
@@ -267,46 +338,57 @@ const mkBgOpsFromRgb = (rgb, wantBright = false) => {
267
338
  return [{ kind: "bg-true", rgb }];
268
339
  };
269
340
  const STYLE_TABLE = {
270
- reset: { kind: "style", open: [SGR_RESET] },
271
- bold: { kind: "style", open: [SGR_BOLD] },
272
- dim: { kind: "style", open: [SGR_DIM] },
273
- italic: { kind: "style", open: [SGR_ITALIC] },
274
- underline: { kind: "style", open: [SGR_UNDERLINE] },
275
- inverse: { kind: "style", open: [SGR_INVERSE] },
276
- hidden: { kind: "style", open: [SGR_HIDDEN] },
277
- strikethrough: { kind: "style", open: [SGR_STRIKETHROUGH] }
278
- };
279
- const STYLE_KEYS = /* @__PURE__ */ new Set([
280
- "reset",
281
- "bold",
282
- "dim",
283
- "italic",
284
- "underline",
285
- "inverse",
286
- "hidden",
287
- "strikethrough"
288
- ]);
289
- const isColorKey = (key) => {
290
- if (!key || typeof key !== "string") {
291
- return false;
292
- }
293
- if (key in NAMED_COLORS) {
294
- return true;
295
- }
296
- if (key.endsWith("Bright") && key.length > BRIGHT_SUFFIX_LENGTH) {
297
- const baseName = key.slice(0, -BRIGHT_SUFFIX_LENGTH);
298
- return baseName in NAMED_COLORS;
299
- }
300
- return false;
341
+ reset: Object.freeze({ kind: "style", open: [SGR_RESET] }),
342
+ bold: Object.freeze({ kind: "style", open: [SGR_BOLD] }),
343
+ dim: Object.freeze({ kind: "style", open: [SGR_DIM] }),
344
+ italic: Object.freeze({ kind: "style", open: [SGR_ITALIC] }),
345
+ underline: Object.freeze({ kind: "style", open: [SGR_UNDERLINE] }),
346
+ inverse: Object.freeze({ kind: "style", open: [SGR_INVERSE] }),
347
+ hidden: Object.freeze({ kind: "style", open: [SGR_HIDDEN] }),
348
+ strikethrough: Object.freeze({ kind: "style", open: [SGR_STRIKETHROUGH] })
301
349
  };
350
+ const STYLE_KEYS = Object.freeze(
351
+ /* @__PURE__ */ new Set([
352
+ "reset",
353
+ "bold",
354
+ "dim",
355
+ "italic",
356
+ "underline",
357
+ "inverse",
358
+ "hidden",
359
+ "strikethrough"
360
+ ])
361
+ );
362
+ const COLOR_KEYS = Object.freeze(new Set(Object.keys(NAMED_COLORS)));
363
+ const BRIGHT_COLOR_KEYS = Object.freeze(
364
+ new Set(Object.keys(NAMED_COLORS).map((name) => `${name}Bright`))
365
+ );
366
+ const BG_COLOR_KEYS = Object.freeze(
367
+ new Set(
368
+ Object.keys(NAMED_COLORS).map(
369
+ (name) => `bg${name[0]?.toUpperCase()}${name.slice(1)}`
370
+ )
371
+ )
372
+ );
373
+ const BG_BRIGHT_COLOR_KEYS = Object.freeze(
374
+ new Set(
375
+ Object.keys(NAMED_COLORS).map(
376
+ (name) => `bg${name[0]?.toUpperCase()}${name.slice(1)}Bright`
377
+ )
378
+ )
379
+ );
380
+ const isColorKey = (key) => COLOR_KEYS.has(key) || BRIGHT_COLOR_KEYS.has(key);
302
381
  const isBgKey = (key) => {
303
- if (!key || typeof key !== "string" || !key.startsWith("bg") || key.length <= BG_PREFIX_LENGTH) {
382
+ const len = key.length;
383
+ if (len <= BG_PREFIX_LENGTH || key[0] !== "b" || key[1] !== "g") {
304
384
  return false;
305
385
  }
306
- const colorPart = key.charAt(BG_PREFIX_LENGTH).toLowerCase() + key.slice(BG_COLOR_START);
307
- return isColorKey(colorPart);
386
+ return BG_COLOR_KEYS.has(key) || BG_BRIGHT_COLOR_KEYS.has(key);
308
387
  };
388
+ const proxyCache = /* @__PURE__ */ new WeakMap();
309
389
  const callableProxy = (ops) => {
390
+ const cached = proxyCache.get(ops);
391
+ if (cached) return cached;
310
392
  const base = ((input) => applyOpsToText(ops, input));
311
393
  Object.defineProperty(base, OP_SYMBOL, {
312
394
  value: ops,
@@ -314,47 +396,60 @@ const callableProxy = (ops) => {
314
396
  configurable: false,
315
397
  writable: false
316
398
  });
317
- return new Proxy(base, {
399
+ const proxy = new Proxy(base, {
318
400
  apply(_target, _thisArg, argArray) {
319
- const [input] = argArray;
320
- return applyOpsToText(ops, input);
401
+ return applyOpsToText(ops, argArray[0]);
321
402
  },
322
403
  get(_target, prop) {
404
+ if (prop === OP_SYMBOL) return ops;
323
405
  const key = String(prop);
324
- if (prop === OP_SYMBOL) {
325
- return ops;
326
- }
327
406
  if (STYLE_KEYS.has(key)) {
328
407
  const op = STYLE_TABLE[key];
329
- return callableProxy([...ops, op]);
408
+ const newOps = [...ops, op];
409
+ return callableProxy(newOps);
330
410
  }
331
411
  if (isBgKey(key)) {
332
- const raw = key.slice(BG_PREFIX_LENGTH);
333
- if (!raw) {
334
- return callableProxy(ops);
335
- }
336
- const colorName = raw.charAt(0).toLowerCase() + raw.slice(1);
412
+ const colorName = key[2]?.toLowerCase() + key.slice(3);
337
413
  const { rgb, wantBright } = parseColorName(colorName);
338
- return callableProxy([...ops, ...mkBgOpsFromRgb(rgb, wantBright)]);
414
+ const bgOps = mkBgOpsFromRgb(rgb, wantBright);
415
+ const newOps = [...ops, ...bgOps];
416
+ return callableProxy(newOps);
339
417
  }
340
418
  if (isColorKey(key)) {
341
419
  const { rgb, wantBright } = parseColorName(key);
342
- return callableProxy([...ops, ...mkFgOpsFromRgb(rgb, wantBright)]);
420
+ const fgOps = mkFgOpsFromRgb(rgb, wantBright);
421
+ const newOps = [...ops, ...fgOps];
422
+ return callableProxy(newOps);
343
423
  }
344
- return callableProxy(ops);
424
+ return proxy;
345
425
  }
346
426
  });
427
+ proxyCache.set(ops, proxy);
428
+ return proxy;
347
429
  };
348
430
  export const re = callableProxy([]);
349
431
  export const chain = (...parts) => {
432
+ if (parts.length === 0) return re;
433
+ if (parts.length === 1) return parts[0];
350
434
  const collected = [];
351
- for (const p of parts) {
352
- const ops = p[OP_SYMBOL];
353
- if (ops && ops.length > 0) {
354
- for (const op of ops) {
355
- collected.push(op);
356
- }
435
+ for (let i = 0; i < parts.length; i++) {
436
+ const ops = parts[i][OP_SYMBOL];
437
+ if (ops?.length) {
438
+ collected.push(...ops);
357
439
  }
358
440
  }
359
441
  return callableProxy(collected);
360
442
  };
443
+ export const color = (input, isBg = false) => {
444
+ if (!IS_BUN) {
445
+ if (typeof input === "object" && "r" in input && "g" in input && "b" in input) {
446
+ const ops2 = isBg ? mkBgOpsFromRgb(input, false) : mkFgOpsFromRgb(input, false);
447
+ return callableProxy(ops2);
448
+ }
449
+ return re;
450
+ }
451
+ const rgb = Bun.color(input, "{rgb}");
452
+ if (!rgb) return re;
453
+ const ops = isBg ? mkBgOpsFromRgb(rgb, false) : mkFgOpsFromRgb(rgb, false);
454
+ return callableProxy(ops);
455
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reliverse/relico",
3
- "author": "reliverse",
4
- "version": "1.4.2",
3
+ "version": "2.2.7",
4
+ "private": false,
5
5
  "type": "module",
6
6
  "description": "@reliverse/relico is a themeable, chainable, typed, truecolor-powered, modern terminal styling toolkit. Designed to make your CLI output colorful, accessible, and human-friendly. It gives you a flexible way to apply colors and styles — with reliability and a smart developer experience baked in. Built for humans, not just terminals.",
7
7
  "exports": {
@@ -10,12 +10,13 @@
10
10
  "default": "./dist/mod.js"
11
11
  }
12
12
  },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "dependencies": {},
13
17
  "files": [
14
18
  "dist",
15
19
  "package.json"
16
20
  ],
17
- "publishConfig": {
18
- "access": "public"
19
- },
20
21
  "license": "MIT"
21
22
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- # MIT License
2
-
3
- Copyright (c) Nazar Kornienko (blefnk), Bleverse, Reliverse
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.