@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
@@ -122,6 +122,15 @@ var react_stripe_js_1 = require("@stripe/react-stripe-js");
122
122
  var stripe_js_1 = require("@stripe/stripe-js");
123
123
  var icons_material_1 = require("@mui/icons-material");
124
124
  var wysiwyg_1 = require("./wysiwyg");
125
+ // Debounce hook for search functionality
126
+ var useDebounce = function (value, delay) {
127
+ var _a = (0, react_1.useState)(value), debouncedValue = _a[0], setDebouncedValue = _a[1];
128
+ (0, react_1.useEffect)(function () {
129
+ var handler = setTimeout(function () { return setDebouncedValue(value); }, delay);
130
+ return function () { return clearTimeout(handler); };
131
+ }, [value, delay]);
132
+ return debouncedValue;
133
+ };
125
134
  var LanguageSelect = function (_a) {
126
135
  var value = _a.value, props = __rest(_a, ["value"]);
127
136
  return ((0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, alignItems: "center", justifyContent: "center", wrap: "nowrap", spacing: 1 }, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(Language_1.default, { color: "primary" }) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, style: { width: 150 } }, { children: (0, jsx_runtime_1.jsx)(StringSelector, __assign({}, props, { options: ["English", "Español"], size: "small", value: value === 'Spanish' ? 'Español' : value, label: (value === 'Español' || value === 'Spanish') ? 'Idioma'
@@ -861,20 +870,59 @@ var MultipleChoiceInput = function (_a) {
861
870
  } }) }))] })));
862
871
  };
863
872
  exports.MultipleChoiceInput = MultipleChoiceInput;
873
+ // Helper to emit GTM purchase event for Stripe payments (single source of truth)
874
+ var emitStripePurchaseEvent = function (field, cost) {
875
+ var _a;
876
+ (0, utilities_1.emit_gtm_event)({
877
+ event: 'form_purchase',
878
+ productIds: ((_a = field.options) === null || _a === void 0 ? void 0 : _a.productIds) || [],
879
+ fieldId: field.id,
880
+ value: cost / 100,
881
+ currency: 'USD',
882
+ });
883
+ };
864
884
  var StripeInput = function (_a) {
865
- var _b, _d;
866
- var field = _a.field, value = _a.value, onChange = _a.onChange, setCustomerId = _a.setCustomerId, enduserId = _a.enduserId;
885
+ var _b, _d, _e;
886
+ 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;
867
887
  var session = (0, __1.useResolvedSession)();
868
- var _e = (0, react_1.useState)(''), clientSecret = _e[0], setClientSecret = _e[1];
869
- var _f = (0, react_1.useState)(''), businessName = _f[0], setBusinessName = _f[1];
870
- var _g = (0, react_1.useState)(false), isCheckout = _g[0], setIsCheckout = _g[1];
871
- var _h = (0, react_1.useState)(), stripePromise = _h[0], setStripePromise = _h[1];
872
- var _j = (0, react_1.useState)(''), answertext = _j[0], setAnswertext = _j[1];
873
- var _k = (0, react_1.useState)(''), error = _k[0], setError = _k[1];
874
- var _l = (0, react_1.useState)([]), selectedProducts = _l[0], setSelectedProducts = _l[1];
875
- var _m = (0, react_1.useState)(false), showProductSelection = _m[0], setShowProductSelection = _m[1];
876
- var _o = (0, react_1.useState)([]), availableProducts = _o[0], setAvailableProducts = _o[1];
877
- var _p = (0, react_1.useState)(false), loadingProducts = _p[0], setLoadingProducts = _p[1];
888
+ var _f = (0, react_1.useState)(''), clientSecret = _f[0], setClientSecret = _f[1];
889
+ var _g = (0, react_1.useState)(''), businessName = _g[0], setBusinessName = _g[1];
890
+ var _h = (0, react_1.useState)(false), isCheckout = _h[0], setIsCheckout = _h[1];
891
+ var _j = (0, react_1.useState)(), stripePromise = _j[0], setStripePromise = _j[1];
892
+ var _k = (0, react_1.useState)(''), answertext = _k[0], setAnswertext = _k[1];
893
+ var _l = (0, react_1.useState)(''), error = _l[0], setError = _l[1];
894
+ var _m = (0, react_1.useState)([]), selectedProducts = _m[0], setSelectedProducts = _m[1];
895
+ var _o = (0, react_1.useState)(false), showProductSelection = _o[0], setShowProductSelection = _o[1];
896
+ var _p = (0, react_1.useState)([]), availableProducts = _p[0], setAvailableProducts = _p[1];
897
+ var _q = (0, react_1.useState)(false), loadingProducts = _q[0], setLoadingProducts = _q[1];
898
+ // Compute visible products based on conditional logic
899
+ var visibleProducts = (0, react_1.useMemo)(function () {
900
+ if (!showProductSelection || availableProducts.length === 0) {
901
+ return availableProducts;
902
+ }
903
+ return availableProducts.filter(function (product) {
904
+ var _a, _b;
905
+ // Find condition for this product
906
+ 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; });
907
+ // If no condition defined, show by default
908
+ if (!(productCondition === null || productCondition === void 0 ? void 0 : productCondition.showCondition) || (0, utilities_1.object_is_empty)(productCondition.showCondition)) {
909
+ return true;
910
+ }
911
+ // Evaluate condition against current form responses
912
+ return (0, utilities_1.responses_satisfy_conditions)(responses || [], productCondition.showCondition, {
913
+ dateOfBirth: enduser === null || enduser === void 0 ? void 0 : enduser.dateOfBirth,
914
+ gender: enduser === null || enduser === void 0 ? void 0 : enduser.gender,
915
+ state: enduser === null || enduser === void 0 ? void 0 : enduser.state,
916
+ form: form,
917
+ activeResponses: responses,
918
+ });
919
+ });
920
+ }, [availableProducts, (_b = field.options) === null || _b === void 0 ? void 0 : _b.productConditions, responses, showProductSelection, enduser, form]);
921
+ // Automatically deselect products that become hidden
922
+ (0, react_1.useEffect)(function () {
923
+ var visibleProductIds = visibleProducts.map(function (p) { return p._id; });
924
+ setSelectedProducts(function (prev) { return prev.filter(function (id) { return visibleProductIds.includes(id); }); });
925
+ }, [visibleProducts]);
878
926
  var fetchRef = (0, react_1.useRef)(false);
879
927
  (0, react_1.useEffect)(function () {
880
928
  var _a, _b, _d;
@@ -940,6 +988,16 @@ var StripeInput = function (_a) {
940
988
  }, 0)
941
989
  : 0 // Will be calculated by existing Stripe flow when not in selection mode
942
990
  );
991
+ // Emit GTM purchase event once when success screen is displayed
992
+ var purchaseEmittedRef = (0, react_1.useRef)(false);
993
+ (0, react_1.useEffect)(function () {
994
+ var _a;
995
+ // Only emit for actual purchases (chargeImmediately), not for saving card details
996
+ if (value && ((_a = field.options) === null || _a === void 0 ? void 0 : _a.chargeImmediately) && !purchaseEmittedRef.current) {
997
+ emitStripePurchaseEvent(field, cost);
998
+ purchaseEmittedRef.current = true;
999
+ }
1000
+ }, [value, field, cost]);
943
1001
  // Handle product selection step
944
1002
  if (showProductSelection) {
945
1003
  if (error) {
@@ -948,7 +1006,11 @@ var StripeInput = function (_a) {
948
1006
  if (loadingProducts) {
949
1007
  return ((0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, direction: "column", spacing: 2, alignItems: "center" }, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(LinearProgress_1.default, {}) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, { children: "Loading product information..." }) }))] })));
950
1008
  }
951
- var isSingleSelection_1 = ((_b = field.options) === null || _b === void 0 ? void 0 : _b.radio) === true;
1009
+ // Check if all products are filtered out by conditional logic
1010
+ if (visibleProducts.length === 0) {
1011
+ return ((0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ container: true, direction: "column", spacing: 2, alignItems: "center" }, { children: (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, __assign({ color: "textSecondary" }, { children: "No products are available based on your previous answers." })) })) })));
1012
+ }
1013
+ var isSingleSelection_1 = ((_d = field.options) === null || _d === void 0 ? void 0 : _d.radio) === true;
952
1014
  var handleProductSelection_1 = function (productId) {
953
1015
  if (isSingleSelection_1) {
954
1016
  setSelectedProducts([productId]);
@@ -985,7 +1047,7 @@ var StripeInput = function (_a) {
985
1047
  }
986
1048
  });
987
1049
  };
988
- return ((0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, direction: "column", spacing: 2 }, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsxs)(material_1.Typography, __assign({ variant: "h6" }, { children: ["Select Product", isSingleSelection_1 ? '' : 's'] })) })), availableProducts.map(function (product) {
1050
+ return ((0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, direction: "column", spacing: 2 }, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsxs)(material_1.Typography, __assign({ variant: "h6" }, { children: ["Select Product", isSingleSelection_1 ? '' : 's'] })) })), visibleProducts.map(function (product) {
989
1051
  var _a, _b, _d;
990
1052
  // Use real-time Stripe pricing if available, fallback to Tellescope pricing
991
1053
  var price = product.currentPrice || product.cost;
@@ -998,7 +1060,7 @@ var StripeInput = function (_a) {
998
1060
  return ((0, jsx_runtime_1.jsx)(material_1.Typography, __assign({ color: "error" }, { children: error })));
999
1061
  }
1000
1062
  if (value) {
1001
- return ((0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, alignItems: "center", wrap: "nowrap" }, { children: [(0, jsx_runtime_1.jsx)(icons_material_1.CheckCircleOutline, { color: "success" }), (0, jsx_runtime_1.jsx)(material_1.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!" }))] })));
1063
+ return ((0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, alignItems: "center", wrap: "nowrap" }, { children: [(0, jsx_runtime_1.jsx)(icons_material_1.CheckCircleOutline, { color: "success" }), (0, jsx_runtime_1.jsx)(material_1.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!" }))] })));
1002
1064
  }
1003
1065
  if (!(clientSecret && stripePromise))
1004
1066
  return (0, jsx_runtime_1.jsx)(LinearProgress_1.default, {});
@@ -1106,26 +1168,30 @@ exports.DropdownInput = DropdownInput;
1106
1168
  var choicesForDatabase = {};
1107
1169
  var preventRefetch = {};
1108
1170
  var LOAD_CHOICES_LIMIT = 500;
1171
+ var MIN_SEARCH_CHARS = 3;
1172
+ var SEARCH_DEBOUNCE_MS = 300;
1109
1173
  var useDatabaseChoices = function (_a) {
1110
- var _b, _d, _e, _f;
1111
- var _g = _a.databaseId, databaseId = _g === void 0 ? '' : _g, field = _a.field, otherAnswers = _a.otherAnswers;
1174
+ var _b, _d;
1175
+ var _e = _a.databaseId, databaseId = _e === void 0 ? '' : _e, field = _a.field, otherAnswers = _a.otherAnswers, _f = _a.searchQuery, searchQuery = _f === void 0 ? '' : _f;
1112
1176
  var session = (0, __1.useResolvedSession)();
1113
- var _h = (0, react_1.useState)(0), renderCount = _h[0], setRenderCount = _h[1];
1114
- // todo: make searchable, don't load all
1177
+ var _g = (0, react_1.useState)(false), isSearching = _g[0], setIsSearching = _g[1];
1178
+ var _h = (0, react_1.useState)([]), searchResults = _h[0], setSearchResults = _h[1];
1179
+ var _j = (0, react_1.useState)(false), initialLoadComplete = _j[0], setInitialLoadComplete = _j[1];
1180
+ var debouncedSearch = useDebounce(searchQuery, SEARCH_DEBOUNCE_MS);
1181
+ // Load initial page on mount (only once, not recursively)
1182
+ var initialLoadRef = (0, react_1.useRef)(false);
1115
1183
  (0, react_1.useEffect)(function () {
1116
- var _a, _b, _d, _e;
1117
- if ((_a = choicesForDatabase[databaseId]) === null || _a === void 0 ? void 0 : _a.done)
1184
+ var _a, _b, _d;
1185
+ if (initialLoadRef.current)
1118
1186
  return;
1119
- if (renderCount > 100)
1120
- return; // limit to 50000 entries / prevent infinite looping
1121
- var choices = (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) !== null && _d !== void 0 ? _d : [];
1122
- var lastId = (_e = choicesForDatabase[databaseId]) === null || _e === void 0 ? void 0 : _e.lastId;
1123
- if (preventRefetch[databaseId + field.id + lastId])
1187
+ 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)) {
1188
+ setInitialLoadComplete(true);
1124
1189
  return;
1125
- preventRefetch[databaseId + field.id + lastId] = true;
1190
+ }
1191
+ initialLoadRef.current = true;
1192
+ preventRefetch[databaseId + field.id] = true;
1126
1193
  session.api.form_fields.load_choices_from_database({
1127
1194
  fieldId: field.id,
1128
- lastId: lastId,
1129
1195
  limit: LOAD_CHOICES_LIMIT,
1130
1196
  databaseId: databaseId,
1131
1197
  })
@@ -1134,17 +1200,67 @@ var useDatabaseChoices = function (_a) {
1134
1200
  var newChoices = _a.choices;
1135
1201
  choicesForDatabase[databaseId] = {
1136
1202
  lastId: (_b = newChoices === null || newChoices === void 0 ? void 0 : newChoices[newChoices.length - 1]) === null || _b === void 0 ? void 0 : _b.id,
1137
- records: __spreadArray(__spreadArray([], choices, true), newChoices, true).sort(function (c1, c2) { return (label_for_database_record(field, c1)
1203
+ records: newChoices.sort(function (c1, c2) { return (label_for_database_record(field, c1)
1138
1204
  .localeCompare(label_for_database_record(field, c2))); }),
1139
- done: newChoices.length < LOAD_CHOICES_LIMIT,
1205
+ done: true, // Don't load more pages automatically
1140
1206
  };
1141
- setRenderCount(function (r) { return r + 1; });
1207
+ setInitialLoadComplete(true);
1208
+ })
1209
+ .catch(function (err) {
1210
+ console.error(err);
1211
+ preventRefetch[databaseId + field.id] = false;
1212
+ setInitialLoadComplete(true); // Mark as complete even on error to avoid infinite loading
1213
+ });
1214
+ }, [session, field, databaseId]);
1215
+ // Handle debounced search
1216
+ var searchRef = (0, react_1.useRef)(debouncedSearch);
1217
+ (0, react_1.useEffect)(function () {
1218
+ var trimmed = debouncedSearch.trim();
1219
+ // If search is cleared, return to initial results
1220
+ if (!trimmed) {
1221
+ setSearchResults([]);
1222
+ setIsSearching(false);
1223
+ searchRef.current = debouncedSearch;
1224
+ return;
1225
+ }
1226
+ // Only search if meets minimum character requirement
1227
+ if (trimmed.length < MIN_SEARCH_CHARS) {
1228
+ setSearchResults([]);
1229
+ setIsSearching(false);
1230
+ return;
1231
+ }
1232
+ // Avoid duplicate searches
1233
+ if (searchRef.current === debouncedSearch)
1234
+ return;
1235
+ searchRef.current = debouncedSearch;
1236
+ setIsSearching(true);
1237
+ session.api.form_fields.load_choices_from_database({
1238
+ fieldId: field.id,
1239
+ limit: LOAD_CHOICES_LIMIT,
1240
+ databaseId: databaseId,
1241
+ search: trimmed,
1242
+ })
1243
+ .then(function (_a) {
1244
+ var _b, _d;
1245
+ var newChoices = _a.choices;
1246
+ // Add search results to the same cache as initial load
1247
+ // This ensures selected search results persist even after search is cleared
1248
+ var existingRecords = (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) !== null && _d !== void 0 ? _d : [];
1249
+ var existingIds = new Set(existingRecords.map(function (r) { return r.id; }));
1250
+ var uniqueNewChoices = newChoices.filter(function (c) { return !existingIds.has(c.id); });
1251
+ if (uniqueNewChoices.length > 0) {
1252
+ choicesForDatabase[databaseId] = __assign(__assign({}, choicesForDatabase[databaseId]), { records: __spreadArray(__spreadArray([], existingRecords, true), uniqueNewChoices, true).sort(function (c1, c2) { return (label_for_database_record(field, c1)
1253
+ .localeCompare(label_for_database_record(field, c2))); }), done: true });
1254
+ }
1255
+ setSearchResults(newChoices.sort(function (c1, c2) { return (label_for_database_record(field, c1)
1256
+ .localeCompare(label_for_database_record(field, c2))); }));
1257
+ setIsSearching(false);
1142
1258
  })
1143
1259
  .catch(function (err) {
1144
1260
  console.error(err);
1145
- preventRefetch[databaseId + field.id + lastId] = false;
1261
+ setIsSearching(false);
1146
1262
  });
1147
- }, [session, field, databaseId, renderCount]);
1263
+ }, [session, field, databaseId, debouncedSearch]);
1148
1264
  var addChoice = (0, react_1.useCallback)(function (record) {
1149
1265
  if (!choicesForDatabase[databaseId]) {
1150
1266
  choicesForDatabase[databaseId] = {
@@ -1154,10 +1270,15 @@ var useDatabaseChoices = function (_a) {
1154
1270
  }
1155
1271
  choicesForDatabase[databaseId].records.push(record);
1156
1272
  }, [choicesForDatabase, databaseId]);
1273
+ // Use search results if searching, otherwise use cached initial results
1274
+ var activeChoices = debouncedSearch.trim().length >= MIN_SEARCH_CHARS
1275
+ ? searchResults
1276
+ : ((_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) !== null && _d !== void 0 ? _d : []);
1157
1277
  return {
1158
1278
  addChoice: addChoice,
1159
- doneLoading: (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.done) !== null && _d !== void 0 ? _d : false,
1160
- 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) {
1279
+ doneLoading: initialLoadComplete,
1280
+ isSearching: isSearching,
1281
+ choices: __spreadArray(__spreadArray([], activeChoices, true), (otherAnswers || []).map(function (v) {
1161
1282
  var _a;
1162
1283
  return ({
1163
1284
  id: v.text,
@@ -1165,7 +1286,7 @@ var useDatabaseChoices = function (_a) {
1165
1286
  values: [{ label: ((_a = field.options) === null || _a === void 0 ? void 0 : _a.databaseLabel) || '', type: 'Text', value: v.text }],
1166
1287
  });
1167
1288
  }), true),
1168
- renderCount: renderCount,
1289
+ minSearchChars: MIN_SEARCH_CHARS,
1169
1290
  };
1170
1291
  };
1171
1292
  var label_for_database_record = function (field, record) {
@@ -1197,13 +1318,15 @@ var get_other_answers = function (_value, typing) {
1197
1318
  };
1198
1319
  var DatabaseSelectInput = function (_a) {
1199
1320
  var _b, _d, _e, _f, _g, _h, _j, _k;
1200
- 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;
1321
+ 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;
1201
1322
  var _l = (0, react_1.useState)(''), typing = _l[0], setTyping = _l[1];
1202
- var _m = useDatabaseChoices({
1323
+ var _m = (0, react_1.useState)(false), open = _m[0], setOpen = _m[1];
1324
+ var _o = useDatabaseChoices({
1203
1325
  databaseId: (_b = field.options) === null || _b === void 0 ? void 0 : _b.databaseId,
1204
1326
  field: field,
1205
1327
  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),
1206
- }), addChoice = _m.addChoice, choices = _m.choices, doneLoading = _m.doneLoading;
1328
+ searchQuery: typing,
1329
+ }), addChoice = _o.addChoice, choices = _o.choices, doneLoading = _o.doneLoading, isSearching = _o.isSearching, minSearchChars = _o.minSearchChars;
1207
1330
  var value = react_1.default.useMemo(function () {
1208
1331
  var _a, _b;
1209
1332
  try {
@@ -1307,9 +1430,13 @@ var DatabaseSelectInput = function (_a) {
1307
1430
  }
1308
1431
  return filtered;
1309
1432
  }, [field, stateFilteredChoices]);
1433
+ // Show placeholder when typing but below minimum search characters
1434
+ var charsNeeded = typing.trim().length > 0 && typing.trim().length < minSearchChars
1435
+ ? minSearchChars - typing.trim().length
1436
+ : 0;
1310
1437
  if (!doneLoading)
1311
1438
  return (0, jsx_runtime_1.jsx)(LinearProgress_1.default, {});
1312
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(material_1.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
1439
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(material_1.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
1313
1440
  ? ''
1314
1441
  : label_for_database_record(field, o)); }, value: value, disabled: disabled, onChange: function (_, v) {
1315
1442
  var _a, _b, _d, _e, _f;
@@ -1332,7 +1459,7 @@ var DatabaseSelectInput = function (_a) {
1332
1459
  recordId: (_f = (_e = v[v.length - 1]) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : '',
1333
1460
  text: label_for_database_record(field, v[v.length - 1]),
1334
1461
  }]), field.id);
1335
- }, inputValue: typing, onInputChange: function (e, v) { return e && setTyping(v); }, renderInput: function (params) { return (0, jsx_runtime_1.jsx)(material_1.TextField, __assign({}, params, { InputProps: __assign(__assign({}, params.InputProps), { sx: exports.defaultInputProps.sx }) })); },
1462
+ }, inputValue: typing, onInputChange: function (e, v) { return e && setTyping(v); }, renderInput: function (params) { return ((0, jsx_runtime_1.jsx)(material_1.TextField, __assign({}, params, { InputProps: __assign(__assign({}, params.InputProps), { sx: (inputProps || exports.defaultInputProps).sx, endAdornment: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [isSearching ? (0, jsx_runtime_1.jsx)(material_1.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 }))); },
1336
1463
  // use custom Chip to ensure very long entries break properly (whitespace: normal)
1337
1464
  renderTags: function (value, getTagProps) {
1338
1465
  return value.map(function (value, index) { return ((0, jsx_runtime_1.jsx)(material_1.Chip, __assign({ label: (0, jsx_runtime_1.jsx)(material_1.Typography, __assign({ style: { whiteSpace: 'normal' } }, { children: Array.isArray(value) ? '' : label_for_database_record(field, value) })) }, getTagProps({ index: index }), { sx: { height: "100%", py: 0.5 } }))); });
@@ -1713,7 +1840,7 @@ var contact_is_valid = function (e) {
1713
1840
  exports.contact_is_valid = contact_is_valid;
1714
1841
  var RelatedContactsInput = function (_a) {
1715
1842
  var _b, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _w, _x;
1716
- var field = _a.field, _value = _a.value, onChange = _a.onChange, props = __rest(_a, ["field", "value", "onChange"]);
1843
+ var field = _a.field, _value = _a.value, onChange = _a.onChange, parentError = _a.error, props = __rest(_a, ["field", "value", "onChange", "error"]);
1717
1844
  // safeguard against any rogue values like empty string
1718
1845
  var value = Array.isArray(_value) ? _value : [];
1719
1846
  var _y = (0, react_1.useState)(value.length === 1 ? 0 : -1), editing = _y[0], setEditing = _y[1];
@@ -1732,7 +1859,7 @@ var RelatedContactsInput = function (_a) {
1732
1859
  (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, xs: 4 }, { children: (0, jsx_runtime_1.jsx)(material_1.TextField, { label: "Last Name", size: "small", fullWidth: true, InputProps: exports.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); } }) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, xs: 4 }, { children: (0, jsx_runtime_1.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 : constants_1.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); } }) }))] })) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsxs)(material_1.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')) &&
1733
1860
  (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, xs: 4 }, { children: (0, jsx_runtime_1.jsx)(exports.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')) &&
1734
1861
  (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, xs: 4 }, { children: (0, jsx_runtime_1.jsx)(material_1.TextField, { label: "Email", size: "small", fullWidth: true, type: "email", InputProps: exports.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')) &&
1735
- (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, xs: 4 }, { children: (0, jsx_runtime_1.jsx)(material_1.TextField, { label: "Phone Number", size: "small", fullWidth: true, InputProps: exports.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 &&
1862
+ (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, xs: 4 }, { children: (0, jsx_runtime_1.jsx)(material_1.TextField, { label: "Phone Number", size: "small", fullWidth: true, InputProps: exports.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 &&
1736
1863
  (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ container: true, spacing: 1 }, { children: (((_x = field.options) === null || _x === void 0 ? void 0 : _x.tableChoices) || []).map(function (_a, i) {
1737
1864
  var info = _a.info, label = _a.label, type = _a.type;
1738
1865
  return ((0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, xs: 6 }, { children: type === 'Text'
@@ -1752,7 +1879,7 @@ var RelatedContactsInput = function (_a) {
1752
1879
  return i === editing ? __assign(__assign({}, v), { fields: __assign(__assign({}, fields_1), (_a = {}, _a[label] = e.target.value, _a)) }) : v;
1753
1880
  }), field.id); } }, { children: [(0, jsx_runtime_1.jsx)(material_1.MenuItem, __assign({ value: "" }, { children: (0, jsx_runtime_1.jsx)("em", { children: "None" }) })), info.choices.map(function (c) { return ((0, jsx_runtime_1.jsx)(material_1.MenuItem, __assign({ value: c }, { children: c }), c)); })] }))] })))
1754
1881
  : null }), i));
1755
- }) })) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, sx: { my: 0.75 } }, { children: (0, jsx_runtime_1.jsx)(material_1.Button, __assign({ variant: "outlined", onClick: function () { return setEditing(-1); }, size: "small" }, { children: "Save Contact" })) })), errorMessage &&
1882
+ }) })) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true, sx: { my: 0.75 } }, { children: (0, jsx_runtime_1.jsx)(material_1.Button, __assign({ variant: "outlined", onClick: function () { return setEditing(-1); }, size: "small", disabled: !!errorMessage || !!parentError }, { children: "Save Contact" })) })), errorMessage &&
1756
1883
  (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, __assign({ color: "error" }, { children: errorMessage })) }))] })));
1757
1884
  }
1758
1885
  return ((0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, direction: "column", spacing: 1 }, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: value.map(function (contact, i) { return ((0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, alignItems: "center", justifyContent: "space-between", wrap: "nowrap", spacing: 1 }, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsxs)(material_1.Grid, __assign({ container: true, alignItems: "center" }, { children: [(0, jsx_runtime_1.jsx)(__1.IconButton, __assign({ onClick: function () { return setEditing(i); }, color: "primary", size: "small" }, { children: (0, jsx_runtime_1.jsx)(icons_material_1.Edit, {}) })), (0, jsx_runtime_1.jsx)(material_1.Typography, __assign({ noWrap: true }, { children: (0, utilities_1.user_display_name)(contact) || "Unnamed Contact ".concat(i + 1) }))] })) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(__1.LabeledIconButton, { Icon: icons_material_1.Delete, label: "Remove", onClick: function () { return onChange(value.filter(function (v, _i) { return i !== _i; }), field.id); } }) }))] })) }), i)); }) })), (0, jsx_runtime_1.jsx)(material_1.Grid, __assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(material_1.Button, __assign({ variant: "contained", onClick: handleAddContact }, { children: "Add Contact" })) }))] })));