@tellescope/react-components 1.231.0 → 1.232.1

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.
Files changed (50) hide show
  1. package/lib/cjs/Forms/forms.js +1 -1
  2. package/lib/cjs/Forms/forms.js.map +1 -1
  3. package/lib/cjs/Forms/forms.v2.d.ts +1 -1
  4. package/lib/cjs/Forms/forms.v2.js +1 -1
  5. package/lib/cjs/Forms/forms.v2.js.map +1 -1
  6. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  7. package/lib/cjs/Forms/hooks.js +24 -0
  8. package/lib/cjs/Forms/hooks.js.map +1 -1
  9. package/lib/cjs/Forms/inputs.d.ts +6 -3
  10. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  11. package/lib/cjs/Forms/inputs.js +171 -44
  12. package/lib/cjs/Forms/inputs.js.map +1 -1
  13. package/lib/cjs/Forms/inputs.v2.d.ts +7 -11
  14. package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
  15. package/lib/cjs/Forms/inputs.v2.js +16 -445
  16. package/lib/cjs/Forms/inputs.v2.js.map +1 -1
  17. package/lib/esm/CMS/components.d.ts +0 -1
  18. package/lib/esm/CMS/components.d.ts.map +1 -1
  19. package/lib/esm/Forms/form_responses.d.ts +0 -1
  20. package/lib/esm/Forms/form_responses.d.ts.map +1 -1
  21. package/lib/esm/Forms/forms.d.ts +3 -3
  22. package/lib/esm/Forms/forms.js +1 -1
  23. package/lib/esm/Forms/forms.js.map +1 -1
  24. package/lib/esm/Forms/forms.v2.d.ts +4 -4
  25. package/lib/esm/Forms/forms.v2.js +1 -1
  26. package/lib/esm/Forms/forms.v2.js.map +1 -1
  27. package/lib/esm/Forms/hooks.d.ts +0 -1
  28. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  29. package/lib/esm/Forms/hooks.js +24 -0
  30. package/lib/esm/Forms/hooks.js.map +1 -1
  31. package/lib/esm/Forms/inputs.d.ts +7 -4
  32. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  33. package/lib/esm/Forms/inputs.js +173 -46
  34. package/lib/esm/Forms/inputs.js.map +1 -1
  35. package/lib/esm/Forms/inputs.v2.d.ts +8 -12
  36. package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
  37. package/lib/esm/Forms/inputs.v2.js +17 -446
  38. package/lib/esm/Forms/inputs.v2.js.map +1 -1
  39. package/lib/esm/controls.d.ts +2 -2
  40. package/lib/esm/inputs.d.ts +1 -1
  41. package/lib/esm/inputs.native.d.ts +0 -1
  42. package/lib/esm/inputs.native.d.ts.map +1 -1
  43. package/lib/esm/state.d.ts +315 -315
  44. package/lib/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +9 -9
  46. package/src/Forms/forms.tsx +1 -1
  47. package/src/Forms/forms.v2.tsx +1 -1
  48. package/src/Forms/hooks.tsx +33 -5
  49. package/src/Forms/inputs.tsx +224 -35
  50. package/src/Forms/inputs.v2.tsx +20 -639
@@ -72,10 +72,10 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
72
72
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
73
73
  import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
74
74
  import axios from "axios";
75
- import { Autocomplete, Box, Button, Checkbox, Chip, Collapse, Divider, FormControl, FormControlLabel, FormLabel, Grid, IconButton as MuiIconButton, InputLabel, MenuItem, Radio, RadioGroup, Select, TextField, Typography } from "@mui/material";
75
+ import { Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormLabel, Grid, IconButton as MuiIconButton, InputLabel, MenuItem, Radio, RadioGroup, Select, TextField, Typography } from "@mui/material";
76
76
  import { useDropzone } from "react-dropzone";
77
77
  import { CANVAS_TITLE, EMOTII_TITLE, INSURANCE_RELATIONSHIPS, INSURANCE_RELATIONSHIPS_CANVAS, PRIMARY_HEX, RELATIONSHIP_TYPES, TELLESCOPE_GENDERS } from "@tellescope/constants";
78
- import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, format_stripe_subscription_interval, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, replace_enduser_template_values, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities";
78
+ import { MM_DD_YYYY_to_YYYY_MM_DD, capture_is_supported, downloadFile, emit_gtm_event, first_letter_capitalized, form_response_value_to_string, format_stripe_subscription_interval, getLocalTimezone, getPublicFileURL, mm_dd_yyyy, object_is_empty, replace_enduser_template_values, responses_satisfy_conditions, truncate_string, update_local_storage, user_display_name } from "@tellescope/utilities";
79
79
  import { TIMEZONES_USA } from "@tellescope/types-models";
80
80
  import { VALID_STATES, emailValidator, phoneValidator } from "@tellescope/validation";
81
81
  import Slider from '@mui/material/Slider';
@@ -93,6 +93,15 @@ import { Elements, PaymentElement, useStripe, useElements, EmbeddedCheckout, Emb
93
93
  import { loadStripe } from '@stripe/stripe-js';
94
94
  import { CheckCircleOutline, Delete, Edit, ExpandMore } from "@mui/icons-material";
95
95
  import { WYSIWYG } from "./wysiwyg";
96
+ // Debounce hook for search functionality
97
+ var useDebounce = function (value, delay) {
98
+ var _a = useState(value), debouncedValue = _a[0], setDebouncedValue = _a[1];
99
+ useEffect(function () {
100
+ var handler = setTimeout(function () { return setDebouncedValue(value); }, delay);
101
+ return function () { return clearTimeout(handler); };
102
+ }, [value, delay]);
103
+ return debouncedValue;
104
+ };
96
105
  export var LanguageSelect = function (_a) {
97
106
  var value = _a.value, props = __rest(_a, ["value"]);
98
107
  return (_jsxs(Grid, __assign({ container: true, alignItems: "center", justifyContent: "center", wrap: "nowrap", spacing: 1 }, { children: [_jsx(Grid, __assign({ item: true }, { children: _jsx(LanguageIcon, { color: "primary" }) })), _jsx(Grid, __assign({ item: true, style: { width: 150 } }, { children: _jsx(StringSelector, __assign({}, props, { options: ["English", "Español"], size: "small", value: value === 'Spanish' ? 'Español' : value, label: (value === 'Español' || value === 'Spanish') ? 'Idioma'
@@ -808,20 +817,59 @@ export var MultipleChoiceInput = function (_a) {
808
817
  ? __spreadArray(__spreadArray([], (value !== null && value !== void 0 ? value : []).filter(function (v) { return v !== otherString; }), true), [e.target.value], false) : value === null || value === void 0 ? void 0 : value.filter(function (v) { return v !== otherString; }))), field.id);
809
818
  } }) }))] })));
810
819
  };
820
+ // Helper to emit GTM purchase event for Stripe payments (single source of truth)
821
+ var emitStripePurchaseEvent = function (field, cost) {
822
+ var _a;
823
+ emit_gtm_event({
824
+ event: 'form_purchase',
825
+ productIds: ((_a = field.options) === null || _a === void 0 ? void 0 : _a.productIds) || [],
826
+ fieldId: field.id,
827
+ value: cost / 100,
828
+ currency: 'USD',
829
+ });
830
+ };
811
831
  export var StripeInput = function (_a) {
812
- var _b, _d;
813
- var field = _a.field, value = _a.value, onChange = _a.onChange, setCustomerId = _a.setCustomerId, enduserId = _a.enduserId;
832
+ var _b, _d, _e;
833
+ var field = _a.field, value = _a.value, onChange = _a.onChange, setCustomerId = _a.setCustomerId, enduserId = _a.enduserId, form = _a.form, responses = _a.responses, enduser = _a.enduser;
814
834
  var session = useResolvedSession();
815
- var _e = useState(''), clientSecret = _e[0], setClientSecret = _e[1];
816
- var _f = useState(''), businessName = _f[0], setBusinessName = _f[1];
817
- var _g = useState(false), isCheckout = _g[0], setIsCheckout = _g[1];
818
- var _h = useState(), stripePromise = _h[0], setStripePromise = _h[1];
819
- var _j = useState(''), answertext = _j[0], setAnswertext = _j[1];
820
- var _k = useState(''), error = _k[0], setError = _k[1];
821
- var _l = useState([]), selectedProducts = _l[0], setSelectedProducts = _l[1];
822
- var _m = useState(false), showProductSelection = _m[0], setShowProductSelection = _m[1];
823
- var _o = useState([]), availableProducts = _o[0], setAvailableProducts = _o[1];
824
- var _p = useState(false), loadingProducts = _p[0], setLoadingProducts = _p[1];
835
+ var _f = useState(''), clientSecret = _f[0], setClientSecret = _f[1];
836
+ var _g = useState(''), businessName = _g[0], setBusinessName = _g[1];
837
+ var _h = useState(false), isCheckout = _h[0], setIsCheckout = _h[1];
838
+ var _j = useState(), stripePromise = _j[0], setStripePromise = _j[1];
839
+ var _k = useState(''), answertext = _k[0], setAnswertext = _k[1];
840
+ var _l = useState(''), error = _l[0], setError = _l[1];
841
+ var _m = useState([]), selectedProducts = _m[0], setSelectedProducts = _m[1];
842
+ var _o = useState(false), showProductSelection = _o[0], setShowProductSelection = _o[1];
843
+ var _p = useState([]), availableProducts = _p[0], setAvailableProducts = _p[1];
844
+ var _q = useState(false), loadingProducts = _q[0], setLoadingProducts = _q[1];
845
+ // Compute visible products based on conditional logic
846
+ var visibleProducts = useMemo(function () {
847
+ if (!showProductSelection || availableProducts.length === 0) {
848
+ return availableProducts;
849
+ }
850
+ return availableProducts.filter(function (product) {
851
+ var _a, _b;
852
+ // Find condition for this product
853
+ var productCondition = (_b = (_a = field.options) === null || _a === void 0 ? void 0 : _a.productConditions) === null || _b === void 0 ? void 0 : _b.find(function (c) { return c.productId === product._id; });
854
+ // If no condition defined, show by default
855
+ if (!(productCondition === null || productCondition === void 0 ? void 0 : productCondition.showCondition) || object_is_empty(productCondition.showCondition)) {
856
+ return true;
857
+ }
858
+ // Evaluate condition against current form responses
859
+ return responses_satisfy_conditions(responses || [], productCondition.showCondition, {
860
+ dateOfBirth: enduser === null || enduser === void 0 ? void 0 : enduser.dateOfBirth,
861
+ gender: enduser === null || enduser === void 0 ? void 0 : enduser.gender,
862
+ state: enduser === null || enduser === void 0 ? void 0 : enduser.state,
863
+ form: form,
864
+ activeResponses: responses,
865
+ });
866
+ });
867
+ }, [availableProducts, (_b = field.options) === null || _b === void 0 ? void 0 : _b.productConditions, responses, showProductSelection, enduser, form]);
868
+ // Automatically deselect products that become hidden
869
+ useEffect(function () {
870
+ var visibleProductIds = visibleProducts.map(function (p) { return p._id; });
871
+ setSelectedProducts(function (prev) { return prev.filter(function (id) { return visibleProductIds.includes(id); }); });
872
+ }, [visibleProducts]);
825
873
  var fetchRef = useRef(false);
826
874
  useEffect(function () {
827
875
  var _a, _b, _d;
@@ -887,6 +935,16 @@ export var StripeInput = function (_a) {
887
935
  }, 0)
888
936
  : 0 // Will be calculated by existing Stripe flow when not in selection mode
889
937
  );
938
+ // Emit GTM purchase event once when success screen is displayed
939
+ var purchaseEmittedRef = useRef(false);
940
+ useEffect(function () {
941
+ var _a;
942
+ // Only emit for actual purchases (chargeImmediately), not for saving card details
943
+ if (value && ((_a = field.options) === null || _a === void 0 ? void 0 : _a.chargeImmediately) && !purchaseEmittedRef.current) {
944
+ emitStripePurchaseEvent(field, cost);
945
+ purchaseEmittedRef.current = true;
946
+ }
947
+ }, [value, field, cost]);
890
948
  // Handle product selection step
891
949
  if (showProductSelection) {
892
950
  if (error) {
@@ -895,7 +953,11 @@ export var StripeInput = function (_a) {
895
953
  if (loadingProducts) {
896
954
  return (_jsxs(Grid, __assign({ container: true, direction: "column", spacing: 2, alignItems: "center" }, { children: [_jsx(Grid, __assign({ item: true }, { children: _jsx(LinearProgress, {}) })), _jsx(Grid, __assign({ item: true }, { children: _jsx(Typography, { children: "Loading product information..." }) }))] })));
897
955
  }
898
- var isSingleSelection_1 = ((_b = field.options) === null || _b === void 0 ? void 0 : _b.radio) === true;
956
+ // Check if all products are filtered out by conditional logic
957
+ if (visibleProducts.length === 0) {
958
+ return (_jsx(Grid, __assign({ container: true, direction: "column", spacing: 2, alignItems: "center" }, { children: _jsx(Grid, __assign({ item: true }, { children: _jsx(Typography, __assign({ color: "textSecondary" }, { children: "No products are available based on your previous answers." })) })) })));
959
+ }
960
+ var isSingleSelection_1 = ((_d = field.options) === null || _d === void 0 ? void 0 : _d.radio) === true;
899
961
  var handleProductSelection_1 = function (productId) {
900
962
  if (isSingleSelection_1) {
901
963
  setSelectedProducts([productId]);
@@ -932,7 +994,7 @@ export var StripeInput = function (_a) {
932
994
  }
933
995
  });
934
996
  };
935
- return (_jsxs(Grid, __assign({ container: true, direction: "column", spacing: 2 }, { children: [_jsx(Grid, __assign({ item: true }, { children: _jsxs(Typography, __assign({ variant: "h6" }, { children: ["Select Product", isSingleSelection_1 ? '' : 's'] })) })), availableProducts.map(function (product) {
997
+ return (_jsxs(Grid, __assign({ container: true, direction: "column", spacing: 2 }, { children: [_jsx(Grid, __assign({ item: true }, { children: _jsxs(Typography, __assign({ variant: "h6" }, { children: ["Select Product", isSingleSelection_1 ? '' : 's'] })) })), visibleProducts.map(function (product) {
936
998
  var _a, _b, _d;
937
999
  // Use real-time Stripe pricing if available, fallback to Tellescope pricing
938
1000
  var price = product.currentPrice || product.cost;
@@ -945,7 +1007,7 @@ export var StripeInput = function (_a) {
945
1007
  return (_jsx(Typography, __assign({ color: "error" }, { children: error })));
946
1008
  }
947
1009
  if (value) {
948
- return (_jsxs(Grid, __assign({ container: true, alignItems: "center", wrap: "nowrap" }, { children: [_jsx(CheckCircleOutline, { color: "success" }), _jsx(Typography, __assign({ sx: { ml: 1, fontSize: 20 } }, { children: ((_d = field.options) === null || _d === void 0 ? void 0 : _d.chargeImmediately) ? 'Your purchase was successful' : "Your payment details have been saved!" }))] })));
1010
+ return (_jsxs(Grid, __assign({ container: true, alignItems: "center", wrap: "nowrap" }, { children: [_jsx(CheckCircleOutline, { color: "success" }), _jsx(Typography, __assign({ sx: { ml: 1, fontSize: 20 } }, { children: ((_e = field.options) === null || _e === void 0 ? void 0 : _e.chargeImmediately) ? 'Your purchase was successful' : "Your payment details have been saved!" }))] })));
949
1011
  }
950
1012
  if (!(clientSecret && stripePromise))
951
1013
  return _jsx(LinearProgress, {});
@@ -1050,26 +1112,30 @@ export var DropdownInput = function (_a) {
1050
1112
  var choicesForDatabase = {};
1051
1113
  var preventRefetch = {};
1052
1114
  var LOAD_CHOICES_LIMIT = 500;
1115
+ var MIN_SEARCH_CHARS = 3;
1116
+ var SEARCH_DEBOUNCE_MS = 300;
1053
1117
  var useDatabaseChoices = function (_a) {
1054
- var _b, _d, _e, _f;
1055
- var _g = _a.databaseId, databaseId = _g === void 0 ? '' : _g, field = _a.field, otherAnswers = _a.otherAnswers;
1118
+ var _b, _d;
1119
+ var _e = _a.databaseId, databaseId = _e === void 0 ? '' : _e, field = _a.field, otherAnswers = _a.otherAnswers, _f = _a.searchQuery, searchQuery = _f === void 0 ? '' : _f;
1056
1120
  var session = useResolvedSession();
1057
- var _h = useState(0), renderCount = _h[0], setRenderCount = _h[1];
1058
- // todo: make searchable, don't load all
1121
+ var _g = useState(false), isSearching = _g[0], setIsSearching = _g[1];
1122
+ var _h = useState([]), searchResults = _h[0], setSearchResults = _h[1];
1123
+ var _j = useState(false), initialLoadComplete = _j[0], setInitialLoadComplete = _j[1];
1124
+ var debouncedSearch = useDebounce(searchQuery, SEARCH_DEBOUNCE_MS);
1125
+ // Load initial page on mount (only once, not recursively)
1126
+ var initialLoadRef = useRef(false);
1059
1127
  useEffect(function () {
1060
- var _a, _b, _d, _e;
1061
- if ((_a = choicesForDatabase[databaseId]) === null || _a === void 0 ? void 0 : _a.done)
1128
+ var _a, _b, _d;
1129
+ if (initialLoadRef.current)
1062
1130
  return;
1063
- if (renderCount > 100)
1064
- return; // limit to 50000 entries / prevent infinite looping
1065
- var choices = (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) !== null && _d !== void 0 ? _d : [];
1066
- var lastId = (_e = choicesForDatabase[databaseId]) === null || _e === void 0 ? void 0 : _e.lastId;
1067
- if (preventRefetch[databaseId + field.id + lastId])
1131
+ if (((_a = choicesForDatabase[databaseId]) === null || _a === void 0 ? void 0 : _a.done) || ((_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) === null || _d === void 0 ? void 0 : _d.length)) {
1132
+ setInitialLoadComplete(true);
1068
1133
  return;
1069
- preventRefetch[databaseId + field.id + lastId] = true;
1134
+ }
1135
+ initialLoadRef.current = true;
1136
+ preventRefetch[databaseId + field.id] = true;
1070
1137
  session.api.form_fields.load_choices_from_database({
1071
1138
  fieldId: field.id,
1072
- lastId: lastId,
1073
1139
  limit: LOAD_CHOICES_LIMIT,
1074
1140
  databaseId: databaseId,
1075
1141
  })
@@ -1078,17 +1144,67 @@ var useDatabaseChoices = function (_a) {
1078
1144
  var newChoices = _a.choices;
1079
1145
  choicesForDatabase[databaseId] = {
1080
1146
  lastId: (_b = newChoices === null || newChoices === void 0 ? void 0 : newChoices[newChoices.length - 1]) === null || _b === void 0 ? void 0 : _b.id,
1081
- records: __spreadArray(__spreadArray([], choices, true), newChoices, true).sort(function (c1, c2) { return (label_for_database_record(field, c1)
1147
+ records: newChoices.sort(function (c1, c2) { return (label_for_database_record(field, c1)
1082
1148
  .localeCompare(label_for_database_record(field, c2))); }),
1083
- done: newChoices.length < LOAD_CHOICES_LIMIT,
1149
+ done: true, // Don't load more pages automatically
1084
1150
  };
1085
- setRenderCount(function (r) { return r + 1; });
1151
+ setInitialLoadComplete(true);
1152
+ })
1153
+ .catch(function (err) {
1154
+ console.error(err);
1155
+ preventRefetch[databaseId + field.id] = false;
1156
+ setInitialLoadComplete(true); // Mark as complete even on error to avoid infinite loading
1157
+ });
1158
+ }, [session, field, databaseId]);
1159
+ // Handle debounced search
1160
+ var searchRef = useRef(debouncedSearch);
1161
+ useEffect(function () {
1162
+ var trimmed = debouncedSearch.trim();
1163
+ // If search is cleared, return to initial results
1164
+ if (!trimmed) {
1165
+ setSearchResults([]);
1166
+ setIsSearching(false);
1167
+ searchRef.current = debouncedSearch;
1168
+ return;
1169
+ }
1170
+ // Only search if meets minimum character requirement
1171
+ if (trimmed.length < MIN_SEARCH_CHARS) {
1172
+ setSearchResults([]);
1173
+ setIsSearching(false);
1174
+ return;
1175
+ }
1176
+ // Avoid duplicate searches
1177
+ if (searchRef.current === debouncedSearch)
1178
+ return;
1179
+ searchRef.current = debouncedSearch;
1180
+ setIsSearching(true);
1181
+ session.api.form_fields.load_choices_from_database({
1182
+ fieldId: field.id,
1183
+ limit: LOAD_CHOICES_LIMIT,
1184
+ databaseId: databaseId,
1185
+ search: trimmed,
1186
+ })
1187
+ .then(function (_a) {
1188
+ var _b, _d;
1189
+ var newChoices = _a.choices;
1190
+ // Add search results to the same cache as initial load
1191
+ // This ensures selected search results persist even after search is cleared
1192
+ var existingRecords = (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) !== null && _d !== void 0 ? _d : [];
1193
+ var existingIds = new Set(existingRecords.map(function (r) { return r.id; }));
1194
+ var uniqueNewChoices = newChoices.filter(function (c) { return !existingIds.has(c.id); });
1195
+ if (uniqueNewChoices.length > 0) {
1196
+ choicesForDatabase[databaseId] = __assign(__assign({}, choicesForDatabase[databaseId]), { records: __spreadArray(__spreadArray([], existingRecords, true), uniqueNewChoices, true).sort(function (c1, c2) { return (label_for_database_record(field, c1)
1197
+ .localeCompare(label_for_database_record(field, c2))); }), done: true });
1198
+ }
1199
+ setSearchResults(newChoices.sort(function (c1, c2) { return (label_for_database_record(field, c1)
1200
+ .localeCompare(label_for_database_record(field, c2))); }));
1201
+ setIsSearching(false);
1086
1202
  })
1087
1203
  .catch(function (err) {
1088
1204
  console.error(err);
1089
- preventRefetch[databaseId + field.id + lastId] = false;
1205
+ setIsSearching(false);
1090
1206
  });
1091
- }, [session, field, databaseId, renderCount]);
1207
+ }, [session, field, databaseId, debouncedSearch]);
1092
1208
  var addChoice = useCallback(function (record) {
1093
1209
  if (!choicesForDatabase[databaseId]) {
1094
1210
  choicesForDatabase[databaseId] = {
@@ -1098,10 +1214,15 @@ var useDatabaseChoices = function (_a) {
1098
1214
  }
1099
1215
  choicesForDatabase[databaseId].records.push(record);
1100
1216
  }, [choicesForDatabase, databaseId]);
1217
+ // Use search results if searching, otherwise use cached initial results
1218
+ var activeChoices = debouncedSearch.trim().length >= MIN_SEARCH_CHARS
1219
+ ? searchResults
1220
+ : ((_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) !== null && _d !== void 0 ? _d : []);
1101
1221
  return {
1102
1222
  addChoice: addChoice,
1103
- doneLoading: (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.done) !== null && _d !== void 0 ? _d : false,
1104
- choices: __spreadArray(__spreadArray([], (_f = (_e = choicesForDatabase[databaseId]) === null || _e === void 0 ? void 0 : _e.records) !== null && _f !== void 0 ? _f : [], true), (otherAnswers || []).map(function (v) {
1223
+ doneLoading: initialLoadComplete,
1224
+ isSearching: isSearching,
1225
+ choices: __spreadArray(__spreadArray([], activeChoices, true), (otherAnswers || []).map(function (v) {
1105
1226
  var _a;
1106
1227
  return ({
1107
1228
  id: v.text,
@@ -1109,7 +1230,7 @@ var useDatabaseChoices = function (_a) {
1109
1230
  values: [{ label: ((_a = field.options) === null || _a === void 0 ? void 0 : _a.databaseLabel) || '', type: 'Text', value: v.text }],
1110
1231
  });
1111
1232
  }), true),
1112
- renderCount: renderCount,
1233
+ minSearchChars: MIN_SEARCH_CHARS,
1113
1234
  };
1114
1235
  };
1115
1236
  var label_for_database_record = function (field, record) {
@@ -1141,13 +1262,15 @@ var get_other_answers = function (_value, typing) {
1141
1262
  };
1142
1263
  export var DatabaseSelectInput = function (_a) {
1143
1264
  var _b, _d, _e, _f, _g, _h, _j, _k;
1144
- var AddToDatabase = _a.AddToDatabase, field = _a.field, _value = _a.value, onChange = _a.onChange, onDatabaseSelect = _a.onDatabaseSelect, responses = _a.responses, size = _a.size, disabled = _a.disabled, enduser = _a.enduser;
1265
+ var AddToDatabase = _a.AddToDatabase, field = _a.field, _value = _a.value, onChange = _a.onChange, onDatabaseSelect = _a.onDatabaseSelect, responses = _a.responses, size = _a.size, disabled = _a.disabled, enduser = _a.enduser, inputProps = _a.inputProps;
1145
1266
  var _l = useState(''), typing = _l[0], setTyping = _l[1];
1146
- var _m = useDatabaseChoices({
1267
+ var _m = useState(false), open = _m[0], setOpen = _m[1];
1268
+ var _o = useDatabaseChoices({
1147
1269
  databaseId: (_b = field.options) === null || _b === void 0 ? void 0 : _b.databaseId,
1148
1270
  field: field,
1149
1271
  otherAnswers: get_other_answers(_value, ((_d = field === null || field === void 0 ? void 0 : field.options) === null || _d === void 0 ? void 0 : _d.other) ? typing : undefined),
1150
- }), addChoice = _m.addChoice, choices = _m.choices, doneLoading = _m.doneLoading;
1272
+ searchQuery: typing,
1273
+ }), addChoice = _o.addChoice, choices = _o.choices, doneLoading = _o.doneLoading, isSearching = _o.isSearching, minSearchChars = _o.minSearchChars;
1151
1274
  var value = React.useMemo(function () {
1152
1275
  var _a, _b;
1153
1276
  try {
@@ -1251,9 +1374,13 @@ export var DatabaseSelectInput = function (_a) {
1251
1374
  }
1252
1375
  return filtered;
1253
1376
  }, [field, stateFilteredChoices]);
1377
+ // Show placeholder when typing but below minimum search characters
1378
+ var charsNeeded = typing.trim().length > 0 && typing.trim().length < minSearchChars
1379
+ ? minSearchChars - typing.trim().length
1380
+ : 0;
1254
1381
  if (!doneLoading)
1255
1382
  return _jsx(LinearProgress, {});
1256
- return (_jsxs(_Fragment, { children: [_jsx(Autocomplete, { id: field.id, freeSolo: false, size: size, componentsProps: { popper: { sx: { wordBreak: "break-word" } } }, options: filteredChoices, multiple: true, getOptionLabel: function (o) { return (Array.isArray(o) // edge case
1383
+ return (_jsxs(_Fragment, { children: [_jsx(Autocomplete, { id: field.id, freeSolo: false, size: size, componentsProps: { popper: { sx: { wordBreak: "break-word" } } }, options: filteredChoices, multiple: true, loading: isSearching, open: open, onOpen: function () { return setOpen(true); }, onClose: function () { return setOpen(false); }, getOptionLabel: function (o) { return (Array.isArray(o) // edge case
1257
1384
  ? ''
1258
1385
  : label_for_database_record(field, o)); }, value: value, disabled: disabled, onChange: function (_, v) {
1259
1386
  var _a, _b, _d, _e, _f;
@@ -1276,7 +1403,7 @@ export var DatabaseSelectInput = function (_a) {
1276
1403
  recordId: (_f = (_e = v[v.length - 1]) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : '',
1277
1404
  text: label_for_database_record(field, v[v.length - 1]),
1278
1405
  }]), field.id);
1279
- }, inputValue: typing, onInputChange: function (e, v) { return e && setTyping(v); }, renderInput: function (params) { return _jsx(TextField, __assign({}, params, { InputProps: __assign(__assign({}, params.InputProps), { sx: defaultInputProps.sx }) })); },
1406
+ }, inputValue: typing, onInputChange: function (e, v) { return e && setTyping(v); }, renderInput: function (params) { return (_jsx(TextField, __assign({}, params, { InputProps: __assign(__assign({}, params.InputProps), { sx: (inputProps || defaultInputProps).sx, endAdornment: (_jsxs(_Fragment, { children: [isSearching ? _jsx(CircularProgress, { color: "inherit", size: 20 }) : null, params.InputProps.endAdornment] })) }), placeholder: charsNeeded > 0 ? "Type ".concat(charsNeeded, " more character").concat(charsNeeded > 1 ? 's' : '', " to search...") : undefined, helperText: charsNeeded > 0 ? "Type ".concat(charsNeeded, " more character").concat(charsNeeded > 1 ? 's' : '', " to search") : undefined }))); },
1280
1407
  // use custom Chip to ensure very long entries break properly (whitespace: normal)
1281
1408
  renderTags: function (value, getTagProps) {
1282
1409
  return value.map(function (value, index) { return (_jsx(Chip, __assign({ label: _jsx(Typography, __assign({ style: { whiteSpace: 'normal' } }, { children: Array.isArray(value) ? '' : label_for_database_record(field, value) })) }, getTagProps({ index: index }), { sx: { height: "100%", py: 0.5 } }))); });
@@ -1652,7 +1779,7 @@ export var contact_is_valid = function (e) {
1652
1779
  };
1653
1780
  export var RelatedContactsInput = function (_a) {
1654
1781
  var _b, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _w, _x;
1655
- var field = _a.field, _value = _a.value, onChange = _a.onChange, props = __rest(_a, ["field", "value", "onChange"]);
1782
+ var field = _a.field, _value = _a.value, onChange = _a.onChange, parentError = _a.error, props = __rest(_a, ["field", "value", "onChange", "error"]);
1656
1783
  // safeguard against any rogue values like empty string
1657
1784
  var value = Array.isArray(_value) ? _value : [];
1658
1785
  var _y = useState(value.length === 1 ? 0 : -1), editing = _y[0], setEditing = _y[1];
@@ -1671,7 +1798,7 @@ export var RelatedContactsInput = function (_a) {
1671
1798
  _jsx(Grid, __assign({ item: true, xs: 4 }, { children: _jsx(TextField, { label: "Last Name", size: "small", fullWidth: true, InputProps: defaultInputProps, value: lname, onChange: function (e) { return onChange(value.map(function (v, i) { return i === editing ? __assign(__assign({}, v), { lname: e.target.value }) : v; }), field.id); } }) })), _jsx(Grid, __assign({ item: true, xs: 4 }, { children: _jsx(StringSelector, { options: ((_j = (_h = field.options) === null || _h === void 0 ? void 0 : _h.relatedContactTypes) === null || _j === void 0 ? void 0 : _j.length) ? field.options.relatedContactTypes : RELATIONSHIP_TYPES, label: "Relationship", size: "small", disabled: ((_l = (_k = field === null || field === void 0 ? void 0 : field.options) === null || _k === void 0 ? void 0 : _k.relatedContactTypes) === null || _l === void 0 ? void 0 : _l.length) === 1, value: (_o = (_m = relationships === null || relationships === void 0 ? void 0 : relationships[0]) === null || _m === void 0 ? void 0 : _m.type) !== null && _o !== void 0 ? _o : '', onChange: function (type) { return onChange(value.map(function (v, i) { return i === editing ? __assign(__assign({}, v), { relationships: [{ type: type, id: '' /* to be filled on server-side */ }] }) : v; }), field.id); } }) }))] })) })), _jsx(Grid, __assign({ item: true }, { children: _jsxs(Grid, __assign({ container: true, alignItems: "center", wrap: "nowrap", spacing: 1 }, { children: [!((_q = (_p = field.options) === null || _p === void 0 ? void 0 : _p.hiddenDefaultFields) === null || _q === void 0 ? void 0 : _q.includes('Date of Birth')) &&
1672
1799
  _jsx(Grid, __assign({ item: true, xs: 4 }, { children: _jsx(DateStringInput, { value: dateOfBirth, field: __assign(__assign({}, field), { isOptional: true }), size: "small", label: "Date of Birth (MM-DD-YYYY)", onChange: function (dateOfBirth) { return onChange(value.map(function (v, i) { return i === editing ? __assign(__assign({}, v), { dateOfBirth: dateOfBirth }) : v; }), field.id); } }) })), !((_s = (_r = field.options) === null || _r === void 0 ? void 0 : _r.hiddenDefaultFields) === null || _s === void 0 ? void 0 : _s.includes('Email')) &&
1673
1800
  _jsx(Grid, __assign({ item: true, xs: 4 }, { children: _jsx(TextField, { label: "Email", size: "small", fullWidth: true, type: "email", InputProps: defaultInputProps, value: email, onChange: function (e) { return onChange(value.map(function (v, i) { return i === editing ? __assign(__assign({}, v), { email: e.target.value }) : v; }), field.id); } }) })), !((_u = (_t = field.options) === null || _t === void 0 ? void 0 : _t.hiddenDefaultFields) === null || _u === void 0 ? void 0 : _u.includes('Phone Number')) &&
1674
- _jsx(Grid, __assign({ item: true, xs: 4 }, { children: _jsx(TextField, { label: "Phone Number", size: "small", fullWidth: true, InputProps: defaultInputProps, value: phone, onChange: function (e) { return onChange(value.map(function (v, i) { return i === editing ? __assign(__assign({}, v), { phone: e.target.value }) : v; }), field.id); } }) }))] })) })), (((_w = field.options) === null || _w === void 0 ? void 0 : _w.tableChoices) || []).length > 0 &&
1801
+ _jsx(Grid, __assign({ item: true, xs: 4 }, { children: _jsx(TextField, { label: "Phone Number", size: "small", fullWidth: true, InputProps: defaultInputProps, value: phone, onChange: function (e) { return onChange(value.map(function (v, i) { return i === editing ? __assign(__assign({}, v), { phone: e.target.value.trim() }) : v; }), field.id); } }) }))] })) })), (((_w = field.options) === null || _w === void 0 ? void 0 : _w.tableChoices) || []).length > 0 &&
1675
1802
  _jsx(Grid, __assign({ item: true }, { children: _jsx(Grid, __assign({ container: true, spacing: 1 }, { children: (((_x = field.options) === null || _x === void 0 ? void 0 : _x.tableChoices) || []).map(function (_a, i) {
1676
1803
  var info = _a.info, label = _a.label, type = _a.type;
1677
1804
  return (_jsx(Grid, __assign({ item: true, xs: 6 }, { children: type === 'Text'
@@ -1691,7 +1818,7 @@ export var RelatedContactsInput = function (_a) {
1691
1818
  return i === editing ? __assign(__assign({}, v), { fields: __assign(__assign({}, fields_1), (_a = {}, _a[label] = e.target.value, _a)) }) : v;
1692
1819
  }), field.id); } }, { children: [_jsx(MenuItem, __assign({ value: "" }, { children: _jsx("em", { children: "None" }) })), info.choices.map(function (c) { return (_jsx(MenuItem, __assign({ value: c }, { children: c }), c)); })] }))] })))
1693
1820
  : null }), i));
1694
- }) })) })), _jsx(Grid, __assign({ item: true, sx: { my: 0.75 } }, { children: _jsx(Button, __assign({ variant: "outlined", onClick: function () { return setEditing(-1); }, size: "small" }, { children: "Save Contact" })) })), errorMessage &&
1821
+ }) })) })), _jsx(Grid, __assign({ item: true, sx: { my: 0.75 } }, { children: _jsx(Button, __assign({ variant: "outlined", onClick: function () { return setEditing(-1); }, size: "small", disabled: !!errorMessage || !!parentError }, { children: "Save Contact" })) })), errorMessage &&
1695
1822
  _jsx(Grid, __assign({ item: true }, { children: _jsx(Typography, __assign({ color: "error" }, { children: errorMessage })) }))] })));
1696
1823
  }
1697
1824
  return (_jsxs(Grid, __assign({ container: true, direction: "column", spacing: 1 }, { children: [_jsx(Grid, __assign({ item: true }, { children: value.map(function (contact, i) { return (_jsx(Grid, __assign({ item: true }, { children: _jsxs(Grid, __assign({ container: true, alignItems: "center", justifyContent: "space-between", wrap: "nowrap", spacing: 1 }, { children: [_jsx(Grid, __assign({ item: true }, { children: _jsxs(Grid, __assign({ container: true, alignItems: "center" }, { children: [_jsx(IconButton, __assign({ onClick: function () { return setEditing(i); }, color: "primary", size: "small" }, { children: _jsx(Edit, {}) })), _jsx(Typography, __assign({ noWrap: true }, { children: user_display_name(contact) || "Unnamed Contact ".concat(i + 1) }))] })) })), _jsx(Grid, __assign({ item: true }, { children: _jsx(LabeledIconButton, { Icon: Delete, label: "Remove", onClick: function () { return onChange(value.filter(function (v, _i) { return i !== _i; }), field.id); } }) }))] })) }), i)); }) })), _jsx(Grid, __assign({ item: true }, { children: _jsx(Button, __assign({ variant: "contained", onClick: handleAddContact }, { children: "Add Contact" })) }))] })));