@extjs/reactor
Version:
Use Ext JS components in React.
985 lines (788 loc) • 35.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _ReactComponentEnvironment = require('react-dom/lib/ReactComponentEnvironment');
var _ReactComponentEnvironment2 = _interopRequireDefault(_ReactComponentEnvironment);
var _react = require('react');
var _ReactMultiChild = require('react-dom/lib/ReactMultiChild');
var _ReactMultiChild2 = _interopRequireDefault(_ReactMultiChild);
var _DOMLazyTree = require('react-dom/lib/DOMLazyTree');
var _DOMLazyTree2 = _interopRequireDefault(_DOMLazyTree);
var _ReactDOMComponentTree = require('react-dom/lib/ReactDOMComponentTree');
var _ReactDOMComponentFlags = require('react-dom/lib/ReactDOMComponentFlags');
var _ReactDOMComponentFlags2 = _interopRequireDefault(_ReactDOMComponentFlags);
var _lodash = require('lodash.union');
var _lodash2 = _interopRequireDefault(_lodash);
var _lodash3 = require('lodash.capitalize');
var _lodash4 = _interopRequireDefault(_lodash3);
var _lodash5 = require('lodash.defaults');
var _lodash6 = _interopRequireDefault(_lodash5);
var _lodash7 = require('lodash.clonedeepwith');
var _lodash8 = _interopRequireDefault(_lodash7);
var _lodash9 = require('lodash.isequal');
var _lodash10 = _interopRequireDefault(_lodash9);
var _toJSON2 = require('./toJSON');
var _toJSON3 = _interopRequireDefault(_toJSON2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// need to ensure ReactDOM is loaded before patching ReactComponentEnvironment.replaceNodeWithMarkup
var Ext = window.Ext;
var CLASS_CACHE = {
Grid: Ext.ClassManager.getByAlias('widget.grid'),
Column: Ext.ClassManager.getByAlias('widget.gridcolumn'),
Button: Ext.ClassManager.getByAlias('widget.button'),
Menu: Ext.ClassManager.getByAlias('widget.menu'),
ToolTip: Ext.ClassManager.getByAlias('widget.tooltip'),
CellBase: Ext.ClassManager.get('Ext.grid.cell.Base'),
WidgetCell: Ext.ClassManager.getByAlias('widget.widgetcell'),
Dialog: Ext.ClassManager.getByAlias('widget.dialog'),
Field: Ext.ClassManager.getByAlias('widget.field'),
FitLayout: Ext.ClassManager.getByAlias('layout.fit'),
TabPanel: Ext.ClassManager.getByAlias('widget.tabpanel'),
RendererCell: Ext.ClassManager.getByAlias('widget.renderercell')
};
var ExtJSComponent = function (_Component) {
(0, _inherits3.default)(ExtJSComponent, _Component);
function ExtJSComponent(element) {
(0, _classCallCheck3.default)(this, ExtJSComponent);
var _this = (0, _possibleConstructorReturn3.default)(this, (ExtJSComponent.__proto__ || (0, _getPrototypeOf2.default)(ExtJSComponent)).call(this, element));
_this.cmp = null;
_this.el = null;
_this._flags = null;
_this._hostNode = null;
_this._hostParent = null;
_this._renderedChildren = null;
_this._hostContainerInfo = null;
_this._currentElement = element;
_this._topLevelWrapper = null;
_this.displayName = 'ExtJSComponent';
_this.unmountSafely = false;
// needed for serializing jest snapshots when using react-test-renderer
if (process.env.NODE_ENV === 'test') {
_this._renderedNodeType = _toJSON2.ReactNodeTypes.HOST; // HOST
_this._renderedComponent = {
toJSON: function toJSON() {
return (0, _toJSON3.default)(_this);
}
};
}
return _this;
}
// begin React renderer methods
/**
* Creates an Ext JS component.
* This is needed by the React rendering API
* @param transaction
* @param nativeParent
* @param nativeContainerInfo
* @param context
* @returns {null|*}
*/
(0, _createClass3.default)(ExtJSComponent, [{
key: 'mountComponent',
value: function mountComponent(transaction, nativeParent, nativeContainerInfo, context) {
var _this2 = this;
var element = this._currentElement;
var renderToDOMNode = void 0;
if (nativeParent instanceof ExtJSComponent) {
this._hostContainerInfo = nativeParent._hostContainerInfo; // propagate _hostContainerInfo - this is needed to render dom elements inside Ext JS components
} else if (nativeParent) {
this._hostContainerInfo = nativeParent._hostContainerInfo; // propagate _hostContainerInfo - this is needed to render dom elements inside Ext JS components
renderToDOMNode = nativeParent._hostNode;
} else {
this._hostContainerInfo = nativeContainerInfo;
renderToDOMNode = nativeContainerInfo._node;
}
this._hostParent = nativeParent; // this is needed by ReactDOMComponentTree#getNodeFromInstance
var config = this._createInitialConfig(element, transaction, context);
var result = void 0;
if (renderToDOMNode) {
result = this._renderRootComponent(renderToDOMNode, config);
} else {
result = this.cmp = this.createExtJSComponent(config);
}
// this allows React internals to get the mounted instance for debug tools when using dangerouslyReplaceNodeWithMarkup
// this is probably not needed in fiber
if (!result.node) Object.defineProperty(result, 'node', {
get: function get() {
return _this2.el;
}
});
// Ensure that componentWillUnmount is called on children.
// We wait until the Ext JS component is destroyed rather than calling unmountChildren in unmountComponent
// so that we don't unmount children during a Transition's animation.
this.cmp.on('destroy', function () {
_this2.unmountChildren(_this2.unmountSafely);
});
this._precacheNode();
return result;
}
/**
* Updates the component
* @param nextComponent
* @param transaction
* @param context
*/
}, {
key: 'receiveComponent',
value: function receiveComponent(nextComponent, transaction, context) {
if (!this.cmp || this.cmp.destroyed) return;
var props = nextComponent.props;
this._rushProps(this._currentElement.props, props);
this.updateChildren(this._applyDefaults(props), transaction, context);
this._applyProps(this._currentElement.props, props);
this._currentElement = nextComponent;
}
/**
* Destroys the component
*/
}, {
key: 'unmountComponent',
value: function unmountComponent(safely) {
this.unmountSafely = safely;
if (this.cmp) {
if (this.cmp.destroying || this.cmp.$reactorConfig) return;
var parentCmp = getParentCmp(this.cmp);
// remember the parent and position in parent for dangerouslyReplaceNodeWithMarkup
// this not needed in fiber
var indexInParent = void 0;
if (parentCmp) {
if (parentCmp.indexOf) {
// modern
indexInParent = parentCmp.indexOf(this.cmp);
} else if (parentCmp.items && parentCmp.items.indexOf) {
// classic
indexInParent = parentCmp.items.indexOf(this.cmp);
}
}
if (this.reactorSettings.debug) console.log('destroy', this.cmp.$className);
if (Ext.navigation && Ext.navigation.View && parentCmp && parentCmp instanceof Ext.navigation.View) {
parentCmp.pop();
} else {
this.cmp.destroy();
}
// remember the parent and position in parent for dangerouslyReplaceNodeWithMarkup
// this not needed in fiber
this.el._extIndexInParent = indexInParent;
this.el._extParent = parentCmp;
}
}
/**
* Returns the Ext JS component instance
*/
}, {
key: 'getHostNode',
value: function getHostNode() {
return this.el;
}
/**
* Returns the Ext JS component instance
*/
}, {
key: 'getPublicInstance',
value: function getPublicInstance() {
return this.cmp;
}
// end react renderer methods
}, {
key: '_renderRootComponent',
value: function _renderRootComponent(renderToDOMNode, config) {
var _this3 = this;
(0, _lodash6.default)(config, {
height: '100%',
width: '100%'
});
config.renderTo = renderToDOMNode;
this.cmp = this.createExtJSComponent(config);
if (Ext.isClassic) {
this.cmp.el.on('resize', function () {
return _this3.cmp && _this3.cmp.updateLayout();
});
this.el = this.cmp.el.dom;
} else {
this.el = this.cmp.renderElement.dom;
}
return { node: this.el, children: [] };
}
}, {
key: '_applyDefaults',
value: function _applyDefaults(_ref) {
var defaults = _ref.defaults,
children = _ref.children;
if (defaults) {
return _react.Children.map(children, function (child) {
if (child.type.prototype instanceof ExtJSComponent) {
return (0, _react.cloneElement)(child, (0, _extends3.default)({}, defaults, child.props));
} else {
return child;
}
});
} else {
return children;
}
}
/**
* Creates an Ext JS component config from react element props
* @private
*/
}, {
key: '_createInitialConfig',
value: function _createInitialConfig(element, transaction, context) {
var type = element.type,
props = element.props;
var config = this._createConfig(props, true);
this._ensureResponsivePlugin(config);
var items = [],
dockedItems = [];
if (props.children) {
var children = this.mountChildren(this._applyDefaults(props), transaction, context);
for (var i = 0; i < children.length; i++) {
var _item = children[i];
if (_item instanceof Ext.Base) {
var prop = this._propForChildElement(_item);
if (prop) {
_item.$reactorConfig = true;
var value = config;
if (prop.array) {
var array = config[prop.name];
if (!array) array = config[prop.name] = [];
array.push(_item);
} else {
config[prop.name] = prop.value || _item;
}
} else {
(_item.dock ? dockedItems : items).push(_item);
}
} else if (_item.node) {
items.push(wrapDOMElement(_item));
} else if (typeof _item === 'string') {
// will get here when rendering html elements in react-test-renderer
// no need to do anything
} else {
throw new Error('Could not render child item: ' + _item);
}
}
}
if (items.length) config.items = items;
if (dockedItems.length) config.dockedItems = dockedItems;
return config;
}
/**
* Determines whether a child element corresponds to a config or a container item based on the presence of a rel config or
* matching other known relationships
* @param {Ext.Base} item
*/
}, {
key: '_propForChildElement',
value: function _propForChildElement(item) {
if (item.config && item.config.rel) {
if (typeof item.config.rel === 'string') {
return { name: item.config.rel };
} else {
return item.config.rel;
}
}
var extJSClass = this.extJSClass;
if (isAssignableFrom(extJSClass, CLASS_CACHE.Button) && CLASS_CACHE.Menu && item instanceof CLASS_CACHE.Menu) {
return { name: 'menu', array: false };
} else if (isAssignableFrom(extJSClass, Ext.Component) && CLASS_CACHE.ToolTip && item instanceof CLASS_CACHE.ToolTip) {
return { name: 'tooltip', array: false };
} else if (CLASS_CACHE.Column && item instanceof CLASS_CACHE.Column) {
return { name: 'columns', array: true };
} else if (isAssignableFrom(extJSClass, CLASS_CACHE.Column) && CLASS_CACHE.CellBase && item instanceof CLASS_CACHE.CellBase) {
return { name: 'cell', array: false, value: this._cloneConfig(item) };
} else if (isAssignableFrom(extJSClass, CLASS_CACHE.WidgetCell)) {
return { name: 'widget', array: false, value: this._cloneConfig(item) };
} else if (isAssignableFrom(extJSClass, CLASS_CACHE.Dialog) && CLASS_CACHE.Button && item instanceof CLASS_CACHE.Button) {
return { name: 'buttons', array: true };
} else if (isAssignableFrom(extJSClass, CLASS_CACHE.Column) && CLASS_CACHE.Field && item instanceof CLASS_CACHE.Field) {
return { name: 'editor', array: false, value: this._cloneConfig(item) };
}
}
}, {
key: '_cloneConfig',
value: function _cloneConfig(item) {
return (0, _extends3.default)({}, item.initialConfig, { xclass: item.$className });
}
/**
* If the propName corresponds to an event listener (starts with "on" followed by a capital letter), returns the name of the event.
* @param {String} propName
* @param {String}
*/
}, {
key: '_eventNameForProp',
value: function _eventNameForProp(propName) {
if (propName.match(/^on[A-Z]/)) {
return propName.slice(2).toLowerCase();
} else {
return null;
}
}
/**
* Creates an Ext config object for this specified props
* @param {Object} props
* @param {Boolean} [includeEvents] true to convert on* props to listeners, false to exclude them
* @private
*/
}, {
key: '_createConfig',
value: function _createConfig(props, includeEvents) {
props = this._cloneProps(props);
var config = {};
if (includeEvents) config.listeners = {};
for (var key in props) {
if (props.hasOwnProperty(key)) {
var value = props[key];
var eventName = this._eventNameForProp(key);
if (eventName) {
if (value && includeEvents) config.listeners[eventName] = value;
} else if (key === 'config') {
(0, _assign2.default)(config, value);
} else if (key !== 'children' && key !== 'defaults') {
config[key.replace(/className/, 'cls')] = value;
}
}
}
var extJSClass = this.extJSClass;
if (isAssignableFrom(extJSClass, CLASS_CACHE.Column) && typeof config.renderer === 'function' && CLASS_CACHE.RendererCell) {
config.cell = config.cell || {};
config.cell.xtype = 'renderercell';
}
return config;
}
}, {
key: '_ensureResponsivePlugin',
value: function _ensureResponsivePlugin(config) {
if (config.responsiveConfig) {
var plugins = config.plugins;
if (plugins == null) {
config.plugins = 'responsive';
} else if (Array.isArray(plugins) && plugins.indexOf('responsive') === -1) {
plugins.push('responsive');
} else if (typeof plugins === 'string') {
if (plugins !== 'responsive') {
config.plugins = [plugins, 'responsive'];
}
} else if (!plugins.resposive) {
plugins.responsive = true;
}
}
}
/**
* Cloning props rather than passing them directly on as configs fixes issues where Ext JS mutates configs during
* component initialization. One example of this is grid columns get $initParent added when the grid initializes.
* @param {Object} props
* @private
*/
}, {
key: '_cloneProps',
value: function _cloneProps(props) {
return (0, _lodash8.default)(props, function (value) {
if (value instanceof Ext.Base || typeof value === 'function') {
return value;
}
});
}
}, {
key: '_rushProps',
value: function _rushProps(oldProps, newProps) {
var rushConfigs = this.extJSClass.__reactorUpdateConfigsBeforeChildren;
if (!rushConfigs) return;
var oldConfigs = {},
newConfigs = {};
for (var name in rushConfigs) {
oldConfigs[name] = oldProps[name];
newConfigs[name] = newProps[name];
}
this._applyProps(oldConfigs, newConfigs);
}
/**
* Calls config setters for all react props that have changed
* @private
*/
}, {
key: '_applyProps',
value: function _applyProps(oldProps, props) {
var keys = (0, _lodash2.default)((0, _keys2.default)(oldProps), (0, _keys2.default)(props));
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(keys), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var key = _step.value;
var oldValue = oldProps[key],
newValue = props[key];
if (key === 'children') continue;
if (!(0, _lodash10.default)(oldValue, newValue)) {
var eventName = this._eventNameForProp(key);
if (eventName) {
this._replaceEvent(eventName, oldValue, newValue);
} else {
var setter = this._setterFor(key);
if (setter) {
var value = this._cloneProps(newValue);
if (this.reactorSettings.debug) console.log(setter, newValue);
this.cmp[setter](value);
}
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
/**
* Detaches the old event listener and adds the new one.
* @param {String} eventName
* @param {Function} oldHandler
* @param {Function} newHandler
*/
}, {
key: '_replaceEvent',
value: function _replaceEvent(eventName, oldHandler, newHandler) {
if (oldHandler) {
if (this.reactorSettings.debug) console.log('detaching old listener for ' + eventName);
this.cmp.un(eventName, oldHandler);
}
if (this.reactorSettings.debug) console.log('attaching new listener for ' + eventName);
this.cmp.on(eventName, newHandler);
}
/**
* Returns the name of the setter method for a given prop.
* @param {String} prop
*/
}, {
key: '_setterFor',
value: function _setterFor(prop) {
if (prop === 'className') {
prop = 'cls';
}
var name = 'set' + this._capitalize(prop);
return this.cmp[name] && name;
}
/**
* Returns the name of a getter for a given prop.
* @param {String} prop
*/
}, {
key: '_getterFor',
value: function _getterFor(prop) {
var name = 'get' + this._capitalize(prop);
return this.cmp[name] && name;
}
/**
* Capitalizes the first letter in the string
* @param {String} str
* @return {String}
* @private
*/
}, {
key: '_capitalize',
value: function _capitalize(str) {
return (0, _lodash4.default)(str[0]) + str.slice(1);
}
}, {
key: '_precacheNode',
value: function _precacheNode() {
this._flags |= _ReactDOMComponentFlags2.default.hasCachedChildNodes;
if (this.el) {
// will get here when rendering root component
(0, _ReactDOMComponentTree.precacheNode)(this, this.el);
} else if (this.cmp.el) {
this._doPrecacheNode();
} else if (Ext.isClassic) {
// we get here when rendering child components due to lazy rendering
this.cmp.on('afterrender', this._doPrecacheNode, this, { single: true });
}
}
}, {
key: '_doPrecacheNode',
value: function _doPrecacheNode() {
this.el = this.cmp.el.dom;
this.el._extCmp = this.cmp;
(0, _ReactDOMComponentTree.precacheNode)(this, this.el);
}
/**
* Returns the child item at the given index, only counting those items which were created by Reactor
* @param {Number} n
*/
}, {
key: '_toReactChildIndex',
value: function _toReactChildIndex(n) {
var items = this.cmp.items;
if (!items) return n;
if (items.items) items = items.items;
var found = 0,
i = void 0,
item = void 0;
for (i = 0; i < items.length; i++) {
item = items[i];
if (item.$createdByReactor && found++ === n) {
return i;
}
}
return i;
}
/**
* Translates and index in props.children to an index within a config value that is an array. Use
* this to determine the position of an item in props.children within the array config to which it is mapped.
* @param {*} prop
* @param {*} indexInChildren
*/
}, {
key: '_toArrayConfigIndex',
value: function _toArrayConfigIndex(prop, indexInChildren) {
var _this4 = this;
var i = 0,
found = 0;
_react.Children.forEach(this.props.children, function (child) {
var propForChild = _this4._propForChildElement(child);
if (propForChild && propForChild.name === prop.name) {
if (i === indexInChildren) return found;
found++;
}
});
return -1;
}
/**
* Updates a config based on a child element
* @param {Object} prop The prop descriptor (name and array)
* @param {Ext.Base} value The value to set
* @param {Number} [index] The index of the child element in props.children
* @param {Boolean} [isArrayDelete=false] True if removing the item from an array
*/
}, {
key: '_mergeConfig',
value: function _mergeConfig(prop, value, index, isArrayDelete) {
var setter = this._setterFor(prop.name);
if (!setter) return;
if (value) value.$reactorConfig = true;
if (prop.array) {
var getter = this._getterFor(prop.name);
if (!getter) return;
var currentValue = this.cmp[getter]() || [];
if (isArrayDelete) {
// delete
value = currentValue.filter(function (item) {
return item !== value;
});
} else if (index !== undefined) {
// move
value = currentValue.filter(function (item) {
return item !== value;
});
value = value.splice(this._toArrayConfigIndex(index, prop), 0, item);
} else {
// append
value = currentValue.concat(value);
}
}
if (this.reactorSettings.debug) console.log(setter, value);
this.cmp[setter](value);
}
}, {
key: '_ignoreChildrenOrder',
value: function _ignoreChildrenOrder() {
// maintaining order in certain components, like Transition's container, can cause problems with animations, _reactorIgnoreOrder gives us a way to opt out in such scenarios
if (this.cmp._reactorIgnoreOrder) return true;
// moving the main child of a container with layout fit causes it to disappear. Instead we do nothing, which
// should be ok because fit containers are not ordered
if (CLASS_CACHE.FitLayout && this.cmp.layout instanceof CLASS_CACHE.FitLayout) return true;
// When tab to the left of the active tab is removed, the left-most tab would always be selected as the tabs to the right are reinserted
if (CLASS_CACHE.TabPanel && this.cmp instanceof CLASS_CACHE.TabPanel) return true;
}
}]);
return ExtJSComponent;
}(_react.Component);
/**
* Extend ReactMultiChild to handle inserting and moving Component instances
* within Ext JS Containers
*/
exports.default = ExtJSComponent;
var ContainerMixin = (0, _assign2.default)({}, _ReactMultiChild2.default.Mixin, {
/**
* Moves a child component to the supplied index.
* @param {ExtJSComponent} child Component to move.
* @param {Component} afterNode The component to move after
* @param {number} toIndex Destination index of the element.
* @param {number} lastIndex Last index visited of the siblings of `child`.
* @protected
*/
moveChild: function moveChild(child, afterNode, toIndex, lastIndex) {
if (this._ignoreChildrenOrder()) return;
if (toIndex === child._mountIndex) return; // only move child if the actual mount index has changed
var childComponent = toComponent(child.cmp || child.getHostNode());
var prop = this._propForChildElement(childComponent);
if (prop) {
this._mergeConfig(prop, childComponent, toIndex);
} else if (childComponent) {
if (childComponent.dock) {
this.cmp.insertDocked(toIndex, childComponent);
} else {
// reordering docked components is known to cause issues in modern
// place items in a container instead
if (childComponent.config && (childComponent.config.docked || childComponent.config.floated || childComponent.config.positioned)) return;
// removing the child first ensures that we get the new index correct
this.cmp.remove(childComponent, false);
var newIndex = this._toReactChildIndex(toIndex);
if (this.reactorSettings.debug) console.log('moving ' + childComponent.$className + ' to position ' + newIndex + ' in ' + this.cmp.$className);
this.cmp.insert(newIndex, childComponent);
}
}
},
/**
* Creates a child component.
* @param {ExtJSComponent} child Component to create.
* @param {Component} afterNode The component to move after
* @param {Component} childNode The component to insert.
* @protected
*/
createChild: function createChild(child, afterNode, childNode) {
var prop = this._propForChildElement(childNode);
if (prop) {
this._mergeConfig(prop, childNode);
} else {
if (!(childNode instanceof Ext.Base)) {
// we're appending a dom node
childNode = wrapDOMElement(childNode);
}
var index = this._toReactChildIndex(child._mountIndex);
if (this.reactorSettings.debug) {
console.log('inserting ' + childNode.$className + ' into ' + this.cmp.$className + ' at position ' + index);
}
this.cmp[childNode.dock ? 'insertDocked' : 'insert'](index, childNode);
}
},
/**
* Removes a child component.
* @param {ExtJSComponent} child Child to remove.
* @param {Ext.Component/HTMLElement} node The node to remove
* @protected
*/
removeChild: function removeChild(child, node) {
var prop = child instanceof ExtJSComponent && this._propForChildElement(child.cmp);
if (prop) {
this._mergeConfig(prop, prop.array ? toComponent(child.cmp) : null, null, true);
} else {
if (node instanceof HTMLElement && node._extCmp && !node._extCmp.destroying) {
if (this.reactorSettings.debug) console.log('removing', node._extCmp.$className);
node._extCmp.destroy();
}
// We don't need to do anything for Ext JS components because a component is automatically removed from it parent when destroyed
}
}
});
/**
* Wraps a dom element in an Ext Component so it can be added as a child item to an Ext Container. We attach
* a reference to the generated Component to the dom element so it can be destroyed later if the dom element
* is removed when rerendering
* @param {Object} node A React node object with node, children, and text
* @returns {Ext.Component}
*/
function wrapDOMElement(node) {
var contentEl = node.node;
var cmp = new Ext.Component({
// We give the wrapper component a class so that developers can reset css
// properties (ex. box-sizing: context-box) for third party components.
cls: 'x-react-element'
});
if (cmp.element) {
// modern
_DOMLazyTree2.default.insertTreeBefore(cmp.element.dom, node);
} else {
// classic
var target = document.createElement('div');
_DOMLazyTree2.default.insertTreeBefore(target, node);
cmp.contentEl = contentEl instanceof HTMLElement ? contentEl : target /* text fragment or comment */;
}
cmp.$createdByReactor = true;
contentEl._extCmp = cmp;
// this is needed for devtools when using dangerouslyReplaceNodeWithMarkup
// this not needed in fiber
cmp.node = contentEl;
return cmp;
}
/**
* Returns the Ext Component corresponding to the given node
* @param {Ext.Component/HTMLElement/DocumentFragment} node
* @returns {Ext.Component}
*/
function toComponent(node) {
if (node instanceof Ext.Base) {
return node;
} else if (node) {
return node._extCmp;
}
}
/**
* Returns true if subClass is parentClass or a sub class of parentClass
* @param {Ext.Class} subClass
* @param {Ext.Class} parentClass
* @return {Boolean}
*/
function isAssignableFrom(subClass, parentClass) {
if (!subClass || !parentClass) return false;
return subClass === parentClass || subClass.prototype instanceof parentClass;
}
/**
* Returns the parent component in both modern and classic toolkits
* @param {Ext.Component} cmp The child component
*/
function getParentCmp(cmp) {
if (cmp.getParent) {
// modern
return cmp.getParent();
} else {
// classic
return cmp.ownerCt;
}
}
// Patch replaceNodeWithMarkup to fix bugs with swapping null and components
// A prime example of this is using react-router 4, which renders a null when a route fails
// to match. React does not call createChild/removeChild in this case, but takes a completely separate
// path through the renderer
var oldReplaceNodeWithMarkup = _ReactComponentEnvironment2.default.replaceNodeWithMarkup;
_ReactComponentEnvironment2.default.replaceNodeWithMarkup = function (oldChild, markup) {
if (oldChild._extCmp) {
var newChild = markup instanceof Ext.Base ? markup : wrapDOMElement(markup);
var parent = oldChild.hasOwnProperty('_extParent') ? oldChild._extParent : getParentCmp(oldChild._extCmp);
var index = oldChild.hasOwnProperty('_extIndexInParent') ? oldChild._extIndexInParent : parent.indexOf(oldChild._extCmp);
parent.insert(index, newChild);
oldChild._extCmp.destroy();
} else {
oldReplaceNodeWithMarkup.apply(this, arguments);
}
};
(0, _assign2.default)(ExtJSComponent.prototype, ContainerMixin);
//# sourceMappingURL=ExtJSComponent.js.map