@servicetitan/dte-unlayer 0.103.0 → 0.104.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/src/index.ts CHANGED
@@ -9,3 +9,5 @@ export * from './shared/schema';
9
9
  export * from './shared/tools';
10
10
  export { UnlayerDesignChangeInfo } from './store';
11
11
  export { versions } from './unlayer';
12
+ export * from './shared/date-calc';
13
+ export { emptyArrayIfNull } from './shared/nunjucks-helpers';
@@ -32,6 +32,7 @@ export interface UnlayerEventConfig {
32
32
  genericConfigMode?: boolean;
33
33
  isSnapshotMode?: boolean;
34
34
  units?: UnlayerEditorUnit[];
35
+ holidays?: string[];
35
36
  }
36
37
 
37
38
  export interface UnlayerEventRegister {
@@ -0,0 +1,180 @@
1
+ export const MAX_DATE_CALC_DAYS = 365;
2
+
3
+ export interface DateCalcOptions {
4
+ holidays?: string[];
5
+ }
6
+
7
+ function toDateOnly(input: string | Date): Date {
8
+ const d = typeof input === 'string' ? new Date(input) : new Date(input.getTime());
9
+ d.setUTCHours(0, 0, 0, 0);
10
+ return d;
11
+ }
12
+
13
+ function toDateKey(d: Date): string {
14
+ const y = d.getUTCFullYear();
15
+ const m = String(d.getUTCMonth() + 1).padStart(2, '0');
16
+ const day = String(d.getUTCDate()).padStart(2, '0');
17
+ return `${y}-${m}-${day}`;
18
+ }
19
+
20
+ function isWeekend(d: Date): boolean {
21
+ const day = d.getUTCDay();
22
+ return day === 0 || day === 6;
23
+ }
24
+
25
+ function buildHolidaySet(holidays?: string[]): Set<string> {
26
+ if (!holidays?.length) {
27
+ return new Set();
28
+ }
29
+ const set = new Set<string>();
30
+ for (const h of holidays) {
31
+ const d = toDateOnly(h);
32
+ if (!isNaN(d.getTime())) {
33
+ set.add(toDateKey(d));
34
+ }
35
+ }
36
+ return set;
37
+ }
38
+
39
+ function isNonBusinessDay(d: Date, holidaySet: Set<string>): boolean {
40
+ if (isWeekend(d)) {
41
+ return true;
42
+ }
43
+ if (holidaySet.size > 0 && holidaySet.has(toDateKey(d))) {
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+
49
+ /**
50
+ * Add business days to a date, always skipping weekends and provided holidays.
51
+ * Negative days subtracts.
52
+ */
53
+ export function addDays(dateInput: string | Date, days: number, options?: DateCalcOptions): Date {
54
+ const result = toDateOnly(dateInput);
55
+ if (!isFinite(result.getTime())) {
56
+ return result;
57
+ }
58
+
59
+ const intDays = Math.trunc(days);
60
+ if (intDays === 0) {
61
+ return result;
62
+ }
63
+ if (Math.abs(intDays) > MAX_DATE_CALC_DAYS) {
64
+ return new Date(NaN);
65
+ }
66
+
67
+ const holidaySet = buildHolidaySet(options?.holidays);
68
+ const direction = intDays > 0 ? 1 : -1;
69
+ let remaining = Math.abs(intDays);
70
+
71
+ while (remaining > 0) {
72
+ result.setUTCDate(result.getUTCDate() + direction);
73
+ if (!isNonBusinessDay(result, holidaySet)) {
74
+ remaining--;
75
+ }
76
+ }
77
+
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Count the number of business days between two dates, always skipping weekends and provided holidays.
83
+ * Returns a positive number when `to` is after `from`, negative otherwise.
84
+ */
85
+ export function dateDiffDays(
86
+ from: string | Date,
87
+ to: string | Date,
88
+ options?: DateCalcOptions,
89
+ ): number {
90
+ const start = toDateOnly(from);
91
+ const end = toDateOnly(to);
92
+
93
+ if (!isFinite(start.getTime()) || !isFinite(end.getTime())) {
94
+ return 0;
95
+ }
96
+
97
+ const calendarDiff = Math.abs(end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000);
98
+ if (calendarDiff > MAX_DATE_CALC_DAYS * 2) {
99
+ return NaN;
100
+ }
101
+
102
+ const holidaySet = buildHolidaySet(options?.holidays);
103
+ const direction = end.getTime() >= start.getTime() ? 1 : -1;
104
+ const cursor = new Date(start.getTime());
105
+ let count = 0;
106
+
107
+ while (toDateKey(cursor) !== toDateKey(end)) {
108
+ cursor.setUTCDate(cursor.getUTCDate() + direction);
109
+ if (!isNonBusinessDay(cursor, holidaySet)) {
110
+ count++;
111
+ }
112
+ }
113
+
114
+ return count * direction;
115
+ }
116
+
117
+ /** Format a Date to ISO date string (YYYY-MM-DD). */
118
+ export function formatDateResult(date: Date): string {
119
+ if (!isFinite(date.getTime())) {
120
+ return '';
121
+ }
122
+ return toDateKey(date);
123
+ }
124
+
125
+ export const DEFAULT_DATE_FORMAT = 'MM/DD/YYYY';
126
+
127
+ const MONTH_NAMES_FULL = [
128
+ 'January',
129
+ 'February',
130
+ 'March',
131
+ 'April',
132
+ 'May',
133
+ 'June',
134
+ 'July',
135
+ 'August',
136
+ 'September',
137
+ 'October',
138
+ 'November',
139
+ 'December',
140
+ ];
141
+
142
+ const MONTH_NAMES_SHORT = [
143
+ 'Jan',
144
+ 'Feb',
145
+ 'Mar',
146
+ 'Apr',
147
+ 'May',
148
+ 'Jun',
149
+ 'Jul',
150
+ 'Aug',
151
+ 'Sep',
152
+ 'Oct',
153
+ 'Nov',
154
+ 'Dec',
155
+ ];
156
+
157
+ /**
158
+ * Format a Date using a pattern string.
159
+ * Supported tokens: YYYY, YY, MMMM, MMM, MM, M, DD, D.
160
+ * All other characters are treated as literals.
161
+ */
162
+ export function formatDate(date: Date, pattern?: string): string {
163
+ if (!isFinite(date.getTime())) {
164
+ return '';
165
+ }
166
+ const fmt = pattern ?? DEFAULT_DATE_FORMAT;
167
+ const year = date.getUTCFullYear();
168
+ const month = date.getUTCMonth();
169
+ const day = date.getUTCDate();
170
+
171
+ return fmt
172
+ .replace(/YYYY/g, String(year))
173
+ .replace(/YY/g, String(year).slice(-2))
174
+ .replace(/MMMM/g, MONTH_NAMES_FULL[month])
175
+ .replace(/MMM/g, MONTH_NAMES_SHORT[month])
176
+ .replace(/MM/g, String(month + 1).padStart(2, '0'))
177
+ .replace(/\bM\b/g, String(month + 1))
178
+ .replace(/DD/g, String(day).padStart(2, '0'))
179
+ .replace(/\bD\b/g, String(day));
180
+ }
@@ -0,0 +1,18 @@
1
+ export function emptyArrayIfNull(obj: any, key: string) {
2
+ if (!obj) {
3
+ return obj;
4
+ }
5
+
6
+ let arrayOfObj = obj;
7
+ if (!Array.isArray(obj)) {
8
+ arrayOfObj = [obj];
9
+ }
10
+
11
+ arrayOfObj.forEach((el: { [x: string]: any[] }) => {
12
+ if (typeof el[key] === 'undefined' || el[key] === null) {
13
+ el[key] = [];
14
+ }
15
+ });
16
+
17
+ return obj;
18
+ }
package/src/store.ts CHANGED
@@ -207,6 +207,7 @@ export class UnlayerStore {
207
207
  generics: this.props.generics,
208
208
  genericConfigMode: this.props.genericConfigMode,
209
209
  isSnapshotMode: this.props.isSnapshotMode,
210
+ holidays: this.props.holidays,
210
211
  };
211
212
 
212
213
  this.sendMessage('--config', JSON.parse(JSON.stringify(data)));
@@ -83,5 +83,6 @@ export interface CreateUnlayerEditorProps {
83
83
  maxWidth?: number;
84
84
  maxHeight?: number;
85
85
  maxFileSize?: number;
86
- }
86
+ };
87
+ holidays?: string[];
87
88
  }