@amimpact/willy-utils 4.14.0 → 4.15.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amimpact/willy-utils",
3
- "version": "4.14.0",
3
+ "version": "4.15.0",
4
4
  "description": "Javascript utils",
5
5
  "scripts": {
6
6
  "publish:prerelease": "npm version prerelease --preid beta && npm publish --tag beta",
package/src/date.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  subDays,
8
8
  } from 'date-fns';
9
9
  import { nl } from 'date-fns/locale';
10
- import { TZDate } from '@date-fns/tz';
10
+ import { TZDate, tzOffset } from '@date-fns/tz';
11
11
  import { parse as parseISO } from './includes/tinyduration';
12
12
 
13
13
  /**
@@ -293,6 +293,47 @@ export const formatNewDate = (dateObject: CraftDate) => {
293
293
  return new Date(parsed);
294
294
  };
295
295
 
296
+ /**
297
+ * Een datum met tijdzone zoals teruggegeven door externe APIs (bijv. Microsoft Graph).
298
+ */
299
+ export interface DateTimeWithZone {
300
+ dateTime: string;
301
+ timeZone: string;
302
+ }
303
+
304
+ /**
305
+ * Parseert een DateTimeWithZone object naar een TZDate in de lokale tijdzone van de browser.
306
+ *
307
+ * TZDate gebruikt de timeZone parameter alleen voor de getters, niet voor het parsen
308
+ * van de string. Daardoor wordt een string zonder timezone-aanduiding altijd als lokale
309
+ * tijd geïnterpreteerd. Om de string correct te interpreteren in de opgegeven tijdzone:
310
+ * 1. Parseer de string naïef als UTC (via 'Z' suffix) als referentiedatum
311
+ * 2. Bepaal de UTC-offset van de opgegeven tijdzone op dat moment via tzOffset
312
+ * 3. Corrigeer de timestamp: UTC = lokale tijd - offset
313
+ * 4. Maak een TZDate in de lokale tijdzone van de browser
314
+ *
315
+ * Retourneert null als de dateTime string ongeldig is of de timeZone onbekend is.
316
+ *
317
+ * @param {DateTimeWithZone} dateTimeWithZone - Object met dateTime string en timeZone
318
+ * @returns {TZDate | null}
319
+ */
320
+ export const parseDateTimeWithZone = (dateTimeWithZone: DateTimeWithZone): TZDate | null => {
321
+ try {
322
+ const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
323
+ const naiveUtc = new Date(dateTimeWithZone.dateTime + 'Z');
324
+
325
+ if (isNaN(naiveUtc.getTime())) {
326
+ return null;
327
+ }
328
+
329
+ const offsetMinutes = tzOffset(dateTimeWithZone.timeZone, naiveUtc);
330
+ const utcMs = naiveUtc.getTime() - offsetMinutes * 60 * 1000;
331
+ return new TZDate(utcMs, localTimeZone);
332
+ } catch {
333
+ return null;
334
+ }
335
+ };
336
+
296
337
  /**
297
338
  * Convert datum
298
339
  * @param {Date} date
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  formatSecondsToHumanReadableTime,
7
7
  formatUTCDate,
8
8
  parseISODuration,
9
+ parseDateTimeWithZone,
9
10
  } from './date';
10
11
  import { convertObjectToFormData } from './form';
11
12
  import {
@@ -14,7 +15,7 @@ import {
14
15
  checkForScrollbars,
15
16
  convertHexToRGBA,
16
17
  downloadFile,
17
- getNestedSet,
18
+ getTree,
18
19
  getUrlsFromString,
19
20
  isEmpty,
20
21
  isHoverableDevice,
@@ -50,7 +51,7 @@ export {
50
51
  formatNewDate,
51
52
  formatSecondsToHumanReadableTime,
52
53
  formatUTCDate,
53
- getNestedSet,
54
+ getTree,
54
55
  getParents,
55
56
  getUrlsFromString,
56
57
  isAsset,
@@ -65,6 +66,7 @@ export {
65
66
  isValidEmail,
66
67
  observeResize,
67
68
  parseISODuration,
69
+ parseDateTimeWithZone,
68
70
  readCookie,
69
71
  slugify,
70
72
  wrap,
package/src/misc.ts CHANGED
@@ -105,17 +105,37 @@ export const downloadFile = (data: any, fileName: string) => {
105
105
  fileLink.setAttribute('download', fileName);
106
106
  document.body.appendChild(fileLink);
107
107
  fileLink.click();
108
+ // direct opruimen
109
+ document.body.removeChild(fileLink);
110
+ if (!isValidUrl(data)) {
111
+ // ook de object URL vrijgeven
112
+ window.URL.revokeObjectURL(fileURL);
113
+ }
108
114
  }
109
115
  };
110
116
 
111
117
  /**
112
- * Flat structure array ombouwen naar een array met children
113
- * @param {array} entries
114
- * @param {number} level
115
- * @param {number | string} left
116
- * @param {number | string} right
118
+ * Detecteert of items een nested-set structuur (lft/rgt) hebben
119
+ *
120
+ * @param {unknown[]} entries
121
+ * @returns {boolean}
117
122
  */
118
- export const getNestedSet = (entries: any[] = [], level: number = 1, left: any, right: any) => {
123
+ const hasNestedSetFields = (entries: unknown[]): boolean => {
124
+ if (entries.length === 0) return false;
125
+ const first = entries[0] as Record<string, unknown>;
126
+ return 'lft' in first && 'rgt' in first;
127
+ };
128
+
129
+ /**
130
+ * Bouwt een boom op basis van nested-set velden (lft, rgt, level)
131
+ *
132
+ * @param {any[]} entries - Platte lijst met lft, rgt en level properties
133
+ * @param {number} level - Huidig level om te filteren
134
+ * @param {number | string} left - Linker grens
135
+ * @param {number | string} right - Rechter grens
136
+ * @returns {any[]}
137
+ */
138
+ const buildNestedSetTree = (entries: any[] = [], level: number = 1, left?: any, right?: any): any[] => {
119
139
  return entries
120
140
  .filter((item) => {
121
141
  return (
@@ -127,7 +147,7 @@ export const getNestedSet = (entries: any[] = [], level: number = 1, left: any,
127
147
  .map((item) => {
128
148
  const returnItem = item;
129
149
 
130
- returnItem['children'] = getNestedSet(
150
+ returnItem['children'] = buildNestedSetTree(
131
151
  entries,
132
152
  parseFloat(item.level) + 1,
133
153
  parseFloat(item.lft),
@@ -138,6 +158,40 @@ export const getNestedSet = (entries: any[] = [], level: number = 1, left: any,
138
158
  });
139
159
  };
140
160
 
161
+ /**
162
+ * Bouwt een boom op basis van parentId velden (id, parentId)
163
+ *
164
+ * @param {any[]} entries - Platte lijst met id en parentId properties
165
+ * @param {string | number | null} parent - parentId van de root
166
+ * @returns {any[] | undefined}
167
+ */
168
+ const buildParentIdTree = (entries: any[] = [], parent: string | number | null): any[] | undefined => {
169
+ const t: Record<string, any> = {};
170
+ entries.forEach((o) =>
171
+ ((t[o.parentId] ??= {}).children ??= []).push(Object.assign((t[o.id] ??= {}), o)),
172
+ );
173
+ return t[`${parent}`]?.children;
174
+ };
175
+
176
+ /**
177
+ * Platte array omzetten naar een boom met children.
178
+ * Detecteert automatisch of de data nested-set velden (lft/rgt) bevat
179
+ * of parentId-gebaseerd is en kiest het juiste algoritme.
180
+ *
181
+ * @param {any[]} entries - Platte lijst van items
182
+ * @param {string | number | null} parent - parentId van de root (alleen gebruikt bij parentId-modus)
183
+ * @returns {any[]}
184
+ */
185
+ export const getTree = (entries: any[] = [], parent: string | number | null = null): any[] => {
186
+ if (entries.length === 0) return [];
187
+
188
+ if (hasNestedSetFields(entries)) {
189
+ return buildNestedSetTree(entries);
190
+ }
191
+
192
+ return buildParentIdTree(entries, parent) || [];
193
+ };
194
+
141
195
  /**
142
196
  * Urls uit een string halen
143
197
  * @param {string} content - html
@@ -257,18 +311,24 @@ export const isValidUrl = (urlString: any) => {
257
311
  /**
258
312
  * Resize observer; checkt of een HTML element zijn dimensies veranderen
259
313
  * en zodra dat gebeurt wordt de callback functie gecalled.
314
+ * Retourneert de observer zodat callers deze kunnen disconnecten bij cleanup.
260
315
  *
261
- * @param {HTMLElement} el
316
+ * @param {HTMLElement} element
262
317
  * @param {Function} callback
318
+ * @returns {ResizeObserver | null} - De observer instantie, of null als het element niet bestaat
263
319
  */
264
- export const observeResize = (element: HTMLElement, callback: Function) => {
320
+ export const observeResize = (element: HTMLElement, callback: Function): ResizeObserver | null => {
321
+ if (!element) {
322
+ return null;
323
+ }
324
+
265
325
  const resizeObserver = new ResizeObserver(() => {
266
326
  callback(element);
267
327
  });
268
328
 
269
- if (element) {
270
- resizeObserver.observe(element);
271
- }
329
+ resizeObserver.observe(element);
330
+
331
+ return resizeObserver;
272
332
  };
273
333
 
274
334
  /**