Skip to content

Commit

Permalink
#37
Browse files Browse the repository at this point in the history
  • Loading branch information
scniro committed Mar 14, 2018
1 parent 02bc9dd commit 57bfc14
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 30 deletions.
41 changes: 19 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,16 @@ require('codemirror/mode/javascript/javascript');

## props

- `autoCursor`
> `boolean` if `false`, allow the defaulted internal codemirror cursor position to reset should a new `value` prop be set. Default: `true`
- `autoFocus`
> `boolean` if `true`, set focus to the instance `onSet`. Will be invoked within the `componentDidMount` lifecycle stage. Default: `false`
- `autoScroll`
> `boolean` if `true`, scroll the cursor position into view automatically. Default: `false`
- `className` - sets `class="react-codemirror2 yourClassName"`
- `defineMode`
> pass a custom mode object `{name: 'custom', fn: myModeFn}`
- `options` - see codemirror [configuration](https://codemirror.net/doc/manual.html#config)
- `value` - set component value through props
> must be managed for controlled components
| prop | type *`default`* | components | description |
|--------------|-------------------|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| `autoCursor` | boolean *`true`* | `Controlled` `UnControlled` | should component cursor position correct when `value` changed |
| `autoFocus` | boolean *`false`* | `Controlled` `UnControlled` | should component focus on mount |
| `autoScroll` | boolean *`true`* | `Controlled` `UnControlled` | should component scroll cursor position into view when `value` changed |
| `className` | string | `Controlled` `UnControlled` | pass through class *`class="react-codemirror2 className"`* |
| `defineMode` | object | `Controlled` `UnControlled` | pass a [custom mode](http://marijnhaverbeke.nl/blog/codemirror-mode-system.html) via `{name: 'custom', fn: myModeFn}` |
| `detach` | boolean | `UnControlled` | should component ignore new props |
| `options` | object | `Controlled` `UnControlled` | [codemirror configuration](https://codemirror.net/doc/manual.html#config) |
| `value` | string | _**`Controlled`**_ `UnControlled` | component value _**must be managed for controlled components**_ |

## props cont. (wrapped codemirror [programming api](https://codemirror.net/doc/manual.html#api))

Expand Down Expand Up @@ -123,16 +121,15 @@ require('codemirror/mode/javascript/javascript');

## events

- `editorDidConfigure(editor)`
- `editorDidMount(editor, next)`
> invoking optional `next()` will trigger `editorDidConfigure`
- `editorWillMount()`
- `editorWillUnmount(editor)`
- `onBeforeChange(editor, data, value)` **[controlled]**
> required - hook to manage state and update `value`
- `onBeforeChange(editor, data, value, next)` **[uncontrolled]**
> optional - if defined, `next()` must be invoked to trigger `onChange`.
- `onChange(editor, data, value)`
| event | returns | components | description |
|----------------------|---------------------------------|-----------------------------------|-------------------------------------------------------------------------------------------------------------|
| `editorDidAttach` | editor | `UnControlled` | component is now **responding** to new props |
| `editorDidConfigure` | editor | `Controlled` `UnControlled` | component configuration has been set |
| `editorDidDetach` | editor | `UnControlled` | component is now **ignoring** new props |
| `editorDidMount` | editor, _**next**_ | `Controlled` `UnControlled` | **invoking optional `next` will trigger `editorDidConfigure`** |
| `editorWillUnmount` | editor | `Controlled` `UnControlled` | invoked before [`componentWillUnmount`](https://reactjs.org/docs/react-component.html#componentwillunmount) |
| `onBeforeChange` | editor, data, value, _**next**_ | `Controlled` _**`UnControlled`**_ | if used, `next` is returned via `UnControlled` and *must* be invoked to trigger onChange |
| `onChange` | editor, data, value | `Controlled` `UnControlled` | the component value has been changed |

## events cont. [wrapped codemirror events](https://codemirror.net/doc/manual.html#events)

Expand Down
2 changes: 1 addition & 1 deletion docs/app.min.js

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion docs/src/components/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ class Editor extends React.Component {
} else {
return (
<UnControlled
detachOnMount={true}
value={this.getUncontrolledValue(this.props.mode)}
defineMode={{name: 'strings', fn: sampleMode}}
detach={this.state.detach}
options={{
mode: this.props.mode,
theme: this.props.theme,
Expand All @@ -131,7 +133,11 @@ class Editor extends React.Component {
}

render() {
return this.renderEditor(this.props.controlled)
return (
<div>
{this.renderEditor(this.props.controlled)}
</div>
)
}
}

Expand Down
3 changes: 3 additions & 0 deletions index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 21 additions & 2 deletions index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 33 additions & 4 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export interface IInstance extends codemirror.Editor, IDoc {
/* </tshacks> */

export interface ICodeMirror {

autoCursor?: boolean; // default: true
autoFocus?: boolean; // default: false
autoScroll?: boolean; // default: false
Expand Down Expand Up @@ -83,6 +82,9 @@ export interface IControlledCodeMirror extends ICodeMirror {
}

export interface IUnControlledCodeMirror extends ICodeMirror {
detach?: boolean;
editorDidAttach?: (editor: IInstance) => void;
editorDidDetach?: (editor: IInstance) => void;
onBeforeChange?: (editor: IInstance, data: codemirror.EditorChange, value: string, next: () => void) => void;
value?: string;
}
Expand Down Expand Up @@ -526,7 +528,7 @@ export class Controlled extends React.Component<IControlledCodeMirror, any> {

this.hydrate(nextProps);

if(!this.appliedNext) {
if (!this.appliedNext) {
this.shared.applyNext(this.props, nextProps, preserved);
this.appliedNext = true;
}
Expand Down Expand Up @@ -572,6 +574,8 @@ export class UnControlled extends React.Component<IUnControlledCodeMirror, any>
/** @internal */
private continueChange: boolean;
/** @internal */
private detached: boolean;
/** @internal */
private editor: IInstance;
/** @internal */
private hydrated: boolean;
Expand All @@ -595,6 +599,7 @@ export class UnControlled extends React.Component<IUnControlledCodeMirror, any>
this.applied = false;
this.appliedUserDefined = false;
this.continueChange = false;
this.detached = false;
this.hydrated = false;
this.initCb = () => {
if (this.props.editorDidConfigure) {
Expand Down Expand Up @@ -650,6 +655,8 @@ export class UnControlled extends React.Component<IUnControlledCodeMirror, any>

if (SERVER_RENDERED) return;

this.detached = (this.props.detach === true);

if (this.props.defineMode) {
if (this.props.defineMode.name && this.props.defineMode.fn) {
cm.defineMode(this.props.defineMode.name, this.props.defineMode.fn);
Expand Down Expand Up @@ -716,7 +723,23 @@ export class UnControlled extends React.Component<IUnControlledCodeMirror, any>
/** @internal */
public componentWillReceiveProps(nextProps) {

if (SERVER_RENDERED) return;
if(this.detached && (nextProps.detach === false)) {
this.detached = false;
if (this.props.editorDidAttach) {
this.props.editorDidAttach(this.editor);
}
}

if(!this.detached && (nextProps.detach === true)) {
this.detached = true;
if (this.props.editorDidDetach) {
this.props.editorDidDetach(this.editor);
}
}



if (SERVER_RENDERED || this.detached) return;

let preserved: IPreservedOptions = {cursor: null};

Expand Down Expand Up @@ -755,7 +778,13 @@ export class UnControlled extends React.Component<IUnControlledCodeMirror, any>

/** @internal */
public shouldComponentUpdate(nextProps, nextState) {
return !SERVER_RENDERED

let update = true;

if (SERVER_RENDERED) update = false;
if (this.detached) update = false;

return update;
}

/** @internal */
Expand Down
85 changes: 85 additions & 0 deletions test/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -884,5 +884,90 @@ describe('Props', () => {

expect(editor.state.focused).toBeTruthy();
});

it('[UnControlled]: detached | should detach', () => {
const spy = sinon.spy();
const wrapper = Enzyme.mount(
<UnControlled detach={false} editorDidDetach={() => spy()}/>
);

expect(spy.called).toBeFalsy();
wrapper.setProps({detach: true});
expect(spy.called).toBeTruthy();
wrapper.unmount();
});

it('[UnControlled]: detached | should attach', () => {
const spy = sinon.spy();
const wrapper = Enzyme.mount(
<UnControlled detach={true} editorDidAttach={() => spy()}/>
);

expect(spy.called).toBeFalsy();
wrapper.setProps({detach: false});
expect(spy.called).toBeTruthy();
wrapper.unmount();
});

it('[UnControlled]: detached:false | should update', done => {
let instance;
const spy = sinon.spy();
const wrapper = Enzyme.mount(
<UnControlled
editorDidMount={editor => instance = editor}/>
);

instance.on('optionChange', () => spy());

wrapper.setProps({options: {lineNumbers: true}});

// force lose `.on` race
setTimeout(() => {
expect(spy.called).toBeTruthy();
wrapper.unmount();
done();
}, 200);
});

it('[UnControlled]: detached:false | should *not* update | on mount', done => {
let instance;
const spy = sinon.spy();
const wrapper = Enzyme.mount(
<UnControlled
detach={true}
editorDidMount={editor => instance = editor}/>
);

instance.on('optionChange', () => spy());

wrapper.setProps({options: {lineNumbers: true}});

// force lose `.on` race
setTimeout(() => {
expect(spy.called).toBeFalsy();
wrapper.unmount();
done();
}, 200);
});

it('[UnControlled]: detached:false | should *not* update | on props', done => {
let instance;
const spy = sinon.spy();
const wrapper = Enzyme.mount(
<UnControlled
editorDidMount={editor => instance = editor}/>
);

instance.on('optionChange', () => spy());

wrapper.setProps({detach: true, options: {lineNumbers: true}});

// force lose `.on` race
setTimeout(() => {
expect(instance.getOption('lineNumbers')).toBeFalsy();
wrapper.unmount();
done();
}, 200);
});
// </misc>
});

0 comments on commit 57bfc14

Please sign in to comment.