@breakside/jskit 2023.13.0 → 2023.14.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.
- package/Frameworks/DOM.jsframework/Info.json +2 -2
- package/Frameworks/DOM.jsframework/io.breakside.JSKit.DOM-bundle.js +2 -2
- package/Frameworks/FontKit.jsframework/Info.json +2 -2
- package/Frameworks/FontKit.jsframework/io.breakside.JSKit.FontKit-bundle.js +2 -2
- package/Frameworks/Foundation.jsframework/Info.json +2 -2
- package/Frameworks/Foundation.jsframework/io.breakside.JSKit.Foundation-bundle.js +2 -2
- package/Frameworks/SecurityKit.jsframework/Info.json +2 -2
- package/Frameworks/SecurityKit.jsframework/io.breakside.JSKit.SecurityKit-bundle.js +2 -2
- package/Info.json +2 -2
- package/Node/io.breakside.jskit-bundle.js +2 -2
- package/Root/Frameworks/APIKit/Info.yaml +1 -1
- package/Root/Frameworks/APIKitTesting/Info.yaml +1 -1
- package/Root/Frameworks/AuthKit/Info.yaml +1 -1
- package/Root/Frameworks/CSSOM/Info.yaml +1 -1
- package/Root/Frameworks/ChartKit/Info.yaml +1 -1
- package/Root/Frameworks/ConferenceKit/Info.yaml +1 -1
- package/Root/Frameworks/DBKit/Info.yaml +1 -1
- package/Root/Frameworks/DOM/Info.yaml +1 -1
- package/Root/Frameworks/Dispatch/Info.yaml +1 -1
- package/Root/Frameworks/FontKit/Info.yaml +1 -1
- package/Root/Frameworks/Foundation/Info.yaml +1 -1
- package/Root/Frameworks/ImageKit/Info.yaml +1 -1
- package/Root/Frameworks/MediaKit/Info.yaml +1 -1
- package/Root/Frameworks/MediaKitUI/Info.yaml +1 -1
- package/Root/Frameworks/NotificationKit/Info.yaml +1 -1
- package/Root/Frameworks/PDFKit/Info.yaml +1 -1
- package/Root/Frameworks/QRKit/Info.yaml +1 -1
- package/Root/Frameworks/SearchKit/Info.yaml +1 -1
- package/Root/Frameworks/SecurityKit/Info.yaml +1 -1
- package/Root/Frameworks/ServerKit/Info.yaml +1 -1
- package/Root/Frameworks/ServerKitTesting/Info.yaml +1 -1
- package/Root/Frameworks/TestKit/Info.yaml +1 -1
- package/Root/Frameworks/UIKit/Info.yaml +1 -1
- package/Root/Frameworks/UIKit/UICollectionView.js +1 -0
- package/Root/Frameworks/UIKit/UIHTMLContentEditableTextInputManager.js +164 -151
- package/Root/Frameworks/UIKit/UIHTMLTextField.js +2 -1
- package/Root/Frameworks/UIKit/UIHTMLTextLine.js +45 -25
- package/Root/Frameworks/UIKit/UIHTMLTextRun.js +7 -2
- package/Root/Frameworks/UIKit/UIHTMLTextTypesetter.js +9 -3
- package/Root/Frameworks/UIKit/UIHTMLWindowServer.js +1 -0
- package/Root/Frameworks/UIKit/UIListView.js +1 -0
- package/Root/Frameworks/UIKit/UITextEditor.js +31 -8
- package/Root/Frameworks/UIKit/UITextField.js +1 -0
- package/Root/Frameworks/UIKit/UITokenField.js +1 -1
- package/Root/Frameworks/UIKit/UIToolbar.js +17 -9
- package/Root/Frameworks/UIKit/UIWindow.js +21 -7
- package/Root/Frameworks/UIKit/UIWindowServer.js +1 -1
- package/Root/Frameworks/UIKitTesting/Info.yaml +1 -1
- package/Root/Templates/html/${PROJECT_NAME}/conf/debug/nginx.conf +2 -0
- package/Root/Templates/html/${PROJECT_NAME}/conf/release/nginx.conf +2 -0
- package/package.json +1 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
// #import "UITextInputManager.js"
|
|
17
17
|
// #feature 'key' in KeyboardEvent.prototype
|
|
18
|
-
/* global UIHTMLTextInputManager */
|
|
18
|
+
/* global UIHTMLTextInputManager, UIDevice, UIView, UIUserInterface */
|
|
19
19
|
// jshint browser: true
|
|
20
20
|
'use strict';
|
|
21
21
|
|
|
@@ -29,7 +29,10 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
29
29
|
domWindow: null,
|
|
30
30
|
domDocument: null,
|
|
31
31
|
rootElement: null,
|
|
32
|
+
textInputLayer: JSDynamicProperty("_textInputLayer", null),
|
|
33
|
+
layoutManager: JSDynamicProperty("_layoutManager", null),
|
|
32
34
|
editableElement: JSDynamicProperty("_editableElement", null),
|
|
35
|
+
editableContext: JSDynamicProperty("_editableContext", null),
|
|
33
36
|
styleElement: null,
|
|
34
37
|
supportsBeforeinputEvent: false,
|
|
35
38
|
|
|
@@ -38,9 +41,9 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
38
41
|
this.rootElement = rootElement;
|
|
39
42
|
this.domDocument = this.rootElement.ownerDocument;
|
|
40
43
|
this.domWindow = this.domDocument.defaultView;
|
|
41
|
-
// NOTE: onbeforeinput insn't exposed in Chrome even though it supports the beforeinput event
|
|
44
|
+
// NOTE: onbeforeinput insn't exposed in older Chrome versions even though it supports the beforeinput event
|
|
42
45
|
this.supportsBeforeinputEvent = ("onbeforeinput" in HTMLElement.prototype) || userAgentKnownToSupportBeforeInput();
|
|
43
|
-
this.
|
|
46
|
+
this.shouldPreventSpaceInput = !userAgentKnownToRequireSpaceInput();
|
|
44
47
|
this.styleElement = this.domDocument.createElement("style");
|
|
45
48
|
this.styleElement.type = "text/css";
|
|
46
49
|
this.domDocument.head.appendChild(this.styleElement);
|
|
@@ -51,21 +54,29 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
51
54
|
windowDidChangeResponder: function(window){
|
|
52
55
|
UIHTMLTextInputManager.$super.windowDidChangeResponder.call(this, window);
|
|
53
56
|
if (this.textInputClient === null){
|
|
54
|
-
this.
|
|
57
|
+
this.textInputLayer = null;
|
|
58
|
+
this.layoutManager = null;
|
|
55
59
|
}else{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
this.textInputLayer = this.textInputClient.textInputLayer ? this.textInputClient.textInputLayer() : null;
|
|
61
|
+
this.layoutManager = this.textInputClient.textInputLayoutManager ? this.textInputClient.textInputLayoutManager() : null;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
setTextInputLayer: function(layer){
|
|
66
|
+
this._textInputLayer = layer;
|
|
67
|
+
if (this._textInputLayer !== null){
|
|
68
|
+
this.editableContext = this.windowServer.displayServer.contextForLayer(this._textInputLayer);
|
|
69
|
+
}else{
|
|
70
|
+
this.editableContext = null;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
setEditableContext: function(context){
|
|
75
|
+
this._editableContext = context;
|
|
76
|
+
if (this._editableContext !== null){
|
|
77
|
+
this.editableElement = this._editableContext.element;
|
|
78
|
+
}else{
|
|
79
|
+
this.editableElement = null;
|
|
69
80
|
}
|
|
70
81
|
},
|
|
71
82
|
|
|
@@ -99,16 +110,12 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
99
110
|
},
|
|
100
111
|
|
|
101
112
|
textFrameForSelections: function(){
|
|
102
|
-
if (this.
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
if (!this.textInputClient.textInputLayoutManager){
|
|
113
|
+
if (this._layoutManager === null){
|
|
106
114
|
return null;
|
|
107
115
|
}
|
|
108
116
|
if (!this.textInputClient.textInputSelections){
|
|
109
117
|
return null;
|
|
110
118
|
}
|
|
111
|
-
var layoutManager = this.textInputClient.textInputLayoutManager();
|
|
112
119
|
var selections = this.textInputClient.textInputSelections();
|
|
113
120
|
var selection;
|
|
114
121
|
var range;
|
|
@@ -128,8 +135,8 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
128
135
|
return null;
|
|
129
136
|
}
|
|
130
137
|
selection = selections[0];
|
|
131
|
-
container1 =
|
|
132
|
-
container2 =
|
|
138
|
+
container1 = this._layoutManager.textContainerForCharacterAtIndex(selection.startLocation);
|
|
139
|
+
container2 = this._layoutManager.textContainerForCharacterAtIndex(selection.insertionLocation);
|
|
133
140
|
if (container1 && container2){
|
|
134
141
|
if (container1 === container2){
|
|
135
142
|
return container1.textFrame;
|
|
@@ -376,7 +383,7 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
376
383
|
logger.warn("dom selection point is not in a text frame");
|
|
377
384
|
return null;
|
|
378
385
|
}
|
|
379
|
-
return +element.dataset.rangeLocation + element.dataset.rangeLength;
|
|
386
|
+
return +element.dataset.rangeLocation + (+element.dataset.rangeLength);
|
|
380
387
|
},
|
|
381
388
|
|
|
382
389
|
keydown: function(e){
|
|
@@ -406,7 +413,7 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
406
413
|
this.windowServer._createKeyEventFromDOMEvent(e, UIEvent.Type.keyDown, preventDefault);
|
|
407
414
|
if (!this.supportsBeforeinputEvent && this.textInputClient !== null && text !== null && text !== ""){
|
|
408
415
|
e.preventDefault();
|
|
409
|
-
this.insertText(text);
|
|
416
|
+
this.textClient.insertText(text);
|
|
410
417
|
}
|
|
411
418
|
},
|
|
412
419
|
|
|
@@ -419,17 +426,16 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
419
426
|
var selections = this.selectionsForDOMRanges(domRanges);
|
|
420
427
|
var inputClientSelections = [];
|
|
421
428
|
// logger.debug("beforeinput %{public}: type=%{public}, data=%{public} dataTransfer=%{public}", selections, e.inputType, e.data, e.dataTransfer !== null ? e.dataTransfer.getData("text/plain") : null);
|
|
422
|
-
if (domRanges.length > 0){
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
if (domSelection.anchorNode){
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
429
|
+
// if (domRanges.length > 0){
|
|
430
|
+
// logger.debug(" range start : %{public} @ %d", domRanges[0].startContainer.nodeValue, domRanges[0].startOffset);
|
|
431
|
+
// logger.debug(" range end : %{public} @ %d", domRanges[0].endContainer.nodeValue, domRanges[0].endOffset);
|
|
432
|
+
// }
|
|
433
|
+
// if (domSelection.anchorNode){
|
|
434
|
+
// logger.debug(" selection anchor: %{public} @ %d", domSelection.anchorNode.nodeValue, domSelection.anchorOffset);
|
|
435
|
+
// logger.debug(" range focus : %{public} @ %d", domSelection.focusNode.nodeValue, domSelection.focusOffset);
|
|
436
|
+
// }
|
|
430
437
|
switch (e.inputType){
|
|
431
438
|
case "insertText":
|
|
432
|
-
e.preventDefault();
|
|
433
439
|
// Chrome will use insertText when it really should use replaceText, so
|
|
434
440
|
// we could look for situations that seem more like a replacement, when
|
|
435
441
|
// the event selections are different from our current selections.
|
|
@@ -446,6 +452,9 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
446
452
|
// }
|
|
447
453
|
// }
|
|
448
454
|
// }
|
|
455
|
+
if (this.shouldPreventSpaceInput || e.data !== " "){
|
|
456
|
+
e.preventDefault();
|
|
457
|
+
}
|
|
449
458
|
this.textInputClient.insertText(e.data);
|
|
450
459
|
this.scheduleDocumentSelectionUpdate();
|
|
451
460
|
break;
|
|
@@ -548,56 +557,26 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
548
557
|
this.scheduleDocumentSelectionUpdate();
|
|
549
558
|
}
|
|
550
559
|
break;
|
|
551
|
-
case "deleteByComposition":
|
|
552
|
-
// 2021-03-26 not supported by any browser
|
|
553
|
-
// - should be called before the first insertCompositionText
|
|
554
|
-
// if there's a current selection
|
|
555
|
-
// - UITextEditor will clear the text automatically if necessary
|
|
556
|
-
// e.preventDefault();
|
|
557
|
-
// this.textInputClient.replaceText(selections, "");
|
|
558
|
-
break;
|
|
559
560
|
case "insertCompositionText":
|
|
560
561
|
// not cancelable
|
|
561
|
-
// browser will insert the new composing text
|
|
562
|
-
if (this.
|
|
563
|
-
|
|
564
|
-
// be replaced. The range of that text is not available
|
|
565
|
-
// until the subsequent beforeinput event.
|
|
566
|
-
// In such a scenario, Firefox will fire compositionupdate
|
|
567
|
-
// before the beforeinput event, which gets things out of
|
|
568
|
-
// order, so we check for that and queue the update data
|
|
569
|
-
// to be handled here.
|
|
570
|
-
if (this.textInputClient.replaceText){
|
|
571
|
-
this.textInputClient.replaceText(selections, "");
|
|
572
|
-
}else{
|
|
573
|
-
logger.warn("UITextInputClient missing implementation of replaceText()");
|
|
574
|
-
}
|
|
575
|
-
this.compositionstartHasData = false;
|
|
576
|
-
if (this.compositionupdateData){
|
|
577
|
-
if (this.textInputClient.setMarkedText){
|
|
578
|
-
this.textInputClient.setMarkedText(this.compositionupdateData);
|
|
579
|
-
}else{
|
|
580
|
-
logger.warn("UITextInput missing implementation of setMarkedText()");
|
|
581
|
-
}
|
|
582
|
-
this.compositionupdateData = null;
|
|
583
|
-
}
|
|
562
|
+
// browser will insert the new composing text
|
|
563
|
+
if (this.textInputClient.replaceText){
|
|
564
|
+
this.textInputClient.replaceText(selections, e.data);
|
|
584
565
|
}
|
|
585
566
|
break;
|
|
586
567
|
case "deleteCompositionText":
|
|
587
|
-
//
|
|
588
|
-
//
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
568
|
+
// Removed from input events spec, but Safari implemented it,
|
|
569
|
+
// so it may still fire. Need to handle.
|
|
570
|
+
if (this.textInputClient.replaceText){
|
|
571
|
+
this.textInputClient.replaceText(selections, "");
|
|
572
|
+
}
|
|
592
573
|
break;
|
|
593
574
|
case "insertFromComposition":
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
// Spec says beforeinput, compositionend, input
|
|
600
|
-
e.preventDefault();
|
|
575
|
+
// Removed from input events spec, but Safari implemented it,
|
|
576
|
+
// so it may still fire. Need to handle.
|
|
577
|
+
if (this.textInputClient.replaceText){
|
|
578
|
+
this.textInputClient.replaceText(selections, e.data);
|
|
579
|
+
}
|
|
601
580
|
break;
|
|
602
581
|
case "deleteByDrag":
|
|
603
582
|
case "deleteByCut":
|
|
@@ -631,6 +610,7 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
631
610
|
break;
|
|
632
611
|
default:
|
|
633
612
|
// anything else, not allowed
|
|
613
|
+
logger.warn("contenteditable received unknown beforeinput %{public}", e.inputType);
|
|
634
614
|
e.preventDefault();
|
|
635
615
|
break;
|
|
636
616
|
}
|
|
@@ -639,76 +619,132 @@ JSClass('UIHTMLContentEditableTextInputManager', UITextInputManager, {
|
|
|
639
619
|
input: function(e){
|
|
640
620
|
// logger.debug("input: type=%{public}, data=%{public} dataTransfer=%{public}", e.inputType, e.data, e.dataTransfer !== null ? e.dataTransfer.getData("text/plain") : null);
|
|
641
621
|
// logger.debug("%{public}", this._editableElement.innerHTML);
|
|
642
|
-
var layoutManager = this.textInputClient.textInputLayoutManager ? this.textInputClient.textInputLayoutManager() : null;
|
|
643
622
|
switch (e.inputType){
|
|
644
623
|
case "insertCompositionText":
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
// We'll use compositionupdate instead, which is fired correctly.
|
|
648
|
-
// Setting the marked text (in compositionupdate) will trigger a layout, which will
|
|
649
|
-
// update DOM. Safari and Firefox are fine, but Chrome doesn't
|
|
650
|
-
// appreciate such an edit and will fail to complete the composition.
|
|
651
|
-
// So instead:
|
|
652
|
-
// - tell the layout manager it doesn't need an update after all
|
|
653
|
-
// - update the text frame to match the edited HTML
|
|
654
|
-
if (!this.canLayoutDuringComposition){
|
|
655
|
-
if (layoutManager !== null){
|
|
656
|
-
layoutManager.updateHTMLFrameRangesAfterEdit();
|
|
657
|
-
}
|
|
658
|
-
}
|
|
624
|
+
// Cannot be canceled during beforeinput, so we expect this
|
|
625
|
+
// input event to run. Allow the browser to do its thing here.
|
|
659
626
|
break;
|
|
660
627
|
case "deleteCompositionText":
|
|
661
|
-
|
|
662
|
-
//
|
|
663
|
-
//
|
|
628
|
+
case "insertFromComposition":
|
|
629
|
+
// Removed from input events spec, but Safari implemented it,
|
|
630
|
+
// so it may still fire. No need to log a warning.
|
|
631
|
+
break;
|
|
632
|
+
case "deleteContentBackward":
|
|
633
|
+
// Android doesn't cancel deleteContentBackward in some situations
|
|
634
|
+
// related to text re-composition. Nothing for us to do here,
|
|
635
|
+
// but also no need to log a warning.
|
|
664
636
|
break;
|
|
665
637
|
default:
|
|
666
638
|
// logger.debug("input: %{public}", e.data);
|
|
667
|
-
|
|
639
|
+
if (this.shouldPreventSpaceInput || e.inputType !== "insertText" || e.data !== " "){
|
|
640
|
+
logger.warn("contenteditable received input %{public}", e.inputType);
|
|
641
|
+
}
|
|
668
642
|
// Trigger a new layout of the edited text, which will recreate
|
|
669
643
|
// the lines/runs without whatever was just inserted by the browser
|
|
670
|
-
if (
|
|
671
|
-
|
|
644
|
+
if (this._layoutManager !== null){
|
|
645
|
+
this._layoutManager.setNeedsLayout();
|
|
672
646
|
this.scheduleDocumentSelectionUpdate();
|
|
673
647
|
}
|
|
674
648
|
break;
|
|
675
649
|
}
|
|
676
650
|
},
|
|
677
651
|
|
|
678
|
-
|
|
679
|
-
compositionupdateData: null,
|
|
652
|
+
_isComposing: false,
|
|
680
653
|
|
|
681
654
|
compositionstart: function(e){
|
|
682
|
-
//
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
655
|
+
// var domSelection = this.domDocument.getSelection();
|
|
656
|
+
// var domRanges = [];
|
|
657
|
+
// for (var i = 0, l = domSelection.rangeCount; i < l; ++i){
|
|
658
|
+
// domRanges.push(domSelection.getRangeAt(i));
|
|
659
|
+
// }
|
|
660
|
+
// var selections = this.selectionsForDOMRanges(domRanges);
|
|
661
|
+
// logger.debug("compositionstart %{public}: %{public}", selections, e.data);
|
|
662
|
+
if (!this._isComposing){
|
|
663
|
+
this._isComposing = true;
|
|
664
|
+
this._preventDOMUpdates();
|
|
665
|
+
}else{
|
|
666
|
+
// known to happen with Android SwiftKey keyboard
|
|
667
|
+
logger.warn("received compositionstart, but already started");
|
|
686
668
|
}
|
|
687
669
|
},
|
|
688
670
|
|
|
689
671
|
compositionupdate: function(e){
|
|
690
|
-
//
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
672
|
+
// var domSelection = this.domDocument.getSelection();
|
|
673
|
+
// var domRanges = [];
|
|
674
|
+
// for (var i = 0, l = domSelection.rangeCount; i < l; ++i){
|
|
675
|
+
// domRanges.push(domSelection.getRangeAt(i));
|
|
676
|
+
// }
|
|
677
|
+
// var selections = this.selectionsForDOMRanges(domRanges);
|
|
678
|
+
// logger.debug("compositionupdate %{public}: %{public}", selections, e.data);
|
|
679
|
+
if (!this._isComposing){
|
|
680
|
+
logger.warn("got compositionupdate, but not started");
|
|
699
681
|
}
|
|
700
682
|
},
|
|
701
683
|
|
|
702
684
|
compositionend: function(e){
|
|
703
685
|
// logger.debug("compositionend: %{public}", e.data);
|
|
704
|
-
|
|
705
|
-
|
|
686
|
+
|
|
687
|
+
if (this._isComposing){
|
|
688
|
+
this._isComposing = false;
|
|
689
|
+
this._resumeDOMUpdates();
|
|
690
|
+
this.scheduleDocumentSelectionUpdate();
|
|
691
|
+
this._cleanupTextNodes();
|
|
706
692
|
}else{
|
|
707
|
-
logger.warn("
|
|
693
|
+
logger.warn("got compositionend, but already ended");
|
|
708
694
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
_originalLayoutMethod: null,
|
|
698
|
+
|
|
699
|
+
_preventDOMUpdates: function(){
|
|
700
|
+
if (this._layoutManager === null){
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
this._originalLayoutMethod = this._layoutManager.layout;
|
|
704
|
+
Object.defineProperty(this._layoutManager, "layout", {
|
|
705
|
+
configurable: true,
|
|
706
|
+
writable: false,
|
|
707
|
+
enumerable: false,
|
|
708
|
+
value: function(){
|
|
709
|
+
var container;
|
|
710
|
+
var offset = 0;
|
|
711
|
+
for (var i = 0, l = this._textContainers.length; i < l; ++i){
|
|
712
|
+
container = this._textContainers[i];
|
|
713
|
+
offset += container._textFrame.recalculateRange(offset);
|
|
714
|
+
container._textFrame.recalculateSize();
|
|
715
|
+
container.framesetter._updateSizesAndPositionsOfLinesInFrame(container._textFrame);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
// var container;
|
|
720
|
+
// var i, l;
|
|
721
|
+
// for (i = 0, l = this._layoutManager._textContainers.length; i < l; ++i){
|
|
722
|
+
// container = this._layoutManager._textContainers[i];
|
|
723
|
+
// if (container._textFrame instanceof UIHTMLTextFrame){
|
|
724
|
+
// container._textFrame.element.style.overflow = "visible";
|
|
725
|
+
// }
|
|
726
|
+
// }
|
|
727
|
+
},
|
|
728
|
+
|
|
729
|
+
_resumeDOMUpdates: function(){
|
|
730
|
+
if (this._layoutManager === null){
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
// var container;
|
|
734
|
+
// var i, l;
|
|
735
|
+
// for (i = 0, l = this._layoutManager._textContainers.length; i < l; ++i){
|
|
736
|
+
// container = this._layoutManager._textContainers[i];
|
|
737
|
+
// if (container._textFrame instanceof UIHTMLTextFrame){
|
|
738
|
+
// container._textFrame.element.style.overflow = "hidden";
|
|
739
|
+
// }
|
|
740
|
+
// }
|
|
741
|
+
Object.defineProperty(this._layoutManager, "layout", {
|
|
742
|
+
configurable: true,
|
|
743
|
+
writable: false,
|
|
744
|
+
enumerable: false,
|
|
745
|
+
value: this._originalLayoutMethod
|
|
746
|
+
});
|
|
747
|
+
this._originalLayoutMethod = null;
|
|
712
748
|
},
|
|
713
749
|
|
|
714
750
|
_ensureCorrectFocus: function(){
|
|
@@ -743,27 +779,20 @@ var userAgentKnownToSupportBeforeInput = function(){
|
|
|
743
779
|
return parseInt(matches[1]) >= 88;
|
|
744
780
|
}
|
|
745
781
|
}catch (e){
|
|
746
|
-
logger(e);
|
|
782
|
+
logger.warn("Failed to check user agent: %{error}", e);
|
|
747
783
|
}
|
|
748
784
|
return false;
|
|
749
785
|
};
|
|
750
786
|
|
|
751
|
-
var
|
|
752
|
-
// User agent checks aren't ideal, but
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
// changes during input composition. Chrome is known have problems.
|
|
787
|
+
var userAgentKnownToRequireSpaceInput = function(){
|
|
788
|
+
// User agent checks aren't ideal, but Android has issues with calling
|
|
789
|
+
// preventDefault() on beforeinput when the input text is a space, so
|
|
790
|
+
// a user agent check is the best we can do.
|
|
756
791
|
try{
|
|
757
|
-
var matches = navigator.userAgent.match(/Android\/(\d+)/);
|
|
758
|
-
|
|
759
|
-
return true;
|
|
760
|
-
}
|
|
761
|
-
matches = navigator.userAgent.match(/Chrome\/(\d+)/);
|
|
762
|
-
if (matches !== null){
|
|
763
|
-
return true;
|
|
764
|
-
}
|
|
792
|
+
var matches = navigator.userAgent.match(/Android[\/ ](\d+)/);
|
|
793
|
+
return matches !== null;
|
|
765
794
|
}catch (e){
|
|
766
|
-
logger(e);
|
|
795
|
+
logger.warn("Failed to check user agent: %{error}", e);
|
|
767
796
|
}
|
|
768
797
|
return false;
|
|
769
798
|
};
|
|
@@ -809,20 +838,4 @@ htmlAutocompleteByTextContentType[UITextInput.TextContentType.locality] = "addre
|
|
|
809
838
|
htmlAutocompleteByTextContentType[UITextInput.TextContentType.country] = "country-name";
|
|
810
839
|
htmlAutocompleteByTextContentType[UITextInput.TextContentType.postalCode] = "postal-code";
|
|
811
840
|
|
|
812
|
-
JSTextLayoutManager.definePropertiesFromExtensions({
|
|
813
|
-
|
|
814
|
-
updateHTMLFrameRangesAfterEdit: function(){
|
|
815
|
-
this._needsLayout = false;
|
|
816
|
-
var container;
|
|
817
|
-
var offset = 0;
|
|
818
|
-
for (var i = 0, l = this._textContainers.length; i < l; ++i){
|
|
819
|
-
container = this._textContainers[i];
|
|
820
|
-
offset += container._textFrame.recalculateRange(offset);
|
|
821
|
-
container._textFrame.recalculateSize();
|
|
822
|
-
container.framesetter._updateSizesAndPositionsOfLinesInFrame(container._textFrame);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
});
|
|
827
|
-
|
|
828
841
|
})();
|
|
@@ -105,7 +105,7 @@ JSClass("UIHTMLTextField", UIControl, {
|
|
|
105
105
|
this._clipView.userInteractionEnabled = false;
|
|
106
106
|
this._clipView.backgroundColor = null;
|
|
107
107
|
this._clipView.clipsToBounds = true;
|
|
108
|
-
this.cursor = UICursor.
|
|
108
|
+
this.cursor = UICursor.systemDefault;
|
|
109
109
|
if (this._styler === null){
|
|
110
110
|
this._styler = UITextField.Styler.default;
|
|
111
111
|
}
|
|
@@ -784,6 +784,7 @@ JSClass("UIHTMLTextField", UIControl, {
|
|
|
784
784
|
|
|
785
785
|
windowDidChangeKeyStatus: function(){
|
|
786
786
|
this.updateInputElementFocus();
|
|
787
|
+
UIHTMLTextField.$super.windowDidChangeKeyStatus.call(this);
|
|
787
788
|
},
|
|
788
789
|
|
|
789
790
|
_inputElementFocused: false,
|
|
@@ -20,28 +20,17 @@
|
|
|
20
20
|
|
|
21
21
|
(function(){
|
|
22
22
|
|
|
23
|
+
var logger = JSLog("uikit", "text");
|
|
24
|
+
|
|
23
25
|
JSClass("UIHTMLTextLine", JSTextLine, {
|
|
24
26
|
|
|
25
27
|
element: null,
|
|
26
|
-
emptyTextNode: null,
|
|
27
28
|
attachmentRuns: null,
|
|
28
29
|
fontLineHeight: 0,
|
|
29
30
|
canvasContext: null,
|
|
31
|
+
brElement: null,
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
UIHTMLTextLine.$super.initWithHeight.call(this, height, -font.displayDescender, location);
|
|
33
|
-
this.element = element;
|
|
34
|
-
element.style.font = font.cssString(height);
|
|
35
|
-
this.emptyTextNode = element.appendChild(element.ownerDocument.createTextNode('\u200B'));
|
|
36
|
-
this.fontLineHeight = font.displayLineHeight;
|
|
37
|
-
this.attachmentRuns = [];
|
|
38
|
-
this.element.dataset.rangeLocation = location;
|
|
39
|
-
this.element.dataset.rangeLength = 0;
|
|
40
|
-
this.element.dataset.jstext = "line";
|
|
41
|
-
this.canvasContext = canvasContext;
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
initWithElement: function(element, runs, trailingWhitespaceWidth, attachmentRuns, canvasContext){
|
|
33
|
+
initWithElement: function(element, runs, trailingWhitespaceWidth, attachmentRuns, canvasContext, endsWithMandatoryLineBreak){
|
|
45
34
|
// constructing this.element before super init because super init calls
|
|
46
35
|
// this.align, which neesd to use this.element
|
|
47
36
|
UIHTMLTextLine.$super.initWithRuns.call(this, runs, trailingWhitespaceWidth);
|
|
@@ -54,6 +43,22 @@ JSClass("UIHTMLTextLine", JSTextLine, {
|
|
|
54
43
|
this.element.appendChild(run.element);
|
|
55
44
|
}
|
|
56
45
|
}
|
|
46
|
+
if (endsWithMandatoryLineBreak && features.shouldIncludeBR){
|
|
47
|
+
// Not necessary for visual, but helps Android Chrome understand
|
|
48
|
+
// there's a hard line break during contenteditable mode (used by
|
|
49
|
+
// UIHTMLContentEditableTextInputManager). Other broswers don't
|
|
50
|
+
// seem to have an issue, as it seems to be an issue related to
|
|
51
|
+
// android's so we'll limit this to Android only.
|
|
52
|
+
//
|
|
53
|
+
// It would perhaps be more correct to remove the trailing newline
|
|
54
|
+
// character in the final run's textNode and replace it with a <br>
|
|
55
|
+
// inside the run, and that's maybe even a job for the HTML
|
|
56
|
+
// typesetter, but since this is only used on Android, it's ok to
|
|
57
|
+
// just throw it at the end of the line.
|
|
58
|
+
this.brElement = this.element.appendChild(this.element.ownerDocument.createElement("br"));
|
|
59
|
+
this.brElement.dataset.rangeLocation = this._range.end - 1;
|
|
60
|
+
this.brElement.dataset.rangeLength = 1;
|
|
61
|
+
}
|
|
57
62
|
this.element.dataset.rangeLocation = this._range.location;
|
|
58
63
|
this.element.dataset.rangeLength = this._range.length;
|
|
59
64
|
this.element.dataset.jstext = "line";
|
|
@@ -151,10 +156,6 @@ JSClass("UIHTMLTextLine", JSTextLine, {
|
|
|
151
156
|
copy: function(){
|
|
152
157
|
var line = UIHTMLTextLine.$super.copy.call(this);
|
|
153
158
|
line.element = this.element.cloneNode();
|
|
154
|
-
if (this.emptyTextNode !== null){
|
|
155
|
-
line.emptyTextNode = this.emptyTextNode.cloneNode();
|
|
156
|
-
line.element.appendChild(line.emptyTextNode);
|
|
157
|
-
}
|
|
158
159
|
for (var i = 0, l = line.runs.length; i < l; ++i){
|
|
159
160
|
line.element.appendChild(line.runs[i].element);
|
|
160
161
|
}
|
|
@@ -169,9 +170,6 @@ JSClass("UIHTMLTextLine", JSTextLine, {
|
|
|
169
170
|
},
|
|
170
171
|
|
|
171
172
|
domSelectionPointForCharacterAtIndex: function(index){
|
|
172
|
-
if (this.range.length === 0){
|
|
173
|
-
return {node: this.emptyTextNode, offset: 0};
|
|
174
|
-
}
|
|
175
173
|
var run = this.runForCharacterAtIndex(index);
|
|
176
174
|
if (run !== null){
|
|
177
175
|
return run.domSelectionPointForCharacterAtIndex(index);
|
|
@@ -182,9 +180,6 @@ JSClass("UIHTMLTextLine", JSTextLine, {
|
|
|
182
180
|
debugDescription: JSReadOnlyProperty(),
|
|
183
181
|
|
|
184
182
|
getDebugDescription: function(){
|
|
185
|
-
if (this.emptyTextNode !== null){
|
|
186
|
-
" %dx%d @%d->%d [empty]".sprintf(this._size.width, this._size.height, this._range.location, this._range.end);
|
|
187
|
-
}
|
|
188
183
|
var lines = [];
|
|
189
184
|
lines.push([" %dx%d @%d->%d".sprintf(this._size.width, this._size.height, this._range.location, this._range.end)]);
|
|
190
185
|
for (var i = 0, l = this.runs.length; i < l; ++i){
|
|
@@ -244,9 +239,34 @@ JSClass("UIHTMLTextLine", JSTextLine, {
|
|
|
244
239
|
}
|
|
245
240
|
this.element.dataset.rangeLocation = this._range.location;
|
|
246
241
|
this.element.dataset.rangeLength = this._range.length;
|
|
242
|
+
if (this.brElement !== null){
|
|
243
|
+
this.brElement.dataset.rangeLocation = this._range.end - 1;
|
|
244
|
+
this.brElement.dataset.rangeLength = 1;
|
|
245
|
+
}
|
|
247
246
|
return diff;
|
|
248
247
|
},
|
|
249
248
|
|
|
250
249
|
});
|
|
251
250
|
|
|
251
|
+
var features = Object.create({}, {
|
|
252
|
+
|
|
253
|
+
shouldIncludeBR: {
|
|
254
|
+
configurable: true,
|
|
255
|
+
get: function(){
|
|
256
|
+
// User agent checks aren't ideal, but Android is the only place
|
|
257
|
+
// where we need to include BRs to have text input work correctly.
|
|
258
|
+
var shouldIncludeBR = false;
|
|
259
|
+
try{
|
|
260
|
+
var matches = navigator.userAgent.match(/Android[\/ ](\d+)/);
|
|
261
|
+
shouldIncludeBR = matches !== null;
|
|
262
|
+
}catch (e){
|
|
263
|
+
logger.warn("Failed to check user agent: %{error}", e);
|
|
264
|
+
}
|
|
265
|
+
Object.defineProperty(this, "shouldIncludeBR", {value: shouldIncludeBR});
|
|
266
|
+
return shouldIncludeBR;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
});
|
|
271
|
+
|
|
252
272
|
})();
|
|
@@ -247,9 +247,14 @@ JSClass("UIHTMLTextRun", JSTextRun, {
|
|
|
247
247
|
|
|
248
248
|
recalculateRange: function(offset){
|
|
249
249
|
var diff = 0;
|
|
250
|
+
var length = 0;
|
|
250
251
|
if (this.textNode){
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
length = this.textNode.nodeValue.length;
|
|
253
|
+
if (length > 0 && this.textNode.nodeValue[length - 1] === 0x200B){
|
|
254
|
+
length -= 1;
|
|
255
|
+
}
|
|
256
|
+
diff = length - this._range.length;
|
|
257
|
+
this._range = JSRange(this._range.location + offset, length);
|
|
253
258
|
}else{
|
|
254
259
|
this._range = JSRange(this._range.location + offset, this._range.length);
|
|
255
260
|
}
|
|
@@ -125,8 +125,12 @@ JSClass("UIHTMLTextTypesetter", JSTextTypesetter, {
|
|
|
125
125
|
var attributes = this._attributedString.attributesAtIndex(range.location);
|
|
126
126
|
attributes = this.resolveAttributes(attributes);
|
|
127
127
|
var font = attributes.font || null;
|
|
128
|
-
var
|
|
129
|
-
|
|
128
|
+
var span = this._createRunElement();
|
|
129
|
+
span.appendChild(span.ownerDocument.createTextNode("\u200B"));
|
|
130
|
+
this._styleTextElementWithAttributes(span, attributes, font);
|
|
131
|
+
var div = this._createLineElement();
|
|
132
|
+
var emptyRun = UIHTMLTextRun.initWithElement(span, font, attributes, range);
|
|
133
|
+
return UIHTMLTextLine.initWithElement(div, [emptyRun], 0, [], this.canvasContext);
|
|
130
134
|
},
|
|
131
135
|
|
|
132
136
|
_createLineFromHTMLLayout: function(layout){
|
|
@@ -136,6 +140,7 @@ JSClass("UIHTMLTextTypesetter", JSTextTypesetter, {
|
|
|
136
140
|
var runDescriptor;
|
|
137
141
|
var span;
|
|
138
142
|
var attachmentRuns = [];
|
|
143
|
+
var endsWithMandatoryLineBreak = false;
|
|
139
144
|
for (var i = 0, l = layout.runDescriptors.length; i < l; ++i){
|
|
140
145
|
runDescriptor = layout.runDescriptors[i];
|
|
141
146
|
span = this._createRunElement();
|
|
@@ -150,6 +155,7 @@ JSClass("UIHTMLTextTypesetter", JSTextTypesetter, {
|
|
|
150
155
|
}else{
|
|
151
156
|
span.appendChild(span.ownerDocument.createTextNode(utf16));
|
|
152
157
|
}
|
|
158
|
+
endsWithMandatoryLineBreak = utf16.userPerceivedCharacterIterator(utf16.length - 1).isMandatoryLineBreak;
|
|
153
159
|
}else{
|
|
154
160
|
this._styleAttachmentElementWithAttributes(span, runDescriptor.attributes, runDescriptor.attachment);
|
|
155
161
|
if (span.childNodes.length > 0){
|
|
@@ -169,7 +175,7 @@ JSClass("UIHTMLTextTypesetter", JSTextTypesetter, {
|
|
|
169
175
|
return this._createEmptyHTMLLine(layout.range);
|
|
170
176
|
}
|
|
171
177
|
var div = this._createLineElement();
|
|
172
|
-
return UIHTMLTextLine.initWithElement(div, runs, layout.trailingWhitespaceWidth, attachmentRuns, this.canvasContext);
|
|
178
|
+
return UIHTMLTextLine.initWithElement(div, runs, layout.trailingWhitespaceWidth, attachmentRuns, this.canvasContext, endsWithMandatoryLineBreak);
|
|
173
179
|
},
|
|
174
180
|
|
|
175
181
|
_styleTextElementWithAttributes: function(span, attributes, font){
|