@finsweet/webflow-apps-utils 1.0.5 → 1.0.7

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 (48) hide show
  1. package/dist/stores/index.d.ts +1 -0
  2. package/dist/stores/index.js +1 -0
  3. package/dist/stores/isPreviewMode.d.ts +1 -0
  4. package/dist/stores/isPreviewMode.js +2 -0
  5. package/dist/types/dom.d.ts +1 -0
  6. package/dist/types/dom.js +1 -0
  7. package/dist/types/index.d.ts +1 -0
  8. package/dist/types/index.js +1 -0
  9. package/dist/types/webflow.d.ts +1 -1
  10. package/dist/ui/components/input/Input.svelte +18 -22
  11. package/dist/ui/components/layout/Layout.svelte +4 -2
  12. package/dist/ui/components/layout/Layout.svelte.d.ts +2 -0
  13. package/dist/ui/components/text/Text.svelte +4 -2
  14. package/dist/ui/components/text/types.d.ts +22 -0
  15. package/dist/ui/icons/ChevronIcon.svelte +1 -1
  16. package/dist/utils/constants.d.ts +5 -0
  17. package/dist/utils/constants.js +11 -0
  18. package/dist/utils/helpers/dom.d.ts +37 -0
  19. package/dist/utils/helpers/dom.js +104 -0
  20. package/dist/utils/helpers/encodeDecodeConfigs.d.ts +13 -0
  21. package/dist/utils/helpers/encodeDecodeConfigs.js +20 -0
  22. package/dist/utils/helpers/events.d.ts +19 -0
  23. package/dist/utils/helpers/events.js +28 -0
  24. package/dist/utils/helpers/forms.d.ts +22 -0
  25. package/dist/utils/helpers/forms.js +82 -0
  26. package/dist/utils/helpers/guards.d.ts +124 -0
  27. package/dist/utils/helpers/guards.js +107 -0
  28. package/dist/utils/helpers/index.d.ts +8 -0
  29. package/dist/utils/helpers/index.js +8 -0
  30. package/dist/utils/helpers/parseCSV.d.ts +6 -0
  31. package/dist/utils/helpers/parseCSV.js +29 -0
  32. package/dist/utils/helpers/string.d.ts +23 -0
  33. package/dist/utils/helpers/string.js +33 -0
  34. package/dist/utils/helpers/wait.d.ts +13 -0
  35. package/dist/utils/helpers/wait.js +27 -0
  36. package/dist/utils/index.d.ts +1 -0
  37. package/dist/utils/index.js +1 -0
  38. package/dist/utils/webflow/CopyJSONButton.d.ts +54 -0
  39. package/dist/utils/webflow/CopyJSONButton.js +117 -0
  40. package/dist/utils/webflow/DisplayController.d.ts +55 -0
  41. package/dist/utils/webflow/DisplayController.js +91 -0
  42. package/dist/utils/webflow/Interaction.d.ts +47 -0
  43. package/dist/utils/webflow/Interaction.js +52 -0
  44. package/dist/utils/webflow/index.d.ts +4 -0
  45. package/dist/utils/webflow/index.js +4 -0
  46. package/dist/utils/webflow/webflow.d.ts +32 -0
  47. package/dist/utils/webflow/webflow.js +90 -0
  48. package/package.json +5 -6
@@ -1,6 +1,7 @@
1
1
  export * from './breakpoints';
2
2
  export * from './forms';
3
3
  export * from './componentInjectErrors';
4
+ export * from './isPreviewMode';
4
5
  export * from './router';
5
6
  export * from './showConfirmActionModal';
6
7
  export * from './siteInfo';
@@ -1,6 +1,7 @@
1
1
  export * from './breakpoints';
2
2
  export * from './forms';
3
3
  export * from './componentInjectErrors';
4
+ export * from './isPreviewMode';
4
5
  export * from './router';
5
6
  export * from './showConfirmActionModal';
6
7
  export * from './siteInfo';
@@ -0,0 +1 @@
1
+ export declare const isPreviewMode: import("svelte/store").Writable<boolean>;
@@ -0,0 +1,2 @@
1
+ import { writable } from 'svelte/store';
2
+ export const isPreviewMode = writable(false);
@@ -0,0 +1 @@
1
+ export type FormField = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
1
  export * from './auth';
2
+ export * from './dom';
2
3
  export * from './customCode';
3
4
  export * from './license';
4
5
  export * from './webflow';
@@ -1,4 +1,5 @@
1
1
  export * from './auth';
2
+ export * from './dom';
2
3
  export * from './customCode';
3
4
  export * from './license';
4
5
  export * from './webflow';
@@ -96,7 +96,7 @@ export type XSCPMetadata = {
96
96
  }[];
97
97
  };
98
98
  };
99
- type PastedNodes = {
99
+ export type PastedNodes = {
100
100
  children: string[];
101
101
  classes: string[];
102
102
  tag: string;
@@ -474,28 +474,24 @@
474
474
  </div>
475
475
  {/snippet}
476
476
 
477
- {#if hasAlert}
478
- <Tooltip
479
- message={alert?.message || ''}
480
- placement="top"
481
- listener="hover"
482
- listenerout="hover"
483
- showArrow={true}
484
- disabled={false}
485
- hidden={false}
486
- fontColor="var(--actionPrimaryText)"
487
- width="max-content"
488
- padding="6px"
489
- bgColor={getTooltipColor(alert?.type || 'info')}
490
- class="input-tooltip"
491
- >
492
- {#snippet target()}
493
- {@render inputWrapper()}
494
- {/snippet}
495
- </Tooltip>
496
- {:else}
497
- {@render inputWrapper()}
498
- {/if}
477
+ <Tooltip
478
+ message={hasAlert ? alert?.message || '' : ''}
479
+ placement="top"
480
+ listener="hover"
481
+ listenerout="hover"
482
+ showArrow={true}
483
+ hidden={!hasAlert}
484
+ disabled={!hasAlert || !alert?.message}
485
+ fontColor="var(--actionPrimaryText)"
486
+ width="max-content"
487
+ padding="6px"
488
+ bgColor={getTooltipColor(alert?.type || 'info')}
489
+ class="input-tooltip"
490
+ >
491
+ {#snippet target()}
492
+ {@render inputWrapper()}
493
+ {/snippet}
494
+ </Tooltip>
499
495
 
500
496
  <style>
501
497
  :global(.input-tooltip) {
@@ -41,6 +41,8 @@
41
41
  containerMode?: boolean;
42
42
  /** Size variant for the footer */
43
43
  footerSize?: 'normal' | 'large';
44
+ /** Padding for the main content container (CSS value) */
45
+ mainContentPadding?: string;
44
46
  /** Array of notification objects for tab status indicators */
45
47
  notifications?: Array<{
46
48
  /** Tab path this notification applies to */
@@ -76,6 +78,7 @@
76
78
  sidebarWidth = '274px',
77
79
  containerMode = false,
78
80
  footerSize = 'normal',
81
+ mainContentPadding = 'var(--Spacing-24, 24px)',
79
82
  notifications = [],
80
83
  sidebar,
81
84
  main,
@@ -240,7 +243,7 @@
240
243
  <!-- Main Content -->
241
244
  <div class="main-content" data-area="main">
242
245
  {#if main}
243
- <div class="main-content-container">
246
+ <div class="main-content-container" style="padding: {mainContentPadding}">
244
247
  {#if showEditModeMessage}
245
248
  <EditModeMessage />
246
249
  {/if}
@@ -462,7 +465,6 @@
462
465
  flex-direction: column;
463
466
  align-items: flex-start;
464
467
  align-self: stretch;
465
- padding: var(--Spacing-24, 24px);
466
468
  gap: var(--Spacing-16, 16px);
467
469
  }
468
470
 
@@ -28,6 +28,8 @@ interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
28
28
  containerMode?: boolean;
29
29
  /** Size variant for the footer */
30
30
  footerSize?: 'normal' | 'large';
31
+ /** Padding for the main content container (CSS value) */
32
+ mainContentPadding?: string;
31
33
  /** Array of notification objects for tab status indicators */
32
34
  notifications?: Array<{
33
35
  /** Tab path this notification applies to */
@@ -43,6 +43,7 @@
43
43
 
44
44
  // Link behavior
45
45
  link = false,
46
+ linkHover = true,
46
47
 
47
48
  // Event handlers
48
49
  onclick,
@@ -211,6 +212,7 @@
211
212
  const classes = ['labels'];
212
213
  if (disabled) classes.push('disabled');
213
214
  if (link) classes.push('link');
215
+ if (link && linkHover) classes.push('link-hover');
214
216
  if (loading) classes.push('is-busy');
215
217
  if (hasPopup && popupConfig.active) classes.push('active');
216
218
  classes.push(className);
@@ -839,8 +841,8 @@
839
841
  border-radius: 4px;
840
842
  }
841
843
 
842
- .labels.link:hover:not(.disabled),
843
- .labels.link.is-busy {
844
+ .labels.link-hover:hover:not(.disabled),
845
+ .labels.link-hover.is-busy {
844
846
  background-color: var(--defaultLightHover);
845
847
  border-radius: 4px;
846
848
  }
@@ -25,26 +25,48 @@ export interface PopupConfig {
25
25
  onclick?: () => void;
26
26
  }
27
27
  export interface TextProps {
28
+ /** The text content to display */
28
29
  label?: string;
30
+ /** Additional CSS classes to apply to the component */
29
31
  class?: string;
32
+ /** Whether to render the label as HTML instead of plain text */
30
33
  raw?: boolean;
34
+ /** Whether to capitalize the first letter of the text */
31
35
  capitalize?: boolean;
36
+ /** Whether the component is disabled and non-interactive */
32
37
  disabled?: boolean;
38
+ /** HTML title attribute for accessibility and hover tooltip */
33
39
  title?: string;
40
+ /** Text wrapping behavior - 'nowrap' prevents wrapping, 'normal' allows wrapping */
34
41
  wrap?: 'nowrap' | 'normal';
42
+ /** Horizontal alignment of the text content */
35
43
  textAlign?: 'left' | 'center' | 'right';
44
+ /** Font size - use predefined sizes or custom CSS value */
36
45
  fontSize?: TEXT_SIZES | string;
46
+ /** Font weight - use predefined weights or custom CSS value */
37
47
  fontWeight?: TEXT_WEIGHTS | string;
48
+ /** Text color as any valid CSS color value */
38
49
  fontColor?: string;
50
+ /** Fixed height for the component container */
39
51
  height?: string;
52
+ /** Width at which text will be truncated with ellipsis */
40
53
  ellipsisOnWidth?: string;
54
+ /** Tooltip configuration object with all tooltip-related settings */
41
55
  tooltip?: Partial<TooltipProps>;
42
56
  /** Specifies whether to show tooltip on the text or icon. Requires icon prop when set to 'icon' */
43
57
  tooltipTarget?: 'text' | 'icon';
58
+ /** Configuration for action popup functionality (delete, reset, etc.) */
44
59
  popup?: PopupConfig;
60
+ /** Icon component to display alongside the text */
45
61
  icon?: Component | null;
62
+ /** Whether to show loading spinner instead of icon */
46
63
  loading?: boolean;
64
+ /** Whether to style the text as a clickable link with pointer cursor */
47
65
  link?: boolean;
66
+ /** Whether to show hover background effect when link is true (default: true) */
67
+ linkHover?: boolean;
68
+ /** Custom content snippet to render instead of the label text */
48
69
  children?: Snippet;
70
+ /** Additional pill/badge content snippet to display */
49
71
  pill?: Snippet;
50
72
  }
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="17" viewBox="0 0 16 17" fill="none">
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
2
2
  <path
3
3
  fill-rule="evenodd"
4
4
  clip-rule="evenodd"
@@ -59,3 +59,8 @@ export declare const BRAND: {
59
59
  */
60
60
  export declare const FINSWEET_COMPONENTS_WEBFLOW_APP_ORIGINS: string[];
61
61
  export declare const ACCEPTED_WEBFLOW_ELEMENTS_FOR_INSERTION: string[];
62
+ /**
63
+ * List of domains that are considered staging.
64
+ */
65
+ export declare const STAGING_DOMAINS: string[];
66
+ export declare const FINSWEET_LOGO_URL = "https://cdn.prod.website-files.com/61819aaca0e7ac73f85a2d54/6865d68f194e84da9c6452e5_Components%20White%20Logo.svg";
@@ -71,3 +71,14 @@ export const ACCEPTED_WEBFLOW_ELEMENTS_FOR_INSERTION = [
71
71
  'Container',
72
72
  'BlockContainer'
73
73
  ];
74
+ /**
75
+ * List of domains that are considered staging.
76
+ */
77
+ export const STAGING_DOMAINS = [
78
+ 'webflow.io',
79
+ 'webflow-ext.com',
80
+ 'localhost',
81
+ 'canvas.webflow.com',
82
+ 'server.wized.com'
83
+ ];
84
+ export const FINSWEET_LOGO_URL = 'https://cdn.prod.website-files.com/61819aaca0e7ac73f85a2d54/6865d68f194e84da9c6452e5_Components%20White%20Logo.svg';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Check if an element is scrollable
3
+ * @param element
4
+ * @returns True or false
5
+ */
6
+ export declare const isScrollable: (element: Element) => boolean;
7
+ /**
8
+ * Finds the first child text node of an element
9
+ * @param element The element to search into.
10
+ */
11
+ export declare const findTextNode: (element: HTMLElement) => ChildNode | undefined;
12
+ /**
13
+ * Function to fetch the Document of a specified URL.
14
+ *
15
+ * @param url - The URL of the page.
16
+ * @param slug - [optional] The slug of the page.
17
+ * @returns
18
+ */
19
+ export declare const fetchDocument: (url: string, slug?: string) => Promise<Document>;
20
+ /**
21
+ * Function to fetch the Elements of a specified URL.
22
+ *
23
+ * @param url - The URL of the page.
24
+ * @param tagName - The tag name of the elements to fetch. If not provided, returns the whole page as a string.
25
+ * @param asElement - [optional] If true, returns the element as an object.
26
+ * @returns
27
+ */
28
+ export declare const fetchElements: (url: URL, tagName?: string, asElement?: boolean) => Promise<string[] | HTMLElement[]>;
29
+ /**
30
+ * Fetch elements by attribute
31
+ */
32
+ export declare const fetchElementsByAttribute: (url: URL, attribute: string) => Promise<string[]>;
33
+ /**
34
+ * Checks if an element is visible
35
+ * @param element
36
+ */
37
+ export declare const isVisible: (element: HTMLElement) => boolean;
@@ -0,0 +1,104 @@
1
+ import { load } from 'cheerio';
2
+ import { FINSWEET_REVERSE_PROXY_URL } from '../constants';
3
+ import { isHTMLElement } from './guards';
4
+ /**
5
+ * Check if an element is scrollable
6
+ * @param element
7
+ * @returns True or false
8
+ */
9
+ export const isScrollable = (element) => {
10
+ const { overflow } = getComputedStyle(element);
11
+ return overflow === 'auto' || overflow === 'scroll';
12
+ };
13
+ /**
14
+ * Finds the first child text node of an element
15
+ * @param element The element to search into.
16
+ */
17
+ export const findTextNode = (element) => {
18
+ let textNode;
19
+ for (const node of Array.from(element.childNodes)) {
20
+ if (isHTMLElement(node) && node.childNodes.length)
21
+ textNode = findTextNode(node);
22
+ else if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim())
23
+ textNode = node;
24
+ if (textNode)
25
+ break;
26
+ }
27
+ return textNode;
28
+ };
29
+ /**
30
+ * Function to fetch the Document of a specified URL.
31
+ *
32
+ * @param url - The URL of the page.
33
+ * @param slug - [optional] The slug of the page.
34
+ * @returns
35
+ */
36
+ export const fetchDocument = async (url, slug) => {
37
+ const pageUrl = new URL(url);
38
+ if (slug)
39
+ pageUrl.pathname = slug;
40
+ const target = `${FINSWEET_REVERSE_PROXY_URL}${pageUrl}`;
41
+ const response = await fetch(target);
42
+ const pageContent = await response.text();
43
+ const parser = new DOMParser();
44
+ return parser.parseFromString(pageContent, 'text/html');
45
+ };
46
+ /**
47
+ * Function to fetch the Elements of a specified URL.
48
+ *
49
+ * @param url - The URL of the page.
50
+ * @param tagName - The tag name of the elements to fetch. If not provided, returns the whole page as a string.
51
+ * @param asElement - [optional] If true, returns the element as an object.
52
+ * @returns
53
+ */
54
+ export const fetchElements = async (url, tagName, asElement) => {
55
+ const target = `${FINSWEET_REVERSE_PROXY_URL}${url.href}`;
56
+ const response = await fetch(target);
57
+ const html = await response.text();
58
+ const $ = load(html);
59
+ // return the whole page if no tag name is provided
60
+ if (!tagName)
61
+ return [$.html()];
62
+ // crawl page and get elements
63
+ if (asElement && tagName) {
64
+ const elements = $(tagName)
65
+ .map((_, el) => {
66
+ const outerHTML = $.html(el); // Get the outer HTML of each element
67
+ const parser = new DOMParser();
68
+ const parsedDoc = parser.parseFromString(outerHTML, 'text/html');
69
+ const element = parsedDoc.querySelector(tagName);
70
+ return element;
71
+ })
72
+ .get();
73
+ return elements;
74
+ }
75
+ // crawl page and get elements as strings
76
+ const elements = $(tagName)
77
+ .map((_, el) => {
78
+ const outerHTML = $.html(el);
79
+ return outerHTML;
80
+ })
81
+ .get();
82
+ return elements;
83
+ };
84
+ /**
85
+ * Fetch elements by attribute
86
+ */
87
+ export const fetchElementsByAttribute = async (url, attribute) => {
88
+ const target = `${FINSWEET_REVERSE_PROXY_URL}${url.href}`;
89
+ const response = await fetch(target);
90
+ const html = await response.text();
91
+ const $ = load(html);
92
+ const elements = $(attribute)
93
+ .map((_, el) => {
94
+ const outerHTML = $.html(el);
95
+ return outerHTML;
96
+ })
97
+ .get();
98
+ return elements;
99
+ };
100
+ /**
101
+ * Checks if an element is visible
102
+ * @param element
103
+ */
104
+ export const isVisible = (element) => !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Converts a configuration object to a base64-encoded string.
3
+ * @param {object} configs
4
+ * @returns
5
+ */
6
+ export declare const encodeComponentConfigs: (configs: object) => string;
7
+ /**
8
+ * Decodes a base64-encoded configuration string to a configuration object.
9
+ * @param configsString
10
+ * @param component
11
+ * @returns
12
+ */
13
+ export declare const decodeComponentConfigs: <T>(configsString: string) => T;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Converts a configuration object to a base64-encoded string.
3
+ * @param {object} configs
4
+ * @returns
5
+ */
6
+ export const encodeComponentConfigs = (configs) => {
7
+ const previewModeConfigs = btoa(JSON.stringify(configs));
8
+ return previewModeConfigs;
9
+ };
10
+ /**
11
+ * Decodes a base64-encoded configuration string to a configuration object.
12
+ * @param configsString
13
+ * @param component
14
+ * @returns
15
+ */
16
+ export const decodeComponentConfigs = (configsString) => {
17
+ const jsonString = atob(configsString);
18
+ const configParsed = JSON.parse(jsonString);
19
+ return configParsed;
20
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Adds an event listener to an element.
3
+ * @returns A callback to remove the event listener from the element.
4
+ *
5
+ * @param target
6
+ * @param type
7
+ * @param listener
8
+ * @param options
9
+ */
10
+ export declare function addListener<TargetInterface extends EventTarget, Type extends TargetInterface extends Window ? keyof WindowEventMap | string : TargetInterface extends Document ? keyof DocumentEventMap | string : TargetInterface extends HTMLElement ? keyof HTMLElementEventMap | string : keyof ElementEventMap | string, Listener extends Type extends keyof WindowEventMap ? (this: Document, ev: WindowEventMap[Type]) => unknown : Type extends keyof DocumentEventMap ? (this: Document, ev: DocumentEventMap[Type]) => unknown : Type extends keyof HTMLElementEventMap ? (this: HTMLElement, ev: HTMLElementEventMap[Type]) => unknown : Type extends keyof ElementEventMap ? (this: Element, ev: ElementEventMap[Type]) => unknown : EventListenerOrEventListenerObject>(target: TargetInterface | null | undefined, type: Type, listener: Listener, options?: boolean | AddEventListenerOptions): () => void;
11
+ type AllowedEvent = keyof DocumentEventMap | 'w-close';
12
+ /**
13
+ * Dispatches a custom event that bubbles from the target.
14
+ * @param target The element where the event will originate.
15
+ * @param events The event name or an array of event names.
16
+ * @returns True if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.
17
+ */
18
+ export declare const simulateEvent: (target: EventTarget, events: AllowedEvent | Array<AllowedEvent>) => boolean;
19
+ export {};
@@ -0,0 +1,28 @@
1
+ import { noop } from './noop';
2
+ /**
3
+ * Adds an event listener to an element.
4
+ * @returns A callback to remove the event listener from the element.
5
+ *
6
+ * @param target
7
+ * @param type
8
+ * @param listener
9
+ * @param options
10
+ */
11
+ export function addListener(target, type, listener, options) {
12
+ if (!target)
13
+ return noop;
14
+ target.addEventListener(type, listener, options);
15
+ return () => target.removeEventListener(type, listener, options);
16
+ }
17
+ /**
18
+ * Dispatches a custom event that bubbles from the target.
19
+ * @param target The element where the event will originate.
20
+ * @param events The event name or an array of event names.
21
+ * @returns True if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise.
22
+ */
23
+ export const simulateEvent = (target, events) => {
24
+ if (!Array.isArray(events))
25
+ events = [events];
26
+ const eventsSuccess = events.map((event) => target.dispatchEvent(new Event(event, { bubbles: true })));
27
+ return eventsSuccess.every((success) => success);
28
+ };
@@ -0,0 +1,22 @@
1
+ import type { FormField } from '../../types';
2
+ import { simulateEvent } from './events';
3
+ /**
4
+ * Clears the form field's value and emits an input and changed event.
5
+ * If the field is a checkbox or a radio, it will unselect it.
6
+ * @param field The `FormField` to clear.
7
+ * @param omitEvents By default, events are dispatched from the `FormField`. In some cases, these events might collide with other logic of the system.
8
+ * You can omit certain events from being dispatched by passing them in an array.
9
+ */
10
+ export declare const clearFormField: (field: FormField, omitEvents?: Parameters<typeof simulateEvent>["1"]) => void;
11
+ /**
12
+ * Gets the value of a given input element.
13
+ * @param {FormField} input
14
+ */
15
+ export declare const getFormFieldValue: (input: FormField) => string;
16
+ /**
17
+ * Sets a value to a FormField element and emits `click`, `input` and `change` Events.
18
+ *
19
+ * @param element The FormField to update.
20
+ * @param value `boolean` for Checkboxes and Radios, `string` for the rest.
21
+ */
22
+ export declare const setFormFieldValue: (element: FormField, value: string | boolean) => void;
@@ -0,0 +1,82 @@
1
+ import { FORM_CSS_CLASSES } from '../webflow';
2
+ import { simulateEvent } from './events';
3
+ import { isHTMLInputElement } from './guards';
4
+ /**
5
+ * Clears the form field's value and emits an input and changed event.
6
+ * If the field is a checkbox or a radio, it will unselect it.
7
+ * @param field The `FormField` to clear.
8
+ * @param omitEvents By default, events are dispatched from the `FormField`. In some cases, these events might collide with other logic of the system.
9
+ * You can omit certain events from being dispatched by passing them in an array.
10
+ */
11
+ export const clearFormField = (field, omitEvents = []) => {
12
+ const { type } = field;
13
+ if (isHTMLInputElement(field) && ['checkbox', 'radio'].includes(type)) {
14
+ if (!field.checked)
15
+ return;
16
+ // Reset the field's value
17
+ field.checked = false;
18
+ // Emit DOM events
19
+ simulateEvent(field, ['click', 'input', 'change'].filter((event) => !omitEvents.includes(event)));
20
+ if (type === 'checkbox')
21
+ return;
22
+ // Clear custom radio button classes
23
+ const { parentElement } = field;
24
+ if (!parentElement)
25
+ return;
26
+ const radioInput = parentElement.querySelector(`.${FORM_CSS_CLASSES.radioInput}`);
27
+ if (!radioInput)
28
+ return;
29
+ radioInput.classList.remove(FORM_CSS_CLASSES.checkboxOrRadioFocus, FORM_CSS_CLASSES.checkboxOrRadioChecked);
30
+ return;
31
+ }
32
+ // Reset the field's value
33
+ field.value = '';
34
+ // Emit DOM events
35
+ simulateEvent(field, ['input', 'change'].filter((eventKey) => !omitEvents.includes(eventKey)));
36
+ };
37
+ /**
38
+ * Gets the value of a given input element.
39
+ * @param {FormField} input
40
+ */
41
+ export const getFormFieldValue = (input) => {
42
+ let { value } = input;
43
+ // Perform actions depending on input type
44
+ if (input.type === 'checkbox')
45
+ value = input.checked.toString();
46
+ if (input.type === 'radio') {
47
+ // Get the checked radio
48
+ const checkedOption = input
49
+ .closest('form')
50
+ ?.querySelector(`input[name="${input.name}"]:checked`);
51
+ // If exists, set its value
52
+ value = isHTMLInputElement(checkedOption) ? checkedOption.value : '';
53
+ }
54
+ return value.toString();
55
+ };
56
+ /**
57
+ * Sets a value to a FormField element and emits `click`, `input` and `change` Events.
58
+ *
59
+ * @param element The FormField to update.
60
+ * @param value `boolean` for Checkboxes and Radios, `string` for the rest.
61
+ */
62
+ export const setFormFieldValue = (element, value) => {
63
+ const { type } = element;
64
+ const isRadio = type === 'radio';
65
+ const isCheckbox = type === 'checkbox';
66
+ if (isRadio || isCheckbox) {
67
+ if (!isHTMLInputElement(element) ||
68
+ typeof value !== 'boolean' ||
69
+ value === element.checked ||
70
+ (isRadio && value === false)) {
71
+ return;
72
+ }
73
+ element.checked = value;
74
+ }
75
+ else {
76
+ if (element.value === value)
77
+ return;
78
+ element.value = value.toString();
79
+ }
80
+ // Emit DOM events
81
+ simulateEvent(element, ['click', 'input', 'change']);
82
+ };