Skip to content
This repository was archived by the owner on Mar 14, 2022. It is now read-only.

Commit 199f234

Browse files
authored
Fix a problem with string minification breaking the editor components. (#172)
* - Change the editor cache key type from string (class constructor name, fails on minification) to the class constructor itself. - Remove the default id for the editor container (breaking change!) in favor of a customizable option to pass 'id', 'className' and 'style' to the editor component. + extend one of the test cases to test it. - Fix some typos and minor inconsistencies. (#152) * - Clean up some typings (which included adding @types/react-redux to devDependencies, thus recreating the package-lock) - Fix the editor removal mechanism #152 � * Post-CR changes: - removed unneeded whitespace - moved the common classname to a constant - added react's runtime prop type checking for id, className and style (#152) * Fix npm audit errors. #152 * - Add editor prop interface for className, id and style and utilize it in the BaseEditorComponent� - Fix a problem with editor components in React's strict mode + development mode. - Update the babel package with the latest minor versions to fix npm audit errors. #152 * Add a replacement string for 'process.env.NODE_EVN' (introduced with React's 'prop-types') in the umd and minified builds. #152 * - Add 'hot-editor' as a required editor prop - post-cr corrections #152
1 parent 9fad057 commit 199f234

14 files changed

Lines changed: 5353 additions & 2171 deletions

.config/minified.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { baseConfig } from './base';
22
import { addLicenseBanner } from './helpers/licenseBanner';
3+
import replace from 'rollup-plugin-replace';
34
import { uglify } from 'rollup-plugin-uglify';
45

56
const minFilename = 'react-handsontable.min.js';
@@ -19,6 +20,9 @@ const minConfig = {
1920
}
2021
},
2122
plugins: baseConfig.plugins.concat([
23+
replace({
24+
'process.env.NODE_ENV': JSON.stringify('production')
25+
}),
2226
uglify({
2327
output: {
2428
comments: /^!/

.config/umd.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { addLicenseBanner } from './helpers/licenseBanner';
22
import { baseConfig } from './base';
3+
import replace from 'rollup-plugin-replace';
34

45
const env = process.env.NODE_ENV;
56
const filename = 'react-handsontable.js';
@@ -18,7 +19,11 @@ const umdConfig = {
1819
handsontable: 'Handsontable'
1920
}
2021
},
21-
plugins: baseConfig.plugins
22+
plugins: baseConfig.plugins.concat([
23+
replace({
24+
'process.env.NODE_ENV': JSON.stringify('production')
25+
})
26+
])
2227
};
2328

2429
addLicenseBanner(umdConfig);

package-lock.json

Lines changed: 5185 additions & 2094 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,21 @@
4343
"url": "https://github.com/handsontable/react-handsontable/issues"
4444
},
4545
"devDependencies": {
46-
"@babel/cli": "^7.2.3",
47-
"@babel/core": "^7.3.4",
48-
"@babel/plugin-proposal-class-properties": "^7.3.4",
49-
"@babel/plugin-transform-runtime": "^7.3.4",
50-
"@babel/polyfill": "^7.2.5",
51-
"@babel/preset-env": "^7.3.4",
52-
"@babel/preset-react": "^7.0.0",
53-
"@babel/preset-typescript": "^7.3.3",
54-
"@babel/runtime": "^7.3.4",
46+
"@babel/cli": "^7.8.4",
47+
"@babel/core": "^7.9.0",
48+
"@babel/plugin-proposal-class-properties": "^7.8.3",
49+
"@babel/plugin-transform-runtime": "^7.9.0",
50+
"@babel/polyfill": "^7.8.7",
51+
"@babel/preset-env": "^7.9.0",
52+
"@babel/preset-react": "^7.9.4",
53+
"@babel/preset-typescript": "^7.9.0",
54+
"@babel/runtime": "^7.9.2",
5555
"@types/enzyme": "^3.1.15",
5656
"@types/enzyme-adapter-react-16": "^1.0.3",
5757
"@types/jest": "^24.0.9",
5858
"@types/react": "^16.9.5",
5959
"@types/react-dom": "^16.9.1",
60+
"@types/react-redux": "^7.1.7",
6061
"babel-core": "^7.0.0-bridge.0",
6162
"cpy-cli": "^2.0.0",
6263
"cross-env": "^5.2.0",
@@ -65,7 +66,8 @@
6566
"enzyme-adapter-react-16": "^1.14.0",
6667
"enzyme-to-json": "^3.4.0",
6768
"handsontable": "^7.2.2",
68-
"jest": "^24.1.0",
69+
"jest": "^25.1.0",
70+
"prop-types": "^15.7.2",
6971
"react": "^16.10.2",
7072
"react-dom": "^16.10.2",
7173
"react-redux": "^7.1.1",
@@ -76,6 +78,7 @@
7678
"rollup-plugin-commonjs": "^10.0.1",
7779
"rollup-plugin-json": "^4.0.0",
7880
"rollup-plugin-node-resolve": "^5.2.0",
81+
"rollup-plugin-replace": "^2.2.0",
7982
"rollup-plugin-typescript2": "^0.22.1",
8083
"rollup-plugin-uglify": "^6.0.4",
8184
"typescript": "^3.5.3",

src/baseEditorComponent.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React from 'react';
22
import Handsontable from 'handsontable';
3+
import { HotEditorProps } from './types';
34

4-
class BaseEditorComponent<P = {}, S = {}, SS = any> extends React.Component<P, S, SS> implements Handsontable._editors.Base {
5+
class BaseEditorComponent<P = {}, S = {}, SS = any> extends React.Component<P | HotEditorProps, S> implements Handsontable._editors.Base {
56
name = 'BaseEditorComponent';
67
instance = null;
78
row = null;
@@ -19,7 +20,7 @@ class BaseEditorComponent<P = {}, S = {}, SS = any> extends React.Component<P, S
1920
super(props);
2021

2122
if (props.emitEditorInstance) {
22-
props.emitEditorInstance(this.constructor.name, this);
23+
props.emitEditorInstance(this);
2324
}
2425
}
2526

src/helpers.tsx

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
3+
import { HotEditorElement } from './types';
34

45
let bulkComponentContainer = null;
56

@@ -9,6 +10,11 @@ let bulkComponentContainer = null;
910
export const AUTOSIZE_WARNING = 'Your `HotTable` configuration includes `autoRowSize`/`autoColumnSize` options, which are not compatible with ' +
1011
' the component-based renderers`. Disable `autoRowSize` and `autoColumnSize` to prevent row and column misalignment.';
1112

13+
/**
14+
* Default classname given to the wrapper container.
15+
*/
16+
const DEFAULT_CLASSNAME = 'hot-wrapper-editor-container';
17+
1218
/**
1319
* Logs warn to the console if the `console` object is exposed.
1420
*
@@ -47,26 +53,27 @@ export function getChildElementByType(children: React.ReactNode, type: string):
4753
}
4854

4955
/**
50-
* Get the component node name.
56+
* Get the reference to the original editor class.
5157
*
52-
* @param {React.ReactElement} componentNode
53-
* @returns {String} Provided component's name.
58+
* @param {React.ReactElement} editorElement React element of the editor class.
59+
* @returns {Function} Original class of the editor component.
5460
*/
55-
export function getComponentNodeName(componentNode: React.ReactElement): string {
56-
if (!componentNode) {
61+
export function getOriginalEditorClass(editorElement: HotEditorElement) {
62+
if (!editorElement) {
5763
return null;
5864
}
5965

60-
return (componentNode.type as Function).name || ((componentNode.type as any).WrappedComponent as Function).name;
66+
return editorElement.type.WrappedComponent ? editorElement.type.WrappedComponent : editorElement.type;
6167
}
6268

6369
/**
6470
* Remove editor containers from DOM.
6571
*
66-
* @param [doc] Document to be used.
72+
* @param {Document} [doc] Document to be used.
73+
* @param {Map} editorCache The editor cache reference.
6774
*/
6875
export function removeEditorContainers(doc = document): void {
69-
doc.querySelectorAll('[id^="hot-wrapper-editor-container-"]').forEach((domNode) => {
76+
doc.querySelectorAll(`[class^="${DEFAULT_CLASSNAME}"]`).forEach((domNode) => {
7077
if (domNode.parentNode) {
7178
domNode.parentNode.removeChild(domNode);
7279
}
@@ -76,23 +83,31 @@ export function removeEditorContainers(doc = document): void {
7683
/**
7784
* Create an editor portal.
7885
*
86+
* @param {Document} [doc] Document to be used.
7987
* @param {React.ReactElement} editorElement Editor's element.
88+
* @param {Map} editorCache The editor cache reference.
8089
* @returns {React.ReactPortal} The portal for the editor.
8190
*/
82-
export function createEditorPortal(editorElement: React.ReactElement): React.ReactPortal {
91+
export function createEditorPortal(doc = document, editorElement: HotEditorElement, editorCache: Map<Function, React.Component>): React.ReactPortal {
8392
if (editorElement === null) {
8493
return;
8594
}
8695

87-
const componentName: string = getComponentNodeName(editorElement);
96+
const editorContainer = doc.createElement('DIV');
97+
const {id, className, style} = getContainerAttributesProps(editorElement.props, false);
8898

89-
let editorContainer = document.querySelector('#hot-wrapper-editor-container-' + componentName);
90-
if (!document.querySelector('#hot-wrapper-editor-container-' + componentName)) {
91-
editorContainer = document.createElement('DIV');
92-
editorContainer.id = 'hot-wrapper-editor-container-' + componentName;
93-
document.body.appendChild(editorContainer);
99+
if (id) {
100+
editorContainer.id = id;
94101
}
95102

103+
editorContainer.className = [DEFAULT_CLASSNAME, className].join(' ');
104+
105+
if (style) {
106+
Object.assign(editorContainer.style, style);
107+
}
108+
109+
doc.body.appendChild(editorContainer);
110+
96111
return ReactDOM.createPortal(editorElement, editorContainer);
97112
}
98113

@@ -103,21 +118,20 @@ export function createEditorPortal(editorElement: React.ReactElement): React.Rea
103118
* @param {Map} editorCache Component's editor cache.
104119
* @returns {React.ReactElement} An editor element containing the additional methods.
105120
*/
106-
export function getExtendedEditorElement(children: React.ReactNode, editorCache: Map<string, object>): React.ReactElement | null {
121+
export function getExtendedEditorElement(children: React.ReactNode, editorCache: Map<Function, object>): React.ReactElement | null {
107122
const editorElement = getChildElementByType(children, 'hot-editor');
123+
const editorClass = getOriginalEditorClass(editorElement);
108124

109125
if (!editorElement) {
110126
return null;
111127
}
112128

113129
return React.cloneElement(editorElement, {
114-
emitEditorInstance: (editorName, editorInstance) => {
115-
if (!editorCache.has(editorName)) {
116-
editorCache.set(editorName, editorInstance);
117-
}
130+
emitEditorInstance: (editorInstance) => {
131+
editorCache.set(editorClass, editorInstance);
118132
},
119133
isEditor: true
120-
});
134+
} as object);
121135
}
122136

123137
/**
@@ -155,6 +169,23 @@ export function createPortal(rElement: React.ReactElement, props, callback: Func
155169
};
156170
}
157171

172+
/**
173+
* Get an object containing the `id`, `className` and `style` keys, representing the corresponding props passed to the
174+
* component.
175+
*
176+
* @param {Object} props Object containing the react element props.
177+
* @param {Boolean} randomizeId If set to `true`, the function will randomize the `id` property when no `id` was present in the `prop` object.
178+
* @returns An object containing the `id`, `className` and `style` keys, representing the corresponding props passed to the
179+
* component.
180+
*/
181+
export function getContainerAttributesProps(props, randomizeId: boolean = true): {id: string, className: string, style: object} {
182+
return {
183+
id: props.id || (randomizeId ? 'hot-' + Math.random().toString(36).substring(5) : void 0),
184+
className: props.className || '',
185+
style: props.style || {},
186+
}
187+
}
188+
158189
/**
159190
* Add the `UNSAFE_` prefixes to the deprecated lifecycle methods for React >= 16.3.
160191
*

src/hotColumn.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class HotColumn extends React.Component<HotColumnProps, {}> {
4141
*/
4242
getSettingsProps(): HotTableProps {
4343
this.internalProps = ['__componentRendererColumns', '_emitColumnSettings', '_columnIndex', '_getChildElementByType', '_getRendererWrapper',
44-
'_getEditorClass', '_getEditorCache', 'hot-renderer', 'hot-editor', 'children'];
44+
'_getEditorClass', '_getEditorCache', '_getOwnerDocument', 'hot-renderer', 'hot-editor', 'children'];
4545

4646
return Object.keys(this.props)
4747
.filter(key => {
@@ -110,10 +110,11 @@ class HotColumn extends React.Component<HotColumnProps, {}> {
110110
* @param {React.ReactNode} [children] Children of the HotTable instance. Defaults to `this.props.children`.
111111
*/
112112
createLocalEditorPortal(children = this.props.children): void {
113-
const localEditorElement: React.ReactElement = getExtendedEditorElement(children, this.props._getEditorCache());
113+
const editorCache = this.props._getEditorCache();
114+
const localEditorElement: React.ReactElement = getExtendedEditorElement(children, editorCache);
114115

115116
if (localEditorElement) {
116-
this.setLocalEditorPortal(createEditorPortal(localEditorElement))
117+
this.setLocalEditorPortal(createEditorPortal(this.props._getOwnerDocument(), localEditorElement, editorCache));
117118
}
118119
}
119120

0 commit comments

Comments
 (0)