@breakside/jskit 2026.6.0 → 2026.9.0

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 (57) 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/JSContext.js +23 -1
  7. package/Frameworks/Foundation.jsframework/JS/JSGradient.js +25 -0
  8. package/Frameworks/Foundation.jsframework/JS/JSPath.js +80 -0
  9. package/Frameworks/Foundation.jsframework/JS/JSRect.js +12 -0
  10. package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
  11. package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
  12. package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
  13. package/Info.json +2 -2
  14. package/Node/io.breakside.jskit-bundle.js +4 -4
  15. package/Resources/doc-default.css +8 -0
  16. package/Root/Frameworks/APIKit/Info.yaml +1 -1
  17. package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
  18. package/Root/Frameworks/AuthKit/Info.yaml +1 -1
  19. package/Root/Frameworks/CSSOM/Info.yaml +1 -1
  20. package/Root/Frameworks/ChartKit/Info.yaml +1 -1
  21. package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
  22. package/Root/Frameworks/DBKit/Info.yaml +1 -1
  23. package/Root/Frameworks/DOM/Info.yaml +1 -1
  24. package/Root/Frameworks/Dispatch/Info.yaml +1 -1
  25. package/Root/Frameworks/FontKit/Info.yaml +1 -1
  26. package/Root/Frameworks/Foundation/Info.yaml +1 -1
  27. package/Root/Frameworks/Foundation/JSContext.js +23 -1
  28. package/Root/Frameworks/Foundation/JSGradient.js +25 -0
  29. package/Root/Frameworks/Foundation/JSPath.js +80 -0
  30. package/Root/Frameworks/Foundation/JSRect.js +12 -0
  31. package/Root/Frameworks/ImageKit/IKHTMLCanvasBitmapContext.js +91 -13
  32. package/Root/Frameworks/ImageKit/Info.yaml +1 -1
  33. package/Root/Frameworks/MediaKit/Info.yaml +1 -1
  34. package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
  35. package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
  36. package/Root/Frameworks/PDFKit/Info.yaml +1 -1
  37. package/Root/Frameworks/QRKit/Info.yaml +1 -1
  38. package/Root/Frameworks/SearchKit/Info.yaml +1 -1
  39. package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
  40. package/Root/Frameworks/ServerKit/Info.yaml +1 -1
  41. package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
  42. package/Root/Frameworks/TestKit/Info.yaml +1 -1
  43. package/Root/Frameworks/UIKit/Info.yaml +1 -1
  44. package/Root/Frameworks/UIKit/UIApplication.js +5 -1
  45. package/Root/Frameworks/UIKit/UIHTMLApplication.js +24 -0
  46. package/Root/Frameworks/UIKit/UIHTMLDisplayServerCanvasContext.js +87 -13
  47. package/Root/Frameworks/UIKit/UIHTMLWindowServer.js +17 -3
  48. package/Root/Frameworks/UIKit/UIKit.js +2 -0
  49. package/Root/Frameworks/UIKit/UILayer.js +6 -0
  50. package/Root/Frameworks/UIKit/UIPopupWindow.js +15 -5
  51. package/Root/Frameworks/UIKit/UITextAction.js +16 -0
  52. package/Root/Frameworks/UIKit/UITextActionsMenu.js +95 -0
  53. package/Root/Frameworks/UIKit/UITextEditor.js +57 -7
  54. package/Root/Frameworks/UIKit/UITextField.js +102 -0
  55. package/Root/Frameworks/UIKit/en.lproj/Localizable.strings.yaml +4 -1
  56. package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
  57. package/package.json +1 -1
@@ -214,25 +214,69 @@ JSClass("UITextEditor", JSObject, {
214
214
  _touchBeganInfo: null,
215
215
 
216
216
  handleTouchesBeganAtLocation: function(location, touches, event){
217
- this._setSingleSelectionAtLocation(location);
217
+ var newSelection = this._createSelectionAtLocation(location);
218
+ var existingSelection = null;
219
+ for (var i = 0, l = this.selections.length; i < l; ++i){
220
+ if (this.selections[i].containsIndex(newSelection.range.location)){
221
+ existingSelection = this.selections[i];
222
+ break;
223
+ }
224
+ }
225
+ if (existingSelection === null){
226
+ this._setSingleSelectionAtLocation(location);
227
+ }
218
228
  this._touchBeganInfo = {
219
229
  timestamp: event.timestamp,
220
230
  location: location,
231
+ selection: existingSelection,
221
232
  moved: false
222
233
  };
223
234
  },
224
235
 
225
236
  handleTouchesMovedAtLocation: function(location, touches, event){
226
- this._setSingleSelectionAtLocation(location);
227
237
  if (this._touchBeganInfo !== null){
238
+ if (this._touchBeganInfo.selection === null){
239
+ this._setSingleSelectionAtLocation(location);
240
+ }
228
241
  this._touchBeganInfo.moved = this._touchBeganInfo.moved || location.distanceToPoint(this._touchBeganInfo.location) > 10;
242
+ if (this._touchBeganInfo.moved && this._touchBeganInfo.selection !== null){
243
+ var existingSelection = this._touchBeganInfo.selection;
244
+ var selection = this._createSelectionAtLocation(location);
245
+ if (selection.range.location < existingSelection.range.location){
246
+ selection.range.length = existingSelection.range.end - selection.range.location;
247
+ this._setSingleSelection(selection);
248
+ this._touchBeganInfo.selection = selection;
249
+ }else if (selection.range.location > existingSelection.range.end){
250
+ selection.range = JSRange(existingSelection.range.location, selection.range.location - existingSelection.range.location);
251
+ this._setSingleSelection(selection);
252
+ this._touchBeganInfo.selection = selection;
253
+ }else{
254
+ var dl = selection.range.location - existingSelection.range.location;
255
+ var de = existingSelection.range.end - selection.range.location;
256
+ if (dl > 0 && de > 0){
257
+ if (dl <= de){
258
+ selection.range.length = existingSelection.range.end - selection.range.location;
259
+ }else{
260
+ selection.range = JSRange(existingSelection.range.location, selection.range.location - existingSelection.range.location);
261
+ }
262
+ this._setSingleSelection(selection);
263
+ this._touchBeganInfo.selection = selection;
264
+ }
265
+ }
266
+ }
267
+ }else{
268
+ this._setSingleSelectionAtLocation(location);
229
269
  }
230
270
  },
231
271
 
232
272
  handleTouchesEndedAtLocation: function(location, touches, event){
233
- this._setSingleSelectionAtLocation(location);
234
273
  if (this._touchBeganInfo !== null){
274
+ if (this._touchBeganInfo.selection === null){
275
+ this._setSingleSelectionAtLocation(location);
276
+ }
235
277
  this._touchBeganInfo = null;
278
+ }else{
279
+ this._setSingleSelectionAtLocation(location);
236
280
  }
237
281
  },
238
282
 
@@ -241,10 +285,16 @@ JSClass("UITextEditor", JSObject, {
241
285
  if (this.selections.length === 1 && !this._touchBeganInfo.moved){
242
286
  if ((event.timestamp - this._touchBeganInfo.timestamp) > JSTimeInterval.seconds(0.5)){
243
287
  var selection = this.selections[0];
244
- var range = this.textLayoutManager.textStorage.string.rangeForWordAtIndex(this.selections[0].range.location);
245
- if (range.length > 0){
246
- var wordSelection = this._createSelection(range);
247
- this._setSingleSelection(wordSelection);
288
+ var updatedSelection;
289
+ if (this._touchBeganInfo.selection !== null){
290
+ updatedSelection = this._createSelection(JSRange(selection.range.location, 0));
291
+ this._setSingleSelection(updatedSelection);
292
+ }else{
293
+ var range = this.textLayoutManager.textStorage.string.rangeForWordAtIndex(selection.range.location);
294
+ if (range.length > 0){
295
+ updatedSelection = this._createSelection(range);
296
+ this._setSingleSelection(updatedSelection);
297
+ }
248
298
  }
249
299
  }
250
300
  }
@@ -22,6 +22,9 @@
22
22
  // #import "UIImageView.js"
23
23
  // #import "UIPasteboard.js"
24
24
  // #import "JSColor+UIKit.js"
25
+ // #import "UITextAction.js"
26
+ // #import "UIMenu.js"
27
+ // #import "UITextActionsMenu.js"
25
28
  'use strict';
26
29
 
27
30
  (function(){
@@ -794,6 +797,7 @@ JSClass("UITextField", UIControl, {
794
797
  },
795
798
 
796
799
  textEditorDidReplaceCharactersInRange: function(textEditor, range, insertedLength){
800
+ this.hideTextActionsMenu();
797
801
  if (this._isEmpty && insertedLength > 0){
798
802
  this._updateIsEmpty(false);
799
803
  }else if (!this._isEmpty && insertedLength === 0 && !this._textLayer.hasText()){
@@ -889,6 +893,7 @@ JSClass("UITextField", UIControl, {
889
893
  },
890
894
 
891
895
  resignFirstResponder: function(){
896
+ this.hideTextActionsMenu();
892
897
  this.active = false;
893
898
  this._updateAccessoryViewHidden(this._leftAccessoryView, this._leftAccessoryVisibility);
894
899
  this._updateAccessoryViewHidden(this._rightAccessoryView, this._rightAccessoryVisibility);
@@ -977,9 +982,27 @@ JSClass("UITextField", UIControl, {
977
982
  UITextField.$super.mouseUp.call(this, event);
978
983
  },
979
984
 
985
+ rightMouseDown: function(event){
986
+ var actions = this.textActions();
987
+ if (actions.length > 0){
988
+ var menu = UIMenu.init();
989
+ var action;
990
+ for (var i = 0, l = actions.length; i < l; ++i){
991
+ action = actions[i];
992
+ menu.addItemWithTitle(action.title, action.action, action.target);
993
+ }
994
+ var location = event.locationInView(this);
995
+ menu.openAtLocationInContextView(location, this);
996
+ return;
997
+ }
998
+ UITextField.$super.rightMouseDown.call(this, event);
999
+ },
1000
+
980
1001
  _touchOrigin: null,
1002
+ _touchesBeganSelections: null,
981
1003
 
982
1004
  touchesBegan: function(touches, event){
1005
+ this._touchesBeganSelections = null;
983
1006
  if (this.enabled){
984
1007
  var location = touches[0].locationInView(this);
985
1008
  this._touchOrigin = this.convertPointToView(this.bounds.origin, this.window);
@@ -989,6 +1012,7 @@ JSClass("UITextField", UIControl, {
989
1012
  // when mobile safari ignores focus() calls that aren't tied to
990
1013
  // user interaction
991
1014
  this.window.firstResponder = this;
1015
+ this._touchesBeganSelections = this.selections.map(function(s){ return UITextInputSelection(s)});
992
1016
  this._localEditor.handleTouchesBeganAtLocation(this.layer.convertPointToLayer(location, this._textLayer), touches, event);
993
1017
  return;
994
1018
  }
@@ -1012,6 +1036,26 @@ JSClass("UITextField", UIControl, {
1012
1036
  var location = touches[0].locationInView(this);
1013
1037
  if (this.isFirstResponder()){
1014
1038
  this._localEditor.handleTouchesEnded(touches, event);
1039
+ if (this.textActionsMenu !== null){
1040
+ this.hideTextActionsMenu();
1041
+ }else if (this._touchesBeganSelections !== null){
1042
+ var selections = this.selections;
1043
+ var match = true;
1044
+ if (selections.length === this._touchesBeganSelections.length){
1045
+ for (var i = 0, l = selections.length; i < l; ++i){
1046
+ if (!selections[i].range.isEqual(this._touchesBeganSelections[i].range)){
1047
+ match = false;
1048
+ break;
1049
+ }
1050
+ }
1051
+ }else{
1052
+ match = false;
1053
+ }
1054
+ if (match){
1055
+ this.showTextActionsMenu(location);
1056
+ }
1057
+ }
1058
+ this._touchesBeganSelections = null;
1015
1059
  return;
1016
1060
  }
1017
1061
  if (this._touchOrigin !== null){
@@ -1022,10 +1066,12 @@ JSClass("UITextField", UIControl, {
1022
1066
  }
1023
1067
  }
1024
1068
  }
1069
+ this._touchesBeganSelections = null;
1025
1070
  UITextField.$super.touchesEnded.call(this, touches, event);
1026
1071
  },
1027
1072
 
1028
1073
  touchesCanceled: function(touches, event){
1074
+ this._touchesBeganSelections = null;
1029
1075
  if (this.enabled){
1030
1076
  var location = touches[0].locationInView(this);
1031
1077
  this._localEditor.handleTouchesCanceled(touches, event);
@@ -1041,6 +1087,51 @@ JSClass("UITextField", UIControl, {
1041
1087
  return text;
1042
1088
  },
1043
1089
 
1090
+ // ----------------------------------------------------------------------
1091
+ // MARK: Text Actions
1092
+
1093
+ copyTextAction: JSLazyInitProperty(function(){
1094
+ return UITextAction.initWithTitle(UITextField.bundle.localizedString("textActions.copy"), "copy", this);
1095
+ }),
1096
+
1097
+ pasteTextAction: JSLazyInitProperty(function(){
1098
+ return UITextAction.initWithTitle(UITextField.bundle.localizedString("textActions.paste"), "paste", this);
1099
+ }),
1100
+
1101
+ textActions: function(){
1102
+ return [this.copyTextAction, this.pasteTextAction];
1103
+ },
1104
+
1105
+ textActionsMenu: null,
1106
+
1107
+ showTextActionsMenu: function(location){
1108
+ var responder = this;
1109
+ var actions = this.textActions().filter(function(textAction){
1110
+ var target = responder.targetForAction(textAction.action, null);
1111
+ return target !== null && target.canPerformAction(textAction.action, null);
1112
+ });
1113
+ if (actions.length === 0){
1114
+ return;
1115
+ }
1116
+ this.textActionsMenu = UITextActionsMenu.initWithActions(actions);
1117
+ this.textActionsMenu.delegate = this;
1118
+ this.textActionsMenu.openAtLocationInView(location, this);
1119
+ },
1120
+
1121
+ hideTextActionsMenu: function(){
1122
+ if (this.textActionsMenu !== null){
1123
+ this.textActionsMenu.close();
1124
+ this.textActionsMenu = null;
1125
+ }
1126
+ },
1127
+
1128
+ textActionsMenuDidClose: function(textActionsMenu){
1129
+ if (textActionsMenu === this.textActionsMenu){
1130
+ textActionsMenu.delegate = null;
1131
+ this.textActionsMenu = null;
1132
+ }
1133
+ },
1134
+
1044
1135
  // --------------------------------------------------------------------
1045
1136
  // MARK: - Accessibility
1046
1137
 
@@ -1265,6 +1356,17 @@ JSClass("UITextField", UIControl, {
1265
1356
 
1266
1357
  });
1267
1358
 
1359
+ Object.defineProperties(UITextField, {
1360
+ bundle: {
1361
+ configurable: true,
1362
+ get: function(){
1363
+ var bundle = JSBundle.initWithIdentifier("io.breakside.JSKit.UIKit");
1364
+ Object.defineProperty(UITextField, "bundle", {value: bundle});
1365
+ return bundle;
1366
+ }
1367
+ }
1368
+ });
1369
+
1268
1370
  UITextField.AccessoryVisibility = {
1269
1371
  always: 0,
1270
1372
  onlyWhenActive: 1,
@@ -17,4 +17,7 @@ en:
17
17
  value:
18
18
  "on": "On"
19
19
  "off": "Off"
20
- mixed: Mixed
20
+ mixed: Mixed
21
+ textActions:
22
+ copy: Copy
23
+ paste: Paste
@@ -1,6 +1,6 @@
1
1
  JSBundleType: framework
2
2
  JSBundleIdentifier: io.breakside.JSKit.UIKitTesting
3
- JSBundleVersion: 2026.6.0
3
+ JSBundleVersion: 2026.9.0
4
4
  JSDevelopmentLanguage: en
5
5
  JSCopyright: Copyright © 2020 Breakside Inc.
6
6
  JSLicenseNotice: |
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "node": ">=10.10.0"
13
13
  },
14
14
  "name": "@breakside/jskit",
15
- "version": "2026.6.0",
15
+ "version": "2026.9.0",
16
16
  "license": "SEE LICENSE IN LICENSE.txt",
17
17
  "files": [
18
18
  "*"