diff --git a/packages/react-dynamic-forms/.storybook/addons.js b/packages/react-dynamic-forms/.storybook/addons.js new file mode 100644 index 0000000..693e0da --- /dev/null +++ b/packages/react-dynamic-forms/.storybook/addons.js @@ -0,0 +1,3 @@ +import "@storybook/addon-actions/register"; +import "@storybook/addon-links/register"; +import "@storybook/addon-knobs/register"; diff --git a/packages/react-dynamic-forms/.storybook/config.js b/packages/react-dynamic-forms/.storybook/config.js new file mode 100644 index 0000000..3543021 --- /dev/null +++ b/packages/react-dynamic-forms/.storybook/config.js @@ -0,0 +1,7 @@ +import { configure } from '@storybook/react'; + +function loadStories() { + require('../src/stories'); +} + +configure(loadStories, module); diff --git a/packages/react-dynamic-forms/.storybook/preview-head.html b/packages/react-dynamic-forms/.storybook/preview-head.html new file mode 100644 index 0000000..416f07c --- /dev/null +++ b/packages/react-dynamic-forms/.storybook/preview-head.html @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/packages/react-dynamic-forms/lib/components/AsyncChooser.js b/packages/react-dynamic-forms/lib/components/AsyncChooser.js new file mode 100644 index 0000000..1a2639b --- /dev/null +++ b/packages/react-dynamic-forms/lib/components/AsyncChooser.js @@ -0,0 +1,378 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.AsyncChooserGroup = exports.AsyncChooser = undefined; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _react = require("react"); + +var _react2 = _interopRequireDefault(_react); + +var _underscore = require("underscore"); + +var _underscore2 = _interopRequireDefault(_underscore); + +var _propTypes = require("prop-types"); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _flexboxReact = require("flexbox-react"); + +var _flexboxReact2 = _interopRequireDefault(_flexboxReact); + +var _immutable = require("immutable"); + +var _immutable2 = _interopRequireDefault(_immutable); + +var _formGroup = require("../js/formGroup"); + +var _formGroup2 = _interopRequireDefault(_formGroup); + +var _renderers = require("../js/renderers"); + +var _actions = require("../js/actions"); + +var _style = require("../js/style"); + +require("react-select/dist/react-select.css"); + +require("react-virtualized/styles.css"); + +require("react-virtualized-select/styles.css"); + +var _reactVirtualizedSelect = require("react-virtualized-select"); + +var _reactVirtualizedSelect2 = _interopRequireDefault(_reactVirtualizedSelect); + +require("../css/chooser.css"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /** + * Copyright (c) 2015 - present, The Regents of the University of California, + * through Lawrence Berkeley National Laboratory (subject to receipt + * of any required approvals from the U.S. Dept. of Energy). + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * React Form control to select an item from a list. The list is built from + * an async call. Once the call has been made and the options list if built + * the list is immutable. Also note that if a value is provided (current or + * default) that value will only actually show once the list is received. + */ +var AsyncChooser = exports.AsyncChooser = function (_React$Component) { + _inherits(AsyncChooser, _React$Component); + + function AsyncChooser(props) { + _classCallCheck(this, AsyncChooser); + + var _this = _possibleConstructorReturn(this, (AsyncChooser.__proto__ || Object.getPrototypeOf(AsyncChooser)).call(this, props)); + + _this.state = { isFocused: false, focusChooser: false }; + _this.handleChange = _this.handleChange.bind(_this); + _this.loadOptions = _this.loadOptions.bind(_this); + + _this.loadedOptions = _immutable2.default.List(); + return _this; + } + + _createClass(AsyncChooser, [{ + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleFocus", + value: function handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + }, { + key: "handleDone", + value: function handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + }, { + key: "handleCancel", + value: function handleCancel() { + if (this.props.onChange) { + var v = this.state.oldValue; + this.props.onChange(this.props.name, v); + } + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + }, { + key: "isEmpty", + value: function isEmpty(value) { + return _underscore2.default.isNull(value) || _underscore2.default.isUndefined(value) || value === ""; + } + }, { + key: "isMissing", + value: function isMissing() { + var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props.value; + + return this.props.required && !this.props.disabled && this.isEmpty(value); + } + }, { + key: "componentDidMount", + value: function componentDidMount() { + var missing = this.props.required && !this.props.disabled && (_underscore2.default.isNull(this.props.value) || _underscore2.default.isUndefined(this.props.value) || this.props.value === ""); + var missingCount = missing ? 1 : 0; + + if (this.props.onMissingCountChange) { + this.props.onMissingCountChange(this.props.name, missingCount); + } + } + }, { + key: "componentWillReceiveProps", + value: function componentWillReceiveProps(nextProps) { + if (nextProps.selected && this.props.edit !== nextProps.edit && nextProps.edit === true) { + this.setState({ focusChooser: true }); + } + if (this.props.value !== nextProps.value || !this.props.value && nextProps.value || this.props.value && !nextProps.value) { + // The value might have been missing and is now set explicitly + // with a prop + var missing = this.props.required && !this.props.disabled && (_underscore2.default.isNull(nextProps.value) || _underscore2.default.isUndefined(nextProps.value) || nextProps.value === ""); + var missingCount = missing ? 1 : 0; + + if (this.props.onMissingCountChange) { + this.props.onMissingCountChange(this.props.name, missingCount); + } + } + } + }, { + key: "componentDidUpdate", + value: function componentDidUpdate() { + if (this.state.focusChooser) { + this.chooser.focus(); + this.setState({ focusChooser: false }); + } + } + }, { + key: "handleChange", + value: function handleChange(item) { + var _ref = item || {}, + value = _ref.value, + label = _ref.label; + + var isMissing = this.props.required && _underscore2.default.isNull(item); + var id = !isMissing && !_underscore2.default.isNaN(Number(value)) ? +value : value; + + // Callbacks + if (this.props.onChange) { + this.props.onChange(this.props.name, _immutable2.default.Map({ id: id, label: label })); + } + if (this.props.onMissingCountChange) { + this.props.onMissingCountChange(this.props.name, isMissing ? 1 : 0); + } + } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); + } + }, { + key: "getOptionList", + value: function getOptionList(options) { + var _this2 = this; + + return options.map(function (item) { + var disabled = false; + var isDisabled = item.has("disabled") && item.get("disabled") === true; + if (_underscore2.default.contains(_this2.props.disableList, item.get("id")) || isDisabled) { + disabled = true; + } + return { value: item.get("id"), label: item.get("label"), disabled: disabled }; + }).toJS(); + } + }, { + key: "loadOptions", + value: function loadOptions(input, cb) { + var _this3 = this; + + if (this.cachedOptions) { + cb(null, { + options: this.getOptionList(this.cachedOptions), + complete: true + }); + return; + } + + this.props.loader(input, function (err, options) { + cb(err, { + options: _this3.getOptionList(options), + complete: true + }); + _this3.cachedOptions = options; + }); + } + }, { + key: "render", + value: function render() { + var _this4 = this; + + var choice = this.props.value ? this.props.value.get("id") : null; + var isMissing = this.isMissing(this.props.value); + + if (this.props.edit) { + var chooserStyle = { marginBottom: 10, width: "100%" }; + var clearable = this.props.allowSingleDeselect; + var matchPos = this.props.searchContains ? "any" : "start"; + + return _react2.default.createElement( + _flexboxReact2.default, + { flexDirection: "row", style: { width: "100%" } }, + _react2.default.createElement( + "div", + { style: chooserStyle, onFocus: function onFocus(e) { + return _this4.handleFocus(e); + } }, + _react2.default.createElement(_reactVirtualizedSelect2.default, { + async: true, + ref: function ref(chooser) { + _this4.chooser = chooser; + }, + className: isMissing ? "is-missing" : "", + value: choice, + loadOptions: this.loadOptions, + openOnFocus: true, + disabled: this.props.disabled, + searchable: true, + matchPos: matchPos, + placeholder: this.props.placeholder, + clearable: clearable, + onChange: function onChange(v) { + return _this4.handleChange(v); + } + }) + ), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 5 } }, + _react2.default.createElement( + "span", + { + style: (0, _style.inlineDoneButtonStyle)(5), + onClick: function onClick() { + return _this4.handleDone(); + } + }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this4.handleCancel(); + } + }, + "CANCEL" + ) + ) : _react2.default.createElement("div", null) + ); + } else { + var label = this.props.value.get("label"); + var View = this.props.view || _renderers.textView; + var style = (0, _style.inlineChooserStyle)(false, false, !!View); + var text = _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + View(label) + ); + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this4.handleEditItem(); + }); + + return _react2.default.createElement( + "div", + { + style: style, + onMouseEnter: function onMouseEnter() { + return _this4.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this4.handleMouseLeave(); + } + }, + text, + edit + ); + } + } + }]); + + return AsyncChooser; +}(_react2.default.Component); + +AsyncChooser.propTypes = { + /** + * The identifier of the field being edited. References back into + * the Form's Schema for additional properties of this field + */ + field: _propTypes2.default.string.isRequired, + + /** + * Pass in the available list of options as a Immutable.List of objects. Each + * object contains a "id" and the user visible "label". For example: + * + * Immutable.fromJS([ + * { id: 1, label: "Dog" }, + * { id: 2, label: "Duck" }, + * { id: 3, label: "Cat" } + * ]); + * + */ + choiceList: _propTypes2.default.object.isRequired, + + /** + * If the chooser should be shown as disabled + */ + disabled: _propTypes2.default.bool, + + /** + * Customize the horizontal size of the Chooser + */ + width: _propTypes2.default.number, + + /** + * Add a [x] icon to the chooser allowing the user to clear the selected value + */ + allowSingleDeselect: _propTypes2.default.bool, + + /** + * Can be "any" or "start", indicating how the search is matched within the items (anywhere, or starting with) + */ + searchContains: _propTypes2.default.oneOf(["any", "start"]) +}; + +AsyncChooser.defaultProps = { + disabled: false, + disableSearch: false, + searchContains: "any", + allowSingleDeselect: false, + width: 300 +}; + +var AsyncChooserGroup = exports.AsyncChooserGroup = (0, _formGroup2.default)(AsyncChooser, "AsyncChooser"); \ No newline at end of file diff --git a/packages/react-dynamic-forms/lib/components/CheckBoxes.js b/packages/react-dynamic-forms/lib/components/CheckBoxes.js index 9743514..458e8c1 100644 --- a/packages/react-dynamic-forms/lib/components/CheckBoxes.js +++ b/packages/react-dynamic-forms/lib/components/CheckBoxes.js @@ -22,6 +22,12 @@ var _formGroup = require("../js/formGroup"); var _formGroup2 = _interopRequireDefault(_formGroup); +var _renderers = require("../js/renderers"); + +var _actions = require("../js/actions"); + +var _style = require("../js/style"); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } @@ -45,10 +51,13 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" var CheckBoxes = function (_React$Component) { _inherits(CheckBoxes, _React$Component); - function CheckBoxes() { + function CheckBoxes(props) { _classCallCheck(this, CheckBoxes); - return _possibleConstructorReturn(this, (CheckBoxes.__proto__ || Object.getPrototypeOf(CheckBoxes)).apply(this, arguments)); + var _this = _possibleConstructorReturn(this, (CheckBoxes.__proto__ || Object.getPrototypeOf(CheckBoxes)).call(this, props)); + + _this.state = { isFocused: false }; + return _this; } _createClass(CheckBoxes, [{ @@ -61,6 +70,35 @@ var CheckBoxes = function (_React$Component) { } } } + }, { + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleFocus", + value: function handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + }, { + key: "handleKeyPress", + value: function handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } + } + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } }, { key: "handleChange", value: function handleChange(i) { @@ -77,6 +115,29 @@ var CheckBoxes = function (_React$Component) { this.props.onChange(this.props.name, value); } } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); + } + }, { + key: "handleDone", + value: function handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + }, { + key: "handleCancel", + value: function handleCancel() { + if (this.props.onChange) { + var v = this.state.oldValue; + this.props.onChange(this.props.name, v); + } + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); + } }, { key: "isEmpty", value: function isEmpty(value) { @@ -92,30 +153,28 @@ var CheckBoxes = function (_React$Component) { return this.props.required && !this.props.disabled && this.isEmpty(value); } - }, { - key: "inlineStyle", - value: function inlineStyle(hasError, isMissing) { - var color = "inherited"; - var background = "inherited"; - if (hasError) { - color = "#b94a48"; - background = "#fff0f3"; - } else if (isMissing) { - background = "floralwhite"; - } - return { - color: color, - background: background, - width: "100%", - paddingLeft: 3 - }; - } + + // border-style: solid; + // border-radius: 2px; + // border-width: 1px; + // padding: 5px; + // border-color: #ececec; + }, { key: "render", value: function render() { var _this2 = this; if (this.props.edit) { + var editStyle = { + borderStyle: "solid", + borderRadius: 2, + borderWidth: 1, + padding: 5, + borderColor: "#ececec", + marginBottom: 5 + }; + var items = []; this.props.optionList.forEach(function (option, i) { items.push(_react2.default.createElement( @@ -138,14 +197,68 @@ var CheckBoxes = function (_React$Component) { return _react2.default.createElement( "div", - null, - items + { style: { marginBottom: 5 } }, + _react2.default.createElement( + "div", + { + style: editStyle, + onFocus: function onFocus(e) { + return _this2.handleFocus(e); + }, + onKeyUp: function onKeyUp(e) { + return _this2.handleKeyPress(e); + } + }, + items + ), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 5 } }, + _react2.default.createElement( + "span", + { + style: (0, _style.inlineDoneButtonStyle)(0), + onClick: function onClick() { + return _this2.handleDone(); + } + }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this2.handleCancel(); + } + }, + "CANCEL" + ) + ) : _react2.default.createElement("div", null) ); } else { + var view = this.props.view || _renderers.textView; + var text = _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + view(this.props.value.join(", ")) + ); + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this2.handleEditItem(); + }); return _react2.default.createElement( "div", - { style: this.inlineStyle(false, false) }, - this.props.value.join(", ") + { + style: (0, _style.inlineStyle)(false, false), + onMouseEnter: function onMouseEnter() { + return _this2.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this2.handleMouseLeave(); + } + }, + text, + edit ); } } diff --git a/packages/react-dynamic-forms/lib/components/Chooser.js b/packages/react-dynamic-forms/lib/components/Chooser.js index 4489d00..ab0025d 100644 --- a/packages/react-dynamic-forms/lib/components/Chooser.js +++ b/packages/react-dynamic-forms/lib/components/Chooser.js @@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); +exports.ChooserGroup = exports.Chooser = undefined; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); @@ -18,10 +19,20 @@ var _propTypes = require("prop-types"); var _propTypes2 = _interopRequireDefault(_propTypes); +var _flexboxReact = require("flexbox-react"); + +var _flexboxReact2 = _interopRequireDefault(_flexboxReact); + var _formGroup = require("../js/formGroup"); var _formGroup2 = _interopRequireDefault(_formGroup); +var _renderers = require("../js/renderers"); + +var _actions = require("../js/actions"); + +var _style = require("../js/style"); + require("react-select/dist/react-select.css"); require("react-virtualized/styles.css"); @@ -53,7 +64,7 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" /** * React Form control to select an item from a list. */ -var Chooser = function (_React$Component) { +var Chooser = exports.Chooser = function (_React$Component) { _inherits(Chooser, _React$Component); function Chooser(props) { @@ -61,11 +72,47 @@ var Chooser = function (_React$Component) { var _this = _possibleConstructorReturn(this, (Chooser.__proto__ || Object.getPrototypeOf(Chooser)).call(this, props)); + _this.state = { isFocused: false, focusChooser: false }; _this.handleChange = _this.handleChange.bind(_this); return _this; } _createClass(Chooser, [{ + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleFocus", + value: function handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + }, { + key: "handleDone", + value: function handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + }, { + key: "handleCancel", + value: function handleCancel() { + if (this.props.onChange) { + var v = this.state.oldValue; + this.props.onChange(this.props.name, v); + } + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + }, { key: "isEmpty", value: function isEmpty(value) { return _underscore2.default.isNull(value) || _underscore2.default.isUndefined(value) || value === ""; @@ -90,6 +137,9 @@ var Chooser = function (_React$Component) { }, { key: "componentWillReceiveProps", value: function componentWillReceiveProps(nextProps) { + if (nextProps.selected && this.props.edit !== nextProps.edit && nextProps.edit === true) { + this.setState({ focusChooser: true }); + } if (this.props.value !== nextProps.value || !this.props.value && nextProps.value || this.props.value && !nextProps.value) { // The value might have been missing and is now set explicitly // with a prop @@ -101,6 +151,14 @@ var Chooser = function (_React$Component) { } } } + }, { + key: "componentDidUpdate", + value: function componentDidUpdate() { + if (this.state.focusChooser) { + this.chooser.focus(); + this.setState({ focusChooser: false }); + } + } }, { key: "handleChange", value: function handleChange(v) { @@ -121,9 +179,11 @@ var Chooser = function (_React$Component) { if (this.props.onMissingCountChange) { this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); } - if (this.props.onBlur) { - this.props.onBlur(this.props.name); - } + } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); } }, { key: "getOptionList", @@ -194,99 +254,127 @@ var Chooser = function (_React$Component) { if (this.props.edit) { var className = ""; - var chooserStyle = { marginBottom: 10 }; + var chooserStyle = { marginBottom: 10, width: "100%" }; var clearable = this.props.allowSingleDeselect; var searchable = !this.props.disableSearch; var matchPos = this.props.searchContains ? "any" : "start"; + var ctl = void 0; if (searchable) { var options = this.getFilteredOptionList(null); var labelList = _underscore2.default.map(options, function (item) { return item.label; }); var key = labelList + "--" + choice; - return _react2.default.createElement( - "div", - { className: className, style: chooserStyle }, - _react2.default.createElement(_reactVirtualizedSelect2.default, { - className: isMissing ? "is-missing" : "", - key: key, - value: choice, - options: options, - disabled: this.props.disabled, - searchable: true, - matchPos: matchPos, - placeholder: this.props.placeholder, - onChange: function onChange(v) { - return _this5.handleChange(v); - } - }) - ); + ctl = _react2.default.createElement(_reactVirtualizedSelect2.default, { + ref: function ref(chooser) { + _this5.chooser = chooser; + }, + className: isMissing ? "is-missing" : "", + key: key, + value: choice, + options: options, + openOnFocus: true, + disabled: this.props.disabled, + searchable: true, + matchPos: matchPos, + placeholder: this.props.placeholder, + onChange: function onChange(v) { + return _this5.handleChange(v); + } + }); } else { var _options = this.getOptionList(); var _labelList = _underscore2.default.map(_options, function (item) { return item.label; }); var _key = _labelList + "--" + choice; - return _react2.default.createElement( + ctl = _react2.default.createElement(_reactVirtualizedSelect2.default, { + ref: function ref(chooser) { + _this5.chooser = chooser; + }, + className: isMissing ? "is-missing" : "", + key: _key, + value: choice, + options: _options, + openOnFocus: true, + disabled: this.props.disabled, + searchable: false, + matchPos: matchPos, + placeholder: this.props.placeholder, + clearable: clearable, + onChange: function onChange(v) { + return _this5.handleChange(v); + } + }); + } + return _react2.default.createElement( + _flexboxReact2.default, + { flexDirection: "row", style: { width: "100%" } }, + _react2.default.createElement( "div", - { className: className, style: chooserStyle }, - _react2.default.createElement(_reactVirtualizedSelect2.default, { - className: isMissing ? "is-missing" : "", - key: _key, - value: choice, - options: _options, - disabled: this.props.disabled, - searchable: false, - matchPos: matchPos, - placeholder: this.props.placeholder, - clearable: clearable, - onChange: function onChange(v) { - return _this5.handleChange(v); + { + className: className, + style: chooserStyle, + onFocus: function onFocus(e) { + return _this5.handleFocus(e); } - }) - ); - } + }, + ctl + ), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 5 } }, + _react2.default.createElement( + "span", + { + style: (0, _style.inlineDoneButtonStyle)(5), + onClick: function onClick() { + return _this5.handleDone(); + } + }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this5.handleCancel(); + } + }, + "CANCEL" + ) + ) : _react2.default.createElement("div", null) + ); } else { - var view = this.props.view; - var text = this.getCurrentChoiceLabel(); - var color = ""; - var background = ""; - if (isMissing) { - text = " "; - background = "floralwhite"; - } - - var viewStyle = { - color: color, - background: background, - minHeight: 23, - width: "100%", - paddingLeft: 3 - }; - - var style = { - color: color, - background: background, - height: 23, - width: "100%", - paddingLeft: 3 - }; - - if (!view) { - return _react2.default.createElement( - "div", - { style: style }, - text - ); - } else { - return _react2.default.createElement( - "div", - { style: viewStyle }, - view(text, choice) - ); - } + var s = this.getCurrentChoiceLabel(); + var view = this.props.view || _renderers.textView; + var style = (0, _style.inlineChooserStyle)(false, false, !!view); + + var text = _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + view(s, choice) + ); + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this5.handleEditItem(); + }); + return _react2.default.createElement( + "div", + { + style: style, + onMouseEnter: function onMouseEnter() { + return _this5.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this5.handleMouseLeave(); + } + }, + text, + edit + ); } } }]); @@ -296,43 +384,48 @@ var Chooser = function (_React$Component) { Chooser.propTypes = { /** - * choiceList* - Pass in the available list of options as a list of - * objects. For example: - * ``` - * [ - * {id: 1: label: "cat"}, - * {id: 2: label: "dog"}, - * ... - * ] - * ``` + * The identifier of the field being edited. References back into + * the Form's Schema for additional properties of this field */ - choiceList: _propTypes2.default.object, + field: _propTypes2.default.string.isRequired, - disabled: _propTypes2.default.bool, + /** + * Pass in the available list of options as a Immutable.List of objects. Each + * object contains a "id" and the user visible "label". For example: + * + * Immutable.fromJS([ + * { id: 1, label: "Dog" }, + * { id: 2, label: "Duck" }, + * { id: 3, label: "Cat" } + * ]); + * + */ + choiceList: _propTypes2.default.object.isRequired, /** - * disableSearch* - If true the chooser becomes a simple pulldown menu - * rather than allowing the user to type into it. + * If the chooser should be shown as disabled */ - disableSearch: _propTypes2.default.bool, + disabled: _propTypes2.default.bool, /** - * width - Customize the horizontal size of the Chooser + * If true the chooser becomes a simple pulldown menu + * rather than allowing the user to type into it to search + * though the entries */ - width: _propTypes2.default.number, + disableSearch: _propTypes2.default.bool, /** - * field - The identifier of the field being edited + * Customize the horizontal size of the Chooser */ - field: _propTypes2.default.string, + width: _propTypes2.default.number, /** - * allowSingleDeselect - Add a [x] icon to the chooser allowing the user to clear the selected value + * Add a [x] icon to the chooser allowing the user to clear the selected value */ allowSingleDeselect: _propTypes2.default.bool, /** - * searchContains - Can be "any" or "start", indicating how the search is matched within the items (anywhere, or starting with) + * Can be "any" or "start", indicating how the search is matched within the items (anywhere, or starting with) */ searchContains: _propTypes2.default.oneOf(["any", "start"]) }; @@ -345,4 +438,4 @@ Chooser.defaultProps = { width: 300 }; -exports.default = (0, _formGroup2.default)(Chooser); \ No newline at end of file +var ChooserGroup = exports.ChooserGroup = (0, _formGroup2.default)(Chooser, "Chooser"); \ No newline at end of file diff --git a/packages/react-dynamic-forms/lib/components/DateEdit.js b/packages/react-dynamic-forms/lib/components/DateEdit.js index 0058cf7..d889959 100644 --- a/packages/react-dynamic-forms/lib/components/DateEdit.js +++ b/packages/react-dynamic-forms/lib/components/DateEdit.js @@ -26,10 +26,20 @@ var _reactDatepicker = require("react-datepicker"); var _reactDatepicker2 = _interopRequireDefault(_reactDatepicker); +var _flexboxReact = require("flexbox-react"); + +var _flexboxReact2 = _interopRequireDefault(_flexboxReact); + var _formGroup = require("../js/formGroup"); var _formGroup2 = _interopRequireDefault(_formGroup); +var _renderers = require("../js/renderers"); + +var _actions = require("../js/actions"); + +var _style = require("../js/style"); + require("react-datepicker/dist/react-datepicker.css"); require("../css/dateedit.css"); @@ -59,23 +69,16 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" var DateEdit = function (_React$Component) { _inherits(DateEdit, _React$Component); - function DateEdit() { + function DateEdit(props) { _classCallCheck(this, DateEdit); - return _possibleConstructorReturn(this, (DateEdit.__proto__ || Object.getPrototypeOf(DateEdit)).apply(this, arguments)); + var _this = _possibleConstructorReturn(this, (DateEdit.__proto__ || Object.getPrototypeOf(DateEdit)).call(this, props)); + + _this.state = { isFocused: false }; + return _this; } _createClass(DateEdit, [{ - key: "isEmpty", - value: function isEmpty(value) { - return _underscore2.default.isNull(value) || _underscore2.default.isUndefined(value) || value === ""; - } - }, { - key: "isMissing", - value: function isMissing(v) { - return this.props.required && !this.props.disabled && this.isEmpty(v); - } - }, { key: "componentWillReceiveProps", value: function componentWillReceiveProps(nextProps) { if (this.props.value && nextProps.value) { @@ -95,6 +98,33 @@ var DateEdit = function (_React$Component) { this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); } } + }, { + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleFocus", + value: function handleFocus() { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + }, { + key: "handleKeyPress", + value: function handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } + } + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } }, { key: "handleDateChange", value: function handleDateChange(v) { @@ -108,28 +138,39 @@ var DateEdit = function (_React$Component) { if (this.props.onMissingCountChange) { this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); } + } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); + } + }, { + key: "isEmpty", + value: function isEmpty(value) { + return _underscore2.default.isNull(value) || _underscore2.default.isUndefined(value) || value === ""; + } + }, { + key: "isMissing", + value: function isMissing(v) { + return this.props.required && !this.props.disabled && this.isEmpty(v); + } + }, { + key: "handleDone", + value: function handleDone() { if (this.props.onBlur) { this.props.onBlur(this.props.name); } + this.setState({ isFocused: false, hover: false, oldValue: null }); } }, { - key: "inlineStyle", - value: function inlineStyle(hasError, isMissing) { - var color = "inherited"; - var background = "inherited"; - if (hasError) { - color = "#b94a48"; - background = "#fff0f3"; - } else if (isMissing) { - background = "floralwhite"; + key: "handleCancel", + value: function handleCancel() { + if (this.props.onChange) { + var v = this.state.oldValue; + this.props.onChange(this.props.name, v); } - return { - color: color, - background: background, - height: 23, - width: "100%", - paddingLeft: 3 - }; + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); } }, { key: "render", @@ -148,39 +189,84 @@ var DateEdit = function (_React$Component) { className += " is-missing"; } + var canCommit = !isMissing; + if (this.props.edit) { return _react2.default.createElement( - "div", - null, - _react2.default.createElement( - "div", - null, - _react2.default.createElement(_reactDatepicker2.default, { - key: "date", - ref: function ref(input) { - _this2.textInput = input; + _flexboxReact2.default, + { flexDirection: "row", style: { width: "100%" } }, + _react2.default.createElement(_reactDatepicker2.default, { + autofocus: true, + key: "date", + ref: function ref(input) { + _this2.textInput = input; + }, + className: className, + disabled: this.props.disabled, + placeholderText: this.props.placeholder, + selected: selected, + onChange: function onChange(v) { + return _this2.handleDateChange(v); + }, + onFocus: function onFocus(e) { + return _this2.handleFocus(e); + }, + onKeyUp: function onKeyUp(e) { + return _this2.handleKeyPress(e); + } + }), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 3 } }, + canCommit ? _react2.default.createElement( + "span", + { + style: (0, _style.inlineDoneButtonStyle)(5, true), + onClick: function onClick() { + return _this2.handleDone(); + } + }, + "DONE" + ) : _react2.default.createElement( + "span", + { style: (0, _style.inlineDoneButtonStyle)(5, false) }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this2.handleCancel(); + } }, - className: className, - disabled: this.props.disabled, - placeholderText: this.props.placeholder, - selected: selected, - onChange: function onChange(v) { - return _this2.handleDateChange(v); - } - }) - ) + "CANCEL" + ) + ) : _react2.default.createElement("div", null) ); } else { - var hasError = false; - var text = selected ? selected.format("MM/DD/YYYY") : ""; - if (isMissing) { - text = " "; - } - var style = this.inlineStyle(hasError, isMissing); + var view = this.props.view || (0, _renderers.dateView)("MM/DD/YYYY"); + var text = _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + view(selected) + ); + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this2.handleEditItem(); + }); return _react2.default.createElement( "div", - { style: style }, - text + { + style: (0, _style.inlineStyle)(false, false), + onMouseEnter: function onMouseEnter() { + return _this2.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this2.handleMouseLeave(); + } + }, + text, + edit ); } } diff --git a/packages/react-dynamic-forms/lib/components/Form.js b/packages/react-dynamic-forms/lib/components/Form.js index 9c9ddde..1fa24ae 100644 --- a/packages/react-dynamic-forms/lib/components/Form.js +++ b/packages/react-dynamic-forms/lib/components/Form.js @@ -147,11 +147,15 @@ var Form = function (_React$Component) { props.hidden = false; props.disabled = false; + props.selected = false; props.edit = false; props.showRequired = true; if (this.props.edit === _constants.FormEditStates.SELECTED) { - props.edit = this.state.selection === fieldName; + if (this.state.selection === fieldName) { + props.edit = true; + props.selected = true; + } props.showRequired = props.edit; props.allowEdit = true; } else if (this.props.edit === _constants.FormEditStates.ALWAYS) { @@ -161,6 +165,7 @@ var Form = function (_React$Component) { } if (this.props.edit === _constants.FormEditStates.TABLE) { + props.allowEdit = false; props.layout = _constants.FormGroupLayout.INLINE; } else { props.layout = this.props.groupLayout; @@ -184,9 +189,10 @@ var Form = function (_React$Component) { props.validation = formRules[fieldName].validation; } - // Field value + // Field value (current and initial) if (this.props.value.has(fieldName)) { props.value = this.props.value.get(fieldName); + props.initialValue = this.props.initialValue ? this.props.initialValue.get(fieldName) : null; } // Callbacks @@ -290,6 +296,7 @@ var Form = function (_React$Component) { if (_this3.props.onErrorCountChange) { _this3.props.onErrorCountChange(_this3.props.name, errorCount, errorFields); } + _this3._pendingErrors = null; } // On change callback @@ -517,25 +524,6 @@ var Form = function (_React$Component) { var formHiddenList = this.getHiddenFields(formFields); var formState = { formFields: formFields, formRules: formRules, formHiddenList: formHiddenList }; - /* -
-
- - -
-
- - -
-
- -
- -
- */ - var formClass = this.props.formClassName; if (this.props.inline) { formClass += "form-inline"; @@ -588,7 +576,8 @@ exports.default = Form; Form.propTypes = { - value: _propTypes2.default.object + value: _propTypes2.default.object, + initialValue: _propTypes2.default.object }; Form.defaultProps = { diff --git a/packages/react-dynamic-forms/lib/components/List.js b/packages/react-dynamic-forms/lib/components/List.js index 016d500..7628870 100644 --- a/packages/react-dynamic-forms/lib/components/List.js +++ b/packages/react-dynamic-forms/lib/components/List.js @@ -22,6 +22,8 @@ var _CSSTransitionGroup = require("react-transition-group/CSSTransitionGroup"); var _CSSTransitionGroup2 = _interopRequireDefault(_CSSTransitionGroup); +var _style = require("../js/style"); + require("../css/list.css"); require("../css/icon.css"); @@ -63,13 +65,28 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" var List = function (_React$Component) { _inherits(List, _React$Component); - function List() { + function List(props) { _classCallCheck(this, List); - return _possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).apply(this, arguments)); + var _this = _possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).call(this, props)); + + _this.state = { + hover: false + }; + return _this; } _createClass(List, [{ + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { key: "addItem", value: function addItem() { if (this.props.onAddItem) { @@ -90,6 +107,14 @@ var List = function (_React$Component) { this.props.onSelectItem(index); } } + }, { + key: "revertItem", + value: function revertItem(index) { + if (this.props.onRevertItem) { + this.props.onRevertItem(index); + this.selectItem(null); + } + } }, { key: "handleDeselect", value: function handleDeselect() { @@ -104,10 +129,12 @@ var List = function (_React$Component) { var addMinus = this.props.canRemoveItems; var addEdit = this.props.canEditItems; + var mouseOver = this.state.hover; + // Plus [+] icon - var plus = void 0; - if (addPlus) { - plus = _react2.default.createElement("i", { + var plusIcon = void 0; + if (addPlus && mouseOver) { + plusIcon = _react2.default.createElement("i", { key: "plus", className: "glyphicon glyphicon-plus icon add-action", onClick: function onClick() { @@ -115,9 +142,12 @@ var List = function (_React$Component) { } }); } else { - plus = _react2.default.createElement("div", null); + plusIcon = _react2.default.createElement("div", null); } + var LISTWIDTH = 600; + var ICONWIDTH = 28; + // Build the item list, which is a list of table rows, each row containing // an item and a [-] icon used for removing that item. var itemList = _underscore2.default.map(this.props.items, function (item, index) { @@ -142,107 +172,161 @@ var List = function (_React$Component) { isEditable = true; } if (isEditable) { - if (addMinus && !itemMinusHide) { + if (addMinus && !itemMinusHide && mouseOver) { minus = _react2.default.createElement("i", { id: index, key: minusActionKey, - className: "glyphicon glyphicon-remove hostile_icon delete-action", - onClick: function onClick() { - return _this2.removeItem(index); - } + className: "glyphicon glyphicon-remove hostile_icon delete-action" }); } else { listEditItemClass += " no-controls"; minus = _react2.default.createElement("div", { className: "icon delete-action" }); } - var flip = { - transform: "scaleX(-1)", - fontSize: 10 - }; - // Edit item icon - if (addEdit) { - if (isBeingEdited) { - edit = _react2.default.createElement("i", { - id: index, - key: minusActionKey, - className: "glyphicon glyphicon-chevron-down icon edit-action active", - onClick: function onClick() { - return _this2.selectItem(index); - } - }); - } else { - edit = _react2.default.createElement("i", { - id: index, - key: minusActionKey, - style: flip, - className: "glyphicon glyphicon-chevron-left icon edit-action", - onClick: function onClick() { - return _this2.selectItem(index); - } - }); - } + if (addEdit && mouseOver) { + edit = _react2.default.createElement("i", { + id: index, + key: minusActionKey, + style: { paddingLeft: 5, paddingRight: 5 }, + className: "glyphicon glyphicon-pencil icon edit-action active" + }); } } var minusAction = addMinus ? _react2.default.createElement( _flexboxReact2.default, - { width: "28px" }, + { width: "28px", onClick: function onClick() { + return _this2.removeItem(index); + } }, _react2.default.createElement( "span", - { key: actionSpanKey, className: "icon", style: { background: "white" } }, + { + key: actionSpanKey, + className: "icon", + style: { paddingLeft: 5, paddingRight: 5 } + }, minus ) - ) : _react2.default.createElement("div", null); + ) : _react2.default.createElement("div", { style: { height: 30 } }); var editAction = addEdit ? _react2.default.createElement( _flexboxReact2.default, - { width: "28px" }, + { + width: "28px", + onClick: function onClick() { + _this2.selectItem(index); + } + }, _react2.default.createElement( "span", - { - key: actionSpanKey, - className: "icon", - style: { background: "white", verticalAlign: "top" } - }, + { key: actionSpanKey, className: "icon", style: { verticalAlign: "top" } }, edit ) ) : _react2.default.createElement("div", null); // JSX for each row, includes: UI Item and [x] remove item button - return _react2.default.createElement( - "li", - { - height: "80px", - key: itemKey, - className: "esnet-forms-list-item", - style: { - borderBottomStyle: "solid", - borderBottomColor: "#DDD", - borderBottomWidth: 1 - } - }, - _react2.default.createElement( - _flexboxReact2.default, - { flexDirection: "row" }, - minusAction, - editAction, + if (!isBeingEdited) { + return _react2.default.createElement( + "li", + { + height: "80px", + width: "600px", + key: itemKey, + className: "esnet-forms-list-item", + style: { + borderBottomStyle: "solid", + borderBottomColor: "#DDD", + borderBottomWidth: 1 + } + }, _react2.default.createElement( _flexboxReact2.default, - { flexGrow: 1 }, + { flexDirection: "row", style: { width: "100%", paddingTop: 5 } }, _react2.default.createElement( - "span", - { key: itemSpanKey, className: listEditItemClass }, - item - ) + _flexboxReact2.default, + { style: { width: LISTWIDTH - ICONWIDTH * 2 } }, + _react2.default.createElement( + "span", + { key: itemSpanKey, className: listEditItemClass }, + item + ) + ), + minusAction, + editAction ) - ) - ); + ); + } else { + return _react2.default.createElement( + "li", + { + height: "80px", + key: itemKey, + className: "esnet-forms-list-item", + style: { + borderBottomStyle: "solid", + borderBottomColor: "#DDD", + borderBottomWidth: 1 + } + }, + _react2.default.createElement( + _flexboxReact2.default, + { + flexDirection: "row", + style: { width: "100%", paddingTop: 10, paddingBottom: 10 } + }, + _react2.default.createElement( + _flexboxReact2.default, + { flexDirection: "column" }, + _react2.default.createElement( + _flexboxReact2.default, + { style: { width: LISTWIDTH - ICONWIDTH * 2 } }, + _react2.default.createElement( + "span", + { key: itemSpanKey, className: listEditItemClass }, + item + ) + ), + _react2.default.createElement( + _flexboxReact2.default, + { + style: { fontSize: 12, marginLeft: _this2.props.buttonIndent } + }, + _this2.props.canCommitItem ? _react2.default.createElement( + "span", + { + style: (0, _style.inlineDoneButtonStyle)(0, true), + onClick: function onClick() { + return _this2.handleDeselect(); + } + }, + "DONE" + ) : _react2.default.createElement( + "span", + { style: (0, _style.inlineDoneButtonStyle)(0, false) }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this2.revertItem(index); + } + }, + "CANCEL" + ) + ) + ), + minusAction + ) + ); + } }); // Build the [+] elements - if (addPlus) { + var plus = void 0; + if (addPlus && mouseOver) { if (this.props.plusElement) { plus = this.props.plusElement; } else { @@ -257,18 +341,50 @@ var List = function (_React$Component) { { key: "plus", className: "icon", - style: { background: "white", verticalAlign: "top", fontSize: 10 } + style: { verticalAlign: "top", fontSize: 10 } }, - plus + plusIcon ) ), _react2.default.createElement(_flexboxReact2.default, { width: "28px" }) ); } } else { - plus = _react2.default.createElement("div", null); + plus = _react2.default.createElement("div", { style: { height: 35 } }); } + // + // Build the header + // + + var headerStyle = { + fontSize: 11, + paddingTop: 3, + height: 20, + color: "#9a9a9a", + borderBottom: "#ddd", + borderBottomStyle: "solid", + borderBottomWidth: 1 + }; + + var headerItems = _underscore2.default.map(this.props.header, function (size, label) { + return _react2.default.createElement( + _flexboxReact2.default, + { width: size + "px" }, + _react2.default.createElement( + "span", + { style: { verticalAlign: "top", fontSize: 10, paddingLeft: 3 } }, + label + ) + ); + }); + + var header = this.props.header ? _react2.default.createElement( + _flexboxReact2.default, + { flexDirection: "row", style: headerStyle }, + headerItems + ) : _react2.default.createElement("div", null); + // // Build the table of item rows, with the [+] at the bottom if required // @@ -277,11 +393,19 @@ var List = function (_React$Component) { "div", { style: { + width: 600, borderTopStyle: "solid", borderTopWidth: 1, borderTopColor: "#DDD" + }, + onMouseEnter: function onMouseEnter() { + return _this2.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this2.handleMouseLeave(); } }, + header, _react2.default.createElement( "ul", { className: "esnet-forms-listeditview-container" }, diff --git a/packages/react-dynamic-forms/lib/components/RadioButtons.js b/packages/react-dynamic-forms/lib/components/RadioButtons.js index 6e4cf9c..aef1706 100644 --- a/packages/react-dynamic-forms/lib/components/RadioButtons.js +++ b/packages/react-dynamic-forms/lib/components/RadioButtons.js @@ -10,14 +10,16 @@ var _react = require("react"); var _react2 = _interopRequireDefault(_react); -var _underscore = require("underscore"); - -var _underscore2 = _interopRequireDefault(_underscore); - var _formGroup = require("../js/formGroup"); var _formGroup2 = _interopRequireDefault(_formGroup); +var _renderers = require("../js/renderers"); + +var _actions = require("../js/actions"); + +var _style = require("../js/style"); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } @@ -37,50 +39,84 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" var RadioButtons = function (_React$Component) { _inherits(RadioButtons, _React$Component); - function RadioButtons() { + function RadioButtons(props) { _classCallCheck(this, RadioButtons); - return _possibleConstructorReturn(this, (RadioButtons.__proto__ || Object.getPrototypeOf(RadioButtons)).apply(this, arguments)); + var _this = _possibleConstructorReturn(this, (RadioButtons.__proto__ || Object.getPrototypeOf(RadioButtons)).call(this, props)); + + _this.state = { isFocused: false }; + return _this; } _createClass(RadioButtons, [{ + key: "getCurrentChoiceLabel", + value: function getCurrentChoiceLabel() { + var _this2 = this; + + var choiceItem = this.props.optionList.find(function (item) { + return item.get("id") === _this2.props.value; + }); + return choiceItem ? choiceItem.get("label") : ""; + } + }, { + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleFocus", + value: function handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + }, { + key: "handleKeyPress", + value: function handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } + } + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } + }, { key: "handleChange", value: function handleChange(v) { // Callbacks if (this.props.onChange) { this.props.onChange(this.props.name, v); } + } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); + } + }, { + key: "handleDone", + value: function handleDone() { if (this.props.onBlur) { this.props.onBlur(this.props.name); } + this.setState({ isFocused: false, hover: false, oldValue: null }); } }, { - key: "getCurrentChoiceLabel", - value: function getCurrentChoiceLabel() { - var _this2 = this; - - var choiceItem = this.props.optionList.find(function (item) { - return item.get("id") === _this2.props.value; - }); - return choiceItem ? choiceItem.get("label") : ""; - } - }, { - key: "inlineStyle", - value: function inlineStyle(hasError, isMissing) { - var color = "inherited"; - var background = "inherited"; - if (hasError) { - color = "#b94a48"; - background = "#fff0f3"; - } else if (isMissing) { - background = "floralwhite"; + key: "handleCancel", + value: function handleCancel() { + if (this.props.onChange) { + var v = this.state.oldValue; + this.props.onChange(this.props.name, v); } - return { - color: color, - background: background, - width: "100%", - paddingLeft: 3 - }; + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); } }, { key: "render", @@ -88,6 +124,15 @@ var RadioButtons = function (_React$Component) { var _this3 = this; if (this.props.edit) { + var editStyle = { + borderStyle: "solid", + borderRadius: 2, + borderWidth: 1, + padding: 5, + borderColor: "#ececec", + marginBottom: 5 + }; + var items = this.props.optionList.map(function (item, i) { var id = item.get("id"); var label = item.get("label"); @@ -113,15 +158,69 @@ var RadioButtons = function (_React$Component) { }); return _react2.default.createElement( "div", - null, - items + { style: { marginBottom: 10 } }, + _react2.default.createElement( + "div", + { + onFocus: function onFocus(e) { + return _this3.handleFocus(e); + }, + onKeyUp: function onKeyUp(e) { + return _this3.handleKeyPress(e); + }, + style: editStyle + }, + items + ), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 5 } }, + _react2.default.createElement( + "span", + { + style: (0, _style.inlineDoneButtonStyle)(0), + onClick: function onClick() { + return _this3.handleDone(); + } + }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this3.handleCancel(); + } + }, + "CANCEL" + ) + ) : _react2.default.createElement("div", null) ); } else { - var text = this.getCurrentChoiceLabel(); + var s = this.getCurrentChoiceLabel(); + var view = this.props.view || _renderers.textView; + var text = _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + view(s) + ); + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this3.handleEditItem(); + }); return _react2.default.createElement( "div", - { style: this.inlineStyle(false, false) }, - text + { + style: (0, _style.inlineStyle)(false, false), + onMouseEnter: function onMouseEnter() { + return _this3.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this3.handleMouseLeave(); + } + }, + text, + edit ); } } diff --git a/packages/react-dynamic-forms/lib/components/TagsEdit.js b/packages/react-dynamic-forms/lib/components/TagsEdit.js index 66443ac..a510ce8 100644 --- a/packages/react-dynamic-forms/lib/components/TagsEdit.js +++ b/packages/react-dynamic-forms/lib/components/TagsEdit.js @@ -20,10 +20,18 @@ var _immutable = require("immutable"); var _immutable2 = _interopRequireDefault(_immutable); +var _flexboxReact = require("flexbox-react"); + +var _flexboxReact2 = _interopRequireDefault(_flexboxReact); + var _formGroup = require("../js/formGroup"); var _formGroup2 = _interopRequireDefault(_formGroup); +var _actions = require("../js/actions"); + +var _style = require("../js/style"); + require("react-select/dist/react-select.css"); require("../css/tagsedit.css"); @@ -72,6 +80,21 @@ var TagsEdit = function (_React$Component) { } } } + }, { + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); + } }, { key: "handleChange", value: function handleChange(tags) { @@ -111,6 +134,43 @@ var TagsEdit = function (_React$Component) { return this.props.required && !this.props.disabled && this.isEmpty(value); } + }, { + key: "handleFocus", + value: function handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + }, { + key: "handleKeyPress", + value: function handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } + } + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } + }, { + key: "handleDone", + value: function handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + }, { + key: "handleCancel", + value: function handleCancel() { + if (this.props.onChange) { + var v = this.state.oldValue; + this.props.onChange(this.props.name, v); + } + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); + } }, { key: "render", value: function render() { @@ -136,21 +196,54 @@ var TagsEdit = function (_React$Component) { return _react2.default.createElement( "div", - null, - _react2.default.createElement(_reactSelect.Creatable, { - key: "bob", - className: className, - multi: true, - disabled: this.props.disabled, - placeholder: "Select tags...", - allowCreate: true, - value: value, - options: options, - onChange: function onChange(value) { - return _this3.handleChange(value); - } - }), - _react2.default.createElement("div", { className: "help-block" }) + { style: { marginBottom: 8 } }, + _react2.default.createElement( + _flexboxReact2.default, + { flexDirection: "row", style: { width: "100%" } }, + _react2.default.createElement(_reactSelect.Creatable, { + className: className, + multi: true, + disabled: this.props.disabled, + placeholder: "Select tags...", + allowCreate: true, + value: value, + options: options, + onChange: function onChange(value) { + return _this3.handleChange(value); + }, + onFocus: function onFocus(e) { + return _this3.handleFocus(e); + }, + onKeyUp: function onKeyUp(e) { + return _this3.handleKeyPress(e); + } + }), + _react2.default.createElement("div", { className: "help-block" }), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 5 } }, + _react2.default.createElement( + "span", + { + style: (0, _style.inlineDoneButtonStyle)(5), + onClick: function onClick() { + return _this3.handleDone(); + } + }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this3.handleCancel(); + } + }, + "CANCEL" + ) + ) : _react2.default.createElement("div", null) + ) ); } else { var tagStyle = { @@ -160,20 +253,34 @@ var TagsEdit = function (_React$Component) { paddingLeft: 5, paddingRight: 5, background: "#ececec", - borderRadius: 2, + borderRadius: 4, + borderStyle: "solid", + borderWidth: 1, + borderColor: "#eaeaea", marginLeft: 2, marginRight: 2 }; + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this3.handleEditItem(); + }); return _react2.default.createElement( "div", - null, + { + onMouseEnter: function onMouseEnter() { + return _this3.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this3.handleMouseLeave(); + } + }, this.props.value.map(function (tag, i) { return _react2.default.createElement( "span", { key: i, style: tagStyle }, tag ); - }) + }), + edit ); } } diff --git a/packages/react-dynamic-forms/lib/components/TextArea.js b/packages/react-dynamic-forms/lib/components/TextArea.js index fb6865c..43eda0b 100644 --- a/packages/react-dynamic-forms/lib/components/TextArea.js +++ b/packages/react-dynamic-forms/lib/components/TextArea.js @@ -4,8 +4,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); -var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; - var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require("react"); @@ -22,6 +20,12 @@ var _formGroup = require("../js/formGroup"); var _formGroup2 = _interopRequireDefault(_formGroup); +var _renderers = require("../js/renderers"); + +var _actions = require("../js/actions"); + +var _style2 = require("../js/style"); + require("../css/textarea.css"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -52,12 +56,30 @@ var TextArea = function (_React$Component) { var _this = _possibleConstructorReturn(this, (TextArea.__proto__ || Object.getPrototypeOf(TextArea)).call(this, props)); _this.state = { - touched: false + value: props.value, + isFocused: false, + touched: false, + selectText: false }; return _this; } _createClass(TextArea, [{ + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); + } + }, { key: "isEmpty", value: function isEmpty(value) { return _underscore2.default.isNull(value) || _underscore2.default.isUndefined(value) || value === ""; @@ -83,9 +105,9 @@ var TextArea = function (_React$Component) { obj[this.props.name] = value; var properties = {}; - properties[this.props.name] = this.props.rules; + properties[this.props.name] = this.props.validation; - var rules = this.props.rules ? { properties: properties } : null; + var rules = this.props.validation ? { properties: properties } : null; if (obj && rules) { var validation = (0, _revalidator.validate)(obj, rules, { cast: true }); var name = this.props.name || "Value"; @@ -102,19 +124,23 @@ var TextArea = function (_React$Component) { }, { key: "componentWillReceiveProps", value: function componentWillReceiveProps(nextProps) { - if (this.props.value !== nextProps.value) { + if (nextProps.selected && this.props.edit !== nextProps.edit && nextProps.edit === true) { + this.setState({ selectText: true }); + } + if (this.state.value !== nextProps.value && !this.state.isFocused) { + this.setState({ value: nextProps.value }); + var missing = this.isMissing(nextProps.value); var _getError = this.getError(nextProps.value), validationError = _getError.validationError; - // Broadcast error and missing states up to the owner + // Broadcast error and missing states up to the parent if (this.props.onErrorCountChange) { this.props.onErrorCountChange(this.props.name, validationError ? 1 : 0); } - if (this.props.onMissingCountChange) { this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); } @@ -128,7 +154,7 @@ var TextArea = function (_React$Component) { var _getError2 = this.getError(this.props.value), validationError = _getError2.validationError; - // Initial error and missing states are fed up to the owner + // Initial error and missing states are fed up to the parent if (this.props.onErrorCountChange) { @@ -140,53 +166,98 @@ var TextArea = function (_React$Component) { } } }, { - key: "onBlur", - value: function onBlur() { - var value = this.textInput.value; + key: "componentDidUpdate", + value: function componentDidUpdate() { + if (this.state.selectText) { + this.textInput.focus(); + this.textInput.select(); + this.setState({ selectText: false }); + } + } + }, { + key: "handleChange", + value: function handleChange(e) { + var _this2 = this; - var missing = this.props.required && this.isEmpty(value); + var name = this.props.name; + var value = e.target.value; - var _getError3 = this.getError(value), - validationError = _getError3.validationError; + this.setState({ value: value }, function () { + var missing = _this2.props.required && _this2.isEmpty(value); - // Callbacks + var _getError3 = _this2.getError(value), + validationError = _getError3.validationError; + // Callbacks - if (this.props.onChange) { - this.props.onChange(this.props.name, value); + + if (_this2.props.onErrorCountChange) { + _this2.props.onErrorCountChange(name, validationError ? 1 : 0); + } + + if (_this2.props.onMissingCountChange) { + _this2.props.onMissingCountChange(name, missing ? 1 : 0); + } + + if (_this2.props.onChange) { + _this2.props.onChange(name, value); + } + }); + } + }, { + key: "handleFocus", + value: function handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); } - if (this.props.onErrorCountChange) { - this.props.onErrorCountChange(this.props.name, validationError ? 1 : 0); + } + }, { + key: "handleKeyPress", + value: function handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } } - if (this.props.onMissingCountChange) { - this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } + }, { + key: "handleDone", + value: function handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); } - - this.setState({ touched: true }); + this.setState({ isFocused: false, hover: false, oldValue: null }); } }, { - key: "inlineStyle", - value: function inlineStyle(hasError, isMissing) { - var color = ""; - var background = ""; - if (hasError) { - color = "#b94a48"; - background = "#fff0f3"; - } else if (isMissing) { - background = "floralwhite"; + key: "handleCancel", + value: function handleCancel() { + if (this.props.onChange) { + var v = this.state.oldValue; + var cast = v; + if (_underscore2.default.has(this.props.rules, "type")) { + switch (this.props.rules.type) { + case "integer": + cast = v === "" ? null : parseInt(v, 10); + break; + case "number": + cast = v === "" ? null : parseFloat(v, 10); + break; + //pass + default: + } + } + this.props.onChange(this.props.name, cast); } - return { - color: color, - background: background, - height: "100%", - width: "100%", - paddingLeft: 3 - }; + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); } }, { key: "render", value: function render() { - var _this2 = this; + var _this3 = this; // Control state var isMissing = this.isMissing(this.props.value); @@ -208,53 +279,92 @@ var TextArea = function (_React$Component) { // Warning style var style = isMissing ? { background: "floralwhite" } : {}; + var canCommit = !isMissing && !validationError; + return _react2.default.createElement( "div", - { className: className }, + { className: className, style: { marginBottom: 10 } }, _react2.default.createElement("textarea", { ref: function ref(input) { - _this2.textInput = input; + _this3.textInput = input; }, - key: this.props.value, className: "form-control", style: style, type: "text", disabled: this.props.disabled, placeholder: this.props.placeholder, - defaultValue: this.props.value, - rows: this.props.rows, - onBlur: function onBlur() { - return _this2.onBlur(); + value: this.state.value, + onChange: function onChange(e) { + return _this3.handleChange(e); + }, + onFocus: function onFocus(e) { + return _this3.handleFocus(e); + }, + onKeyUp: function onKeyUp(e) { + return _this3.handleKeyPress(e); } }), _react2.default.createElement( "div", { className: helpClassName }, msg - ) + ), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 5 } }, + canCommit ? _react2.default.createElement( + "span", + { + style: (0, _style2.inlineDoneButtonStyle)(5, true), + onClick: function onClick() { + return _this3.handleDone(); + } + }, + "DONE" + ) : _react2.default.createElement( + "span", + { style: (0, _style2.inlineDoneButtonStyle)(5, false) }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style2.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this3.handleCancel(); + } + }, + "CANCEL" + ) + ) : _react2.default.createElement("div", null) ); } else { - var view = this.props.view; - var text = this.props.value; - if (isMissing) { - text = " "; - } - var _style = _extends({ - height: 100 - }, this.inlineStyle(validationError, isMissing)); - if (!view) { - return _react2.default.createElement( - "div", - { style: _style }, - text - ); - } else { - return _react2.default.createElement( - "div", - { style: _style }, - view(text) - ); - } + var view = this.props.view || _renderers.textView; + var text = isMissing ? _react2.default.createElement("span", null) : view(this.props.value); + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this3.handleEditItem(); + }); + + var _style = (0, _style2.inlineTextAreaStyle)(validationError, isMissing); + + return _react2.default.createElement( + "div", + { + style: _style, + onMouseEnter: function onMouseEnter() { + return _this3.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this3.handleMouseLeave(); + } + }, + text, + _react2.default.createElement( + "span", + null, + edit + ) + ); } } }]); diff --git a/packages/react-dynamic-forms/lib/components/TextEdit.js b/packages/react-dynamic-forms/lib/components/TextEdit.js index 7948689..52c8a80 100644 --- a/packages/react-dynamic-forms/lib/components/TextEdit.js +++ b/packages/react-dynamic-forms/lib/components/TextEdit.js @@ -16,10 +16,20 @@ var _underscore2 = _interopRequireDefault(_underscore); var _revalidator = require("revalidator"); +var _flexboxReact = require("flexbox-react"); + +var _flexboxReact2 = _interopRequireDefault(_flexboxReact); + var _formGroup = require("../js/formGroup"); var _formGroup2 = _interopRequireDefault(_formGroup); +var _renderers = require("../js/renderers"); + +var _actions = require("../js/actions"); + +var _style2 = require("../js/style"); + require("../css/textedit.css"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -40,8 +50,6 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" /** * Form control to edit a text field. - * Set the initial value with `initialValue` and set a callback for - * value changed with `onChange`. */ var TextEdit = function (_React$Component) { _inherits(TextEdit, _React$Component); @@ -52,12 +60,30 @@ var TextEdit = function (_React$Component) { var _this = _possibleConstructorReturn(this, (TextEdit.__proto__ || Object.getPrototypeOf(TextEdit)).call(this, props)); _this.state = { - touched: false + value: props.value, + isFocused: false, + touched: false, + selectText: false }; return _this; } _createClass(TextEdit, [{ + key: "handleMouseEnter", + value: function handleMouseEnter() { + this.setState({ hover: true }); + } + }, { + key: "handleMouseLeave", + value: function handleMouseLeave() { + this.setState({ hover: false }); + } + }, { + key: "handleEditItem", + value: function handleEditItem() { + this.props.onEditItem(this.props.name); + } + }, { key: "isEmpty", value: function isEmpty(value) { return _underscore2.default.isNull(value) || _underscore2.default.isUndefined(value) || value === ""; @@ -102,19 +128,23 @@ var TextEdit = function (_React$Component) { }, { key: "componentWillReceiveProps", value: function componentWillReceiveProps(nextProps) { - if (this.props.value !== nextProps.value) { + if (nextProps.selected && this.props.edit !== nextProps.edit && nextProps.edit === true) { + this.setState({ selectText: true }); + } + if (this.state.value !== nextProps.value && !this.state.isFocused) { + this.setState({ value: nextProps.value }); + var missing = this.isMissing(nextProps.value); var _getError = this.getError(nextProps.value), - validationError = _getError.validationError; + error = _getError.validationError; - // Broadcast error and missing states up to the owner + // Broadcast error and missing states up to the parent if (this.props.onErrorCountChange) { - this.props.onErrorCountChange(this.props.name, validationError ? 1 : 0); + this.props.onErrorCountChange(this.props.name, error ? 1 : 0); } - if (this.props.onMissingCountChange) { this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); } @@ -128,7 +158,7 @@ var TextEdit = function (_React$Component) { var _getError2 = this.getError(this.props.value), validationError = _getError2.validationError; - // Initial error and missing states are fed up to the owner + // Initial error and missing states are fed up to the parent if (this.props.onErrorCountChange) { @@ -139,35 +169,97 @@ var TextEdit = function (_React$Component) { this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); } } + }, { + key: "componentDidUpdate", + value: function componentDidUpdate() { + if (this.state.selectText) { + this.textInput.focus(); + this.textInput.select(); + this.setState({ selectText: false }); + } + } }, { key: "handleChange", - value: function handleChange() { - var value = this.textInput.value; + value: function handleChange(e) { + var _this2 = this; - var missing = this.props.required && this.isEmpty(value); + var name = this.props.name; + var value = e.target.value; - var _getError3 = this.getError(value), - validationError = _getError3.validationError; + this.setState({ value: value }, function () { + var missing = _this2.props.required && _this2.isEmpty(value); - var cast = value; + var _getError3 = _this2.getError(value), + validationError = _getError3.validationError; - // Callbacks - if (this.props.onErrorCountChange) { - this.props.onErrorCountChange(this.props.name, validationError ? 1 : 0); - } + var cast = value; - if (this.props.onMissingCountChange) { - this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); - } + // Callbacks + if (_this2.props.onErrorCountChange) { + _this2.props.onErrorCountChange(name, validationError ? 1 : 0); + } + + if (_this2.props.onMissingCountChange) { + _this2.props.onMissingCountChange(name, missing ? 1 : 0); + } + if (_this2.props.onChange) { + if (_underscore2.default.has(_this2.props.rules, "type")) { + switch (_this2.props.rules.type) { + case "integer": + cast = value === "" ? null : parseInt(value, 10); + break; + case "number": + cast = value === "" ? null : parseFloat(value, 10); + break; + //pass + default: + } + } + _this2.props.onChange(name, cast); + } + }); + } + }, { + key: "handleFocus", + value: function handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + }, { + key: "handleKeyPress", + value: function handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } + } + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } + }, { + key: "handleDone", + value: function handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + }, { + key: "handleCancel", + value: function handleCancel() { if (this.props.onChange) { + var v = this.state.oldValue; + var cast = v; if (_underscore2.default.has(this.props.rules, "type")) { switch (this.props.rules.type) { case "integer": - cast = value === "" ? null : parseInt(value, 10); + cast = v === "" ? null : parseInt(v, 10); break; case "number": - cast = value === "" ? null : parseFloat(value, 10); + cast = v === "" ? null : parseFloat(v, 10); break; //pass default: @@ -175,39 +267,13 @@ var TextEdit = function (_React$Component) { } this.props.onChange(this.props.name, cast); } - } - }, { - key: "handleBlur", - value: function handleBlur() { - if (this.props.onBlur) { - this.props.onBlur(this.props.name); - } - - this.setState({ touched: true }); - } - }, { - key: "inlineStyle", - value: function inlineStyle(hasError, isMissing) { - var color = ""; - var background = ""; - if (hasError) { - color = "#b94a48"; - background = "#fff0f3"; - } else if (isMissing) { - background = "floralwhite"; - } - return { - color: color, - background: background, - height: 23, - width: "100%", - paddingLeft: 3 - }; + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); } }, { key: "render", value: function render() { - var _this2 = this; + var _this3 = this; // Control state var isMissing = this.isMissing(this.props.value); @@ -227,56 +293,98 @@ var TextEdit = function (_React$Component) { } // Warning style - var style = isMissing ? { background: "floralwhite" } : {}; - + var style = isMissing ? { background: _style2.colors.MISSING_COLOR_BG } : {}; var type = this.props.type || "text"; + var canCommit = !isMissing && !validationError; + return _react2.default.createElement( - "div", - { className: className }, - _react2.default.createElement("input", { - ref: function ref(input) { - _this2.textInput = input; - }, - className: "form-control input-sm", - style: style, - type: type, - disabled: this.props.disabled, - placeholder: this.props.placeholder, - value: this.props.value, - onChange: function onChange() { - return _this2.handleChange(); - }, - onBlur: function onBlur() { - return _this2.handleBlur(); - } - }), + _flexboxReact2.default, + { flexDirection: "row", style: { width: "100%" } }, _react2.default.createElement( "div", - { className: helpClassName }, - msg - ) + { className: className, style: { width: "100%" } }, + _react2.default.createElement("input", { + ref: function ref(input) { + _this3.textInput = input; + }, + className: "form-control input-sm", + style: style, + type: type, + disabled: this.props.disabled, + placeholder: this.props.placeholder, + value: this.state.value, + onChange: function onChange(e) { + return _this3.handleChange(e); + }, + onFocus: function onFocus(e) { + return _this3.handleFocus(e); + }, + onKeyUp: function onKeyUp(e) { + return _this3.handleKeyPress(e); + } + }), + _react2.default.createElement( + "div", + { className: helpClassName }, + msg + ) + ), + this.props.selected ? _react2.default.createElement( + "span", + { style: { marginTop: 3 } }, + canCommit ? _react2.default.createElement( + "span", + { + style: (0, _style2.inlineDoneButtonStyle)(5, true), + onClick: function onClick() { + return _this3.handleDone(); + } + }, + "DONE" + ) : _react2.default.createElement( + "span", + { style: (0, _style2.inlineDoneButtonStyle)(5, false) }, + "DONE" + ), + _react2.default.createElement( + "span", + { + style: (0, _style2.inlineCancelButtonStyle)(), + onClick: function onClick() { + return _this3.handleCancel(); + } + }, + "CANCEL" + ) + ) : _react2.default.createElement("div", null) ); } else { - var view = this.props.view; - var text = this.props.value; - if (isMissing) { - text = " "; - } - var _style = this.inlineStyle(validationError, isMissing); - if (!view) { - return _react2.default.createElement( - "div", - { style: _style }, - text - ); - } else { - return _react2.default.createElement( - "div", - { style: _style }, - view(text) - ); - } + var view = this.props.view || _renderers.textView; + var text = isMissing ? _react2.default.createElement("span", null) : _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + view(this.props.value) + ); + var edit = (0, _actions.editAction)(this.state.hover && this.props.allowEdit, function () { + return _this3.handleEditItem(); + }); + var _style = (0, _style2.inlineStyle)(validationError, isMissing); + return _react2.default.createElement( + "div", + { + style: _style, + key: "key-" + isMissing + "-" + validationError, + onMouseEnter: function onMouseEnter() { + return _this3.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this3.handleMouseLeave(); + } + }, + text, + edit + ); } } }]); diff --git a/packages/react-dynamic-forms/lib/css/tagsedit.css b/packages/react-dynamic-forms/lib/css/tagsedit.css index d46dc83..a155566 100644 --- a/packages/react-dynamic-forms/lib/css/tagsedit.css +++ b/packages/react-dynamic-forms/lib/css/tagsedit.css @@ -13,31 +13,31 @@ } .Select-value { - line-height: 26px !important; + line-height: 26px !important; } .Select-value-label { line-height: 24px; font-size: 12px; - } +} .Select-menu-outer { font-size: 12px; } .Select-input { - height: 26px; + height: 26px; } .Select.is-missing > .Select-control { - background: floralwhite; + background: floralwhite; } .Select--multi .Select-value { height: 26px; height: 24px; margin-top: 1px; - border-color: #ccc + border-color: #ccc; } .Select--multi .Select-value-label { @@ -47,9 +47,9 @@ margin-top: -6px; padding-left: 5px; padding-right: 5px; - background: #DDD; - color: #555555; - border-color: #555; + background: #ececec; + color: #333; + border-color: #ececec; } .Select--multi .Select-value-icon { @@ -59,16 +59,16 @@ margin-top: -6px; padding-left: 5px; padding-right: 5px; - background: #DDD; - color: #555555; - border-right-color: #ccc; + background: #ececec; + color: #333; + border-right-color: #eaeaea; } .Select--multi .Select-value-icon:hover { - background: #DFDFDF; + background: #dfdfdf; color: #b94a48; } .Select.missing.Select--multi.is-searchable > .Select-control { background: floralwhite; -} \ No newline at end of file +} diff --git a/packages/react-dynamic-forms/lib/index.js b/packages/react-dynamic-forms/lib/index.js index ae98e4d..9c968ae 100644 --- a/packages/react-dynamic-forms/lib/index.js +++ b/packages/react-dynamic-forms/lib/index.js @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); -exports.FormGroupLayout = exports.FormEditStates = exports.View = exports.CheckBoxes = exports.RadioButtons = exports.TagsEdit = exports.DateEdit = exports.Chooser = exports.TextArea = exports.TextEdit = exports.List = exports.formList = exports.formGroup = exports.Field = exports.Schema = exports.Form = undefined; +exports.colors = exports.colorView = exports.dateView = exports.markdownView = exports.linkView = exports.textView = exports.FormGroupLayout = exports.FormEditStates = exports.View = exports.CheckBoxes = exports.RadioButtons = exports.TagsEdit = exports.DateEdit = exports.AsyncChooser = exports.Chooser = exports.TextArea = exports.TextEdit = exports.List = exports.formList = exports.formGroup = exports.Field = exports.Schema = exports.Form = undefined; var _Form = require("./components/Form"); @@ -37,9 +37,9 @@ var _TextArea = require("./components/TextArea"); var _TextArea2 = _interopRequireDefault(_TextArea); -var _Chooser = require("./components/Chooser.js"); +var _Chooser = require("./components/Chooser"); -var _Chooser2 = _interopRequireDefault(_Chooser); +var _AsyncChooser = require("./components/AsyncChooser"); var _DateEdit = require("./components/DateEdit"); @@ -63,18 +63,23 @@ var _View2 = _interopRequireDefault(_View); var _constants = require("./js/constants"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +var _renderers = require("./js/renderers"); + +var _style = require("./js/style"); -exports.Form = _Form2.default; /** - * Copyright (c) 2015 - present, The Regents of the University of California, - * through Lawrence Berkeley National Laboratory (subject to receipt - * of any required approvals from the U.S. Dept. of Energy). - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. - */ +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +/** + * Copyright (c) 2015 - present, The Regents of the University of California, + * through Lawrence Berkeley National Laboratory (subject to receipt + * of any required approvals from the U.S. Dept. of Energy). + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +exports.Form = _Form2.default; exports.Schema = _Schema2.default; exports.Field = _Field2.default; exports.formGroup = _formGroup2.default; @@ -82,7 +87,8 @@ exports.formList = _formList2.default; exports.List = _List2.default; exports.TextEdit = _TextEdit2.default; exports.TextArea = _TextArea2.default; -exports.Chooser = _Chooser2.default; +exports.Chooser = _Chooser.ChooserGroup; +exports.AsyncChooser = _AsyncChooser.AsyncChooserGroup; exports.DateEdit = _DateEdit2.default; exports.TagsEdit = _TagsEdit2.default; exports.RadioButtons = _RadioButtons2.default; @@ -90,3 +96,9 @@ exports.CheckBoxes = _CheckBoxes2.default; exports.View = _View2.default; exports.FormEditStates = _constants.FormEditStates; exports.FormGroupLayout = _constants.FormGroupLayout; +exports.textView = _renderers.textView; +exports.linkView = _renderers.linkView; +exports.markdownView = _renderers.markdownView; +exports.dateView = _renderers.dateView; +exports.colorView = _renderers.colorView; +exports.colors = _style.colors; diff --git a/packages/react-dynamic-forms/lib/js/actions.js b/packages/react-dynamic-forms/lib/js/actions.js new file mode 100644 index 0000000..89f59d4 --- /dev/null +++ b/packages/react-dynamic-forms/lib/js/actions.js @@ -0,0 +1,40 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.editAction = editAction; + +var _react = require("react"); + +var _react2 = _interopRequireDefault(_react); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function editAction(enabled, handler) { + var iconStyle = { + fontSize: 11 + }; + var editAction = _react2.default.createElement("span", null); + if (enabled) { + editAction = _react2.default.createElement( + "span", + { style: { marginLeft: 5 }, onClick: handler }, + _react2.default.createElement("i", { + style: iconStyle, + className: "glyphicon glyphicon-pencil icon edit-action active" + }) + ); + } else { + editAction = _react2.default.createElement("div", null); + } + return editAction; +} /** + * Copyright (c) 2018 - present, The Regents of the University of California, + * through Lawrence Berkeley National Laboratory (subject to receipt + * of any required approvals from the U.S. Dept. of Energy). + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ \ No newline at end of file diff --git a/packages/react-dynamic-forms/lib/js/formGroup.js b/packages/react-dynamic-forms/lib/js/formGroup.js index 6c003f9..45e909e 100644 --- a/packages/react-dynamic-forms/lib/js/formGroup.js +++ b/packages/react-dynamic-forms/lib/js/formGroup.js @@ -4,6 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); exports.default = formGroup; @@ -20,6 +22,14 @@ var _classnames = require("classnames"); var _classnames2 = _interopRequireDefault(_classnames); +var _immutable = require("immutable"); + +var _immutable2 = _interopRequireDefault(_immutable); + +var _underscore = require("underscore"); + +var _underscore2 = _interopRequireDefault(_underscore); + var _constants = require("../js/constants"); require("../css/group.css"); @@ -66,8 +76,8 @@ function _inherits(subClass, superClass) { if (typeof superClass !== "function" * users code. */ -function formGroup(Widget, hideEdit) { - return function (_React$Component) { +function formGroup(Control, name) { + var wrapped = function (_React$Component) { _inherits(Group, _React$Component); function Group(props) { @@ -105,8 +115,7 @@ function formGroup(Widget, hideEdit) { _props$hidden = _props.hidden, hidden = _props$hidden === undefined ? false : _props$hidden, width = _props.width, - allowEdit = _props.allowEdit, - props = _objectWithoutProperties(_props, ["hidden", "width", "allowEdit"]); + props = _objectWithoutProperties(_props, ["hidden", "width"]); var name = props.name, label = props.label, @@ -115,8 +124,13 @@ function formGroup(Widget, hideEdit) { disabled = props.disabled, required = props.required, showRequired = props.showRequired, - onSelectItem = props.onSelectItem; + onSelectItem = props.onSelectItem, + value = props.value, + initialValue = props.initialValue; + + var isBeingEdited = edit; + var hasChanged = _underscore2.default.isNull(initialValue) ? false : !_immutable2.default.is(value, initialValue); var selectStyle = {}; @@ -128,18 +142,18 @@ function formGroup(Widget, hideEdit) { } // - // Widget + // Control // - var widgetWidth = width ? width + "px" : "100%"; - var widget = _react2.default.createElement( + var controlWidth = width ? width : "100%"; + var control = _react2.default.createElement( "div", { style: { - width: widgetWidth + width: controlWidth } }, - _react2.default.createElement(Widget, props) + _react2.default.createElement(Control, _extends({}, props, { onEditItem: onSelectItem })) ); // @@ -167,6 +181,14 @@ function formGroup(Widget, hideEdit) { if (this.props.layout === _constants.FormGroupLayout.COLUMN) { marginLeft = null; } + + var labelColor = "inherit"; + if (this.state.error) { + labelColor = "b94a48"; + } else if (hasChanged) { + labelColor = "steelblue"; + } + var fieldLabel = _react2.default.createElement( "div", { @@ -175,7 +197,7 @@ function formGroup(Widget, hideEdit) { whiteSpace: "nowrap", marginLeft: marginLeft, paddingTop: 3, - color: this.state.error ? "b94a48" : "inherit" + color: labelColor } }, _react2.default.createElement( @@ -186,60 +208,28 @@ function formGroup(Widget, hideEdit) { ); var labelWidth = this.props.labelWidth ? this.props.labelWidth + "px" : "300px"; - // - // Edit - // - - var isBeingEdited = edit; - - var flip = { - transform: "scaleX(-1)", - fontSize: 11 - }; - - var editIcon = _react2.default.createElement("span", null); - if (this.state.over && allowEdit && !hideEdit) { - editIcon = _react2.default.createElement("i", { - style: flip, - className: isBeingEdited ? "glyphicon glyphicon-pencil icon edit-action active" : "glyphicon glyphicon-pencil icon edit-action", - onClick: function onClick() { - return onSelectItem ? onSelectItem(name) : null; - } - }); - } - // Group - if (this.props.layout === _constants.FormGroupLayout.INLINE) { - return _react2.default.createElement( - _flexboxReact2.default, - { - flexDirection: "column", - width: widgetWidth, - onMouseEnter: function onMouseEnter() { - return _this2.handleMouseEnter(); - }, - onMouseLeave: function onMouseLeave() { - return _this2.handleMouseLeave(); - } - }, - widget - ); - } else if (this.props.layout === _constants.FormGroupLayout.COLUMN) { - return _react2.default.createElement( - _flexboxReact2.default, - { - flexDirection: "column", - onMouseEnter: function onMouseEnter() { - return _this2.handleMouseEnter(); + switch (this.props.layout) { + case _constants.FormGroupLayout.INLINE: + return _react2.default.createElement( + _flexboxReact2.default, + { + flexDirection: "column", + width: controlWidth, + onMouseEnter: function onMouseEnter() { + return _this2.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this2.handleMouseLeave(); + } }, - onMouseLeave: function onMouseLeave() { - return _this2.handleMouseLeave(); - } - }, - _react2.default.createElement( + control + ); + case _constants.FormGroupLayout.COLUMN: + return _react2.default.createElement( _flexboxReact2.default, { - flexDirection: "row", + flexDirection: "column", onMouseEnter: function onMouseEnter() { return _this2.handleMouseEnter(); }, @@ -249,21 +239,46 @@ function formGroup(Widget, hideEdit) { }, _react2.default.createElement( _flexboxReact2.default, - null, - fieldLabel - ), - _react2.default.createElement( - _flexboxReact2.default, - { minWidth: "14px", width: "14px" }, - requiredMarker + { + flexDirection: "row", + onMouseEnter: function onMouseEnter() { + return _this2.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this2.handleMouseLeave(); + } + }, + _react2.default.createElement( + _flexboxReact2.default, + null, + fieldLabel + ), + _react2.default.createElement( + _flexboxReact2.default, + { minWidth: "14px", width: "14px" }, + requiredMarker + ) ), _react2.default.createElement( _flexboxReact2.default, - { minWidth: "18px", width: "18px", style: selectStyle }, - editIcon + { + flexDirection: "row", + onMouseEnter: function onMouseEnter() { + return _this2.handleMouseEnter(); + }, + onMouseLeave: function onMouseLeave() { + return _this2.handleMouseLeave(); + } + }, + _react2.default.createElement( + _flexboxReact2.default, + { flexGrow: 1, style: selectStyle }, + control + ) ) - ), - _react2.default.createElement( + ); + case _constants.FormGroupLayout.ROW: + return _react2.default.createElement( _flexboxReact2.default, { flexDirection: "row", @@ -276,55 +291,35 @@ function formGroup(Widget, hideEdit) { }, _react2.default.createElement( _flexboxReact2.default, - { flexGrow: 1, style: selectStyle }, - widget - ) - ) - ); - } else { - return _react2.default.createElement( - _flexboxReact2.default, - { - flexDirection: "row", - onMouseEnter: function onMouseEnter() { - return _this2.handleMouseEnter(); - }, - onMouseLeave: function onMouseLeave() { - return _this2.handleMouseLeave(); - } - }, - _react2.default.createElement( - _flexboxReact2.default, - { minWidth: labelWidth, width: labelWidth }, - fieldLabel - ), - _react2.default.createElement( - _flexboxReact2.default, - { minWidth: "14px", width: "14px" }, - requiredMarker - ), - _react2.default.createElement( - _flexboxReact2.default, - { minWidth: "18px", width: "18px", style: selectStyle }, - editIcon - ), - _react2.default.createElement( - _flexboxReact2.default, - { - width: widgetWidth, - style: selectStyle, - onDoubleClick: function onDoubleClick() { - return onSelectItem && !isBeingEdited ? onSelectItem(name) : null; - } - }, - widget - ), - _react2.default.createElement(_flexboxReact2.default, { flexGrow: 1 }) - ); + { minWidth: labelWidth, width: labelWidth }, + fieldLabel + ), + _react2.default.createElement( + _flexboxReact2.default, + { minWidth: "14px", width: "14px" }, + requiredMarker + ), + _react2.default.createElement( + _flexboxReact2.default, + { + width: controlWidth, + style: selectStyle, + onDoubleClick: function onDoubleClick() { + return onSelectItem && !isBeingEdited ? onSelectItem(name) : null; + } + }, + control + ), + _react2.default.createElement(_flexboxReact2.default, { flexGrow: 1 }) + ); + default: + return _react2.default.createElement("div", null); } } }]); return Group; }(_react2.default.Component); + wrapped.displayName = name; + return wrapped; } \ No newline at end of file diff --git a/packages/react-dynamic-forms/lib/js/formList.js b/packages/react-dynamic-forms/lib/js/formList.js index 567bc0f..61c7903 100644 --- a/packages/react-dynamic-forms/lib/js/formList.js +++ b/packages/react-dynamic-forms/lib/js/formList.js @@ -56,7 +56,10 @@ function list(ItemComponent, hideEditRemove) { var _this = _possibleConstructorReturn(this, (HOC.__proto__ || Object.getPrototypeOf(HOC)).call(this, props)); - _this.state = { errors: [], missing: [], selected: null }; + _this.errors = []; + _this.missing = []; + + _this.state = { selected: null }; return _this; } @@ -64,9 +67,22 @@ function list(ItemComponent, hideEditRemove) { key: "handleSelectItem", value: function handleSelectItem(i) { if (this.state.selected !== i) { - this.setState({ selected: i }); + this.setState({ selected: i, oldValue: this.props.value.get(i) }); } else { - this.setState({ selected: null }); + this.setState({ selected: null, oldValue: null }); + } + } + }, { + key: "handleRevertItem", + value: function handleRevertItem(i) { + var oldValue = this.state.oldValue; + if (oldValue) { + var newValue = this.props.value.set(i, oldValue); + if (this.props.onChange) { + this.props.onChange(this.props.name, newValue); + } + } else { + this.handleRemovedItem(i); } } @@ -83,16 +99,16 @@ function list(ItemComponent, hideEditRemove) { }, { key: "handleMissingCountChange", value: function handleMissingCountChange(i, missingCount) { - var totalMissingCount = void 0; - var missingList = this.state.missing; - missingList[i] = missingCount; - totalMissingCount = _underscore2.default.reduce(missingList, function (memo, num) { - return memo + num; - }, 0); + if (i >= this.props.value.size) { + return; + } + + // Mutate our missing count + this.missing[i] = missingCount; // Callback if (this.props.onMissingCountChange) { - this.props.onMissingCountChange(this.props.name, totalMissingCount); + this.props.onMissingCountChange(this.props.name, this.numMissing()); } } @@ -103,16 +119,12 @@ function list(ItemComponent, hideEditRemove) { }, { key: "handleErrorCountChange", value: function handleErrorCountChange(i, errorCount) { - var totalErrorCount = void 0; - var errorList = this.state.errors; - errorList[i] = errorCount; - totalErrorCount = _underscore2.default.reduce(errorList, function (memo, num) { - return memo + num; - }, 0); + // Mutate our error count + this.errors[i] = errorCount; // Callback if (this.props.onErrorCountChange) { - this.props.onErrorCountChange(this.props.name, totalErrorCount); + this.props.onErrorCountChange(this.props.name, this.numErrors()); } } @@ -127,122 +139,112 @@ function list(ItemComponent, hideEditRemove) { var value = this.props.value; var n = 1; - var errors = this.state.errors; - var missing = this.state.missing; - errors.splice(i - n + 1, n); - missing.splice(i - n + 1, n); - this.setState({ errors: errors, missing: missing }); + this.errors.splice(i - n + 1, n); + this.missing.splice(i - n + 1, n); // Callbacks if (this.props.onChange) { this.props.onChange(this.props.name, value.splice(i - n + 1, n)); } + if (this.props.onErrorCountChange) { - this.props.onErrorCountChange(this.props.name, this.numErrors(errors)); + this.props.onErrorCountChange(this.props.name, this.numErrors()); } if (this.props.onMissingCountChange) { - this.props.onMissingCountChange(this.props.name, this.numMissing(missing)); + this.props.onMissingCountChange(this.props.name, this.numMissing()); } } }, { key: "handleAddItem", value: function handleAddItem() { var value = this.props.value; - - var errors = this.state.errors; - var missing = this.state.missing; var created = _immutable2.default.fromJS(ItemComponent.defaultValues); - errors.push(0); - missing.push(0); + this.errors.push(0); + this.missing.push(0); // Callbacks if (this.props.onChange) { this.props.onChange(this.props.name, value.push(created)); } - if (this.props.onErrorCountChange) { - this.props.onErrorCountChange(this.props.name, this.numErrors(errors)); - } - if (this.props.onMissingCountChange) { - this.props.onMissingCountChange(this.props.name, this.numMissing(missing)); - } this.setState({ selected: this.props.value.size }); } - //Determine the total count of missing fields in the entire list + /** + * Utility function to determine the total count of missing fields in the entire list + */ }, { key: "numMissing", - value: function numMissing(missing) { + value: function numMissing() { var total = 0; - _underscore2.default.each(missing, function (c) { + _underscore2.default.each(this.missing, function (c) { total += c; }); return total; } - //Determine the total count of error fields in the entire list + /** + * Utility function to determine the total number of errors in the entire list + */ }, { key: "numErrors", - value: function numErrors(errors) { + value: function numErrors() { var total = 0; - _underscore2.default.each(errors, function (c) { + _underscore2.default.each(this.errors, function (c) { total += c; }); return total; } - }, { - key: "componentWillReceiveProps", - value: function componentWillReceiveProps(nextProps) { - if (nextProps.edit === false) { - this.setState({ selected: null }); - } - } }, { key: "render", value: function render() { var _this2 = this; + var selected = this.state.selected; var itemComponents = []; this.props.value.forEach(function (item, index) { var _item$key = item.key, key = _item$key === undefined ? index : _item$key; + + var itemInitialValue = _this2.props.initialValue ? _this2.props.initialValue.get(index) : null; + var props = { key: key, name: index, - edit: _this2.props.edit, innerForm: true, hideMinus: hideEditRemove && index < _this2.props.value.size - 1, types: _this2.props.types, options: _this2.props.options, actions: _this2.props.actions, - onErrorCountChange: function onErrorCountChange(name, errorCount) { - return _this2.handleErrorCountChange(name, errorCount); + onErrorCountChange: function onErrorCountChange(index, errorCount) { + return _this2.handleErrorCountChange(index, errorCount); }, - onMissingCountChange: function onMissingCountChange(name, missingCount) { - return _this2.handleMissingCountChange(name, missingCount); + onMissingCountChange: function onMissingCountChange(index, missingCount) { + return _this2.handleMissingCountChange(index, missingCount); }, - onChange: function onChange(name, value) { - _this2.handleChangeItem(name, value); + onChange: function onChange(index, value) { + return _this2.handleChangeItem(index, value); } }; itemComponents.push(_react2.default.createElement(ItemComponent, _extends({}, props, { value: item, + initialValue: itemInitialValue, editable: _this2.props.edit, - edit: _this2.state.selected === index && _this2.props.edit + edit: index === selected }))); }); - var errors = _underscore2.default.find(this.state.errors, function (item) { + var hasErrors = _underscore2.default.find(this.errors, function (item) { return item >= 1; }); - var missing = _underscore2.default.find(this.state.missing, function (item) { + var hasMissing = _underscore2.default.find(this.missing, function (item) { return item >= 1; }); - var plusElement = (errors || missing) && hideEditRemove ? _react2.default.createElement("div", null) : null; + var plusElement = (hasErrors || hasMissing) && hideEditRemove ? _react2.default.createElement("div", null) : null; var _props = this.props, _props$canAddItems = _props.canAddItems, canAddItems = _props$canAddItems === undefined ? true : _props$canAddItems, @@ -250,14 +252,19 @@ function list(ItemComponent, hideEditRemove) { canRemoveItems = _props$canRemoveItems === undefined ? true : _props$canRemoveItems; + var canCommitItem = !_underscore2.default.isNull(selected) ? this.errors[selected] === 0 && this.missing[selected] === 0 : false; + return _react2.default.createElement(_List2.default, { items: itemComponents, - canAddItems: canAddItems && this.props.edit, - canRemoveItems: canRemoveItems && this.props.edit, - canEditItems: this.props.edit, + header: ItemComponent.header, + buttonIndent: ItemComponent.actionButtonIndex, + canAddItems: canAddItems, + canRemoveItems: canRemoveItems, + canEditItems: true, hideEditRemove: hideEditRemove, plusWidth: 400, plusElement: plusElement, + canCommitItem: canCommitItem, onAddItem: function onAddItem() { return _this2.handleAddItem(); }, @@ -266,6 +273,9 @@ function list(ItemComponent, hideEditRemove) { }, onSelectItem: function onSelectItem(index) { return _this2.handleSelectItem(index); + }, + onRevertItem: function onRevertItem(index) { + return _this2.handleRevertItem(index); } }); } diff --git a/packages/react-dynamic-forms/lib/js/renderers.js b/packages/react-dynamic-forms/lib/js/renderers.js new file mode 100644 index 0000000..ac22a38 --- /dev/null +++ b/packages/react-dynamic-forms/lib/js/renderers.js @@ -0,0 +1,93 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.textView = textView; +exports.linkView = linkView; +exports.markdownView = markdownView; +exports.dateView = dateView; +exports.colorView = colorView; + +var _underscore = require("underscore"); + +var _underscore2 = _interopRequireDefault(_underscore); + +var _react = require("react"); + +var _react2 = _interopRequireDefault(_react); + +var _reactMarkdown = require("react-markdown"); + +var _reactMarkdown2 = _interopRequireDefault(_reactMarkdown); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function textView(value) { + return _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + value + ); +} /** + * Copyright (c) 2018 - present, The Regents of the University of California, + * through Lawrence Berkeley National Laboratory (subject to receipt + * of any required approvals from the U.S. Dept. of Energy). + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +function linkView(value) { + return _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + _react2.default.createElement( + "a", + null, + value + ) + ); +} + +function markdownView(value) { + if (value === "" || _underscore2.default.isUndefined(value)) { + return _react2.default.createElement("div", { style: { height: 28 } }); + } else { + return _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + _react2.default.createElement(_reactMarkdown2.default, { source: value }) + ); + } +} + +function dateView() { + var fmt = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "MM/DD/YYYYY"; + + return function (d) { + var s = d ? d.format(fmt) : ""; + return _react2.default.createElement( + "span", + { style: { minHeight: 28 } }, + s + ); + }; +} + +function colorView(hex) { + var style = { + marginTop: 4, + width: 14, + height: 14, + borderRadius: 2, + background: hex, + float: "left" + }; + return _react2.default.createElement( + "span", + null, + _react2.default.createElement("div", { style: style }) + ); +} \ No newline at end of file diff --git a/packages/react-dynamic-forms/lib/js/style.js b/packages/react-dynamic-forms/lib/js/style.js new file mode 100644 index 0000000..626957f --- /dev/null +++ b/packages/react-dynamic-forms/lib/js/style.js @@ -0,0 +1,132 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.colors = undefined; +exports.inlineDoneButtonStyle = inlineDoneButtonStyle; +exports.inlineCancelButtonStyle = inlineCancelButtonStyle; +exports.inlineStyle = inlineStyle; +exports.inlineTextAreaStyle = inlineTextAreaStyle; +exports.inlineChooserStyle = inlineChooserStyle; + +var _underscore = require("underscore"); + +var _underscore2 = _interopRequireDefault(_underscore); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var LINE_HEIGHT = 23; /** + * Copyright (c) 2018 - present, The Regents of the University of California, + * through Lawrence Berkeley National Laboratory (subject to receipt + * of any required approvals from the U.S. Dept. of Energy). + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +var LEFT_SPACING = 3; + +var colors = exports.colors = { + ERROR_COLOR: "#b94a48", + ERROR_COLOR_BG: "#fff0f3", + MISSING_COLOR_BG: "floralwhite", + PRIMARY_ACTION_COLOR: "#2379c1", + PRIMARY_ACTION_COLOR_DISABLED: "#2379c169", + SECONDARY_ACTION_COLOR: "#AAA" +}; + +function inlineDoneButtonStyle(marginLeft, enabled) { + var isEnabled = _underscore2.default.isUndefined(enabled) ? true : enabled; + return { + padding: 5, + marginLeft: marginLeft, + fontSize: 12, + height: 30, + borderStyle: "solid", + borderWidth: 1, + borderColor: "rgba(70, 129, 180, 0.19)", + borderRadius: 2, + color: isEnabled ? colors.PRIMARY_ACTION_COLOR : colors.PRIMARY_ACTION_COLOR_DISABLED, + cursor: "pointer" + }; +} + +function inlineCancelButtonStyle() { + return { + padding: 5, + marginLeft: 3, + marginBottom: 5, + height: 30, + color: colors.SECONDARY_ACTION_COLOR, + cursor: "pointer", + fontSize: 12 + }; +} + +function inlineStyle(hasError, isMissing) { + var color = "inherited"; + var background = "inherited"; + if (hasError) { + color = colors.ERROR_COLOR; + background = colors.ERROR_COLOR_BG; + } else if (isMissing) { + background = colors.MISSING_COLOR_BG; + } + return { + color: color, + background: background, + width: "100%", + height: LINE_HEIGHT, + paddingLeft: LEFT_SPACING + }; +} + +function inlineTextAreaStyle(hasError, isMissing) { + var color = ""; + var background = ""; + if (hasError) { + color = colors.ERROR_COLOR; + background = colors.ERROR_COLOR_BG; + } else if (isMissing) { + background = colors.MISSING_COLOR_BG; + } + return { + color: color, + background: background, + height: "100%", + width: "100%", + minHeight: 28, + paddingLeft: LEFT_SPACING + }; +} + +function inlineChooserStyle(hasError, isMissing, isView) { + var color = ""; + var background = ""; + if (hasError) { + color = colors.ERROR_COLOR; + background = colors.ERROR_COLOR_BG; + } else if (isMissing) { + background = colors.MISSING_COLOR_BG; + } + + if (isView) { + return { + color: color, + background: background, + minHeight: LINE_HEIGHT, + width: "100%", + paddingLeft: LEFT_SPACING + }; + } else { + return { + color: color, + background: background, + height: LINE_HEIGHT, + width: "100%", + paddingLeft: LEFT_SPACING + }; + } +} \ No newline at end of file diff --git a/packages/react-dynamic-forms/package.json b/packages/react-dynamic-forms/package.json index 249b141..f7a9d1d 100644 --- a/packages/react-dynamic-forms/package.json +++ b/packages/react-dynamic-forms/package.json @@ -1,6 +1,6 @@ { "name": "react-dynamic-forms", - "version": "1.0.2", + "version": "1.1.1-alpha.1", "description": "Dynamic forms library for React", "keywords": [ "forms", @@ -17,10 +17,10 @@ "lint": "eslint src/components/*.js", "test": "npm run lint", "build": "echo \"*** Building lib\n\" && rm -rf lib/* && babel src/components --optional runtime --stage 0 --out-dir lib/components && mkdir lib/css && cp ./src/css/*.css ./lib/css/ && babel src/js --optional runtime --stage 0 --out-dir lib/js && babel src/index.js --optional runtime --stage 0 --out-file lib/index.js", - "start-website": "react-scripts start", - "build-website": "echo \"*** Building website\n\" && rm -rf docs && react-scripts build && mv build docs", "precommit": "lint-staged", - "prettier": "prettier --print-width 100 --tab-width 4 --write \"src/**/*.js\"" + "prettier": "prettier --print-width 100 --tab-width 4 --write \"src/**/*.js\"", + "storybook": "start-storybook -p 9009 -s public", + "build-storybook": "build-storybook -s public" }, "lint-staged": { "*.js": [ @@ -53,6 +53,14 @@ "underscore": "^1.8.3" }, "devDependencies": { + "@sambego/storybook-state": "^1.0.7", + "@storybook/addon-actions": "^3.4.8", + "@storybook/addon-info": "^3.4.8", + "@storybook/addon-knobs": "^3.4.8", + "@storybook/addon-links": "^3.4.8", + "@storybook/addon-storyshots": "^3.4.8", + "@storybook/addons": "^3.4.8", + "@storybook/react": "^3.4.8", "babel-cli": "^6.5.1", "babel-core": "^6.5.2", "babel-loader": "^7.1.4", @@ -60,11 +68,13 @@ "babel-preset-es2015": "^6.5.0", "babel-preset-react": "^6.5.0", "babel-preset-stage-0": "^6.5.0", + "babel-runtime": "^7.0.0-beta.3", "chance": "^1.0.6", "enzyme": "3.3.0", "enzyme-to-json": "3.3.1", "eslint-config-prettier": "^2.9.0", "eslint-config-react-app": "^2.1.0", + "jest-css-modules": "^1.1.0", "lint-staged": "^7.0.0", "prettier": "^1.11.1", "prop-types": "^15.6.1", @@ -77,5 +87,10 @@ }, "eslintConfig": { "extends": "react-app" + }, + "jest": { + "moduleNameMapper": { + "\\.(css)$": "/node_modules/jest-css-modules" + } } } diff --git a/packages/react-dynamic-forms/src/components/AsyncChooser.js b/packages/react-dynamic-forms/src/components/AsyncChooser.js new file mode 100644 index 0000000..89fb1a2 --- /dev/null +++ b/packages/react-dynamic-forms/src/components/AsyncChooser.js @@ -0,0 +1,301 @@ +/** + * Copyright (c) 2015 - present, The Regents of the University of California, + * through Lawrence Berkeley National Laboratory (subject to receipt + * of any required approvals from the U.S. Dept. of Energy). + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from "react"; +import _ from "underscore"; +import PropTypes from "prop-types"; +import Flexbox from "flexbox-react"; +import Immutable from "immutable"; + +import formGroup from "../js/formGroup"; +import { textView } from "../js/renderers"; +import { editAction } from "../js/actions"; +import { inlineChooserStyle, inlineDoneButtonStyle, inlineCancelButtonStyle } from "../js/style"; + +import "react-select/dist/react-select.css"; +import "react-virtualized/styles.css"; +import "react-virtualized-select/styles.css"; + +import Select from "react-virtualized-select"; + +import "../css/chooser.css"; + +/** + * React Form control to select an item from a list. The list is built from + * an async call. Once the call has been made and the options list if built + * the list is immutable. Also note that if a value is provided (current or + * default) that value will only actually show once the list is received. + */ +export class AsyncChooser extends React.Component { + constructor(props) { + super(props); + this.state = { isFocused: false, focusChooser: false }; + this.handleChange = this.handleChange.bind(this); + this.loadOptions = this.loadOptions.bind(this); + + this.loadedOptions = Immutable.List(); + } + + handleMouseEnter() { + this.setState({ hover: true }); + } + + handleMouseLeave() { + this.setState({ hover: false }); + } + + handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + + handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + + handleCancel() { + if (this.props.onChange) { + const v = this.state.oldValue; + this.props.onChange(this.props.name, v); + } + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + + isEmpty(value) { + return _.isNull(value) || _.isUndefined(value) || value === ""; + } + + isMissing(value = this.props.value) { + return this.props.required && !this.props.disabled && this.isEmpty(value); + } + + componentDidMount() { + const missing = + this.props.required && + !this.props.disabled && + (_.isNull(this.props.value) || + _.isUndefined(this.props.value) || + this.props.value === ""); + const missingCount = missing ? 1 : 0; + + if (this.props.onMissingCountChange) { + this.props.onMissingCountChange(this.props.name, missingCount); + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.selected && this.props.edit !== nextProps.edit && nextProps.edit === true) { + this.setState({ focusChooser: true }); + } + if ( + this.props.value !== nextProps.value || + (!this.props.value && nextProps.value) || + (this.props.value && !nextProps.value) + ) { + // The value might have been missing and is now set explicitly + // with a prop + const missing = + this.props.required && + !this.props.disabled && + (_.isNull(nextProps.value) || + _.isUndefined(nextProps.value) || + nextProps.value === ""); + const missingCount = missing ? 1 : 0; + + if (this.props.onMissingCountChange) { + this.props.onMissingCountChange(this.props.name, missingCount); + } + } + } + + componentDidUpdate() { + if (this.state.focusChooser) { + this.chooser.focus(); + this.setState({ focusChooser: false }); + } + } + + handleChange(item) { + let { value, label } = item || {}; + + const isMissing = this.props.required && _.isNull(item); + const id = !isMissing && !_.isNaN(Number(value)) ? +value : value; + + // Callbacks + if (this.props.onChange) { + this.props.onChange(this.props.name, Immutable.Map({ id, label })); + } + if (this.props.onMissingCountChange) { + this.props.onMissingCountChange(this.props.name, isMissing ? 1 : 0); + } + } + + handleEditItem() { + this.props.onEditItem(this.props.name); + } + + getOptionList(options) { + return options + .map(item => { + let disabled = false; + const isDisabled = item.has("disabled") && item.get("disabled") === true; + if (_.contains(this.props.disableList, item.get("id")) || isDisabled) { + disabled = true; + } + return { value: item.get("id"), label: item.get("label"), disabled }; + }) + .toJS(); + } + + loadOptions(input, cb) { + if (this.cachedOptions) { + cb(null, { + options: this.getOptionList(this.cachedOptions), + complete: true + }); + return; + } + + this.props.loader(input, (err, options) => { + cb(err, { + options: this.getOptionList(options), + complete: true + }); + this.cachedOptions = options; + }); + } + + render() { + const choice = this.props.value ? this.props.value.get("id") : null; + const isMissing = this.isMissing(this.props.value); + + if (this.props.edit) { + const chooserStyle = { marginBottom: 10, width: "100%" }; + const clearable = this.props.allowSingleDeselect; + const matchPos = this.props.searchContains ? "any" : "start"; + + return ( + +
this.handleFocus(e)}> + -
-
- - -
-
- -
- - - */ - let formClass = this.props.formClassName; if (this.props.inline) { formClass += "form-inline"; @@ -493,7 +486,8 @@ export default class Form extends React.Component { } Form.propTypes = { - value: PropTypes.object + value: PropTypes.object, + initialValue: PropTypes.object }; Form.defaultProps = { @@ -501,4 +495,4 @@ Form.defaultProps = { formClass: "form-horizontal", formKey: "form", groupLayout: FormGroupLayout.ROW -}; \ No newline at end of file +}; diff --git a/packages/react-dynamic-forms/src/components/List.js b/packages/react-dynamic-forms/src/components/List.js index a66defe..187d9cf 100644 --- a/packages/react-dynamic-forms/src/components/List.js +++ b/packages/react-dynamic-forms/src/components/List.js @@ -12,6 +12,7 @@ import React from "react"; import _ from "underscore"; import Flexbox from "flexbox-react"; import ReactCSSTransitionGroup from "react-transition-group/CSSTransitionGroup"; +import { inlineDoneButtonStyle, inlineCancelButtonStyle } from "../js/style"; import "../css/list.css"; import "../css/icon.css"; @@ -35,6 +36,20 @@ import "../css/icon.css"; * possible items that can be added from a list). */ export default class List extends React.Component { + constructor(props) { + super(props); + this.state = { + hover: false + }; + } + handleMouseEnter() { + this.setState({ hover: true }); + } + + handleMouseLeave() { + this.setState({ hover: false }); + } + addItem() { if (this.props.onAddItem) { this.props.onAddItem(); @@ -53,6 +68,13 @@ export default class List extends React.Component { } } + revertItem(index) { + if (this.props.onRevertItem) { + this.props.onRevertItem(index); + this.selectItem(null); + } + } + handleDeselect() { this.selectItem(null); } @@ -62,10 +84,12 @@ export default class List extends React.Component { const addMinus = this.props.canRemoveItems; const addEdit = this.props.canEditItems; + const mouseOver = this.state.hover; + // Plus [+] icon - let plus; - if (addPlus) { - plus = ( + let plusIcon; + if (addPlus && mouseOver) { + plusIcon = ( ); } else { - plus =
; + plusIcon =
; } + const LISTWIDTH = 600; + const ICONWIDTH = 28; + // Build the item list, which is a list of table rows, each row containing // an item and a [-] icon used for removing that item. let itemList = _.map(this.props.items, (item, index) => { @@ -100,13 +127,12 @@ export default class List extends React.Component { isEditable = true; } if (isEditable) { - if (addMinus && !itemMinusHide) { + if (addMinus && !itemMinusHide && mouseOver) { minus = ( this.removeItem(index)} /> ); } else { @@ -114,53 +140,41 @@ export default class List extends React.Component { minus =
; } - const flip = { - transform: "scaleX(-1)", - fontSize: 10 - }; - // Edit item icon - if (addEdit) { - if (isBeingEdited) { - edit = ( - this.selectItem(index)} - /> - ); - } else { - edit = ( - this.selectItem(index)} - /> - ); - } + if (addEdit && mouseOver) { + edit = ( + + ); } } const minusAction = addMinus ? ( - - + this.removeItem(index)}> + {minus} ) : ( -
+
); const editAction = addEdit ? ( - - + { + this.selectItem(index); + }} + > + {edit} @@ -169,32 +183,84 @@ export default class List extends React.Component { ); // JSX for each row, includes: UI Item and [x] remove item button - return ( -
  • - - {minusAction} - {editAction} - - - {item} - + if (!isBeingEdited) { + return ( +
  • + + + + {item} + + + {minusAction} + {editAction} - -
  • - ); + + ); + } else { + return ( +
  • + + + + + {item} + + + + + {this.props.canCommitItem ? ( + this.handleDeselect()} + > + DONE + + ) : ( + DONE + )} + this.revertItem(index)} + > + CANCEL + + + + {minusAction} + +
  • + ); + } }); // Build the [+] elements - if (addPlus) { + let plus; + if (addPlus && mouseOver) { if (this.props.plusElement) { plus = this.props.plusElement; } else { @@ -204,9 +270,9 @@ export default class List extends React.Component { - {plus} + {plusIcon}
    @@ -214,9 +280,41 @@ export default class List extends React.Component { ); } } else { - plus =
    ; + plus =
    ; } + // + // Build the header + // + + const headerStyle = { + fontSize: 11, + paddingTop: 3, + height: 20, + color: "#9a9a9a", + borderBottom: "#ddd", + borderBottomStyle: "solid", + borderBottomWidth: 1 + }; + + const headerItems = _.map(this.props.header, (size, label) => { + return ( + + + {label} + + + ); + }); + + const header = this.props.header ? ( + + {headerItems} + + ) : ( +
    + ); + // // Build the table of item rows, with the [+] at the bottom if required // @@ -224,11 +322,15 @@ export default class List extends React.Component { return (
    this.handleMouseEnter()} + onMouseLeave={() => this.handleMouseLeave()} > + {header}
      { + return item.get("id") === this.props.value; + }); + return choiceItem ? choiceItem.get("label") : ""; + } + + handleMouseEnter() { + this.setState({ hover: true }); + } + + handleMouseLeave() { + this.setState({ hover: false }); + } + + handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + + handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } + } + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } + handleChange(v) { // Callbacks if (this.props.onChange) { this.props.onChange(this.props.name, v); } + } + + handleEditItem() { + this.props.onEditItem(this.props.name); + } + + handleDone() { if (this.props.onBlur) { this.props.onBlur(this.props.name); } + this.setState({ isFocused: false, hover: false, oldValue: null }); } - getCurrentChoiceLabel() { - const choiceItem = this.props.optionList.find(item => { - return item.get("id") === this.props.value; - }); - return choiceItem ? choiceItem.get("label") : ""; - } - - inlineStyle(hasError, isMissing) { - let color = "inherited"; - let background = "inherited"; - if (hasError) { - color = "#b94a48"; - background = "#fff0f3"; - } else if (isMissing) { - background = "floralwhite"; + handleCancel() { + if (this.props.onChange) { + const v = this.state.oldValue; + this.props.onChange(this.props.name, v); } - return { - color, - background, - width: "100%", - paddingLeft: 3 - }; + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); } render() { if (this.props.edit) { + const editStyle = { + borderStyle: "solid", + borderRadius: 2, + borderWidth: 1, + padding: 5, + borderColor: "#ececec", + marginBottom: 5 + }; + const items = this.props.optionList.map((item, i) => { const id = item.get("id"); const label = item.get("label"); @@ -70,15 +111,49 @@ class RadioButtons extends React.Component { ); }); return ( -
      - {items} +
      +
      this.handleFocus(e)} + onKeyUp={e => this.handleKeyPress(e)} + style={editStyle} + > + {items} +
      + {this.props.selected ? ( + + this.handleDone()} + > + DONE + + this.handleCancel()} + > + CANCEL + + + ) : ( +
      + )}
      ); } else { - let text = this.getCurrentChoiceLabel(); + let s = this.getCurrentChoiceLabel(); + const view = this.props.view || textView; + const text = {view(s)}; + const edit = editAction(this.state.hover && this.props.allowEdit, () => + this.handleEditItem() + ); return ( -
      +
      this.handleMouseEnter()} + onMouseLeave={() => this.handleMouseLeave()} + > {text} + {edit}
      ); } diff --git a/packages/react-dynamic-forms/src/components/TagsEdit.js b/packages/react-dynamic-forms/src/components/TagsEdit.js index d3d791e..19814b6 100644 --- a/packages/react-dynamic-forms/src/components/TagsEdit.js +++ b/packages/react-dynamic-forms/src/components/TagsEdit.js @@ -12,8 +12,11 @@ import React from "react"; import _ from "underscore"; import { Creatable } from "react-select"; import Immutable from "immutable"; +import Flexbox from "flexbox-react"; import formGroup from "../js/formGroup"; +import { editAction } from "../js/actions"; +import { inlineDoneButtonStyle, inlineCancelButtonStyle } from "../js/style"; import "react-select/dist/react-select.css"; import "../css/tagsedit.css"; @@ -25,8 +28,8 @@ import "../css/tagsedit.css"; class TagsEdit extends React.Component { constructor(props) { super(props); - this.state = { - touched: false + this.state = { + touched: false }; } @@ -38,6 +41,17 @@ class TagsEdit extends React.Component { } } } + handleMouseEnter() { + this.setState({ hover: true }); + } + + handleMouseLeave() { + this.setState({ hover: false }); + } + + handleEditItem() { + this.props.onEditItem(this.props.name); + } handleChange(tags) { const value = _.map(tags, tag => tag.label); @@ -69,6 +83,39 @@ class TagsEdit extends React.Component { return this.props.required && !this.props.disabled && this.isEmpty(value); } + handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); + } + } + + handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } + } + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); + } + } + + handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + + handleCancel() { + if (this.props.onChange) { + const v = this.state.oldValue; + this.props.onChange(this.props.name, v); + } + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); + } + render() { const isMissing = this.isMissing(this.props.value); if (this.props.edit) { @@ -89,19 +136,40 @@ class TagsEdit extends React.Component { } return ( -
      - this.handleChange(value)} - /> -
      +
      + + this.handleChange(value)} + onFocus={e => this.handleFocus(e)} + onKeyUp={e => this.handleKeyPress(e)} + /> +
      + {this.props.selected ? ( + + this.handleDone()} + > + DONE + + this.handleCancel()} + > + CANCEL + + + ) : ( +
      + )} +
      ); } else { @@ -112,13 +180,27 @@ class TagsEdit extends React.Component { paddingLeft: 5, paddingRight: 5, background: "#ececec", - borderRadius: 2, + borderRadius: 4, + borderStyle: "solid", + borderWidth: 1, + borderColor: "#eaeaea", marginLeft: 2, marginRight: 2 }; + const edit = editAction(this.state.hover && this.props.allowEdit, () => + this.handleEditItem() + ); return ( -
      - {this.props.value.map((tag, i) => {tag})} +
      this.handleMouseEnter()} + onMouseLeave={() => this.handleMouseLeave()} + > + {this.props.value.map((tag, i) => ( + + {tag} + + ))} + {edit}
      ); } diff --git a/packages/react-dynamic-forms/src/components/TextArea.js b/packages/react-dynamic-forms/src/components/TextArea.js index b49c999..5fbd536 100644 --- a/packages/react-dynamic-forms/src/components/TextArea.js +++ b/packages/react-dynamic-forms/src/components/TextArea.js @@ -13,6 +13,9 @@ import _ from "underscore"; import { validate } from "revalidator"; import formGroup from "../js/formGroup"; +import { textView } from "../js/renderers"; +import { editAction } from "../js/actions"; +import { inlineTextAreaStyle, inlineDoneButtonStyle, inlineCancelButtonStyle } from "../js/style"; import "../css/textarea.css"; @@ -22,11 +25,26 @@ import "../css/textarea.css"; class TextArea extends React.Component { constructor(props) { super(props); - this.state = { - touched: false + this.state = { + value: props.value, + isFocused: false, + touched: false, + selectText: false }; } + handleMouseEnter() { + this.setState({ hover: true }); + } + + handleMouseLeave() { + this.setState({ hover: false }); + } + + handleEditItem() { + this.props.onEditItem(this.props.name); + } + isEmpty(value) { return _.isNull(value) || _.isUndefined(value) || value === ""; } @@ -49,9 +67,9 @@ class TextArea extends React.Component { obj[this.props.name] = value; let properties = {}; - properties[this.props.name] = this.props.rules; + properties[this.props.name] = this.props.validation; - const rules = this.props.rules ? { properties } : null; + const rules = this.props.validation ? { properties } : null; if (obj && rules) { const validation = validate(obj, rules, { cast: true }); const name = this.props.name || "Value"; @@ -67,15 +85,19 @@ class TextArea extends React.Component { } componentWillReceiveProps(nextProps) { - if (this.props.value !== nextProps.value) { + if (nextProps.selected && this.props.edit !== nextProps.edit && nextProps.edit === true) { + this.setState({ selectText: true }); + } + if (this.state.value !== nextProps.value && !this.state.isFocused) { + this.setState({ value: nextProps.value }); + const missing = this.isMissing(nextProps.value); const { validationError } = this.getError(nextProps.value); - // Broadcast error and missing states up to the owner + // Broadcast error and missing states up to the parent if (this.props.onErrorCountChange) { this.props.onErrorCountChange(this.props.name, validationError ? 1 : 0); } - if (this.props.onMissingCountChange) { this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); } @@ -86,7 +108,7 @@ class TextArea extends React.Component { const missing = this.isMissing(this.props.value); const { validationError } = this.getError(this.props.value); - // Initial error and missing states are fed up to the owner + // Initial error and missing states are fed up to the parent if (this.props.onErrorCountChange) { this.props.onErrorCountChange(this.props.name, validationError ? 1 : 0); } @@ -96,41 +118,81 @@ class TextArea extends React.Component { } } - onBlur() { - const { value } = this.textInput; - const missing = this.props.required && this.isEmpty(value); - const { validationError } = this.getError(value); + componentDidUpdate() { + if (this.state.selectText) { + this.textInput.focus(); + this.textInput.select(); + this.setState({ selectText: false }); + } + } + + handleChange(e) { + const name = this.props.name; + const value = e.target.value; - // Callbacks - if (this.props.onChange) { - this.props.onChange(this.props.name, value); + this.setState({ value }, () => { + const missing = this.props.required && this.isEmpty(value); + const { validationError } = this.getError(value); + + // Callbacks + if (this.props.onErrorCountChange) { + this.props.onErrorCountChange(name, validationError ? 1 : 0); + } + + if (this.props.onMissingCountChange) { + this.props.onMissingCountChange(name, missing ? 1 : 0); + } + + if (this.props.onChange) { + this.props.onChange(name, value); + } + }); + } + + handleFocus() { + if (!this.state.isFocused) { + this.setState({ isFocused: true, oldValue: this.props.value }); } - if (this.props.onErrorCountChange) { - this.props.onErrorCountChange(this.props.name, validationError ? 1 : 0); + } + + handleKeyPress(e) { + if (e.key === "Enter") { + if (!e.shiftKey) { + this.handleDone(); + } } - if (this.props.onMissingCountChange) { - this.props.onMissingCountChange(this.props.name, missing ? 1 : 0); + if (e.keyCode === 27 /* ESC */) { + this.handleCancel(); } + } - this.setState({ touched: true }); + handleDone() { + if (this.props.onBlur) { + this.props.onBlur(this.props.name); + } + this.setState({ isFocused: false, hover: false, oldValue: null }); } - inlineStyle(hasError, isMissing) { - let color = ""; - let background = ""; - if (hasError) { - color = "#b94a48"; - background = "#fff0f3"; - } else if (isMissing) { - background = "floralwhite"; + handleCancel() { + if (this.props.onChange) { + const v = this.state.oldValue; + let cast = v; + if (_.has(this.props.rules, "type")) { + switch (this.props.rules.type) { + case "integer": + cast = v === "" ? null : parseInt(v, 10); + break; + case "number": + cast = v === "" ? null : parseFloat(v, 10); + break; + //pass + default: + } + } + this.props.onChange(this.props.name, cast); } - return { - color, - background, - height: "100%", - width: "100%", - paddingLeft: 3 - }; + this.props.onBlur(this.props.name); + this.setState({ isFocused: false, hover: false, oldValue: null }); } render() { @@ -151,38 +213,68 @@ class TextArea extends React.Component { // Warning style const style = isMissing ? { background: "floralwhite" } : {}; + const canCommit = !isMissing && !validationError; + return ( -
      -