@breakside/jskit 2022.25.0 → 2022.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/Frameworks/DOM.jsframework/Info.json +2 -2
  2. package/Frameworks/DOM.jsframework/io.breakside.JSKit.DOM-bundle.js +2 -2
  3. package/Frameworks/FontKit.jsframework/Info.json +2 -2
  4. package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
  5. package/Frameworks/Foundation.jsframework/Info.json +2 -2
  6. package/Frameworks/Foundation.jsframework/JS/JSURL.js +3 -0
  7. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  8. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  9. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  10. package/Info.json +2 -2
  11. package/Node/io.breakside.jskit-bundle.js +2 -2
  12. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  13. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  14. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  15. package/Root/Frameworks/AuthKit/OAService.js +8 -0
  16. package/Root/Frameworks/AuthKit/Services.yaml +2 -0
  17. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  18. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  19. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  20. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  21. package/Root/Frameworks/DOM/Info.yaml +1 -1
  22. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  23. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  24. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  25. package/Root/Frameworks/Foundation/JSURL.js +3 -0
  26. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  27. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  28. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  29. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  30. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  31. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  32. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  33. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  34. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  35. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  36. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  37. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  38. package/Root/Frameworks/UIKit/UICursor.js +19 -4
  39. package/Root/Frameworks/UIKit/UIHTMLApplication.js +52 -14
  40. package/Root/Frameworks/UIKit/UIHTMLDisplayServerCanvasContext.js +2 -48
  41. package/Root/Frameworks/UIKit/UIHTMLDisplayServerContext.js +0 -6
  42. package/Root/Frameworks/UIKit/UIHTMLDisplayServerSVGContext.js +6 -77
  43. package/Root/Frameworks/UIKit/UIHTMLTextField.js +9 -3
  44. package/Root/Frameworks/UIKit/UIHTMLTextTypesetter.js +2 -29
  45. package/Root/Frameworks/UIKit/UIHTMLWindowServer.js +6 -79
  46. package/Root/Frameworks/UIKit/UIKit.js +1 -0
  47. package/Root/Frameworks/UIKit/UILabel.js +74 -1
  48. package/Root/Frameworks/UIKit/UIMouseTrackingArea.js +55 -0
  49. package/Root/Frameworks/UIKit/UIState.js +25 -0
  50. package/Root/Frameworks/UIKit/UITextEditor.js +277 -76
  51. package/Root/Frameworks/UIKit/UITextEditorDelegate.js +3 -1
  52. package/Root/Frameworks/UIKit/UITextLayer.js +1 -1
  53. package/Root/Frameworks/UIKit/UIView.js +118 -46
  54. package/Root/Frameworks/UIKit/UIViewController.js +6 -0
  55. package/Root/Frameworks/UIKit/UIWindow.js +89 -0
  56. package/Root/Frameworks/UIKit/UIWindowServer.js +20 -79
  57. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  58. package/package.json +1 -1
@@ -145,17 +145,22 @@ JSClass("UITextEditor", JSObject, {
145
145
  }else{
146
146
  this._handledSelectOnMouseDown = true;
147
147
  var range = null;
148
+ var atomicRange;
148
149
  var affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
149
150
  var selection = this._createSelectionAtLocation(location);
150
- var index = selection.range.location - (selection.affinity == UITextInput.SelectionAffinity.afterPreviousCharacter ? 1 : 0);
151
- if (event.clickCount === 2){
152
- range = this.textLayoutManager.textStorage.string.rangeForWordAtIndex(index);
153
- affinity = selection.affinity;
154
- }else if (event.clickCount === 3){
155
- range = this.textLayoutManager.textStorage.string.rangeForLineAtIndex(index);
156
- }
157
- if (range !== null){
158
- selection = this._createSelection(range, UITextInput.SelectionInsertionPoint.end, affinity);
151
+ if (selection.range.length === 0){
152
+ var index = selection.range.location - (selection.affinity == UITextInput.SelectionAffinity.afterPreviousCharacter ? 1 : 0);
153
+ if (event.clickCount === 2){
154
+ range = this.textLayoutManager.textStorage.string.rangeForWordAtIndex(index);
155
+ affinity = selection.affinity;
156
+ }else if (event.clickCount === 3){
157
+ range = this.textLayoutManager.textStorage.string.rangeForLineAtIndex(index);
158
+ }
159
+ if (range !== null){
160
+ selection = this._createSelection(range, UITextInput.SelectionInsertionPoint.end, affinity);
161
+ this._insertSelection(selection);
162
+ }
163
+ }else{
159
164
  this._insertSelection(selection);
160
165
  }
161
166
  this._cursorOn();
@@ -172,12 +177,12 @@ JSClass("UITextEditor", JSObject, {
172
177
  if (newSelection.range.location < draggingSelection.range.end){
173
178
  draggingSelection.range = JSRange(newSelection.range.location, draggingSelection.range.end - newSelection.range.location);
174
179
  }else{
175
- draggingSelection.range = JSRange(draggingSelection.range.end, newSelection.range.location - draggingSelection.range.end);
180
+ draggingSelection.range = JSRange(draggingSelection.range.end, newSelection.range.end - draggingSelection.range.end);
176
181
  draggingSelection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
177
182
  }
178
183
  }else{
179
- if (newSelection.range.location >= draggingSelection.range.location){
180
- draggingSelection.range = JSRange(draggingSelection.range.location, newSelection.range.location - draggingSelection.range.location);
184
+ if (newSelection.range.end >= draggingSelection.range.location){
185
+ draggingSelection.range = JSRange(draggingSelection.range.location, newSelection.range.end - draggingSelection.range.location);
181
186
  }else{
182
187
  draggingSelection.range = JSRange(newSelection.range.location, draggingSelection.range.location - newSelection.range.location);
183
188
  draggingSelection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
@@ -562,6 +567,13 @@ JSClass("UITextEditor", JSObject, {
562
567
 
563
568
  _createSelectionAtLocation: function(location){
564
569
  var index = this.textLayoutManager.characterIndexAtPoint(location);
570
+ var atomicRange;
571
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
572
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, index);
573
+ if (atomicRange){
574
+ return this._createSelection(atomicRange, UITextInput.SelectionInsertionPoint.end, UITextInput.SelectionAffinity.beforeCurrentCharacter);
575
+ }
576
+ }
565
577
  var rect = this.textLayoutManager.rectForCharacterAtIndex(index);
566
578
  var affinity;
567
579
  if (rect.origin.y > location.y){
@@ -722,9 +734,23 @@ JSClass("UITextEditor", JSObject, {
722
734
  if (JSAttributedString.Attribute.attachment in attributes){
723
735
  delete attributes[JSAttributedString.Attribute.attachment];
724
736
  }
737
+ if (this.delegate && this.delegate.textEditorPrepareInsertAttributes){
738
+ this.delegate.textEditorPrepareInsertAttributes(this, attributes);
739
+ }
725
740
  return attributes;
726
741
  },
727
742
 
743
+ deleteSelections: function(){
744
+ var textStorage = this.textLayoutManager.textStorage;
745
+ var selection;
746
+ var ranges = [];
747
+ for (var i = 0, l = this.selections.length; i < l; ++i){
748
+ selection = this.selections[i];
749
+ ranges.push(JSRange(selection.range));
750
+ }
751
+ this._deleteRanges(ranges);
752
+ },
753
+
728
754
  _isHandlingSelectionAdjustments: false,
729
755
 
730
756
  textStorageDidReplaceCharactersInRange: function(range, insertedLength){
@@ -811,17 +837,6 @@ JSClass("UITextEditor", JSObject, {
811
837
  this.insertText("\t");
812
838
  },
813
839
 
814
- deleteSelections: function(){
815
- var textStorage = this.textLayoutManager.textStorage;
816
- var selection;
817
- var ranges = [];
818
- for (var i = 0, l = this.selections.length; i < l; ++i){
819
- selection = this.selections[i];
820
- ranges.push(JSRange(selection.range));
821
- }
822
- this._deleteRanges(ranges);
823
- },
824
-
825
840
  deleteBackward: function(){
826
841
  if (this.hasSelectionRange()){
827
842
  this.deleteSelections();
@@ -829,14 +844,17 @@ JSClass("UITextEditor", JSObject, {
829
844
  }
830
845
  var textStorage = this.textLayoutManager.textStorage;
831
846
  var selection;
832
- var perceivedCharacterRange;
847
+ var range;
833
848
  var ranges = [];
834
849
  // we only get here if the selections all have 0 length
835
850
  for (var i = 0, l = this.selections.length; i < l; ++i){
836
851
  selection = this.selections[i];
837
852
  if (selection.range.location > 0){
838
- perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location - 1);
839
- ranges.push(perceivedCharacterRange);
853
+ range = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location - 1);
854
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
855
+ range = JSRange(this.delegate.atomicRangeForTextEditorIndex(this, range.location)) || range;
856
+ }
857
+ ranges.push(range);
840
858
  }else{
841
859
  ranges.push(JSRange(selection.range));
842
860
  }
@@ -852,11 +870,18 @@ JSClass("UITextEditor", JSObject, {
852
870
  var textStorage = this.textLayoutManager.textStorage;
853
871
  var selection;
854
872
  var indexOfPreviousWord;
873
+ var range;
855
874
  var ranges = [];
856
875
  // we only get here if the selections all have 0 length
857
876
  for (var i = 0, l = this.selections.length; i < l; ++i){
858
877
  selection = this.selections[i];
859
878
  indexOfPreviousWord = textStorage.string.indexOfWordStartBeforeIndex(selection.range.location);
879
+ if (indexOfPreviousWord < selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
880
+ range = this.delegate.atomicRangeForTextEditorIndex(this, indexOfPreviousWord);
881
+ if (range){
882
+ indexOfPreviousWord = range.location;
883
+ }
884
+ }
860
885
  ranges.push(JSRange(indexOfPreviousWord, selection.range.location - indexOfPreviousWord));
861
886
  }
862
887
  this._deleteRanges(ranges);
@@ -871,12 +896,21 @@ JSClass("UITextEditor", JSObject, {
871
896
  var ranges = [];
872
897
  var selection;
873
898
  var line;
899
+ var range;
900
+ var indexOfLineStart;
874
901
  // we only get here if the selections all have 0 length
875
902
  for (var i = 0, l = this.selections.length; i < l; ++i){
876
903
  selection = this.selections[i];
877
904
  line = this._lineForSelectionStart(selection);
878
905
  if (line !== null && selection.range.location > line.range.location){
879
- ranges.push(JSRange(line.range.location, selection.range.location - line.range.location));
906
+ indexOfLineStart = line.range.location;
907
+ if (indexOfLineStart < selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
908
+ range = this.delegate.atomicRangeForTextEditorIndex(this, indexOfLineStart);
909
+ if (range){
910
+ indexOfLineStart = range.location;
911
+ }
912
+ }
913
+ ranges.push(JSRange(indexOfLineStart, selection.range.location - indexOfLineStart));
880
914
  }else{
881
915
  ranges.push(JSRange(selection.range));
882
916
  }
@@ -902,14 +936,17 @@ JSClass("UITextEditor", JSObject, {
902
936
  }
903
937
  var textStorage = this.textLayoutManager.textStorage;
904
938
  var selection;
905
- var perceivedCharacterRange;
939
+ var range;
906
940
  var ranges = [];
907
941
  // we only get here if the selections all have 0 length
908
942
  for (var i = 0, l = this.selections.length; i < l; ++i){
909
943
  selection = this.selections[i];
910
944
  if (selection.range.location < textStorage.string.length){
911
- perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location);
912
- ranges.push(perceivedCharacterRange);
945
+ range = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location);
946
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
947
+ range = JSRange(this.delegate.atomicRangeForTextEditorIndex(this, range.location)) || range;
948
+ }
949
+ ranges.push(range);
913
950
  }else{
914
951
  ranges.push(JSRange(selection.range));
915
952
  }
@@ -925,11 +962,18 @@ JSClass("UITextEditor", JSObject, {
925
962
  var textStorage = this.textLayoutManager.textStorage;
926
963
  var selection;
927
964
  var nextWordIndex;
965
+ var range;
928
966
  var ranges = [];
929
967
  // we only get here if the selections all have 0 length
930
968
  for (var i = 0, l = this.selections.length; i < l; ++i){
931
969
  selection = this.selections[i];
932
970
  nextWordIndex = textStorage.string.indexOfWordEndAfterIndex(selection.range.location);
971
+ if (nextWordIndex > selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
972
+ range = this.delegate.atomicRangeForTextEditorIndex(this, nextWordIndex - 1);
973
+ if (range){
974
+ nextWordIndex = range.end;
975
+ }
976
+ }
933
977
  ranges.push(JSRange(selection.range.location, nextWordIndex - selection.range.location));
934
978
  }
935
979
  this._deleteRanges(ranges);
@@ -944,18 +988,26 @@ JSClass("UITextEditor", JSObject, {
944
988
  var ranges = [];
945
989
  var selection;
946
990
  var line;
991
+ var range;
992
+ var indexOfLineEnd;
947
993
  // we only get here if the selections all have 0 length
948
994
  for (var i = 0, l = this.selections.length; i < l; ++i){
949
995
  selection = this.selections[i];
950
996
  line = this._lineForSelectionStart(selection);
951
997
  if (line !== null && selection.range.location < line.range.end){
998
+ indexOfLineEnd = line.range.end;
999
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1000
+ range = this.delegate.atomicRangeForTextEditorIndex(this, indexOfLineEnd - 1);
1001
+ if (range){
1002
+ indexOfLineEnd = range.end;
1003
+ }
1004
+ }
952
1005
  // Only delete up to a line break if one exists, but don't include the line break
953
- var iterator = textStorage.string.userPerceivedCharacterIterator(line.range.end - 1);
1006
+ var iterator = textStorage.string.userPerceivedCharacterIterator(indexOfLineEnd - 1);
954
1007
  if (iterator.isMandatoryLineBreak){
955
- ranges.push(JSRange(selection.range.location, iterator.index - selection.range.location));
956
- }else{
957
- ranges.push(JSRange(selection.range.location, line.range.end - selection.range.location));
1008
+ indexOfLineEnd = iterator.index;
958
1009
  }
1010
+ ranges.push(JSRange(selection.range.location, indexOfLineEnd - selection.range.location));
959
1011
  }else{
960
1012
  ranges.push(JSRange(selection.range));
961
1013
  }
@@ -979,16 +1031,28 @@ JSClass("UITextEditor", JSObject, {
979
1031
  var textStorage = this.textLayoutManager.textStorage;
980
1032
  var selection;
981
1033
  var perceivedCharacterRange;
1034
+ var atomicRange;
982
1035
  for (var i = 0, l = this.selections.length; i < l; ++i){
983
1036
  selection = this.selections[i];
984
1037
  selection.attributes = null;
985
1038
  if (selection.range.length === 0){
986
1039
  if (selection.range.location > 0){
987
1040
  perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location - 1);
988
- selection.range = JSRange(perceivedCharacterRange.location, 0);
1041
+ atomicRange = null;
1042
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1043
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, perceivedCharacterRange.location);
1044
+ }
1045
+ if (atomicRange){
1046
+ selection.range = JSRange(atomicRange);
1047
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
1048
+ }else{
1049
+ selection.range = JSRange(perceivedCharacterRange.location, 0);
1050
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1051
+ }
989
1052
  }
990
1053
  }else{
991
1054
  selection.range = JSRange(selection.range.location, 0);
1055
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
992
1056
  }
993
1057
  }
994
1058
  this._collapseOverlappingSelections();
@@ -1000,13 +1064,23 @@ JSClass("UITextEditor", JSObject, {
1000
1064
  moveWordBackward: function(){
1001
1065
  var textStorage = this.textLayoutManager.textStorage;
1002
1066
  var selection;
1003
- var wordRange;
1004
1067
  var indexOfPreviousWord;
1068
+ var atomicRange;
1005
1069
  for (var i = 0, l = this.selections.length; i < l; ++i){
1006
1070
  selection = this.selections[i];
1007
1071
  selection.attributes = null;
1008
1072
  indexOfPreviousWord = textStorage.string.indexOfWordStartBeforeIndex(selection.range.location);
1009
- selection.range = JSRange(indexOfPreviousWord, 0);
1073
+ atomicRange = null;
1074
+ if (indexOfPreviousWord < selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1075
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, indexOfPreviousWord);
1076
+ }
1077
+ if (atomicRange){
1078
+ selection.range = JSRange(atomicRange);
1079
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
1080
+ }else{
1081
+ selection.range = JSRange(indexOfPreviousWord, 0);
1082
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1083
+ }
1010
1084
  }
1011
1085
  this._collapseOverlappingSelections();
1012
1086
  this._resetSelectionAffinity();
@@ -1018,12 +1092,23 @@ JSClass("UITextEditor", JSObject, {
1018
1092
  var textStorage = this.textLayoutManager.textStorage;
1019
1093
  var selection;
1020
1094
  var line;
1095
+ var atomicRange;
1021
1096
  for (var i = 0, l = this.selections.length; i < l; ++i){
1022
1097
  selection = this.selections[i];
1023
1098
  selection.attributes = null;
1024
1099
  line = this._lineForSelectionStart(selection);
1025
1100
  if (line !== null){
1026
- selection.range = JSRange(line.range.location, 0);
1101
+ atomicRange = null;
1102
+ if (line.range.location < selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1103
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, line.range.location);
1104
+ }
1105
+ if (atomicRange){
1106
+ selection.range = JSRange(atomicRange);
1107
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
1108
+ }else{
1109
+ selection.range = JSRange(line.range.location, 0);
1110
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1111
+ }
1027
1112
  }
1028
1113
  }
1029
1114
  this._collapseOverlappingSelections();
@@ -1042,6 +1127,7 @@ JSClass("UITextEditor", JSObject, {
1042
1127
  var cursorRect;
1043
1128
  var lineEnumerator;
1044
1129
  var cursorX;
1130
+ var atomicRange;
1045
1131
  for (var i = 0, l = this.selections.length; i < l; ++i){
1046
1132
  selection = this.selections[i];
1047
1133
  selection.attributes = null;
@@ -1055,17 +1141,27 @@ JSClass("UITextEditor", JSObject, {
1055
1141
  lineRect = lineEnumerator.rect;
1056
1142
  pointOnPreviousLine = JSPoint(cursorX, lineRect.origin.y + lineRect.size.height / 2);
1057
1143
  index = this.textLayoutManager.characterIndexAtPoint(pointOnPreviousLine);
1058
- selection.range = JSRange(index, 0);
1059
- if (selection.range.location === line.range.location && selection.range.location > 0){
1060
- var iterator = textStorage.string.userPerceivedCharacterIterator(selection.range.location - 1);
1061
- if (iterator.isMandatoryLineBreak){
1062
- selection.range = JSRange(iterator.index, 0);
1063
- selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1144
+ atomicRange = null;
1145
+ if (index < selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1146
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, index);
1147
+ }
1148
+ if (atomicRange){
1149
+ selection.range = JSRange(atomicRange);
1150
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
1151
+ selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1152
+ }else{
1153
+ selection.range = JSRange(index, 0);
1154
+ if (selection.range.location === line.range.location && selection.range.location > 0){
1155
+ var iterator = textStorage.string.userPerceivedCharacterIterator(selection.range.location - 1);
1156
+ if (iterator.isMandatoryLineBreak){
1157
+ selection.range = JSRange(iterator.index, 0);
1158
+ selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1159
+ }else{
1160
+ selection.affinity = UITextInput.SelectionAffinity.afterPreviousCharacter;
1161
+ }
1064
1162
  }else{
1065
- selection.affinity = UITextInput.SelectionAffinity.afterPreviousCharacter;
1163
+ selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1066
1164
  }
1067
- }else{
1068
- selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1069
1165
  }
1070
1166
  }else{
1071
1167
  selection.range = JSRange.Zero;
@@ -1086,13 +1182,24 @@ JSClass("UITextEditor", JSObject, {
1086
1182
  var textStorage = this.textLayoutManager.textStorage;
1087
1183
  var selection;
1088
1184
  var perceivedCharacterRange;
1185
+ var atomicRange;
1089
1186
  for (var i = 0, l = this.selections.length; i < l; ++i){
1090
1187
  selection = this.selections[i];
1091
1188
  selection.attributes = null;
1092
1189
  if (selection.range.length === 0){
1093
1190
  if (selection.range.location < textStorage.string.length){
1094
1191
  perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location);
1095
- selection.range = JSRange(perceivedCharacterRange.end, 0);
1192
+ atomicRange = null;
1193
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1194
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, perceivedCharacterRange.location);
1195
+ }
1196
+ if (atomicRange){
1197
+ selection.range = JSRange(atomicRange);
1198
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1199
+ }else{
1200
+ selection.range = JSRange(perceivedCharacterRange.end, 0);
1201
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1202
+ }
1096
1203
  }
1097
1204
  }else{
1098
1205
  selection.range = JSRange(selection.range.end, 0);
@@ -1109,11 +1216,22 @@ JSClass("UITextEditor", JSObject, {
1109
1216
  var selection;
1110
1217
  var wordRange;
1111
1218
  var nextWordIndex;
1219
+ var atomicRange;
1112
1220
  for (var i = 0, l = this.selections.length; i < l; ++i){
1113
1221
  selection = this.selections[i];
1114
1222
  selection.attributes = null;
1115
1223
  nextWordIndex = textStorage.string.indexOfWordEndAfterIndex(selection.range.end);
1116
- selection.range = JSRange(nextWordIndex, 0);
1224
+ atomicRange = null;
1225
+ if (nextWordIndex > selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1226
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, nextWordIndex - 1);
1227
+ }
1228
+ if (atomicRange){
1229
+ selection.range = JSRange(atomicRange);
1230
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1231
+ }else{
1232
+ selection.range = JSRange(nextWordIndex, 0);
1233
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1234
+ }
1117
1235
  }
1118
1236
  this._collapseOverlappingSelections();
1119
1237
  this._resetSelectionAffinity();
@@ -1125,18 +1243,31 @@ JSClass("UITextEditor", JSObject, {
1125
1243
  var textStorage = this.textLayoutManager.textStorage;
1126
1244
  var selection;
1127
1245
  var line;
1246
+ var atomicRange;
1128
1247
  for (var i = 0, l = this.selections.length; i < l; ++i){
1129
1248
  selection = this.selections[i];
1130
1249
  selection.attributes = null;
1131
1250
  line = this._lineForSelectionEnd(selection);
1132
1251
  if (line !== null && line.range.end > selection.range.location){
1133
- var iterator = textStorage.string.userPerceivedCharacterIterator(line.range.end - 1);
1134
- if (iterator.isMandatoryLineBreak){
1135
- selection.range = JSRange(iterator.index, 0);
1252
+ atomicRange = null;
1253
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1254
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, line.range.end);
1255
+ }
1256
+ if (atomicRange){
1257
+ selection.range = JSRange(atomicRange);
1258
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1136
1259
  selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1137
1260
  }else{
1138
- selection.range = JSRange(line.range.end, 0);
1139
- selection.affinity = UITextInput.SelectionAffinity.afterPreviousCharacter;
1261
+ var iterator = textStorage.string.userPerceivedCharacterIterator(line.range.end - 1);
1262
+ if (iterator.isMandatoryLineBreak){
1263
+ selection.range = JSRange(iterator.index, 0);
1264
+ selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1265
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1266
+ }else{
1267
+ selection.range = JSRange(line.range.end, 0);
1268
+ selection.affinity = UITextInput.SelectionAffinity.afterPreviousCharacter;
1269
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1270
+ }
1140
1271
  }
1141
1272
  }
1142
1273
  }
@@ -1155,6 +1286,7 @@ JSClass("UITextEditor", JSObject, {
1155
1286
  var cursorRect;
1156
1287
  var cursorX;
1157
1288
  var lineEnumerator;
1289
+ var atomicRange;
1158
1290
  for (var i = 0, l = this.selections.length; i < l; ++i){
1159
1291
  selection = this.selections[i];
1160
1292
  selection.attributes = null;
@@ -1168,17 +1300,27 @@ JSClass("UITextEditor", JSObject, {
1168
1300
  lineRect = lineEnumerator.rect;
1169
1301
  pointOnNextLine = JSPoint(cursorX, lineRect.origin.y + lineRect.size.height / 2);
1170
1302
  index = this.textLayoutManager.characterIndexAtPoint(pointOnNextLine);
1171
- selection.range = JSRange(index, 0);
1172
- if (selection.range.location === lineEnumerator.line.range.end && selection.range.location > 0){
1173
- var iterator = textStorage.string.userPerceivedCharacterIterator(selection.range.location - 1);
1174
- if (iterator.isMandatoryLineBreak){
1175
- selection.range = JSRange(iterator.index, 0);
1176
- selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1303
+ atomicRange = null;
1304
+ if (index > selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1305
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, index);
1306
+ }
1307
+ if (atomicRange){
1308
+ selection.range = JSRange(atomicRange);
1309
+ selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1310
+ selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1311
+ }else{
1312
+ selection.range = JSRange(index, 0);
1313
+ if (selection.range.location === lineEnumerator.line.range.end && selection.range.location > 0){
1314
+ var iterator = textStorage.string.userPerceivedCharacterIterator(selection.range.location - 1);
1315
+ if (iterator.isMandatoryLineBreak){
1316
+ selection.range = JSRange(iterator.index, 0);
1317
+ selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1318
+ }else{
1319
+ selection.affinity = UITextInput.SelectionAffinity.afterPreviousCharacter;
1320
+ }
1177
1321
  }else{
1178
- selection.affinity = UITextInput.SelectionAffinity.afterPreviousCharacter;
1322
+ selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1179
1323
  }
1180
- }else{
1181
- selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1182
1324
  }
1183
1325
  }else{
1184
1326
  selection.range = this._sanitizedRange(JSRange(line.range.end, 0));
@@ -1199,17 +1341,23 @@ JSClass("UITextEditor", JSObject, {
1199
1341
  moveBackwardAndModifySelection: function(){
1200
1342
  var textStorage = this.textLayoutManager.textStorage;
1201
1343
  var selection;
1202
- var perceivedCharacterRange;
1344
+ var range;
1203
1345
  for (var i = 0, l = this.selections.length; i < l; ++i){
1204
1346
  selection = this.selections[i];
1205
1347
  selection.attributes = null;
1206
1348
  if (selection.range.length > 0 && selection.insertionPoint == UITextInput.SelectionInsertionPoint.end){
1207
- perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.end - 1);
1208
- selection.range.length -= perceivedCharacterRange.length;
1349
+ range = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.end - 1);
1350
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1351
+ range = this.delegate.atomicRangeForTextEditorIndex(this, range.location) || range;
1352
+ }
1353
+ selection.range.length -= range.length;
1209
1354
  }else{
1210
1355
  if (selection.range.location > 0){
1211
- perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location - 1);
1212
- selection.range = JSRange(perceivedCharacterRange.location, perceivedCharacterRange.length + selection.range.length);
1356
+ range = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location - 1);
1357
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1358
+ range = this.delegate.atomicRangeForTextEditorIndex(this, range.location) || range;
1359
+ }
1360
+ selection.range = JSRange(range.location, range.length + selection.range.length);
1213
1361
  }
1214
1362
  selection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
1215
1363
  }
@@ -1226,6 +1374,7 @@ JSClass("UITextEditor", JSObject, {
1226
1374
  var wordRange;
1227
1375
  var indexOfPreviousWord;
1228
1376
  var end;
1377
+ var atomicRange;
1229
1378
  for (var i = 0, l = this.selections.length; i < l; ++i){
1230
1379
  selection = this.selections[i];
1231
1380
  selection.attributes = null;
@@ -1235,6 +1384,12 @@ JSClass("UITextEditor", JSObject, {
1235
1384
  end = selection.range.location;
1236
1385
  }
1237
1386
  indexOfPreviousWord = textStorage.string.indexOfWordStartBeforeIndex(selection.range.location);
1387
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1388
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, indexOfPreviousWord);
1389
+ if (atomicRange){
1390
+ indexOfPreviousWord = atomicRange.location;
1391
+ }
1392
+ }
1238
1393
  selection.range = JSRange(indexOfPreviousWord, end - indexOfPreviousWord);
1239
1394
  selection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
1240
1395
  }
@@ -1249,17 +1404,27 @@ JSClass("UITextEditor", JSObject, {
1249
1404
  var selection;
1250
1405
  var line;
1251
1406
  var end;
1407
+ var atomicRange;
1408
+ var indexOfLineStart;
1252
1409
  for (var i = 0, l = this.selections.length; i < l; ++i){
1253
1410
  selection = this.selections[i];
1254
1411
  selection.attributes = null;
1255
1412
  line = this._lineForSelectionStart(selection);
1413
+ atomicRange = null;
1256
1414
  if (line !== null){
1257
1415
  if (selection.insertionPoint == UITextInput.SelectionInsertionPoint.start){
1258
1416
  end = selection.range.end;
1259
1417
  }else{
1260
1418
  end = selection.range.location;
1261
1419
  }
1262
- selection.range = JSRange(line.range.location, end - line.range.location);
1420
+ indexOfLineStart = line.range.location;
1421
+ if (indexOfLineStart < selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1422
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, indexOfLineStart);
1423
+ if (atomicRange){
1424
+ indexOfLineStart = atomicRange.location;
1425
+ }
1426
+ }
1427
+ selection.range = JSRange(indexOfLineStart, end - indexOfLineStart);
1263
1428
  }
1264
1429
  selection.insertionPoint = UITextInput.SelectionInsertionPoint.start;
1265
1430
  }
@@ -1279,6 +1444,7 @@ JSClass("UITextEditor", JSObject, {
1279
1444
  var cursorRect;
1280
1445
  var cursorX;
1281
1446
  var lineEnumerator;
1447
+ var atomicRange;
1282
1448
  for (var i = 0, l = this.selections.length; i < l; ++i){
1283
1449
  selection = this.selections[i];
1284
1450
  selection.attributes = null;
@@ -1291,6 +1457,12 @@ JSClass("UITextEditor", JSObject, {
1291
1457
  lineRect = lineEnumerator.rect;
1292
1458
  pointOnPreviousLine = JSPoint(cursorX, lineRect.origin.y + lineRect.size.height / 2);
1293
1459
  index = this.textLayoutManager.characterIndexAtPoint(pointOnPreviousLine);
1460
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1461
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, index);
1462
+ if (atomicRange){
1463
+ index = atomicRange.location;
1464
+ }
1465
+ }
1294
1466
  if (index === line.range.location && index > 0){
1295
1467
  var iterator = textStorage.string.userPerceivedCharacterIterator(index - 1);
1296
1468
  if (iterator.isMandatoryLineBreak){
@@ -1344,17 +1516,23 @@ JSClass("UITextEditor", JSObject, {
1344
1516
  moveForwardAndModifySelection: function(){
1345
1517
  var textStorage = this.textLayoutManager.textStorage;
1346
1518
  var selection;
1347
- var perceivedCharacterRange;
1519
+ var range;
1348
1520
  for (var i = 0, l = this.selections.length; i < l; ++i){
1349
1521
  selection = this.selections[i];
1350
1522
  selection.attributes = null;
1351
1523
  if (selection.range.length > 0 && selection.insertionPoint == UITextInput.SelectionInsertionPoint.start){
1352
- perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location);
1353
- selection.range.advance(perceivedCharacterRange.length);
1524
+ range = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.location);
1525
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1526
+ range = this.delegate.atomicRangeForTextEditorIndex(this, range.location) || range;
1527
+ }
1528
+ selection.range.advance(range.length);
1354
1529
  }else{
1355
1530
  if (selection.range.end < textStorage.string.length){
1356
- perceivedCharacterRange = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.end);
1357
- selection.range = JSRange(selection.range.location, perceivedCharacterRange.length + selection.range.length);
1531
+ range = textStorage.string.rangeForUserPerceivedCharacterAtIndex(selection.range.end);
1532
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1533
+ range = this.delegate.atomicRangeForTextEditorIndex(this, range.location) || range;
1534
+ }
1535
+ selection.range = JSRange(selection.range.location, range.length + selection.range.length);
1358
1536
  }
1359
1537
  selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1360
1538
  }
@@ -1371,6 +1549,7 @@ JSClass("UITextEditor", JSObject, {
1371
1549
  var wordRange;
1372
1550
  var nextWordIndex;
1373
1551
  var start;
1552
+ var atomicRange;
1374
1553
  for (var i = 0, l = this.selections.length; i < l; ++i){
1375
1554
  selection = this.selections[i];
1376
1555
  selection.attributes = null;
@@ -1380,6 +1559,12 @@ JSClass("UITextEditor", JSObject, {
1380
1559
  start = selection.range.location;
1381
1560
  }
1382
1561
  nextWordIndex = textStorage.string.indexOfWordEndAfterIndex(selection.range.end);
1562
+ if (nextWordIndex > selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1563
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, nextWordIndex - 1);
1564
+ if (atomicRange){
1565
+ nextWordIndex = atomicRange.end;
1566
+ }
1567
+ }
1383
1568
  selection.range = JSRange(start, nextWordIndex - start);
1384
1569
  selection.insertionPoint = UITextInput.SelectionInsertionPoint.end;
1385
1570
  }
@@ -1394,6 +1579,8 @@ JSClass("UITextEditor", JSObject, {
1394
1579
  var selection;
1395
1580
  var line;
1396
1581
  var start;
1582
+ var atomicRange;
1583
+ var indexOfLineEnd;
1397
1584
  for (var i = 0, l = this.selections.length; i < l; ++i){
1398
1585
  selection = this.selections[i];
1399
1586
  selection.attributes = null;
@@ -1404,12 +1591,19 @@ JSClass("UITextEditor", JSObject, {
1404
1591
  start = selection.range.location;
1405
1592
  }
1406
1593
  if (line !== null){
1407
- var iterator = textStorage.string.userPerceivedCharacterIterator(line.range.end - 1);
1594
+ indexOfLineEnd = line.range.end;
1595
+ if (line.range.location > selection.range.location && this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1596
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, indexOfLineEnd - 1);
1597
+ if (atomicRange){
1598
+ indexOfLineEnd = atomicRange.end;
1599
+ }
1600
+ }
1601
+ var iterator = textStorage.string.userPerceivedCharacterIterator(indexOfLineEnd - 1);
1408
1602
  if (iterator.isMandatoryLineBreak){
1409
1603
  selection.range = JSRange(start, iterator.index - start);
1410
1604
  selection.affinity = UITextInput.SelectionAffinity.beforeCurrentCharacter;
1411
1605
  }else{
1412
- selection.range = JSRange(start, line.range.end - start);
1606
+ selection.range = JSRange(start, indexOfLineEnd - start);
1413
1607
  selection.affinity = UITextInput.SelectionAffinity.afterPreviousCharacter;
1414
1608
  }
1415
1609
  }
@@ -1430,6 +1624,7 @@ JSClass("UITextEditor", JSObject, {
1430
1624
  var lineRect;
1431
1625
  var cursorRect;
1432
1626
  var cursorX;
1627
+ var atomicRange;
1433
1628
  for (var i = 0, l = this.selections.length; i < l; ++i){
1434
1629
  selection = this.selections[i];
1435
1630
  selection.attributes = null;
@@ -1442,6 +1637,12 @@ JSClass("UITextEditor", JSObject, {
1442
1637
  lineRect = lineEnumerator.rect;
1443
1638
  pointOnNextLine = JSPoint(cursorX, lineRect.origin.y + lineRect.size.height / 2);
1444
1639
  index = this.textLayoutManager.characterIndexAtPoint(pointOnNextLine);
1640
+ if (this.delegate && this.delegate.atomicRangeForTextEditorIndex){
1641
+ atomicRange = this.delegate.atomicRangeForTextEditorIndex(this, index - 1);
1642
+ if (atomicRange){
1643
+ index = atomicRange.end;
1644
+ }
1645
+ }
1445
1646
  if (index === lineEnumerator.line.range.end && index > 0){
1446
1647
  var iterator = textStorage.string.userPerceivedCharacterIterator(index - 1);
1447
1648
  if (iterator.isMandatoryLineBreak){