@tellescope/react-components 1.232.0 → 1.233.0

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 +4 -1
  10. package/lib/cjs/Forms/inputs.d.ts.map +1 -1
  11. package/lib/cjs/Forms/inputs.js +100 -26
  12. package/lib/cjs/Forms/inputs.js.map +1 -1
  13. package/lib/cjs/Forms/inputs.v2.d.ts +5 -7
  14. package/lib/cjs/Forms/inputs.v2.d.ts.map +1 -1
  15. package/lib/cjs/Forms/inputs.v2.js +7 -234
  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 +5 -2
  32. package/lib/esm/Forms/inputs.d.ts.map +1 -1
  33. package/lib/esm/Forms/inputs.js +101 -27
  34. package/lib/esm/Forms/inputs.js.map +1 -1
  35. package/lib/esm/Forms/inputs.v2.d.ts +6 -8
  36. package/lib/esm/Forms/inputs.v2.d.ts.map +1 -1
  37. package/lib/esm/Forms/inputs.v2.js +7 -234
  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 +11 -11
  46. package/src/Forms/forms.tsx +2 -2
  47. package/src/Forms/forms.v2.tsx +1 -1
  48. package/src/Forms/hooks.tsx +33 -5
  49. package/src/Forms/inputs.tsx +151 -29
  50. package/src/Forms/inputs.v2.tsx +9 -299
@@ -1754,310 +1754,20 @@ export const DropdownInput = ({ field, value, onChange }: FormInputProps<'Dropdo
1754
1754
  )
1755
1755
  }
1756
1756
 
1757
- const choicesForDatabase: {
1758
- [index: string]: {
1759
- done: boolean,
1760
- records: DatabaseRecord[],
1761
- lastId?: string,
1762
- } | {
1763
- done: undefined,
1764
- records: undefined,
1765
- lastId?: string,
1766
- }
1767
- } = {}
1768
- const preventRefetch: Record<string, boolean> = {}
1769
-
1770
- const LOAD_CHOICES_LIMIT = 500
1771
- const useDatabaseChoices = ({ databaseId='', field, otherAnswers } : { databaseId?: string, field: FormField, otherAnswers?: DatabaseSelectResponse[] }) => {
1772
- const session = useResolvedSession()
1773
- const [renderCount, setRenderCount] = useState(0)
1774
-
1775
- // todo: make searchable, don't load all
1776
- useEffect(() => {
1777
- if (choicesForDatabase[databaseId]?.done) return
1778
- if (renderCount > 100) return // limit to 50000 entries / prevent infinite looping
1779
- const choices = choicesForDatabase[databaseId]?.records ?? []
1780
- const lastId = choicesForDatabase[databaseId]?.lastId
1781
-
1782
- if (preventRefetch[databaseId + field.id + lastId]) return
1783
- preventRefetch[databaseId + field.id + lastId] = true
1784
-
1785
- session.api.form_fields.load_choices_from_database({
1786
- fieldId: field.id,
1787
- lastId,
1788
- limit: LOAD_CHOICES_LIMIT,
1789
- databaseId, // overrides fieldId, supports using Database question in Table Input
1790
- })
1791
- .then(({ choices: newChoices }) => {
1792
- choicesForDatabase[databaseId] = {
1793
- lastId: newChoices?.[newChoices.length - 1]?.id,
1794
- records: [...choices, ...newChoices]
1795
- .sort((c1, c2) => (
1796
- label_for_database_record(field, c1)
1797
- .localeCompare(label_for_database_record(field, c2))
1798
- )
1799
- ),
1800
- done: newChoices.length < LOAD_CHOICES_LIMIT,
1801
- }
1802
- setRenderCount(r => r + 1)
1803
- })
1804
- .catch(err => {
1805
- console.error(err)
1806
- preventRefetch[databaseId + field.id + lastId] = false
1807
- })
1808
- }, [session, field, databaseId, renderCount])
1809
-
1810
- const addChoice = useCallback((record: DatabaseRecord) => {
1811
- if (!choicesForDatabase[databaseId]) {
1812
- choicesForDatabase[databaseId] = {
1813
- done: false,
1814
- records: [],
1815
- }
1816
- }
1817
- choicesForDatabase[databaseId].records!.push(record)
1818
- }, [choicesForDatabase, databaseId])
1819
-
1820
- return {
1821
- addChoice,
1822
- doneLoading: choicesForDatabase[databaseId]?.done ?? false,
1823
- choices: [
1824
- ...choicesForDatabase[databaseId]?.records ?? [],
1825
- ...(otherAnswers || []).map(v => ({
1826
- id: v.text,
1827
- databaseId,
1828
- values: [{ label: field.options?.databaseLabel || '', type: 'Text', value: v.text }],
1829
- }) as Pick<DatabaseRecord, 'id' | 'values' | 'databaseId'>)
1830
- ],
1831
- renderCount,
1832
- }
1833
- }
1834
-
1835
-
1836
- const label_for_database_record = (field: FormField, record?: Pick<DatabaseRecord, 'values'>) => {
1837
- if (!record) return ''
1838
-
1839
- const addedLabels = (
1840
- (field.options?.databaseLabels || [])
1841
- .map(l => record.values.find(v => v.label === l)?.value?.toString())
1842
- .filter(v => v?.trim())
1843
- ) as string[]
1844
-
1845
- return (
1846
- (record.values.find(v => v.label === field.options?.databaseLabel)?.value?.toString() ?? '')
1847
- + (
1848
- addedLabels.length
1849
- ? ` (${addedLabels.join(', ')})`
1850
- : ''
1851
- )
1852
- )
1853
- }
1757
+ // DatabaseSelectInput logic is shared with inputs.tsx to avoid duplication
1758
+ // Import the interface and component from the shared implementation
1759
+ import { AddToDatabaseProps as AddToDatabasePropsImported, DatabaseSelectInput as SharedDatabaseSelectInput } from './inputs'
1854
1760
 
1855
- const get_other_answers = (_value?: DatabaseSelectResponse[], typing?: string) => {
1856
- try {
1857
- const existing = (
1858
- (_value || [])
1859
- .filter(v => typeof v === 'string' || v.recordId === v.text)
1860
- .map(v => typeof v === 'string' ? { databaseId: '', recordId: v, text: v } : v)
1861
- )
1862
- if (typing) {
1863
- existing.push({ text: typing, databaseId: '', recordId: typing })
1864
- }
1865
-
1866
- return existing
1867
- } catch(err) { console.error(err) }
1868
-
1869
- return []
1870
- }
1871
-
1872
- export interface AddToDatabaseProps {
1873
- databaseId: string,
1874
- onAdd: (record: DatabaseRecord) => void
1875
- }
1761
+ // Re-export the interface for external use
1762
+ export type AddToDatabaseProps = AddToDatabasePropsImported
1876
1763
 
1877
- export const DatabaseSelectInput = ({ AddToDatabase, field, value: _value, onChange, onDatabaseSelect, responses, size, disabled, enduser }: FormInputProps<'Database Select'> & {
1764
+ // Wrap the shared DatabaseSelectInput component with v2-specific props
1765
+ export const DatabaseSelectInput = (props: FormInputProps<'Database Select'> & {
1878
1766
  responses: FormResponseValue[],
1879
1767
  AddToDatabase?: React.JSXElementConstructor<AddToDatabaseProps>,
1880
1768
  }) => {
1881
- const [typing, setTyping] = useState('')
1882
- const { addChoice, choices, doneLoading } = useDatabaseChoices({
1883
- databaseId: field.options?.databaseId,
1884
- field,
1885
- otherAnswers: get_other_answers(_value, field?.options?.other ? typing : undefined),
1886
- })
1887
-
1888
- const value = React.useMemo(() => {
1889
- try {
1890
- // if the value is a string (some single answer that was save), make sure we coerce to array
1891
- const __value = typeof _value === 'string' ? [_value] : _value
1892
- return (
1893
- (__value?.map(v =>
1894
- choices.find(c =>
1895
- c.id === v.recordId || (typeof v === 'string' && label_for_database_record(field, c) === v)
1896
- )
1897
- )?.filter(v => v!) ?? []) as DatabaseRecord[]
1898
- )
1899
- } catch(err) {
1900
- console.error('Error resolving database answers for _value', err)
1901
- return []
1902
- }
1903
- }, [_value, choices, field])
1904
-
1905
- const filterResponse = useMemo(() => (
1906
- field.options?.databaseFilter?.fieldId
1907
- ? responses.find(r => r.fieldId === field.options?.databaseFilter?.fieldId)?.answer?.value
1908
- : undefined
1909
- ), [responses, field.options?.databaseFilter])
1910
-
1911
- // State filtering logic similar to Insurance component
1912
- const addressQuestion = useMemo(() => responses?.find(r => {
1913
- if (r.answer.type !== 'Address') return false
1914
- if (r.field.intakeField !== 'Address') return false
1915
-
1916
- // make sure state is actually defined (in case of multiple address questions, where 1+ are blank)
1917
- if (!r.answer.value?.state) return false
1918
-
1919
- return true
1920
- }), [responses])
1921
-
1922
- const state = useMemo(() => (
1923
- field.options?.filterByEnduserState
1924
- ? ((addressQuestion?.answer?.type === 'Address' ? addressQuestion?.answer?.value?.state : undefined) || enduser?.state)
1925
- : undefined
1926
- ), [enduser?.state, addressQuestion, field.options?.filterByEnduserState])
1927
-
1928
- const filteredChoicesWithPotentialDuplicates = useMemo(() => {
1929
- if (!choices) return []
1930
- if (!filterResponse) return choices
1931
- if (!field?.options?.databaseFilter?.databaseLabel)
1932
- if (!value || value.length === 0) return choices
1933
-
1934
- return (
1935
- choices
1936
- .filter(c => {
1937
- const v = c.values.find(_v => _v.label === field.options?.databaseFilter?.databaseLabel)?.value
1938
- if (!v) return true
1939
-
1940
- // use .text on r values to handle Database Select types as filter source (in addition to basic text and list of text)
1941
-
1942
- if (typeof v === 'object') {
1943
- return !!(
1944
- Object.values(v).find(oVal => (
1945
- typeof oVal === 'string' || typeof oVal === 'number'
1946
- ? (
1947
- Array.isArray(filterResponse)
1948
- ? (filterResponse as any[]).find(r => r === oVal.toString() || (typeof r === 'object' && r.text === oVal))
1949
- : (typeof filterResponse === 'string' || typeof filterResponse === 'number')
1950
- ? filterResponse.toString() === oVal.toString()
1951
- : false
1952
- )
1953
- : false
1954
- ))
1955
- )
1956
- }
1957
-
1958
- if (typeof v === 'string' || typeof v === 'number') {
1959
- return !!(
1960
- Array.isArray(filterResponse)
1961
- ? (filterResponse as any[]).find(r => r === v.toString() || (typeof r === 'object' && r.text === v))
1962
- : (typeof filterResponse === 'string' || typeof filterResponse === 'number')
1963
- ? filterResponse.toString() === v.toString()
1964
- : (typeof filterResponse === 'object' && (filterResponse as Address).city === v.toString()) ? true
1965
- : (typeof filterResponse === 'object' && (filterResponse as Address).state === v.toString()) ? true
1966
- : (typeof filterResponse === 'object' && (filterResponse as Address).zipCode === v.toString()) ? true
1967
- : false
1968
- )
1969
- }
1970
-
1971
- return false
1972
- })
1973
- )
1974
- }, [choices, filterResponse, field.options?.databaseFilter, value])
1975
-
1976
- // Apply state filtering as a secondary filter (doesn't modify existing logic)
1977
- const stateFilteredChoices = useMemo(() => {
1978
- if (!field.options?.filterByEnduserState || !state) {
1979
- return filteredChoicesWithPotentialDuplicates
1980
- }
1981
-
1982
- return filteredChoicesWithPotentialDuplicates.filter(c => {
1983
- const recordState = c.values.find(v => v.label?.trim()?.toLowerCase() === 'state')?.value?.toString() || ''
1984
- return !recordState || recordState === state
1985
- })
1986
- }, [filteredChoicesWithPotentialDuplicates, field.options?.filterByEnduserState, state])
1987
-
1988
- const filteredChoices = useMemo(() => {
1989
- const filtered = []
1990
-
1991
- const uniques = new Set<string>([])
1992
- for (const c of stateFilteredChoices) {
1993
- const text = label_for_database_record(field, c)
1994
- if (uniques.has(text)) continue // duplicate found
1995
-
1996
- uniques.add(text)
1997
- filtered.push(c)
1998
- }
1999
-
2000
- return filtered
2001
- }, [field, stateFilteredChoices])
2002
-
2003
- if (!doneLoading) return <LinearProgress />
2004
- return (
2005
- <>
2006
- <Autocomplete id={field.id} freeSolo={false} size={size}
2007
- componentsProps={{ popper: { sx: { wordBreak: "break-word" } } } }
2008
- options={filteredChoices} multiple={true}
2009
- getOptionLabel={o => (
2010
- Array.isArray(o) // edge case
2011
- ? ''
2012
- : label_for_database_record(field, o)
2013
- )}
2014
- value={value}
2015
- disabled={disabled}
2016
- onChange={(_, v) => {
2017
- if (v.length && onDatabaseSelect) {
2018
- onDatabaseSelect(
2019
- field.options?.radio
2020
- ? [v[v.length - 1]] // if radio, only last selected
2021
- : v
2022
- )
2023
- }
2024
- return onChange(
2025
- (
2026
- !field.options?.radio
2027
- ? v.map(_v => ({
2028
- databaseId: field.options?.databaseId!,
2029
- recordId: _v.id,
2030
- text: label_for_database_record(field, _v),
2031
- }))
2032
- : [{
2033
- databaseId: field.options?.databaseId!,
2034
- recordId: v[v.length -1]?.id ?? '',
2035
- text: label_for_database_record(field, v[v.length - 1]),
2036
- }]
2037
- ),
2038
- field.id,
2039
- )
2040
- }}
2041
- inputValue={typing}
2042
- onInputChange={(e, v) => e && setTyping(v)}
2043
- renderInput={params => <TextField {...params} InputProps={{ ...params.InputProps, sx: defaultInputProps.sx }} />}
2044
- // use custom Chip to ensure very long entries break properly (whitespace: normal)
2045
- renderTags={(value, getTagProps) =>
2046
- value.map((value, index) => (
2047
- <Chip
2048
- label={<Typography style={{whiteSpace: 'normal'}}>{Array.isArray(value) ? '' : label_for_database_record(field, value)}</Typography>}
2049
- {...getTagProps({ index })}
2050
- sx={{height:"100%", py: 0.5 }}
2051
- />
2052
- ))
2053
- }
2054
- />
2055
-
2056
- {AddToDatabase && field?.options?.allowAddToDatabase && (
2057
- <AddToDatabase databaseId={field.options?.databaseId!} onAdd={addChoice} />
2058
- )}
2059
- </>
2060
- )
1769
+ // Pass all props plus v2-specific defaultInputProps to the shared component
1770
+ return <SharedDatabaseSelectInput {...props} inputProps={defaultInputProps} />
2061
1771
  }
2062
1772
 
2063
1773
  type DisplayTermsResult = { displayTermsList: { term: string[] } }