@servicetitan/dte-unlayer 0.143.0 → 0.144.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.
@@ -1 +1 @@
1
- {"version":3,"file":"LookupValueControl.d.ts","sourceRoot":"","sources":["../../src/display-conditions/LookupValueControl.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAqBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,EAC/B,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,KAAK,GACR,EAAE,QAAQ,CAAC,uBAAuB,CAAC,2CA8HnC"}
1
+ {"version":3,"file":"LookupValueControl.d.ts","sourceRoot":"","sources":["../../src/display-conditions/LookupValueControl.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAqBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,EAC/B,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,KAAK,GACR,EAAE,QAAQ,CAAC,uBAAuB,CAAC,2CAsJnC"}
@@ -31,13 +31,40 @@ const sanitizeNumericInput = (raw)=>{
31
31
  * re-searches and re-selects.
32
32
  */ export function LookupValueControl({ fieldType, lookupSource, onChange, value }) {
33
33
  const { loadOptions, options, resolveOptions, status } = useLookupOptions(lookupSource);
34
- const staticSelected = useMemo(()=>{
35
- var _options_filter_map_;
36
- return (_options_filter_map_ = options.filter((option)=>option.key === value).map(toOption)[0]) !== null && _options_filter_map_ !== void 0 ? _options_filter_map_ : null;
34
+ const staticOptions = useMemo(()=>options.map(toOption), [
35
+ options
36
+ ]);
37
+ /*
38
+ * Fall back to the raw value so a saved key that the host omits from its list
39
+ * (e.g. an inactive/renamed option) still shows on edit instead of vanishing.
40
+ */ const staticSelected = useMemo(()=>{
41
+ if (!value) {
42
+ return null;
43
+ }
44
+ var _staticOptions_find;
45
+ return (_staticOptions_find = staticOptions.find((option)=>String(option.id) === value)) !== null && _staticOptions_find !== void 0 ? _staticOptions_find : {
46
+ id: value,
47
+ label: value
48
+ };
37
49
  }, [
38
- options,
50
+ staticOptions,
39
51
  value
40
52
  ]);
53
+ /*
54
+ * Ensure the saved value is present in the list so it is visible/selectable
55
+ * even when the host did not return it.
56
+ */ const staticOptionsWithSelected = useMemo(()=>{
57
+ if (staticSelected && !staticOptions.some((option)=>String(option.id) === String(staticSelected.id))) {
58
+ return [
59
+ staticSelected,
60
+ ...staticOptions
61
+ ];
62
+ }
63
+ return staticOptions;
64
+ }, [
65
+ staticOptions,
66
+ staticSelected
67
+ ]);
41
68
  const [searchableSelected, setSearchableSelected] = useState(value ? {
42
69
  id: value,
43
70
  label: value
@@ -116,7 +143,7 @@ const sanitizeNumericInput = (raw)=>{
116
143
  size: "small",
117
144
  label: "Value",
118
145
  placeholder: "Search value...",
119
- options: options.map(toOption),
146
+ options: staticOptionsWithSelected,
120
147
  value: staticSelected,
121
148
  onSelectedOptionChange: (option)=>onChange(option ? String(option.id) : '')
122
149
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/display-conditions/LookupValueControl.tsx"],"sourcesContent":["import { TextField } from '@servicetitan/anvil2';\nimport { SelectField, SelectFieldOption, SelectFieldSync } from '@servicetitan/anvil2/beta';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { LookupOption } from './lookupOptionsController';\nimport { useLookupOptions } from './useLookupOptions';\n\nexport interface LookupValueControlProps {\n value: string;\n fieldType: 'number' | 'string';\n /** The selected data point's `options.fieldLookupSource`; empty -> free-text input. */\n lookupSource?: string;\n onChange: (value: string) => void;\n}\n\nconst toOption = (option: LookupOption): SelectFieldOption => ({\n id: option.key,\n label: option.label,\n});\n\nconst sanitizeNumericInput = (raw: string): string => {\n let value = raw.replaceAll(/[^0-9.-]/g, '');\n const isNegative = value.startsWith('-');\n value = value.replaceAll('-', '');\n if (isNegative) {\n value = `-${value}`;\n }\n const firstDot = value.indexOf('.');\n if (firstDot >= 0) {\n value = `${value.slice(0, firstDot + 1)}${value.slice(firstDot + 1).replaceAll('.', '')}`;\n }\n return value;\n};\n\n/**\n * Renders the value control for a single condition based on the field's lookup source:\n * - `static` -> full list returned by the host, filtered locally (`SelectFieldSync`).\n * - `searchable` -> host searches per typed query (`SelectField` + `loadOptions`).\n * - `idle`/`fallback`/`loading` -> free-text input (never block the user).\n *\n * Unlayer persists conditions as a Nunjucks string, so a saved searchable value\n * has no stored label — we display the saved key as the label until the user\n * re-searches and re-selects.\n */\nexport function LookupValueControl({\n fieldType,\n lookupSource,\n onChange,\n value,\n}: Readonly<LookupValueControlProps>) {\n const { loadOptions, options, resolveOptions, status } = useLookupOptions(lookupSource);\n\n const staticSelected = useMemo<SelectFieldOption | null>(\n () => options.filter(option => option.key === value).map(toOption)[0] ?? null,\n [options, value],\n );\n\n const [searchableSelected, setSearchableSelected] = useState<SelectFieldOption | null>(\n value ? { id: value, label: value } : null,\n );\n\n // Tracks the key we've already turned into a human-readable label so we don't re-resolve it.\n const resolvedKeyRef = useRef<string | null>(null);\n\n useEffect(() => {\n setSearchableSelected(prev => {\n if (!value) {\n return null;\n }\n if (prev?.id === value) {\n return prev;\n }\n return { id: value, label: value };\n });\n }, [value]);\n\n // On open (or when a saved searchable value appears), ask the host to resolve key -> label.\n useEffect(() => {\n if (status !== 'searchable' || !value) {\n resolvedKeyRef.current = null;\n return;\n }\n if (resolvedKeyRef.current === value) {\n return;\n }\n\n let cancelled = false;\n resolveOptions([value]).then(result => {\n if (cancelled) {\n return;\n }\n const match = result.find(option => option.key === value);\n if (match) {\n resolvedKeyRef.current = value;\n setSearchableSelected({ id: match.key, label: match.label });\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [status, value, resolveOptions]);\n\n const handleLoadOptions = useCallback(\n async (query: string) => {\n const result = await loadOptions(query);\n return result.map(toOption);\n },\n [loadOptions],\n );\n\n if (status === 'loading') {\n return (\n <TextField\n label=\"Value\"\n size=\"small\"\n flex={1}\n disabled\n value=\"\"\n placeholder=\"\"\n aria-label=\"Value\"\n />\n );\n }\n\n if (status === 'static') {\n return (\n <SelectFieldSync\n flex={1}\n size=\"small\"\n label=\"Value\"\n placeholder=\"Search value...\"\n options={options.map(toOption)}\n value={staticSelected}\n onSelectedOptionChange={option => onChange(option ? String(option.id) : '')}\n />\n );\n }\n\n if (status === 'searchable') {\n return (\n <SelectField\n flex={1}\n size=\"small\"\n label=\"Value\"\n placeholder=\"Type to search...\"\n initialLoad=\"open\"\n loadOptions={handleLoadOptions}\n value={searchableSelected}\n onSelectedOptionChange={option => {\n resolvedKeyRef.current = option ? String(option.id) : null;\n setSearchableSelected(option);\n onChange(option ? String(option.id) : '');\n }}\n />\n );\n }\n\n // idle / fallback (no source, empty, error, or timeout) -> free text.\n const handleTextChange = (raw: string) => {\n onChange(fieldType === 'number' ? sanitizeNumericInput(raw) : raw);\n };\n\n return (\n <TextField\n label=\"Value\"\n size=\"small\"\n flex={1}\n value={value}\n onChange={e => handleTextChange(e.target.value)}\n placeholder={fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...'}\n aria-label=\"Value\"\n {...(fieldType === 'number' && { inputMode: 'decimal' })}\n />\n );\n}\n"],"names":["TextField","SelectField","SelectFieldSync","useCallback","useEffect","useMemo","useRef","useState","useLookupOptions","toOption","option","id","key","label","sanitizeNumericInput","raw","value","replaceAll","isNegative","startsWith","firstDot","indexOf","slice","LookupValueControl","fieldType","lookupSource","onChange","loadOptions","options","resolveOptions","status","staticSelected","filter","map","searchableSelected","setSearchableSelected","resolvedKeyRef","prev","current","cancelled","then","result","match","find","handleLoadOptions","query","size","flex","disabled","placeholder","aria-label","onSelectedOptionChange","String","initialLoad","handleTextChange","e","target","inputMode"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/display-conditions/LookupValueControl.tsx"],"sourcesContent":["import { TextField } from '@servicetitan/anvil2';\nimport { SelectField, SelectFieldOption, SelectFieldSync } from '@servicetitan/anvil2/beta';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { LookupOption } from './lookupOptionsController';\nimport { useLookupOptions } from './useLookupOptions';\n\nexport interface LookupValueControlProps {\n value: string;\n fieldType: 'number' | 'string';\n /** The selected data point's `options.fieldLookupSource`; empty -> free-text input. */\n lookupSource?: string;\n onChange: (value: string) => void;\n}\n\nconst toOption = (option: LookupOption): SelectFieldOption => ({\n id: option.key,\n label: option.label,\n});\n\nconst sanitizeNumericInput = (raw: string): string => {\n let value = raw.replaceAll(/[^0-9.-]/g, '');\n const isNegative = value.startsWith('-');\n value = value.replaceAll('-', '');\n if (isNegative) {\n value = `-${value}`;\n }\n const firstDot = value.indexOf('.');\n if (firstDot >= 0) {\n value = `${value.slice(0, firstDot + 1)}${value.slice(firstDot + 1).replaceAll('.', '')}`;\n }\n return value;\n};\n\n/**\n * Renders the value control for a single condition based on the field's lookup source:\n * - `static` -> full list returned by the host, filtered locally (`SelectFieldSync`).\n * - `searchable` -> host searches per typed query (`SelectField` + `loadOptions`).\n * - `idle`/`fallback`/`loading` -> free-text input (never block the user).\n *\n * Unlayer persists conditions as a Nunjucks string, so a saved searchable value\n * has no stored label — we display the saved key as the label until the user\n * re-searches and re-selects.\n */\nexport function LookupValueControl({\n fieldType,\n lookupSource,\n onChange,\n value,\n}: Readonly<LookupValueControlProps>) {\n const { loadOptions, options, resolveOptions, status } = useLookupOptions(lookupSource);\n\n const staticOptions = useMemo<SelectFieldOption[]>(() => options.map(toOption), [options]);\n\n /*\n * Fall back to the raw value so a saved key that the host omits from its list\n * (e.g. an inactive/renamed option) still shows on edit instead of vanishing.\n */\n const staticSelected = useMemo<SelectFieldOption | null>(() => {\n if (!value) {\n return null;\n }\n return (\n staticOptions.find(option => String(option.id) === value) ?? { id: value, label: value }\n );\n }, [staticOptions, value]);\n\n /*\n * Ensure the saved value is present in the list so it is visible/selectable\n * even when the host did not return it.\n */\n const staticOptionsWithSelected = useMemo<SelectFieldOption[]>(() => {\n if (\n staticSelected &&\n !staticOptions.some(option => String(option.id) === String(staticSelected.id))\n ) {\n return [staticSelected, ...staticOptions];\n }\n return staticOptions;\n }, [staticOptions, staticSelected]);\n\n const [searchableSelected, setSearchableSelected] = useState<SelectFieldOption | null>(\n value ? { id: value, label: value } : null,\n );\n\n // Tracks the key we've already turned into a human-readable label so we don't re-resolve it.\n const resolvedKeyRef = useRef<string | null>(null);\n\n useEffect(() => {\n setSearchableSelected(prev => {\n if (!value) {\n return null;\n }\n if (prev?.id === value) {\n return prev;\n }\n return { id: value, label: value };\n });\n }, [value]);\n\n // On open (or when a saved searchable value appears), ask the host to resolve key -> label.\n useEffect(() => {\n if (status !== 'searchable' || !value) {\n resolvedKeyRef.current = null;\n return;\n }\n if (resolvedKeyRef.current === value) {\n return;\n }\n\n let cancelled = false;\n resolveOptions([value]).then(result => {\n if (cancelled) {\n return;\n }\n const match = result.find(option => option.key === value);\n if (match) {\n resolvedKeyRef.current = value;\n setSearchableSelected({ id: match.key, label: match.label });\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [status, value, resolveOptions]);\n\n const handleLoadOptions = useCallback(\n async (query: string) => {\n const result = await loadOptions(query);\n return result.map(toOption);\n },\n [loadOptions],\n );\n\n if (status === 'loading') {\n return (\n <TextField\n label=\"Value\"\n size=\"small\"\n flex={1}\n disabled\n value=\"\"\n placeholder=\"\"\n aria-label=\"Value\"\n />\n );\n }\n\n if (status === 'static') {\n return (\n <SelectFieldSync\n flex={1}\n size=\"small\"\n label=\"Value\"\n placeholder=\"Search value...\"\n options={staticOptionsWithSelected}\n value={staticSelected}\n onSelectedOptionChange={option => onChange(option ? String(option.id) : '')}\n />\n );\n }\n\n if (status === 'searchable') {\n return (\n <SelectField\n flex={1}\n size=\"small\"\n label=\"Value\"\n placeholder=\"Type to search...\"\n initialLoad=\"open\"\n loadOptions={handleLoadOptions}\n value={searchableSelected}\n onSelectedOptionChange={option => {\n resolvedKeyRef.current = option ? String(option.id) : null;\n setSearchableSelected(option);\n onChange(option ? String(option.id) : '');\n }}\n />\n );\n }\n\n // idle / fallback (no source, empty, error, or timeout) -> free text.\n const handleTextChange = (raw: string) => {\n onChange(fieldType === 'number' ? sanitizeNumericInput(raw) : raw);\n };\n\n return (\n <TextField\n label=\"Value\"\n size=\"small\"\n flex={1}\n value={value}\n onChange={e => handleTextChange(e.target.value)}\n placeholder={fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...'}\n aria-label=\"Value\"\n {...(fieldType === 'number' && { inputMode: 'decimal' })}\n />\n );\n}\n"],"names":["TextField","SelectField","SelectFieldSync","useCallback","useEffect","useMemo","useRef","useState","useLookupOptions","toOption","option","id","key","label","sanitizeNumericInput","raw","value","replaceAll","isNegative","startsWith","firstDot","indexOf","slice","LookupValueControl","fieldType","lookupSource","onChange","loadOptions","options","resolveOptions","status","staticOptions","map","staticSelected","find","String","staticOptionsWithSelected","some","searchableSelected","setSearchableSelected","resolvedKeyRef","prev","current","cancelled","then","result","match","handleLoadOptions","query","size","flex","disabled","placeholder","aria-label","onSelectedOptionChange","initialLoad","handleTextChange","e","target","inputMode"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/dte-unlayer",
3
- "version": "0.143.0",
3
+ "version": "0.144.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "typings": "./dist/index.d.ts",
@@ -49,10 +49,34 @@ export function LookupValueControl({
49
49
  }: Readonly<LookupValueControlProps>) {
50
50
  const { loadOptions, options, resolveOptions, status } = useLookupOptions(lookupSource);
51
51
 
52
- const staticSelected = useMemo<SelectFieldOption | null>(
53
- () => options.filter(option => option.key === value).map(toOption)[0] ?? null,
54
- [options, value],
55
- );
52
+ const staticOptions = useMemo<SelectFieldOption[]>(() => options.map(toOption), [options]);
53
+
54
+ /*
55
+ * Fall back to the raw value so a saved key that the host omits from its list
56
+ * (e.g. an inactive/renamed option) still shows on edit instead of vanishing.
57
+ */
58
+ const staticSelected = useMemo<SelectFieldOption | null>(() => {
59
+ if (!value) {
60
+ return null;
61
+ }
62
+ return (
63
+ staticOptions.find(option => String(option.id) === value) ?? { id: value, label: value }
64
+ );
65
+ }, [staticOptions, value]);
66
+
67
+ /*
68
+ * Ensure the saved value is present in the list so it is visible/selectable
69
+ * even when the host did not return it.
70
+ */
71
+ const staticOptionsWithSelected = useMemo<SelectFieldOption[]>(() => {
72
+ if (
73
+ staticSelected &&
74
+ !staticOptions.some(option => String(option.id) === String(staticSelected.id))
75
+ ) {
76
+ return [staticSelected, ...staticOptions];
77
+ }
78
+ return staticOptions;
79
+ }, [staticOptions, staticSelected]);
56
80
 
57
81
  const [searchableSelected, setSearchableSelected] = useState<SelectFieldOption | null>(
58
82
  value ? { id: value, label: value } : null,
@@ -129,7 +153,7 @@ export function LookupValueControl({
129
153
  size="small"
130
154
  label="Value"
131
155
  placeholder="Search value..."
132
- options={options.map(toOption)}
156
+ options={staticOptionsWithSelected}
133
157
  value={staticSelected}
134
158
  onSelectedOptionChange={option => onChange(option ? String(option.id) : '')}
135
159
  />