@gpa-gemstone/react-forms
Version:
React Form modules for gpa webapps
226 lines (225 loc) • 10.8 kB
JavaScript
;
// ******************************************************************************************************
// 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;