Merge pull request #2 from Microsoft/excel-range

Initial check in an simple excel range react component based on open …
This commit is contained in:
markxyoung 2016-12-05 12:35:18 -08:00 коммит произвёл GitHub
Родитель c84c9b322c 7287e38510
Коммит 228d206269
30 изменённых файлов: 43472 добавлений и 0 удалений

20
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,20 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

Просмотреть файл

@ -0,0 +1,20 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

Просмотреть файл

@ -0,0 +1,75 @@
{
"root": true,
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
"plugins": [
"react"
],
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"experimentalObjectRestSpread": true
}
},
"env": {
"es6": true,
"browser": true,
"node": true,
"jquery": true,
"mocha": true,
"jest": true
},
"rules": {
"quotes": 0,
"no-console": 1,
"no-debugger": 1,
"no-var": 1,
"no-unused-vars": 0,
"semi": [1, "always"],
"no-trailing-spaces": 0,
"eol-last": 0,
"no-underscore-dangle": 0,
"no-alert": 0,
"no-lone-blocks": 0,
"jsx-quotes": 1,
"react/display-name": [ 1, {"ignoreTranspilerName": false }],
"react/forbid-prop-types": [1, {"forbid": ["any"]}],
"react/jsx-boolean-value": 0,
"react/jsx-closing-bracket-location": 0,
"react/jsx-curly-spacing": 1,
"react/jsx-indent-props": 0,
"react/jsx-key": 1,
"react/jsx-max-props-per-line": 0,
"react/jsx-no-bind": 0,
"react/jsx-no-duplicate-props": 1,
"react/jsx-no-literals": 0,
"react/jsx-no-undef": 1,
"react/jsx-pascal-case": 1,
"react/jsx-sort-prop-types": 0,
"react/jsx-sort-props": 0,
"react/jsx-uses-react": 1,
"react/jsx-uses-vars": 1,
"react/no-danger": 1,
"react/no-did-mount-set-state": 1,
"react/no-did-update-set-state": 1,
"react/no-direct-mutation-state": 1,
"react/no-multi-comp": 1,
"react/no-set-state": 0,
"react/no-unknown-property": 1,
"react/prefer-es6-class": 1,
"react/prop-types": 1,
"react/react-in-jsx-scope": 1,
"react/require-extension": 1,
"react/self-closing-comp": 1,
"react/sort-comp": 1,
"react/wrap-multilines": 1
},
"globals": {
}
}

32
excel/react-sheet/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,32 @@
build/
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Users Environment Variables
.lock-wscript
lib

Просмотреть файл

@ -0,0 +1,30 @@
build/
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Users Environment Variables
.lock-wscript

Просмотреть файл

@ -0,0 +1,3 @@
language: node_js
node_js:
- "5.0"

21
excel/react-sheet/LICENSE Normal file
Просмотреть файл

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Felix Rieseberg & Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11602
excel/react-sheet/dist/spreadsheet.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

8
excel/react-sheet/dist/spreadsheet.min.js поставляемый Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

68
excel/react-sheet/example.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,68 @@
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var Spreadsheet = require('./lib/spreadsheet');
// Example One
var exampleOne = {};
exampleOne.initialData = {
rows: [
['', '', '', '', '', '', '', ''],
['', 1, 2, 3, 4, 5, 6, 7],
['', 1, '', 3, 4, 5, 6, 7],
['', 1, 2, 3, 4, 5, 6, 7],
['', 1, 2, 3, 4, 5, 6, 7]
]
};
exampleOne.config = {
rows: 5,
columns: 8,
hasHeadColumn: true,
isHeadColumnString: true,
hasHeadRow: true,
isHeadRowString: true,
canAddRow: true,
canAddColumn: true,
emptyValueSymbol: '-',
hasLetterNumberHeads: true
};
// Example Two
var exampleTwo = {};
exampleTwo.initialData = {
rows: [
['Customer', 'Job', 'Contact', 'City', 'Revenue'],
['iDiscovery', 'Build', 'John Doe', 'Boston, MA', '500,000'],
['SxSW', 'Build', 'Tom Fuller', 'San Francisco, CA', '600,000'],
['CapitalTwo', 'Failed', 'Eric Pixel', 'Seattle, WA', '450,000']
]
};
exampleTwo.cellClasses = {
rows: [
['', '', '', '', '', '', '', ''],
['green', '', '', '', '', '', '', 'dollar'],
['purple', '', '', '', '', '', '', 'dollar'],
['yellow', 'failed', '', '', '', '', '', 'dollar'],
]
};
exampleTwo.config = {
rows: 5,
columns: 5,
headColumn: true,
headColumnIsString: true,
headRow: true,
headRowIsString: true,
canAddRow: false,
canAddColumn: false,
emptyValueSymbol: '-',
letterNumberHeads: false
};
// Render
ReactDOM.render(<Spreadsheet initialData={exampleOne.initialData} config={exampleOne.config} cellClasses={exampleOne.cellClasses} />, document.getElementById('exampleOne'));
ReactDOM.render(<Spreadsheet initialData={exampleTwo.initialData} config={exampleTwo.config} cellClasses={exampleTwo.cellClasses} />, document.getElementById('exampleTwo'));

Двоичные данные
excel/react-sheet/example/.reactspreadsheet.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 20 KiB

Двоичные данные
excel/react-sheet/example/.reactspreadsheet2.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 86 KiB

30065
excel/react-sheet/example/bundle.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,98 @@
h2, h3 {
color: #000
}
h2 a {
text-decoration: none;
color: #000;
}
body {
background-color: #F7F7F7;
}
div.example {
margin-bottom: 80px;
margin-left: 20px;
}
div.intro {
margin-bottom: 80px;
width: 700px;
margin-left: 20px;
color: #000;
}
div#exampleTwo td, div#exampleTwo th {
width: 128px;
height: 35px;
border: none;
text-align: left;
background-color: inherit;
}
div#exampleTwo th {
background-color: #fff;
}
div#exampleTwo span {
line-height: 28px;
}
div#exampleTwo table {
-webkit-box-shadow: 2px 2px 36px 0px rgba(0,0,0,0.39);
-moz-box-shadow: 2px 2px 36px 0px rgba(0,0,0,0.39);
box-shadow: 2px 2px 36px 0px rgba(0,0,0,0.39);
border: none;
padding: 8px;
font-family: 'Roboto', sans-serif;
border: 1px solid #D7D7D7;
background-color: #FFFFFF;
}
div#exampleTwo table > tbody > tr:first-child > th {
border-bottom: 1px solid #D8D8D8;
border-right: none;
text-transform: uppercase;
text-align: left;
height: 30px;
font-weight: 300;
margin-bottom: 8px;
}
div#exampleTwo table > tbody > tr > th {
border-right: 1px solid #D8D8D8;
}
div#exampleTwo table > tbody > tr > th:nth-child(2),
div#exampleTwo table > tbody > tr > td:nth-child(2) {
padding-left: 15px;
}
div#exampleTwo td input {
background-color: #F0FAFF;
}
div#exampleTwo tr:hover {
background-color: #EEEEEE;
}
div#exampleTwo input::selection {
background: #F0FAFF;
}
div#exampleTwo .selected {
border-bottom: 2px solid #FF074E;
}
div#exampleTwo .green {
border-left: 13px solid #62B9A4;
}
div#exampleTwo .purple {
border-left: 13px solid #985DFF;
}
div#exampleTwo .yellow {
border-left: 13px solid #FF9E39;
}

Просмотреть файл

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-type' content='text/html; charset=utf-8'>
<title>React Spreadsheet Example</title>
<link rel="stylesheet" type="text/css" href="../styles/excel.css" />
<link rel="stylesheet" type="text/css" href="example.css" />
<link href='http://fonts.googleapis.com/css?family=Roboto:400,300' rel='stylesheet' type='text/css'>
</head>
<body>
<div class="intro">
<h2><a href="https://github.com/felixrieseberg/React-Spreadsheet-Component">React-Spreadsheet-Component</a></h2>
<p>These are two rather simple examples of <a href="https://github.com/felixrieseberg/React-Spreadsheet-Component">React-Spreadsheet-Component</a>, a simple spreadsheet component in React (creative name, huh?). It's made with <3 by Microsoft DX and released under the MIT License.</p>
</div>
<div class="example">
<h3>Simple Example Spreadsheet</h3>
<div id="exampleOne"></div>
</div>
<div class="example">
<h3>Fancy Editable Spreadsheet</h3>
<div id="exampleTwo"></div>
</div>
<script src="bundle.js"></script>
</body>
</html>

91
excel/react-sheet/gulpfile.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,91 @@
var browserify = require('browserify');
var del = require('del');
var gulp = require('gulp');
var source = require('vinyl-source-stream');
var header = require('gulp-header');
var eslint = require('gulp-eslint');
var rename = require('gulp-rename');
var plumber = require('gulp-plumber');
var react = require('gulp-react');
var streamify = require('gulp-streamify');
var uglify = require('gulp-uglify');
var gutil = require('gulp-util');
var connect = require('gulp-connect');
var reactify = require('reactify');
var pkg = require('./package.json');
var devBuild = (process.env.NODE_ENV === 'production') ? '' : ' (dev build at ' + (new Date()).toUTCString() + ')';
var distHeader = '/*!\n\
* <%= pkg.name %> <%= pkg.version %><%= devBuild %> - <%= pkg.homepage %>\n\
* <%= pkg.license %> Licensed\n\
*/\n';
var jsSrcPaths = './src/*.js*'
var jsLibPaths = './lib/*.js'
gulp.task('clean-lib', function (cb) {
del(jsLibPaths).then(function () {
cb();
});
});
gulp.task('transpile-js', ['clean-lib'], function () {
return gulp.src(jsSrcPaths)
.pipe(plumber())
.pipe(react({ harmony: true }))
.pipe(gulp.dest('./lib'));
});
gulp.task('lint-js', ['transpile-js'], function () {
return gulp.src(jsLibPaths)
.pipe(eslint('./.eslintrc.json'));
//.pipe(eslint.reporter('eshint-stylish'));
});
gulp.task('bundle-js', ['lint-js'], function () {
var b = browserify(pkg.main, {
debug: !!gutil.env.debug
, standalone: pkg.standalone
, detectGlobals: false
});
b.transform('browserify-shim')
var stream = b.bundle()
.pipe(source('spreadsheet.js'))
.pipe(streamify(header(distHeader, { pkg: pkg, devBuild: devBuild })))
.pipe(gulp.dest('./dist'));
if (process.env.NODE_ENV === 'production') {
stream = stream
.pipe(rename('spreadsheet.min.js'))
.pipe(streamify(uglify()))
.pipe(streamify(header(distHeader, { pkg: pkg, devBuild: devBuild })))
.pipe(gulp.dest('./dist'));
}
return stream;
});
gulp.task('watch', function () {
gulp.watch(jsSrcPaths, ['bundle-js']);
});
gulp.task('connect', function () {
connect.server();
gutil.log('--------------------------------------------')
gutil.log(gutil.colors.magenta('To see the example, open up a browser and go'));
gutil.log(gutil.colors.bold.red('to http://localhost:8080/example'));
gutil.log('--------------------------------------------');
});
gulp.task('example', ['transpile-js'], function () {
return browserify('./example.js')
.transform(reactify)
.bundle()
.pipe(source('bundle.js'))
.pipe(gulp.dest('./example'));
});
gulp.task('default', ['bundle-js', 'connect', 'watch']);

Просмотреть файл

@ -0,0 +1,72 @@
{
"name": "react-spreadsheet-component",
"description": "A Spreadsheet Component for ReactJS",
"version": "0.5.1",
"license": "MIT",
"engines": {
"node": ">=6.5",
"npm": ">=3.10"
},
"dependencies": {
"jquery": "^2.1",
"mousetrap": "^1.5.3"
},
"peerDependencies": {
"react": "^0.14.3"
},
"devDependencies": {
"babel-eslint": "^7.1.1",
"body-parser": "^1.12.4",
"browserify": "^12.0.1",
"browserify-shim": "^3.8.11",
"del": "^2.0.2",
"eslint": "^3.11.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-react": "^6.8.0",
"express": "^4.12.3",
"gulp": "^3.8.11",
"gulp-connect": "^4.0.0",
"gulp-eslint": "^3.0.1",
"gulp-header": "^1.2.2",
"gulp-plumber": "^1.0.0",
"gulp-react": "^3.0.1",
"gulp-rename": "^1.2.0",
"gulp-streamify": "1.0.2",
"gulp-uglify": "^1.1.0",
"gulp-util": "^3.0.4",
"jest-cli": "^0.8.2",
"multer": "^1.1.0",
"react": "^0.14.0",
"react-addons-test-utils": "^0.14.3",
"react-dom": "^0.14.0",
"react-tools": "^0.13.3",
"reactify": "^1.1.1",
"tape": "^4.2.2",
"vinyl-source-stream": "^1.1.0"
},
"scripts": {
"debug": "gulp --debug",
"dist": "gulp bundle-js --production --release && gulp bundle-js --development --release",
"watch": "gulp",
"test": "jest",
"lint": "eslint src"
},
"jest": {
"scriptPreprocessor": "<rootDir>/preprocessor.js",
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/*"
]
},
"browserify-shim": {
"react": "global:React",
"react/addons": "global:React",
"react-dom": "global:ReactDOM"
},
"browserify": {
"transform": [
"browserify-shim"
]
},
"main": "./lib/spreadsheet.js",
"standalone": "React-Spreadsheet"
}

7
excel/react-sheet/preprocessor.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
var ReactTools = require('react-tools')
module.exports = {
process: function(src) {
return ReactTools.transform(src, {harmony: true, stripTypes: true})
}
};

125
excel/react-sheet/readme.md Normal file
Просмотреть файл

@ -0,0 +1,125 @@
## Spreadsheet Component for React
[![Build Status](https://travis-ci.org/felixrieseberg/React-Spreadsheet-Component.svg?branch=master)](https://travis-ci.org/felixrieseberg/React-Spreadsheet-Component) [![Dependency Status](https://david-dm.org/felixrieseberg/react-spreadsheet-component.svg)](https://david-dm.org/felixrieseberg/react-spreadsheet-component) [![npm version](https://badge.fury.io/js/react-spreadsheet-component.svg)](https://badge.fury.io/js/react-spreadsheet-component) ![Downloads](https://img.shields.io/npm/dm/react-spreadsheet-component.svg)
This is a spreadsheet component built in Facebook's ReactJS. [You can see a demo here](http://felixrieseberg.github.io/React-Spreadsheet-Component/).
![Screenshot](https://raw.githubusercontent.com/felixrieseberg/React-Spreadsheet-Component/master/example/.reactspreadsheet.gif)
![Screenshot](https://raw.githubusercontent.com/felixrieseberg/React-Spreadsheet-Component/master/example/.reactspreadsheet2.gif)
## Usage
The component is initialized with a configuration object. If desired, initial data for the spreadsheet can be passed in as an array of rows. In addition, you can pass in a second array filled with class names for each cell, allowing you to style each cell differently.
```js
var SpreadsheetComponent = require('react-spreadsheet-component');
React.render(<SpreadsheetComponent initialData={initialData} config={config} spreadsheetId="1" />, document.getElementsByTagName('body'));
```
##### Configuration Object
```js
var config = {
// Initial number of row
rows: 5,
// Initial number of columns
columns: 8,
// True if the first column in each row is a header (th)
hasHeadColumn: true,
// True if the data for the first column is just a string.
// Set to false if you want to pass custom DOM elements.
isHeadColumnString: true,
// True if the first row is a header (th)
hasHeadRow: true,
// True if the data for the cells in the first row contains strings.
// Set to false if you want to pass custom DOM elements.
isHeadRowString: true,
// True if the user can add rows (by navigating down from the last row)
canAddRow: true,
// True if the user can add columns (by navigating right from the last column)
canAddColumn: true,
// Override the display value for an empty cell
emptyValueSymbol: '-',
// Fills the first column with index numbers (1...n) and the first row with index letters (A...ZZZ)
hasLetterNumberHeads: true
};
```
##### Initial Data Object
The initial data object contains an array `rows`, which itself contains an array for every single row. Each index in the array represents a cell. It is used by the component to pre-populate the cells with data. Be aware that user input is not written to this object, as it is merely used in initialization to populate the state. If you want to capture user input, read below.
```js
var data = {
rows: [
['Key', 'AAA', 'BBB', 'CCC', 'DDD', 'EEE', 'FFF', 'GGG'],
['COM', '0,0', '0,1', '0,2', '0,3', '0,4', '0,5', '0,6'],
['DIV', '1,0', '1,1', '1,2', '1,3', '1,4', '1,5', '1,6'],
['DEV', '2,0', '2,1', '2,2', '2,3', '2,4', '2,5', '2,6'],
['ACC', '3,0', '3,1', '3,2', '3,3', '3,4', '3,5', '3,6']
]
};
```
##### Cell Classes Object
You can assign custom classes to individual cells. Follow the same schema as for the initial data object.
```js
var classes = {
rows: [
['', 'specialHead', '', '', '', '', '', ''],
['', '', '', '', '', '', '', ''],
['', 'error', '', '', '', '', '', ''],
['', 'error changed', '', '', '', '', '', ''],
['', '', '', '', '', '', '', '']
]
};
```
## Data Lifecycle
User input is not written to the `initialData` object, as it is merely used in initialization to populate the state. If you want to capture user input, subscribe callbacks to the `cellValueChanged` and `dataChanged` events on the dispatcher.
The last parameter is the `spreadsheetId` of the spreadsheet you want to subscribe to.
##### Get the full data state after each change
```js
var Dispatcher = require('./src/dispatcher');
Dispatcher.subscribe('dataChanged', function (data) {
// data: The entire new data state
}, "spreadsheet-1")
```
##### Get the data change before state change
```js
var Dispatcher = require('./src/dispatcher');
Dispatcher.subscribe('cellValueChanged', function (cell, newValue, oldValue) {
// cell: An array indicating the cell position by row/column, ie: [1,1]
// newValue: The new value for that cell
}, "spreadsheet-1")
```
### Other Dispatcher Events
The dispatcher offers some other events you can subscribe to:
* `headCellClicked` A head cell was clicked (returns a cell array `[row, column]`)
* `cellSelected` A cell was selected (returns a cell array `[row, column]`)
* `cellBlur` A cell was blurred (returns returns a cell array `[row, column]`)
* `editStarted` The user started editing (returns a cell array `[row, column]`)
* `editStopped` The user stopped editing (returns a cell array `[row, column]`)
* `newRow` The user created a new row (returns the row index)
* `newColumn` The user created a new column (returns the row index)
## Running the Example
Clone the repository from GitHub and open the created folder:
Install npm packages and compile JSX
```bash
npm install
gulp
```
Eslint is run automatically when you type 'gulp'. To check lint errors, do 'npm run lint'.
If you are using Windows, run the following commands instead:
```bash
npm install --msvs_version=2013
gulp
```

42
excel/react-sheet/src/__tests__/cell-test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,42 @@
'use strict';
//const jest = require('jest');
jest.dontMock('../cell');
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const CellComponent = require('../cell');
const testVars = {
key: 'row_0000_cell_1',
uid: [0, 0],
val: 'test',
spreadsheetId: '1',
selected: false,
editing: false
};
describe('Cell', () => {
it('Renders a cell', () => {
const cell = TestUtils.renderIntoDocument(
<table>
<tbody>
<tr>
<CellComponent
uid={testVars.uid}
key={testVars.key}
value={testVars.val}
spreadsheetId={testVars.spreadsheetId}
selected={testVars.selected}
editing={testVars.editing}
/>
</tr>
</tbody>
</table>
);
ReactDOM.findDOMNode(cell);
});
});

62
excel/react-sheet/src/__tests__/helpers-test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,62 @@
'use strict';
//const jest = require('jest');
//const expect = require('expect');
jest.dontMock('../helpers');
const Helpers = require('../helpers');
describe('Helpers', () => {
it('Correctly finds the first element in an array', () => {
let arr = [{myProp: 1}, {myProp: 2}, {myProp: 3, xProp: 1}, {myProp: 4}, {myProp: 3, xProp: 2}];
let firstFound = Helpers.firstInArray(arr, function (element) {
return (element.myProp === 3);
});
expect(firstFound.myProp).toBe(3);
expect(firstFound.xProp).toBe(1);
});
it('Correctly finds the first element in an array', () => {
let arr = [{nodeName: 'x'}, {nodeName: 'TD', myProp: 1}, {nodeName: 'TD', myProp: 2}];
let firstFound = Helpers.firstTDinArray(arr);
expect(firstFound.nodeName).toBe('TD');
expect(firstFound.myProp).toBe(1);
});
it('Correctly identifies two cells as equal', () => {
let cell1 = ['prop', 'propTwo'];
let cell2 = ['prop', 'propTwo'];
let cellsEqual = Helpers.equalCells(cell1, cell2);
expect(cellsEqual).toBe(true);
});
it('Correctly identifies two cells as unequal', () => {
let cell1 = ['prop', 'propTwo'];
let cell2 = ['prop', 'propThree'];
let cellsEqual = Helpers.equalCells(cell1, cell2);
expect(cellsEqual).toBe(false);
});
it('Correctly counts with letters', () => {
expect(Helpers.countWithLetters(1)).toBe('A');
expect(Helpers.countWithLetters(2)).toBe('B');
expect(Helpers.countWithLetters(26)).toBe('Z');
expect(Helpers.countWithLetters(27)).toBe('AA');
expect(Helpers.countWithLetters(28)).toBe('AB');
});
it('Correctly makes a spreadsheet id', () => {
expect(Helpers.makeSpreadsheetId().length).toBe(5);
expect(Helpers.makeSpreadsheetId().length).toBe(5);
expect(Helpers.makeSpreadsheetId().length).toBe(5);
expect(Helpers.makeSpreadsheetId().length).toBe(5);
});
});

42
excel/react-sheet/src/__tests__/row-test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,42 @@
'use strict';
//const jest = require('jest');
//const expect = require('expect');
jest.dontMock('../row');
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const RowComponent = require('../cell');
const testVars = {
cells: [],
cellClasses: [],
uid: 0,
key: 'testkey',
spreadsheetId: '0',
className: 'cellComponent'
};
describe('Row', () => {
it('Renders a row', () => {
const row = TestUtils.renderIntoDocument(
<table>
<tbody>
<RowComponent
cells={testVars.cells}
cellClasses={testVars.cellClasses}
uid={testVars.uid}
key={testVars.key}
spreadsheetId={testVars.spreadsheetId}
className={testVars.className}
/>
</tbody>
</table>
);
const rowNode = ReactDOM.findDOMNode(row);
expect(rowNode).toBeDefined();
});
});

48
excel/react-sheet/src/__tests__/spreadsheet-test.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,48 @@
'use strict';
//const jest = require('jest');
//const expect = require('expect');
jest.dontMock('../spreadsheet');
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const SpreadsheetComponent = require('../spreadsheet');
const testVars = {
initialData: {
rows: [
['', '', '', '', '', '', '', ''],
['', 1, 2, 3, 4, 5, 6, 7],
['', 1, '', 3, 4, 5, 6, 7],
['', 1, 2, 3, 4, 5, 6, 7],
['', 1, 2, 3, 4, 5, 6, 7]
]
},
config: {
rows: 5,
columns: 8,
hasHeadColumn: true,
isHeadColumnString: true,
hasHeadRow: true,
isHeadRowString: true,
canAddRow: true,
canAddColumn: true,
emptyValueSymbol: '-',
hasLetterNumberHeads: true
}
};
describe('Spreadsheet', () => {
it('Renders a spreadsheet', () => {
const spreadsheet = TestUtils.renderIntoDocument(
<SpreadsheetComponent
initialData={testVars.initialData}
config={testVars.config}
cellClasses={testVars.cellClasses} />
);
const spreadsheetNode = ReactDOM.findDOMNode(spreadsheet);
expect(spreadsheetNode).toBeDefined();
});
});

178
excel/react-sheet/src/cell.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,178 @@
"use strict";
const React = require('react');
var ReactDOM = require('react-dom');
var Dispatcher = require('./dispatcher');
var Helpers = require('./helpers');
var CellComponent = React.createClass({
/**
* React "getInitialState" method, setting whether or not
* the cell is being edited and its changing value
*/
getInitialState: function() {
return {
editing: this.props.editing,
changedValue: this.props.value
};
},
/**
* React "render" method, rendering the individual cell
*/
render: function() {
var props = this.props,
selected = (props.selected) ? 'selected' : '',
ref = 'input_' + props.uid.join('_'),
config = props.config || { emptyValueSymbol: ''},
displayValue = (props.value === '' || !props.value) ? config.emptyValueSymbol : props.value,
cellClasses = (props.cellClasses && props.cellClasses.length > 0) ? props.cellClasses + ' ' + selected : selected,
cellContent;
// Check if header - if yes, render it
var header = this.renderHeader();
if (header) {
return header;
}
// If not a header, check for editing and return
if (props.selected && props.editing) {
cellContent = (
<input className="mousetrap"
onChange={this.handleChange}
onBlur={this.handleBlur}
ref={ref}
defaultValue={this.props.value} />
)
}
return (
<td className={cellClasses} ref={props.uid.join('_')}>
<div className="reactTableCell">
{cellContent}
<span onDoubleClick={this.handleDoubleClick} onClick={this.handleClick}>
{displayValue}
</span>
</div>
</td>
);
},
/**
* React "componentDidUpdate" method, ensuring correct input focus
* @param {React previous properties} prevProps
* @param {React previous state} prevState
*/
componentDidUpdate: function(prevProps, prevState) {
if (this.props.editing && this.props.selected) {
var node = ReactDOM.findDOMNode(this.refs['input_' + this.props.uid.join('_')]);
node.focus();
}
if (prevProps.selected && prevProps.editing && this.state.changedValue !== this.props.value) {
this.props.onCellValueChange(this.props.uid, this.state.changedValue);
}
},
/**
* Click handler for individual cell, ensuring navigation and selection
* @param {event} e
*/
handleClick: function (e) {
var cellElement = ReactDOM.findDOMNode(this.refs[this.props.uid.join('_')]);
this.props.handleSelectCell(this.props.uid, cellElement);
},
/**
* Click handler for individual cell if the cell is a header cell
* @param {event} e
*/
handleHeadClick: function (e) {
var cellElement = ReactDOM.findDOMNode(this.refs[this.props.uid.join('_')]);
Dispatcher.publish('headCellClicked', cellElement, this.props.spreadsheetId);
},
/**
* Double click handler for individual cell, ensuring navigation and selection
* @param {event} e
*/
handleDoubleClick: function (e) {
e.preventDefault();
this.props.handleDoubleClickOnCell(this.props.uid);
},
/**
* Blur handler for individual cell
* @param {event} e
*/
handleBlur: function (e) {
var newValue = ReactDOM.findDOMNode(this.refs['input_' + this.props.uid.join('_')]).value;
this.props.onCellValueChange(this.props.uid, newValue, e);
this.props.handleCellBlur(this.props.uid);
Dispatcher.publish('cellBlurred', this.props.uid, this.props.spreadsheetId);
},
/**
* Change handler for an individual cell, propagating the value change
* @param {event} e
*/
handleChange: function (e) {
var newValue = ReactDOM.findDOMNode(this.refs['input_' + this.props.uid.join('_')]).value;
this.setState({changedValue: newValue});
},
/**
* Checks if a header exists - if it does, it returns a header object
* @return {false|react} [Either false if it's not a header cell, a react object if it is]
*/
renderHeader: function () {
var props = this.props,
selected = (props.selected) ? 'selected' : '',
uid = props.uid,
config = props.config || { emptyValueSymbol: ''},
displayValue = (props.value === '' || !props.value) ? config.emptyValueSymbol : props.value,
cellClasses = (props.cellClasses && props.cellClasses.length > 0) ? this.props.cellClasses + ' ' + selected : selected;
// Cases
var headRow = (uid[0] === 0),
headColumn = (uid[1] === 0),
headRowAndEnabled = (config.hasHeadRow && uid[0] === 0),
headColumnAndEnabled = (config.hasHeadColumn && uid[1] === 0)
// Head Row enabled, cell is in head row
// Head Column enabled, cell is in head column
if (headRowAndEnabled || headColumnAndEnabled) {
if (headColumn && config.hasLetterNumberHeads) {
displayValue = uid[0];
} else if (headRow && config.hasLetterNumberHeads) {
displayValue = Helpers.countWithLetters(uid[1]);
}
if ((config.isHeadRowString && headRow) || (config.isHeadColumnString && headColumn)) {
return (
<th className={cellClasses} ref={this.props.uid.join('_')}>
<div>
<span onClick={this.handleHeadClick}>
{displayValue}
</span>
</div>
</th>
);
} else {
return (
<th ref={this.props.uid.join('_')}>
{displayValue}
</th>
);
}
} else {
return false;
}
}
});
module.exports = CellComponent;

113
excel/react-sheet/src/dispatcher.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,113 @@
"use strict";
var Mousetrap = require('mousetrap');
var $ = require('jquery');
var dispatcher = {
// Event Pub/Sub System
//
// Topics used:
// [headCellClicked] - A head cell was clicked
// @return {array} [row, column]
// [cellSelected] - A cell was selected
// @return {array} [row, column]
// [cellBlur] - A cell was blurred
// @return {array} [row, column]
// [cellValueChanged] - A cell value changed.
// @return {cell, newValue} Origin cell, new value entered
// [dataChanged] - Data changed
// @return {data} New data
// [editStarted] - The user started editing
// @return {cell} Origin cell
// [editStopped] - The user stopped editing
// @return {cell} Origin cell
// [rowCreated] - The user created a row
// @return {number} Row index
// [columnCreated] - The user created a column
// @return {number} Column index
topics: {},
/**
* Subscribe to an event
* @param {string} topic [The topic subscribing to]
* @param {function} listener [The callback for published events]
* @param {string} spreadsheetId [The reactId (data-spreadsheetId) of the origin element]
*/
subscribe: function(topic, listener, spreadsheetId) {
if (!this.topics[spreadsheetId]) {
this.topics[spreadsheetId] = [];
}
if (!this.topics[spreadsheetId][topic]) {
this.topics[spreadsheetId][topic] = [];
}
this.topics[spreadsheetId][topic].push(listener);
},
/**
* Publish to an event channel
* @param {string} topic [The topic publishing to]
* @param {object} data [An object passed to the subscribed callbacks]
* @param {string} spreadsheetId [The reactId (data-spreadsheetId) of the origin element]
*/
publish: function(topic, data, spreadsheetId) {
// return if the topic doesn't exist, or there are no listeners
if (!this.topics[spreadsheetId] || !this.topics[spreadsheetId][topic] || this.topics[spreadsheetId][topic].length < 1) {
return
}
this.topics[spreadsheetId][topic].forEach(function(listener) {
listener(data || {});
});
},
keyboardShortcuts: [
// Name, Keys, Events
['down', 'down', ['keyup']],
['up', 'up', ['keyup']],
['left', 'left', ['keyup']],
['right', 'right', ['keyup']],
['tab', 'tab', ['keyup', 'keydown']],
['enter', 'enter', ['keyup']],
['esc', 'esc', ['keyup']],
['remove', ['backspace', 'delete'], ['keyup', 'keydown']],
['letter', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'w', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '=', '.', ',', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'W', 'Y', 'Z'], ['keyup', 'keydown']]
],
/**
* Initializes the keyboard bindings
* @param {object} domNode [The DOM node of the element that should be bound]
* @param {string} spreadsheetId [The id of the spreadsheet element]
*/
setupKeyboardShortcuts: function (domNode, spreadsheetId) {
var self = this;
this.keyboardShortcuts.map(function (shortcut) {
var shortcutName = shortcut[0],
shortcutKey = shortcut[1],
events = shortcut[2];
events.map(event => {
Mousetrap(domNode).bind(shortcutKey, function (e) {
self.publish(shortcutName + '_' + event, e, spreadsheetId);
}, event);
})
});
// Avoid scroll
window.addEventListener('keydown', function(e) {
// space and arrow keys
if ([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1 && $(document.activeElement)[0].tagName !== 'INPUT') {
if (e.preventDefault) {
e.preventDefault();
} else {
// Oh, old IE, you 💩
e.returnValue = false;
}
}
}, false);
}
};
module.exports = dispatcher;

84
excel/react-sheet/src/helpers.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,84 @@
"use strict";
var Helpers = {
/**
* Find the first element in an array matching a boolean
* @param {[array]} arr [Array to test]
* @param {[function]} test [Test Function]
* @param {[type]} context [Context]
* @return {[object]} [Found element]
*/
firstInArray: function (arr, test, context) {
var result = null;
arr.some(function(el, i) {
return test.call(context, el, i, arr) ? ((result = el), true) : false;
});
return result;
},
/**
* Find the first TD in a path array
* @param {[array]} arr [Path array containing elements]
* @return {[object]} [Found element]
*/
firstTDinArray: function (arr) {
var cell = Helpers.firstInArray(arr, function (element) {
if (element.nodeName && element.nodeName === 'TD') {
return true;
} else {
return false;
}
});
return cell;
},
/**
* Check if two cell objects reference the same cell
* @param {[array]} cell1 [First cell]
* @param {[array]} cell2 [Second cell]
* @return {[boolean]} [Boolean indicating if the cells are equal]
*/
equalCells: function (cell1, cell2) {
if (!cell1 || !cell2 || cell1.length !== cell2.length) {
return false;
}
if (cell1[0] === cell2[0] && cell1[1] === cell2[1]) {
return true;
} else {
return false;
}
},
/**
* Counts in letters (A, B, C...Z, AA);
* @return {[string]} [Letter]
*/
countWithLetters: function (num) {
var mod = num % 26,
pow = num / 26 | 0,
out = mod ? String.fromCharCode(64 + mod) : (--pow, 'Z');
return pow ? this.countWithLetters(pow) + out : out;
},
/**
* Creates a random 5-character id
* @return {string} [Somewhat random id]
*/
makeSpreadsheetId: function()
{
var text = '',
possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 5; i = i + 1) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
}
module.exports = Helpers;

49
excel/react-sheet/src/row.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,49 @@
"use strict";
var React = require('react');
var CellComponent = require('./cell');
var Helpers = require('./helpers');
var RowComponent = React.createClass({
/**
* React Render method
* @return {[JSX]} [JSX to render]
*/
render: function() {
var config = this.props.config,
cells = this.props.cells,
columns = [],
key, uid, selected, cellClasses, i;
if (!config.columns || cells.length === 0) {
return console.error('Table can\'t be initialized without set number of columsn and no data!');
}
for (i = 0; i < cells.length; i = i + 1) {
// If a cell is selected, check if it's this one
selected = Helpers.equalCells(this.props.selected, [this.props.uid, i]);
cellClasses = (this.props.cellClasses && this.props.cellClasses[i]) ? this.props.cellClasses[i] : '';
key = 'row_' + this.props.uid + '_cell_' + i;
uid = [this.props.uid, i];
columns.push(<CellComponent key={key}
uid={uid}
value={cells[i]}
config={config}
cellClasses={cellClasses}
onCellValueChange={this.props.onCellValueChange}
handleSelectCell={this.props.handleSelectCell}
handleDoubleClickOnCell={this.props.handleDoubleClickOnCell}
handleCellBlur={this.props.handleCellBlur}
spreadsheetId={this.props.spreadsheetId}
selected={selected}
editing={this.props.editing} />
);
}
return <tr>{columns}</tr>;
}
});
module.exports = RowComponent;

309
excel/react-sheet/src/spreadsheet.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,309 @@
"use strict";
var React = require('react');
var ReactDOM = require('react-dom');
var $ = require('jquery');
var RowComponent = require('./row');
var Dispatcher = require('./dispatcher');
var Helpers = require('./helpers');
var SpreadsheetComponent = React.createClass({
spreadsheetId: null,
/**
* React 'getInitialState' method
*/
getInitialState: function() {
var initialData = this.props.initialData || {};
if (!initialData.rows) {
initialData.rows = [];
for (var i = 0; i < this.props.config.rows; i = i + 1) {
initialData.rows[i] = [];
for (var ci = 0; ci < this.props.config.columns; ci = ci + 1) {
initialData.rows[i][ci] = '';
}
}
}
return {
data: initialData,
selected: null,
lastBlurred: null,
selectedElement: null,
editing: false
};
},
/**
* React 'componentDidMount' method
*/
componentDidMount: function () {
this.bindKeyboard();
$('body').on('focus', 'input', function (e) {
$(this)
.one('mouseup', function () {
$(this).select();
return false;
})
.select();
});
},
/**
* React Render method
* @return {[JSX]} [JSX to render]
*/
render: function() {
var data = this.state.data,
config = this.props.config,
_cellClasses = this.props.cellClasses,
rows = [], key, i, cellClasses;
this.spreadsheetId = this.props.spreadsheetId || Helpers.makeSpreadsheetId();
// Sanity checks
if (!data.rows && !config.rows) {
return console.error('Table Component: Number of colums not defined in both data and config!');
}
// Create Rows
for (i = 0; i < data.rows.length; i = i + 1) {
key = 'row_' + i;
cellClasses = (_cellClasses && _cellClasses.rows && _cellClasses.rows[i]) ? _cellClasses.rows[i] : null;
rows.push(<RowComponent cells={data.rows[i]}
cellClasses={cellClasses}
uid={i}
key={key}
config={config}
selected={this.state.selected}
editing={this.state.editing}
handleSelectCell={this.handleSelectCell}
handleDoubleClickOnCell={this.handleDoubleClickOnCell}
handleCellBlur={this.handleCellBlur}
onCellValueChange={this.handleCellValueChange}
spreadsheetId={this.spreadsheetId}
className="cellComponent" />);
}
return (
<table tabIndex="0" data-spreasheet-id={this.spreadsheetId}>
<tbody>
{rows}
</tbody>
</table>
);
},
/**
* Binds the various keyboard events dispatched to table functions
*/
bindKeyboard: function () {
Dispatcher.setupKeyboardShortcuts($(ReactDOM.findDOMNode(this))[0], this.spreadsheetId);
Dispatcher.subscribe('up_keyup', data => {
this.navigateTable('up', data);
}, this.spreadsheetId);
Dispatcher.subscribe('down_keyup', data => {
this.navigateTable('down', data);
}, this.spreadsheetId);
Dispatcher.subscribe('left_keyup', data => {
this.navigateTable('left', data);
}, this.spreadsheetId);
Dispatcher.subscribe('right_keyup', data => {
this.navigateTable('right', data);
}, this.spreadsheetId);
Dispatcher.subscribe('tab_keyup', data => {
this.navigateTable('right', data, null, true);
}, this.spreadsheetId);
// Prevent brower's from jumping to URL bar
Dispatcher.subscribe('tab_keydown', data => {
if ($(document.activeElement) && $(document.activeElement)[0].tagName === 'INPUT') {
if (data.preventDefault) {
data.preventDefault();
} else {
// Oh, old IE, you 💩
data.returnValue = false;
}
}
}, this.spreadsheetId);
Dispatcher.subscribe('remove_keydown', data => {
if (!$(data.target).is('input, textarea')) {
if (data.preventDefault) {
data.preventDefault();
} else {
// Oh, old IE, you 💩
data.returnValue = false;
}
}
}, this.spreadsheetId);
Dispatcher.subscribe('enter_keyup', () => {
if (this.state.selectedElement) {
this.setState({editing: !this.state.editing});
}
$(ReactDOM.findDOMNode(this)).first().focus();
}, this.spreadsheetId);
// Go into edit mode when the user starts typing on a field
Dispatcher.subscribe('letter_keydown', () => {
if (!this.state.editing && this.state.selectedElement) {
Dispatcher.publish('editStarted', this.state.selectedElement, this.spreadsheetId);
this.setState({editing: true});
}
}, this.spreadsheetId);
// Delete on backspace and delete
Dispatcher.subscribe('remove_keyup', () => {
if (this.state.selected && !Helpers.equalCells(this.state.selected, this.state.lastBlurred)) {
this.handleCellValueChange(this.state.selected, '');
}
}, this.spreadsheetId);
},
/**
* Navigates the table and moves selection
* @param {string} direction [Direction ('up' || 'down' || 'left' || 'right')]
* @param {Array: [number: row, number: cell]} originCell [Origin Cell]
* @param {boolean} inEdit [Currently editing]
*/
navigateTable: function (direction, data, originCell, inEdit) {
// Only traverse the table if the user isn't editing a cell,
// unless override is given
if (!inEdit && this.state.editing) {
return false;
}
// Use the curently active cell if one isn't passed
if (!originCell) {
originCell = this.state.selectedElement;
}
// Prevent default
if (data.preventDefault) {
data.preventDefault();
} else {
// Oh, old IE, you 💩
data.returnValue = false;
}
var $origin = $(originCell),
cellIndex = $origin.index(),
target;
if (direction === 'up') {
target = $origin.closest('tr').prev().children().eq(cellIndex).find('span');
} else if (direction === 'down') {
target = $origin.closest('tr').next().children().eq(cellIndex).find('span');
} else if (direction === 'left') {
target = $origin.closest('td').prev().find('span');
} else if (direction === 'right') {
target = $origin.closest('td').next().find('span');
}
if (target.length > 0) {
target.click();
} else {
this.extendTable(direction, originCell);
}
},
/**
* Extends the table with an additional row/column, if permitted by config
* @param {string} direction [Direction ('up' || 'down' || 'left' || 'right')]
*/
extendTable: function (direction) {
var config = this.props.config,
data = this.state.data,
newRow, i;
if (direction === 'down' && config.canAddRow) {
newRow = [];
for (i = 0; i < this.state.data.rows[0].length; i = i + 1) {
newRow[i] = '';
}
data.rows.push(newRow);
Dispatcher.publish('rowCreated', data.rows.length, this.spreadsheetId);
return this.setState({data: data});
}
if (direction === 'right' && config.canAddColumn) {
for (i = 0; i < data.rows.length; i = i + 1) {
data.rows[i].push('');
}
Dispatcher.publish('columnCreated', data.rows[0].length, this.spreadsheetId);
return this.setState({data: data});
}
},
/**
* Callback for 'selectCell', updating the selected Cell
* @param {Array: [number: row, number: cell]} cell [Selected Cell]
* @param {object} cellElement [Selected Cell Element]
*/
handleSelectCell: function (cell, cellElement) {
Dispatcher.publish('cellSelected', cell, this.spreadsheetId);
$(ReactDOM.findDOMNode(this)).first().focus();
this.setState({
selected: cell,
selectedElement: cellElement
});
},
/**
* Callback for 'cellValueChange', updating the cell data
* @param {Array: [number: row, number: cell]} cell [Selected Cell]
* @param {object} newValue [Value to set]
*/
handleCellValueChange: function (cell, newValue) {
var data = this.state.data,
row = cell[0],
column = cell[1],
oldValue = data.rows[row][column];
Dispatcher.publish('cellValueChanged', [cell, newValue, oldValue], this.spreadsheetId);
data.rows[row][column] = newValue;
this.setState({
data: data
});
Dispatcher.publish('dataChanged', data, this.spreadsheetId);
},
/**
* Callback for 'doubleClickonCell', enabling 'edit' mode
*/
handleDoubleClickOnCell: function () {
this.setState({
editing: true
});
},
/**
* Callback for 'cellBlur'
*/
handleCellBlur: function (cell) {
if (this.state.editing) {
Dispatcher.publish('editStopped', this.state.selectedElement);
}
this.setState({
editing: false,
lastBlurred: cell
});
}
});
module.exports = SpreadsheetComponent;

Просмотреть файл

@ -0,0 +1,77 @@
body
{
background-color: #2C2C2C;
font-family: Lato, Helvetica, Arial, sans-serif;
color: #fff;
}
table
{
font-family: Lato, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #878787;
border-width: 0 0 1px 1px;
border-style: solid;
border-color: #1a1a1a;
border-spacing: 0;
background-color: #262626;
}
td, th
{
box-sizing: border-box;
position: relative;
display: inline-block;
height: 35px;
width: 75px;
margin: 0;
padding: 0;
text-align: center;
border-width: 1px 1px 0 0;
border-style: solid;
border-color: #1a1a1a;
}
td div, th div {
height: 100%;
}
td span
{
display: inline-block;
width: calc(100% - 8px);
height: calc(100% - 8px);
padding: 4px;
line-height: 26px;
}
td input
{
position: absolute;
z-index: 100;
font-family: Lato, Helvetica, Arial, sans-serif;
font-size: 11px;
display: inline-block;
width: calc(100% - 8px);
height: calc(100% - 8px);
margin: 0;
padding: 4px;
text-align: center;
color: #878787;
border: none;
background-color: #202020;
}
td.selected {
border: 2px solid #7F7F7F;
background-color: #2C2C2C;
}

Просмотреть файл

@ -0,0 +1,106 @@
body
{
font-family: Lato, Helvetica, Arial, sans-serif;
color: #fff;
background-color: #fff;
}
table
{
font-family: Calibri, 'Segoe UI', Thonburi, Arial, Verdana, sans-serif;
font-size: 14px;
border-spacing: 0;
color: #000;
border-width: 0 0 1px 1px;
border-style: solid;
border-color: #cacaca;
table-layout:fixed;
}
table:focus {
outline: none;
}
td,
th
{
position: relative;
display: inline-block;
box-sizing: border-box;
width: 80px;
height: 25px;
margin: 0;
padding: 0;
border-width: 1px 1px 0 0;
border-style: solid;
border-color: #cacaca;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
th
{
background-color: #f0f0f0;
}
td div,
th div
{
height: 100%;
}
td span, th span
{
line-height: 15px;
display: inline-block;
width: calc(100% - 8px);
height: calc(100% - 8px);
padding: 4px;
}
td input
{
font-family: Calibri, 'Segoe UI', Thonburi, Arial, Verdana, sans-serif;
font-size: 14px;
position: absolute;
z-index: 100;
display: inline-block;
width: calc(100% - 8px);
height: calc(100% - 8px);
margin: 0;
padding: 4px;
color: #000;
border: none;
background-color: #fff;
}
td.selected
{
border: 2px solid #1e6337;
}
td.selected span
{
padding: 3px 3px 3px 2px;
}
input:focus,
select:focus,
textarea:focus,
button:focus
{
outline: none;
}