Skip to content

feat(color): add es-toolkit/color module#1683

Open
dayongkr wants to merge 13 commits intomainfrom
feat/color
Open

feat(color): add es-toolkit/color module#1683
dayongkr wants to merge 13 commits intomainfrom
feat/color

Conversation

@dayongkr
Copy link
Copy Markdown
Collaborator

@dayongkr dayongkr commented Apr 5, 2026

Summary

  • Add es-toolkit/color subpath for terminal color utilities
  • Fixes known picocolors bugs: FORCE_COLOR=0 handling, process ReferenceError, CI pipe detection
  • Supports 16 basic colors, 256-color, RGB, and Hex
  • Includes stripAnsi, createColors, colorLevel, isColorSupported
  • Pre-configured color object for direct usage (color.red('hello'))
  • FORCE_COLOR / NO_COLOR spec compliant
  • 55 tests, 100% coverage
  • Documentation in 4 languages (en, ko, ja, zh_hans)

Test plan

  • yarn vitest run src/color/ — all 55 tests pass
  • yarn lint — no errors in src/color/
  • tsc --noEmit — no type errors
  • yarn build — dist/color/ generated correctly
  • Verify docs render at /reference/color/color

dayongkr added 2 commits April 5, 2026 16:10
Add a new `es-toolkit/color` subpath with ANSI terminal color utilities:
- 16 basic colors, bright variants, and background colors
- Extended colors: ansi256, rgb, hex
- Color level detection (FORCE_COLOR, NO_COLOR, TTY, COLORTERM, TERM)
- stripAnsi utility
- createColors factory for custom instances
- Pre-configured `color` object for direct usage
Add reference docs for color, createColors, colorLevel, and stripAnsi
in English, Korean, Japanese, and Chinese. Update sidebar configs.
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
es-toolkit Ready Ready Preview, Comment Apr 17, 2026 5:52am

Request Review

parseInt('abcxyz', 16) silently parses 'abc' and ignores 'xyz'.
Switch to Number('0x' + str) which returns NaN for any non-hex character.
Validate hex strings with /^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$/ instead
of Number('0x' + str). Only #RGB and #RRGGBB are accepted — #RGBA and
#RRGGBBAA are excluded because ANSI terminals don't support alpha.
@dayongkr dayongkr marked this pull request as ready for review April 10, 2026 05:41
@dayongkr dayongkr requested a review from raon0211 as a code owner April 10, 2026 05:41
- Rename stripAnsi to stripColor to clarify SGR-only scope
- Add ./color to ENTRYPOINTS in check-dist.spec.ts
- Document that colorLevel is evaluated once at import time
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.97%. Comparing base (b4c3cb4) to head (5529c76).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##             main    #1683    +/-   ##
========================================
  Coverage   99.97%   99.97%            
========================================
  Files         497      548    +51     
  Lines        4662     4777   +115     
  Branches     1348     1369    +21     
========================================
+ Hits         4661     4776   +115     
  Misses          1        1            
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread src/color/formatter.ts Outdated
Comment on lines +10 to +15
const str = toString(text);

if (str === '') {
return '';
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about requiring strings in text and removing this logic?

Comment thread src/color/formatter.spec.ts Outdated
@@ -0,0 +1,93 @@
// @vitest-environment node
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There might be some edge cases here -- I think we need to make the test cases more complete.

Comment thread src/color/createColors.ts Outdated
* const noColor = createColors(false);
* noColor.red('hello'); // 'hello'
*/
export function createColors(enabled?: boolean): Colors {
Copy link
Copy Markdown
Collaborator

@raon0211 raon0211 Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about we make these into functions like this?

export function reset(str: string) {
  if (detectColorLevel()) {
    return str;
  }

  return formatWithColor(str, '\x1b[0m', '\x1b[0m');
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import colors from 'es-toolkit/colors';

I think we also have to provide this too

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import * as colors from './colors';

export default colors;

Require callers to pass strings to color functions (no more
`unknown` coercion via `String(text)`). This matches es-toolkit's
stricter API philosophy and removes a tiny per-call cost.

Split the monolithic `formatter.ts` into `createFormatter.ts`
(the factory) and `parseHex.ts` (the hex parser) so each concern
has its own file and spec.
Each color function (red, bold, hex, …) now lives in its own
file with its own spec, enabling tree-shaking when users only
need one or two utilities.

The `createColors(enabled)` factory is removed — runtime toggling
was not a real need. `FORCE_COLOR`/`NO_COLOR` environment variables
and auto-detection cover the common cases; tests force enable via
`vi.mock('./colorLevel.ts')`.

Usage:

  import { red, bold, hex } from 'es-toolkit/color';
  import color from 'es-toolkit/color';

  red('error');
  color.bold(color.red('error'));

Internally, a shared `makeColor` factory wires each utility to
`createFormatter` when color is supported, or an identity
passthrough otherwise.
Backgrounds wrapped across CRLF (`\r\n`) previously left the `\r`
inside the background scope, which could render incorrectly on
terminals that reset cursor position on CR. Match chalk's behavior
by handling `\r?\n` as one atomic newline.

Also add a spec for the `makeColor` disabled path to bring the
color module to 100% branch coverage.
dayongkr and others added 2 commits April 17, 2026 13:51
Rewrite the color reference in all four languages to list every
utility up front and show the new import patterns:

  import { red } from 'es-toolkit/color';
  import color from 'es-toolkit/color';

Remove the `createColors` page since the factory was dropped.
…trol-regex

The regex literal `/\u001b\[[0-9;]*m/g` tripped ESLint's
`no-control-regex` rule because the escape sequence resolved to
a control character. Build the pattern through `RegExp` with
`String.fromCharCode(0x1b)` so the control byte stays out of
the source at lint time.

Also update the JSDoc example to use the new individual imports.
The `Colors` interface was the return type of the removed
`createColors` factory and has no remaining callers.

// Re-open the outer style when a nested call with the same color
// inserts a close code inside the text.
if (open !== close && result.includes(close)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is open !== close needed here?

Comment thread src/color/colorLevel.ts
* 7. Node.js hasColors() — Runtime API (Node 11.13+) for accurate detection
* 8. Default — Basic 16 colors if none of the above matched
*/
export function detectColorLevel(): ColorLevel {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have to simplify this function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants