@servicetitan/dte-unlayer 0.158.0 → 0.160.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 (158) hide show
  1. package/dist/api-core.js +22 -29
  2. package/dist/api-core.js.map +1 -1
  3. package/dist/api-custom-tools.js +23 -29
  4. package/dist/api-custom-tools.js.map +1 -1
  5. package/dist/business-calendar/BusinessCalendarDrawer.d.ts +19 -0
  6. package/dist/business-calendar/BusinessCalendarDrawer.d.ts.map +1 -0
  7. package/dist/business-calendar/BusinessCalendarDrawer.js +41 -0
  8. package/dist/business-calendar/BusinessCalendarDrawer.js.map +1 -0
  9. package/dist/business-calendar/business-calendar-requests.d.ts +25 -0
  10. package/dist/business-calendar/business-calendar-requests.d.ts.map +1 -0
  11. package/dist/business-calendar/business-calendar-requests.js +46 -0
  12. package/dist/business-calendar/business-calendar-requests.js.map +1 -0
  13. package/dist/business-calendar/business-calendar-state.d.ts +22 -0
  14. package/dist/business-calendar/business-calendar-state.d.ts.map +1 -0
  15. package/dist/business-calendar/business-calendar-state.js +74 -0
  16. package/dist/business-calendar/business-calendar-state.js.map +1 -0
  17. package/dist/business-calendar/useBusinessCalendarRequests.d.ts +15 -0
  18. package/dist/business-calendar/useBusinessCalendarRequests.d.ts.map +1 -0
  19. package/dist/business-calendar/useBusinessCalendarRequests.js +30 -0
  20. package/dist/business-calendar/useBusinessCalendarRequests.js.map +1 -0
  21. package/dist/display-conditions/ConditionGroup.d.ts.map +1 -1
  22. package/dist/display-conditions/ConditionGroup.js +20 -89
  23. package/dist/display-conditions/ConditionGroup.js.map +1 -1
  24. package/dist/display-conditions/ConditionGroupsSection.d.ts.map +1 -1
  25. package/dist/display-conditions/ConditionGroupsSection.js +2 -70
  26. package/dist/display-conditions/ConditionGroupsSection.js.map +1 -1
  27. package/dist/display-conditions/ConditionRow.d.ts.map +1 -1
  28. package/dist/display-conditions/ConditionRow.js +88 -212
  29. package/dist/display-conditions/ConditionRow.js.map +1 -1
  30. package/dist/display-conditions/DisplayConditionModal.d.ts.map +1 -1
  31. package/dist/display-conditions/DisplayConditionModal.js +39 -194
  32. package/dist/display-conditions/DisplayConditionModal.js.map +1 -1
  33. package/dist/display-conditions/LookupValueControl.js +42 -105
  34. package/dist/display-conditions/LookupValueControl.js.map +1 -1
  35. package/dist/display-conditions/SeparatorWithChip.js +1 -10
  36. package/dist/display-conditions/SeparatorWithChip.js.map +1 -1
  37. package/dist/display-conditions/constants.js +15 -20
  38. package/dist/display-conditions/constants.js.map +1 -1
  39. package/dist/display-conditions/displayConditionController.js +5 -8
  40. package/dist/display-conditions/displayConditionController.js.map +1 -1
  41. package/dist/display-conditions/lookupOptionsController.js +16 -16
  42. package/dist/display-conditions/lookupOptionsController.js.map +1 -1
  43. package/dist/display-conditions/nunjucks.js +98 -116
  44. package/dist/display-conditions/nunjucks.js.map +1 -1
  45. package/dist/display-conditions/schemaDataPoints.js +13 -11
  46. package/dist/display-conditions/schemaDataPoints.js.map +1 -1
  47. package/dist/display-conditions/types.js +23 -67
  48. package/dist/display-conditions/types.js.map +1 -1
  49. package/dist/display-conditions/useConditionForms.js +38 -56
  50. package/dist/display-conditions/useConditionForms.js.map +1 -1
  51. package/dist/display-conditions/useLookupOptions.js +24 -44
  52. package/dist/display-conditions/useLookupOptions.js.map +1 -1
  53. package/dist/e-sign-offset/ESignDateSignedOffsetModal.js +42 -155
  54. package/dist/e-sign-offset/ESignDateSignedOffsetModal.js.map +1 -1
  55. package/dist/e-sign-offset/ESignOffsetRuleRow.js +12 -73
  56. package/dist/e-sign-offset/ESignOffsetRuleRow.js.map +1 -1
  57. package/dist/e-sign-offset/eSignOffsetController.js +5 -8
  58. package/dist/e-sign-offset/eSignOffsetController.js.map +1 -1
  59. package/dist/editor-core-source.d.ts +2 -2
  60. package/dist/editor-core-source.d.ts.map +1 -1
  61. package/dist/editor-core-source.js +3 -3
  62. package/dist/editor-core-source.js.map +1 -1
  63. package/dist/editor-core.js +2 -2
  64. package/dist/editor-core.js.map +1 -1
  65. package/dist/editor.d.ts.map +1 -1
  66. package/dist/editor.js +56 -118
  67. package/dist/editor.js.map +1 -1
  68. package/dist/image-upload-error.js +3 -3
  69. package/dist/image-upload-error.js.map +1 -1
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +0 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/loadScript.js +7 -8
  75. package/dist/loadScript.js.map +1 -1
  76. package/dist/log.js +2 -3
  77. package/dist/log.js.map +1 -1
  78. package/dist/merge-tags.js +18 -17
  79. package/dist/merge-tags.js.map +1 -1
  80. package/dist/shared/configs.js +1 -2
  81. package/dist/shared/configs.js.map +1 -1
  82. package/dist/shared/const.d.ts +70 -0
  83. package/dist/shared/const.d.ts.map +1 -1
  84. package/dist/shared/const.js +13 -4
  85. package/dist/shared/const.js.map +1 -1
  86. package/dist/shared/date-calc.js +47 -38
  87. package/dist/shared/date-calc.js.map +1 -1
  88. package/dist/shared/e-sign-offset.js +46 -63
  89. package/dist/shared/e-sign-offset.js.map +1 -1
  90. package/dist/shared/edit-icon.js +0 -1
  91. package/dist/shared/edit-icon.js.map +1 -1
  92. package/dist/shared/fonts.js +16 -17
  93. package/dist/shared/fonts.js.map +1 -1
  94. package/dist/shared/forms.js +16 -38
  95. package/dist/shared/forms.js.map +1 -1
  96. package/dist/shared/instance-id.js +6 -9
  97. package/dist/shared/instance-id.js.map +1 -1
  98. package/dist/shared/nunjucks-helpers.js +2 -5
  99. package/dist/shared/nunjucks-helpers.js.map +1 -1
  100. package/dist/shared/schema.js +52 -96
  101. package/dist/shared/schema.js.map +1 -1
  102. package/dist/shared/tools.js +6 -13
  103. package/dist/shared/tools.js.map +1 -1
  104. package/dist/store.d.ts +15 -1
  105. package/dist/store.d.ts.map +1 -1
  106. package/dist/store.js +851 -548
  107. package/dist/store.js.map +1 -1
  108. package/dist/tools.js +133 -143
  109. package/dist/tools.js.map +1 -1
  110. package/dist/unlayer-interface.d.ts +15 -4
  111. package/dist/unlayer-interface.d.ts.map +1 -1
  112. package/dist/unlayer-interface.js +1 -2
  113. package/dist/unlayer-interface.js.map +1 -1
  114. package/dist/unlayer.js +86 -113
  115. package/dist/unlayer.js.map +1 -1
  116. package/dist/vendor/dte-shared/business-calendar-validation.utils.d.ts +25 -0
  117. package/dist/vendor/dte-shared/business-calendar-validation.utils.js +80 -0
  118. package/dist/vendor/dte-shared/components/business-calendar-section.d.ts +15 -0
  119. package/dist/vendor/dte-shared/components/business-calendar-section.js +14 -0
  120. package/dist/vendor/dte-shared/components/schedule-drawer.d.ts +18 -0
  121. package/dist/vendor/dte-shared/components/schedule-drawer.js +119 -0
  122. package/dist/vendor/dte-shared/components/schedule-picker.d.ts +8 -0
  123. package/dist/vendor/dte-shared/components/schedule-picker.js +88 -0
  124. package/dist/vendor/dte-shared/constants.d.ts +31 -0
  125. package/dist/vendor/dte-shared/constants.js +34 -0
  126. package/dist/vendor/dte-shared/field-schedule-editor-context.d.ts +31 -0
  127. package/dist/vendor/dte-shared/field-schedule-editor-context.js +110 -0
  128. package/dist/vendor/dte-shared/index.d.ts +8 -0
  129. package/dist/vendor/dte-shared/index.js +7 -0
  130. package/dist/vendor/dte-shared/schedule-bindings.utils.d.ts +22 -0
  131. package/dist/vendor/dte-shared/schedule-bindings.utils.js +31 -0
  132. package/dist/vendor/dte-shared/styles/schedule.css +19 -0
  133. package/dist/vendor/dte-shared/types.d.ts +129 -0
  134. package/dist/vendor/dte-shared/types.js +1 -0
  135. package/package.json +7 -1
  136. package/src/__tests__/business-calendar-drawer.test.tsx +183 -0
  137. package/src/__tests__/business-calendar-requests.test.ts +166 -0
  138. package/src/__tests__/business-calendar-state.test.tsx +142 -0
  139. package/src/__tests__/condition-row.test.tsx +51 -8
  140. package/src/__tests__/e-sign-offset-modal.test.tsx +13 -2
  141. package/src/__tests__/store.business-calendar.test.ts +185 -0
  142. package/src/__tests__/unlayer.e-sign-types.test.ts +30 -0
  143. package/src/business-calendar/BusinessCalendarDrawer.tsx +76 -0
  144. package/src/business-calendar/business-calendar-requests.ts +80 -0
  145. package/src/business-calendar/business-calendar-state.ts +113 -0
  146. package/src/business-calendar/useBusinessCalendarRequests.ts +49 -0
  147. package/src/display-conditions/ConditionGroup.tsx +13 -3
  148. package/src/display-conditions/ConditionGroupsSection.tsx +24 -21
  149. package/src/display-conditions/ConditionRow.tsx +75 -32
  150. package/src/display-conditions/DisplayConditionModal.tsx +18 -59
  151. package/src/e-sign-offset/ESignDateSignedOffsetModal.tsx +6 -6
  152. package/src/editor-core-source.ts +2 -2
  153. package/src/editor.tsx +15 -0
  154. package/src/index.ts +10 -0
  155. package/src/shared/const.ts +59 -0
  156. package/src/store.ts +67 -5
  157. package/src/unlayer-interface.tsx +15 -4
  158. package/src/unlayer.tsx +5 -5
@@ -36,28 +36,31 @@ export function ConditionGroupsSection({
36
36
  }
37
37
 
38
38
  return (
39
- <Flex direction="column" gap="4">
40
- <Flex direction="row" alignItems="center" gap="1" style={{ flexWrap: 'wrap' }}>
41
- <Text size="small" variant="body" style={{ fontWeight: 'bold' }}>
42
- When
43
- </Text>
44
- <SegmentedControl
45
- selected={matchType}
46
- size="small"
47
- onChange={(value: string) => onMatchTypeChange(value as MatchType)}
48
- >
49
- <SegmentedControl.Segment value="all">All</SegmentedControl.Segment>
50
- <SegmentedControl.Segment value="any">Any</SegmentedControl.Segment>
51
- </SegmentedControl>
52
- <Text size="small" variant="body" style={{ fontWeight: 'bold' }}>
53
- of the
54
- </Text>
55
- <Chip label="IF" size="small" />
56
- <Text size="small" variant="body" style={{ fontWeight: 'bold' }}>
57
- conditions are met
58
- </Text>
39
+ // Spacing matches BYOD's display-condition modal: header rows at gap 3, rules 6 below them.
40
+ <Flex direction="column" gap="6">
41
+ <Flex direction="column" gap="3">
42
+ <Flex direction="row" alignItems="center" gap="2" style={{ flexWrap: 'wrap' }}>
43
+ <Text size="medium" variant="body" style={{ fontWeight: 'bold' }}>
44
+ When
45
+ </Text>
46
+ <SegmentedControl
47
+ selected={matchType}
48
+ size="small"
49
+ onChange={(value: string) => onMatchTypeChange(value as MatchType)}
50
+ >
51
+ <SegmentedControl.Segment value="all">All</SegmentedControl.Segment>
52
+ <SegmentedControl.Segment value="any">Any</SegmentedControl.Segment>
53
+ </SegmentedControl>
54
+ <Text size="medium" variant="body" style={{ fontWeight: 'bold' }}>
55
+ of the
56
+ </Text>
57
+ <Chip label="IF" size="small" />
58
+ <Text size="medium" variant="body" style={{ fontWeight: 'bold' }}>
59
+ conditions are met
60
+ </Text>
61
+ </Flex>
62
+ {behaviorSection}
59
63
  </Flex>
60
- {behaviorSection}
61
64
  <ConditionGroup
62
65
  dataPointOptions={dataPointOptions}
63
66
  formFieldOptions={formFieldOptions}
@@ -22,12 +22,18 @@ interface OperatorOption {
22
22
  value: string;
23
23
  }
24
24
 
25
+ type SourceKind = 'schema' | 'form';
26
+
25
27
  interface SourceOption {
26
- formId?: number;
27
28
  label: string;
28
- value: string;
29
+ value: SourceKind;
29
30
  }
30
31
 
32
+ const SOURCE_KIND_LABELS: Record<SourceKind, string> = {
33
+ form: 'Form',
34
+ schema: 'Merge tags',
35
+ };
36
+
31
37
  export function ConditionRow({
32
38
  canRemove,
33
39
  condition,
@@ -42,31 +48,38 @@ export function ConditionRow({
42
48
  () => parseFormFieldKey(condition.dataPointKey),
43
49
  [condition.dataPointKey],
44
50
  );
45
- const [selectedSourceValue, setSelectedSourceValue] = useState<string>(
46
- selectedFormFieldMeta ? `form:${selectedFormFieldMeta.formId}` : 'schema',
51
+ // As in BYOD: the source is "Merge tags" or "Form", and a form source gets its own form picker.
52
+ const [sourceKind, setSourceKind] = useState<SourceKind>(
53
+ selectedFormFieldMeta ? 'form' : 'schema',
47
54
  );
55
+ const [selectedFormId, setSelectedFormId] = useState<number | undefined>(
56
+ selectedFormFieldMeta?.formId,
57
+ );
58
+
59
+ const sourceOptions = useMemo<SourceOption[]>(() => {
60
+ const kinds: SourceKind[] =
61
+ forms.length || selectedFormFieldMeta ? ['schema', 'form'] : ['schema'];
62
+
63
+ return kinds.map(value => ({ label: SOURCE_KIND_LABELS[value], value }));
64
+ }, [forms.length, selectedFormFieldMeta]);
48
65
 
49
- const sourceOptions = useMemo<SourceOption[]>(
50
- () => [
51
- { label: 'Merge tags', value: 'schema' },
52
- ...forms.map(form => ({
53
- formId: form.id,
54
- label: form.name,
55
- value: `form:${form.id}`,
56
- })),
57
- ],
66
+ const selectedSource =
67
+ sourceOptions.find(opt => opt.value === sourceKind) ?? sourceOptions[0] ?? null;
68
+
69
+ const sortedForms = useMemo(
70
+ () => [...forms].sort((a, b) => a.name.localeCompare(b.name)),
58
71
  [forms],
59
72
  );
60
73
 
61
- const selectedSource = useMemo<SourceOption | null>(() => {
62
- return (
63
- sourceOptions.find(opt => opt.value === selectedSourceValue) ?? sourceOptions[0] ?? null
64
- );
65
- }, [selectedSourceValue, sourceOptions]);
74
+ const selectedForm = useMemo(
75
+ () => sortedForms.find(form => form.id === selectedFormId) ?? null,
76
+ [selectedFormId, sortedForms],
77
+ );
66
78
 
67
79
  useEffect(() => {
68
80
  if (selectedFormFieldMeta) {
69
- setSelectedSourceValue(`form:${selectedFormFieldMeta.formId}`);
81
+ setSourceKind('form');
82
+ setSelectedFormId(selectedFormFieldMeta.formId);
70
83
  return;
71
84
  }
72
85
  if (!condition.dataPointKey) {
@@ -74,18 +87,21 @@ export function ConditionRow({
74
87
  }
75
88
  const hasSchemaMatch = dataPointOptions.some(opt => opt.fullKey === condition.dataPointKey);
76
89
  if (hasSchemaMatch) {
77
- setSelectedSourceValue('schema');
90
+ setSourceKind('schema');
78
91
  }
79
92
  }, [condition.dataPointKey, dataPointOptions, selectedFormFieldMeta]);
80
93
 
81
- const currentFormId = selectedSource?.formId;
82
- const activeDataPointOptions = useMemo(
83
- () =>
84
- currentFormId
85
- ? formFieldOptions.filter(option => option.formId === currentFormId)
86
- : dataPointOptions,
87
- [currentFormId, dataPointOptions, formFieldOptions],
88
- );
94
+ const isFormSource = selectedSource?.value === 'form';
95
+ const currentFormId = isFormSource ? selectedFormId : undefined;
96
+ const activeDataPointOptions = useMemo(() => {
97
+ if (!isFormSource) {
98
+ return dataPointOptions;
99
+ }
100
+
101
+ return currentFormId
102
+ ? formFieldOptions.filter(option => option.formId === currentFormId)
103
+ : [];
104
+ }, [currentFormId, dataPointOptions, formFieldOptions, isFormSource]);
89
105
 
90
106
  const selectedDataPoint = useMemo(
91
107
  () => activeDataPointOptions.find(opt => opt.fullKey === condition.dataPointKey) ?? null,
@@ -128,13 +144,20 @@ export function ConditionRow({
128
144
  if (!item) {
129
145
  return;
130
146
  }
131
- setSelectedSourceValue(item.value);
147
+ setSourceKind(item.value);
148
+ setSelectedFormId(undefined);
132
149
  // As in BYOD, a new source starts the condition over instead of picking a data point for it.
133
150
  onChange({ ...condition, dataPointKey: '', operator: 'is_equal_to', value: '' });
151
+ };
134
152
 
135
- if (item.formId) {
136
- onFormSelect(item.formId);
153
+ const handleFormChange = (formId: number | undefined) => {
154
+ if (formId === undefined) {
155
+ return;
137
156
  }
157
+ setSelectedFormId(formId);
158
+ // Switching forms keeps the "Form" source and its picker, and starts the condition over.
159
+ onChange({ ...condition, dataPointKey: '', operator: 'is_equal_to', value: '' });
160
+ onFormSelect(formId);
138
161
  };
139
162
 
140
163
  // Same controls and layout as BYOD's condition row (`dte-pdf-editor`'s `ConditionRow`).
@@ -142,6 +165,7 @@ export function ConditionRow({
142
165
  id: option.value,
143
166
  label: option.label,
144
167
  }));
168
+ const formSelectOptions = sortedForms.map(form => ({ id: form.id, label: form.name }));
145
169
  const dataPointSelectOptions = activeDataPointOptions.map(option => ({
146
170
  id: option.fullKey,
147
171
  label: option.title,
@@ -170,6 +194,22 @@ export function ConditionRow({
170
194
  handleSourceChange(sourceOptions.find(o => o.value === option?.id) ?? null)
171
195
  }
172
196
  />
197
+ {isFormSource && (
198
+ <SelectFieldSync
199
+ flex={1}
200
+ disableSearch
201
+ disableClearButton
202
+ label="Select Form"
203
+ placeholder="Select form..."
204
+ options={formSelectOptions}
205
+ value={
206
+ selectedForm ? { id: selectedForm.id, label: selectedForm.name } : null
207
+ }
208
+ onSelectedOptionChange={(option: { id: number; label: string } | null) =>
209
+ handleFormChange(option?.id)
210
+ }
211
+ />
212
+ )}
173
213
  </Flex>
174
214
  <Flex
175
215
  direction="row"
@@ -186,9 +226,12 @@ export function ConditionRow({
186
226
  flex={1}
187
227
  disableSearch
188
228
  disableClearButton
229
+ disabled={isFormSource && !currentFormId}
189
230
  label="Data point"
190
231
  placeholder={
191
- currentFormId ? 'Select form field...' : 'Select data point...'
232
+ isFormSource && !currentFormId
233
+ ? 'Select a form first...'
234
+ : 'Select data point...'
192
235
  }
193
236
  options={dataPointSelectOptions}
194
237
  value={
@@ -1,4 +1,4 @@
1
- import { Button, Combobox, Dialog, Flex, Text, ThemeProvider } from '@servicetitan/anvil2';
1
+ import { Button, Dialog, Flex, SegmentedControl, Text, ThemeProvider } from '@servicetitan/anvil2';
2
2
  import { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { ConditionGroupsSection, MatchType } from './ConditionGroupsSection';
@@ -23,7 +23,6 @@ const BEHAVIOR_OPTIONS = [
23
23
  { label: 'Hide', value: 'hide' },
24
24
  ] as const;
25
25
  const NUMERIC_VALUE_RE = /^-?(?:\d+\.?\d*|\.\d+)$/;
26
- type BehaviorOption = (typeof BEHAVIOR_OPTIONS)[number];
27
26
 
28
27
  function deriveMatchType(conditionState: DisplayConditionState): MatchType {
29
28
  const firstGroup = conditionState.groups[0];
@@ -129,15 +128,8 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
129
128
  });
130
129
  }, []);
131
130
 
132
- const selectedBehavior = useMemo(
133
- () => BEHAVIOR_OPTIONS.find(opt => opt.value === state.behavior) ?? BEHAVIOR_OPTIONS[0],
134
- [state.behavior],
135
- );
136
-
137
- const handleBehaviorChange = useCallback((item: BehaviorOption | null) => {
138
- if (item) {
139
- setState(prev => ({ ...prev, behavior: item.value as DisplayBehavior }));
140
- }
131
+ const handleBehaviorChange = useCallback((behavior: string) => {
132
+ setState(prev => ({ ...prev, behavior: behavior as DisplayBehavior }));
141
133
  }, []);
142
134
 
143
135
  const handleFormSelect = useCallback(
@@ -187,66 +179,33 @@ export const DisplayConditionModal = (props: DisplayConditionModalProps) => {
187
179
  <Dialog open={isOpen} onClose={handleClose} size="xlarge">
188
180
  <Dialog.Header>Conditions</Dialog.Header>
189
181
  <Dialog.Content>
182
+ {/* Spacing matches BYOD's `DisplayConditionModal` (`dte-pdf-editor`). */}
190
183
  <Flex
191
184
  direction="column"
192
- gap="4"
185
+ gap="6"
186
+ flex={1}
193
187
  style={{
194
188
  maxHeight: MODAL_CONTENT_MAX_HEIGHT,
195
189
  overflowY: 'auto',
196
- paddingRight: 8,
197
- width: '100%',
198
190
  }}
199
191
  >
200
192
  <ConditionGroupsSection
201
193
  behaviorSection={
202
- <Flex
203
- direction="row"
204
- alignItems="center"
205
- gap="2"
206
- style={{ flexWrap: 'wrap' }}
207
- >
194
+ <Flex direction="row" alignItems="center" gap="3">
208
195
  <Text size="small" subdued variant="body">
209
196
  Select to include or exclude selected template/file.
210
197
  </Text>
211
- <div style={{ width: 90 }}>
212
- <Combobox.Select
213
- disableClearSelection
214
- itemToKey={(item: BehaviorOption | null) =>
215
- item?.value ?? ''
216
- }
217
- itemToString={(item: BehaviorOption | null) =>
218
- item?.label ?? ''
219
- }
220
- items={[...BEHAVIOR_OPTIONS]}
221
- selectedItem={selectedBehavior}
222
- onChange={handleBehaviorChange}
223
- >
224
- <Combobox.SelectTrigger
225
- label=""
226
- placeholder="Select..."
227
- size="small"
228
- />
229
- <Combobox.Content>
230
- {({
231
- items,
232
- }: {
233
- items: BehaviorOption[];
234
- }) => (
235
- <Combobox.List>
236
- {items.map((item, i) => (
237
- <Combobox.Item
238
- index={i}
239
- item={item}
240
- key={item.value}
241
- >
242
- {item.label}
243
- </Combobox.Item>
244
- ))}
245
- </Combobox.List>
246
- )}
247
- </Combobox.Content>
248
- </Combobox.Select>
249
- </div>
198
+ <SegmentedControl
199
+ size="small"
200
+ selected={state.behavior}
201
+ onChange={handleBehaviorChange}
202
+ >
203
+ {BEHAVIOR_OPTIONS.map(opt => (
204
+ <SegmentedControl.Segment key={opt.value} value={opt.value}>
205
+ {opt.label}
206
+ </SegmentedControl.Segment>
207
+ ))}
208
+ </SegmentedControl>
250
209
  </Flex>
251
210
  }
252
211
  dataPointOptions={dataPointOptions}
@@ -83,21 +83,21 @@ export function ESignDateSignedOffsetModal({
83
83
  );
84
84
 
85
85
  /*
86
- * As in BYOD: "Clear Rules" shows once there is something to clear, and leaves one empty rule
87
- * that is saved as no rules until it is filled in.
86
+ * As in BYOD: "Clear Rules" shows once there is something to clear, and leaves one empty rule.
87
+ * Save stays disabled after it until a rule is filled in again.
88
88
  */
89
89
  const hasConfiguredRules = rules.some(isCompleteRule);
90
- const shouldSaveNoRules = hasClearedRules && !hasConfiguredRules;
91
- const canSave = !isFieldDataLoading && (shouldSaveNoRules || rules.every(isCompleteRule));
90
+ const isClearedWithoutRules = hasClearedRules && !hasConfiguredRules;
91
+ const canSave = !isFieldDataLoading && !isClearedWithoutRules && rules.every(isCompleteRule);
92
92
 
93
93
  const handleClose = useCallback(() => {
94
94
  setRequest(null);
95
95
  }, []);
96
96
 
97
97
  const handleSave = useCallback(() => {
98
- request?.done(buildESignOffsetSettings(shouldSaveNoRules ? [] : rules, finalDays));
98
+ request?.done(buildESignOffsetSettings(rules, finalDays));
99
99
  setRequest(null);
100
- }, [finalDays, request, rules, shouldSaveNoRules]);
100
+ }, [finalDays, request, rules]);
101
101
 
102
102
  const handleClearRules = useCallback(() => {
103
103
  setRules([createESignOffsetRule()]);