riec
Version:
Modern React component for inline edit of text/select values, written in Typescript
506 lines (456 loc) • 13.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
var react = require('@xstate/react');
var xstate = require('xstate');
var getInlineEditMachine = function getInlineEditMachine(_ref) {
var value = _ref.value,
isDisabled = _ref.isDisabled,
allowEditWhileLoading = _ref.allowEditWhileLoading,
optimisticUpdate = _ref.optimisticUpdate,
validate = _ref.validate,
onChange = _ref.onChange,
saveTimeout = _ref.saveTimeout,
savedDuration = _ref.savedDuration,
errorDuration = _ref.errorDuration;
return xstate.Machine({
id: 'inlineEdit',
initial: 'view',
context: {
value: value,
newValue: '',
oldValue: '',
isValid: validate && typeof validate === 'function' ? validate(value) : true
},
states: {
view: {
entry: 'reset',
on: {
CLICK: {
target: 'edit',
cond: 'isEnabled'
},
FOCUS: {
target: 'edit',
cond: 'isEnabled'
},
SAVED: {
target: 'saved',
actions: 'commitChange'
}
}
},
edit: {
entry: 'validate',
on: {
CHANGE: {
target: 'edit',
actions: 'change'
},
ESC: 'view',
ENTER: [{
target: 'loading',
cond: 'shouldSend'
}, {
target: 'view'
}],
BLUR: [{
target: 'loading',
cond: 'shouldSend'
}, {
target: 'view'
}]
}
},
loading: {
entry: [optimisticUpdate ? 'optimisticUpdate' : 'noAction', 'sendChange'],
on: {
CLICK: {
target: 'edit',
cond: 'canEditWhileLoading'
},
FOCUS: {
target: 'edit',
cond: 'canEditWhileLoading'
},
SAVED: {
target: 'saved',
actions: 'commitChange'
}
},
after: {
SAVE_TIMEOUT: {
target: 'error',
actions: optimisticUpdate ? 'cancelChange' : 'noAction'
}
}
},
saved: {
on: {
CLICK: {
target: 'edit',
cond: 'isEnabled'
},
FOCUS: {
target: 'edit',
cond: 'isEnabled'
},
SAVED: {
target: 'saved',
actions: 'commitChange'
}
},
after: {
SAVED_DURATION: {
target: 'view'
}
}
},
error: {
on: {
CLICK: {
target: 'edit',
cond: 'isEnabled'
},
FOCUS: {
target: 'edit',
cond: 'isEnabled'
},
SAVED: {
target: 'saved',
actions: 'commitChange'
}
},
after: {
ERROR_DURATION: {
target: 'view'
}
}
}
}
}, {
actions: {
change: xstate.assign({
newValue: function newValue(_, event) {
return event.value;
}
}),
reset: xstate.assign({
newValue: function newValue(context) {
return context.value;
}
}),
optimisticUpdate: xstate.assign({
oldValue: function oldValue(context) {
return context.value;
},
value: function value(context) {
return context.newValue;
}
}),
noAction: function noAction() {},
sendChange: function sendChange(context) {
onChange(context.newValue);
},
commitChange: xstate.assign({
value: function value(_, event) {
return event.value;
}
}),
cancelChange: xstate.assign({
value: function value(context) {
return context.oldValue;
}
}),
validate: validate && typeof validate === 'function' ? xstate.assign({
isValid: function isValid(context) {
return validate(context.newValue);
}
}) : function () {}
},
guards: {
shouldSend: function shouldSend(context) {
return context.isValid && context.newValue !== context.value;
},
isEnabled: function isEnabled() {
return !isDisabled;
},
canEditWhileLoading: function canEditWhileLoading() {
return !isDisabled && allowEditWhileLoading;
}
},
delays: {
SAVE_TIMEOUT: saveTimeout,
SAVED_DURATION: savedDuration,
ERROR_DURATION: errorDuration
}
});
};
var InputType;
(function (InputType) {
InputType["Text"] = "text";
InputType["Number"] = "number";
InputType["Email"] = "email";
InputType["Password"] = "password";
InputType["Date"] = "date";
InputType["Range"] = "range";
InputType["TextArea"] = "textarea";
InputType["Select"] = "select";
})(InputType || (InputType = {}));
var InputType$1 = InputType;
var Input = function Input(_ref) {
var value = _ref.value,
type = _ref.type,
editProps = _ref.editProps,
editClassProp = _ref.editClassProp,
options = _ref.options,
valueKey = _ref.valueKey,
labelKey = _ref.labelKey,
handleChange = _ref.handleChange,
handleKeyDown = _ref.handleKeyDown,
handleBlur = _ref.handleBlur;
//==========================
// Focus input as it mounts
// =========================
var inputRef = React.useRef(null);
var textareaRef = React.useRef(null);
var selectRef = React.useRef(null);
var getRef = React.useCallback(function () {
if (type === InputType$1.Select) {
return selectRef;
}
if (type === InputType$1.TextArea) {
return textareaRef;
}
return inputRef;
}, [type]);
React.useEffect(function () {
var controlRef = getRef();
if (controlRef.current) {
setTimeout(function () {
if (controlRef.current) {
// Focus input
controlRef.current.focus();
if (controlRef === inputRef || controlRef === textareaRef) {
// If it is not a Select => select input content
controlRef.current.select();
}
}
}, 10);
}
}, [getRef]); //==========================
// Select
// =========================
if (type === InputType$1.Select) {
return React__default.createElement("select", Object.assign({}, editProps, editClassProp, {
ref: selectRef,
value: value,
onChange: function onChange(event) {
return handleChange(event.target.value);
},
onKeyDown: handleKeyDown,
onBlur: handleBlur
}), options.map(function (option) {
return React__default.createElement("option", {
key: option[valueKey],
value: option[valueKey]
}, option[labelKey]);
}));
} //==========================
// TextArea
// =========================
if (type === InputType$1.TextArea) {
return React__default.createElement("textarea", Object.assign({}, editProps, editClassProp, {
ref: textareaRef,
value: value,
onChange: function onChange(event) {
return handleChange(event.target.value);
},
onKeyDown: handleKeyDown,
onBlur: handleBlur
}));
} //==========================
// All Others
// =========================
return React__default.createElement("input", Object.assign({}, editProps, editClassProp, {
ref: inputRef,
type: type,
value: value,
onChange: function onChange(event) {
return handleChange(event.target.value);
},
onKeyDown: handleKeyDown,
onBlur: handleBlur
}));
};
var InlineEdit = function InlineEdit(_ref) {
var value = _ref.value,
onChange = _ref.onChange,
_ref$type = _ref.type,
type = _ref$type === void 0 ? InputType$1.Text : _ref$type,
format = _ref.format,
render = _ref.render,
validate = _ref.validate,
_ref$isDisabled = _ref.isDisabled,
isDisabled = _ref$isDisabled === void 0 ? false : _ref$isDisabled,
_ref$allowEditWhileLo = _ref.allowEditWhileLoading,
allowEditWhileLoading = _ref$allowEditWhileLo === void 0 ? false : _ref$allowEditWhileLo,
_ref$optimisticUpdate = _ref.optimisticUpdate,
optimisticUpdate = _ref$optimisticUpdate === void 0 ? true : _ref$optimisticUpdate,
_ref$saveTimeout = _ref.saveTimeout,
saveTimeout = _ref$saveTimeout === void 0 ? 2000 : _ref$saveTimeout,
_ref$savedDuration = _ref.savedDuration,
savedDuration = _ref$savedDuration === void 0 ? 700 : _ref$savedDuration,
_ref$errorDuration = _ref.errorDuration,
errorDuration = _ref$errorDuration === void 0 ? 1000 : _ref$errorDuration,
editProps = _ref.editProps,
viewClass = _ref.viewClass,
editClass = _ref.editClass,
disabledClass = _ref.disabledClass,
loadingClass = _ref.loadingClass,
invalidClass = _ref.invalidClass,
savedClass = _ref.savedClass,
errorClass = _ref.errorClass,
_ref$showNewLines = _ref.showNewLines,
showNewLines = _ref$showNewLines === void 0 ? true : _ref$showNewLines,
_ref$options = _ref.options,
options = _ref$options === void 0 ? [] : _ref$options,
_ref$valueKey = _ref.valueKey,
valueKey = _ref$valueKey === void 0 ? 'value' : _ref$valueKey,
_ref$labelKey = _ref.labelKey,
labelKey = _ref$labelKey === void 0 ? 'label' : _ref$labelKey;
//==========================
// XState Machine
// =========================
var _useMachine = react.useMachine(getInlineEditMachine({
value: value,
isDisabled: isDisabled,
allowEditWhileLoading: allowEditWhileLoading,
optimisticUpdate: optimisticUpdate,
validate: validate,
onChange: onChange,
saveTimeout: saveTimeout,
savedDuration: savedDuration,
errorDuration: errorDuration
})),
current = _useMachine[0],
send = _useMachine[1]; //==========================
// Send SAVED event when a
// new value is received
// =========================
var isFirstRun = React.useRef(true);
React.useEffect(function () {
// Prevent triggering SAVED
// on first render
if (isFirstRun.current) {
isFirstRun.current = false;
return;
} // Trigger it on value changes
send({
type: 'SAVED',
value: value
});
}, [value]); //==========================
// Event Handlers
// =========================
var handleChange = function handleChange(value) {
send({
type: 'CHANGE',
value: value
});
if (type === InputType$1.Select) {
send('ENTER');
}
};
var handleBlur = function handleBlur() {
send('BLUR');
};
var handleKeyDown = function handleKeyDown(event) {
if (event.keyCode === 13 && type !== InputType$1.TextArea) {
send('ENTER');
} else if (event.keyCode === 27) {
send('ESC');
}
}; //==========================
// CSS Classes View
// =========================
var viewClassNames = [];
if (viewClass) {
viewClassNames.push(viewClass);
}
if (loadingClass && current.value === 'loading') {
viewClassNames.push(loadingClass);
}
if (savedClass && current.value === 'saved') {
viewClassNames.push(savedClass);
}
if (errorClass && current.value === 'error') {
viewClassNames.push(errorClass);
}
if (disabledClass && isDisabled) {
viewClassNames.push(disabledClass);
}
var viewClassProp = viewClassNames.length > 0 ? {
className: viewClassNames.join(' ')
} : {}; //==========================
// CSS Classes Edit
// =========================
var editClassNames = [];
if (editClass) {
editClassNames.push(editClass);
}
if (invalidClass && !current.context.isValid) {
editClassNames.push(invalidClass);
}
var editClassProp = editClassNames.length > 0 ? {
className: editClassNames.join(' ')
} : {}; //==========================
// Format View Value
// =========================
var viewValue = current.context.value; // If Select => get label
if (type === InputType$1.Select) {
var valueOption = options.find(function (option) {
return option[valueKey] + '' === current.context.value;
});
if (valueOption) {
viewValue = valueOption[labelKey];
}
} // If format function, apply
if (format) {
viewValue = format(viewValue);
} // If TextArea and showNewLine, do it
if (type === InputType$1.TextArea && showNewLines) {
viewValue = viewValue.split('\n').map(function (item, key) {
return React.createElement("span", {
key: key
}, item, React.createElement("br", null));
});
} //==========================
// Render
// =========================
return React.createElement(React.Fragment, null, (current.value === 'view' || current.value === 'loading' || current.value === 'saved' || current.value === 'error') && React.createElement("span", Object.assign({}, viewClassProp, {
onClick: function onClick() {
return send('CLICK');
},
onFocus: function onFocus() {
return send('FOCUS');
},
tabIndex: 0
}), render ? render(viewValue) : viewValue), current.value === 'edit' && React.createElement(Input, {
type: type,
value: current.context.newValue,
editProps: editProps,
editClassProp: editClassProp,
options: options,
valueKey: valueKey,
labelKey: labelKey,
handleChange: handleChange,
handleKeyDown: handleKeyDown,
handleBlur: handleBlur
}));
};
exports.InputType = InputType$1;
exports.default = InlineEdit;
//# sourceMappingURL=riec.cjs.development.js.map