@servicetitan/dte-unlayer 0.139.0 → 0.140.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 (28) hide show
  1. package/dist/display-conditions/ConditionRow.d.ts.map +1 -1
  2. package/dist/display-conditions/ConditionRow.js +80 -15
  3. package/dist/display-conditions/ConditionRow.js.map +1 -1
  4. package/dist/display-conditions/lookupOptionsController.d.ts +56 -0
  5. package/dist/display-conditions/lookupOptionsController.d.ts.map +1 -0
  6. package/dist/display-conditions/lookupOptionsController.js +65 -0
  7. package/dist/display-conditions/lookupOptionsController.js.map +1 -0
  8. package/dist/display-conditions/schemaDataPoints.d.ts.map +1 -1
  9. package/dist/display-conditions/schemaDataPoints.js +6 -1
  10. package/dist/display-conditions/schemaDataPoints.js.map +1 -1
  11. package/dist/display-conditions/types.d.ts +2 -0
  12. package/dist/display-conditions/types.d.ts.map +1 -1
  13. package/dist/display-conditions/types.js.map +1 -1
  14. package/dist/display-conditions/useLookupOptions.d.ts +22 -0
  15. package/dist/display-conditions/useLookupOptions.d.ts.map +1 -0
  16. package/dist/display-conditions/useLookupOptions.js +65 -0
  17. package/dist/display-conditions/useLookupOptions.js.map +1 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/index.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/display-conditions/ConditionRow.tsx +76 -14
  24. package/src/display-conditions/lookupOptionsController.ts +104 -0
  25. package/src/display-conditions/schemaDataPoints.ts +3 -0
  26. package/src/display-conditions/types.ts +2 -0
  27. package/src/display-conditions/useLookupOptions.ts +84 -0
  28. package/src/index.ts +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"ConditionRow.d.ts","sourceRoot":"","sources":["../../src/display-conditions/ConditionRow.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAqB,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAoB,eAAe,EAA0C,MAAM,SAAS,CAAC;AACpG,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,eAAe,CAAC;IAC3B,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AA4BD,wBAAgB,YAAY,CAAC,EACzB,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,QAAQ,GACX,EAAE,QAAQ,CAAC,iBAAiB,CAAC,2CA4R7B"}
1
+ {"version":3,"file":"ConditionRow.d.ts","sourceRoot":"","sources":["../../src/display-conditions/ConditionRow.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAqB,MAAM,iBAAiB,CAAC;AAE9D,OAAO,EAAoB,eAAe,EAA0C,MAAM,SAAS,CAAC;AACpG,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGhE,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,eAAe,CAAC;IAC3B,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AA4BD,wBAAgB,YAAY,CAAC,EACzB,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,QAAQ,GACX,EAAE,QAAQ,CAAC,iBAAiB,CAAC,2CAwV7B"}
@@ -4,6 +4,7 @@ import TrashIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.s
4
4
  import { useEffect, useMemo, useState } from 'react';
5
5
  import { parseFormFieldKey } from '../shared/forms';
6
6
  import { NUMBER_OPERATORS, STRING_OPERATORS, VALUE_LESS_OPERATORS } from './types';
7
+ import { useLookupOptions } from './useLookupOptions';
7
8
  function sanitizeNumericInput(raw) {
8
9
  // Allow only a single leading minus and one decimal separator.
9
10
  let value = raw.replaceAll(/[^0-9.-]/g, '');
@@ -75,6 +76,16 @@ export function ConditionRow({ canRemove, condition, dataPointOptions, formField
75
76
  ]);
76
77
  var _selectedDataPoint_fieldType;
77
78
  const fieldType = (_selectedDataPoint_fieldType = selectedDataPoint === null || selectedDataPoint === void 0 ? void 0 : selectedDataPoint.fieldType) !== null && _selectedDataPoint_fieldType !== void 0 ? _selectedDataPoint_fieldType : 'string';
79
+ const isValueLess = VALUE_LESS_OPERATORS.includes(condition.operator);
80
+ // Value-less operators (is empty / is not empty) need no value, so skip the host lookup.
81
+ const { options: lookupOptions, status: lookupStatus } = useLookupOptions(isValueLess ? undefined : selectedDataPoint === null || selectedDataPoint === void 0 ? void 0 : selectedDataPoint.lookupSource);
82
+ const selectedLookupOption = useMemo(()=>{
83
+ var _lookupOptions_find;
84
+ return (_lookupOptions_find = lookupOptions.find((opt)=>opt.key === condition.value)) !== null && _lookupOptions_find !== void 0 ? _lookupOptions_find : null;
85
+ }, [
86
+ lookupOptions,
87
+ condition.value
88
+ ]);
78
89
  const operatorItems = useMemo(()=>fieldType === 'number' ? [
79
90
  ...NUMBER_OPERATORS
80
91
  ] : [
@@ -89,7 +100,6 @@ export function ConditionRow({ canRemove, condition, dataPointOptions, formField
89
100
  operatorItems,
90
101
  condition.operator
91
102
  ]);
92
- const isValueLess = VALUE_LESS_OPERATORS.includes(condition.operator);
93
103
  const handleValueChange = (raw)=>{
94
104
  const value = fieldType === 'number' ? sanitizeNumericInput(raw) : raw;
95
105
  onChange({
@@ -97,6 +107,74 @@ export function ConditionRow({ canRemove, condition, dataPointOptions, formField
97
107
  value
98
108
  });
99
109
  };
110
+ const renderValueControl = ()=>{
111
+ if (lookupStatus === 'loading') {
112
+ return /*#__PURE__*/ _jsx(TextField, {
113
+ disabled: true,
114
+ label: "Value",
115
+ size: "small",
116
+ value: "",
117
+ placeholder: "",
118
+ style: {
119
+ width: '100%'
120
+ },
121
+ "aria-label": "Value"
122
+ });
123
+ }
124
+ if (lookupStatus === 'ready') {
125
+ return /*#__PURE__*/ _jsxs(Combobox.Select, {
126
+ disableClearSelection: true,
127
+ itemToKey: (item)=>{
128
+ var _item_key;
129
+ return (_item_key = item === null || item === void 0 ? void 0 : item.key) !== null && _item_key !== void 0 ? _item_key : '';
130
+ },
131
+ itemToString: (item)=>{
132
+ var _item_label;
133
+ return (_item_label = item === null || item === void 0 ? void 0 : item.label) !== null && _item_label !== void 0 ? _item_label : '';
134
+ },
135
+ items: lookupOptions,
136
+ selectedItem: selectedLookupOption,
137
+ onChange: (item)=>{
138
+ var _item_key;
139
+ return onChange({
140
+ ...condition,
141
+ value: (_item_key = item === null || item === void 0 ? void 0 : item.key) !== null && _item_key !== void 0 ? _item_key : ''
142
+ });
143
+ },
144
+ children: [
145
+ /*#__PURE__*/ _jsx(Combobox.SelectTrigger, {
146
+ label: "Value",
147
+ placeholder: "Select value...",
148
+ size: "small"
149
+ }),
150
+ /*#__PURE__*/ _jsx(Combobox.Content, {
151
+ children: ({ items })=>/*#__PURE__*/ _jsx(Combobox.List, {
152
+ children: items.map((item, i)=>/*#__PURE__*/ _jsx(Combobox.Item, {
153
+ index: i,
154
+ item: item,
155
+ children: item.label
156
+ }, item.key))
157
+ })
158
+ })
159
+ ]
160
+ });
161
+ }
162
+ // idle / fallback (empty/error/timeout) -> never block the user.
163
+ return /*#__PURE__*/ _jsx(TextField, {
164
+ label: "Value",
165
+ size: "small",
166
+ value: condition.value,
167
+ onChange: (e)=>handleValueChange(e.target.value),
168
+ placeholder: fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...',
169
+ style: {
170
+ width: '100%'
171
+ },
172
+ "aria-label": "Value",
173
+ ...fieldType === 'number' && {
174
+ inputMode: 'decimal'
175
+ }
176
+ });
177
+ };
100
178
  const handleDataPointChange = (item)=>{
101
179
  const nextIsNumber = (item === null || item === void 0 ? void 0 : item.fieldType) === 'number';
102
180
  const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;
@@ -293,20 +371,7 @@ export function ConditionRow({ canRemove, condition, dataPointOptions, formField
293
371
  flex: '1 1 180px',
294
372
  minWidth: 150
295
373
  },
296
- children: /*#__PURE__*/ _jsx(TextField, {
297
- label: "Value",
298
- size: "small",
299
- value: condition.value,
300
- onChange: (e)=>handleValueChange(e.target.value),
301
- placeholder: fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...',
302
- style: {
303
- width: '100%'
304
- },
305
- "aria-label": "Value",
306
- ...fieldType === 'number' && {
307
- inputMode: 'decimal'
308
- }
309
- })
374
+ children: renderValueControl()
310
375
  }),
311
376
  canRemove && /*#__PURE__*/ _jsx("div", {
312
377
  style: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/display-conditions/ConditionRow.tsx"],"sourcesContent":["import { Button, Chip, Combobox, Flex, TextField } from '@servicetitan/anvil2';\nimport TrashIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';\nimport { useEffect, useMemo, useState } from 'react';\nimport { FormInfo, parseFormFieldKey } from '../shared/forms';\nimport { NUMBER_OPERATORS, SingleCondition, STRING_OPERATORS, VALUE_LESS_OPERATORS } from './types';\nimport type { DataPointOption, FormFieldOption } from './types';\n\nexport interface ConditionRowProps {\n canRemove: boolean;\n condition: SingleCondition;\n dataPointOptions: DataPointOption[];\n formFieldOptions: FormFieldOption[];\n forms: FormInfo[];\n onChange: (c: SingleCondition) => void;\n onFormSelect: (formId: number) => void;\n onRemove: () => void;\n}\n\ninterface OperatorOption {\n label: string;\n value: string;\n}\n\ninterface SourceOption {\n formId?: number;\n label: string;\n value: string;\n}\n\nfunction sanitizeNumericInput(raw: string): string {\n // Allow only a single leading minus and one decimal separator.\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\nexport function ConditionRow({\n canRemove,\n condition,\n dataPointOptions,\n formFieldOptions,\n forms,\n onChange,\n onFormSelect,\n onRemove,\n}: Readonly<ConditionRowProps>) {\n const selectedFormFieldMeta = useMemo(\n () => parseFormFieldKey(condition.dataPointKey),\n [condition.dataPointKey],\n );\n const [selectedSourceValue, setSelectedSourceValue] = useState<string>(\n selectedFormFieldMeta ? `form:${selectedFormFieldMeta.formId}` : 'schema',\n );\n\n const sourceOptions = useMemo<SourceOption[]>(\n () => [\n { label: 'Data point', value: 'schema' },\n ...forms.map(form => ({\n formId: form.id,\n label: form.name,\n value: `form:${form.id}`,\n })),\n ],\n [forms],\n );\n\n const selectedSource = useMemo<SourceOption | null>(() => {\n return (\n sourceOptions.find(opt => opt.value === selectedSourceValue) ?? sourceOptions[0] ?? null\n );\n }, [selectedSourceValue, sourceOptions]);\n\n useEffect(() => {\n if (selectedFormFieldMeta) {\n setSelectedSourceValue(`form:${selectedFormFieldMeta.formId}`);\n return;\n }\n if (!condition.dataPointKey) {\n return;\n }\n const hasSchemaMatch = dataPointOptions.some(opt => opt.fullKey === condition.dataPointKey);\n if (hasSchemaMatch) {\n setSelectedSourceValue('schema');\n }\n }, [condition.dataPointKey, dataPointOptions, selectedFormFieldMeta]);\n\n const currentFormId = selectedSource?.formId;\n const activeDataPointOptions = useMemo(\n () =>\n currentFormId\n ? formFieldOptions.filter(option => option.formId === currentFormId)\n : dataPointOptions,\n [currentFormId, dataPointOptions, formFieldOptions],\n );\n\n const selectedDataPoint = useMemo(\n () => activeDataPointOptions.find(opt => opt.fullKey === condition.dataPointKey) ?? null,\n [activeDataPointOptions, condition.dataPointKey],\n );\n\n const fieldType = selectedDataPoint?.fieldType ?? 'string';\n\n const operatorItems: OperatorOption[] = useMemo(\n () =>\n fieldType === 'number'\n ? ([...NUMBER_OPERATORS] as OperatorOption[])\n : ([...STRING_OPERATORS] as OperatorOption[]),\n [fieldType],\n );\n\n const selectedOperator = useMemo(\n () => operatorItems.find(op => op.value === condition.operator) ?? null,\n [operatorItems, condition.operator],\n );\n\n const isValueLess = VALUE_LESS_OPERATORS.includes(condition.operator);\n\n const handleValueChange = (raw: string) => {\n const value = fieldType === 'number' ? sanitizeNumericInput(raw) : raw;\n onChange({ ...condition, value });\n };\n\n const handleDataPointChange = (item: DataPointOption | null) => {\n const nextIsNumber = item?.fieldType === 'number';\n const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;\n const operatorReset = !nextOperatorItems.some(op => op.value === condition.operator);\n onChange({\n ...condition,\n dataPointKey: item?.fullKey ?? '',\n operator: operatorReset\n ? nextIsNumber\n ? 'num_eq'\n : 'is_equal_to'\n : condition.operator,\n });\n };\n\n const handleSourceChange = (item: SourceOption | null) => {\n if (!item) {\n return;\n }\n setSelectedSourceValue(item.value);\n\n if (!item.formId) {\n const schemaField =\n dataPointOptions.find(opt => opt.fullKey === condition.dataPointKey) ?? null;\n const nextFieldType = schemaField?.fieldType ?? 'string';\n const nextIsNumber = nextFieldType === 'number';\n const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;\n const operatorReset = !nextOperatorItems.some(op => op.value === condition.operator);\n\n onChange({\n ...condition,\n dataPointKey: schemaField?.fullKey ?? '',\n operator: operatorReset\n ? nextIsNumber\n ? 'num_eq'\n : 'is_equal_to'\n : condition.operator,\n });\n return;\n }\n\n onFormSelect(item.formId);\n const nextFormFieldOptions = formFieldOptions.filter(\n option => option.formId === item.formId,\n );\n const currentSelected = nextFormFieldOptions.find(\n opt => opt.fullKey === condition.dataPointKey,\n );\n const nextSelected = currentSelected ?? nextFormFieldOptions[0] ?? null;\n const nextFieldType = nextSelected?.fieldType ?? 'string';\n const nextIsNumber = nextFieldType === 'number';\n const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;\n const operatorReset = !nextOperatorItems.some(op => op.value === condition.operator);\n\n onChange({\n ...condition,\n dataPointKey: nextSelected?.fullKey ?? '',\n operator: operatorReset\n ? nextIsNumber\n ? 'num_eq'\n : 'is_equal_to'\n : condition.operator,\n });\n };\n\n return (\n <Flex direction=\"column\" gap=\"3\" style={{ width: '100%' }}>\n <div style={{ width: '100%' }}>\n <Combobox.Select\n disableClearSelection\n itemToKey={(item: SourceOption | null) => item?.value ?? ''}\n itemToString={(item: SourceOption | null) => item?.label ?? ''}\n items={sourceOptions}\n selectedItem={selectedSource}\n onChange={handleSourceChange}\n >\n <Combobox.SelectTrigger\n label=\"Data Type\"\n placeholder=\"Select source...\"\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: SourceOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item index={i} item={item} key={item.value}>\n {item.label}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n <Flex\n direction=\"row\"\n alignItems=\"flex-end\"\n gap=\"2\"\n style={{ flexWrap: 'wrap', rowGap: 8, width: '100%' }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-end',\n flexShrink: 0,\n padding: '4px 8px',\n }}\n >\n <Chip label=\"IF\" size=\"small\" />\n </div>\n <div style={{ flex: '2 1 240px', minWidth: 200 }}>\n <Combobox.Select\n disableClearSelection\n itemToKey={(item: DataPointOption | null) => item?.fullKey ?? ''}\n itemToString={(item: DataPointOption | null) => item?.title ?? ''}\n items={activeDataPointOptions}\n selectedItem={selectedDataPoint}\n onChange={handleDataPointChange}\n >\n <Combobox.SelectTrigger\n label=\"Data Point\"\n placeholder={\n currentFormId ? 'Select form field...' : 'Select data point...'\n }\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: DataPointOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item index={i} item={item} key={item.fullKey}>\n {item.title}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n <div style={{ flex: '1 1 180px', minWidth: 150 }}>\n <Combobox.Select\n disableClearSelection\n itemToKey={(item: OperatorOption | null) => item?.value ?? ''}\n itemToString={(item: OperatorOption | null) => item?.label ?? ''}\n items={operatorItems}\n selectedItem={selectedOperator}\n onChange={(item: OperatorOption | null) =>\n onChange({\n ...condition,\n operator: (item?.value ??\n 'is_equal_to') as SingleCondition['operator'],\n })\n }\n >\n <Combobox.SelectTrigger\n label=\"Condition\"\n placeholder=\"Select...\"\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: OperatorOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item index={i} item={item} key={item.value}>\n {item.label}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n {!isValueLess && (\n <div style={{ flex: '1 1 180px', minWidth: 150 }}>\n <TextField\n label=\"Value\"\n size=\"small\"\n value={condition.value}\n onChange={e => handleValueChange(e.target.value)}\n placeholder={fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...'}\n style={{ width: '100%' }}\n aria-label=\"Value\"\n {...(fieldType === 'number' && { inputMode: 'decimal' })}\n />\n </div>\n )}\n {canRemove && (\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-end',\n flexShrink: 0,\n padding: '4px 8px',\n }}\n >\n <Button\n appearance=\"secondary\"\n aria-label=\"Remove condition\"\n icon={{ before: TrashIcon }}\n size=\"small\"\n onClick={onRemove}\n />\n </div>\n )}\n </Flex>\n </Flex>\n );\n}\n"],"names":["Button","Chip","Combobox","Flex","TextField","TrashIcon","useEffect","useMemo","useState","parseFormFieldKey","NUMBER_OPERATORS","STRING_OPERATORS","VALUE_LESS_OPERATORS","sanitizeNumericInput","raw","value","replaceAll","isNegative","startsWith","firstDot","indexOf","slice","ConditionRow","canRemove","condition","dataPointOptions","formFieldOptions","forms","onChange","onFormSelect","onRemove","selectedFormFieldMeta","dataPointKey","selectedSourceValue","setSelectedSourceValue","formId","sourceOptions","label","map","form","id","name","selectedSource","find","opt","hasSchemaMatch","some","fullKey","currentFormId","activeDataPointOptions","filter","option","selectedDataPoint","fieldType","operatorItems","selectedOperator","op","operator","isValueLess","includes","handleValueChange","handleDataPointChange","item","nextIsNumber","nextOperatorItems","operatorReset","handleSourceChange","schemaField","nextFieldType","nextFormFieldOptions","currentSelected","nextSelected","direction","gap","style","width","div","Select","disableClearSelection","itemToKey","itemToString","items","selectedItem","SelectTrigger","placeholder","size","Content","List","i","Item","index","alignItems","flexWrap","rowGap","display","flexShrink","padding","flex","minWidth","title","e","target","aria-label","inputMode","appearance","icon","before","onClick"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/display-conditions/ConditionRow.tsx"],"sourcesContent":["import { Button, Chip, Combobox, Flex, TextField } from '@servicetitan/anvil2';\nimport TrashIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';\nimport { useEffect, useMemo, useState } from 'react';\nimport { FormInfo, parseFormFieldKey } from '../shared/forms';\nimport type { LookupOption } from './lookupOptionsController';\nimport { NUMBER_OPERATORS, SingleCondition, STRING_OPERATORS, VALUE_LESS_OPERATORS } from './types';\nimport type { DataPointOption, FormFieldOption } from './types';\nimport { useLookupOptions } from './useLookupOptions';\n\nexport interface ConditionRowProps {\n canRemove: boolean;\n condition: SingleCondition;\n dataPointOptions: DataPointOption[];\n formFieldOptions: FormFieldOption[];\n forms: FormInfo[];\n onChange: (c: SingleCondition) => void;\n onFormSelect: (formId: number) => void;\n onRemove: () => void;\n}\n\ninterface OperatorOption {\n label: string;\n value: string;\n}\n\ninterface SourceOption {\n formId?: number;\n label: string;\n value: string;\n}\n\nfunction sanitizeNumericInput(raw: string): string {\n // Allow only a single leading minus and one decimal separator.\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\nexport function ConditionRow({\n canRemove,\n condition,\n dataPointOptions,\n formFieldOptions,\n forms,\n onChange,\n onFormSelect,\n onRemove,\n}: Readonly<ConditionRowProps>) {\n const selectedFormFieldMeta = useMemo(\n () => parseFormFieldKey(condition.dataPointKey),\n [condition.dataPointKey],\n );\n const [selectedSourceValue, setSelectedSourceValue] = useState<string>(\n selectedFormFieldMeta ? `form:${selectedFormFieldMeta.formId}` : 'schema',\n );\n\n const sourceOptions = useMemo<SourceOption[]>(\n () => [\n { label: 'Data point', value: 'schema' },\n ...forms.map(form => ({\n formId: form.id,\n label: form.name,\n value: `form:${form.id}`,\n })),\n ],\n [forms],\n );\n\n const selectedSource = useMemo<SourceOption | null>(() => {\n return (\n sourceOptions.find(opt => opt.value === selectedSourceValue) ?? sourceOptions[0] ?? null\n );\n }, [selectedSourceValue, sourceOptions]);\n\n useEffect(() => {\n if (selectedFormFieldMeta) {\n setSelectedSourceValue(`form:${selectedFormFieldMeta.formId}`);\n return;\n }\n if (!condition.dataPointKey) {\n return;\n }\n const hasSchemaMatch = dataPointOptions.some(opt => opt.fullKey === condition.dataPointKey);\n if (hasSchemaMatch) {\n setSelectedSourceValue('schema');\n }\n }, [condition.dataPointKey, dataPointOptions, selectedFormFieldMeta]);\n\n const currentFormId = selectedSource?.formId;\n const activeDataPointOptions = useMemo(\n () =>\n currentFormId\n ? formFieldOptions.filter(option => option.formId === currentFormId)\n : dataPointOptions,\n [currentFormId, dataPointOptions, formFieldOptions],\n );\n\n const selectedDataPoint = useMemo(\n () => activeDataPointOptions.find(opt => opt.fullKey === condition.dataPointKey) ?? null,\n [activeDataPointOptions, condition.dataPointKey],\n );\n\n const fieldType = selectedDataPoint?.fieldType ?? 'string';\n\n const isValueLess = VALUE_LESS_OPERATORS.includes(condition.operator);\n\n // Value-less operators (is empty / is not empty) need no value, so skip the host lookup.\n const { options: lookupOptions, status: lookupStatus } = useLookupOptions(\n isValueLess ? undefined : selectedDataPoint?.lookupSource,\n );\n const selectedLookupOption = useMemo(\n () => lookupOptions.find(opt => opt.key === condition.value) ?? null,\n [lookupOptions, condition.value],\n );\n\n const operatorItems: OperatorOption[] = useMemo(\n () =>\n fieldType === 'number'\n ? ([...NUMBER_OPERATORS] as OperatorOption[])\n : ([...STRING_OPERATORS] as OperatorOption[]),\n [fieldType],\n );\n\n const selectedOperator = useMemo(\n () => operatorItems.find(op => op.value === condition.operator) ?? null,\n [operatorItems, condition.operator],\n );\n\n const handleValueChange = (raw: string) => {\n const value = fieldType === 'number' ? sanitizeNumericInput(raw) : raw;\n onChange({ ...condition, value });\n };\n\n const renderValueControl = () => {\n if (lookupStatus === 'loading') {\n return (\n <TextField\n disabled\n label=\"Value\"\n size=\"small\"\n value=\"\"\n placeholder=\"\"\n style={{ width: '100%' }}\n aria-label=\"Value\"\n />\n );\n }\n\n if (lookupStatus === 'ready') {\n return (\n <Combobox.Select\n disableClearSelection\n itemToKey={(item: LookupOption | null) => item?.key ?? ''}\n itemToString={(item: LookupOption | null) => item?.label ?? ''}\n items={lookupOptions}\n selectedItem={selectedLookupOption}\n onChange={(item: LookupOption | null) =>\n onChange({ ...condition, value: item?.key ?? '' })\n }\n >\n <Combobox.SelectTrigger\n label=\"Value\"\n placeholder=\"Select value...\"\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: LookupOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item index={i} item={item} key={item.key}>\n {item.label}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n );\n }\n\n // idle / fallback (empty/error/timeout) -> never block the user.\n return (\n <TextField\n label=\"Value\"\n size=\"small\"\n value={condition.value}\n onChange={e => handleValueChange(e.target.value)}\n placeholder={fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...'}\n style={{ width: '100%' }}\n aria-label=\"Value\"\n {...(fieldType === 'number' && { inputMode: 'decimal' })}\n />\n );\n };\n\n const handleDataPointChange = (item: DataPointOption | null) => {\n const nextIsNumber = item?.fieldType === 'number';\n const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;\n const operatorReset = !nextOperatorItems.some(op => op.value === condition.operator);\n onChange({\n ...condition,\n dataPointKey: item?.fullKey ?? '',\n operator: operatorReset\n ? nextIsNumber\n ? 'num_eq'\n : 'is_equal_to'\n : condition.operator,\n });\n };\n\n const handleSourceChange = (item: SourceOption | null) => {\n if (!item) {\n return;\n }\n setSelectedSourceValue(item.value);\n\n if (!item.formId) {\n const schemaField =\n dataPointOptions.find(opt => opt.fullKey === condition.dataPointKey) ?? null;\n const nextFieldType = schemaField?.fieldType ?? 'string';\n const nextIsNumber = nextFieldType === 'number';\n const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;\n const operatorReset = !nextOperatorItems.some(op => op.value === condition.operator);\n\n onChange({\n ...condition,\n dataPointKey: schemaField?.fullKey ?? '',\n operator: operatorReset\n ? nextIsNumber\n ? 'num_eq'\n : 'is_equal_to'\n : condition.operator,\n });\n return;\n }\n\n onFormSelect(item.formId);\n const nextFormFieldOptions = formFieldOptions.filter(\n option => option.formId === item.formId,\n );\n const currentSelected = nextFormFieldOptions.find(\n opt => opt.fullKey === condition.dataPointKey,\n );\n const nextSelected = currentSelected ?? nextFormFieldOptions[0] ?? null;\n const nextFieldType = nextSelected?.fieldType ?? 'string';\n const nextIsNumber = nextFieldType === 'number';\n const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;\n const operatorReset = !nextOperatorItems.some(op => op.value === condition.operator);\n\n onChange({\n ...condition,\n dataPointKey: nextSelected?.fullKey ?? '',\n operator: operatorReset\n ? nextIsNumber\n ? 'num_eq'\n : 'is_equal_to'\n : condition.operator,\n });\n };\n\n return (\n <Flex direction=\"column\" gap=\"3\" style={{ width: '100%' }}>\n <div style={{ width: '100%' }}>\n <Combobox.Select\n disableClearSelection\n itemToKey={(item: SourceOption | null) => item?.value ?? ''}\n itemToString={(item: SourceOption | null) => item?.label ?? ''}\n items={sourceOptions}\n selectedItem={selectedSource}\n onChange={handleSourceChange}\n >\n <Combobox.SelectTrigger\n label=\"Data Type\"\n placeholder=\"Select source...\"\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: SourceOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item index={i} item={item} key={item.value}>\n {item.label}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n <Flex\n direction=\"row\"\n alignItems=\"flex-end\"\n gap=\"2\"\n style={{ flexWrap: 'wrap', rowGap: 8, width: '100%' }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-end',\n flexShrink: 0,\n padding: '4px 8px',\n }}\n >\n <Chip label=\"IF\" size=\"small\" />\n </div>\n <div style={{ flex: '2 1 240px', minWidth: 200 }}>\n <Combobox.Select\n disableClearSelection\n itemToKey={(item: DataPointOption | null) => item?.fullKey ?? ''}\n itemToString={(item: DataPointOption | null) => item?.title ?? ''}\n items={activeDataPointOptions}\n selectedItem={selectedDataPoint}\n onChange={handleDataPointChange}\n >\n <Combobox.SelectTrigger\n label=\"Data Point\"\n placeholder={\n currentFormId ? 'Select form field...' : 'Select data point...'\n }\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: DataPointOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item index={i} item={item} key={item.fullKey}>\n {item.title}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n <div style={{ flex: '1 1 180px', minWidth: 150 }}>\n <Combobox.Select\n disableClearSelection\n itemToKey={(item: OperatorOption | null) => item?.value ?? ''}\n itemToString={(item: OperatorOption | null) => item?.label ?? ''}\n items={operatorItems}\n selectedItem={selectedOperator}\n onChange={(item: OperatorOption | null) =>\n onChange({\n ...condition,\n operator: (item?.value ??\n 'is_equal_to') as SingleCondition['operator'],\n })\n }\n >\n <Combobox.SelectTrigger\n label=\"Condition\"\n placeholder=\"Select...\"\n size=\"small\"\n />\n <Combobox.Content>\n {({ items }: { items: OperatorOption[] }) => (\n <Combobox.List>\n {items.map((item, i) => (\n <Combobox.Item index={i} item={item} key={item.value}>\n {item.label}\n </Combobox.Item>\n ))}\n </Combobox.List>\n )}\n </Combobox.Content>\n </Combobox.Select>\n </div>\n {!isValueLess && (\n <div style={{ flex: '1 1 180px', minWidth: 150 }}>{renderValueControl()}</div>\n )}\n {canRemove && (\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-end',\n flexShrink: 0,\n padding: '4px 8px',\n }}\n >\n <Button\n appearance=\"secondary\"\n aria-label=\"Remove condition\"\n icon={{ before: TrashIcon }}\n size=\"small\"\n onClick={onRemove}\n />\n </div>\n )}\n </Flex>\n </Flex>\n );\n}\n"],"names":["Button","Chip","Combobox","Flex","TextField","TrashIcon","useEffect","useMemo","useState","parseFormFieldKey","NUMBER_OPERATORS","STRING_OPERATORS","VALUE_LESS_OPERATORS","useLookupOptions","sanitizeNumericInput","raw","value","replaceAll","isNegative","startsWith","firstDot","indexOf","slice","ConditionRow","canRemove","condition","dataPointOptions","formFieldOptions","forms","onChange","onFormSelect","onRemove","selectedFormFieldMeta","dataPointKey","selectedSourceValue","setSelectedSourceValue","formId","sourceOptions","label","map","form","id","name","selectedSource","find","opt","hasSchemaMatch","some","fullKey","currentFormId","activeDataPointOptions","filter","option","selectedDataPoint","fieldType","isValueLess","includes","operator","options","lookupOptions","status","lookupStatus","undefined","lookupSource","selectedLookupOption","key","operatorItems","selectedOperator","op","handleValueChange","renderValueControl","disabled","size","placeholder","style","width","aria-label","Select","disableClearSelection","itemToKey","item","itemToString","items","selectedItem","SelectTrigger","Content","List","i","Item","index","e","target","inputMode","handleDataPointChange","nextIsNumber","nextOperatorItems","operatorReset","handleSourceChange","schemaField","nextFieldType","nextFormFieldOptions","currentSelected","nextSelected","direction","gap","div","alignItems","flexWrap","rowGap","display","flexShrink","padding","flex","minWidth","title","appearance","icon","before","onClick"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Host <-> editor contract for "data-model lookup source" dropdown options.
3
+ *
4
+ * A schema field can be tagged (in the template-type editor) with
5
+ * `options.fieldLookupSource`. When such a field is selected in the conditional
6
+ * UI, the editor does NOT own the option data — it asks the HOST for it over the
7
+ * EventTarget bus below and renders whatever the host returns.
8
+ *
9
+ * Transport mirrors the existing `displayConditionController` EventBus pattern.
10
+ *
11
+ * Event names (host <-> editor):
12
+ * - "dte-unlayer:lookup-options-request" (editor -> host)
13
+ * - "dte-unlayer:lookup-options-response" (host -> editor)
14
+ *
15
+ * The host-side resolver is intentionally NOT implemented in this package. The
16
+ * host listens via `onLookupOptionsRequest` and replies via
17
+ * `emitLookupOptionsResponse`.
18
+ */
19
+ /** The single dropdown option shape used end-to-end. */
20
+ export interface LookupOption {
21
+ /** Human-readable label shown in the dropdown. */
22
+ label: string;
23
+ /** Stable value baked into the stored condition. */
24
+ key: string;
25
+ }
26
+ /** editor -> host */
27
+ export interface LookupOptionsRequest {
28
+ /** Correlates a response to its originating request. */
29
+ requestId: string;
30
+ /** The selected field's `options.fieldLookupSource`. */
31
+ sourceKey: string;
32
+ /** Reserved for searchable sources (e.g. "city"); omitted for now. */
33
+ query?: string;
34
+ }
35
+ /** host -> editor */
36
+ export interface LookupOptionsResponse {
37
+ requestId: string;
38
+ options: LookupOption[];
39
+ }
40
+ export declare const LOOKUP_OPTIONS_REQUEST_EVENT = "dte-unlayer:lookup-options-request";
41
+ export declare const LOOKUP_OPTIONS_RESPONSE_EVENT = "dte-unlayer:lookup-options-response";
42
+ /**
43
+ * Searchable sources require the host to query an API per typed query. These are
44
+ * skipped for now and fall back to the free-text input.
45
+ */
46
+ export declare const SEARCHABLE_LOOKUP_SOURCES: readonly ["city"];
47
+ export declare const isSearchableLookupSource: (sourceKey: string | undefined) => boolean;
48
+ /** editor -> host: ask the host to resolve options for a lookup source. */
49
+ export declare const emitLookupOptionsRequest: (request: LookupOptionsRequest) => void;
50
+ /** host side: listen for editor requests. Returns an unsubscribe function. */
51
+ export declare const onLookupOptionsRequest: (listener: (request: LookupOptionsRequest) => void) => () => void;
52
+ /** host -> editor: deliver resolved options back to the editor. */
53
+ export declare const emitLookupOptionsResponse: (response: LookupOptionsResponse) => void;
54
+ /** editor side: listen for host responses. Returns an unsubscribe function. */
55
+ export declare const onLookupOptionsResponse: (listener: (response: LookupOptionsResponse) => void) => () => void;
56
+ //# sourceMappingURL=lookupOptionsController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookupOptionsController.d.ts","sourceRoot":"","sources":["../../src/display-conditions/lookupOptionsController.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,wDAAwD;AACxD,MAAM,WAAW,YAAY;IACzB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;CACf;AAED,qBAAqB;AACrB,MAAM,WAAW,oBAAoB;IACjC,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAqB;AACrB,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,4BAA4B,uCAAuC,CAAC;AACjF,eAAO,MAAM,6BAA6B,wCAAwC,CAAC;AAEnF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,mBAAoB,CAAC;AAE3D,eAAO,MAAM,wBAAwB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,OACc,CAAC;AAIxF,2EAA2E;AAC3E,eAAO,MAAM,wBAAwB,GAAI,SAAS,oBAAoB,SAIrE,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,GAAI,UAAU,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,eAcvF,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,yBAAyB,GAAI,UAAU,qBAAqB,SAIxE,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,GAAI,UAAU,CAAC,QAAQ,EAAE,qBAAqB,KAAK,IAAI,eAc1F,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Host <-> editor contract for "data-model lookup source" dropdown options.
3
+ *
4
+ * A schema field can be tagged (in the template-type editor) with
5
+ * `options.fieldLookupSource`. When such a field is selected in the conditional
6
+ * UI, the editor does NOT own the option data — it asks the HOST for it over the
7
+ * EventTarget bus below and renders whatever the host returns.
8
+ *
9
+ * Transport mirrors the existing `displayConditionController` EventBus pattern.
10
+ *
11
+ * Event names (host <-> editor):
12
+ * - "dte-unlayer:lookup-options-request" (editor -> host)
13
+ * - "dte-unlayer:lookup-options-response" (host -> editor)
14
+ *
15
+ * The host-side resolver is intentionally NOT implemented in this package. The
16
+ * host listens via `onLookupOptionsRequest` and replies via
17
+ * `emitLookupOptionsResponse`.
18
+ */ /** The single dropdown option shape used end-to-end. */ export const LOOKUP_OPTIONS_REQUEST_EVENT = 'dte-unlayer:lookup-options-request';
19
+ export const LOOKUP_OPTIONS_RESPONSE_EVENT = 'dte-unlayer:lookup-options-response';
20
+ /**
21
+ * Searchable sources require the host to query an API per typed query. These are
22
+ * skipped for now and fall back to the free-text input.
23
+ */ export const SEARCHABLE_LOOKUP_SOURCES = [
24
+ 'city'
25
+ ];
26
+ export const isSearchableLookupSource = (sourceKey)=>!!sourceKey && SEARCHABLE_LOOKUP_SOURCES.includes(sourceKey);
27
+ const lookupOptionsEvents = new EventTarget();
28
+ /** editor -> host: ask the host to resolve options for a lookup source. */ export const emitLookupOptionsRequest = (request)=>{
29
+ lookupOptionsEvents.dispatchEvent(new CustomEvent(LOOKUP_OPTIONS_REQUEST_EVENT, {
30
+ detail: request
31
+ }));
32
+ };
33
+ /** host side: listen for editor requests. Returns an unsubscribe function. */ export const onLookupOptionsRequest = (listener)=>{
34
+ const handler = (event)=>{
35
+ const detail = event.detail;
36
+ if (!detail) {
37
+ return;
38
+ }
39
+ listener(detail);
40
+ };
41
+ lookupOptionsEvents.addEventListener(LOOKUP_OPTIONS_REQUEST_EVENT, handler);
42
+ return ()=>{
43
+ lookupOptionsEvents.removeEventListener(LOOKUP_OPTIONS_REQUEST_EVENT, handler);
44
+ };
45
+ };
46
+ /** host -> editor: deliver resolved options back to the editor. */ export const emitLookupOptionsResponse = (response)=>{
47
+ lookupOptionsEvents.dispatchEvent(new CustomEvent(LOOKUP_OPTIONS_RESPONSE_EVENT, {
48
+ detail: response
49
+ }));
50
+ };
51
+ /** editor side: listen for host responses. Returns an unsubscribe function. */ export const onLookupOptionsResponse = (listener)=>{
52
+ const handler = (event)=>{
53
+ const detail = event.detail;
54
+ if (!detail) {
55
+ return;
56
+ }
57
+ listener(detail);
58
+ };
59
+ lookupOptionsEvents.addEventListener(LOOKUP_OPTIONS_RESPONSE_EVENT, handler);
60
+ return ()=>{
61
+ lookupOptionsEvents.removeEventListener(LOOKUP_OPTIONS_RESPONSE_EVENT, handler);
62
+ };
63
+ };
64
+
65
+ //# sourceMappingURL=lookupOptionsController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/display-conditions/lookupOptionsController.ts"],"sourcesContent":["/**\n * Host <-> editor contract for \"data-model lookup source\" dropdown options.\n *\n * A schema field can be tagged (in the template-type editor) with\n * `options.fieldLookupSource`. When such a field is selected in the conditional\n * UI, the editor does NOT own the option data — it asks the HOST for it over the\n * EventTarget bus below and renders whatever the host returns.\n *\n * Transport mirrors the existing `displayConditionController` EventBus pattern.\n *\n * Event names (host <-> editor):\n * - \"dte-unlayer:lookup-options-request\" (editor -> host)\n * - \"dte-unlayer:lookup-options-response\" (host -> editor)\n *\n * The host-side resolver is intentionally NOT implemented in this package. The\n * host listens via `onLookupOptionsRequest` and replies via\n * `emitLookupOptionsResponse`.\n */\n\n/** The single dropdown option shape used end-to-end. */\nexport interface LookupOption {\n /** Human-readable label shown in the dropdown. */\n label: string;\n /** Stable value baked into the stored condition. */\n key: string;\n}\n\n/** editor -> host */\nexport interface LookupOptionsRequest {\n /** Correlates a response to its originating request. */\n requestId: string;\n /** The selected field's `options.fieldLookupSource`. */\n sourceKey: string;\n /** Reserved for searchable sources (e.g. \"city\"); omitted for now. */\n query?: string;\n}\n\n/** host -> editor */\nexport interface LookupOptionsResponse {\n requestId: string;\n options: LookupOption[];\n}\n\nexport const LOOKUP_OPTIONS_REQUEST_EVENT = 'dte-unlayer:lookup-options-request';\nexport const LOOKUP_OPTIONS_RESPONSE_EVENT = 'dte-unlayer:lookup-options-response';\n\n/**\n * Searchable sources require the host to query an API per typed query. These are\n * skipped for now and fall back to the free-text input.\n */\nexport const SEARCHABLE_LOOKUP_SOURCES = ['city'] as const;\n\nexport const isSearchableLookupSource = (sourceKey: string | undefined): boolean =>\n !!sourceKey && (SEARCHABLE_LOOKUP_SOURCES as readonly string[]).includes(sourceKey);\n\nconst lookupOptionsEvents = new EventTarget();\n\n/** editor -> host: ask the host to resolve options for a lookup source. */\nexport const emitLookupOptionsRequest = (request: LookupOptionsRequest) => {\n lookupOptionsEvents.dispatchEvent(\n new CustomEvent<LookupOptionsRequest>(LOOKUP_OPTIONS_REQUEST_EVENT, { detail: request }),\n );\n};\n\n/** host side: listen for editor requests. Returns an unsubscribe function. */\nexport const onLookupOptionsRequest = (listener: (request: LookupOptionsRequest) => void) => {\n const handler = (event: Event) => {\n const detail = (event as CustomEvent<LookupOptionsRequest>).detail;\n if (!detail) {\n return;\n }\n listener(detail);\n };\n\n lookupOptionsEvents.addEventListener(LOOKUP_OPTIONS_REQUEST_EVENT, handler);\n\n return () => {\n lookupOptionsEvents.removeEventListener(LOOKUP_OPTIONS_REQUEST_EVENT, handler);\n };\n};\n\n/** host -> editor: deliver resolved options back to the editor. */\nexport const emitLookupOptionsResponse = (response: LookupOptionsResponse) => {\n lookupOptionsEvents.dispatchEvent(\n new CustomEvent<LookupOptionsResponse>(LOOKUP_OPTIONS_RESPONSE_EVENT, { detail: response }),\n );\n};\n\n/** editor side: listen for host responses. Returns an unsubscribe function. */\nexport const onLookupOptionsResponse = (listener: (response: LookupOptionsResponse) => void) => {\n const handler = (event: Event) => {\n const detail = (event as CustomEvent<LookupOptionsResponse>).detail;\n if (!detail) {\n return;\n }\n listener(detail);\n };\n\n lookupOptionsEvents.addEventListener(LOOKUP_OPTIONS_RESPONSE_EVENT, handler);\n\n return () => {\n lookupOptionsEvents.removeEventListener(LOOKUP_OPTIONS_RESPONSE_EVENT, handler);\n };\n};\n"],"names":["LOOKUP_OPTIONS_REQUEST_EVENT","LOOKUP_OPTIONS_RESPONSE_EVENT","SEARCHABLE_LOOKUP_SOURCES","isSearchableLookupSource","sourceKey","includes","lookupOptionsEvents","EventTarget","emitLookupOptionsRequest","request","dispatchEvent","CustomEvent","detail","onLookupOptionsRequest","listener","handler","event","addEventListener","removeEventListener","emitLookupOptionsResponse","response","onLookupOptionsResponse"],"mappings":"AAAA;;;;;;;;;;;;;;;;;CAiBC,GAED,sDAAsD,GAwBtD,OAAO,MAAMA,+BAA+B,qCAAqC;AACjF,OAAO,MAAMC,gCAAgC,sCAAsC;AAEnF;;;CAGC,GACD,OAAO,MAAMC,4BAA4B;IAAC;CAAO,CAAU;AAE3D,OAAO,MAAMC,2BAA2B,CAACC,YACrC,CAAC,CAACA,aAAa,AAACF,0BAAgDG,QAAQ,CAACD,WAAW;AAExF,MAAME,sBAAsB,IAAIC;AAEhC,yEAAyE,GACzE,OAAO,MAAMC,2BAA2B,CAACC;IACrCH,oBAAoBI,aAAa,CAC7B,IAAIC,YAAkCX,8BAA8B;QAAEY,QAAQH;IAAQ;AAE9F,EAAE;AAEF,4EAA4E,GAC5E,OAAO,MAAMI,yBAAyB,CAACC;IACnC,MAAMC,UAAU,CAACC;QACb,MAAMJ,SAAS,AAACI,MAA4CJ,MAAM;QAClE,IAAI,CAACA,QAAQ;YACT;QACJ;QACAE,SAASF;IACb;IAEAN,oBAAoBW,gBAAgB,CAACjB,8BAA8Be;IAEnE,OAAO;QACHT,oBAAoBY,mBAAmB,CAAClB,8BAA8Be;IAC1E;AACJ,EAAE;AAEF,iEAAiE,GACjE,OAAO,MAAMI,4BAA4B,CAACC;IACtCd,oBAAoBI,aAAa,CAC7B,IAAIC,YAAmCV,+BAA+B;QAAEW,QAAQQ;IAAS;AAEjG,EAAE;AAEF,6EAA6E,GAC7E,OAAO,MAAMC,0BAA0B,CAACP;IACpC,MAAMC,UAAU,CAACC;QACb,MAAMJ,SAAS,AAACI,MAA6CJ,MAAM;QACnE,IAAI,CAACA,QAAQ;YACT;QACJ;QACAE,SAASF;IACb;IAEAN,oBAAoBW,gBAAgB,CAAChB,+BAA+Bc;IAEpE,OAAO;QACHT,oBAAoBY,mBAAmB,CAACjB,+BAA+Bc;IAC3E;AACJ,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"schemaDataPoints.d.ts","sourceRoot":"","sources":["../../src/display-conditions/schemaDataPoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,YAAY,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAQ1C,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,eAAe,EAAE,CAqB7F"}
1
+ {"version":3,"file":"schemaDataPoints.d.ts","sourceRoot":"","sources":["../../src/display-conditions/schemaDataPoints.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,YAAY,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAQ1C,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,eAAe,EAAE,CAwB7F"}
@@ -9,11 +9,16 @@ export function getSchemaDataPointOptions(schema) {
9
9
  }
10
10
  const { map } = schemaBuildMap(schema);
11
11
  return Object.values(map).filter((f)=>f.isValue && !f.isArrayed && isUseInConditionals(f.node)).map((f)=>{
12
+ var _f_node_options;
12
13
  const fieldType = schemaIsNumber(f.node) ? 'number' : 'string';
14
+ const lookupSource = (_f_node_options = f.node.options) === null || _f_node_options === void 0 ? void 0 : _f_node_options.fieldLookupSource;
13
15
  return {
14
16
  fieldType,
15
17
  fullKey: f.fullKey,
16
- title: f.fullTitle || f.title || f.fullKey
18
+ title: f.fullTitle || f.title || f.fullKey,
19
+ ...lookupSource ? {
20
+ lookupSource
21
+ } : {}
17
22
  };
18
23
  }).sort((a, b)=>a.title.localeCompare(b.title));
19
24
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/display-conditions/schemaDataPoints.ts"],"sourcesContent":["import {\n SchemaFieldBaseOptions,\n SchemaObject,\n schemaBuildMap,\n schemaIsNumber,\n} from '../shared/schema';\nimport { DataPointOption } from './types';\n\nfunction isUseInConditionals(node: { options?: SchemaFieldBaseOptions } | undefined): boolean {\n return (\n node?.options?.useInConditionals === true || node?.options?.useInCalculatedFields === true\n );\n}\n\nexport function getSchemaDataPointOptions(schema: SchemaObject | undefined): DataPointOption[] {\n if (!schema?.properties) {\n return [];\n }\n const { map } = schemaBuildMap(schema);\n return Object.values(map)\n .filter(\n f =>\n f.isValue &&\n !f.isArrayed &&\n isUseInConditionals(f.node as { options?: SchemaFieldBaseOptions }),\n )\n .map(f => {\n const fieldType = schemaIsNumber(f.node) ? ('number' as const) : ('string' as const);\n return {\n fieldType,\n fullKey: f.fullKey,\n title: f.fullTitle || f.title || f.fullKey,\n };\n })\n .sort((a, b) => a.title.localeCompare(b.title));\n}\n"],"names":["schemaBuildMap","schemaIsNumber","isUseInConditionals","node","options","useInConditionals","useInCalculatedFields","getSchemaDataPointOptions","schema","properties","map","Object","values","filter","f","isValue","isArrayed","fieldType","fullKey","title","fullTitle","sort","a","b","localeCompare"],"mappings":"AAAA,SAGIA,cAAc,EACdC,cAAc,QACX,mBAAmB;AAG1B,SAASC,oBAAoBC,IAAsD;QAE3EA,eAA6CA;IADjD,OACIA,CAAAA,iBAAAA,4BAAAA,gBAAAA,KAAMC,OAAO,cAAbD,oCAAAA,cAAeE,iBAAiB,MAAK,QAAQF,CAAAA,iBAAAA,4BAAAA,iBAAAA,KAAMC,OAAO,cAAbD,qCAAAA,eAAeG,qBAAqB,MAAK;AAE9F;AAEA,OAAO,SAASC,0BAA0BC,MAAgC;IACtE,IAAI,EAACA,mBAAAA,6BAAAA,OAAQC,UAAU,GAAE;QACrB,OAAO,EAAE;IACb;IACA,MAAM,EAAEC,GAAG,EAAE,GAAGV,eAAeQ;IAC/B,OAAOG,OAAOC,MAAM,CAACF,KAChBG,MAAM,CACHC,CAAAA,IACIA,EAAEC,OAAO,IACT,CAACD,EAAEE,SAAS,IACZd,oBAAoBY,EAAEX,IAAI,GAEjCO,GAAG,CAACI,CAAAA;QACD,MAAMG,YAAYhB,eAAea,EAAEX,IAAI,IAAK,WAAsB;QAClE,OAAO;YACHc;YACAC,SAASJ,EAAEI,OAAO;YAClBC,OAAOL,EAAEM,SAAS,IAAIN,EAAEK,KAAK,IAAIL,EAAEI,OAAO;QAC9C;IACJ,GACCG,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEH,KAAK,CAACK,aAAa,CAACD,EAAEJ,KAAK;AACrD"}
1
+ {"version":3,"sources":["../../src/display-conditions/schemaDataPoints.ts"],"sourcesContent":["import {\n SchemaFieldBaseOptions,\n SchemaObject,\n schemaBuildMap,\n schemaIsNumber,\n} from '../shared/schema';\nimport { DataPointOption } from './types';\n\nfunction isUseInConditionals(node: { options?: SchemaFieldBaseOptions } | undefined): boolean {\n return (\n node?.options?.useInConditionals === true || node?.options?.useInCalculatedFields === true\n );\n}\n\nexport function getSchemaDataPointOptions(schema: SchemaObject | undefined): DataPointOption[] {\n if (!schema?.properties) {\n return [];\n }\n const { map } = schemaBuildMap(schema);\n return Object.values(map)\n .filter(\n f =>\n f.isValue &&\n !f.isArrayed &&\n isUseInConditionals(f.node as { options?: SchemaFieldBaseOptions }),\n )\n .map(f => {\n const fieldType = schemaIsNumber(f.node) ? ('number' as const) : ('string' as const);\n const lookupSource = (f.node as { options?: SchemaFieldBaseOptions }).options\n ?.fieldLookupSource;\n return {\n fieldType,\n fullKey: f.fullKey,\n title: f.fullTitle || f.title || f.fullKey,\n ...(lookupSource ? { lookupSource } : {}),\n };\n })\n .sort((a, b) => a.title.localeCompare(b.title));\n}\n"],"names":["schemaBuildMap","schemaIsNumber","isUseInConditionals","node","options","useInConditionals","useInCalculatedFields","getSchemaDataPointOptions","schema","properties","map","Object","values","filter","f","isValue","isArrayed","fieldType","lookupSource","fieldLookupSource","fullKey","title","fullTitle","sort","a","b","localeCompare"],"mappings":"AAAA,SAGIA,cAAc,EACdC,cAAc,QACX,mBAAmB;AAG1B,SAASC,oBAAoBC,IAAsD;QAE3EA,eAA6CA;IADjD,OACIA,CAAAA,iBAAAA,4BAAAA,gBAAAA,KAAMC,OAAO,cAAbD,oCAAAA,cAAeE,iBAAiB,MAAK,QAAQF,CAAAA,iBAAAA,4BAAAA,iBAAAA,KAAMC,OAAO,cAAbD,qCAAAA,eAAeG,qBAAqB,MAAK;AAE9F;AAEA,OAAO,SAASC,0BAA0BC,MAAgC;IACtE,IAAI,EAACA,mBAAAA,6BAAAA,OAAQC,UAAU,GAAE;QACrB,OAAO,EAAE;IACb;IACA,MAAM,EAAEC,GAAG,EAAE,GAAGV,eAAeQ;IAC/B,OAAOG,OAAOC,MAAM,CAACF,KAChBG,MAAM,CACHC,CAAAA,IACIA,EAAEC,OAAO,IACT,CAACD,EAAEE,SAAS,IACZd,oBAAoBY,EAAEX,IAAI,GAEjCO,GAAG,CAACI,CAAAA;YAEoB;QADrB,MAAMG,YAAYhB,eAAea,EAAEX,IAAI,IAAK,WAAsB;QAClE,MAAMe,gBAAe,kBAAA,AAACJ,EAAEX,IAAI,CAA0CC,OAAO,cAAxD,sCAAA,gBACfe,iBAAiB;QACvB,OAAO;YACHF;YACAG,SAASN,EAAEM,OAAO;YAClBC,OAAOP,EAAEQ,SAAS,IAAIR,EAAEO,KAAK,IAAIP,EAAEM,OAAO;YAC1C,GAAIF,eAAe;gBAAEA;YAAa,IAAI,CAAC,CAAC;QAC5C;IACJ,GACCK,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEH,KAAK,CAACK,aAAa,CAACD,EAAEJ,KAAK;AACrD"}
@@ -118,6 +118,8 @@ export interface DataPointOption {
118
118
  fieldType: 'number' | 'string';
119
119
  fullKey: string;
120
120
  title: string;
121
+ /** When set, the value input becomes a host-resolved dropdown (see `options.fieldLookupSource`). */
122
+ lookupSource?: string;
121
123
  }
122
124
  export interface FormFieldOption extends DataPointOption {
123
125
  formId: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/display-conditions/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,IAAI,CAAC;AAE3C,wCAAwC;AACxC,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;EASnB,CAAC;AAEX,wCAAwC;AACxC,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;EAOnB,CAAC;AAEX,mDAAmD;AACnD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAsD,CAAC;AAEvF,MAAM,MAAM,iBAAiB,GACvB,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAC1C,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AAEjD,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,EAAiC,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,EAAE,eAAe,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACpD,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/display-conditions/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,IAAI,CAAC;AAE3C,wCAAwC;AACxC,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;EASnB,CAAC;AAEX,wCAAwC;AACxC,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;EAOnB,CAAC;AAEX,mDAAmD;AACnD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAsD,CAAC;AAEvF,MAAM,MAAM,iBAAiB,GACvB,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,GAC1C,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AAEjD,iEAAiE;AACjE,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,EAAiC,CAAC;AAEtF,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,EAAE,eAAe,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,oGAAoG;IACpG,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACpD,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,uDAAuD;AACvD,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACjB"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/display-conditions/types.ts"],"sourcesContent":["/**\n * Display condition types for the modal and Nunjucks generation.\n */\n\nexport type DisplayBehavior = 'show' | 'hide';\n\nexport type LogicalOperator = 'and' | 'or';\n\n/** Operators for string-type fields. */\nexport const STRING_OPERATORS = [\n { label: 'Contains', value: 'contains' },\n { label: 'Does not contain', value: 'does_not_contain' },\n { label: 'Is equal to', value: 'is_equal_to' },\n { label: 'Is not equal to', value: 'is_not_equal_to' },\n { label: 'Starts with', value: 'starts_with' },\n { label: 'Ends with', value: 'ends_with' },\n { label: 'Is empty', value: 'is_empty' },\n { label: 'Is not empty', value: 'is_not_empty' },\n] as const;\n\n/** Operators for number-type fields. */\nexport const NUMBER_OPERATORS = [\n { label: '== (equal to)', value: 'num_eq' },\n { label: '!= (not equal to)', value: 'num_neq' },\n { label: '> (greater than)', value: 'num_gt' },\n { label: '< (less than)', value: 'num_lt' },\n { label: '>= (greater than or equal to)', value: 'num_gte' },\n { label: '<= (less than or equal to)', value: 'num_lte' },\n] as const;\n\n/** All operators combined for type definitions. */\nexport const CONDITION_OPERATORS = [...STRING_OPERATORS, ...NUMBER_OPERATORS] as const;\n\nexport type ConditionOperator =\n | (typeof STRING_OPERATORS)[number]['value']\n | (typeof NUMBER_OPERATORS)[number]['value'];\n\n/** Operators that do not require a value (empty/is not empty) */\nexport const VALUE_LESS_OPERATORS: ConditionOperator[] = ['is_empty', 'is_not_empty'];\n\nexport interface SingleCondition {\n id: string;\n dataPointKey: string;\n /** How this condition connects to the PREVIOUS condition. Absent on the first condition. */\n logicalOperator?: LogicalOperator;\n operator: ConditionOperator;\n value: string;\n}\n\nexport interface ConditionGroup {\n id: string;\n /** How this group connects to the PREVIOUS group. Absent on the first group. */\n logicalOperator?: LogicalOperator;\n conditions: SingleCondition[];\n}\n\nexport interface DisplayConditionState {\n behavior: DisplayBehavior;\n groups: ConditionGroup[];\n}\n\nexport interface DataPointOption {\n fieldType: 'number' | 'string';\n fullKey: string;\n title: string;\n}\n\nexport interface FormFieldOption extends DataPointOption {\n formId: number;\n}\n\n/** Unlayer display condition shape passed to done() */\nexport interface UnlayerDisplayCondition {\n type: string;\n label: string;\n description?: string;\n before: string;\n after: string;\n}\n"],"names":["STRING_OPERATORS","label","value","NUMBER_OPERATORS","CONDITION_OPERATORS","VALUE_LESS_OPERATORS"],"mappings":"AAAA;;CAEC,GAMD,sCAAsC,GACtC,OAAO,MAAMA,mBAAmB;IAC5B;QAAEC,OAAO;QAAYC,OAAO;IAAW;IACvC;QAAED,OAAO;QAAoBC,OAAO;IAAmB;IACvD;QAAED,OAAO;QAAeC,OAAO;IAAc;IAC7C;QAAED,OAAO;QAAmBC,OAAO;IAAkB;IACrD;QAAED,OAAO;QAAeC,OAAO;IAAc;IAC7C;QAAED,OAAO;QAAaC,OAAO;IAAY;IACzC;QAAED,OAAO;QAAYC,OAAO;IAAW;IACvC;QAAED,OAAO;QAAgBC,OAAO;IAAe;CAClD,CAAU;AAEX,sCAAsC,GACtC,OAAO,MAAMC,mBAAmB;IAC5B;QAAEF,OAAO;QAAiBC,OAAO;IAAS;IAC1C;QAAED,OAAO;QAAqBC,OAAO;IAAU;IAC/C;QAAED,OAAO;QAAoBC,OAAO;IAAS;IAC7C;QAAED,OAAO;QAAiBC,OAAO;IAAS;IAC1C;QAAED,OAAO;QAAiCC,OAAO;IAAU;IAC3D;QAAED,OAAO;QAA8BC,OAAO;IAAU;CAC3D,CAAU;AAEX,iDAAiD,GACjD,OAAO,MAAME,sBAAsB;OAAIJ;OAAqBG;CAAiB,CAAU;AAMvF,+DAA+D,GAC/D,OAAO,MAAME,uBAA4C;IAAC;IAAY;CAAe,CAAC"}
1
+ {"version":3,"sources":["../../src/display-conditions/types.ts"],"sourcesContent":["/**\n * Display condition types for the modal and Nunjucks generation.\n */\n\nexport type DisplayBehavior = 'show' | 'hide';\n\nexport type LogicalOperator = 'and' | 'or';\n\n/** Operators for string-type fields. */\nexport const STRING_OPERATORS = [\n { label: 'Contains', value: 'contains' },\n { label: 'Does not contain', value: 'does_not_contain' },\n { label: 'Is equal to', value: 'is_equal_to' },\n { label: 'Is not equal to', value: 'is_not_equal_to' },\n { label: 'Starts with', value: 'starts_with' },\n { label: 'Ends with', value: 'ends_with' },\n { label: 'Is empty', value: 'is_empty' },\n { label: 'Is not empty', value: 'is_not_empty' },\n] as const;\n\n/** Operators for number-type fields. */\nexport const NUMBER_OPERATORS = [\n { label: '== (equal to)', value: 'num_eq' },\n { label: '!= (not equal to)', value: 'num_neq' },\n { label: '> (greater than)', value: 'num_gt' },\n { label: '< (less than)', value: 'num_lt' },\n { label: '>= (greater than or equal to)', value: 'num_gte' },\n { label: '<= (less than or equal to)', value: 'num_lte' },\n] as const;\n\n/** All operators combined for type definitions. */\nexport const CONDITION_OPERATORS = [...STRING_OPERATORS, ...NUMBER_OPERATORS] as const;\n\nexport type ConditionOperator =\n | (typeof STRING_OPERATORS)[number]['value']\n | (typeof NUMBER_OPERATORS)[number]['value'];\n\n/** Operators that do not require a value (empty/is not empty) */\nexport const VALUE_LESS_OPERATORS: ConditionOperator[] = ['is_empty', 'is_not_empty'];\n\nexport interface SingleCondition {\n id: string;\n dataPointKey: string;\n /** How this condition connects to the PREVIOUS condition. Absent on the first condition. */\n logicalOperator?: LogicalOperator;\n operator: ConditionOperator;\n value: string;\n}\n\nexport interface ConditionGroup {\n id: string;\n /** How this group connects to the PREVIOUS group. Absent on the first group. */\n logicalOperator?: LogicalOperator;\n conditions: SingleCondition[];\n}\n\nexport interface DisplayConditionState {\n behavior: DisplayBehavior;\n groups: ConditionGroup[];\n}\n\nexport interface DataPointOption {\n fieldType: 'number' | 'string';\n fullKey: string;\n title: string;\n /** When set, the value input becomes a host-resolved dropdown (see `options.fieldLookupSource`). */\n lookupSource?: string;\n}\n\nexport interface FormFieldOption extends DataPointOption {\n formId: number;\n}\n\n/** Unlayer display condition shape passed to done() */\nexport interface UnlayerDisplayCondition {\n type: string;\n label: string;\n description?: string;\n before: string;\n after: string;\n}\n"],"names":["STRING_OPERATORS","label","value","NUMBER_OPERATORS","CONDITION_OPERATORS","VALUE_LESS_OPERATORS"],"mappings":"AAAA;;CAEC,GAMD,sCAAsC,GACtC,OAAO,MAAMA,mBAAmB;IAC5B;QAAEC,OAAO;QAAYC,OAAO;IAAW;IACvC;QAAED,OAAO;QAAoBC,OAAO;IAAmB;IACvD;QAAED,OAAO;QAAeC,OAAO;IAAc;IAC7C;QAAED,OAAO;QAAmBC,OAAO;IAAkB;IACrD;QAAED,OAAO;QAAeC,OAAO;IAAc;IAC7C;QAAED,OAAO;QAAaC,OAAO;IAAY;IACzC;QAAED,OAAO;QAAYC,OAAO;IAAW;IACvC;QAAED,OAAO;QAAgBC,OAAO;IAAe;CAClD,CAAU;AAEX,sCAAsC,GACtC,OAAO,MAAMC,mBAAmB;IAC5B;QAAEF,OAAO;QAAiBC,OAAO;IAAS;IAC1C;QAAED,OAAO;QAAqBC,OAAO;IAAU;IAC/C;QAAED,OAAO;QAAoBC,OAAO;IAAS;IAC7C;QAAED,OAAO;QAAiBC,OAAO;IAAS;IAC1C;QAAED,OAAO;QAAiCC,OAAO;IAAU;IAC3D;QAAED,OAAO;QAA8BC,OAAO;IAAU;CAC3D,CAAU;AAEX,iDAAiD,GACjD,OAAO,MAAME,sBAAsB;OAAIJ;OAAqBG;CAAiB,CAAU;AAMvF,+DAA+D,GAC/D,OAAO,MAAME,uBAA4C;IAAC;IAAY;CAAe,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { LookupOption } from './lookupOptionsController';
2
+ /**
3
+ * - `idle` -> no lookup source (or searchable source): use free-text input.
4
+ * - `loading` -> request in flight: show a disabled/loading value control.
5
+ * - `ready` -> host returned options: render the dropdown.
6
+ * - `fallback` -> empty/error/timeout: fall back to free-text so the user is never blocked.
7
+ */
8
+ export type LookupOptionsStatus = 'idle' | 'loading' | 'ready' | 'fallback';
9
+ interface UseLookupOptionsResult {
10
+ status: LookupOptionsStatus;
11
+ options: LookupOption[];
12
+ }
13
+ /**
14
+ * Requests host-resolved dropdown options for a field's `fieldLookupSource`.
15
+ *
16
+ * Re-runs whenever `sourceKey` changes, ignores stale responses whose requestId
17
+ * does not match the latest request, and cleans up its listener/timeout on
18
+ * unmount or source change.
19
+ */
20
+ export declare const useLookupOptions: (sourceKey: string | undefined) => UseLookupOptionsResult;
21
+ export {};
22
+ //# sourceMappingURL=useLookupOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLookupOptions.d.ts","sourceRoot":"","sources":["../../src/display-conditions/useLookupOptions.ts"],"names":[],"mappings":"AAEA,OAAO,EAGH,YAAY,EAEf,MAAM,2BAA2B,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AAK5E,UAAU,sBAAsB;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,YAAY,EAAE,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,sBAmDhE,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { generateId } from './constants';
3
+ import { emitLookupOptionsRequest, isSearchableLookupSource, onLookupOptionsResponse } from './lookupOptionsController';
4
+ /** Time to wait for the host before falling back to free-text input. */ const LOOKUP_OPTIONS_TIMEOUT_MS = 10000;
5
+ /**
6
+ * Requests host-resolved dropdown options for a field's `fieldLookupSource`.
7
+ *
8
+ * Re-runs whenever `sourceKey` changes, ignores stale responses whose requestId
9
+ * does not match the latest request, and cleans up its listener/timeout on
10
+ * unmount or source change.
11
+ */ export const useLookupOptions = (sourceKey)=>{
12
+ const [status, setStatus] = useState('idle');
13
+ const [options, setOptions] = useState([]);
14
+ const latestRequestIdRef = useRef(null);
15
+ useEffect(()=>{
16
+ // No source, or a searchable source we intentionally skip for now.
17
+ if (!sourceKey || isSearchableLookupSource(sourceKey)) {
18
+ latestRequestIdRef.current = null;
19
+ setStatus('idle');
20
+ setOptions([]);
21
+ return;
22
+ }
23
+ const requestId = generateId();
24
+ latestRequestIdRef.current = requestId;
25
+ setStatus('loading');
26
+ setOptions([]);
27
+ const timeoutId = setTimeout(()=>{
28
+ if (latestRequestIdRef.current !== requestId) {
29
+ return;
30
+ }
31
+ setStatus('fallback');
32
+ setOptions([]);
33
+ }, LOOKUP_OPTIONS_TIMEOUT_MS);
34
+ const unsubscribe = onLookupOptionsResponse((response)=>{
35
+ // Ignore stale responses (different request, or superseded selection).
36
+ if (response.requestId !== requestId || latestRequestIdRef.current !== requestId) {
37
+ return;
38
+ }
39
+ clearTimeout(timeoutId);
40
+ if (response.options.length > 0) {
41
+ setOptions(response.options);
42
+ setStatus('ready');
43
+ } else {
44
+ setOptions([]);
45
+ setStatus('fallback');
46
+ }
47
+ });
48
+ emitLookupOptionsRequest({
49
+ requestId,
50
+ sourceKey
51
+ });
52
+ return ()=>{
53
+ clearTimeout(timeoutId);
54
+ unsubscribe();
55
+ };
56
+ }, [
57
+ sourceKey
58
+ ]);
59
+ return {
60
+ status,
61
+ options
62
+ };
63
+ };
64
+
65
+ //# sourceMappingURL=useLookupOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/display-conditions/useLookupOptions.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { generateId } from './constants';\nimport {\n emitLookupOptionsRequest,\n isSearchableLookupSource,\n LookupOption,\n onLookupOptionsResponse,\n} from './lookupOptionsController';\n\n/**\n * - `idle` -> no lookup source (or searchable source): use free-text input.\n * - `loading` -> request in flight: show a disabled/loading value control.\n * - `ready` -> host returned options: render the dropdown.\n * - `fallback` -> empty/error/timeout: fall back to free-text so the user is never blocked.\n */\nexport type LookupOptionsStatus = 'idle' | 'loading' | 'ready' | 'fallback';\n\n/** Time to wait for the host before falling back to free-text input. */\nconst LOOKUP_OPTIONS_TIMEOUT_MS = 10_000;\n\ninterface UseLookupOptionsResult {\n status: LookupOptionsStatus;\n options: LookupOption[];\n}\n\n/**\n * Requests host-resolved dropdown options for a field's `fieldLookupSource`.\n *\n * Re-runs whenever `sourceKey` changes, ignores stale responses whose requestId\n * does not match the latest request, and cleans up its listener/timeout on\n * unmount or source change.\n */\nexport const useLookupOptions = (sourceKey: string | undefined): UseLookupOptionsResult => {\n const [status, setStatus] = useState<LookupOptionsStatus>('idle');\n const [options, setOptions] = useState<LookupOption[]>([]);\n const latestRequestIdRef = useRef<string | null>(null);\n\n useEffect(() => {\n // No source, or a searchable source we intentionally skip for now.\n if (!sourceKey || isSearchableLookupSource(sourceKey)) {\n latestRequestIdRef.current = null;\n setStatus('idle');\n setOptions([]);\n return;\n }\n\n const requestId = generateId();\n latestRequestIdRef.current = requestId;\n setStatus('loading');\n setOptions([]);\n\n const timeoutId = setTimeout(() => {\n if (latestRequestIdRef.current !== requestId) {\n return;\n }\n setStatus('fallback');\n setOptions([]);\n }, LOOKUP_OPTIONS_TIMEOUT_MS);\n\n const unsubscribe = onLookupOptionsResponse(response => {\n // Ignore stale responses (different request, or superseded selection).\n if (response.requestId !== requestId || latestRequestIdRef.current !== requestId) {\n return;\n }\n clearTimeout(timeoutId);\n if (response.options.length > 0) {\n setOptions(response.options);\n setStatus('ready');\n } else {\n setOptions([]);\n setStatus('fallback');\n }\n });\n\n emitLookupOptionsRequest({ requestId, sourceKey });\n\n return () => {\n clearTimeout(timeoutId);\n unsubscribe();\n };\n }, [sourceKey]);\n\n return { status, options };\n};\n"],"names":["useEffect","useRef","useState","generateId","emitLookupOptionsRequest","isSearchableLookupSource","onLookupOptionsResponse","LOOKUP_OPTIONS_TIMEOUT_MS","useLookupOptions","sourceKey","status","setStatus","options","setOptions","latestRequestIdRef","current","requestId","timeoutId","setTimeout","unsubscribe","response","clearTimeout","length"],"mappings":"AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AACpD,SAASC,UAAU,QAAQ,cAAc;AACzC,SACIC,wBAAwB,EACxBC,wBAAwB,EAExBC,uBAAuB,QACpB,4BAA4B;AAUnC,sEAAsE,GACtE,MAAMC,4BAA4B;AAOlC;;;;;;CAMC,GACD,OAAO,MAAMC,mBAAmB,CAACC;IAC7B,MAAM,CAACC,QAAQC,UAAU,GAAGT,SAA8B;IAC1D,MAAM,CAACU,SAASC,WAAW,GAAGX,SAAyB,EAAE;IACzD,MAAMY,qBAAqBb,OAAsB;IAEjDD,UAAU;QACN,mEAAmE;QACnE,IAAI,CAACS,aAAaJ,yBAAyBI,YAAY;YACnDK,mBAAmBC,OAAO,GAAG;YAC7BJ,UAAU;YACVE,WAAW,EAAE;YACb;QACJ;QAEA,MAAMG,YAAYb;QAClBW,mBAAmBC,OAAO,GAAGC;QAC7BL,UAAU;QACVE,WAAW,EAAE;QAEb,MAAMI,YAAYC,WAAW;YACzB,IAAIJ,mBAAmBC,OAAO,KAAKC,WAAW;gBAC1C;YACJ;YACAL,UAAU;YACVE,WAAW,EAAE;QACjB,GAAGN;QAEH,MAAMY,cAAcb,wBAAwBc,CAAAA;YACxC,uEAAuE;YACvE,IAAIA,SAASJ,SAAS,KAAKA,aAAaF,mBAAmBC,OAAO,KAAKC,WAAW;gBAC9E;YACJ;YACAK,aAAaJ;YACb,IAAIG,SAASR,OAAO,CAACU,MAAM,GAAG,GAAG;gBAC7BT,WAAWO,SAASR,OAAO;gBAC3BD,UAAU;YACd,OAAO;gBACHE,WAAW,EAAE;gBACbF,UAAU;YACd;QACJ;QAEAP,yBAAyB;YAAEY;YAAWP;QAAU;QAEhD,OAAO;YACHY,aAAaJ;YACbE;QACJ;IACJ,GAAG;QAACV;KAAU;IAEd,OAAO;QAAEC;QAAQE;IAAQ;AAC7B,EAAE"}
package/dist/index.d.ts CHANGED
@@ -12,4 +12,5 @@ export { UnlayerDesignChangeInfo } from './store';
12
12
  export { versions } from './unlayer';
13
13
  export * from './shared/date-calc';
14
14
  export { emptyArrayIfNull } from './shared/nunjucks-helpers';
15
+ export * from './display-conditions/lookupOptionsController';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,cAAc,8CAA8C,CAAC"}
package/dist/index.js CHANGED
@@ -12,5 +12,6 @@ export { UnlayerDesignChangeInfo } from './store';
12
12
  export { versions } from './unlayer';
13
13
  export * from './shared/date-calc';
14
14
  export { emptyArrayIfNull } from './shared/nunjucks-helpers';
15
+ export * from './display-conditions/lookupOptionsController';
15
16
 
16
17
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './editor';\nexport * from './tools';\nexport * from './api-core';\nexport * from './api-custom-tools';\nexport * from './unlayer-interface';\nexport * from './loadScript';\nexport * from './shared/const';\nexport * from './shared/forms';\nexport * from './shared/schema';\nexport * from './shared/tools';\nexport { UnlayerDesignChangeInfo } from './store';\nexport { versions } from './unlayer';\nexport * from './shared/date-calc';\nexport { emptyArrayIfNull } from './shared/nunjucks-helpers';\n"],"names":["UnlayerDesignChangeInfo","versions","emptyArrayIfNull"],"mappings":"AAAA,cAAc,WAAW;AACzB,cAAc,UAAU;AACxB,cAAc,aAAa;AAC3B,cAAc,qBAAqB;AACnC,cAAc,sBAAsB;AACpC,cAAc,eAAe;AAC7B,cAAc,iBAAiB;AAC/B,cAAc,iBAAiB;AAC/B,cAAc,kBAAkB;AAChC,cAAc,iBAAiB;AAC/B,SAASA,uBAAuB,QAAQ,UAAU;AAClD,SAASC,QAAQ,QAAQ,YAAY;AACrC,cAAc,qBAAqB;AACnC,SAASC,gBAAgB,QAAQ,4BAA4B"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './editor';\nexport * from './tools';\nexport * from './api-core';\nexport * from './api-custom-tools';\nexport * from './unlayer-interface';\nexport * from './loadScript';\nexport * from './shared/const';\nexport * from './shared/forms';\nexport * from './shared/schema';\nexport * from './shared/tools';\nexport { UnlayerDesignChangeInfo } from './store';\nexport { versions } from './unlayer';\nexport * from './shared/date-calc';\nexport { emptyArrayIfNull } from './shared/nunjucks-helpers';\nexport * from './display-conditions/lookupOptionsController';\n"],"names":["UnlayerDesignChangeInfo","versions","emptyArrayIfNull"],"mappings":"AAAA,cAAc,WAAW;AACzB,cAAc,UAAU;AACxB,cAAc,aAAa;AAC3B,cAAc,qBAAqB;AACnC,cAAc,sBAAsB;AACpC,cAAc,eAAe;AAC7B,cAAc,iBAAiB;AAC/B,cAAc,iBAAiB;AAC/B,cAAc,kBAAkB;AAChC,cAAc,iBAAiB;AAC/B,SAASA,uBAAuB,QAAQ,UAAU;AAClD,SAASC,QAAQ,QAAQ,YAAY;AACrC,cAAc,qBAAqB;AACnC,SAASC,gBAAgB,QAAQ,4BAA4B;AAC7D,cAAc,+CAA+C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/dte-unlayer",
3
- "version": "0.139.0",
3
+ "version": "0.140.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "typings": "./dist/index.d.ts",
@@ -2,8 +2,10 @@ import { Button, Chip, Combobox, Flex, TextField } from '@servicetitan/anvil2';
2
2
  import TrashIcon from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';
3
3
  import { useEffect, useMemo, useState } from 'react';
4
4
  import { FormInfo, parseFormFieldKey } from '../shared/forms';
5
+ import type { LookupOption } from './lookupOptionsController';
5
6
  import { NUMBER_OPERATORS, SingleCondition, STRING_OPERATORS, VALUE_LESS_OPERATORS } from './types';
6
7
  import type { DataPointOption, FormFieldOption } from './types';
8
+ import { useLookupOptions } from './useLookupOptions';
7
9
 
8
10
  export interface ConditionRowProps {
9
11
  canRemove: boolean;
@@ -108,6 +110,17 @@ export function ConditionRow({
108
110
 
109
111
  const fieldType = selectedDataPoint?.fieldType ?? 'string';
110
112
 
113
+ const isValueLess = VALUE_LESS_OPERATORS.includes(condition.operator);
114
+
115
+ // Value-less operators (is empty / is not empty) need no value, so skip the host lookup.
116
+ const { options: lookupOptions, status: lookupStatus } = useLookupOptions(
117
+ isValueLess ? undefined : selectedDataPoint?.lookupSource,
118
+ );
119
+ const selectedLookupOption = useMemo(
120
+ () => lookupOptions.find(opt => opt.key === condition.value) ?? null,
121
+ [lookupOptions, condition.value],
122
+ );
123
+
111
124
  const operatorItems: OperatorOption[] = useMemo(
112
125
  () =>
113
126
  fieldType === 'number'
@@ -121,13 +134,73 @@ export function ConditionRow({
121
134
  [operatorItems, condition.operator],
122
135
  );
123
136
 
124
- const isValueLess = VALUE_LESS_OPERATORS.includes(condition.operator);
125
-
126
137
  const handleValueChange = (raw: string) => {
127
138
  const value = fieldType === 'number' ? sanitizeNumericInput(raw) : raw;
128
139
  onChange({ ...condition, value });
129
140
  };
130
141
 
142
+ const renderValueControl = () => {
143
+ if (lookupStatus === 'loading') {
144
+ return (
145
+ <TextField
146
+ disabled
147
+ label="Value"
148
+ size="small"
149
+ value=""
150
+ placeholder=""
151
+ style={{ width: '100%' }}
152
+ aria-label="Value"
153
+ />
154
+ );
155
+ }
156
+
157
+ if (lookupStatus === 'ready') {
158
+ return (
159
+ <Combobox.Select
160
+ disableClearSelection
161
+ itemToKey={(item: LookupOption | null) => item?.key ?? ''}
162
+ itemToString={(item: LookupOption | null) => item?.label ?? ''}
163
+ items={lookupOptions}
164
+ selectedItem={selectedLookupOption}
165
+ onChange={(item: LookupOption | null) =>
166
+ onChange({ ...condition, value: item?.key ?? '' })
167
+ }
168
+ >
169
+ <Combobox.SelectTrigger
170
+ label="Value"
171
+ placeholder="Select value..."
172
+ size="small"
173
+ />
174
+ <Combobox.Content>
175
+ {({ items }: { items: LookupOption[] }) => (
176
+ <Combobox.List>
177
+ {items.map((item, i) => (
178
+ <Combobox.Item index={i} item={item} key={item.key}>
179
+ {item.label}
180
+ </Combobox.Item>
181
+ ))}
182
+ </Combobox.List>
183
+ )}
184
+ </Combobox.Content>
185
+ </Combobox.Select>
186
+ );
187
+ }
188
+
189
+ // idle / fallback (empty/error/timeout) -> never block the user.
190
+ return (
191
+ <TextField
192
+ label="Value"
193
+ size="small"
194
+ value={condition.value}
195
+ onChange={e => handleValueChange(e.target.value)}
196
+ placeholder={fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...'}
197
+ style={{ width: '100%' }}
198
+ aria-label="Value"
199
+ {...(fieldType === 'number' && { inputMode: 'decimal' })}
200
+ />
201
+ );
202
+ };
203
+
131
204
  const handleDataPointChange = (item: DataPointOption | null) => {
132
205
  const nextIsNumber = item?.fieldType === 'number';
133
206
  const nextOperatorItems = nextIsNumber ? NUMBER_OPERATORS : STRING_OPERATORS;
@@ -301,18 +374,7 @@ export function ConditionRow({
301
374
  </Combobox.Select>
302
375
  </div>
303
376
  {!isValueLess && (
304
- <div style={{ flex: '1 1 180px', minWidth: 150 }}>
305
- <TextField
306
- label="Value"
307
- size="small"
308
- value={condition.value}
309
- onChange={e => handleValueChange(e.target.value)}
310
- placeholder={fieldType === 'number' ? 'e.g. 1.5' : 'Enter value...'}
311
- style={{ width: '100%' }}
312
- aria-label="Value"
313
- {...(fieldType === 'number' && { inputMode: 'decimal' })}
314
- />
315
- </div>
377
+ <div style={{ flex: '1 1 180px', minWidth: 150 }}>{renderValueControl()}</div>
316
378
  )}
317
379
  {canRemove && (
318
380
  <div
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Host <-> editor contract for "data-model lookup source" dropdown options.
3
+ *
4
+ * A schema field can be tagged (in the template-type editor) with
5
+ * `options.fieldLookupSource`. When such a field is selected in the conditional
6
+ * UI, the editor does NOT own the option data — it asks the HOST for it over the
7
+ * EventTarget bus below and renders whatever the host returns.
8
+ *
9
+ * Transport mirrors the existing `displayConditionController` EventBus pattern.
10
+ *
11
+ * Event names (host <-> editor):
12
+ * - "dte-unlayer:lookup-options-request" (editor -> host)
13
+ * - "dte-unlayer:lookup-options-response" (host -> editor)
14
+ *
15
+ * The host-side resolver is intentionally NOT implemented in this package. The
16
+ * host listens via `onLookupOptionsRequest` and replies via
17
+ * `emitLookupOptionsResponse`.
18
+ */
19
+
20
+ /** The single dropdown option shape used end-to-end. */
21
+ export interface LookupOption {
22
+ /** Human-readable label shown in the dropdown. */
23
+ label: string;
24
+ /** Stable value baked into the stored condition. */
25
+ key: string;
26
+ }
27
+
28
+ /** editor -> host */
29
+ export interface LookupOptionsRequest {
30
+ /** Correlates a response to its originating request. */
31
+ requestId: string;
32
+ /** The selected field's `options.fieldLookupSource`. */
33
+ sourceKey: string;
34
+ /** Reserved for searchable sources (e.g. "city"); omitted for now. */
35
+ query?: string;
36
+ }
37
+
38
+ /** host -> editor */
39
+ export interface LookupOptionsResponse {
40
+ requestId: string;
41
+ options: LookupOption[];
42
+ }
43
+
44
+ export const LOOKUP_OPTIONS_REQUEST_EVENT = 'dte-unlayer:lookup-options-request';
45
+ export const LOOKUP_OPTIONS_RESPONSE_EVENT = 'dte-unlayer:lookup-options-response';
46
+
47
+ /**
48
+ * Searchable sources require the host to query an API per typed query. These are
49
+ * skipped for now and fall back to the free-text input.
50
+ */
51
+ export const SEARCHABLE_LOOKUP_SOURCES = ['city'] as const;
52
+
53
+ export const isSearchableLookupSource = (sourceKey: string | undefined): boolean =>
54
+ !!sourceKey && (SEARCHABLE_LOOKUP_SOURCES as readonly string[]).includes(sourceKey);
55
+
56
+ const lookupOptionsEvents = new EventTarget();
57
+
58
+ /** editor -> host: ask the host to resolve options for a lookup source. */
59
+ export const emitLookupOptionsRequest = (request: LookupOptionsRequest) => {
60
+ lookupOptionsEvents.dispatchEvent(
61
+ new CustomEvent<LookupOptionsRequest>(LOOKUP_OPTIONS_REQUEST_EVENT, { detail: request }),
62
+ );
63
+ };
64
+
65
+ /** host side: listen for editor requests. Returns an unsubscribe function. */
66
+ export const onLookupOptionsRequest = (listener: (request: LookupOptionsRequest) => void) => {
67
+ const handler = (event: Event) => {
68
+ const detail = (event as CustomEvent<LookupOptionsRequest>).detail;
69
+ if (!detail) {
70
+ return;
71
+ }
72
+ listener(detail);
73
+ };
74
+
75
+ lookupOptionsEvents.addEventListener(LOOKUP_OPTIONS_REQUEST_EVENT, handler);
76
+
77
+ return () => {
78
+ lookupOptionsEvents.removeEventListener(LOOKUP_OPTIONS_REQUEST_EVENT, handler);
79
+ };
80
+ };
81
+
82
+ /** host -> editor: deliver resolved options back to the editor. */
83
+ export const emitLookupOptionsResponse = (response: LookupOptionsResponse) => {
84
+ lookupOptionsEvents.dispatchEvent(
85
+ new CustomEvent<LookupOptionsResponse>(LOOKUP_OPTIONS_RESPONSE_EVENT, { detail: response }),
86
+ );
87
+ };
88
+
89
+ /** editor side: listen for host responses. Returns an unsubscribe function. */
90
+ export const onLookupOptionsResponse = (listener: (response: LookupOptionsResponse) => void) => {
91
+ const handler = (event: Event) => {
92
+ const detail = (event as CustomEvent<LookupOptionsResponse>).detail;
93
+ if (!detail) {
94
+ return;
95
+ }
96
+ listener(detail);
97
+ };
98
+
99
+ lookupOptionsEvents.addEventListener(LOOKUP_OPTIONS_RESPONSE_EVENT, handler);
100
+
101
+ return () => {
102
+ lookupOptionsEvents.removeEventListener(LOOKUP_OPTIONS_RESPONSE_EVENT, handler);
103
+ };
104
+ };
@@ -26,10 +26,13 @@ export function getSchemaDataPointOptions(schema: SchemaObject | undefined): Dat
26
26
  )
27
27
  .map(f => {
28
28
  const fieldType = schemaIsNumber(f.node) ? ('number' as const) : ('string' as const);
29
+ const lookupSource = (f.node as { options?: SchemaFieldBaseOptions }).options
30
+ ?.fieldLookupSource;
29
31
  return {
30
32
  fieldType,
31
33
  fullKey: f.fullKey,
32
34
  title: f.fullTitle || f.title || f.fullKey,
35
+ ...(lookupSource ? { lookupSource } : {}),
33
36
  };
34
37
  })
35
38
  .sort((a, b) => a.title.localeCompare(b.title));
@@ -63,6 +63,8 @@ export interface DataPointOption {
63
63
  fieldType: 'number' | 'string';
64
64
  fullKey: string;
65
65
  title: string;
66
+ /** When set, the value input becomes a host-resolved dropdown (see `options.fieldLookupSource`). */
67
+ lookupSource?: string;
66
68
  }
67
69
 
68
70
  export interface FormFieldOption extends DataPointOption {
@@ -0,0 +1,84 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { generateId } from './constants';
3
+ import {
4
+ emitLookupOptionsRequest,
5
+ isSearchableLookupSource,
6
+ LookupOption,
7
+ onLookupOptionsResponse,
8
+ } from './lookupOptionsController';
9
+
10
+ /**
11
+ * - `idle` -> no lookup source (or searchable source): use free-text input.
12
+ * - `loading` -> request in flight: show a disabled/loading value control.
13
+ * - `ready` -> host returned options: render the dropdown.
14
+ * - `fallback` -> empty/error/timeout: fall back to free-text so the user is never blocked.
15
+ */
16
+ export type LookupOptionsStatus = 'idle' | 'loading' | 'ready' | 'fallback';
17
+
18
+ /** Time to wait for the host before falling back to free-text input. */
19
+ const LOOKUP_OPTIONS_TIMEOUT_MS = 10_000;
20
+
21
+ interface UseLookupOptionsResult {
22
+ status: LookupOptionsStatus;
23
+ options: LookupOption[];
24
+ }
25
+
26
+ /**
27
+ * Requests host-resolved dropdown options for a field's `fieldLookupSource`.
28
+ *
29
+ * Re-runs whenever `sourceKey` changes, ignores stale responses whose requestId
30
+ * does not match the latest request, and cleans up its listener/timeout on
31
+ * unmount or source change.
32
+ */
33
+ export const useLookupOptions = (sourceKey: string | undefined): UseLookupOptionsResult => {
34
+ const [status, setStatus] = useState<LookupOptionsStatus>('idle');
35
+ const [options, setOptions] = useState<LookupOption[]>([]);
36
+ const latestRequestIdRef = useRef<string | null>(null);
37
+
38
+ useEffect(() => {
39
+ // No source, or a searchable source we intentionally skip for now.
40
+ if (!sourceKey || isSearchableLookupSource(sourceKey)) {
41
+ latestRequestIdRef.current = null;
42
+ setStatus('idle');
43
+ setOptions([]);
44
+ return;
45
+ }
46
+
47
+ const requestId = generateId();
48
+ latestRequestIdRef.current = requestId;
49
+ setStatus('loading');
50
+ setOptions([]);
51
+
52
+ const timeoutId = setTimeout(() => {
53
+ if (latestRequestIdRef.current !== requestId) {
54
+ return;
55
+ }
56
+ setStatus('fallback');
57
+ setOptions([]);
58
+ }, LOOKUP_OPTIONS_TIMEOUT_MS);
59
+
60
+ const unsubscribe = onLookupOptionsResponse(response => {
61
+ // Ignore stale responses (different request, or superseded selection).
62
+ if (response.requestId !== requestId || latestRequestIdRef.current !== requestId) {
63
+ return;
64
+ }
65
+ clearTimeout(timeoutId);
66
+ if (response.options.length > 0) {
67
+ setOptions(response.options);
68
+ setStatus('ready');
69
+ } else {
70
+ setOptions([]);
71
+ setStatus('fallback');
72
+ }
73
+ });
74
+
75
+ emitLookupOptionsRequest({ requestId, sourceKey });
76
+
77
+ return () => {
78
+ clearTimeout(timeoutId);
79
+ unsubscribe();
80
+ };
81
+ }, [sourceKey]);
82
+
83
+ return { status, options };
84
+ };
package/src/index.ts CHANGED
@@ -12,3 +12,4 @@ export { UnlayerDesignChangeInfo } from './store';
12
12
  export { versions } from './unlayer';
13
13
  export * from './shared/date-calc';
14
14
  export { emptyArrayIfNull } from './shared/nunjucks-helpers';
15
+ export * from './display-conditions/lookupOptionsController';