@fisherwise/react-autocomplete-hint
Version:
A React component for Autocomplete hint
256 lines (255 loc) • 12.6 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hint = void 0;
var react_1 = __importStar(require("react"));
var utils_1 = require("./utils");
var Hint = function (props) {
var _a, _b;
var child = react_1.default.Children.only(props.children);
if (((_b = (_a = child.type) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== 'input') {
throw new TypeError("react-autocomplete-hint: 'Hint' only accepts an 'input' element as child.");
}
var options = props.options, disableHint = props.disableHint, allowLeftClickFill = props.allowLeftClickFill, allowArrowFill = props.allowArrowFill, allowTabFill = props.allowTabFill, allowEnterFill = props.allowEnterFill, detectFocus = props.detectFocus, detectBlur = props.detectBlur, continuousHint = props.continuousHint, hintColor = props.hintColor, onFill = props.onFill, onHint = props.onHint, onEmpty = props.onEmpty, valueModifier = props.valueModifier;
var childProps = child.props;
var mainInputRef = (0, react_1.useRef)(null);
var hintWrapperRef = (0, react_1.useRef)(null);
var hintRef = (0, react_1.useRef)(null);
var _c = (0, react_1.useState)(''), unModifiedInputText = _c[0], setUnmodifiedInputText = _c[1];
var _d = (0, react_1.useState)(''), text = _d[0], setText = _d[1];
var _e = (0, react_1.useState)(''), hint = _e[0], setHint = _e[1];
var _f = (0, react_1.useState)(), match = _f[0], setMatch = _f[1];
var _g = (0, react_1.useState)(), changeEvent = _g[0], setChangeEvent = _g[1];
(0, react_1.useEffect)(function () {
if (typeof options[0] === 'object') {
var duplicate = (0, utils_1.getFirstDuplicateOption)(options);
if (duplicate) {
console.warn("react-autocomplete-hint: \"".concat(duplicate, "\" occurs more than once and may cause errors. Options should not contain duplicate values!"));
}
}
}, [options]);
(0, react_1.useEffect)(function () {
if (disableHint) {
return;
}
var inputStyle = mainInputRef.current && window.getComputedStyle(mainInputRef.current);
inputStyle && styleHint(hintWrapperRef, hintRef, inputStyle);
});
var getMatches = (0, react_1.useCallback)(function (text) {
if (!text || text === '') {
return [];
}
if (typeof (options[0]) === 'string') {
var matches = options
.filter(function (x) { return x.toLowerCase().startsWith(text.toLowerCase()); })
.sort();
onHint && onHint(matches);
var matchesWithoutOriginal = matches
.filter(function (x) { return x.toLowerCase() !== text.toLowerCase(); })
.sort();
return matchesWithoutOriginal;
}
else {
var matches = options
.filter(function (x) { return x.label.toLowerCase().startsWith(text.toLowerCase()); })
.sort(function (a, b) { return (0, utils_1.sortAsc)(a.label, b.label); });
onHint && onHint(matches);
var matchesWithoutOriginal = matches
.filter(function (x) { return x.label.toLowerCase() !== text.toLowerCase(); })
.sort(function (a, b) { return (0, utils_1.sortAsc)(a.label, b.label); });
return matchesWithoutOriginal;
}
}, [options, onHint]);
var setHintTextAndId = function (text) {
setText(text);
var matches = getMatches(text);
var match = matches && matches.length > 0 ? matches[0] : undefined;
var hint;
if (!match) {
hint = '';
}
else if (typeof match === 'string') {
hint = match.slice(text.length);
}
else {
hint = match.label.slice(text.length);
}
setHint(hint);
setMatch(match);
};
var handleOnFill = function () {
if (hint === '' || !changeEvent) {
return;
}
var newUnModifiedText = unModifiedInputText + hint;
changeEvent.target.value = newUnModifiedText;
childProps.onChange && childProps.onChange(changeEvent);
if (!continuousHint) {
setHintTextAndId('');
}
else {
setHintTextAndId(newUnModifiedText);
}
onFill && onFill(match);
setUnmodifiedInputText(newUnModifiedText);
};
var handleOnEmpty = function (text) {
if (onEmpty && text === '')
onEmpty();
};
var styleHint = function (hintWrapperRef, hintRef, inputStyle) {
var _a, _b;
if ((_a = hintWrapperRef === null || hintWrapperRef === void 0 ? void 0 : hintWrapperRef.current) === null || _a === void 0 ? void 0 : _a.style) {
hintWrapperRef.current.style.fontFamily = inputStyle.fontFamily;
hintWrapperRef.current.style.fontSize = inputStyle.fontSize;
hintWrapperRef.current.style.width = inputStyle.width;
hintWrapperRef.current.style.height = inputStyle.height;
hintWrapperRef.current.style.lineHeight = inputStyle.lineHeight;
hintWrapperRef.current.style.boxSizing = inputStyle.boxSizing;
hintWrapperRef.current.style.margin = (0, utils_1.interpolateStyle)(inputStyle, 'margin');
hintWrapperRef.current.style.padding = (0, utils_1.interpolateStyle)(inputStyle, 'padding');
hintWrapperRef.current.style.borderStyle = (0, utils_1.interpolateStyle)(inputStyle, 'border', 'style');
hintWrapperRef.current.style.borderWidth = (0, utils_1.interpolateStyle)(inputStyle, 'border', 'width');
}
if ((_b = hintRef === null || hintRef === void 0 ? void 0 : hintRef.current) === null || _b === void 0 ? void 0 : _b.style) {
hintRef.current.style.fontFamily = inputStyle.fontFamily;
hintRef.current.style.fontSize = inputStyle.fontSize;
hintRef.current.style.lineHeight = inputStyle.lineHeight;
}
};
var onChange = function (e) {
setChangeEvent(e);
e.persist();
setUnmodifiedInputText(e.target.value);
var modifiedValue = valueModifier ? valueModifier(e.target.value) : e.target.value;
setHintTextAndId(modifiedValue);
handleOnEmpty(e.target.value);
childProps.onChange && childProps.onChange(e);
};
var onFocus = function (e) {
if (detectFocus) {
setHintTextAndId(e.target.value);
childProps.onFocus && childProps.onFocus(e);
}
};
var onBlur = function (e) {
// Only blur it if the new focus isn't the the hint input
if (detectBlur && (hintRef === null || hintRef === void 0 ? void 0 : hintRef.current) !== e.relatedTarget) {
setHintTextAndId('');
childProps.onBlur && childProps.onBlur(e);
}
};
var caretIsAtTextEnd = (0, react_1.useCallback)(function (e) {
// For selectable input types ("text", "search"), only select the hint if
// it's at the end of the input value. For non-selectable types ("email",
// "number"), always select the hint.
var isNonSelectableType = e.currentTarget.selectionEnd === null;
var caretIsAtTextEnd = isNonSelectableType || e.currentTarget.selectionEnd === e.currentTarget.value.length;
return caretIsAtTextEnd;
}, []);
var ARROWRIGHT = 'ArrowRight';
var TAB = 'Tab';
var ENTER = 'Enter';
var onKeyDown = (0, react_1.useCallback)(function (e) {
if (caretIsAtTextEnd(e) && allowArrowFill && e.key === ARROWRIGHT) {
e.preventDefault();
handleOnFill();
}
else if (caretIsAtTextEnd(e) && allowTabFill && e.key === TAB && hint !== '') {
e.preventDefault();
handleOnFill();
}
else if (caretIsAtTextEnd(e) && allowEnterFill && e.key === ENTER && hint !== '') {
e.preventDefault();
handleOnFill();
}
childProps.onKeyDown && childProps.onKeyDown(e);
}, [caretIsAtTextEnd, allowArrowFill, allowTabFill, allowEnterFill, hint]);
var onHintClick = function (e) {
var _a;
var hintCaretPosition = e.currentTarget.selectionEnd || 0;
(_a = mainInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
if (allowLeftClickFill && !!hint && hint !== '') {
handleOnFill();
setTimeout(function () {
var _a, _b;
(_a = mainInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
var caretPosition = text.length + hintCaretPosition;
(_b = mainInputRef.current) === null || _b === void 0 ? void 0 : _b.setSelectionRange(caretPosition, caretPosition);
}, 0);
}
};
var childRef = (0, react_1.cloneElement)(child).ref;
var mainInput = (0, react_1.cloneElement)(child, __assign(__assign({}, childProps), { onChange: onChange, onBlur: onBlur, onFocus: onFocus, onKeyDown: onKeyDown, ref: childRef && typeof (childRef) !== 'string'
? (0, utils_1.mergeRefs)(childRef, mainInputRef)
: mainInputRef }));
return (react_1.default.createElement(react_1.StrictMode, null,
react_1.default.createElement("div", { className: "rah-input-wrapper", style: {
position: 'relative'
} }, disableHint
? child
: (react_1.default.createElement(react_1.default.Fragment, null,
mainInput,
react_1.default.createElement("span", { className: "rah-hint-wrapper", ref: hintWrapperRef, style: {
display: 'flex',
pointerEvents: 'none',
backgroundColor: 'transparent',
borderColor: 'transparent',
boxShadow: 'none',
color: 'rgba(0, 0, 0, 0.35)',
position: 'absolute',
top: 0,
left: 0,
} },
react_1.default.createElement("span", { className: 'rah-text-filler', style: {
visibility: 'hidden',
pointerEvents: 'none',
whiteSpace: 'pre'
} }, text),
react_1.default.createElement("input", { className: "rah-hint", ref: hintRef, onClick: onHintClick, style: {
pointerEvents: !hint || hint === '' ? 'none' : 'visible',
background: 'transparent',
width: '100%',
outline: 'none',
border: 'none',
boxShadow: 'none',
padding: 0,
margin: 0,
color: hintColor ? hintColor : 'rgba(0, 0, 0, 0.30)',
caretColor: 'transparent'
}, defaultValue: hint, tabIndex: -1 })))))));
};
exports.Hint = Hint;
;