This is an automated email from the ASF dual-hosted git repository.

gregdove pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-asjs.git

commit df82c921770f801ad8a45adb0e7d49b990a37200
Author: greg-dove <[email protected]>
AuthorDate: Thu Sep 1 17:00:11 2022 +1200

    Adding an extra mx-level implementation similar to 
org.apache.royale.html.SimpleTextHighlighter
---
 .../src/main/resources/mx-royale-manifest.xml      |   2 +
 .../MXRoyale/src/main/royale/MXRoyaleClasses.as    |   3 +
 .../royale/mx/controls/SimpleTextHighlighter.as    | 578 +++++++++++++++++++++
 3 files changed, 583 insertions(+)

diff --git 
a/frameworks/projects/MXRoyale/src/main/resources/mx-royale-manifest.xml 
b/frameworks/projects/MXRoyale/src/main/resources/mx-royale-manifest.xml
index 302df0833b..c4c102e7e1 100644
--- a/frameworks/projects/MXRoyale/src/main/resources/mx-royale-manifest.xml
+++ b/frameworks/projects/MXRoyale/src/main/resources/mx-royale-manifest.xml
@@ -242,4 +242,6 @@
     <component id="DataGridHeaderRenderer" 
class="mx.controls.dataGridClasses.DataGridHeaderRenderer"/>
     <component id="CascadingMenuFactory" 
class="mx.controls.menuClasses.CascadingMenuFactory"/>
 
+    <!-- new/experimental -->
+    <component id="SimpleTextHighlighter" 
class="mx.controls.SimpleTextHighlighter" />
 </componentPackage>
diff --git a/frameworks/projects/MXRoyale/src/main/royale/MXRoyaleClasses.as 
b/frameworks/projects/MXRoyale/src/main/royale/MXRoyaleClasses.as
index 049c9f4d76..bf581a3f38 100644
--- a/frameworks/projects/MXRoyale/src/main/royale/MXRoyaleClasses.as
+++ b/frameworks/projects/MXRoyale/src/main/royale/MXRoyaleClasses.as
@@ -433,6 +433,9 @@ internal class MXRoyaleClasses
        import mx.containers.accordionClasses.AccordionHeader; AccordionHeader;
        /*import mx.net.FileReferenceList; FileReferenceList;*/
        import mx.controls.beads.ProgressBarView; ProgressBarView;
+
+
+       import mx.controls.SimpleTextHighlighter;SimpleTextHighlighter;
 }
 
 }
diff --git 
a/frameworks/projects/MXRoyale/src/main/royale/mx/controls/SimpleTextHighlighter.as
 
b/frameworks/projects/MXRoyale/src/main/royale/mx/controls/SimpleTextHighlighter.as
new file mode 100644
index 0000000000..13b42eda1a
--- /dev/null
+++ 
b/frameworks/projects/MXRoyale/src/main/royale/mx/controls/SimpleTextHighlighter.as
@@ -0,0 +1,578 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+package mx.controls
+{
+
+
+    import mx.core.UIComponent;
+
+    import org.apache.royale.core.ITextModel;
+       import org.apache.royale.core.UIBase;
+    import org.apache.royale.events.Event;
+    import org.apache.royale.events.IEventDispatcher;
+    import org.apache.royale.html.supportClasses.HighlightTextSpan;
+
+    COMPILE::JS
+    {
+        import goog.events;
+        import org.apache.royale.core.WrappedHTMLElement;
+        import org.apache.royale.html.util.addElementToWrapper;
+    }
+
+
+    /**
+     *  Dispatched when the user changes the text.
+     *
+     *  @langversion 3.0
+     *  @playerversion Flash 10.2
+     *  @playerversion AIR 2.6
+     *  @productversion Royale 0.9.0
+     */
+    [Event(name="change", type="org.apache.royale.events.Event")]
+
+    /**
+     *  This class is not part of the original Flex component set,
+     *  it is additional - a utility class for simple highlighting of
+     *  editable text. Similar to org.apache.royale.html.SimpleTextHighlighter
+     *
+     *  @toplevel
+     *  @langversion 3.0
+     *  @playerversion Flash 10.2
+     *  @playerversion AIR 2.6
+     *  @productversion Royale 0.0
+     */
+       public class SimpleTextHighlighter extends UIComponent
+       {
+
+
+
+
+
+        /**
+         *  Constructor.
+         *
+         *  @langversion 3.0
+         *  @playerversion Flash 10.2
+         *  @playerversion AIR 2.6
+         *  @productversion Royale 0.0
+         */
+               public function SimpleTextHighlighter()
+               {
+                       super();
+
+            COMPILE::SWF
+            {
+                model.addEventListener("textChange", textChangeHandler);
+            }
+               }
+
+
+        override public function setFocus():void
+        {
+            COMPILE::SWF
+            {
+                stage.focus = this;
+            }
+            COMPILE::JS
+            {
+                _textContainer.focus();
+            }
+        }
+
+
+
+        protected var _parentRef:Object;
+
+
+        COMPILE::JS
+        private var _textContainer:HTMLSpanElement;
+
+        private var _textLength:uint;
+
+        private var _lengthValid:Boolean = true;
+
+        private function textLength():uint{
+            if (!_lengthValid) {
+                _textLength = text.length;
+                _lengthValid = true;
+            }
+            return _textLength;
+        }
+
+        /**
+         *  @copy org.apache.royale.html.Label#text
+         *
+         *  @langversion 3.0
+         *  @playerversion Flash 10.2
+         *  @playerversion AIR 2.6
+         *  @productversion Royale 0.0
+         *  @royaleignorecoercion HTMLTextAreaElement
+         */
+       [Bindable(event="change")]
+               public function get text():String
+               {
+            COMPILE::SWF
+            {
+                return ITextModel(model).text;
+            }
+            COMPILE::JS
+            {
+                return  _textContainer.textContent;
+            }
+               }
+
+        /**
+         *  @private
+         *  @royaleignorecoercion HTMLDivElement
+         */
+               public function set text(value:String):void
+               {
+            COMPILE::SWF
+            {
+                inSetter = true;
+                ITextModel(model).text = value;
+                inSetter = false;
+            }
+            COMPILE::JS
+            {
+                if (value != _textContainer.textContent) {
+                    _textContainer.textContent = value;
+                    dispatchEvent(new Event('textChange'));
+                }
+            }
+
+            _lengthValid = false;
+               }
+
+
+        protected var _highLights:Array = [];
+
+        public function removeHighlights():void{
+            COMPILE::JS
+            {
+
+
+                var selection:Selection = window.getSelection();
+                var reselect:Boolean;
+                var offset:uint = 0;
+                var end:uint = 0;
+                if (selection.rangeCount) {
+                    var selectionRange:Range = selection.getRangeAt(0);
+                    if (_textContainer == 
selectionRange.commonAncestorContainer || 
_textContainer.contains(selectionRange.commonAncestorContainer)) {
+                        var foundStart:Boolean;
+                        reselect = true;
+                        var node:Node = _textContainer.firstChild;
+                        while(node) {
+                            var tn:Node = node.nodeType == Node.TEXT_NODE ? 
node : node.firstChild;
+                            if (!foundStart) {
+                                if (selectionRange.startContainer == node || 
selectionRange.startContainer == tn) {
+                                    foundStart = true;
+                                    offset += selectionRange.startOffset;
+
+                                    if (selectionRange.endContainer == node || 
selectionRange.endContainer == tn) {
+                                        end = offset + 
(selectionRange.endOffset - selectionRange.startOffset);
+                                        break;
+                                    } else {
+                                        end += node.textContent.length;
+                                    }
+                                } else {
+                                    offset += node.textContent.length;
+                                    end = offset;
+                                }
+                            } else {
+                                if (selectionRange.endContainer == node || 
selectionRange.endContainer == tn) {
+                                    end += selectionRange.endOffset;
+                                    break;
+                                } else {
+                                    end += node.textContent.length;
+                                }
+                            }
+                            node = node.nextSibling;
+                        }
+                    }
+                }
+                //this will quickly remove all html (highlight blocks)
+                var plainText:String = text;
+                _textContainer.textContent = plainText;
+                _highLights.length = 0;
+                if (reselect) {
+                    selectionRange = document.createRange();
+                    node = _textContainer.firstChild ? 
_textContainer.firstChild : _textContainer;
+                    selectionRange.setStart(node,offset);
+                    selectionRange.setEnd(node,end);
+                    selection.removeAllRanges();
+                    selection.addRange(selectionRange);
+                }
+            }
+        }
+
+        /**
+         *
+         * can be overridden in subclasses
+         */
+        protected function createHighlightSpan(begin:int, 
end:int):HighlightTextSpan{
+            return new HighlightTextSpan(begin,end,_parentRef);
+        }
+
+        private var _autoRemoveHighlightChanges:Boolean;
+        public function get autoRemoveHighlightChanges():Boolean{
+            return _autoRemoveHighlightChanges
+        }
+
+        public function set autoRemoveHighlightChanges(value:Boolean):void{
+            _autoRemoveHighlightChanges = value;
+        }
+
+        /**
+         * @royaleignorecoercion org.apache.royale.core.WrappedHTMLElement
+         */
+        private function restoreIfNeeded():void{
+            COMPILE::JS{
+                //"undo" editing can restore items that were previously 
'removed'
+                var l:uint = _highLights.length;
+                if (_textContainer.childElementCount > l) {
+                    var collect:Array = [];
+                    var wrappedElement:WrappedHTMLElement = 
WrappedHTMLElement(_textContainer.firstElementChild);
+                    while (wrappedElement) {
+                        var highlight:HighlightTextSpan = 
wrappedElement.royale_wrapper as HighlightTextSpan;
+                        if (highlight) collect.push(highlight);
+                        wrappedElement = 
WrappedHTMLElement(wrappedElement.nextElementSibling)
+                    }
+                    _highLights = collect;
+                }
+            }
+        }
+
+
+        private function performAutoRemove():void{
+            var l:uint = _highLights.length;
+            if (l) {
+                var retained:Array = [];
+                for (var i:int = 0;i<l;i++) {
+                    var removeCheck:HighlightTextSpan = _highLights[i] as 
HighlightTextSpan;
+                    if (_autoRemoveHighlightChanges) {
+                        if (removeCheck.isUnchanged) {
+                            retained.push(removeCheck)
+                            removeCheck = null;
+                        }
+                    } else {
+                        if (removeCheck.isPresent) {
+                            retained.push(removeCheck)
+                            removeCheck = null;
+                        }
+                    }
+                    if (removeCheck) {
+                        removeCheck.unapply();
+                    }
+                }
+                _highLights = retained;
+            }
+        }
+
+        /**
+         *
+         * Utility method for unHighlighting text.
+         *
+         * returns true if highlighting occurred, false otherwise
+         */
+        public function removeHighlightForText(string:String, 
fromIndex:uint=0, all:Boolean=false):Boolean{
+            var l:uint = _highLights.length;
+            var success:Boolean;
+            if (l && string) {
+                if (string) {
+                    var idx:int = text.indexOf(string,fromIndex);
+                    while (idx != -1) {
+                        success =  removeHighlightForRange(idx) || success;
+                        if (all || !success) {
+                            idx = text.indexOf(string,idx + string.length);
+                        } else {
+                            break;
+                        }
+                    }
+                }
+            }
+            return success;
+        }
+
+
+        public function removeHighlightForRange(containsIndex:uint):Boolean{
+            var l:uint = _highLights.length;
+            if (l) {
+                for (var i:int = 0;i<l;i++) {
+                    var highlight:HighlightTextSpan = _highLights[i] as 
HighlightTextSpan;
+                    if (highlight.containsIndex(containsIndex)) {
+                        _highLights.splice(i,1);
+                        highlight.unapply();
+                        return true;
+                        break;
+                    }
+                }
+            }
+            return false;
+        }
+
+
+        /**
+         *
+         * Utility method for highlighting text.
+         *
+         * returns true if highlighting occurred, false otherwise
+         */
+        public function highlightText(string:String, fromIndex:uint=0, 
all:Boolean=false):Boolean{
+            var ret:Boolean;
+            if (string) {
+                var text:String = this.text;
+                var idx:int = text.indexOf(string,fromIndex);
+                while (idx != -1) {
+                    ret = highlightOffsetRange(idx, string.length, !all) || 
ret;
+                    if (all && text.length > idx + string.length) {
+                        idx = text.indexOf(string, idx + string.length);
+                    } else idx = -1;
+                }
+            }
+            return ret;
+        }
+
+        /**
+         *
+         * This method uses the same range logic as String.substr
+         * (begin: first char index, length: char count to be included)
+         * if 'single' is true, any previous highlights will be removed prior 
to the new highlight range
+         * being added
+         *
+         * returns true if highlighting occurred, false otherwise
+         *
+         */
+        public function highlightOffsetRange(begin:uint, length:uint, 
single:Boolean):Boolean{
+            if (length > 0) {
+                return highlightRange(begin,begin+length,single);
+            }
+            return false;
+        }
+
+
+        /**
+         *
+         * This method uses the same range logic as String.substring
+         * (begin: first char index, end: char index after last)
+         * if 'single' is true, any previous highlights will be removed prior 
to the new highlight range
+         * being added
+         *
+         * returns true if highlighting occurred, false otherwise
+         *
+         * @royaleignorecoercion 
org.apache.royale.html.supportClasses.HighlightTextSpan
+         */
+        public function highlightRange(begin:uint, end:uint, 
single:Boolean):Boolean{
+            var tl:uint = textLength();
+            if (end > tl) end = tl;
+            if (begin >= end) return false;
+            var l:uint = _highLights.length;
+            if (single) {
+                if (l) {
+                    removeHighlights();
+                    l = 0;
+                }
+            }
+
+            var insertAt:int = -1;
+            if (l) {
+                for (var i:int = 0;i<l;i++) {
+                    var highlight:HighlightTextSpan = _highLights[i] as 
HighlightTextSpan;
+                    if (highlight.intersectsRange(begin,end)) {
+                        //for now, simple exclusions apply
+                       // throw new Error('Highlight ranges must be exclusive')
+
+                        return false;
+                    }
+                    if (highlight.beginIndex >= end ) {
+                        if (insertAt == -1) //we need the first location
+                            insertAt = i;
+                    }
+                }
+            }
+            if (insertAt == -1) insertAt = l;
+            var newHighLight:HighlightTextSpan = 
createHighlightSpan(begin,end);
+            if (insertAt == l) {
+                _highLights[insertAt] = newHighLight
+            } else {
+                _highLights.splice(insertAt,0,newHighLight);
+            }
+
+            newHighLight.apply();
+            return true;
+        }
+
+        COMPILE::JS
+        private var docListening:Boolean;
+
+        COMPILE::JS
+        private function enforceUnselectable(selection:Selection):void{
+
+            var selectionRange:Range = selection.getRangeAt(0);
+            var location:Node = selectionRange.endContainer;
+            var endOffset:uint = selectionRange.endOffset;
+            if (location != selectionRange.startContainer || 
selectionRange.startOffset != selectionRange.endOffset) {
+                selection.removeAllRanges();
+                selectionRange = document.createRange();
+                selectionRange.setStart(location,endOffset);
+                selectionRange.collapse(true);
+                selection.addRange(selectionRange);
+            }
+        }
+
+        COMPILE::JS
+        protected function onLostFocus(event:Event):void{
+            if (docListening){
+                document.removeEventListener('selectionchange',onSelectEvent);
+                docListening = false;
+            }
+        }
+
+        protected function onSelectEvent(event:Event):void{
+
+            COMPILE::JS{
+                var selection:Selection;
+                if (!docListening) {
+                    document.addEventListener('selectionchange',onSelectEvent);
+                    docListening = true;
+                }
+                if (!_selectable) {
+                    if (docListening){
+                        selection = window.getSelection();
+                        if (event.type == 'selectionchange') {
+                            if (selection.baseNode != _textContainer && 
!_textContainer.contains(selection.baseNode)) {
+                                
document.removeEventListener('selectionchange',onSelectEvent);
+                                docListening = false;
+                                return;
+                            }
+                        }
+                    }
+                    enforceUnselectable(selection);
+                }
+                selection = window.getSelection();
+
+            }
+
+            //trace(event)
+        }
+
+
+        private var _wordWrap:Boolean = true;
+        public function get wordWrap():Boolean {
+            return _wordWrap;
+        }
+
+        public function set wordWrap(value:Boolean):void {
+            if (_wordWrap != value) {
+                _wordWrap = value;
+                COMPILE::JS{
+                    if (value) {
+                        element.style.whiteSpace = 'pre-wrap';
+                    } else {
+                        element.style.whiteSpace = 'pre';
+                    }
+                }
+            }
+        }
+
+
+        private var _selectable:Boolean = true;
+        public function get selectable():Boolean {
+            return _selectable;
+        }
+
+        public function set selectable(value:Boolean):void {
+            if (_selectable != value) {
+                _selectable = value;
+                COMPILE::JS{
+                    if (value) {
+                        _textContainer.style.userSelect = '';
+                    } else {
+                        _textContainer.style.userSelect = 'none';
+                    }
+                }
+            }
+        }
+
+        private var _editable:Boolean = true;
+        public function get editable():Boolean {
+            return _editable;
+        }
+
+        public function set editable(value:Boolean):void {
+            if (_editable != value) {
+                _editable = value;
+                COMPILE::JS{
+                    _textContainer.contentEditable = String(value) ;
+                    if (value) {
+                        // element.addEventListener('input', onInput)
+                        goog.events.listen(_textContainer, 'input', 
textChangeHandler);
+                    } else {
+                        // element.removeEventListener('input', onInput);
+                        goog.events.unlisten(_textContainer, 'input', 
textChangeHandler);
+                    }
+                }
+            }
+        }
+
+        /**
+         * @royaleignorecoercion org.apache.royale.core.WrappedHTMLElement
+         * @royaleignorecoercion HTMLSpanElement
+         */
+        COMPILE::JS
+        override protected function createElement():WrappedHTMLElement
+        {
+                       addElementToWrapper(this,'div');
+            _textContainer = document.createElement('span') as HTMLSpanElement;
+            _textContainer.onselectstart = onSelectEvent;
+            _textContainer.onblur = onLostFocus;
+            //for JS the parentRef is the parent span
+            _parentRef = _textContainer;
+            element.appendChild(_textContainer);
+            _textContainer.contentEditable = "true";
+            goog.events.listen(_textContainer, 'input', textChangeHandler);
+            typeNames = 'SimpleTextHighlighter';
+            return element;
+        }
+
+        private var inSetter:Boolean;
+
+        /**
+         *  dispatch change event in response to a textChange event
+         *
+         *  @langversion 3.0
+         *  @playerversion Flash 10.2
+         *  @playerversion AIR 2.6
+         *  @productversion Royale 0.9.0
+         */
+        public function textChangeHandler(event:Event):void
+        {
+            COMPILE::JS{
+                if (!inSetter)
+                {
+                    _lengthValid = false;
+                    restoreIfNeeded();
+                    performAutoRemove();
+                    dispatchEvent(new Event(Event.CHANGE));
+                }
+            }
+
+        }
+       }
+}

Reply via email to