@sdziadkowiec/react-datasheet-grid
Version:
An Excel-like React component to create beautiful spreadsheets.
146 lines • 7.38 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTextColumn = exports.textColumn = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = __importStar(require("react"));
const classnames_1 = __importDefault(require("classnames"));
const useFirstRender_1 = require("../hooks/useFirstRender");
const TextComponent = react_1.default.memo(({ active, focus, rowData, setRowData, columnData: { placeholder, alignRight, formatInputOnFocus, formatBlurredInput, parseUserInput, continuousUpdates, }, }) => {
const ref = (0, react_1.useRef)(null);
const firstRender = (0, useFirstRender_1.useFirstRender)();
// We create refs for async access so we don't have to add it to the useEffect dependencies
const asyncRef = (0, react_1.useRef)({
rowData,
formatInputOnFocus,
formatBlurredInput,
setRowData,
parseUserInput,
continuousUpdates,
firstRender,
// Timestamp of last focus (when focus becomes true) and last change (input change)
// used to prevent un-necessary updates when value was not changed
focusedAt: 0,
changedAt: 0,
// This allows us to keep track of whether or not the user blurred the input using the Esc key
// If the Esc key is used we do not update the row's value (only relevant when continuousUpdates is false)
escPressed: false,
});
asyncRef.current = {
rowData,
formatInputOnFocus,
formatBlurredInput,
setRowData,
parseUserInput,
continuousUpdates,
firstRender,
// Keep the same values across renders
focusedAt: asyncRef.current.focusedAt,
changedAt: asyncRef.current.changedAt,
escPressed: asyncRef.current.escPressed,
};
(0, react_1.useLayoutEffect)(() => {
// When the cell gains focus we make sure to immediately select the text in the input:
// - If the user gains focus by typing, it will replace the existing text, as expected
// - If the user gains focus by clicking or pressing Enter, the text will be preserved and selected
if (focus) {
if (ref.current) {
// Make sure to first format the input
ref.current.value = asyncRef.current.formatInputOnFocus(asyncRef.current.rowData);
ref.current.focus();
ref.current.select();
}
// We immediately reset the escPressed
asyncRef.current.escPressed = false;
// Save current timestamp
asyncRef.current.focusedAt = Date.now();
}
// When the cell looses focus (by pressing Esc, Enter, clicking away...) we make sure to blur the input
// Otherwise the user would still see the cursor blinking
else {
if (ref.current) {
// Update the row's value on blur only if the user did not press escape (only relevant when continuousUpdates is false)
if (!asyncRef.current.escPressed &&
!asyncRef.current.continuousUpdates &&
!asyncRef.current.firstRender &&
// Make sure that focus was gained more than 10 ms ago, used to prevent flickering
asyncRef.current.changedAt >= asyncRef.current.focusedAt) {
asyncRef.current.setRowData(asyncRef.current.parseUserInput(ref.current.value));
}
ref.current.blur();
}
}
}, [focus]);
(0, react_1.useEffect)(() => {
if (!focus && ref.current) {
// On blur or when the data changes, format it for display
ref.current.value = asyncRef.current.formatBlurredInput(rowData);
}
}, [focus, rowData]);
return ((0, jsx_runtime_1.jsx)("input", {
// We use an uncontrolled component for better performance
defaultValue: formatBlurredInput(rowData), className: (0, classnames_1.default)('dsg-input', alignRight && 'dsg-input-align-right'), placeholder: active ? placeholder : undefined,
// Important to prevent any undesired "tabbing"
tabIndex: -1, ref: ref,
// Make sure that while the cell is not focus, the user cannot interact with the input
// The cursor will not change to "I", the style of the input will not change,
// and the user cannot click and edit the input (this part is handled by DataSheetGrid itself)
style: { pointerEvents: focus ? 'auto' : 'none' }, onChange: (e) => {
asyncRef.current.changedAt = Date.now();
// Only update the row's value as the user types if continuousUpdates is true
if (continuousUpdates) {
setRowData(parseUserInput(e.target.value));
}
}, onKeyDown: (e) => {
// Track when user presses the Esc key
if (e.key === 'Escape') {
asyncRef.current.escPressed = true;
}
} }));
});
TextComponent.displayName = 'TextComponent';
exports.textColumn = createTextColumn();
function createTextColumn({ placeholder, alignRight = false, continuousUpdates = true, deletedValue = null, parseUserInput = (value) => (value.trim() || null), formatBlurredInput = (value) => String(value !== null && value !== void 0 ? value : ''), formatInputOnFocus = (value) => String(value !== null && value !== void 0 ? value : ''), formatForCopy = (value) => String(value !== null && value !== void 0 ? value : ''), parsePastedValue = (value) => (value.replace(/[\n\r]+/g, ' ').trim() || null), } = {}) {
return {
component: TextComponent,
columnData: {
placeholder,
alignRight,
continuousUpdates,
formatInputOnFocus,
formatBlurredInput,
parseUserInput,
},
deleteValue: () => deletedValue,
copyValue: ({ rowData }) => formatForCopy(rowData),
pasteValue: ({ value }) => parsePastedValue(value),
isCellEmpty: ({ rowData }) => rowData === null || rowData === undefined,
};
}
exports.createTextColumn = createTextColumn;
//# sourceMappingURL=textColumn.js.map