@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.
- package/dist/display-conditions/ConditionRow.d.ts.map +1 -1
- package/dist/display-conditions/ConditionRow.js +80 -15
- package/dist/display-conditions/ConditionRow.js.map +1 -1
- package/dist/display-conditions/lookupOptionsController.d.ts +56 -0
- package/dist/display-conditions/lookupOptionsController.d.ts.map +1 -0
- package/dist/display-conditions/lookupOptionsController.js +65 -0
- package/dist/display-conditions/lookupOptionsController.js.map +1 -0
- package/dist/display-conditions/schemaDataPoints.d.ts.map +1 -1
- package/dist/display-conditions/schemaDataPoints.js +6 -1
- package/dist/display-conditions/schemaDataPoints.js.map +1 -1
- package/dist/display-conditions/types.d.ts +2 -0
- package/dist/display-conditions/types.d.ts.map +1 -1
- package/dist/display-conditions/types.js.map +1 -1
- package/dist/display-conditions/useLookupOptions.d.ts +22 -0
- package/dist/display-conditions/useLookupOptions.d.ts.map +1 -0
- package/dist/display-conditions/useLookupOptions.js +65 -0
- package/dist/display-conditions/useLookupOptions.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/display-conditions/ConditionRow.tsx +76 -14
- package/src/display-conditions/lookupOptionsController.ts +104 -0
- package/src/display-conditions/schemaDataPoints.ts +3 -0
- package/src/display-conditions/types.ts +2 -0
- package/src/display-conditions/useLookupOptions.ts +84 -0
- 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;
|
|
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:
|
|
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,
|
|
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;
|
|
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;
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
@@ -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