feat(editor): add multicolor highlight with auto-contrast text (#3110)

* feat(editor): add multicolor highlight with auto-contrast text

Enable the Tiptap Highlight extension in multicolor mode, replacing the
single-color yellow toggle with a full color picker (16 presets + custom).
When the chosen highlight color is perceptually dark, text inside the mark
automatically renders white for readability.

Changes span the full pipeline:
- Editor: ColorPicker UI, extended renderHTML for contrast detection
- PDF: normalizeMarkElements preserves data-color as inline style
- DOCX: mergeStyle reads actual background-color from <mark>
- Utils: new isDarkColor() luminance helper

Backward-compatible: legacy <mark> without data-color still renders yellow.

Resolves #3109

* fix: handle multicolor highlight edge cases

---------

Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
JamesGoslings
2026-06-01 14:58:15 +02:00
committed by GitHub
co-authored by Amruth Pillai
parent 1507d869c7
commit e96a51f31c
63 changed files with 565 additions and 179 deletions
+11 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { parseColorString, rgbaStringToHex } from "./color";
import { isDarkColor, parseColorString, rgbaStringToHex } from "./color";
describe("rgbaStringToHex", () => {
it("converts opaque rgb to hex", () => {
@@ -28,6 +28,16 @@ describe("rgbaStringToHex", () => {
});
});
describe("isDarkColor", () => {
it("classifies opaque dark colors as dark", () => {
expect(isDarkColor("rgba(0, 0, 0, 1)")).toBe(true);
});
it("composites transparent colors over white before checking darkness", () => {
expect(isDarkColor("rgba(0, 0, 0, 0.1)")).toBe(false);
});
});
describe("parseColorString", () => {
describe("rgb format", () => {
it("parses rgb without alpha as alpha=1", () => {