Open nicolo-ribaudo opened 2 weeks ago
I agree that this is a significant ergonomic problem with the highlight registry. Of the suggestions (thanks for the proposals!) the first seems best in the sense that it is clearly possible to implement efficiently.
The other proposals all have significant downside. Some still require the app to manage the ranges associated with a highlight, in which case the first proposal addresses the issue. Others complicate the design of the Highlight or HighlightRegistry object but still leave the app having to control the logic mapping highlight names to ranges.
For what is worth, the for project that I was working on when I opened this issue I ended up writing my own LocalHighlightRegistry
that abstracts away the synchronization across different usages: https://jsr.io/@nic/local-highlight-registry.
It's working well for me, I encourage you and others to try it :)
Example use case: I have a contenteditable
div
that marks as::highlight(has-x)
all the words that containx
.The simplest way of doing it is:
This works perfectly as long as I only call this function on a single element.
One day I decide that actually, I need this behavior on two different elements on my page, so I try doing this:
Except that this doesn't work at all: the highlighting logic over
myElement1
andmyElement2
fight over control of thehas-x
entry in the globalCSS.highlights
registry, and delete each other's highlights as they get updated.To make it work, I need to instead update the logic in
highlightWords
to never overwrite an existing entry in theCSS.highlights
map. Instead, it must check through all the ranges in the entry and only remove/add its own, and then if the map ends up being empty it can remove it.The global registry introduces a synchronization point between different parts of the page that is easier to get wrong than it's to get right, and if you are a library author (for example, publishing the logic above in a custom element) you won't notice unless you explicitly test multiple instances on the same page.
There are multiple ways that the API can be changed to make it difficult to do the wrong thing.
Do not allow reading
Highlight
objects from the global registry, and do not allow removingHighlight
objects that you do not control:.get()
is removedCSS.highlights.has(name, highlight)
instead ofCSS.highlights.has(name)
.set(name, highlight)
throws if there is already an entry calledname
.delete(name, highlight)
instead of.delete(name)
This keeps the synchronization complexity, but forces developers to think about it.
Allow registering multiple
Highlights
with the same name:Similar to above, except that instead of it makes conflicts work well together instead of overwriting each other
.get()
is removed, or it returns an array ofHighlight
objecsCSS.highlights.has(name, highlight)
instead ofCSS.highlights.has(name)
.set(name, highlight)
adds a new highlight namedname
, without removing any existing highlight with the same name..delete(name, highlight)
instead of.delete(name)
Allow using local registries
Allow the function above to create its own
highlights
registry by doing something likeconst highlights = new HighlightsRegistry(document)
. Different registries can declare the same name without conflicting with each other. The example above would becomeGet rid of the registry
Highlight
objects themselves could be automatically "registered". You'd have to create them withand then you can "unregister" them with
highlight.removeAllRanges()
.