UNPKG

@gpa-gemstone/react-forms

Version:
226 lines (225 loc) 10.8 kB
"use strict"; // ****************************************************************************************************** // AutoCompleteInput.tsx - Gbtc // // Copyright © 2024, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this // file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 01/21/2026 - Natalie Beatty // Generated original version of source code. // // ****************************************************************************************************** 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCurrentVariable = exports.getSuggestions = void 0; exports.default = AutoCompleteInput; var React = require("react"); var Input_1 = require("./Input"); var react_portal_1 = require("react-portal"); var _ = require("lodash"); function AutoCompleteInput(props) { var autoCompleteInput = React.useRef(null); var inputElement = React.useRef(null); var tableContainer = React.useRef(null); var selectTable = React.useRef(null); var _a = React.useState([]), suggestions = _a[0], setSuggestions = _a[1]; var _b = React.useState(null), position = _b[0], setPosition = _b[1]; var _c = React.useState(true), show = _c[0], setShow = _c[1]; // Handle showing and hiding of the dropdown. var HandleShow = React.useCallback(function (evt) { var _a; // Ignore if disabled or not a mousedown event if ((props.Disabled === undefined ? false : props.Disabled) || evt.type !== 'mousedown') { return; } //ignore the click if it was inside the table or table container if ((selectTable.current != null && selectTable.current.contains(evt.target)) || (tableContainer.current != null && tableContainer.current.contains(evt.target))) { return; } if (inputElement.current !== null && !((_a = inputElement.current) === null || _a === void 0 ? void 0 : _a.contains(evt.target))) { setShow(false); } else { setShow(true); } }, [props.Disabled]); // update dropdown position React.useLayoutEffect(function () { if ((suggestions === null || suggestions === void 0 ? void 0 : suggestions.length) == 0) { setPosition(null); return; } var updatePosition = _.debounce(function () { if (inputElement.current == null) { return; } var rect = inputElement.current.getBoundingClientRect(); setPosition({ Top: rect.bottom, Left: rect.left, Width: rect.width, Height: rect.height }); }, 200); var handleScroll = function () { if (tableContainer.current == null) return; updatePosition(); }; updatePosition(); window.addEventListener('scroll', handleScroll, true); window.addEventListener('resize', updatePosition); return function () { window.removeEventListener('scroll', handleScroll, true); window.removeEventListener('resize', updatePosition); updatePosition.cancel(); }; }, [suggestions]); // listen for changes in input caret position React.useEffect(function () { var autoComplete = inputElement.current; if (autoComplete == null) return; autoComplete.addEventListener("keyup", handleCaretPosition); autoComplete.addEventListener("click", handleCaretPosition); window.addEventListener('mousedown', HandleShow, false); return function () { autoComplete.removeEventListener("keyup", handleCaretPosition); autoComplete.removeEventListener("click", handleCaretPosition); window.removeEventListener('mousedown', HandleShow, false); }; }, [HandleShow]); // edit input text when suggestion is selected var handleOptionClick = function (option) { var _a; var _b, _c, _d, _e, _f; if (inputElement.current == null) return; var currentPos = (_b = inputElement.current.selectionStart) !== null && _b !== void 0 ? _b : 0; var optionLength = option.Value.length; props.Setter(__assign(__assign({}, props.Record), (_a = {}, _a[props.Field] = option.Value, _a))); var textLength = (_d = (_c = inputElement.current.textContent) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0; var newCaretPos = (optionLength > textLength ? textLength - 1 : optionLength + currentPos); (_e = inputElement.current) === null || _e === void 0 ? void 0 : _e.focus(); (_f = inputElement.current) === null || _f === void 0 ? void 0 : _f.setSelectionRange(newCaretPos, newCaretPos); setSuggestions([]); }; // update variable when caret position changes var handleCaretPosition = function () { var _a, _b, _c, _d, _e; if (inputElement.current !== null) { var selection = ((_a = inputElement.current.selectionStart) !== null && _a !== void 0 ? _a : 0); var variable = (0, exports.getCurrentVariable)((_c = (_b = inputElement.current) === null || _b === void 0 ? void 0 : _b.getAttribute('value')) !== null && _c !== void 0 ? _c : "", selection); var suggests = (0, exports.getSuggestions)(variable, (_e = (_d = inputElement.current) === null || _d === void 0 ? void 0 : _d.getAttribute('value')) !== null && _e !== void 0 ? _e : "", props.Options); setSuggestions(suggests); } }; return (React.createElement("div", { ref: autoCompleteInput }, React.createElement(Input_1.default, __assign({}, props, { InputRef: inputElement })), position == null || !show ? null : React.createElement(react_portal_1.Portal, null, React.createElement("div", { ref: tableContainer, className: 'popover', style: { maxHeight: window.innerHeight - position.Top, overflowY: 'auto', padding: '10 5', display: 'block', position: 'absolute', zIndex: 9999, top: "".concat(position.Top, "px"), left: "".concat(position.Left, "px"), minWidth: "".concat(position.Width, "px"), maxWidth: '100%' } }, React.createElement("table", { className: "table table-hover", style: { margin: 0 }, ref: selectTable }, React.createElement("tbody", null, suggestions.map(function (f, i) { return (f.Value === props.Record[props.Field] ? null : React.createElement("tr", { key: i, onMouseDown: function (_) { return handleOptionClick(f); } }, React.createElement("td", null, f.Label))); }))))))); } var getSuggestions = function (variable, text, options) { if (!(variable.Variable != null)) { return []; } // if variable is valid option and hasEndBracket, assume it doesn't need autocompletion. if (options.includes(variable.Variable)) { return []; } if (text === "") { return []; } // Find suggestions for the variable var possibleVariables = options.filter(function (v) { var _a; return v.toLowerCase().includes(((_a = variable.Variable) !== null && _a !== void 0 ? _a : "").toLowerCase()); }); var before = text.substring(0, (variable.Start) - 1); var after = text.substring(variable.End); var hasEndBracket = (text[variable.End] === '}'); // Generate suggestions var suggestions = possibleVariables.map(function (pv) { // Ensure we have braces around the variable and add closing '}' if it was missing var variableWithBraces = hasEndBracket ? "{".concat(pv) : "{".concat(pv, "}"); return { Label: "".concat(variableWithBraces).concat(hasEndBracket ? '}' : ''), Value: "".concat(before).concat(variableWithBraces).concat(after) }; }); return suggestions; }; exports.getSuggestions = getSuggestions; var getCurrentVariable = function (text, selection) { var thisVariable = { Start: 0, End: 0, Variable: null }; if (text === "") { return thisVariable; } // easy returns if selection start could not have a curly bracket before it if (selection === null || selection < 0 || selection > text.length) { return thisVariable; } // check backwards from the caret to find the nearest open curly bracket or space var start = selection; while (start > 0) { // check for open curly bracket. if found, assign and break as start of valid variable expression if (/{/g.test(text[start - 1])) { break; } // if space is encountered first, return if (/[\s}]/g.test(text[start - 1])) { return thisVariable; } start--; } // if no variable found, return if (start == 0) { return thisVariable; } thisVariable.Start = start; // then, get the rest of the word. var end = start !== null && start !== void 0 ? start : 0; while (end < text.length) { if (/[}{\s}]/.test(text[end])) { break; } end++; } thisVariable.End = end; // get variable as substring of text var variable = text.substring(start, end); return { Start: thisVariable.Start, End: thisVariable.End, Variable: variable }; }; exports.getCurrentVariable = getCurrentVariable;