From f25c5d9a64e483bdadd8f35aa683cc666b99ae65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Thu, 4 Aug 2016 22:11:23 +0200 Subject: [PATCH] Add handler "onBeforeChange" (#219) * Add handler "onReceiveState" * Change onReceiveChange to onBeforeChange and call it before onChange as well * Dont'call props.onBeforeChange since it's added to CorePlugin * Update documentation for onBeforeChange --- docs/reference/plugins/plugins.md | 23 +++++++++++++++-------- lib/components/editor.js | 27 +++++++++++++++++++++++++-- lib/plugins/core.js | 5 ++++- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/docs/reference/plugins/plugins.md b/docs/reference/plugins/plugins.md index 56fa166f3..ac8bdf95b 100644 --- a/docs/reference/plugins/plugins.md +++ b/docs/reference/plugins/plugins.md @@ -23,7 +23,7 @@ When the editor needs to resolve a plugin-related handler, it will loop through - [`renderNode`](#rendernode) - [Other Properties](#other-properties) - [`onChange`](#onchange) - + - [`onBeforeChange`](#onbeforechange) ## Conventions @@ -57,7 +57,7 @@ All of the event handler properties are passed the same React `event` object you Each event handler can choose to return a new `state` object, in which case the editor's state will be updated. If nothing is returned, the editor will simply continue resolving the plugin stack. -### `onBeforeInput` +### `onBeforeInput` `Function onBeforeInput(event: Event, data: Object, state: State, editor: Editor) => State || Void` This handler is called right before a string of text is inserted into the `contenteditable` element. @@ -124,12 +124,12 @@ The `data` object is a convenience object created to standardize the drop metada If no other plugin handles this event, it will be handled by the [Core plugin](./core.md). -### `onKeyDown` +### `onKeyDown` `Function onKeyDown(event: Event, data: Object, state: State, editor: Editor) => State || Void` This handler is called when any key is pressed in the `contenteditable` element, before any action is taken. -The `data` object contains the `key` which is a string name of the key that was pressed, as well as it's `code`. It also contains a series of helpful utility properties for determining hotkey logic. For example, `isCtrl` which is true if the control key was pressed, or +The `data` object contains the `key` which is a string name of the key that was pressed, as well as it's `code`. It also contains a series of helpful utility properties for determining hotkey logic. For example, `isCtrl` which is true if the control key was pressed, or ```js { @@ -201,14 +201,14 @@ If no other plugin handles this event, it will be handled by the [Core plugin](. To customize the renderer output of the editor, plugins can define a set of "renderer" properties. -### `renderDecorations` +### `renderDecorations` `Function renderDecorations(text: Text, state: State, editor: Editor) => Characters || Void` The `renderDecorations` handler allows you to add dynamic, content-aware [`Marks`](../models/mark.md) to ranges of text, without having them show up in the serialized state of the editor. This is useful for things like code highlighting, where the marks will change as the user types. `renderDecorations` is called for every `text` node in the document, and should return a set of updated [`Characters`](../models/character.md) for the text node in question. Every plugin's decoration logic is called, and the resulting characters are unioned, such that multiple plugins can apply decorations to the same pieces of text. -### `renderMark` +### `renderMark` `Function renderMark(mark: Mark, marks: Set, state: State, editor: Editor) => Component || Object || String || Void` The `renderMark` handler allows you to define the styles that each mark should be rendered with. It is passed a `mark` and the set of `marks`, and should return either a React component, an object of styles, or a class string. For example any of these are valid return values: @@ -239,7 +239,7 @@ function renderMark(mark) { } ``` -### `renderNode` +### `renderNode` `Function renderNode(node: Block || Inline, state: State, editor: Editor) => Component || Void` The `renderNode` handler allows you to define the component that will be used to render a node—both blocks and inlines. It takes a [`Node`](../models/node.md) object, and should return a React component. @@ -285,7 +285,7 @@ The `node` itself is passed in, so you can access any custom data associated wit } ``` -### `onChange` +### `onChange` `Function onChange(state: State) => State || Void` The `onChange` handler isn't a native browser event handler. Instead, it is invoked whenever the editor state changes. Returning a new state will update the editor's state, continuing down the plugin stack. @@ -293,3 +293,10 @@ The `onChange` handler isn't a native browser event handler. Instead, it is invo Unlike the native event handlers, results from the `onChange` handler **are cummulative**! This means that every plugin in the stack that defines an `onChange` handler will have its handler resolved for every change the editor makes; the editor will not return early after the first plugin's handler is called. This allows you to stack up changes across the entire plugin stack. + +### `onBeforeChange` +`Function onBeforeChange(state: State) => State || Void` + +The `onBeforeChange` handler isn't a native browser event handler. Instead, it is invoked whenever the editor receives a new state and before propagating a new state to `onChange`. Returning a new state will update the editor's state before rendering, continuing down the plugin stack. + +Like `onChange`, `onBeforeChange` is cummulative. diff --git a/lib/components/editor.js b/lib/components/editor.js index a01520529..ddf950a2c 100644 --- a/lib/components/editor.js +++ b/lib/components/editor.js @@ -51,6 +51,7 @@ class Editor extends React.Component { static propTypes = { className: React.PropTypes.string, + onBeforeChange: React.PropTypes.func, onChange: React.PropTypes.func.isRequired, onDocumentChange: React.PropTypes.func, onSelectionChange: React.PropTypes.func, @@ -90,7 +91,7 @@ class Editor extends React.Component { this.tmp = {} this.state = {} this.state.plugins = this.resolvePlugins(props) - this.state.state = props.state + this.state.state = this.onBeforeChange(props.state) // Mix in the event handlers. for (const method of EVENT_HANDLERS) { @@ -107,7 +108,7 @@ class Editor extends React.Component { */ componentWillReceiveProps = (props) => { - this.state.state = props.state + this.state.state = this.onBeforeChange(props.state) if (props.plugins != this.props.plugins) { this.setState({ plugins: this.resolvePlugins(props) }) @@ -159,6 +160,8 @@ class Editor extends React.Component { onChange = (state) => { if (state == this.state.state) return + state = this.onBeforeChange(state) + for (const plugin of this.state.plugins) { if (!plugin.onChange) continue const newState = plugin.onChange(state, this) @@ -179,6 +182,26 @@ class Editor extends React.Component { } } + /** + * When the editor receives a new 'state' + * + * @param {State} state + * @return {State} newState + */ + + onBeforeChange = (state) => { + if (state == this.state.state) return + + for (const plugin of this.state.plugins) { + if (!plugin.onBeforeChange) continue + const newState = plugin.onBeforeChange(state, this) + if (newState == null) continue + state = newState + } + + return state + } + /** * When an event by `name` fires, pass it through the plugins, and update the * state if one of them chooses to. diff --git a/lib/plugins/core.js b/lib/plugins/core.js index 4a545e72b..eca5832dc 100644 --- a/lib/plugins/core.js +++ b/lib/plugins/core.js @@ -21,6 +21,7 @@ const debug = Debug('slate:core') * @property {Element} placeholder * @property {String} placeholderClassName * @property {Object} placeholderStyle + * @property {Function} onBeforeChange * @return {Object} */ @@ -28,7 +29,8 @@ function Plugin(options = {}) { const { placeholder, placeholderClassName, - placeholderStyle + placeholderStyle, + onBeforeChange } = options /** @@ -612,6 +614,7 @@ function Plugin(options = {}) { */ return { + onBeforeChange, onBeforeInput, onBlur, onCopy,