w3c / input-events

Input Events
https://w3c.github.io/input-events/
Other
23 stars 16 forks source link

Introduce inputType 'deleteCompositionText' and 'insertFromComposition', fired right before 'compositionend' #34

Open chong-z opened 7 years ago

chong-z commented 7 years ago

Original Discussion https://github.com/w3c/editing/issues/118#issuecomment-209646801 We want 'deleteComposedCharacter' during composition because of it's special non-cancelable attribute.

Issues

  1. 'insertText' during composition is also non-cancelable, do we want 'insertCompositionText'?
  2. 'deleteComposedCharacter' is not so helpful as Chrome (and maybe WebKit) always replace the entire composition text

Proposal

  1. Text replacement should only be split into deletion and insertion at 1. the start and 2. the end of the composition
    • During composition both deletion and insertion are non-cancelable, so there is no reason to split
  2. Have 4 composition related events
    1. Single or none 'deleteByComposition', fired before 'compositionstart', cancelable
    2. Multiple 'insertCompositionText', fired during composition, non-cancelable
    3. Single 'deleteCompositionText', fired before 'compositionend', non-cancelable
    4. Single or none 'insertFromComposition', fired between iii. and 'compositionend', cancelable

Default Action

InputType Default preventDefault()
'deleteByComposition' Delete original text Similar to collapse selection forward and start composition, but next 'insertCompositionText' will pre-insert selected text in certain cases
'insertCompositionText' Insert/replace marked text (with marked text) N/A
'deleteCompositionText' Clear marked text N/A
'insertFromComposition' Insert final confirmed text Only cancels text insertion and won't affect IME (IME terminates as usual)

Example Order

(1. beforeinput - 'deleteByComposition')
(2. input - 'deleteByComposition')
3. 'compositionstart'

4. beforeinput - 'insertCompositionText'
5. 'compositionupdate'
6. input - 'insertCompositionText'
7. beforeinput - 'insertCompositionText'
8. 'compositionupdate'
9. input - 'insertCompositionText'
(...)

10. beforeinput - 'deleteCompositionText'
11. 'compositionupdate'
12. input - 'deleteCompositionText'

(13. beforeinput - 'insertFromComposition')
(14. input - 'insertFromComposition')
15. 'compositionend'

Example of Recomposition on Android

# User Action Event Type inputType DOM event.data
1 Start 'Wo'
2 Tap 'Wo' to start composition 'beforeinput' 'deleteByComposition' 'Wo'
3 'input' 'deleteByComposition' ''
4 'compositionstart' '' 'Wo'
5 'beforeinput' 'insertCompositionText' '' 'Wo'
6 'compositionupdate' '' 'Wo'
7 'input' 'insertCompositionText' Underlined 'Wo'
8 Press key 'r' 'beforeinput' 'insertCompositionText' Underlined 'Wo' 'Wor'
9 'compositionupdate' Underlined 'Wor' 'Wor'
10 'input' 'insertCompositionText' Underlined 'Wor'
11 Tap suggested word 'Work' to commit 'beforeinput' 'deleteCompositionText' Underlined 'Wor'
12 'compositionupdate' Underlined 'Wor' ''
13 'input' 'deleteCompositionText' ''
14 'beforeinput' 'insertFromComposition' '' 'Work'
15 'input' 'insertFromComposition' 'Work'
16 'compositionend' 'Work' 'Work'
chong-z commented 7 years ago

RESOLUTION: Yes we can do the issues 33/34 proposal for cancelable at each end of IME. https://www.w3.org/2016/09/22-webapps-minutes.html#resolution02

johanneswilm commented 7 years ago

@choniong I tried to add a non-normative note in humanly readable language about how the beforeinput events should relate to IME composition [1]. This is meant largely for JavaScript developers who try to built their apps on top of the spec. Some terms still need to be linked to the p[roper definitions, but let me know if this is more or less correct.

[1] https://w3c.github.io/input-events/#h-note4

chong-z commented 7 years ago

Thanks for the explainer, that's really helpful!

Just a small concern about

4.. ...or a part of it does while another part of the composition string continues to be composed in the IME...

This feels confusing to me, as there is actually no way for IME to remove/commit part of the composition. What Japanese IME doing is just commit the original composition and start a new one (although visually looks weird, it actually makes sense from JS's side), and JS shouldn't need to be aware of it as long as they have the implementation for normal IME.

If we really want to mention the Japanese IME case, would it be better to put it into a separate note, and have the current note only for atomic actions?

johanneswilm commented 7 years ago

@choniong I see. yes that can be confusing. I agree that it would be better to have two different notes. But also I think I misunderstood part of this for these Japanese IMEs:

So in the case of this Japanese IME, when this happens, how many beforeinput events do we get whent his happens? Do we get

  1. deleteCompositionText - removes the entire text.
  2. inputFromComposition - inserts the entire text.
  3. deleteByComposition - removes the part of the text that is to be put into the next composition
  4. insertCompositionText - inserts the part of the text that is to be edited in the IME

?

If this is the case, I think this can be problematic if the JS handles step 2 and inserts the string in a way the browser doesn't expect, because that will mean it won't know what it can delete in step 3. Of course, the JS could simply handle both 2 and 3, but the JS doesn't know which part of the string will be moved back into the DOM.

That's why I thought that it would only move that part into the DOM in step 2 that is finished and can be committed.

Is there any way we can expose which part of the string is done and can be committed permanently in step 2?

chong-z commented 7 years ago

So during a single keydown/up, there will be 1 compositionend and 1 new compositionstart.

  1. deleteCompositionText - One, for the first composition
  2. insertFromComposition - One, for the first composition
  3. deleteByComposition - None
  4. insertCompositionText - One, for the second composition

Here is the expected event order for pressing 'a' when the marked text is 'あああああああああああああ' (x13), the result is 2 committed 'あ' and 12 marked ''.

Event InputType/data DOM Notes
keydown 'a' 'あああああああああああああ' x13 This key will cause 'partial commit'
beforeinput 'deleteCompositionText' 'あああああああああああああ' About to commit composition 1, clearing marked text
compositionupdate '' 'あああああああああああああ'
input 'deleteCompositionText' ''
-Important- beforeinput 'insertFromComposition', 'ああ' '' 'partial commit' first 2 characters, no 'compositionupdate'
input 'insertFromComposition' 'ああ'
compositionend 'ああ' 'ああ' 'Composition 1, commit 2 characters'
--Just-- --A-- --hr-- ----
compositionstart '' 'ああ' 'Start composition 2'
beforeinput 'insertCompositionText', 'ああああああああああああ' 'ああ' Re-insert leftover text
compositionupdate 'ああああああああああああ' 'ああ'
input 'insertCompositionText' 'ああああああああああああああ' x14 The rear 12 characters are composition
keyup 'a' 'ああああああああああああああ' x14 All of this happens in one keydown/up
johanneswilm commented 7 years ago

@choniong Ah sorry. Then I had understood you correctly the first time and it's all not a problem. Then it's all a matter of turning 1 note into 2 notes. I will make sure to do that. I have tried to avoid such words as "commit" or say they will become part of the "permanent DOM" as that may invoke wrong connotations. But now I am thinking maybe we need to use the term "commit" and then add a description for it.

The point of this exercise is of course to make IME handling so obvious and easy to the JS readers of the spec that they all will do it. For that purpose: could I include your above example as well?

chong-z commented 7 years ago

Yep sure.

Just want to conclude, my point is: JS doesn't need to have any knowledge about Japanese IME, all they need to do is to handle those 4 inputTypes, and there is no special cases.

johanneswilm commented 7 years ago

Just want to conclude, my point is: JS doesn't need to have any knowledge about Japanese IME, all they need to do is to handle those 4 inputTypes, and there is no special cases.

Yes, I get it now. The thing I wasn't sure about was whether it would reinsert the entire composition string and then delete just the part that needs to go into the next composition. That's what I feared after I read

What Japanese IME doing is just commit the original composition and start a new one

But with your example you made it clear that only the part that is final goes into the first 'insertFromComposition'. That way it's all quite easy for JS. In many cases, JS developers only need to listen to 'deleteByComposition' and 'insertFromComposition', handling those themselves and recording them in their undo stack.

johanneswilm commented 7 years ago

Reopened because the event order should probably be added as normative text to a spec. For example tge new event order spec, @garykac?

And the example could be added to the input events spec.

johanneswilm commented 7 years ago

@choniong What do you think should happen in these cases:

  1. JS doesn't preventDefault beforeinput - 'deleteByComposition' but does make changes to the DOM that possibly interrupt the range which covers the initial composition string?
  2. JS handles beforeinput - 'deleteByComposition' and does some changes to the selection and the DOM which mean that the selection is not collapsed or there is no selection at the end of the JS function that handles the event?
  3. Same as 2 but for beforeinput - 'insertFromComposition' in the case where only part of the string is submitted.

All these are situations which JS may mess up and I think it's totally fine to say that JS is responsible for leaving a collapsed selection for or else the site simple doesn't work as expected. But I'm a bit afraid that if for example Chrome and Firefox coincidentally end up implementing this in a way where removing the selection in the code that handles 'deleteByComposition' leads to the IME process being cancelled, JS developers will discover that and start depending on this behavior.

chong-z commented 7 years ago

That's some difficult situations, and I'm afraid it will cause more potential issues by splitting the first text replacing into 'deleteByComposition' and 'insertCompositionText'.

Do you think it would be better to make 'deleteByComposition' as a flag and only do real mutation in the next event? e.g.

johanneswilm commented 7 years ago

Do you think it would be better to make 'deleteByComposition' as a flag and only do real mutation in the next event?

I would prefer to keep things as they are now if at all possible, as it's quite easy to understand. JS editor developer should be perfectly capable of ensuring that they leave a collapsed selection and to remove all that is is covered by the target ranges. But we should still spec what happens in those situations. One simple solution would be to say that the composition is canceled automatically if there is no collapsed selection at the end of those JS functions. But that would break a bit with the principle that canceling beforeinput events only prevents DOM changes.

Or are there other reasons why you would prefer to unite the two dom changes? I don't think it would be a huge issue either, as JS should still be able to do everything in more or less the same way. The only "odd" thing would be that the second beforeinput event will be called before any of the DOM has changed, right?