powerpagestoolkit 2.221.12 → 2.701.4

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.
@@ -0,0 +1,1251 @@
1
+ /* @ts-self-types="./index.d.ts" */
2
+ // src/utils/safeAjax.ts
3
+ function safeAjax(ajaxOptions) {
4
+ const deferredAjax = $.Deferred();
5
+ shell.getTokenDeferred().done(function(token) {
6
+ if (!ajaxOptions.headers) {
7
+ $.extend(ajaxOptions, {
8
+ headers: {
9
+ __RequestVerificationToken: token
10
+ }
11
+ });
12
+ } else {
13
+ ajaxOptions.headers["__RequestVerificationToken"] = token;
14
+ }
15
+ $.ajax(ajaxOptions).done(function(data, textStatus, jqXHR) {
16
+ validateLoginSession(data, textStatus, jqXHR, deferredAjax.resolve);
17
+ }).fail(deferredAjax.reject);
18
+ }).fail(function() {
19
+ deferredAjax.rejectWith(this, arguments);
20
+ });
21
+ return deferredAjax.promise();
22
+ }
23
+
24
+ // src/core/API.ts
25
+ var API = class {
26
+ /**
27
+ * @param tableSetName The dataverse set name for the table that you are updating a record in
28
+ * @param data The JSON of the fields and data that are to be updated on the targeted record
29
+ * @returns a Promise resolving the successful results *[record id]* of the POST request, or rejecting the failed results *[error]* of the POST request.
30
+ */
31
+ static createRecord(tableSetName, data) {
32
+ return new Promise((resolve, reject) => {
33
+ safeAjax({
34
+ type: "POST",
35
+ url: `/_api/${tableSetName}`,
36
+ data: JSON.stringify(data),
37
+ contentType: "application/json",
38
+ success: function(_response, _status, xhr) {
39
+ resolve(xhr.getResponseHeader("entityid"));
40
+ },
41
+ error: (error) => {
42
+ reject(error);
43
+ }
44
+ });
45
+ });
46
+ }
47
+ /**
48
+ *
49
+ * @param tableSetName The DataVerse SET name of the table being queried
50
+ * @param recordID the GUID of the records to be retrieved
51
+ * @param selectColumns *OPTIONAL* if desired, enter your own custom OData query for advanced GET results. Format = select=column1,column2,column3...
52
+ * @returns a Promise resolving the successful results of the GET request, or rejecting the failed results of the GET request
53
+ */
54
+ static getRecord(tableSetName, recordID, selectColumns) {
55
+ return new Promise((resolve, reject) => {
56
+ const url = `/_api/${tableSetName}(${recordID})${selectColumns ? `?$${selectColumns}` : ""}`;
57
+ safeAjax({
58
+ type: "GET",
59
+ url,
60
+ success: resolve,
61
+ error: reject
62
+ });
63
+ });
64
+ }
65
+ /**
66
+ *
67
+ * @param tableSetName The dataverse set name of the table being queried
68
+ * @param queryParameters *OPTIONAL* the OData query parameters for refining search results: *format = $filter=filters&$select=columns*
69
+ * @returns a Promise resolving the successful results of the GET request, or rejecting the failed results of the GET request
70
+ */
71
+ static getMultiple(tableSetName, queryParameters) {
72
+ return new Promise((resolve, reject) => {
73
+ const url = `/_api/${tableSetName}${queryParameters ? `?${queryParameters}` : ""}`;
74
+ safeAjax({
75
+ type: "GET",
76
+ url,
77
+ success: function(response) {
78
+ resolve(response.value);
79
+ },
80
+ error: reject
81
+ });
82
+ });
83
+ }
84
+ /**
85
+ *
86
+ * @param tableSetName The dataverse set name for the table that you are updating a record in
87
+ * @param recordId The GUID of the record that is being updated
88
+ * @param data The JSON of the fields and data that are to be updated on the targeted record
89
+ * @returns A Promise with the results of the API execution
90
+ */
91
+ static updateRecord(tableSetName, recordId, data) {
92
+ return new Promise((resolve, reject) => {
93
+ const url = `/_api/${tableSetName}(${recordId})`;
94
+ safeAjax({
95
+ type: "PATCH",
96
+ url,
97
+ data: JSON.stringify(data),
98
+ success: resolve,
99
+ error: reject
100
+ });
101
+ });
102
+ }
103
+ };
104
+ var API_default = API;
105
+
106
+ // src/core/waitFor.ts
107
+ function waitFor(target, root = document, multiple = false, debounceTime2) {
108
+ return new Promise((resolve, reject) => {
109
+ if (multiple) {
110
+ let timeout;
111
+ const observedElements = [];
112
+ const observedSet = /* @__PURE__ */ new Set();
113
+ if (debounceTime2 < 1) {
114
+ return resolve(
115
+ Array.from(root.querySelectorAll(target))
116
+ );
117
+ }
118
+ const observer = new MutationObserver(() => {
119
+ const found = Array.from(root.querySelectorAll(target));
120
+ found.forEach((element) => {
121
+ if (!observedSet.has(element)) {
122
+ observedSet.add(element);
123
+ observedElements.push(element);
124
+ }
125
+ });
126
+ clearTimeout(timeout);
127
+ timeout = setTimeout(() => {
128
+ if (observedElements.length > 0) {
129
+ observer.disconnect();
130
+ resolve(observedElements);
131
+ } else {
132
+ reject(
133
+ new Error(
134
+ `No elements found with target: "${target}" within ${debounceTime2 / 1e3} seconds. If the element you are expecting has not loaded yet, consider raising your timeout.`
135
+ )
136
+ );
137
+ }
138
+ }, debounceTime2);
139
+ });
140
+ observer.observe(root, {
141
+ childList: true,
142
+ subtree: true,
143
+ attributes: false
144
+ });
145
+ } else {
146
+ const observer = new MutationObserver(() => {
147
+ const observedElement = root.querySelector(target);
148
+ if (observedElement) {
149
+ clearTimeout(timeout);
150
+ observer.disconnect();
151
+ resolve(observedElement);
152
+ }
153
+ });
154
+ const timeout = setTimeout(() => {
155
+ observer.disconnect();
156
+ reject(
157
+ new Error(
158
+ `Element not found by target: "${target}" within ${debounceTime2 / 1e3} second. If the element you are expecting has not loaded yet, consider raising your timeout.`
159
+ )
160
+ );
161
+ }, debounceTime2);
162
+ const element = root.querySelector(target);
163
+ if (element) {
164
+ clearTimeout(timeout);
165
+ return resolve(element);
166
+ }
167
+ observer.observe(root, {
168
+ subtree: true,
169
+ attributes: true,
170
+ childList: true
171
+ // Detects added/removed child elements
172
+ });
173
+ }
174
+ });
175
+ }
176
+
177
+ // src/utils/createInfoElement.ts
178
+ function CreateInfoEl(titleString, iconStyle) {
179
+ if (typeof titleString !== "string") {
180
+ throw new Error(
181
+ `argument "titleString" must be of type "string". Received: "${typeof titleString}"`
182
+ );
183
+ }
184
+ if (iconStyle && typeof iconStyle !== "object") {
185
+ throw new Error(
186
+ `argument "iconStyle" must be of type "object". Received: "${typeof iconStyle}"`
187
+ );
188
+ }
189
+ const span = document.createElement("span");
190
+ span.classList.add("info-icon");
191
+ const icon = document.createElement("i");
192
+ icon.classList.add("fa", "fa-solid", "fa-info-circle");
193
+ icon.setAttribute("aria-label", "Info");
194
+ icon.style.cursor = "pointer";
195
+ const flyoutContent = document.createElement("div");
196
+ flyoutContent.innerHTML = titleString;
197
+ flyoutContent.classList.add("flyout-content");
198
+ span.appendChild(icon);
199
+ span.appendChild(flyoutContent);
200
+ if (iconStyle) {
201
+ Object.assign(icon.style, iconStyle);
202
+ }
203
+ const positionFlyout = () => {
204
+ flyoutContent.style.display = "block";
205
+ const flyoutRect = flyoutContent.getBoundingClientRect();
206
+ const viewportWidth = window.innerWidth;
207
+ if (flyoutRect.right > viewportWidth) {
208
+ const overflowAmount = flyoutRect.right - viewportWidth;
209
+ flyoutContent.style.left = `calc(50% - ${overflowAmount}px)`;
210
+ }
211
+ if (flyoutRect.left < 0) {
212
+ const overflowAmount = Math.abs(flyoutRect.left);
213
+ flyoutContent.style.left = `calc(50% + ${overflowAmount}px)`;
214
+ }
215
+ };
216
+ span.addEventListener("mouseenter", () => {
217
+ positionFlyout();
218
+ });
219
+ span.addEventListener("mouseleave", (event) => {
220
+ const relatedTarget = event.relatedTarget;
221
+ if (!span.contains(relatedTarget)) {
222
+ flyoutContent.style.display = "none";
223
+ }
224
+ });
225
+ icon.addEventListener("touchstart", (event) => {
226
+ event.preventDefault();
227
+ flyoutContent.style.display = flyoutContent.style.display === "block" ? "none" : "block";
228
+ if (flyoutContent.style.display === "block") {
229
+ positionFlyout();
230
+ }
231
+ });
232
+ document.body.addEventListener("click", (event) => {
233
+ if (!span.contains(event.target)) {
234
+ flyoutContent.style.display = "none";
235
+ }
236
+ });
237
+ flyoutContent.style.display = "none";
238
+ return span;
239
+ }
240
+
241
+ // src/constants/symbols.ts
242
+ var init = Symbol("__I");
243
+ var destroy = Symbol("__D");
244
+ var valueSync = Symbol("__VS");
245
+ var dateSync = Symbol("__DS");
246
+ var getElementValue = Symbol("__GEV");
247
+ var attachVisibilityController = Symbol("__AVC");
248
+ var attachRadioButtons = Symbol("__ARB");
249
+ var bindMethods = Symbol("__B");
250
+ var debounceTime = Symbol("__DT");
251
+ var observers = Symbol("__O");
252
+ var boundEventListeners = Symbol("__BEV");
253
+ var isValidFormElement = Symbol("__VFE");
254
+ var registerEventListener = Symbol("__REV");
255
+
256
+ // src/errors/errors.ts
257
+ var DOMNodeInitializationError = class extends Error {
258
+ constructor(instance, error) {
259
+ super(
260
+ `There was an error initializing a DOMNodeReference for target: ${instance.target}, :: ${error}`
261
+ );
262
+ this.name = "DOMNodeInitializationError";
263
+ }
264
+ };
265
+ var DOMNodeNotFoundError = class extends Error {
266
+ constructor(instance) {
267
+ super(`The targeted DOM element was not found: ${instance.target}`);
268
+ }
269
+ };
270
+ var ValidationConfigError = class extends Error {
271
+ constructor(node, message) {
272
+ super(`Validation configuration error for ${node.target}: ${message}`);
273
+ this.name = "ValidationConfigError";
274
+ }
275
+ };
276
+
277
+ // src/core/DOMNodeReference.ts
278
+ var EventTypes = {
279
+ CHECKBOX: "click",
280
+ RADIO: "click",
281
+ SELECT: "change",
282
+ TEXT: "keyup",
283
+ DEFAULT: "input"
284
+ };
285
+ var DOMNodeReference = class _DOMNodeReference {
286
+ // properties initialized in the constructor
287
+ target;
288
+ logicalName;
289
+ root;
290
+ [debounceTime];
291
+ isLoaded;
292
+ defaultDisplay;
293
+ [observers] = [];
294
+ [boundEventListeners] = [];
295
+ isRadio = false;
296
+ radioType = null;
297
+ /**
298
+ * The value of the element that this node represents
299
+ * stays in syncs with the live DOM elements?.,m via event handler
300
+ */
301
+ value;
302
+ /**
303
+ * Creates an instance of DOMNodeReference.
304
+ * @param target - The CSS selector to find the desired DOM element.
305
+ * @param root - Optionally specify the element within to search for the element targeted by 'target'
306
+ * Defaults to 'document.body'
307
+ */
308
+ /******/
309
+ /******/
310
+ constructor(target, root = document.body, debounceTime2) {
311
+ this.target = target;
312
+ this.logicalName = this.extractLogicalName(target);
313
+ this.root = root;
314
+ this[debounceTime] = debounceTime2;
315
+ this.isLoaded = false;
316
+ this.defaultDisplay = "";
317
+ this.value = null;
318
+ this[bindMethods]();
319
+ }
320
+ extractLogicalName(target) {
321
+ if (typeof target !== "string") return "";
322
+ const bracketMatch = target.match(/\[([^\]]+)\]/);
323
+ if (!bracketMatch) return target.replace(/[#\[\]]/g, "");
324
+ const content = bracketMatch[1];
325
+ const quoteMatch = content.match(/["']([^"']+)["']/);
326
+ return (quoteMatch?.[1] || content).replace(/[#\[\]]/g, "");
327
+ }
328
+ async [init]() {
329
+ try {
330
+ if (this.target instanceof HTMLElement) {
331
+ this.element = this.target;
332
+ } else {
333
+ this.element = await waitFor(
334
+ this.target,
335
+ this.root,
336
+ false,
337
+ this[debounceTime]
338
+ );
339
+ }
340
+ if (!this.element) {
341
+ throw new DOMNodeNotFoundError(this);
342
+ }
343
+ if (this.element.id && this.element.querySelectorAll(
344
+ `#${this.element.id} > input[type="radio"]`
345
+ ).length > 0) {
346
+ await this[attachRadioButtons]();
347
+ }
348
+ this[valueSync]();
349
+ this[attachVisibilityController]();
350
+ this.defaultDisplay = this.visibilityController.style.display;
351
+ const observer = new MutationObserver((mutations) => {
352
+ for (const mutation of mutations) {
353
+ if (Array.from(mutation.removedNodes).includes(this.element)) {
354
+ this[destroy]();
355
+ observer.disconnect();
356
+ break;
357
+ }
358
+ }
359
+ });
360
+ observer.observe(document.body, {
361
+ childList: true,
362
+ subtree: true
363
+ });
364
+ this.isLoaded = true;
365
+ } catch (error) {
366
+ const errorMessage = error instanceof Error ? error.message : String(error);
367
+ throw new DOMNodeInitializationError(this, errorMessage);
368
+ }
369
+ }
370
+ /**
371
+ * Initializes value synchronization with appropriate event listeners
372
+ * based on element type.
373
+ */
374
+ [valueSync]() {
375
+ if (!this[isValidFormElement](this.element)) return;
376
+ this.updateValue();
377
+ const eventType = this.determineEventType();
378
+ this[registerEventListener](this.element, eventType, this.updateValue);
379
+ if (this.isDateInput()) {
380
+ this[dateSync](this.element);
381
+ }
382
+ }
383
+ determineEventType() {
384
+ if (this.element instanceof HTMLSelectElement) return "change";
385
+ if (this.element instanceof HTMLTextAreaElement) return "keyup";
386
+ if (!(this.element instanceof HTMLInputElement)) return EventTypes.DEFAULT;
387
+ return EventTypes[this.element.type.toUpperCase()] || EventTypes.DEFAULT;
388
+ }
389
+ isDateInput() {
390
+ return this.element instanceof HTMLInputElement && this.element.dataset.type === "date";
391
+ }
392
+ [isValidFormElement](element) {
393
+ return element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSpanElement || element instanceof HTMLButtonElement || element instanceof HTMLFieldSetElement;
394
+ }
395
+ [registerEventListener](element, eventType, handler) {
396
+ element.addEventListener(eventType, handler);
397
+ this[boundEventListeners].push({
398
+ element,
399
+ handler,
400
+ event: eventType
401
+ });
402
+ }
403
+ async [dateSync](element) {
404
+ const parentElement = element.parentElement;
405
+ if (!parentElement) {
406
+ throw new Error("Date input must have a parent element");
407
+ }
408
+ const dateNode = await waitFor(
409
+ "[data-date-format]",
410
+ parentElement,
411
+ false,
412
+ 1500
413
+ );
414
+ this[registerEventListener](dateNode, "select", this.updateValue);
415
+ }
416
+ /**
417
+ * Gets the current value of the element based on its type
418
+ * @protected
419
+ * @returns Object containing value and optional checked state
420
+ */
421
+ [getElementValue]() {
422
+ const input = this.element;
423
+ const select = this.element;
424
+ if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
425
+ return {
426
+ value: this.yesRadio.checked,
427
+ checked: this.yesRadio.checked
428
+ };
429
+ }
430
+ let returnValue;
431
+ switch (input.type) {
432
+ case "checkbox":
433
+ case "radio":
434
+ return {
435
+ value: input.checked,
436
+ checked: input.checked
437
+ };
438
+ case "select-multiple":
439
+ return {
440
+ value: Array.from(select.selectedOptions).map(
441
+ (option) => option.value
442
+ )
443
+ };
444
+ case "select-one":
445
+ return {
446
+ value: select.value
447
+ };
448
+ case "number":
449
+ return {
450
+ value: input.value !== "" ? Number(input.value) : null
451
+ };
452
+ default: {
453
+ let cleanValue = input.value;
454
+ if (this.element.classList.contains("decimal") || this.element.classList.contains("money")) {
455
+ cleanValue = input.value.replace(/[$,]/g, "");
456
+ }
457
+ returnValue = {
458
+ value: this.element.classList.contains("decimal") || this.element.classList.contains("money") ? parseFloat(cleanValue) : cleanValue
459
+ };
460
+ }
461
+ }
462
+ returnValue = {
463
+ ...returnValue,
464
+ value: this.validateValue(returnValue.value)
465
+ };
466
+ return returnValue;
467
+ }
468
+ [attachVisibilityController]() {
469
+ this.visibilityController = this.element;
470
+ if (this.element.tagName === "TABLE") {
471
+ const fieldset = this.element.closest("fieldset");
472
+ if (fieldset) {
473
+ this.visibilityController = fieldset;
474
+ }
475
+ return;
476
+ }
477
+ const tagsRequiringTdParent = [
478
+ "SPAN",
479
+ "INPUT",
480
+ "TEXTAREA",
481
+ "SELECT",
482
+ "TABLE"
483
+ ];
484
+ if (tagsRequiringTdParent.includes(this.element.tagName)) {
485
+ const tdParent = this.element.closest("td");
486
+ if (tdParent) {
487
+ this.visibilityController = tdParent;
488
+ }
489
+ }
490
+ }
491
+ async [attachRadioButtons]() {
492
+ if (!this.element) {
493
+ console.error(
494
+ "'this.element' not found: cannot attach radio buttons for ",
495
+ this.target
496
+ );
497
+ return;
498
+ }
499
+ this.yesRadio = await createDOMNodeReference('input[type="radio"][value="1"]', {
500
+ root: this.element
501
+ });
502
+ this.yesRadio.isRadio = true;
503
+ this.yesRadio.radioType = "truthy";
504
+ this.noRadio = await createDOMNodeReference('input[type="radio"][value="0"]', {
505
+ root: this.element
506
+ });
507
+ this.noRadio.isRadio = true;
508
+ this.noRadio.radioType = "falsy";
509
+ }
510
+ [bindMethods]() {
511
+ const prototype = Object.getPrototypeOf(this);
512
+ for (const key of Object.getOwnPropertyNames(prototype)) {
513
+ const value = this[key];
514
+ if (key !== "constructor" && typeof value === "function") {
515
+ this[key] = value.bind(this);
516
+ }
517
+ }
518
+ }
519
+ [destroy]() {
520
+ this[boundEventListeners]?.forEach((binding) => {
521
+ binding.element?.removeEventListener(binding.event, binding.handler);
522
+ });
523
+ this[observers]?.forEach((observer) => {
524
+ observer.disconnect();
525
+ });
526
+ this.yesRadio?.[destroy]();
527
+ this.noRadio?.[destroy]();
528
+ this.yesRadio = null;
529
+ this.noRadio = null;
530
+ this.isLoaded = false;
531
+ this.value = null;
532
+ }
533
+ /**
534
+ * Updates the value and checked state based on element type
535
+ * @public
536
+ */
537
+ updateValue(e) {
538
+ if (e && !e.isTrusted) return;
539
+ if (e) {
540
+ e.stopPropagation();
541
+ }
542
+ if (this.yesRadio && this.noRadio) {
543
+ this.yesRadio.updateValue();
544
+ this.noRadio.updateValue();
545
+ }
546
+ const elementValue = this[getElementValue]();
547
+ this.value = elementValue.value;
548
+ if (elementValue.checked !== void 0) {
549
+ this.checked = elementValue.checked;
550
+ }
551
+ }
552
+ validateValue(value) {
553
+ if (value === null || value === "") {
554
+ return value;
555
+ }
556
+ if (!isNaN(Number(value))) {
557
+ return Number(value);
558
+ }
559
+ return value;
560
+ }
561
+ /**
562
+ * Sets up an event listener based on the specified event type, executing the specified
563
+ * event handler
564
+ * @param eventType - The DOM event to watch for
565
+ * @param eventHandler - The callback function that runs when the
566
+ * specified event occurs.
567
+ * @returns - Instance of this [provides option to method chain]
568
+ */
569
+ on(eventType, eventHandler) {
570
+ if (typeof eventHandler !== "function") {
571
+ throw new Error(
572
+ `Argument "eventHandler" must be a Function. Received: ${typeof eventHandler}`
573
+ );
574
+ }
575
+ this[registerEventListener](
576
+ this.element,
577
+ eventType,
578
+ eventHandler.bind(this)
579
+ );
580
+ return this;
581
+ }
582
+ /**
583
+ * Hides the element by setting its display style to "none".
584
+ * @returns - Instance of this [provides option to method chain]
585
+ */
586
+ hide() {
587
+ this.visibilityController.style.display = "none";
588
+ return this;
589
+ }
590
+ /**
591
+ * Shows the element by restoring its default display style.
592
+ * @returns - Instance of this [provides option to method chain]
593
+ */
594
+ show() {
595
+ this.visibilityController.style.display = this.defaultDisplay;
596
+ return this;
597
+ }
598
+ /**
599
+ * @param shouldShow - Either a function that returns true or false,
600
+ * or a natural boolean to determine the visibility of this
601
+ * @returns - Instance of this [provides option to method chain]
602
+ */
603
+ toggleVisibility(shouldShow) {
604
+ if (shouldShow instanceof Function) {
605
+ shouldShow(this) ? this.show() : this.hide();
606
+ } else {
607
+ shouldShow ? this.show() : this.hide();
608
+ }
609
+ return this;
610
+ }
611
+ /**
612
+ * Sets the value of the HTML element.
613
+ * @param value - The value to set for the HTML element.
614
+ * for parents of boolean radios, pass true or false as value, or
615
+ * an expression returning a boolean
616
+ * @returns - Instance of this [provides option to method chain]
617
+ */
618
+ setValue(value) {
619
+ if (value instanceof Function) {
620
+ value = value();
621
+ }
622
+ if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
623
+ this.yesRadio.element.checked = value;
624
+ this.noRadio.element.checked = !value;
625
+ this.value = value;
626
+ this.checked = value;
627
+ this.element.checked = value;
628
+ } else if (this.isRadio || this.element.type === "radio") {
629
+ this.checked = value;
630
+ this.element.checked = value;
631
+ } else {
632
+ this.element.value = value;
633
+ }
634
+ this.value = this.validateValue(value);
635
+ return this;
636
+ }
637
+ /**
638
+ * Disables the element so that users cannot input any data
639
+ * @returns - Instance of this [provides option to method chain]
640
+ */
641
+ disable() {
642
+ try {
643
+ this.element.disabled = true;
644
+ } catch (error) {
645
+ const errorMessage = error instanceof Error ? error.message : String(error);
646
+ throw new Error(
647
+ `There was an error trying to disable the target: ${this.target}: "${errorMessage}"`
648
+ );
649
+ }
650
+ return this;
651
+ }
652
+ /**
653
+ * Clears all values and states of the element.
654
+ * Handles different input types appropriately, and can be called
655
+ * on an element containing N child inputs to clear all
656
+ *
657
+ * @returns - Instance of this [provides option to method chain]
658
+ * @throws If clearing values fails
659
+ */
660
+ async clearValue() {
661
+ try {
662
+ const element = this.element;
663
+ if (element instanceof HTMLInputElement) {
664
+ switch (element.type.toLowerCase()) {
665
+ case "checkbox":
666
+ case "radio":
667
+ element.checked = false;
668
+ this.checked = false;
669
+ this.value = false;
670
+ break;
671
+ case "number":
672
+ element.value = "";
673
+ this.value = "";
674
+ break;
675
+ default:
676
+ element.value = "";
677
+ this.value = "";
678
+ break;
679
+ }
680
+ } else if (element instanceof HTMLSelectElement) {
681
+ if (element.multiple) {
682
+ Array.from(element.options).forEach(
683
+ (option) => option.selected = false
684
+ );
685
+ this.value = [];
686
+ } else {
687
+ element.selectedIndex = -1;
688
+ this.value = "";
689
+ }
690
+ } else if (element instanceof HTMLTextAreaElement) {
691
+ element.value = "";
692
+ this.value = "";
693
+ } else {
694
+ this.value = "";
695
+ const childInputs = Array.from(
696
+ this.element.querySelectorAll("input, select, textarea")
697
+ );
698
+ if (childInputs.length > 0) {
699
+ const promises = childInputs.map(async (input) => {
700
+ const inputRef = await createDOMNodeReference(
701
+ input
702
+ );
703
+ return inputRef.clearValue();
704
+ });
705
+ await Promise.all(promises);
706
+ }
707
+ }
708
+ if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
709
+ await this.yesRadio.clearValue();
710
+ await this.noRadio.clearValue();
711
+ }
712
+ const events = [
713
+ new Event("input", { bubbles: true }),
714
+ new Event("change", { bubbles: true }),
715
+ new Event("click", { bubbles: true })
716
+ ];
717
+ events.forEach((event) => this.element.dispatchEvent(event));
718
+ return this;
719
+ } catch (error) {
720
+ const errorMessage = `Failed to clear values for element with target "${this.target}": ${error instanceof Error ? error.message : String(error)}`;
721
+ throw new Error(errorMessage);
722
+ }
723
+ }
724
+ /**
725
+ * Enables the element so that users can input data
726
+ * @returns - Instance of this [provides option to method chain]
727
+ */
728
+ enable() {
729
+ try {
730
+ this.element.disabled = false;
731
+ } catch (_error) {
732
+ throw new Error(
733
+ `There was an error trying to disable the target: ${this.target}`
734
+ );
735
+ }
736
+ return this;
737
+ }
738
+ /**
739
+ * @param elements - The elements to prepend to the element targeted by this.
740
+ * @returns - Instance of this [provides option to method chain]
741
+ */
742
+ prepend(...elements) {
743
+ this.element.prepend(...elements);
744
+ return this;
745
+ }
746
+ /**
747
+ * Appends child elements to the HTML element.
748
+ * @param elements - The elements to append to the element targeted by this.
749
+ * @returns - Instance of this [provides option to method chain]
750
+ */
751
+ append(...elements) {
752
+ this.element.append(...elements);
753
+ return this;
754
+ }
755
+ /**
756
+ * Inserts elements before the HTML element.
757
+ * @param elements - The elements to insert before the HTML element.
758
+ * @returns - Instance of this [provides option to method chain]
759
+ */
760
+ before(...elements) {
761
+ this.element.before(...elements);
762
+ return this;
763
+ }
764
+ /**
765
+ * Inserts elements after the HTML element.
766
+ * @param elements - The elements to insert after the HTML element.
767
+ * @returns - Instance of this [provides option to method chain]
768
+ */
769
+ after(...elements) {
770
+ this.element.after(...elements);
771
+ return this;
772
+ }
773
+ /**
774
+ * Retrieves the label associated with the HTML element.
775
+ * @returns {HTMLElement} The label element associated with this element.
776
+ */
777
+ getLabel() {
778
+ return document.querySelector(`#${this.element.id}_label`) || null;
779
+ }
780
+ /**
781
+ * Adds a tooltip with specified text to the label associated with the HTML element.
782
+ * @param innerHTML - The innerHTML to append into the tooltip.
783
+ * @param containerStyle - Optional object with CSS Styles to apply to the info element
784
+ * @returns - Instance of this [provides option to method chain]
785
+ */
786
+ addLabelTooltip(innerHTML, containerStyle) {
787
+ this.getLabel()?.append(
788
+ CreateInfoEl(innerHTML, containerStyle || void 0)
789
+ );
790
+ return this;
791
+ }
792
+ /**
793
+ * Adds a tooltip with the specified text to the element
794
+ * @param innerHTML - The innerHTML to append into the tooltip
795
+ * @param containerStyle - Optional object with CSS Styles to apply to the info element
796
+ * @returns - Instance of this [provides option to method chain]
797
+ */
798
+ addTooltip(innerHTML, containerStyle) {
799
+ this.append(CreateInfoEl(innerHTML, containerStyle || void 0));
800
+ return this;
801
+ }
802
+ /**
803
+ * Sets the inner HTML content of the HTML element.
804
+ * @param {string} string - The text to set as the inner HTML of the element.
805
+ * @returns - Instance of this [provides option to method chain]
806
+ */
807
+ setInnerHTML(string) {
808
+ this.element.innerHTML = string;
809
+ return this;
810
+ }
811
+ /**
812
+ * Removes this element from the DOM
813
+ * @returns - Instance of this [provides option to method chain]
814
+ */
815
+ remove() {
816
+ this.element.remove();
817
+ return this;
818
+ }
819
+ /**
820
+ * @param options and object containing the styles you want to set : {key: value} e.g.: {'display': 'block'}
821
+ * @returns - Instance of this [provides option to method chain]
822
+ */
823
+ setStyle(options) {
824
+ if (Object.prototype.toString.call(options) !== "[object Object]") {
825
+ throw new Error(
826
+ `powerpagestoolkit: 'DOMNodeReference.setStyle' required options to be in the form of an object. Argument passed was of type: ${typeof options}`
827
+ );
828
+ }
829
+ for (const _key in options) {
830
+ const key = _key;
831
+ this.element.style[key] = options[key];
832
+ }
833
+ return this;
834
+ }
835
+ /**
836
+ * Unchecks both the yes and no radio buttons if they exist.
837
+ * @returns - Instance of this [provides option to method chain]
838
+ */
839
+ uncheckRadios() {
840
+ if (this.yesRadio instanceof _DOMNodeReference && this.noRadio instanceof _DOMNodeReference) {
841
+ this.yesRadio.element.checked = false;
842
+ this.noRadio.element.checked = false;
843
+ } else {
844
+ console.error(
845
+ "[SYNACT] Attempted to uncheck radios for an element that has no radios"
846
+ );
847
+ }
848
+ return this;
849
+ }
850
+ /**
851
+ * Applies a business rule to manage visibility, required state, value, and disabled state dynamically.
852
+ * @see {@link BusinessRule}
853
+ * @param rule The business rule containing conditions for various actions.
854
+ * @param dependencies For re-evaluation conditions when the state of the dependencies change
855
+ * @returns Instance of this for method chaining.
856
+ */
857
+ applyBusinessRule(rule, dependencies) {
858
+ try {
859
+ const clearValuesOnHide = rule.setVisibility && rule.setVisibility.length > 1 ? rule.setVisibility[1] : true;
860
+ if (rule.setVisibility) {
861
+ const [condition] = rule.setVisibility;
862
+ const initialState = condition.call(this);
863
+ this.toggleVisibility(initialState);
864
+ }
865
+ if (rule.setRequired) {
866
+ const [isRequired, isValid] = rule.setRequired;
867
+ const fieldDisplayName = (() => {
868
+ let label = this.getLabel();
869
+ if (!label) {
870
+ throw new Error(
871
+ `There was an error accessing the label for this element: ${this.target}`
872
+ );
873
+ }
874
+ label = label.innerHTML;
875
+ if (label.length > 50) {
876
+ label = label.substring(0, 50) + "...";
877
+ }
878
+ return label;
879
+ })();
880
+ if (typeof Page_Validators === "undefined") {
881
+ throw new ValidationConfigError(this, "Page_Validators not found");
882
+ }
883
+ const validatorId = `${this.element.id}Validator`;
884
+ const newValidator = document.createElement("span");
885
+ newValidator.style.display = "none";
886
+ newValidator.id = validatorId;
887
+ Object.assign(newValidator, {
888
+ controltovalidate: this.element.id,
889
+ errormessage: `<a href='#${this.element.id}_label'>${fieldDisplayName} is a required field</a>`,
890
+ evaluationfunction: () => {
891
+ const isFieldRequired = isRequired.call(this);
892
+ const isFieldVisible = window.getComputedStyle(this.visibilityController).display !== "none";
893
+ return !isFieldRequired || !isFieldVisible || isValid.call(this, isFieldRequired);
894
+ }
895
+ });
896
+ Page_Validators.push(newValidator);
897
+ this.setRequiredLevel(isRequired.call(this));
898
+ }
899
+ if (rule.setValue) {
900
+ let [condition, value] = rule.setValue;
901
+ if (value instanceof Function) value = value();
902
+ if (condition.call(this)) {
903
+ this.setValue.call(this, value);
904
+ }
905
+ }
906
+ if (rule.setDisabled) {
907
+ const condition = rule.setDisabled;
908
+ condition.call(this) ? this.disable() : this.enable();
909
+ }
910
+ if (dependencies.length) {
911
+ let visibilityCondition, isRequired, valueCondition, value, disabledCondition;
912
+ const aggregateHandler = (rule2) => {
913
+ if (rule2.setVisibility) {
914
+ [visibilityCondition] = rule2.setVisibility;
915
+ this.toggleVisibility(visibilityCondition.call(this));
916
+ }
917
+ if (rule2.setRequired) {
918
+ [isRequired] = rule2.setRequired;
919
+ this.setRequiredLevel(isRequired.call(this));
920
+ }
921
+ if (rule2.setValue) {
922
+ [valueCondition, value] = rule2.setValue;
923
+ if (valueCondition.call(this)) this.setValue.call(this, value);
924
+ }
925
+ if (rule2.setDisabled) {
926
+ disabledCondition = rule2.setDisabled;
927
+ disabledCondition.call(this) ? this.disable() : this.enable;
928
+ }
929
+ };
930
+ this._configDependencyTracking(
931
+ () => aggregateHandler(rule),
932
+ dependencies,
933
+ {
934
+ clearValuesOnHide,
935
+ observeVisibility: true,
936
+ trackInputEvents: false,
937
+ trackRadioButtons: false
938
+ }
939
+ );
940
+ }
941
+ return this;
942
+ } catch (error) {
943
+ throw new ValidationConfigError(
944
+ this,
945
+ `Failed to apply business rule: ${error}`
946
+ );
947
+ }
948
+ }
949
+ /**
950
+ * Sets up tracking for dependencies using both event listeners and mutation observers.
951
+ * @protected
952
+ * @param handler The function to execute when dependencies change
953
+ * @param dependencies Array of dependent DOM nodes to track
954
+ * @param options Additional configuration options. clearValuesOnHide defaults to false.
955
+ * all other options defaults to true
956
+ */
957
+ _configDependencyTracking(handler, dependencies, options = {
958
+ clearValuesOnHide: false,
959
+ observeVisibility: true,
960
+ trackInputEvents: true,
961
+ trackRadioButtons: true
962
+ }) {
963
+ const {
964
+ clearValuesOnHide = false,
965
+ observeVisibility = true,
966
+ trackInputEvents = true,
967
+ trackRadioButtons = true
968
+ } = options;
969
+ if (!dependencies?.length) {
970
+ console.warn(
971
+ `powerpagestoolkit: No dependencies specified for ${this.element.id}. Include all referenced nodes in the dependency array for proper tracking.`
972
+ );
973
+ return;
974
+ }
975
+ dependencies.forEach((dep) => {
976
+ if (!dep || !(dep instanceof _DOMNodeReference)) {
977
+ throw new TypeError(
978
+ "Each dependency must be a valid DOMNodeReference instance"
979
+ );
980
+ }
981
+ const handleChange = () => {
982
+ handler();
983
+ if (clearValuesOnHide && window.getComputedStyle(this.visibilityController).display === "none") {
984
+ this.clearValue();
985
+ }
986
+ };
987
+ this[registerEventListener](dep.element, "change", handleChange);
988
+ if (trackInputEvents) {
989
+ this[registerEventListener](dep.element, "input", handleChange);
990
+ }
991
+ if (observeVisibility) {
992
+ const observer = new MutationObserver(() => {
993
+ const display = window.getComputedStyle(
994
+ dep.visibilityController
995
+ ).display;
996
+ if (display !== "none") {
997
+ handler();
998
+ }
999
+ });
1000
+ observer.observe(dep.visibilityController, {
1001
+ attributes: true,
1002
+ attributeFilter: ["style"],
1003
+ subtree: false
1004
+ });
1005
+ this[observers].push(observer);
1006
+ }
1007
+ if (trackRadioButtons && dep.yesRadio && dep.noRadio) {
1008
+ [dep.yesRadio, dep.noRadio].forEach((radio) => {
1009
+ radio.on("change", handleChange);
1010
+ this[boundEventListeners].push({
1011
+ element: radio.element,
1012
+ event: "change",
1013
+ handler: handleChange
1014
+ });
1015
+ });
1016
+ }
1017
+ });
1018
+ }
1019
+ /**
1020
+ * Sets the required level for the field by adding or removing the "required-field" class on the label.
1021
+ *
1022
+ * @param isRequired Determines whether the field should be marked as required.
1023
+ * If true, the "required-field" class is added to the label; if false, it is removed.
1024
+ * @returns Instance of this [provides option to method chain]
1025
+ */
1026
+ setRequiredLevel(isRequired) {
1027
+ if (isRequired instanceof Function) {
1028
+ isRequired() ? this.getLabel()?.classList.add("required-field") : this.getLabel()?.classList.remove("required-field");
1029
+ return this;
1030
+ } else {
1031
+ isRequired ? this.getLabel()?.classList.add("required-field") : this.getLabel()?.classList.remove("required-field");
1032
+ return this;
1033
+ }
1034
+ }
1035
+ /**
1036
+ * Executes a callback function once the element is fully loaded.
1037
+ * If the element is already loaded, the callback is called immediately.
1038
+ * Otherwise, a MutationObserver is used to detect when the element is added to the DOM.
1039
+ * @param callback A callback function to execute once the element is loaded.
1040
+ * Receives instance of 'this' as an argument
1041
+ */
1042
+ onceLoaded(callback) {
1043
+ if (this.isLoaded) {
1044
+ callback(this);
1045
+ return;
1046
+ }
1047
+ if (this.target instanceof HTMLElement) {
1048
+ callback(this);
1049
+ return;
1050
+ }
1051
+ const observer = new MutationObserver(() => {
1052
+ if (document.querySelector(this.target)) {
1053
+ observer.disconnect();
1054
+ this.isLoaded = true;
1055
+ callback(this);
1056
+ }
1057
+ });
1058
+ observer.observe(document.body, {
1059
+ subtree: true,
1060
+ childList: true
1061
+ });
1062
+ this[observers].push(observer);
1063
+ }
1064
+ };
1065
+
1066
+ // src/core/DOMNodeReferenceArray.ts
1067
+ var DOMNodeReferenceArray = class extends Array {
1068
+ /**
1069
+ * Hides all the containers of the DOMNodeReference instances in the array.
1070
+ */
1071
+ hideAll() {
1072
+ this.forEach((instance) => instance.hide());
1073
+ return this;
1074
+ }
1075
+ /**
1076
+ * Shows all the containers of the DOMNodeReference instances in the array.
1077
+ */
1078
+ showAll() {
1079
+ this.forEach((instance) => instance.show());
1080
+ return this;
1081
+ }
1082
+ };
1083
+
1084
+ // src/utils/enhanceArray.ts
1085
+ function enhanceArray(array) {
1086
+ const enhancedArray = new DOMNodeReferenceArray(...array);
1087
+ return new Proxy(enhancedArray, {
1088
+ get(target, prop, receiver) {
1089
+ if (prop in target) {
1090
+ return Reflect.get(target, prop, receiver);
1091
+ }
1092
+ if (typeof prop === "string") {
1093
+ return target.find(
1094
+ (instance) => instance.target.toString().replace(/[#\[\]]/g, "") === prop || instance.logicalName === prop
1095
+ );
1096
+ }
1097
+ return void 0;
1098
+ }
1099
+ });
1100
+ }
1101
+
1102
+ // src/core/createDOMNodeReferences.ts
1103
+ async function createDOMNodeReference(target, options = {
1104
+ multiple: false,
1105
+ root: document.body,
1106
+ timeoutMs: 0
1107
+ }) {
1108
+ try {
1109
+ if (typeof options !== "object") {
1110
+ throw new Error(
1111
+ `'options' must be of type 'object'. Received type: '${typeof options}'`
1112
+ );
1113
+ }
1114
+ validateOptions(options);
1115
+ const { multiple = false, root = document.body, timeoutMs = 0 } = options;
1116
+ const isMultiple = typeof multiple === "function" ? multiple() : multiple;
1117
+ if (isMultiple) {
1118
+ if (typeof target !== "string") {
1119
+ throw new Error(
1120
+ `'target' must be of type 'string' if 'multiple' is set to 'true'. Received type: '${typeof target}'`
1121
+ );
1122
+ }
1123
+ const elements = await waitFor(target, root, true, timeoutMs);
1124
+ const initializedElements = await Promise.all(
1125
+ elements.map(async (element) => {
1126
+ const instance2 = new DOMNodeReference(element, root, timeoutMs);
1127
+ await instance2[init]();
1128
+ return new Proxy(instance2, createProxyHandler());
1129
+ })
1130
+ );
1131
+ return enhanceArray(initializedElements);
1132
+ }
1133
+ const instance = new DOMNodeReference(target, root, timeoutMs);
1134
+ await instance[init]();
1135
+ return new Proxy(instance, createProxyHandler());
1136
+ } catch (e) {
1137
+ throw new Error(e);
1138
+ }
1139
+ }
1140
+ function validateOptions(options) {
1141
+ const { multiple = false, root = document.body, timeoutMs = 0 } = options;
1142
+ if (typeof multiple !== "boolean" && typeof multiple !== "function") {
1143
+ throw new Error(
1144
+ `'multiple' must be of type 'boolean' or 'function'. Received type: '${typeof multiple}'`
1145
+ );
1146
+ }
1147
+ if (typeof multiple === "function") {
1148
+ const value = multiple();
1149
+ if (typeof value !== "boolean") {
1150
+ throw new Error(
1151
+ `'multiple' function must return a boolean. Received type: '${typeof value}'`
1152
+ );
1153
+ }
1154
+ }
1155
+ if (!(root instanceof HTMLElement)) {
1156
+ throw new Error(
1157
+ `'root' must be of type 'HTMLElement'. Received type: '${typeof root}'`
1158
+ );
1159
+ }
1160
+ if (typeof timeoutMs !== "number") {
1161
+ throw new Error(
1162
+ `'timeout' must be of type 'number'. Received type: '${typeof timeoutMs}'`
1163
+ );
1164
+ }
1165
+ return;
1166
+ }
1167
+ function createProxyHandler() {
1168
+ return {
1169
+ get: (target, prop) => {
1170
+ if (prop.toString().startsWith("_")) return void 0;
1171
+ const value = target[prop];
1172
+ if (typeof value === "function" && prop !== "onceLoaded") {
1173
+ return (...args) => {
1174
+ target.onceLoaded(() => value.apply(target, args));
1175
+ return target;
1176
+ };
1177
+ }
1178
+ return value;
1179
+ }
1180
+ };
1181
+ }
1182
+
1183
+ // src/core/bindForm.ts
1184
+ async function bindForm(formId) {
1185
+ try {
1186
+ const form = await API_default.getRecord("systemforms", formId);
1187
+ const { formxml } = form;
1188
+ const parser = new DOMParser();
1189
+ const xmlDoc = parser.parseFromString(formxml, "application/xml");
1190
+ const controls = processElements(xmlDoc.getElementsByTagName("control"));
1191
+ const sections = processElements(xmlDoc.getElementsByTagName("section"));
1192
+ const tabs = processElements(xmlDoc.getElementsByTagName("tab"));
1193
+ const resolvedRefs = await Promise.all([...controls, ...sections, ...tabs]);
1194
+ return enhanceArray(
1195
+ resolvedRefs.filter((ref) => ref !== null)
1196
+ );
1197
+ } catch (error) {
1198
+ if (error instanceof Error) {
1199
+ console.error(error.message);
1200
+ throw error;
1201
+ } else {
1202
+ console.error(error);
1203
+ throw new Error(String(error));
1204
+ }
1205
+ }
1206
+ }
1207
+ function processElements(element) {
1208
+ return Array.from(element).map((element2) => {
1209
+ const identifyingAttribute = getIdentifyingAttribute(element2.tagName);
1210
+ const datafieldname = element2.getAttribute(identifyingAttribute);
1211
+ if (!datafieldname) return null;
1212
+ const referenceString = createReferenceString(
1213
+ element2.tagName,
1214
+ datafieldname
1215
+ );
1216
+ if (!referenceString) return null;
1217
+ return createDOMNodeReference(referenceString).catch((error) => {
1218
+ console.warn(
1219
+ `Failed to create a reference to the form field: ${datafieldname}`,
1220
+ error
1221
+ );
1222
+ return null;
1223
+ });
1224
+ }).filter(Boolean);
1225
+ }
1226
+ function getIdentifyingAttribute(tagName) {
1227
+ return tagName === "control" ? "id" : tagName === "tab" || tagName === "section" ? "name" : "id";
1228
+ }
1229
+ function createReferenceString(tagName, datafieldname) {
1230
+ if (tagName === "control") return `#${datafieldname}`;
1231
+ if (tagName === "tab" || tagName === "section") {
1232
+ return `[data-name="${datafieldname}"]`;
1233
+ }
1234
+ return null;
1235
+ }
1236
+ export {
1237
+ API_default as API,
1238
+ bindForm,
1239
+ createDOMNodeReference as createRef,
1240
+ waitFor
1241
+ };
1242
+ /*! For license information please see index.js.LEGAL.txt */
1243
+ //# sourceMappingURL=index.js.map
1244
+
1245
+
1246
+ if (typeof document !== 'undefined') {
1247
+ const style = document.createElement('style');
1248
+ style.textContent = ".info-icon {\r\n position: relative;\r\n display: inline-block;\r\n}\r\n\r\n.info-icon .fa-info-circle {\r\n cursor: pointer; /* Ensures the icon is recognized as interactive */\r\n}\r\n\r\n.info-icon .flyout-content {\r\n max-width: calc(100vw - 20px); /* 20px margin on both sides */\r\n display: none; /* Initially hidden */\r\n position: absolute;\r\n left: 50%; /* Center horizontally */\r\n transform: translateX(-50%); /* Adjust positioning */\r\n color: #000;\r\n background-color: #f9f9f9;\r\n padding: 10px;\r\n border: 1px solid #ddd;\r\n z-index: 1;\r\n min-width: 200px; /* Minimum width for better readability */\r\n box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);\r\n border-radius: 4px; /* Rounded corners */\r\n}\r\n\r\n@media (max-width: 600px) {\r\n .info-icon .flyout-content {\r\n max-width: 95vw;\r\n padding: 12px;\r\n font-size: 0.9em;\r\n display: block;\r\n right: auto;\r\n }\r\n}\r\n\r\n.required-field::after {\r\n content: \" *\" !important;\r\n color: #f00 !important;\r\n}\r\n";
1249
+ document.head.appendChild(style);
1250
+ }
1251
+