Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multi range selection #41

Open
rniwa opened this issue Jan 13, 2015 · 14 comments
Open

Support multi range selection #41

rniwa opened this issue Jan 13, 2015 · 14 comments
Labels

Comments

@rniwa
Copy link
Contributor

rniwa commented Jan 13, 2015

See https://lists.w3.org/Archives/Public/public-webapps/2015JanMar/0098.html

@ayg
Copy link
Contributor

ayg commented Jan 17, 2015

Copied from my post in the thread:

I think thought should go into an API that supports non-contiguous selections without
making authors try to handle the non-contiguous case specially,
because they won't. Exposing a list of selected nodes/parts of
CharacterData nodes is a possibility that has occurred to me -- like
returning a list of SelectedNodes, where SelectedNode has .node,
.start, and .end properties, and .start and .end are null unless it's
a partially-selected CharacterData node, and no node is in the list if
it has an ancestor in a list. So "fo[o<b>bar<i>baz</i></b>]quuz"
would expose [{node: "foo", start: 2, end: 3}, {node: <b>, start:
null, end: null}, {node: "quuz", start: 0, end: 0}] as the selected
nodes. Then authors would use it by iterating over the selected
nodes, and non-contiguous selections would be handled automatically.
I once thought over some use-cases and concluded that a lot of them
would Just Work for non-contiguous selections this way -- although
doubtless some cases would still break.

(Obvious disadvantages disadvantages of this approach include a)
authors will still continue using the old API, and b) calculating the
list might be somewhat expensive. (a) might be mitigated by the fact
that it's easier to use for some applications, particularly
editing-related ones -- it saves you from having to walk through the
range yourself.)

@rniwa rniwa added the NewAPI label Aug 18, 2016
@ayg
Copy link
Contributor

ayg commented Mar 28, 2017

@ehsan @masayuki-nakano @MatsPalmgren

There was a discussion about this in December on Mozilla's dev-platform list, so I'm CCing some of the involved parties to alert them to the API idea I outlined in the previous comment. Related: https://bugzilla.mozilla.org/show_bug.cgi?id=1323681

@MatsPalmgren
Copy link

An enumeration of text nodes seems a bit awkward to work with for use cases that want to do something to elements in the selection, but perhaps it works for most cases. What worries me with returning an array though is the issue of DOM mutations, let's say the user made this (multi-range) selection:
"[foo] bar [baz]" (a single text node)
Your API would return: [{node: "foo...", start: 0, end: 3}, {node: "foo...", start: 8, end: 11}]
Let's say I want to make the selected text bold by wrapping it in <b> elements, so I iterate and wrap "foo" ... but this makes the second SelectedNode invalid!
(Ranges are so much nicer in this situation since they update their offsets in response to mutations.)

A callback approach seems better in that respect, something like this perhaps:
selection.forEachNode(function (node,start,end) {})
For my multi-range example above, this would result in two callbacks: with "foo...",0,3 and " bar...",5,8 and it would work correctly.

Perhaps we could also support a selector to make it easier to work with selected elements?
selection.forEachNode(function (node,start,end) {}, "b")
(like querySelectorAll restricted to selected nodes)

If we invent some selector for text nodes we would get something like your proposal, except the caller doesn't have to deal with mutations (in most cases):
selection.forEachNode(function (node,start,end) {}, "::text")

There might be better alternatives like ES6 generators/iterables or something. Like, for ([node,start,end] in selection.querySelectorAll("b")) ... (assuming that this also let's us control each returned result as we go so we can deal with mutations).

I think the primary requirements for a new API should be (in no particular order):

  1. easy to upgrade existing code
  2. easy to use
  3. robust even when DOM mutations occur
  4. hide multi-range selection behind the scene so web developers don't have to know about them
  5. offer more features than the existing API (and be extensible for more in the future)

I think the last point is important to persuade web developers to use the new API instead of what they are currently doing, which is crucial for solving this problem.

@rniwa
Copy link
Contributor Author

rniwa commented Mar 28, 2017

Ranges are so much nicer in this situation since they update their offsets in response to mutations
3. robust even when DOM mutations occur

The problem is that it's impossible to create an API that adopts to DOM mutations as expected expect the simplest of the simplest of the case.

For example, what if the script removed the entire text node from "[foo] bar [baz]", what should happen? For starters, for "foo" to be wrapped in a b, one has to split the text node. Range's boundary points don't (as currently spec'ed nor can we change their behavior due to backwards compatibilities) update themselves to refer to "foo" and "baz" after the split.

@MatsPalmgren
Copy link

Hmm, I don't understand why you say this is impossible or not specified.

It's been implemented in Gecko for decades. Here's a simple demo:
https://people-mozilla.org/~mpalmgren/tests/splitText1.html

It is very well specified how this should work:
https://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Mutation

@rniwa
Copy link
Contributor Author

rniwa commented Mar 28, 2017

It is very well specified how this should work

Yes, it's very well specified. The problem is that that specified behavior doesn't work as (author) expect in many cases when elements are moved around for editing purposes.

Also, when one of the ranges are modified. For example, let's say we have multiple ranges like: <b><i>[foo]</i>ba[r</b>baz]. If we wanted to unbold this multi-range selection, then we must first split the outer b and get to <b><i>[foo]</i></b><b>ba[r</b>baz]. Ordinary ranges aren't going to be adjusted like that. Because the very first step involves detaching the first half or the second half of b, and that would immediately moves both ranges to before b as in: []<b>ba[r</b>baz] or <b><i>[foo]</i></b>[baz] respectively. Inserting back the content would result in: []<b><i>foo</i></b><b>ba[r</b>baz] or <b><i>[foo]</i></b><b>bar</b>[baz] respectively. Neither results is satisfactory for handling the second range.

@MatsPalmgren
Copy link

Right, I'm not claiming it preserves the selection for every DOM mutation you throw at it. I'm just saying it's implemented and well defined in a spec.

I think your API hands over the complexity of mutations to the web developer and I don't think this is an edge case we can ignore - if they do mutations then the start,end offsets in the array are going to be wrong in many cases. I tend to think the UA is better positioned to deal with that. In my proposal, if a web developer wants to handle it for some reason they can just iterate the nodes non-destructively and save an array as in your proposal and then work from that.

@ayg
Copy link
Contributor

ayg commented Mar 29, 2017

@MatsPalmgren Sorry if I was unclear. I meant the list should contain 1) nodes, and 2) partially-selected CharacterNodes. I would also want the list to be live, as Ranges currently are, not a static array. A method that takes a callback would also work, but a list-like structure would be more flexible if we supported all array methods (like forEach but also filter, etc.). Then you don't need to have a special-cased selector argument, you can do selectedNodes.filter(n => n.matches ? n.matches("...") : false).forEach(...), and any other filtering or other operations you like.

@rniwa This isn't a problem with the proposed API, it's orthogonal. If authors want to implement an editor, more APIs will be needed, such as to split up elements while preserving the selection properly.

@MatsPalmgren
Copy link

I would also want the list to be live, as Ranges currently are, not a static array ...

@ayg OK, then I misunderstood your proposal, I thought you meant a static array.
A live array that gets updated in response to DOM mutations similar to Ranges in a Selection seems fine to me. But, does the DOM actually have a concept of "live array" currently? if not, is it implementable?

@ayg
Copy link
Contributor

ayg commented Apr 18, 2017

NodeLists are live, so I'd assume this should be implementable as well. In practice it could be implemented as an iterator that secretly looks at the underlying Ranges, I would think.

@yoichio
Copy link

yoichio commented Oct 26, 2017

As Google Chrome, we are interested in supporting multiple range.
I know there is much complexity especially around DOM mutation, Range reaction and editing, at least
representing user selection on BIDI or not normal flow layout is good feature for web author.

@yoichio
Copy link

yoichio commented Nov 9, 2017

I proposed new multiple range API at TPAC(discussion log).
That is very rough idea and no implementation yet.

@ebraminio
Copy link

Somehow related, just opened these to see what happens

https://bugs.webkit.org/show_bug.cgi?id=186465
https://bugs.chromium.org/p/chromium/issues/detail?id=851279

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants