@gpa-gemstone/react-forms 1.1.92 → 1.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ interface IProps {
2
2
  /**
3
3
  * Callback function that will be called when a file is uploaded
4
4
  * */
5
- OnLoadHandler: (result: File) => void;
5
+ OnLoadHandler: (result: File) => Promise<any>;
6
6
  /**
7
7
  * Callback function that will be called when clear button is clicked
8
8
  * */
package/lib/FileUpload.js CHANGED
@@ -28,9 +28,12 @@ var FileUpload = function (props) {
28
28
  var _a = React.useState(null), fileName = _a[0], setFileName = _a[1];
29
29
  var _b = React.useState(null), fileSize = _b[0], setFileSize = _b[1];
30
30
  var _c = React.useState(false), isFileUpload = _c[0], setIsFileUploaded = _c[1];
31
+ var _d = React.useState('unintiated'), uploadStatus = _d[0], setUploadStatus = _d[1];
31
32
  var handleFileUpload = function (evt) {
32
- if (evt.target == null || evt.target.files == null || evt.target.files.length === 0)
33
+ if (evt.target == null || evt.target.files == null || evt.target.files.length === 0) {
34
+ setUploadStatus('unintiated');
33
35
  return;
36
+ }
34
37
  var file = evt.target.files[0];
35
38
  setMetaData(file);
36
39
  };
@@ -49,6 +52,7 @@ var FileUpload = function (props) {
49
52
  e.stopPropagation();
50
53
  };
51
54
  var handleOnClear = function () {
55
+ setUploadStatus('unintiated');
52
56
  setIsFileUploaded(false);
53
57
  setFileName(null);
54
58
  setFileSize(null);
@@ -57,31 +61,42 @@ var FileUpload = function (props) {
57
61
  var setMetaData = function (file) {
58
62
  setFileName(file.name);
59
63
  setFileSize(file.size);
60
- props.OnLoadHandler(file);
61
- setIsFileUploaded(true);
64
+ setUploadStatus('loading');
65
+ props.OnLoadHandler(file).then(function () {
66
+ setIsFileUploaded(true);
67
+ setUploadStatus('idle');
68
+ }).catch(function () {
69
+ setIsFileUploaded(false);
70
+ setUploadStatus('error');
71
+ });
62
72
  };
63
73
  return (React.createElement(React.Fragment, null,
64
74
  React.createElement("div", { className: 'row' },
65
75
  React.createElement("div", { className: 'col-auto mt-2 pl-0' },
66
76
  React.createElement("label", { style: { cursor: 'pointer' } },
67
77
  React.createElement(gpa_symbols_1.ReactIcons.ShareArrow, { Color: 'var(--info)' }),
68
- React.createElement("input", { type: "file", onChange: handleFileUpload, accept: props.FileTypeAttribute, style: { display: 'none' } }))),
78
+ React.createElement("input", { type: "file", accept: props.FileTypeAttribute, style: { display: 'none' }, onChange: handleFileUpload }))),
69
79
  React.createElement("div", { className: 'col-auto pl-0' },
70
80
  React.createElement("button", { className: 'btn', onClick: handleOnClear },
71
81
  React.createElement(gpa_symbols_1.ReactIcons.CircledX, { Color: 'red' })))),
72
- isFileUpload ?
73
- React.createElement(React.Fragment, null,
74
- React.createElement("div", { className: 'row align-items-center justify-content-center', style: { border: '2px dashed var(--secondary)', borderRadius: '0.5em' } },
75
- React.createElement("div", { className: 'col-auto' },
76
- "File Name: ", fileName !== null && fileName !== void 0 ? fileName : ''),
77
- React.createElement("div", { className: 'col-auto' },
78
- "File Size: ",
79
- formatFileSize(fileSize))))
82
+ uploadStatus === 'error' ?
83
+ React.createElement("div", { className: "alert alert-danger fade show" }, "An error occured while uploading file.")
84
+ : null,
85
+ React.createElement("div", { className: 'row', onDragOver: handleDragOver, onDrop: handleDrop, style: { border: '2px dashed var(--secondary)', borderRadius: '0.5em' } }, uploadStatus === 'loading' ?
86
+ React.createElement("div", { className: 'd-flex col-12 align-items-center justify-content-center' },
87
+ React.createElement(gpa_symbols_1.ReactIcons.SpiningIcon, { Size: 200 }))
80
88
  :
81
- React.createElement("div", { className: 'row', onDragOver: handleDragOver, onDrop: handleDrop, style: { border: '2px dashed var(--secondary)', borderRadius: '0.5em' } },
82
- React.createElement("div", { className: 'col-12 pt-3 pb-3 d-flex justify-content-center align-items-center' },
83
- React.createElement(gpa_symbols_1.ReactIcons.Image, { Size: 100 }),
84
- React.createElement("span", null, "Drag and Drop")))));
89
+ isFileUpload ?
90
+ React.createElement(React.Fragment, null,
91
+ React.createElement("div", { className: 'col-auto' },
92
+ "File Name: ", fileName !== null && fileName !== void 0 ? fileName : ''),
93
+ React.createElement("div", { className: 'col-auto' },
94
+ "File Size: ",
95
+ formatFileSize(fileSize)))
96
+ :
97
+ React.createElement("div", { className: 'col-12 pt-3 pb-3 d-flex justify-content-center align-items-center' },
98
+ React.createElement(gpa_symbols_1.ReactIcons.Image, { Size: 100 }),
99
+ React.createElement("span", null, "Drag and Drop")))));
85
100
  };
86
101
  //Helper Functions
87
102
  var formatFileSize = function (size) {
@@ -0,0 +1,10 @@
1
+ import { IProps as ISearchableSelectProps } from './SearchableSelect';
2
+ interface IProps<T> extends ISearchableSelectProps<T> {
3
+ /**
4
+ * Default value to use when adding an item and when value is null
5
+ * @type {number}
6
+ */
7
+ DefaultValue: number | string;
8
+ }
9
+ declare function MultiSearchableSelect<T>(props: IProps<T>): JSX.Element;
10
+ export default MultiSearchableSelect;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ //******************************************************************************************************
3
+ // MultiSearchableSelect.tsx - Gbtc
4
+ //
5
+ // Copyright © 2024, Grid Protection Alliance. All Rights Reserved.
6
+ //
7
+ // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
8
+ // the NOTICE file distributed with this work for additional information regarding copyright ownership.
9
+ // The GPA may license this file to you under the MIT License (MIT), the "License"; you may not use this
10
+ // file except in compliance with the License. You may obtain a copy of the License at:
11
+ //
12
+ // http://opensource.org/licenses/MIT
13
+ //
14
+ // Unless agreed to in writing, the subject software distributed under the License is distributed on an
15
+ // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
16
+ // License for the specific language governing permissions and limitations.
17
+ //
18
+ // Code Modification History:
19
+ // ----------------------------------------------------------------------------------------------------
20
+ // 07/01/2025 - Preston Crawford
21
+ // Generated original version of source code.
22
+ //
23
+ //******************************************************************************************************
24
+ var __assign = (this && this.__assign) || function () {
25
+ __assign = Object.assign || function(t) {
26
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
27
+ s = arguments[i];
28
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
29
+ t[p] = s[p];
30
+ }
31
+ return t;
32
+ };
33
+ return __assign.apply(this, arguments);
34
+ };
35
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
36
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
37
+ if (ar || !(i in from)) {
38
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
39
+ ar[i] = from[i];
40
+ }
41
+ }
42
+ return to.concat(ar || Array.prototype.slice.call(from));
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ var React = require("react");
46
+ var SearchableSelect_1 = require("./SearchableSelect");
47
+ var ToolTip_1 = require("./ToolTip");
48
+ var gpa_symbols_1 = require("@gpa-gemstone/gpa-symbols");
49
+ var helper_functions_1 = require("@gpa-gemstone/helper-functions");
50
+ //Only supporting string/number arrays for now
51
+ function MultiSearchableSelect(props) {
52
+ var _a;
53
+ var guid = React.useState((0, helper_functions_1.CreateGuid)())[0];
54
+ var _b = React.useState(false), showHelp = _b[0], setShowHelp = _b[1];
55
+ var fieldArray = props.Record[props.Field];
56
+ if ((fieldArray === null || fieldArray === void 0 ? void 0 : fieldArray.constructor) !== Array) {
57
+ console.warn("MultiInput: ".concat(props.Field.toString(), " is not of type array."));
58
+ return React.createElement(React.Fragment, null);
59
+ }
60
+ return (React.createElement(React.Fragment, null,
61
+ fieldArray.length === 0 ?
62
+ React.createElement(React.Fragment, null,
63
+ React.createElement("label", { className: 'd-flex align-items-center' },
64
+ React.createElement("span", null, (_a = props.Label) !== null && _a !== void 0 ? _a : props.Field),
65
+ props.Help != null ?
66
+ React.createElement("span", { className: "ml-2 d-flex align-items-center", onMouseEnter: function () { return setShowHelp(true); }, onMouseLeave: function () { return setShowHelp(false); }, "data-tooltip": guid },
67
+ React.createElement(gpa_symbols_1.ReactIcons.QuestionMark, { Color: "var(--info)", Size: 20 }))
68
+ : null,
69
+ React.createElement("button", { className: 'btn', onClick: function () {
70
+ var _a;
71
+ return props.Setter(__assign(__assign({}, props.Record), (_a = {}, _a[props.Field] = [props.DefaultValue], _a)));
72
+ } },
73
+ " ",
74
+ React.createElement(gpa_symbols_1.ReactIcons.CirclePlus, null),
75
+ " ")),
76
+ React.createElement(ToolTip_1.default, { Show: showHelp && props.Help != null, Target: guid, Class: "info", Position: "bottom" }, props.Help))
77
+ : null,
78
+ fieldArray.map(function (r, index) {
79
+ var _a, _b;
80
+ return (React.createElement("div", { className: 'row align-items-center', key: index },
81
+ React.createElement("div", { className: 'col' },
82
+ React.createElement(SearchableSelect_1.default, { Record: fieldArray, Field: index, Label: index === 0 ? props.Label : '', Help: index === 0 ? props.Help : undefined, Feedback: props.Feedback, Valid: function () { return props.Valid != null ? props.Valid(props.Field) : true; }, Style: props.Style, Disabled: props.Disabled, Setter: function (record) {
83
+ var _a;
84
+ var newArray = __spreadArray([], fieldArray, true);
85
+ newArray[index] = record[index];
86
+ props.Setter(__assign(__assign({}, props.Record), (_a = {}, _a[props.Field] = newArray, _a)));
87
+ }, Search: props.Search, BtnStyle: props.BtnStyle, GetLabel: props.GetLabel, ResetSearchOnSelect: props.ResetSearchOnSelect, AllowCustom: props.AllowCustom })),
88
+ React.createElement("div", { className: 'col-auto' },
89
+ React.createElement("button", { className: 'btn', style: ((_a = props.Disabled) !== null && _a !== void 0 ? _a : false) ? { display: 'none' } : undefined, onClick: function () {
90
+ var _a;
91
+ var newRecords = __spreadArray([], fieldArray, true).filter(function (_, i) { return i !== index; });
92
+ props.Setter(__assign(__assign({}, props.Record), (_a = {}, _a[props.Field] = newRecords, _a)));
93
+ } },
94
+ React.createElement(gpa_symbols_1.ReactIcons.TrashCan, { Color: 'red' }))),
95
+ index === __spreadArray([], fieldArray, true).length - 1 ?
96
+ React.createElement("div", { className: 'col-auto' },
97
+ React.createElement("button", { className: 'btn', style: ((_b = props.Disabled) !== null && _b !== void 0 ? _b : false) ? { display: 'none' } : undefined, onClick: function () {
98
+ var _a;
99
+ var newRecords = __spreadArray(__spreadArray([], __spreadArray([], fieldArray, true), true), [props.DefaultValue], false);
100
+ props.Setter(__assign(__assign({}, props.Record), (_a = {}, _a[props.Field] = newRecords, _a)));
101
+ } },
102
+ React.createElement(gpa_symbols_1.ReactIcons.CirclePlus, null)))
103
+ : null));
104
+ })));
105
+ }
106
+ exports.default = MultiSearchableSelect;
@@ -108,7 +108,7 @@ var MultiSelect = function (props) {
108
108
  ((_a = props.ItemTooltip) !== null && _a !== void 0 ? _a : 'no-tip') !== 'no-tip' ?
109
109
  React.createElement(ToolTip_1.default, { Show: showItems, Target: guid, Position: "bottom" },
110
110
  React.createElement("p", null, "Selected Options:"),
111
- selectedOptions.slice(0, 10).map(function (opt) { return React.createElement("p", null, opt.Label); }),
111
+ selectedOptions.slice(0, 10).map(function (opt, i) { return React.createElement("p", { key: i }, opt.Label); }),
112
112
  selectedOptions.length > 10 ? React.createElement("p", null, "and ".concat(selectedOptions.length - 10, " other(s)")) : null)
113
113
  : null,
114
114
  React.createElement("div", { ref: multiSelect, style: { position: 'relative', display: 'block', width: 'inherit' } },
@@ -1,9 +1,18 @@
1
1
  import * as React from 'react';
2
2
  import { Gemstone } from '@gpa-gemstone/application-typings';
3
- export interface AbortablePromise<T> extends PromiseLike<T> {
4
- abort?: () => void;
5
- }
6
- interface IProps<T> extends Gemstone.TSX.Interfaces.IBaseFormProps<T> {
3
+ export interface IProps<T> extends Gemstone.TSX.Interfaces.IBaseFormProps<T> {
4
+ /**
5
+ * Function to determine the validity of a field
6
+ * @param field - Field of the record to check
7
+ * @returns {boolean}
8
+ */
9
+ Valid?: (field: keyof T) => boolean;
10
+ /**
11
+ * Feedback message to show when input is invalid
12
+ * @type {string}
13
+ * @optional
14
+ */
15
+ Feedback?: string;
7
16
  /**
8
17
  * Flag to allow custom input values
9
18
  * @type {boolean}
@@ -15,7 +24,7 @@ interface IProps<T> extends Gemstone.TSX.Interfaces.IBaseFormProps<T> {
15
24
  * @param search - Search string
16
25
  * @returns {AbortablePromise<T>}
17
26
  */
18
- Search: (search: string) => AbortablePromise<Gemstone.TSX.Interfaces.ILabelValue<string | number>[]>;
27
+ Search: (search: string) => Gemstone.TSX.Interfaces.AbortablePromise<Gemstone.TSX.Interfaces.ILabelValue<string | number>[]>;
19
28
  /**
20
29
  * CSS styles to apply to the form group
21
30
  * @type {React.CSSProperties}
@@ -28,11 +37,10 @@ interface IProps<T> extends Gemstone.TSX.Interfaces.IBaseFormProps<T> {
28
37
  * @optional
29
38
  */
30
39
  BtnStyle?: React.CSSProperties;
31
- GetLabel?: () => AbortablePromise<string>;
40
+ GetLabel?: () => Gemstone.TSX.Interfaces.AbortablePromise<string>;
32
41
  /**
33
- * Flag to reset search text to an empty string when a user selects an option. Defaulting to false
42
+ * Flag to reset search text to an empty string when a user selects an option or when the element loses focus. Defaulting to false
34
43
  */
35
44
  ResetSearchOnSelect?: boolean;
36
45
  }
37
46
  export default function SearchableSelect<T>(props: IProps<T>): JSX.Element;
38
- export {};
@@ -30,33 +30,35 @@ var getInitialSearchText = function (useBlankString, recordValue) {
30
30
  return useBlankString ? '' : recordValue;
31
31
  };
32
32
  function SearchableSelect(props) {
33
- var _a, _b;
34
- var _c = React.useState(function () { var _a, _b, _c; return getInitialSearchText((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false, (_c = (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : ''); }), search = _c[0], setSearch = _c[1];
35
- var _d = React.useState((_b = (_a = props.Record[props.Field]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ''), label = _d[0], setLabel = _d[1];
36
- var _e = React.useState([]), results = _e[0], setResults = _e[1];
37
- var _f = React.useState(false), loading = _f[0], setLoading = _f[1];
38
- React.useEffect(function () {
33
+ var _a = React.useState(function () { var _a, _b, _c; return getInitialSearchText((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false, (_c = (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : ''); }), search = _a[0], setSearch = _a[1];
34
+ var _b = React.useState([]), searchOptions = _b[0], setSearchOptions = _b[1];
35
+ var _c = React.useState(false), loading = _c[0], setLoading = _c[1];
36
+ var setter = React.useCallback(function (record, selectedOption) {
37
+ handleSetSearch(selectedOption);
38
+ props.Setter(record);
39
+ }, [props.Setter, props.Field]);
40
+ var handleSetSearch = React.useCallback(function (selectedOption) {
39
41
  var _a, _b, _c;
40
- if (props.GetLabel === undefined) {
41
- if ((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false)
42
- setSearch('');
43
- else
44
- setLabel((_c = (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '');
42
+ if ((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false) {
43
+ setSearch('');
44
+ return;
45
45
  }
46
- else {
47
- setLoading(true);
48
- var handle_1 = props.GetLabel();
49
- handle_1.then(function (lab) {
50
- setLabel(lab);
51
- setSearch(lab);
52
- setLoading(false);
53
- });
54
- return function () {
55
- if ((handle_1 === null || handle_1 === void 0 ? void 0 : handle_1.abort) != null)
56
- handle_1.abort();
57
- };
46
+ if (props.GetLabel === undefined) {
47
+ var newSearch = (_c = selectedOption !== null && selectedOption !== void 0 ? selectedOption : (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '';
48
+ setSearch(newSearch);
49
+ return;
58
50
  }
59
- }, [props.GetLabel, props.Record[props.Field], props.ResetSearchOnSelect]);
51
+ setLoading(true);
52
+ var handle = props.GetLabel();
53
+ handle.then(function (lab) {
54
+ setSearch(lab);
55
+ setLoading(false);
56
+ }, function () { return setLoading(false); });
57
+ }, [props.ResetSearchOnSelect, props.GetLabel, props.Record[props.Field]]);
58
+ //Effect to set search when props.Record[props.Field] changes externally
59
+ React.useEffect(function () {
60
+ handleSetSearch();
61
+ }, [props.Record[props.Field], handleSetSearch]);
60
62
  // Call props.Search every 500ms to avoid hammering the server while typing
61
63
  React.useEffect(function () {
62
64
  setLoading(true);
@@ -64,7 +66,7 @@ function SearchableSelect(props) {
64
66
  var timeoutHandle = setTimeout(function () {
65
67
  searchHandle = props.Search(search);
66
68
  searchHandle.then(function (d) {
67
- setResults(d.map(function (o) { return ({ Value: o.Value, Element: o.Label }); }));
69
+ setSearchOptions(d.map(function (o) { return ({ Value: o.Value, Element: o.Label }); }));
68
70
  setLoading(false);
69
71
  }, function () {
70
72
  setLoading(false);
@@ -77,38 +79,25 @@ function SearchableSelect(props) {
77
79
  clearTimeout(timeoutHandle);
78
80
  };
79
81
  }, [search]);
80
- var update = React.useCallback(function (record, selectedOption) {
81
- var _a, _b, _c;
82
- var stringVal = (_b = (_a = record[props.Field]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '';
83
- var newLabel = stringVal;
84
- if (!React.isValidElement(selectedOption.Element))
85
- newLabel = selectedOption.Element;
86
- setLabel(newLabel);
87
- props.Setter(record);
88
- if ((_c = props.ResetSearchOnSelect) !== null && _c !== void 0 ? _c : false)
89
- setSearch('');
90
- else
91
- setSearch(newLabel);
92
- }, [props.Setter, props.Field, label]);
93
82
  var options = React.useMemo(function () {
94
- var _a, _b;
83
+ var _a, _b, _c, _d;
95
84
  var ops = [];
96
85
  ops.push({
97
86
  Value: props.Record[props.Field],
98
87
  Element: React.createElement("div", { className: 'input-group' },
99
- React.createElement("input", { type: "text", className: "form-control", value: search, onChange: function (d) { return setSearch(d.target.value); }, onBlur: function () { return setSearch(label); }, onClick: function (evt) { evt.preventDefault(); evt.stopPropagation(); }, disabled: (_a = props.Disabled) !== null && _a !== void 0 ? _a : false }),
88
+ React.createElement("input", { type: "text", className: "form-control ".concat(((_b = (_a = props.Valid) === null || _a === void 0 ? void 0 : _a.call(props, props.Field)) !== null && _b !== void 0 ? _b : true) ? '' : 'border-danger'), value: search, onChange: function (d) { return setSearch(d.target.value); }, onBlur: function () { return handleSetSearch(); }, onClick: function (evt) { evt.preventDefault(); evt.stopPropagation(); }, disabled: (_c = props.Disabled) !== null && _c !== void 0 ? _c : false }),
100
89
  loading ?
101
90
  React.createElement("div", { className: "input-group-append" },
102
91
  React.createElement("span", { className: "input-group-text" },
103
92
  React.createElement(gpa_symbols_1.ReactIcons.SpiningIcon, null)))
104
93
  : null)
105
94
  });
106
- if ((_b = props.AllowCustom) !== null && _b !== void 0 ? _b : false)
95
+ if ((_d = props.AllowCustom) !== null && _d !== void 0 ? _d : false)
107
96
  ops.push({ Value: search, Element: React.createElement(React.Fragment, null,
108
97
  search,
109
98
  " (Entered Value)") });
110
- ops.push.apply(ops, results.filter(function (f) { return f.Value !== search && f.Value !== props.Record[props.Field]; }));
99
+ ops.push.apply(ops, searchOptions.filter(function (f) { return f.Value !== search && f.Value !== props.Record[props.Field]; }));
111
100
  return ops;
112
- }, [search, props.Record[props.Field], results, props.Disabled, loading, label]);
113
- return React.createElement(StylableSelect_1.default, { Record: props.Record, Field: props.Field, Setter: update, Label: props.Label, Disabled: props.Disabled, Help: props.Help, Style: props.Style, Options: options, BtnStyle: props.BtnStyle });
101
+ }, [search, props.Record[props.Field], props.Field, searchOptions, props.Disabled, loading, props.Valid, handleSetSearch]);
102
+ return React.createElement(StylableSelect_1.default, { Record: props.Record, Field: props.Field, Setter: setter, Label: props.Label, Disabled: props.Disabled, Help: props.Help, Style: props.Style, Options: options, BtnStyle: props.BtnStyle, Valid: props.Valid, Feedback: props.Feedback });
114
103
  }
@@ -4,6 +4,18 @@ export interface IOption {
4
4
  Element: React.ReactElement<any> | string;
5
5
  }
6
6
  interface IProps<T> {
7
+ /**
8
+ * Function to determine the validity of a field
9
+ * @param field - Field of the record to check
10
+ * @returns {boolean}
11
+ */
12
+ Valid?: (field: keyof T) => boolean;
13
+ /**
14
+ * Feedback message to show when input is invalid
15
+ * @type {string}
16
+ * @optional
17
+ */
18
+ Feedback?: string;
7
19
  /**
8
20
  * Record to be used in form
9
21
  * @type {T}
@@ -42,17 +42,17 @@ var react_portal_1 = require("react-portal");
42
42
  var _ = require("lodash");
43
43
  var gpa_symbols_1 = require("@gpa-gemstone/gpa-symbols");
44
44
  function StylableSelect(props) {
45
- var _a;
45
+ var _a, _b, _c;
46
46
  // State hooks and ref for managing component state and interactions.
47
47
  var stylableSelect = React.useRef(null);
48
48
  var selectTable = React.useRef(null);
49
49
  var tableContainer = React.useRef(null);
50
- var _b = React.useState(false), show = _b[0], setShow = _b[1];
51
- var _c = React.useState(props.Options[0].Element), selected = _c[0], setSelected = _c[1];
52
- var _d = React.useState(""), guid = _d[0], setGuid = _d[1];
53
- var _e = React.useState(false), showHelp = _e[0], setShowHelp = _e[1];
54
- var _f = React.useState({ Top: 0, Left: 0, Width: 0, Height: 0 }), position = _f[0], setPosition = _f[1];
55
- React.useEffect(function () {
50
+ var _d = React.useState(false), show = _d[0], setShow = _d[1];
51
+ var _e = React.useState(props.Options[0].Element), selected = _e[0], setSelected = _e[1];
52
+ var _f = React.useState(""), guid = _f[0], setGuid = _f[1];
53
+ var _g = React.useState(false), showHelp = _g[0], setShowHelp = _g[1];
54
+ var _h = React.useState({ Top: 0, Left: 0, Width: 0, Height: 0 }), position = _h[0], setPosition = _h[1];
55
+ React.useLayoutEffect(function () {
56
56
  var updatePosition = _.debounce(function () {
57
57
  if (stylableSelect.current != null) {
58
58
  var rect = stylableSelect.current.getBoundingClientRect();
@@ -82,6 +82,16 @@ function StylableSelect(props) {
82
82
  // Ignore if disabled or not a mousedown event
83
83
  if ((props.Disabled === undefined ? false : props.Disabled) || evt.type !== 'mousedown' || stylableSelect.current == null)
84
84
  return;
85
+ // if we’re about to OPEN it, measure right now
86
+ if (!show && stylableSelect.current != null) {
87
+ var rect = stylableSelect.current.getBoundingClientRect();
88
+ setPosition({
89
+ Top: rect.bottom,
90
+ Left: rect.left,
91
+ Width: rect.width,
92
+ Height: rect.height
93
+ });
94
+ }
85
95
  //ignore the click if it was inside the table or table container
86
96
  if ((selectTable.current != null && selectTable.current.contains(evt.target)) || (tableContainer.current != null && tableContainer.current.contains(evt.target)))
87
97
  return;
@@ -131,8 +141,9 @@ function StylableSelect(props) {
131
141
  props.Help !== undefined ?
132
142
  React.createElement(ToolTip_1.default, { Show: showHelp, Target: guid, Class: "info", Position: "bottom" }, props.Help)
133
143
  : null,
134
- React.createElement("button", { type: "button", style: __assign({ border: '1px solid #ced4da', padding: '.375rem .75rem', fontSize: '1rem', borderRadius: '.25rem' }, ((_a = props.BtnStyle) !== null && _a !== void 0 ? _a : {})), className: "btn form-control dropdown-toggle", onClick: HandleShow, disabled: props.Disabled === undefined ? false : props.Disabled },
144
+ React.createElement("button", { type: "button", style: __assign({ padding: '.375rem .75rem' }, ((_a = props.BtnStyle) !== null && _a !== void 0 ? _a : {})), className: "dropdown-toggle form-control ".concat(((_c = (_b = props.Valid) === null || _b === void 0 ? void 0 : _b.call(props, props.Field)) !== null && _c !== void 0 ? _c : true) ? '' : 'is-invalid'), onClick: HandleShow, disabled: props.Disabled === undefined ? false : props.Disabled },
135
145
  React.createElement("div", { style: props.Style }, selected)),
146
+ React.createElement("div", { className: "invalid-feedback" }, props.Feedback == null ? props.Field.toString() + ' is a required field.' : props.Feedback),
136
147
  React.createElement(react_portal_1.Portal, null,
137
148
  React.createElement("div", { ref: tableContainer, className: 'popover', style: {
138
149
  maxHeight: window.innerHeight - position.Top,
@@ -45,7 +45,7 @@ function ToggleSwitch(props) {
45
45
  // Variables to control the rendering of label and help icon.
46
46
  var showHelpIcon = props.Help !== undefined;
47
47
  var label = props.Label === undefined ? props.Field : props.Label;
48
- return (React.createElement("div", { className: "custom-control custom-switch", style: props.Style },
48
+ return (React.createElement("div", { className: "custom-control custom-switch form-group", style: props.Style },
49
49
  React.createElement("input", { type: "checkbox", className: "custom-control-input", onChange: function (evt) {
50
50
  var record = __assign({}, props.Record);
51
51
  record[props.Field] = evt.target.checked;
package/lib/index.d.ts CHANGED
@@ -19,4 +19,5 @@ import RadioButtons from './RadioButtons';
19
19
  import FileUpload from './FileUpload';
20
20
  import MultiInput from './MultiInput';
21
21
  import ToolTip from './ToolTip';
22
- export { CheckBox, Input, DatePicker, Select, TextArea, DateRangePicker, EnumCheckBoxes, ArrayMultiSelect, ArrayCheckBoxes, MultiCheckBoxSelect, DoubleInput, TimePicker, StylableSelect, ColorPicker, SearchableSelect, ToggleSwitch, InputWithButton, RadioButtons, FileUpload, MultiInput, ToolTip };
22
+ import MultiSearchableSelect from './MultiSearchableSelect';
23
+ export { CheckBox, Input, DatePicker, Select, TextArea, DateRangePicker, EnumCheckBoxes, ArrayMultiSelect, ArrayCheckBoxes, MultiCheckBoxSelect, DoubleInput, TimePicker, StylableSelect, ColorPicker, SearchableSelect, ToggleSwitch, InputWithButton, RadioButtons, FileUpload, MultiInput, ToolTip, MultiSearchableSelect };
package/lib/index.js CHANGED
@@ -22,7 +22,7 @@
22
22
  //
23
23
  // ******************************************************************************************************
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.ToolTip = exports.MultiInput = exports.FileUpload = exports.RadioButtons = exports.InputWithButton = exports.ToggleSwitch = exports.SearchableSelect = exports.ColorPicker = exports.StylableSelect = exports.TimePicker = exports.DoubleInput = exports.MultiCheckBoxSelect = exports.ArrayCheckBoxes = exports.ArrayMultiSelect = exports.EnumCheckBoxes = exports.DateRangePicker = exports.TextArea = exports.Select = exports.DatePicker = exports.Input = exports.CheckBox = void 0;
25
+ exports.MultiSearchableSelect = exports.ToolTip = exports.MultiInput = exports.FileUpload = exports.RadioButtons = exports.InputWithButton = exports.ToggleSwitch = exports.SearchableSelect = exports.ColorPicker = exports.StylableSelect = exports.TimePicker = exports.DoubleInput = exports.MultiCheckBoxSelect = exports.ArrayCheckBoxes = exports.ArrayMultiSelect = exports.EnumCheckBoxes = exports.DateRangePicker = exports.TextArea = exports.Select = exports.DatePicker = exports.Input = exports.CheckBox = void 0;
26
26
  var CheckBox_1 = require("./CheckBox");
27
27
  exports.CheckBox = CheckBox_1.default;
28
28
  var Input_1 = require("./Input");
@@ -65,3 +65,5 @@ var MultiInput_1 = require("./MultiInput");
65
65
  exports.MultiInput = MultiInput_1.default;
66
66
  var ToolTip_1 = require("./ToolTip");
67
67
  exports.ToolTip = ToolTip_1.default;
68
+ var MultiSearchableSelect_1 = require("./MultiSearchableSelect");
69
+ exports.MultiSearchableSelect = MultiSearchableSelect_1.default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpa-gemstone/react-forms",
3
- "version": "1.1.92",
3
+ "version": "1.1.94",
4
4
  "description": "React Form modules for gpa webapps",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -45,9 +45,9 @@
45
45
  "typescript": "5.5.3"
46
46
  },
47
47
  "dependencies": {
48
- "@gpa-gemstone/application-typings": "0.0.86",
48
+ "@gpa-gemstone/application-typings": "0.0.87",
49
49
  "@gpa-gemstone/gpa-symbols": "0.0.52",
50
- "@gpa-gemstone/helper-functions": "0.0.43",
50
+ "@gpa-gemstone/helper-functions": "0.0.44",
51
51
  "@types/react": "^17.0.14",
52
52
  "@types/styled-components": "^5.1.11",
53
53
  "lodash": "^4.17.21",