@tellescope/react-components 1.232.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 (40) hide show
  1. package/lib/cjs/Forms/forms.v2.d.ts +1 -1
  2. package/lib/cjs/Forms/hooks.d.ts.map +1 -1
  3. package/lib/cjs/Forms/hooks.js +24 -0
  4. package/lib/cjs/Forms/hooks.js.map +1 -1
  5. package/lib/cjs/Forms/inputs.d.ts +4 -1
  6. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  7. package/lib/cjs/Forms/inputs.js +100 -26
  8. package/lib/cjs/Forms/inputs.js.map +1 -1
  9. package/lib/cjs/Forms/inputs.v2.d.ts +5 -7
  10. package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
  11. package/lib/cjs/Forms/inputs.v2.js +7 -234
  12. package/lib/cjs/Forms/inputs.v2.js.map +1 -1
  13. package/lib/esm/CMS/components.d.ts +0 -1
  14. package/lib/esm/CMS/components.d.ts.map +1 -1
  15. package/lib/esm/Forms/form_responses.d.ts +0 -1
  16. package/lib/esm/Forms/form_responses.d.ts.map +1 -1
  17. package/lib/esm/Forms/forms.d.ts +3 -3
  18. package/lib/esm/Forms/forms.v2.d.ts +4 -4
  19. package/lib/esm/Forms/hooks.d.ts +0 -1
  20. package/lib/esm/Forms/hooks.d.ts.map +1 -1
  21. package/lib/esm/Forms/hooks.js +24 -0
  22. package/lib/esm/Forms/hooks.js.map +1 -1
  23. package/lib/esm/Forms/inputs.d.ts +5 -2
  24. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  25. package/lib/esm/Forms/inputs.js +101 -27
  26. package/lib/esm/Forms/inputs.js.map +1 -1
  27. package/lib/esm/Forms/inputs.v2.d.ts +6 -8
  28. package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
  29. package/lib/esm/Forms/inputs.v2.js +7 -234
  30. package/lib/esm/Forms/inputs.v2.js.map +1 -1
  31. package/lib/esm/controls.d.ts +2 -2
  32. package/lib/esm/inputs.d.ts +1 -1
  33. package/lib/esm/inputs.native.d.ts +0 -1
  34. package/lib/esm/inputs.native.d.ts.map +1 -1
  35. package/lib/esm/state.d.ts +315 -315
  36. package/lib/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +9 -9
  38. package/src/Forms/hooks.tsx +33 -5
  39. package/src/Forms/inputs.tsx +151 -29
  40. package/src/Forms/inputs.v2.tsx +9 -299
@@ -72,7 +72,7 @@ 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
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";
@@ -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'
@@ -1103,26 +1112,30 @@ export var DropdownInput = function (_a) {
1103
1112
  var choicesForDatabase = {};
1104
1113
  var preventRefetch = {};
1105
1114
  var LOAD_CHOICES_LIMIT = 500;
1115
+ var MIN_SEARCH_CHARS = 3;
1116
+ var SEARCH_DEBOUNCE_MS = 300;
1106
1117
  var useDatabaseChoices = function (_a) {
1107
- var _b, _d, _e, _f;
1108
- 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;
1109
1120
  var session = useResolvedSession();
1110
- var _h = useState(0), renderCount = _h[0], setRenderCount = _h[1];
1111
- // 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);
1112
1127
  useEffect(function () {
1113
- var _a, _b, _d, _e;
1114
- if ((_a = choicesForDatabase[databaseId]) === null || _a === void 0 ? void 0 : _a.done)
1128
+ var _a, _b, _d;
1129
+ if (initialLoadRef.current)
1115
1130
  return;
1116
- if (renderCount > 100)
1117
- return; // limit to 50000 entries / prevent infinite looping
1118
- var choices = (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.records) !== null && _d !== void 0 ? _d : [];
1119
- var lastId = (_e = choicesForDatabase[databaseId]) === null || _e === void 0 ? void 0 : _e.lastId;
1120
- 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);
1121
1133
  return;
1122
- preventRefetch[databaseId + field.id + lastId] = true;
1134
+ }
1135
+ initialLoadRef.current = true;
1136
+ preventRefetch[databaseId + field.id] = true;
1123
1137
  session.api.form_fields.load_choices_from_database({
1124
1138
  fieldId: field.id,
1125
- lastId: lastId,
1126
1139
  limit: LOAD_CHOICES_LIMIT,
1127
1140
  databaseId: databaseId,
1128
1141
  })
@@ -1131,17 +1144,67 @@ var useDatabaseChoices = function (_a) {
1131
1144
  var newChoices = _a.choices;
1132
1145
  choicesForDatabase[databaseId] = {
1133
1146
  lastId: (_b = newChoices === null || newChoices === void 0 ? void 0 : newChoices[newChoices.length - 1]) === null || _b === void 0 ? void 0 : _b.id,
1134
- 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)
1135
1148
  .localeCompare(label_for_database_record(field, c2))); }),
1136
- done: newChoices.length < LOAD_CHOICES_LIMIT,
1149
+ done: true, // Don't load more pages automatically
1137
1150
  };
1138
- 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);
1139
1202
  })
1140
1203
  .catch(function (err) {
1141
1204
  console.error(err);
1142
- preventRefetch[databaseId + field.id + lastId] = false;
1205
+ setIsSearching(false);
1143
1206
  });
1144
- }, [session, field, databaseId, renderCount]);
1207
+ }, [session, field, databaseId, debouncedSearch]);
1145
1208
  var addChoice = useCallback(function (record) {
1146
1209
  if (!choicesForDatabase[databaseId]) {
1147
1210
  choicesForDatabase[databaseId] = {
@@ -1151,10 +1214,15 @@ var useDatabaseChoices = function (_a) {
1151
1214
  }
1152
1215
  choicesForDatabase[databaseId].records.push(record);
1153
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 : []);
1154
1221
  return {
1155
1222
  addChoice: addChoice,
1156
- doneLoading: (_d = (_b = choicesForDatabase[databaseId]) === null || _b === void 0 ? void 0 : _b.done) !== null && _d !== void 0 ? _d : false,
1157
- 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) {
1158
1226
  var _a;
1159
1227
  return ({
1160
1228
  id: v.text,
@@ -1162,7 +1230,7 @@ var useDatabaseChoices = function (_a) {
1162
1230
  values: [{ label: ((_a = field.options) === null || _a === void 0 ? void 0 : _a.databaseLabel) || '', type: 'Text', value: v.text }],
1163
1231
  });
1164
1232
  }), true),
1165
- renderCount: renderCount,
1233
+ minSearchChars: MIN_SEARCH_CHARS,
1166
1234
  };
1167
1235
  };
1168
1236
  var label_for_database_record = function (field, record) {
@@ -1194,13 +1262,15 @@ var get_other_answers = function (_value, typing) {
1194
1262
  };
1195
1263
  export var DatabaseSelectInput = function (_a) {
1196
1264
  var _b, _d, _e, _f, _g, _h, _j, _k;
1197
- 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;
1198
1266
  var _l = useState(''), typing = _l[0], setTyping = _l[1];
1199
- var _m = useDatabaseChoices({
1267
+ var _m = useState(false), open = _m[0], setOpen = _m[1];
1268
+ var _o = useDatabaseChoices({
1200
1269
  databaseId: (_b = field.options) === null || _b === void 0 ? void 0 : _b.databaseId,
1201
1270
  field: field,
1202
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),
1203
- }), 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;
1204
1274
  var value = React.useMemo(function () {
1205
1275
  var _a, _b;
1206
1276
  try {
@@ -1304,9 +1374,13 @@ export var DatabaseSelectInput = function (_a) {
1304
1374
  }
1305
1375
  return filtered;
1306
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;
1307
1381
  if (!doneLoading)
1308
1382
  return _jsx(LinearProgress, {});
1309
- 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
1310
1384
  ? ''
1311
1385
  : label_for_database_record(field, o)); }, value: value, disabled: disabled, onChange: function (_, v) {
1312
1386
  var _a, _b, _d, _e, _f;
@@ -1329,7 +1403,7 @@ export var DatabaseSelectInput = function (_a) {
1329
1403
  recordId: (_f = (_e = v[v.length - 1]) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : '',
1330
1404
  text: label_for_database_record(field, v[v.length - 1]),
1331
1405
  }]), field.id);
1332
- }, 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 }))); },
1333
1407
  // use custom Chip to ensure very long entries break properly (whitespace: normal)
1334
1408
  renderTags: function (value, getTagProps) {
1335
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 } }))); });