tiptap
tiptap copied to clipboard
Text color seems to be automatically converted to RGB
Description It seems like the colors that should be in hex, are automatically transformed to rgb and therefore the check for the color is failing.
- The video
https://user-images.githubusercontent.com/45892659/131475139-2cbc0921-af20-40ca-9acc-75f48340f178.mov
- Rendered HTML

- Editor Content String from Vue component of the demo

Steps to reproduce the bug Steps to reproduce the behavior:
- Go to https://www.tiptap.dev/api/extensions/color
- Set color for some text to any color you like
- See the
color inputin the menubar and you will notice that the color from the text that you changed will get recognized by input, but the color which was already there, i.e. purple(#958DF1) gets converted to RGB
Expected behavior The color shouldn't be converted to rgb so that we have consistent behaviour
Environment?
- operating system: MacOs 11
- browser: Chrome
- mobile/desktop: Desktop
- tiptap version: Whichever it is used in the demo website(https://www.tiptap.dev/api/extensions/color) on 31.08.2021
I think that’s a browser thing, but I’ll check it.
Yep, it's a browser thing!
The computed value is the corresponding rgb() or rgba() value:
https://developer.mozilla.org/en-US/docs/Web/CSS/color#formal_definition
const div = document.createElement('div');
div.style.color = '#958DF1';
console.log(div.style.color);
// rgb(149, 141, 241)
Three things we could do:
- Recommend to use rgb
- Use rgb in the example
- Make isActive smart enough to convert it and check for both
I think we go with 1 and 2, not sure about 3.
Anyway, thanks for reporting!
I fiddled around a bit. A funny side node: the native HTML color picker only works with hex colors 🙃
There are some options now:
- we only support HEX or RGB and convert between them to a specific format (defaults to HEX I think). It’s easy to convert between these two formats to this could happen within the color extension package.
- we support any color input (like HSL, LAB, ...) and convert it to a specific format. This requires an external library.
- we prevent any color conversion (so that hex colors are not converted to RGB for example). So everything is up to the user. This may require the user to convert colors somehow before/after.
What do you think?
+1 for 1
we only support HEX or RGB and convert between them to a specific format (defaults to HEX I think). It’s easy to convert between these two formats to this could happen within the color extension package.
+1 for 1. same as @hanspagel , also would love to contribute
What about cases where alpha channel is set (rgba() and #rrggbbaa)? Chrome seems to convert #rrggbbaa to rgba():
const div = document.createElement('div');
div.style.color = '#958DF100';
console.log(div.style.color);
// rgba(149, 141, 241, 0)
I also agree with the 1 option – it would cover most use cases out of the box and if the user wants to use a different color space, they would simply need to convert color to RGB or HEX.
yeah with HEX/RGB I meant HEX/RGB/RGBA :)
lets’s do option 1 then!
Seems like that not every value will be converted by the browser :(
const div = document.createElement('div');
div.style.color = '#958DF100';
console.log(div.style.color);
// rgba(149, 141, 241, 0)
const div = document.createElement('div');
div.style.color = 'red';
console.log(div.style.color);
// red
So option 1 is not enough I think.
Ok some more thoughts.
color2k seems like a great small library and good fit for our needs.
But converting always to Hex will cause other issues.
isActivewill fail for other color formats than Hex Should we add an extension optionconvertToHexto opt-out?
editor.commands.setColor('rgb(0,0,0)')
editor.isActive('textStyle', { color: 'rgb(0,0,0)' }) // false -> would expect this returns true
editor.isActive('textStyle', { color: '#000000' }) // true
- what about the highlight extension?
There is a
multicoloroption which allows to use any color. Should we convert them to Hex too?
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Issue still relevant
Please reopen this, this issue is still relevant
@philippkuehn Sorry for tagging :/ Please reopen this issue, I'm currently struggling with this
edit: My workaround for now
function convertRgbColorsToHex(string) {
return string.replace(/rgb\(\d+,\s*\d+,\s*\d+\)/g,
(rgbString) => "#" + rgbString
.match(/\b(\d+)\b/g)
.map((digit) =>
parseInt(digit).toString(16).padStart(2, "0").toUpperCase()
)
.join("")
)
}
Currently having the same issue, @thieleju kind of fixes it in converting it but still seeing the error happening.
Same issue here.
Same issue here.
I put it back into our 2.0.0 Release Milestone.
I just ran into this same issue. This issue also occurs when you are setting content on update for the editor. all of your styles will get parsed into rgb for hex styles. Hope the next update fixes this,
https://github.com/ueberdosis/tiptap/issues/4167
Same issue here.
Made an extension to it, also added support for backgroundColor, hope this will help:
import Color from '@tiptap/extension-color'
declare module '@tiptap/core' {
interface Commands<ReturnType> {
backgroundColor: {
setBgColor: (backgroundColor: string) => ReturnType,
unsetBgColor: () => ReturnType,
}
}
}
// rgbStyleToHex
// from https://stackoverflow.com/a/3627747/5433572
// and https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
function rgbStyleToHex(color: string | null): string | null {
if (!color || color.indexOf("rgb") < 0) {
return color
}
if (color.charAt(0) == "#") {
return color
}
const nums = /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/i.exec(color),
r = parseInt(nums![2], 10).toString(16),
g = parseInt(nums![3], 10).toString(16),
b = parseInt(nums![4], 10).toString(16)
return ("#"+ ((r.length == 1 ? "0"+ r : r) + (g.length == 1 ? "0"+ g : g) + (b.length == 1 ? "0"+ b : b))).toUpperCase()
}
export const MyColor = Color.extend({
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
color: {
default: null,
parseHTML: element => element.style.color?.replace(/['"]+/g, ''),
renderHTML: attributes => {
if (!attributes.color) {
return {}
}
return {
style: `color: ${rgbStyleToHex(attributes.color)}`,
}
},
},
backgroundColor: {
default: null,
parseHTML: element => element.style.backgroundColor?.replace(/['"]+/g, ''),
renderHTML: attributes => {
if (!attributes.backgroundColor) {
return {}
}
return {
style: `background-color: ${rgbStyleToHex(attributes.backgroundColor)}`,
}
},
},
},
},
]
},
addCommands() {
return {
...this.parent?.(),
setBgColor: backgroundColor => ({ chain }) => {
return chain().setMark('textStyle', { backgroundColor }).run()
},
unsetBgColor: () => ({ chain }) => {
return chain().setMark('textStyle', { backgroundColor: null }).removeEmptyTextStyle().run()
},
}
},
})
Made an extension to it, also added support for backgroundColor, hope this will help:
import Color from '@tiptap/extension-color' declare module '@tiptap/core' { interface Commands<ReturnType> { backgroundColor: { setBgColor: (backgroundColor: string) => ReturnType, unsetBgColor: () => ReturnType, } } } // rgbStyleToHex // from https://stackoverflow.com/a/3627747/5433572 // and https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb function rgbStyleToHex(color: string | null): string | null { if (!color || color.indexOf("rgb") < 0) { return color } if (color.charAt(0) == "#") { return color } const nums = /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/i.exec(color), r = parseInt(nums![2], 10).toString(16), g = parseInt(nums![3], 10).toString(16), b = parseInt(nums![4], 10).toString(16) return ("#"+ ((r.length == 1 ? "0"+ r : r) + (g.length == 1 ? "0"+ g : g) + (b.length == 1 ? "0"+ b : b))).toUpperCase() } export const MyColor = Color.extend({ addGlobalAttributes() { return [ { types: this.options.types, attributes: { color: { default: null, parseHTML: element => element.style.color?.replace(/['"]+/g, ''), renderHTML: attributes => { if (!attributes.color) { return {} } return { style: `color: ${rgbStyleToHex(attributes.color)}`, } }, }, backgroundColor: { default: null, parseHTML: element => element.style.backgroundColor?.replace(/['"]+/g, ''), renderHTML: attributes => { if (!attributes.backgroundColor) { return {} } return { style: `background-color: ${rgbStyleToHex(attributes.backgroundColor)}`, } }, }, }, }, ] }, addCommands() { return { ...this.parent?.(), setBgColor: backgroundColor => ({ chain }) => { return chain().setMark('textStyle', { backgroundColor }).run() }, unsetBgColor: () => ({ chain }) => { return chain().setMark('textStyle', { backgroundColor: null }).removeEmptyTextStyle().run() }, } }, })
In parseHTML you must do this to make it work:
// for color:
rgbStyleToHex(element.style.color?.replace(/['"]+/g, ''))
// for backgroundColor
rgbStyleToHex(element.style.backgroundColor?.replace(/['"]+/g, ''))
That works, thanks!
So we need to parse it to Hex, s***t!
I'd be happy to take a PR for this to be included as part of the color extension. Should probably warn on any other color values set
I just checked it, first it gets the color in rgba, and later it gets the color in hex, why?
I just upgraded to v3 and this issue is now inside of TextStyleKit - I had to remove the previous color extension per the upgrade guide. The code however just throws warnings, and my color selection (and the entire editor) otherwise works fine, so for now I am just ignoring it. I am using the default browser input (i.e. <input type="color" ...> and have only tested in Brave Browser)
I just upgraded to v3 and this issue is now inside of
TextStyleKit- I had to remove the previous color extension per the upgrade guide. The code however just throws warnings, and my color selection (and the entire editor) otherwise works fine, so for now I am just ignoring it. I am using the default browser input (i.e.<input type="color" ...>and have only tested in Brave Browser)
But the issue where fixed, right?
I would be good to open issues with the warnings.
@OliverGrimsley what warnings are thrown?
@OliverGrimsley what warnings are thrown?
The console warning:
The specified value "" does not conform to the required format. The format is "#rrggbb" where rr, gg, bb are two-digit hexadecimal numbers.
This is a Vue App, using Vite (fully current version), and my package json has:
"@tiptap/core": "^3.0.1",
"@tiptap/extension-bullet-list": "^3.0.1",
"@tiptap/extension-color": "^3.0.1",
"@tiptap/extension-highlight": "^3.0.1",
"@tiptap/extension-link": "^3.0.1",
"@tiptap/extension-list-item": "^3.0.1",
"@tiptap/extension-text-align": "^3.0.1",
"@tiptap/extension-text-style": "^3.0.1",
"@tiptap/pm": "^3.0.1",
"@tiptap/starter-kit": "^3.0.1",
"@tiptap/vue-3": "^3.0.1",
The actual code where the button is located:
<input
class="rounded-md border border-slate-400 bg-slate-300 h-6 w-6"
type="color"
@input="editor.chain().focus().setColor($event.target.value).run()"
:value="editor.getAttributes('textStyle').color"
/>
<button class="rounded-sm border border-slate-400 bg-green-300 dark:bg-slate-500 h-6 px-1" @click="editor.chain().focus().unsetColor().run()">
Unset color
</button>
The editor works just fine, despite the warnings