@servicetitan/dte-pdf-editor 1.94.0 → 1.96.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 (145) hide show
  1. package/README.md +121 -4
  2. package/dist/components/field-config-panel/advanced-settings.d.ts +2 -0
  3. package/dist/components/field-config-panel/advanced-settings.d.ts.map +1 -1
  4. package/dist/components/field-config-panel/advanced-settings.js +5 -4
  5. package/dist/components/field-config-panel/advanced-settings.js.map +1 -1
  6. package/dist/components/field-config-panel/e-sign-options-modal.d.ts +3 -0
  7. package/dist/components/field-config-panel/e-sign-options-modal.d.ts.map +1 -1
  8. package/dist/components/field-config-panel/e-sign-options-modal.js +6 -4
  9. package/dist/components/field-config-panel/e-sign-options-modal.js.map +1 -1
  10. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts +14 -1
  11. package/dist/components/field-config-panel/field-config-panel-overlay.d.ts.map +1 -1
  12. package/dist/components/field-config-panel/field-config-panel-overlay.js +41 -2
  13. package/dist/components/field-config-panel/field-config-panel-overlay.js.map +1 -1
  14. package/dist/components/field-config-panel/field-config-panel.d.ts +6 -0
  15. package/dist/components/field-config-panel/field-config-panel.d.ts.map +1 -1
  16. package/dist/components/field-config-panel/field-config-panel.js +17 -3
  17. package/dist/components/field-config-panel/field-config-panel.js.map +1 -1
  18. package/dist/components/field-config-panel/formula-generator.d.ts +4 -0
  19. package/dist/components/field-config-panel/formula-generator.d.ts.map +1 -1
  20. package/dist/components/field-config-panel/formula-generator.js +2 -2
  21. package/dist/components/field-config-panel/formula-generator.js.map +1 -1
  22. package/dist/components/field-config-panel/formula-modal.d.ts +5 -0
  23. package/dist/components/field-config-panel/formula-modal.d.ts.map +1 -1
  24. package/dist/components/field-config-panel/formula-modal.js +13 -6
  25. package/dist/components/field-config-panel/formula-modal.js.map +1 -1
  26. package/dist/components/field-config-panel/formula-workspace.d.ts +2 -0
  27. package/dist/components/field-config-panel/formula-workspace.d.ts.map +1 -1
  28. package/dist/components/field-config-panel/formula-workspace.js +6 -4
  29. package/dist/components/field-config-panel/formula-workspace.js.map +1 -1
  30. package/dist/components/pdf-editor/pdf-editor.d.ts +52 -1
  31. package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
  32. package/dist/components/pdf-editor/pdf-editor.js +2 -2
  33. package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
  34. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.d.ts.map +1 -1
  35. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js +4 -6
  36. package/dist/components/pdf-fields-overlay/pdf-overlay-field-fillable.js.map +1 -1
  37. package/dist/components/pdf-view/pdf-view-calculated.d.ts +3 -1
  38. package/dist/components/pdf-view/pdf-view-calculated.d.ts.map +1 -1
  39. package/dist/components/pdf-view/pdf-view-calculated.js +14 -4
  40. package/dist/components/pdf-view/pdf-view-calculated.js.map +1 -1
  41. package/dist/components/pdf-view/pdf-view-fillable.d.ts.map +1 -1
  42. package/dist/components/pdf-view/pdf-view-fillable.js +27 -4
  43. package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
  44. package/dist/components/pdf-view/pdf-view.d.ts +18 -4
  45. package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
  46. package/dist/components/pdf-view/pdf-view.js +2 -2
  47. package/dist/components/pdf-view/pdf-view.js.map +1 -1
  48. package/dist/components/shared/char-limit-indicator.d.ts +8 -0
  49. package/dist/components/shared/char-limit-indicator.d.ts.map +1 -0
  50. package/dist/components/shared/char-limit-indicator.js +9 -0
  51. package/dist/components/shared/char-limit-indicator.js.map +1 -0
  52. package/dist/components/shared/index.d.ts +1 -0
  53. package/dist/components/shared/index.d.ts.map +1 -1
  54. package/dist/components/shared/index.js +1 -0
  55. package/dist/components/shared/index.js.map +1 -1
  56. package/dist/constants/calculated.constants.d.ts +6 -0
  57. package/dist/constants/calculated.constants.d.ts.map +1 -1
  58. package/dist/constants/calculated.constants.js +6 -0
  59. package/dist/constants/calculated.constants.js.map +1 -1
  60. package/dist/hooks/usePinchZoom.d.ts.map +1 -1
  61. package/dist/hooks/usePinchZoom.js +1 -1
  62. package/dist/hooks/usePinchZoom.js.map +1 -1
  63. package/dist/interface/types.d.ts +37 -1
  64. package/dist/interface/types.d.ts.map +1 -1
  65. package/dist/interface/types.js.map +1 -1
  66. package/dist/styles/pdf-field-overlay.css +9 -2
  67. package/dist/styles/pdf-field.css +22 -1
  68. package/dist/utils/field/field-text-box.utils.d.ts +31 -0
  69. package/dist/utils/field/field-text-box.utils.d.ts.map +1 -0
  70. package/dist/utils/field/field-text-box.utils.js +102 -0
  71. package/dist/utils/field/field-text-box.utils.js.map +1 -0
  72. package/dist/utils/field/fit-field-text.utils.d.ts +13 -0
  73. package/dist/utils/field/fit-field-text.utils.d.ts.map +1 -0
  74. package/dist/utils/field/fit-field-text.utils.js +44 -0
  75. package/dist/utils/field/fit-field-text.utils.js.map +1 -0
  76. package/dist/utils/field/get-field-max-length.utils.d.ts +38 -0
  77. package/dist/utils/field/get-field-max-length.utils.d.ts.map +1 -0
  78. package/dist/utils/field/get-field-max-length.utils.js +166 -0
  79. package/dist/utils/field/get-field-max-length.utils.js.map +1 -0
  80. package/dist/utils/field/index.d.ts +2 -0
  81. package/dist/utils/field/index.d.ts.map +1 -1
  82. package/dist/utils/field/index.js +2 -0
  83. package/dist/utils/field/index.js.map +1 -1
  84. package/dist/utils/field/text-wrap.utils.d.ts +26 -0
  85. package/dist/utils/field/text-wrap.utils.d.ts.map +1 -0
  86. package/dist/utils/field/text-wrap.utils.js +53 -0
  87. package/dist/utils/field/text-wrap.utils.js.map +1 -0
  88. package/dist/utils/formula/business-days.utils.d.ts +32 -0
  89. package/dist/utils/formula/business-days.utils.d.ts.map +1 -0
  90. package/dist/utils/formula/business-days.utils.js +148 -0
  91. package/dist/utils/formula/business-days.utils.js.map +1 -0
  92. package/dist/utils/formula/caret.utils.js +1 -1
  93. package/dist/utils/formula/caret.utils.js.map +1 -1
  94. package/dist/utils/formula/evaluate-formula.utils.d.ts +25 -6
  95. package/dist/utils/formula/evaluate-formula.utils.d.ts.map +1 -1
  96. package/dist/utils/formula/evaluate-formula.utils.js +61 -101
  97. package/dist/utils/formula/evaluate-formula.utils.js.map +1 -1
  98. package/dist/utils/formula/index.d.ts +1 -0
  99. package/dist/utils/formula/index.d.ts.map +1 -1
  100. package/dist/utils/formula/index.js +1 -0
  101. package/dist/utils/formula/index.js.map +1 -1
  102. package/dist/utils/formula/schedule-bindings.utils.d.ts +12 -0
  103. package/dist/utils/formula/schedule-bindings.utils.d.ts.map +1 -0
  104. package/dist/utils/formula/schedule-bindings.utils.js +17 -0
  105. package/dist/utils/formula/schedule-bindings.utils.js.map +1 -0
  106. package/package.json +2 -1
  107. package/src/__tests__/field-types/date-fields.test.ts +29 -3
  108. package/src/__tests__/field-types/e-sign-fields.test.ts +2 -2
  109. package/src/__tests__/field-types/fillable-fields.test.ts +2 -2
  110. package/src/components/field-config-panel/advanced-settings.tsx +16 -3
  111. package/src/components/field-config-panel/e-sign-options-modal.tsx +13 -2
  112. package/src/components/field-config-panel/field-config-panel-overlay.tsx +97 -1
  113. package/src/components/field-config-panel/field-config-panel.tsx +28 -0
  114. package/src/components/field-config-panel/formula-generator.tsx +8 -1
  115. package/src/components/field-config-panel/formula-modal.tsx +19 -4
  116. package/src/components/field-config-panel/formula-workspace.tsx +31 -6
  117. package/src/components/pdf-editor/pdf-editor.tsx +82 -0
  118. package/src/components/pdf-fields-overlay/pdf-overlay-field-fillable.tsx +41 -28
  119. package/src/components/pdf-view/pdf-view-calculated.tsx +28 -3
  120. package/src/components/pdf-view/pdf-view-fillable.tsx +82 -36
  121. package/src/components/pdf-view/pdf-view.tsx +23 -3
  122. package/src/components/shared/char-limit-indicator.tsx +23 -0
  123. package/src/components/shared/index.ts +1 -0
  124. package/src/constants/calculated.constants.ts +7 -0
  125. package/src/hooks/usePinchZoom.ts +1 -6
  126. package/src/interface/types.ts +47 -0
  127. package/src/styles/pdf-field-overlay.css +9 -2
  128. package/src/styles/pdf-field.css +22 -1
  129. package/src/utils/field/__tests__/field-background-color.utils.test.ts +8 -8
  130. package/src/utils/field/__tests__/fit-field-text.utils.test.ts +224 -0
  131. package/src/utils/field/__tests__/get-field-bounds-style.utils.test.ts +16 -0
  132. package/src/utils/field/__tests__/get-field-max-length.utils.test.ts +751 -0
  133. package/src/utils/field/__tests__/text-wrap.utils.test.ts +181 -0
  134. package/src/utils/field/field-text-box.utils.ts +143 -0
  135. package/src/utils/field/fit-field-text.utils.ts +61 -0
  136. package/src/utils/field/get-field-max-length.utils.ts +211 -0
  137. package/src/utils/field/index.ts +2 -0
  138. package/src/utils/field/text-wrap.utils.ts +78 -0
  139. package/src/utils/formula/__tests__/evaluate-formula.utils.test.ts +198 -16
  140. package/src/utils/formula/__tests__/schedule-bindings.utils.test.ts +73 -0
  141. package/src/utils/formula/business-days.utils.ts +188 -0
  142. package/src/utils/formula/caret.utils.ts +1 -1
  143. package/src/utils/formula/evaluate-formula.utils.ts +94 -114
  144. package/src/utils/formula/index.ts +1 -0
  145. package/src/utils/formula/schedule-bindings.utils.ts +27 -0
@@ -1,10 +1,50 @@
1
1
  import {
2
+ type BusinessCalendar,
2
3
  type DataModelValues,
4
+ type FieldScheduleBinding,
3
5
  type FormulaOperator,
4
6
  type FormulaToken,
5
7
  type StructuredFormula,
6
8
  } from '../../interface/types';
7
9
  import { resolvePdfDataValues } from '../data-model';
10
+ import {
11
+ addBusinessDays,
12
+ addCalendarDays,
13
+ countBusinessDays,
14
+ countCalendarDays,
15
+ defaultBusinessCalendar,
16
+ } from './business-days.utils';
17
+
18
+ /**
19
+ * Which calendar a date formula uses. `enabled` is the field's custom business-day exclusion
20
+ * toggle (DTE's schedule binding): off uses plain calendar-day arithmetic (no weekend/holiday
21
+ * skipping, the pre-DTE-4695 behavior); on uses the field's own calendar (`calendar`, a field
22
+ * bound to a schedule) or the default one, Saturday and Sunday plus `holidays`.
23
+ */
24
+ export interface DateCalcOptions {
25
+ enabled?: boolean;
26
+ calendar?: BusinessCalendar;
27
+ holidays?: string[];
28
+ }
29
+
30
+ /**
31
+ * A field's options: disabled (plain calendar-day math) unless it has a binding in
32
+ * `scheduleBindings` (DTE's `fieldKey` = `PdfField.id`), in which case it uses its own calendar
33
+ * when the host passed one for its id, else the default calendar.
34
+ */
35
+ export function getFieldDateCalcOptions(
36
+ fieldId: string | undefined,
37
+ scheduleBindings: FieldScheduleBinding[] | undefined,
38
+ holidays: string[] | undefined,
39
+ fieldCalendars: Record<string, BusinessCalendar> | undefined,
40
+ ): DateCalcOptions {
41
+ const enabled = !!fieldId && !!scheduleBindings?.some(b => b.fieldKey === fieldId);
42
+ if (!enabled) {
43
+ return { enabled: false };
44
+ }
45
+ const calendar = fieldCalendars?.[fieldId!];
46
+ return calendar ? { enabled: true, calendar } : { enabled: true, holidays };
47
+ }
8
48
 
9
49
  /**
10
50
  * Convert formula operand to number. Merge tags and fillable only ever give:
@@ -199,94 +239,9 @@ function parseDateValue(raw: string): number {
199
239
  return isNaN(d.getTime()) ? NaN : d.getTime();
200
240
  }
201
241
 
202
- function toDateOnly(ms: number): Date {
203
- const d = new Date(ms);
204
- d.setUTCHours(0, 0, 0, 0);
205
- return d;
206
- }
207
-
208
- function toDateKey(d: Date): string {
209
- const y = d.getUTCFullYear();
210
- const m = String(d.getUTCMonth() + 1).padStart(2, '0');
211
- const day = String(d.getUTCDate()).padStart(2, '0');
212
- return `${y}-${m}-${day}`;
213
- }
214
-
215
- function isWeekend(d: Date): boolean {
216
- const day = d.getUTCDay();
217
- return day === 0 || day === 6;
218
- }
219
-
220
- function buildHolidaySet(holidays?: string[]): Set<string> {
221
- if (!holidays?.length) {
222
- return new Set();
223
- }
224
- const set = new Set<string>();
225
- for (const h of holidays) {
226
- const d = toDateOnly(new Date(h).getTime());
227
- if (!isNaN(d.getTime())) {
228
- set.add(toDateKey(d));
229
- }
230
- }
231
- return set;
232
- }
233
-
234
- function isNonBusinessDay(d: Date, holidaySet: Set<string>): boolean {
235
- return isWeekend(d) || (holidaySet.size > 0 && holidaySet.has(toDateKey(d)));
236
- }
237
-
238
- /**
239
- * Add business days to a date (epoch ms), skipping weekends and holidays.
240
- * Returns the resulting date as epoch ms.
241
- */
242
- function addBusinessDays(startMs: number, days: number, holidaySet: Set<string>): number {
243
- const result = toDateOnly(startMs);
244
- if (!isFinite(result.getTime())) {
245
- return result.getTime();
246
- }
247
-
248
- const intDays = Math.trunc(days);
249
- if (intDays === 0) {
250
- return result.getTime();
251
- }
252
-
253
- const direction = intDays > 0 ? 1 : -1;
254
- let remaining = Math.abs(intDays);
255
-
256
- while (remaining > 0) {
257
- result.setUTCDate(result.getUTCDate() + direction);
258
- if (!isNonBusinessDay(result, holidaySet)) {
259
- remaining--;
260
- }
261
- }
262
-
263
- return result.getTime();
264
- }
265
-
266
- /**
267
- * Count business days between two dates (epoch ms), skipping weekends and holidays.
268
- * Positive when `toMs` is after `fromMs`, negative otherwise.
269
- */
270
- function businessDayDiff(fromMs: number, toMs: number, holidaySet: Set<string>): number {
271
- const start = toDateOnly(fromMs);
272
- const end = toDateOnly(toMs);
273
-
274
- if (!isFinite(start.getTime()) || !isFinite(end.getTime())) {
275
- return 0;
276
- }
277
-
278
- const direction = end.getTime() >= start.getTime() ? 1 : -1;
279
- const cursor = new Date(start.getTime());
280
- let count = 0;
281
-
282
- while (toDateKey(cursor) !== toDateKey(end)) {
283
- cursor.setUTCDate(cursor.getUTCDate() + direction);
284
- if (!isNonBusinessDay(cursor, holidaySet)) {
285
- count++;
286
- }
287
- }
288
-
289
- return count * direction;
242
+ /** Blank date arithmetic is NaN, so it carries through to a null result like other invalid values. */
243
+ function orNaN(value: number | null): number {
244
+ return value ?? NaN;
290
245
  }
291
246
 
292
247
  function resolveDateAwareTokens(
@@ -297,8 +252,7 @@ function resolveDateAwareTokens(
297
252
  if (t.type === 'field') {
298
253
  const raw = resolvePdfDataValues(data, t.path);
299
254
  if (t.fieldType === 'date') {
300
- const ms = parseDateValue(raw);
301
- return { kind: 'date', value: isNaN(ms) ? 0 : ms };
255
+ return { kind: 'date', value: parseDateValue(raw) };
302
256
  }
303
257
  return { kind: 'number', value: valueToNumber(raw) };
304
258
  }
@@ -309,38 +263,60 @@ function resolveDateAwareTokens(
309
263
  });
310
264
  }
311
265
 
266
+ /** Date add/diff behind the field's exclusion toggle: business-day-aware when on, plain when off. */
267
+ interface DateMath {
268
+ add(ms: number, days: number): number | null;
269
+ diff(fromMs: number, toMs: number): number | null;
270
+ }
271
+
272
+ function businessDayMath(calendar: BusinessCalendar): DateMath {
273
+ return {
274
+ add: (ms, days) => addBusinessDays(ms, days, calendar),
275
+ diff: (fromMs, toMs) => countBusinessDays(fromMs, toMs, calendar),
276
+ };
277
+ }
278
+
279
+ function calendarDayMath(): DateMath {
280
+ return {
281
+ add: (ms, days) => addCalendarDays(ms, days),
282
+ diff: (fromMs, toMs) => countCalendarDays(fromMs, toMs),
283
+ };
284
+ }
285
+
312
286
  /**
313
- * Apply an operator to two typed values with date-aware, business-day semantics:
314
- * date + number → add business days → date
315
- * number + date → add business days → date
316
- * date - number → sub business days → date
317
- * date - date → business day diff → number
318
- * number ± number → normal → number
287
+ * Apply an operator to two typed values with date-aware semantics:
288
+ * date + number → add days → date
289
+ * number + date → add days → date
290
+ * date - number → sub days → date
291
+ * date - date → day diff → number (`D1 - D2` counts from D2 to D1, as DTE does)
292
+ * number ± number → normal → number
293
+ *
294
+ * Steps are applied one at a time, left to right, as DTE's `ByodFormulaEvaluator` does.
319
295
  */
320
296
  function applyDateOp(
321
297
  a: TypedValue,
322
298
  op: FormulaOperator,
323
299
  b: TypedValue,
324
- holidaySet: Set<string>,
300
+ dateMath: DateMath,
325
301
  ): TypedValue {
326
302
  if (a.kind === 'date' && b.kind === 'number') {
327
303
  if (op === '+') {
328
- return { kind: 'date', value: addBusinessDays(a.value, b.value, holidaySet) };
304
+ return { kind: 'date', value: orNaN(dateMath.add(a.value, b.value)) };
329
305
  }
330
306
  if (op === '-') {
331
- return { kind: 'date', value: addBusinessDays(a.value, -b.value, holidaySet) };
307
+ return { kind: 'date', value: orNaN(dateMath.add(a.value, -b.value)) };
332
308
  }
333
309
  }
334
310
  if (a.kind === 'number' && b.kind === 'date' && op === '+') {
335
- return { kind: 'date', value: addBusinessDays(b.value, a.value, holidaySet) };
311
+ return { kind: 'date', value: orNaN(dateMath.add(b.value, a.value)) };
336
312
  }
337
313
  if (a.kind === 'date' && b.kind === 'date' && op === '-') {
338
- return { kind: 'number', value: businessDayDiff(a.value, b.value, holidaySet) };
314
+ return { kind: 'number', value: orNaN(dateMath.diff(b.value, a.value)) };
339
315
  }
340
316
  return { kind: 'number', value: applyOp(a.value, op, b.value) };
341
317
  }
342
318
 
343
- function evalDateFlat(tokens: DateAwareToken[], holidaySet: Set<string>): TypedValue {
319
+ function evalDateFlat(tokens: DateAwareToken[], dateMath: DateMath): TypedValue {
344
320
  const list = [...tokens];
345
321
 
346
322
  for (let i = 1; i < list.length - 1; ) {
@@ -349,7 +325,7 @@ function evalDateFlat(tokens: DateAwareToken[], holidaySet: Set<string>): TypedV
349
325
  const left = list[i - 1];
350
326
  const right = list[i + 1];
351
327
  if (isTypedValue(left) && isTypedValue(right)) {
352
- const result = applyDateOp(left, t.value, right, holidaySet);
328
+ const result = applyDateOp(left, t.value, right, dateMath);
353
329
  list.splice(i - 1, 3, result);
354
330
  continue;
355
331
  }
@@ -361,7 +337,7 @@ function evalDateFlat(tokens: DateAwareToken[], holidaySet: Set<string>): TypedV
361
337
  return isTypedValue(single) ? single : { kind: 'number', value: 0 };
362
338
  }
363
339
 
364
- function evalDateAwareTokens(tokens: DateAwareToken[], holidaySet: Set<string>): TypedValue {
340
+ function evalDateAwareTokens(tokens: DateAwareToken[], dateMath: DateMath): TypedValue {
365
341
  if (tokens.length === 0) {
366
342
  return { kind: 'number', value: 0 };
367
343
  }
@@ -379,14 +355,14 @@ function evalDateAwareTokens(tokens: DateAwareToken[], holidaySet: Set<string>):
379
355
  break;
380
356
  }
381
357
  const inner = list.slice(idx + 1, close);
382
- const result = evalDateAwareTokens(inner as DateAwareToken[], holidaySet);
358
+ const result = evalDateAwareTokens(inner as DateAwareToken[], dateMath);
383
359
  list.splice(idx, close - idx + 1, result);
384
360
  continue;
385
361
  }
386
362
  idx++;
387
363
  }
388
364
 
389
- return evalDateFlat(list as DateAwareToken[], holidaySet);
365
+ return evalDateFlat(list as DateAwareToken[], dateMath);
390
366
  }
391
367
 
392
368
  /*
@@ -406,14 +382,16 @@ function evalDateAwareTokens(tokens: DateAwareToken[], holidaySet: Set<string>):
406
382
  * field is omitted rather than showing 0.
407
383
  *
408
384
  * Date fields are identified by `fieldType: 'date'` on field tokens. When date
409
- * fields are present, arithmetic follows business-day semantics — weekends and
410
- * holidays are skipped when adding/subtracting days, and date diffs count only
411
- * business days.
385
+ * fields are present and `options.enabled` is on, arithmetic follows business-day
386
+ * semantics — the calendar's weekend days and holidays are skipped when adding/subtracting
387
+ * days, and date diffs count only business days, with a result outside the calendar's years
388
+ * blank. Otherwise (the field's exclusion toggle is off) it's plain calendar-day arithmetic.
389
+ * Either way, as in DTE, a fractional offset or a difference over 365 days is `null` (blank).
412
390
  */
413
391
  export function evaluateFormula(
414
392
  formula: StructuredFormula | undefined | null,
415
393
  data: DataModelValues | undefined,
416
- holidays?: string[],
394
+ options?: DateCalcOptions,
417
395
  ): number | null {
418
396
  if (!formula?.tokens?.length) {
419
397
  return null;
@@ -427,8 +405,10 @@ export function evaluateFormula(
427
405
 
428
406
  if (dateFieldPaths.size) {
429
407
  const resolved = resolveDateAwareTokens(formula, data);
430
- const holidaySet = buildHolidaySet(holidays);
431
- const result = evalDateAwareTokens(resolved, holidaySet);
408
+ const dateMath = options?.enabled
409
+ ? businessDayMath(options.calendar ?? defaultBusinessCalendar(options.holidays))
410
+ : calendarDayMath();
411
+ const result = evalDateAwareTokens(resolved, dateMath);
432
412
  const n = result.value;
433
413
  return Number.isFinite(n) ? n : null;
434
414
  }
@@ -445,7 +425,7 @@ export function evaluateFormula(
445
425
  export function hasDivisionByZero(
446
426
  formula: StructuredFormula | undefined | null,
447
427
  data?: DataModelValues,
448
- holidays?: string[],
428
+ options?: DateCalcOptions,
449
429
  ) {
450
430
  if (!formula?.tokens?.length) {
451
431
  return false;
@@ -458,5 +438,5 @@ export function hasDivisionByZero(
458
438
  if (!hasAllFieldValues(formula, data, dateFieldPaths)) {
459
439
  return false;
460
440
  }
461
- return evaluateFormula(formula, data, holidays) === null;
441
+ return evaluateFormula(formula, data, options) === null;
462
442
  }
@@ -5,5 +5,6 @@ export * from './expression.utils';
5
5
  export * from './format-calculated-result.utils';
6
6
  export * from './formula-types';
7
7
  export * from './render-formula.utils';
8
+ export * from './schedule-bindings.utils';
8
9
  export * from './validate-formula.utils';
9
10
  export * from './form-fields.utils';
@@ -0,0 +1,27 @@
1
+ import { type FieldScheduleBinding } from '../../interface/types';
2
+
3
+ /**
4
+ * Replaces `fieldKey`'s binding: enabling adds it (default schedule, `scheduleId` undefined);
5
+ * disabling removes it. Every other field's binding is returned unchanged.
6
+ */
7
+ export function setFieldScheduleEnabled(
8
+ bindings: FieldScheduleBinding[],
9
+ fieldKey: string,
10
+ enabled: boolean,
11
+ ): FieldScheduleBinding[] {
12
+ const withoutField = bindings.filter(b => b.fieldKey !== fieldKey);
13
+ return enabled ? [...withoutField, { fieldKey }] : withoutField;
14
+ }
15
+
16
+ /**
17
+ * Sets `fieldKey`'s selected schedule (`scheduleId` undefined for the default schedule),
18
+ * enabling its toggle if it wasn't already on. Every other field's binding is returned unchanged.
19
+ */
20
+ export function setFieldSchedule(
21
+ bindings: FieldScheduleBinding[],
22
+ fieldKey: string,
23
+ scheduleId: number | undefined,
24
+ ): FieldScheduleBinding[] {
25
+ const withoutField = bindings.filter(b => b.fieldKey !== fieldKey);
26
+ return [...withoutField, { fieldKey, scheduleId }];
27
+ }