Bläddra i källkod

feat(input): add compositionEvent callback in input component (#2922)

* feat(input): add compositionEvent callback in input component

* feat: add jest test for onCompositionStart/End/Update method for input/textarea

* chore: add support version for API

---------

Co-authored-by: ouxiaofeng.utlf <[email protected]>
Co-authored-by: zhangyumei.0319 <[email protected]>
rubbishmaker 5 månader sedan
förälder
incheckning
5bbcc241fb

+ 6 - 0
content/input/input/index-en-US.md

@@ -463,6 +463,9 @@ Answers to some questions:
 | onKeyDown         | Callback invoked when keydown                                                                                                                                                                 | function(e:event)               |           |
 | onKeyPress        | Callback invoked when keypress                                                                                                                                                                | function(e:event)               |           |
 | onKeyUp           | Callback invoked when keyup                                                                                                                                                                   | function(e:event)               |           |
+| onCompositionStart           | Callback invoked when compositionstart                                       | function(e:event)               |           |
+| onCompositionEnd           | Callback invoked when compositionend                                        | function(e:event)               |           |
+| onCompositionUpdate           |Callback invoked when compositionupdate                                      | function(e:event)               |           |
 ### TextArea
 
 > Other attributes are same with html `<textarea>`
@@ -497,6 +500,9 @@ Answers to some questions:
 | onKeyPress        | Callback invoked when keypress, html event                                                                             | (e:event) => void               | -       |
 | onKeyUp           | Callback invoked when keyup, html event                                                                                | (e:event) => void               | -       |
 | onResize          | Callback invoked when height changes in autosize mode **v>=0.37.0**                                                    | ({ height:number }) => void     | -       |
+| onCompositionStart           | Callback invoked when compositionstart **>=2.85.0** | function(e:event)               |           |
+| onCompositionEnd           | Callback invoked when compositionend **>=2.85.0** | function(e:event)               |           |
+| onCompositionUpdate           | Callback invoked when compositionupdate **>=2.85.0**  | function(e:event)               |           |
 
 ### InputGroup
 

+ 6 - 0
content/input/input/index.md

@@ -476,6 +476,9 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 | onKeyDown         | keydown回调                                      | function(e:event)               |           |
 | onKeyPress        | keypress回调                                     | function(e:event)               |           |
 | onKeyUp           | keyup回调                                        | function(e:event)               |           |
+| onCompositionStart | onCompositionStart回调, **>=2.85.0**  | function(e:event) | - |
+| onCompositionEnd | onCompositionEnd回调, **>=2.85.0**  | function(e:event) | - |
+| onCompositionUpdate | onCompositionUpdate回调, **>=2.85.0**  | function(e:event) | - |
 
 ### TextArea
 
@@ -510,6 +513,9 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 | onKeyPress   | keypress 回调,html 事件            | (e:event) => void               | -      |
 | onKeyUp      | keyup 回调,html 事件               | (e:event) => void               | -      |
 | onResize     | 触发高度变化时的回调 **>=v0.37.0** | ({ height:number }) => void    | -      |
+| onCompositionStart | onCompositionStart回调, **>=2.85.0**   | function(e:event) | - |
+| onCompositionEnd | onCompositionEnd回调, **>=2.85.0**  | function(e:event) | - |
+| onCompositionUpdate | onCompositionUpdate回调, **>=2.85.0**  | function(e:event) | - |
 
 ### InputGroup
 

+ 11 - 2
packages/semi-foundation/input/foundation.ts

@@ -23,6 +23,9 @@ export interface InputAdapter extends Partial<DefaultAdapter>, Partial<InputDefa
     notifyKeyUp(e: any): void;
     notifyKeyPress(e: any): void;
     notifyEnterPress(e: any): void;
+    notifyCompositionStart(e: any): void;
+    notifyCompositionEnd(e: any): void;
+    notifyCompositionUpdate(e: any): void;
     isEventTarget(e: any): boolean
 }
 
@@ -92,13 +95,15 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
         }
     }
 
-    handleCompositionStart = () => {
+    handleCompositionStart = (e) => {
         this.compositionEnter = true;
+        this._adapter.notifyCompositionStart(e);
     }
 
     handleCompositionEnd = (e: any) => {
         const value = e.target.value;
         this.compositionEnter = false;
+        this._adapter.notifyCompositionEnd(e); 
         const { getValueLength, maxLength, minLength } = this.getProps();
         if (!isFunction(getValueLength)) {
             return;
@@ -109,7 +114,11 @@ class InputFoundation extends BaseFoundation<InputAdapter> {
         }
         if (minLength) {
             this.handleVisibleMinLength(value);
-        } 
+        }
+    }
+
+    handleCompositionUpdate = (e) => {
+        this._adapter.notifyCompositionUpdate(e);
     }
 
     /**

+ 11 - 2
packages/semi-foundation/input/textareaFoundation.ts

@@ -18,7 +18,10 @@ export interface TextAreaDefaultAdapter {
     notifyKeyDown: noopFunction;
     notifyEnterPress: noopFunction;
     toggleHovering(hovering: boolean): void;
-    notifyClear(e: any): void
+    notifyClear(e: any): void;
+    notifyCompositionStart(e: any): void;
+    notifyCompositionEnd(e: any): void;
+    notifyCompositionUpdate(e: any): void
 }
 
 export interface TextAreaAdapter extends Partial<DefaultAdapter>, Partial<TextAreaDefaultAdapter> {
@@ -89,12 +92,14 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
         return value;
     }
 
-    handleCompositionStart = () => {
+    handleCompositionStart = (e) => {
         this.compositionEnter = true;
+        this._adapter.notifyCompositionStart(e);
     }
 
     handleCompositionEnd = (e: any) => {
         this.compositionEnter = false;
+        this._adapter.notifyCompositionEnd(e); 
         const { getValueLength, maxLength, minLength } = this.getProps();
         if (!isFunction(getValueLength)) {
             return;
@@ -108,6 +113,10 @@ export default class TextAreaFoundation extends BaseFoundation<TextAreaAdapter>
             this.handleVisibleMinLength(value);
         } 
     }
+    
+    handleCompositionUpdate = (e) => {
+        this._adapter.notifyCompositionUpdate(e);
+    }
 
     /**
      * Modify minLength to trigger browser check for minimum length

+ 27 - 0
packages/semi-ui/input/__test__/input.test.js

@@ -282,4 +282,31 @@ describe('Input', () => {
     expect(inputGroup.find('input').at(0).instance().disabled).toBe(false);
     expect(inputGroup.find('input').at(1).instance().disabled).toBe(true);
   })
+
+  it('test onCompositionStart callback', () => {
+    const spyOnCompositionStart = sinon.spy();
+    const input = mount(<Input onCompositionStart={spyOnCompositionStart} />);
+    const inputDom = input.find('input');
+    
+    inputDom.simulate('compositionstart', { target: { value: 'test' } });
+    expect(spyOnCompositionStart.calledOnce).toBe(true);
+  });
+
+  it('test onCompositionEnd callback', () => {
+      const spyOnCompositionEnd = sinon.spy();
+      const input =  mount(<Input onCompositionEnd={spyOnCompositionEnd} />);
+      const inputDom = input.find('input');
+      
+      inputDom.simulate('compositionend', { target: { value: 'test' } });
+      expect(spyOnCompositionEnd.calledOnce).toBe(true);
+  });
+
+  it('test onCompositionUpdate callback', () => {
+      const spyOnCompositionUpdate = sinon.spy();
+      const input =  mount(<Input onCompositionUpdate={spyOnCompositionUpdate} />);
+      const inputDom = input.find('input');
+      
+      inputDom.simulate('compositionupdate', { target: { value: 'test' } });
+      expect(spyOnCompositionUpdate.calledOnce).toBe(true);
+  });
 });

+ 27 - 0
packages/semi-ui/input/__test__/textArea.test.js

@@ -224,4 +224,31 @@ describe('TextArea', () => {
             truncateValue(value, length, expectedCalcTimes);
         }
     });
+
+    it('test onCompositionStart callback', () => {
+        const spyOnCompositionStart = sinon.spy();
+        const textArea = mount(<TextArea onCompositionStart={spyOnCompositionStart} />);
+        const textareaDom = textArea.find('textarea');
+        
+        textareaDom.simulate('compositionstart', { target: { value: 'test' } });
+        expect(spyOnCompositionStart.calledOnce).toBe(true);
+    });
+
+    it('test onCompositionEnd callback', () => {
+        const spyOnCompositionEnd = sinon.spy();
+        const textArea = mount(<TextArea onCompositionEnd={spyOnCompositionEnd} />);
+        const textareaDom = textArea.find('textarea');
+        
+        textareaDom.simulate('compositionend', { target: { value: 'test' } });
+        expect(spyOnCompositionEnd.calledOnce).toBe(true);
+    });
+
+    it('test onCompositionUpdate callback', () => {
+        const spyOnCompositionUpdate = sinon.spy();
+        const textArea = mount(<TextArea onCompositionUpdate={spyOnCompositionUpdate} />);
+        const textareaDom = textArea.find('textarea');
+        
+        textareaDom.simulate('compositionupdate', { target: { value: 'test' } });
+        expect(spyOnCompositionUpdate.calledOnce).toBe(true);
+    });
 });

+ 10 - 0
packages/semi-ui/input/index.tsx

@@ -117,6 +117,9 @@ class Input extends BaseComponent<InputProps, InputState> {
         onKeyUp: PropTypes.func,
         onKeyPress: PropTypes.func,
         onEnterPress: PropTypes.func,
+        onCompositionStart: PropTypes.func,
+        onCompositionEnd: PropTypes.func,
+        onCompositionUpdate: PropTypes.func,
         insetLabel: PropTypes.node,
         insetLabelId: PropTypes.string,
         inputStyle: PropTypes.object,
@@ -146,6 +149,9 @@ class Input extends BaseComponent<InputProps, InputState> {
         onKeyUp: noop,
         onKeyPress: noop,
         onEnterPress: noop,
+        onCompositionStart: noop,
+        onCompositionEnd: noop,
+        onCompositionUpdate: noop,
         validateStatus: 'default',
         borderless: false,
     };
@@ -198,6 +204,9 @@ class Input extends BaseComponent<InputProps, InputState> {
             notifyKeyUp: (e: React.KeyboardEvent<HTMLInputElement>) => this.props.onKeyUp(e),
             notifyEnterPress: (e: React.KeyboardEvent<HTMLInputElement>) => this.props.onEnterPress(e),
             notifyClear: (e: React.MouseEvent<HTMLDivElement>) => this.props.onClear(e),
+            notifyCompositionStart: (e: React.CompositionEvent<HTMLInputElement>) => this.props.onCompositionStart(e),
+            notifyCompositionEnd: (e: React.CompositionEvent<HTMLInputElement>) => this.props.onCompositionEnd(e),
+            notifyCompositionUpdate: (e: React.CompositionEvent<HTMLInputElement>) => this.props.onCompositionUpdate(e),
             setMinLength: (minLength: number) => this.setState({ minLength }),
             isEventTarget: (e: React.MouseEvent) => e && e.target === e.currentTarget,
         };
@@ -505,6 +514,7 @@ class Input extends BaseComponent<InputProps, InputState> {
             onKeyPress: e => this.foundation.handleKeyPress(e),
             onCompositionStart: this.foundation.handleCompositionStart,
             onCompositionEnd: this.foundation.handleCompositionEnd,
+            onCompositionUpdate: this.foundation.handleCompositionUpdate,
             value: inputValue,
         };
         if (!isFunction(getValueLength)) {

+ 10 - 0
packages/semi-ui/input/textarea.tsx

@@ -89,6 +89,9 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         showClear: PropTypes.bool,
         onClear: PropTypes.func,
         onResize: PropTypes.func,
+        onCompositionStart: PropTypes.func,
+        onCompositionEnd: PropTypes.func,
+        onCompositionUpdate: PropTypes.func,
         getValueLength: PropTypes.func,
         disabledEnterStartNewLine: PropTypes.bool,
         // TODO
@@ -109,6 +112,9 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
         onKeyDown: noop,
         onResize: noop,
         onClear: noop,
+        onCompositionStart: noop,
+        onCompositionEnd: noop,
+        onCompositionUpdate: noop,
         // resize: false,
     };
 
@@ -163,6 +169,9 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
             notifyPressEnter: (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
                 this.props.onEnterPress && this.props.onEnterPress(e);
             },
+            notifyCompositionStart: (e: React.CompositionEvent<HTMLTextAreaElement>) => this.props.onCompositionStart(e),
+            notifyCompositionEnd: (e: React.CompositionEvent<HTMLTextAreaElement>) => this.props.onCompositionEnd(e),
+            notifyCompositionUpdate: (e: React.CompositionEvent<HTMLTextAreaElement>) => this.props.onCompositionUpdate(e),
             setMinLength: (minLength: number) => this.setState({ minLength }),
         };
     }
@@ -303,6 +312,7 @@ class TextArea extends BaseComponent<TextAreaProps, TextAreaState> {
             value: value === null || value === undefined ? '' : value,
             onCompositionStart: this.foundation.handleCompositionStart,
             onCompositionEnd: this.foundation.handleCompositionEnd,
+            onCompositionUpdate: this.foundation.handleCompositionUpdate,
         };
         if (!isFunction(getValueLength)) {
             (itemProps as any).maxLength = maxLength;