@aurodesignsystem/auro-library 5.12.2 → 5.13.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/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Semantic Release Automated Changelog
2
2
 
3
+ # [5.13.0](https://github.com/AlaskaAirlines/auro-library/compare/v5.12.3...v5.13.0) (2026-05-14)
4
+
5
+
6
+ ### Features
7
+
8
+ * add ISO date formatting support AB[#1495381](https://github.com/AlaskaAirlines/auro-library/issues/1495381) ([006947a](https://github.com/AlaskaAirlines/auro-library/commit/006947abf0736ec8efa25fe7861d99327e459a3b))
9
+
10
+
11
+ ### Performance Improvements
12
+
13
+ * **dateutil:** add null check guard ([e5187f4](https://github.com/AlaskaAirlines/auro-library/commit/e5187f45a96aeec66c286ba9ca858e5bce9b6860))
14
+ * **dateutil:** clean up some dup logics ([bbed2ee](https://github.com/AlaskaAirlines/auro-library/commit/bbed2ee4257ff1d107505158809eaa8cc3245685))
15
+ * refactor structure and performance ([42247a0](https://github.com/AlaskaAirlines/auro-library/commit/42247a03392c80dd29ab3447bf65b73fc461ba38))
16
+
17
+ ## [5.12.3](https://github.com/AlaskaAirlines/auro-library/compare/v5.12.2...v5.12.3) (2026-04-30)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * **floatingUI:** restore bib transform ([65a236f](https://github.com/AlaskaAirlines/auro-library/commit/65a236f0419d4946b6cb194e1ea6e8af74cdf15e))
23
+ * **floatingUI:** set `role="application"` on bib Container to lock 3finger swipe ([88fa5ca](https://github.com/AlaskaAirlines/auro-library/commit/88fa5ca6875f91a301b4a26f2d195d47b7e0192a))
24
+ * **floatingUI:** use `aria-modal` instead of `role="application"` ([7a3dddc](https://github.com/AlaskaAirlines/auro-library/commit/7a3dddc2a42a6bc416ae5b35f0b9efc5aeb2998f))
25
+
26
+
27
+ ### Performance Improvements
28
+
29
+ * **floatingUI:** add queue to main track opened floatingUI activities ([662ce52](https://github.com/AlaskaAirlines/auro-library/commit/662ce52efc0b0b74db6512f7e6b2a60d64a3ab98))
30
+ * **floatingUI:** lock scroll for fullscreen mode ([6d425b6](https://github.com/AlaskaAirlines/auro-library/commit/6d425b64e66a2ec1519f098b5472580e29dcf6a7))
31
+
3
32
  ## [5.12.2](https://github.com/AlaskaAirlines/auro-library/compare/v5.12.1...v5.12.2) (2026-04-09)
4
33
 
5
34
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aurodesignsystem/auro-library",
3
- "version": "5.12.2",
3
+ "version": "5.13.0",
4
4
  "description": "This repository holds shared scripts, utilities, and workflows utilized across repositories along the Auro Design System.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,21 +1,26 @@
1
1
  /* eslint-disable max-classes-per-file */
2
- import { fixture, html, expect, elementUpdated } from '@open-wc/testing';
2
+ import { expect, fixtureSync, html } from "@open-wc/testing";
3
3
 
4
- import {
5
- isFocusableComponent,
6
- getFocusableElements
7
- } from '../Focusables.mjs';
4
+ import { getFocusableElements, isFocusableComponent } from "../Focusables.mjs";
8
5
 
9
- describe('isFocusableComponent', () => {
10
- it('returns true for enabled custom focusable components', async () => {
6
+ describe("isFocusableComponent", () => {
7
+ it("returns true for enabled custom focusable components", async () => {
11
8
  for (const tag of [
12
- 'auro-checkbox', 'auro-radio', 'auro-dropdown', 'auro-button', 'auro-combobox',
13
- 'auro-input', 'auro-counter', 'auro-select', 'auro-datepicker',
14
- 'auro-hyperlink', 'auro-accordion'
9
+ "auro-checkbox",
10
+ "auro-radio",
11
+ "auro-dropdown",
12
+ "auro-button",
13
+ "auro-combobox",
14
+ "auro-input",
15
+ "auro-counter",
16
+ "auro-select",
17
+ "auro-datepicker",
18
+ "auro-hyperlink",
19
+ "auro-accordion",
15
20
  ]) {
16
21
  const el = document.createElement(tag);
17
- if (tag === 'auro-hyperlink') {
18
- el.setAttribute('href', '#');
22
+ if (tag === "auro-hyperlink") {
23
+ el.setAttribute("href", "#");
19
24
  }
20
25
  document.body.appendChild(el);
21
26
  expect(isFocusableComponent(el)).to.be.true;
@@ -23,16 +28,24 @@ describe('isFocusableComponent', () => {
23
28
  }
24
29
  });
25
30
 
26
- it('returns false for custom components with disabled attribute', async () => {
31
+ it("returns false for custom components with disabled attribute", async () => {
27
32
  for (const tag of [
28
- 'auro-checkbox', 'auro-radio', 'auro-dropdown', 'auro-button', 'auro-combobox',
29
- 'auro-input', 'auro-counter', 'auro-select', 'auro-datepicker',
30
- 'auro-hyperlink', 'auro-accordion'
33
+ "auro-checkbox",
34
+ "auro-radio",
35
+ "auro-dropdown",
36
+ "auro-button",
37
+ "auro-combobox",
38
+ "auro-input",
39
+ "auro-counter",
40
+ "auro-select",
41
+ "auro-datepicker",
42
+ "auro-hyperlink",
43
+ "auro-accordion",
31
44
  ]) {
32
45
  const el = document.createElement(tag);
33
- el.setAttribute('disabled', '');
34
- if (tag === 'auro-hyperlink') {
35
- el.setAttribute('href', '#');
46
+ el.setAttribute("disabled", "");
47
+ if (tag === "auro-hyperlink") {
48
+ el.setAttribute("href", "#");
36
49
  }
37
50
  document.body.appendChild(el);
38
51
  expect(isFocusableComponent(el)).to.be.false;
@@ -40,24 +53,26 @@ describe('isFocusableComponent', () => {
40
53
  }
41
54
  });
42
55
 
43
- it('returns false for auro-hyperlink without href', async () => {
44
- const el = document.createElement('auro-hyperlink');
56
+ it("returns false for auro-hyperlink without href", async () => {
57
+ const el = document.createElement("auro-hyperlink");
45
58
  document.body.appendChild(el);
46
59
  expect(isFocusableComponent(el)).to.be.false;
47
60
  el.remove();
48
61
  });
49
62
 
50
- it('returns false for non-custom elements', async () => {
51
- const el = document.createElement('div');
63
+ it("returns false for non-custom elements", async () => {
64
+ const el = document.createElement("div");
52
65
  document.body.appendChild(el);
53
66
  expect(isFocusableComponent(el)).to.be.false;
54
67
  el.remove();
55
68
  });
56
69
  });
57
70
 
58
- describe('getFocusableElements', () => {
59
- it('finds standard focusable elements', async () => {
60
- const el = await fixture(html`
71
+ // fixtureSync is used instead of fixture to avoid requestAnimationFrame, which Chrome
72
+ // pauses in background tabs when multiple test files run concurrently under WTR.
73
+ describe("getFocusableElements", () => {
74
+ it("finds standard focusable elements", () => {
75
+ const el = fixtureSync(html`
61
76
  <div>
62
77
  <button id="btn"></button>
63
78
  <input id="input">
@@ -67,11 +82,17 @@ describe('getFocusableElements', () => {
67
82
  </div>
68
83
  `);
69
84
  const focusables = getFocusableElements(el);
70
- expect(focusables.map(e => e.id)).to.include.members(['btn', 'input', 'link', 'ta', 'sel']);
85
+ expect(focusables.map((e) => e.id)).to.include.members([
86
+ "btn",
87
+ "input",
88
+ "link",
89
+ "ta",
90
+ "sel",
91
+ ]);
71
92
  });
72
93
 
73
- it('skips disabled elements', async () => {
74
- const el = await fixture(html`
94
+ it("skips disabled elements", () => {
95
+ const el = fixtureSync(html`
75
96
  <div>
76
97
  <button id="btn" disabled></button>
77
98
  <input id="input" disabled>
@@ -83,8 +104,8 @@ describe('getFocusableElements', () => {
83
104
  expect(focusables.length).to.equal(0);
84
105
  });
85
106
 
86
- it('finds custom focusable components', async () => {
87
- const el = await fixture(html`
107
+ it("finds custom focusable components", () => {
108
+ const el = fixtureSync(html`
88
109
  <div>
89
110
  <auro-checkbox id="cb"></auro-checkbox>
90
111
  <auro-hyperlink id="hl" href="#"></auro-hyperlink>
@@ -92,11 +113,11 @@ describe('getFocusableElements', () => {
92
113
  </div>
93
114
  `);
94
115
  const focusables = getFocusableElements(el);
95
- expect(focusables.map(e => e.id)).to.include.members(['cb', 'hl', 'ab']);
116
+ expect(focusables.map((e) => e.id)).to.include.members(["cb", "hl", "ab"]);
96
117
  });
97
118
 
98
- it('skips disabled custom components', async () => {
99
- const el = await fixture(html`
119
+ it("skips disabled custom components", () => {
120
+ const el = fixtureSync(html`
100
121
  <div>
101
122
  <auro-checkbox id="cb" disabled></auro-checkbox>
102
123
  <auro-hyperlink id="hl" disabled href="#"></auro-hyperlink>
@@ -106,60 +127,59 @@ describe('getFocusableElements', () => {
106
127
  expect(focusables.length).to.equal(0);
107
128
  });
108
129
 
109
- it('finds elements in shadow DOM', async () => {
130
+ it("finds elements in shadow DOM", () => {
110
131
  class ShadowEl extends HTMLElement {
111
132
  constructor() {
112
133
  super();
113
- this.attachShadow({ mode: 'open' });
134
+ this.attachShadow({ mode: "open" });
114
135
  }
115
136
  connectedCallback() {
116
- this.shadowRoot.innerHTML = `<button id="shadowBtn"></button>`;
137
+ const btn = document.createElement("button");
138
+ btn.id = "shadowBtn";
139
+ this.shadowRoot.appendChild(btn);
117
140
  }
118
141
  }
119
- customElements.define('shadow-el', ShadowEl);
120
- const el = await fixture(html`
142
+ customElements.define("shadow-el", ShadowEl);
143
+ const el = fixtureSync(html`
121
144
  <div>
122
145
  <shadow-el id="host"></shadow-el>
123
146
  </div>
124
147
  `);
125
- await elementUpdated(el);
126
148
  const focusables = getFocusableElements(el);
127
- // Should find the button inside shadow DOM
128
- const shadowBtn = el.querySelector('shadow-el').shadowRoot.getElementById('shadowBtn');
149
+ const shadowBtn = el
150
+ .querySelector("shadow-el")
151
+ .shadowRoot.getElementById("shadowBtn");
129
152
  expect(focusables).to.include(shadowBtn);
130
153
  });
131
154
 
132
- it('finds elements assigned to slots', async () => {
155
+ it("finds elements assigned to slots", () => {
133
156
  class SlotEl extends HTMLElement {
134
157
  constructor() {
135
158
  super();
136
- this.attachShadow({ mode: 'open' });
159
+ this.attachShadow({ mode: "open" });
137
160
  }
138
161
  connectedCallback() {
139
- this.shadowRoot.innerHTML = `<slot></slot>`;
162
+ this.shadowRoot.appendChild(document.createElement("slot"));
140
163
  }
141
164
  }
142
- customElements.define('slot-el', SlotEl);
143
- const el = await fixture(html`
165
+ customElements.define("slot-el", SlotEl);
166
+ const el = fixtureSync(html`
144
167
  <slot-el>
145
168
  <button id="slottedBtn"></button>
146
169
  </slot-el>
147
170
  `);
148
- await elementUpdated(el);
149
171
  const focusables = getFocusableElements(el);
150
- const slottedBtn = el.querySelector('#slottedBtn');
172
+ const slottedBtn = el.querySelector("#slottedBtn");
151
173
  expect(focusables).to.include(slottedBtn);
152
174
  });
153
175
 
154
- it('does not return duplicates', async () => {
155
- // This is a contrived case, but let's check that duplicates are not returned
156
- const el = await fixture(html`
176
+ it("does not return duplicates", () => {
177
+ const el = fixtureSync(html`
157
178
  <div>
158
179
  <button id="btn"></button>
159
180
  </div>
160
181
  `);
161
182
  const focusables = getFocusableElements(el);
162
- // Should only have one instance of the button
163
- expect(focusables.filter(e => e.id === 'btn').length).to.equal(1);
183
+ expect(focusables.filter((e) => e.id === "btn").length).to.equal(1);
164
184
  });
165
- });
185
+ });
@@ -1,104 +1,230 @@
1
- class DateFormatter {
2
-
3
- constructor() {
4
-
5
- /**
6
- * @description Parses a date string into its components.
7
- * @param {string} dateStr - Date string to parse.
8
- * @param {string} format - Date format to parse.
9
- * @returns {Object<key["month" | "day" | "year"]: number>|undefined}
10
- */
11
- this.parseDate = (dateStr, format = 'mm/dd/yyyy') => {
12
-
13
- // Guard Clause: Date string is defined
14
- if (!dateStr) {
15
- return undefined;
16
- }
17
-
18
- // Assume the separator is a "/" a defined in our code base
19
- const separator = '/';
20
-
21
- // Get the parts of the date and format
22
- const valueParts = dateStr.split(separator);
23
- const formatParts = format.split(separator);
24
-
25
- // Check if the value and format have the correct number of parts
26
- if (valueParts.length !== formatParts.length) {
27
- throw new Error('AuroDatepickerUtilities | parseDate: Date string and format length do not match');
28
- }
29
-
30
- // Holds the result to be returned
31
- const result = formatParts.reduce((acc, part, index) => {
32
- const value = valueParts[index];
33
-
34
- if ((/m/iu).test(part)) {
35
- acc.month = value;
36
- } else if ((/d/iu).test(part)) {
37
- acc.day = value;
38
- } else if ((/y/iu).test(part)) {
39
- acc.year = value;
40
- }
41
-
42
- return acc;
43
- }, {});
44
-
45
- // If we found all the parts, return the result
46
- if (result.month && result.year) {
47
- return result;
48
- }
49
-
50
- // Throw an error to let the dev know we were unable to parse the date string
51
- throw new Error('AuroDatepickerUtilities | parseDate: Unable to parse date string');
52
- };
53
-
54
- /**
55
- * Convert a date object to string format.
56
- * @param {Object} date - Date to convert to string.
57
- * @param {String} locale - Optional locale to use for the date string. Defaults to user's locale.
58
- * @returns {String} Returns the date as a string.
59
- */
60
- this.getDateAsString = (date, locale = undefined) => date.toLocaleDateString(locale, {
61
- year: "numeric",
62
- month: "2-digit",
63
- day: "2-digit",
64
- });
65
-
66
- /**
67
- * Converts a date string to a North American date format.
68
- * @param {String} dateStr - Date to validate.
69
- * @param {String} format - Date format to validate against.
70
- * @returns {Boolean}
71
- */
72
- this.toNorthAmericanFormat = (dateStr, format) => {
73
-
74
- if (format === 'mm/dd/yyyy') {
75
- return dateStr;
76
- }
77
-
78
- const parsedDate = this.parseDate(dateStr, format);
79
-
80
- if (!parsedDate) {
81
- throw new Error('AuroDatepickerUtilities | toNorthAmericanFormat: Unable to parse date string');
82
- }
83
-
84
- const { month, day, year } = parsedDate;
85
-
86
- const dateParts = [];
87
- if (month) {
88
- dateParts.push(month);
89
- }
90
-
91
- if (day) {
92
- dateParts.push(day);
93
- }
94
-
95
- if (year) {
96
- dateParts.push(year);
97
- }
98
-
99
- return dateParts.join('/');
100
- };
1
+ /**
2
+ * @description Splits a date string into its parts according to the provided format. Does NOT validate that the result is a real calendar date — use `parseDate` when validation is required.
3
+ * @param {string} dateStr - Date string to parse.
4
+ * @param {string} format - Date format to parse.
5
+ * @returns {{ month?: string, day?: string, year?: string }|undefined}
6
+ */
7
+ function getDateParts(dateStr, format) {
8
+ if (!dateStr) {
9
+ return undefined;
10
+ }
11
+
12
+ const formatSeparatorMatch = format.match(/[/.-]/);
13
+ let valueParts;
14
+ let formatParts;
15
+
16
+ if (formatSeparatorMatch) {
17
+ const separator = formatSeparatorMatch[0];
18
+ valueParts = dateStr.split(separator);
19
+ formatParts = format.split(separator);
20
+ } else {
21
+ if (dateStr.match(/[/.-]/)) {
22
+ throw new Error(
23
+ "AuroDatepickerUtilities | parseDate: Date string has no separators",
24
+ );
25
+ }
26
+
27
+ if (dateStr.length !== format.length) {
28
+ throw new Error(
29
+ "AuroDatepickerUtilities | parseDate: Date string and format length do not match",
30
+ );
31
+ }
32
+
33
+ valueParts = [dateStr];
34
+ formatParts = [format];
35
+ }
36
+
37
+ if (valueParts.length !== formatParts.length) {
38
+ throw new Error(
39
+ `AuroDatepickerUtilities | parseDate: Date string and format do not match : ${dateStr} vs ${format}`,
40
+ );
101
41
  }
102
- };
103
42
 
104
- export const dateFormatter = new DateFormatter();
43
+ const result = formatParts.reduce((acc, part, index) => {
44
+ const value = valueParts[index];
45
+
46
+ if (/m/iu.test(part) && part.length === value.length) {
47
+ acc.month = value;
48
+ } else if (/d/iu.test(part) && part.length === value.length) {
49
+ acc.day = value;
50
+ } else if (/y/iu.test(part) && part.length === value.length) {
51
+ acc.year = value;
52
+ }
53
+
54
+ return acc;
55
+ }, {});
56
+
57
+ if (!result.month && !result.day && !result.year) {
58
+ throw new Error(
59
+ "AuroDatepickerUtilities | parseDate: Unable to parse date string",
60
+ );
61
+ }
62
+
63
+ return result;
64
+ }
65
+
66
+ function isCalendarDate(year, month, day) {
67
+ let yearNumber = Number(year);
68
+ const monthNumber = Number(month);
69
+ const dayNumber = Number(day);
70
+
71
+ if (
72
+ !Number.isInteger(yearNumber) ||
73
+ !Number.isInteger(monthNumber) ||
74
+ !Number.isInteger(dayNumber)
75
+ ) {
76
+ return false;
77
+ }
78
+
79
+ // Handle 2-digit years by converting them to 4-digit years based on a cutoff. This allows for parsing of 2-digit year formats while still validating the resulting date.
80
+ if (yearNumber < 100 && yearNumber >= 50) {
81
+ yearNumber += 1900;
82
+ } else if (yearNumber < 50) {
83
+ yearNumber += 2000;
84
+ }
85
+
86
+ const stringified = `${String(yearNumber).padStart(4, "0")}-${String(monthNumber).padStart(2, "0")}-${String(dayNumber).padStart(2, "0")}`;
87
+ const date = new Date(stringified.replace(/[.-]/g, "/"));
88
+
89
+ return (
90
+ !Number.isNaN(date.getTime()) && toISOFormatString(date) === stringified
91
+ );
92
+ }
93
+
94
+ /**
95
+ * @description Parses a date string into its components and validates that the result is a real calendar date. Use `getDateParts` instead when raw splitting without validation is needed (e.g. for in-progress input).
96
+ *
97
+ * Partial formats are supported: components absent from `format` default to `year → "0"`,
98
+ * `month → "01"`, `day → "01"` for calendar validation only. The returned object contains
99
+ * only the fields actually present in the format string — missing fields are never injected.
100
+ * @param {string} dateStr - Date string to parse.
101
+ * @param {string} format - Date format to parse.
102
+ * @returns {{ month?: string, day?: string, year?: string }|undefined}
103
+ * @throws {Error} Throws when the parsed result does not represent a valid calendar date.
104
+ */
105
+ function parseDate(dateStr, format = "mm/dd/yyyy") {
106
+ if (!dateStr || !format) {
107
+ return undefined;
108
+ }
109
+ const result = getDateParts(dateStr.trim(), format);
110
+
111
+ if (!result) {
112
+ return undefined;
113
+ }
114
+
115
+ const lowerFormat = format.toLowerCase();
116
+ const year = lowerFormat.includes("yy") ? result.year : "0";
117
+ const month = lowerFormat.includes("mm") ? result.month : "01";
118
+ const day = lowerFormat.includes("dd") ? result.day : "01";
119
+
120
+ if (isCalendarDate(year, month, day)) {
121
+ return result;
122
+ }
123
+
124
+ throw new Error(
125
+ `AuroDatepickerUtilities | parseDate: Date string is not a valid date ${JSON.stringify(result)} with format ${format}`,
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Convert a date object to string format.
131
+ * @param {Object} date - Date to convert to string.
132
+ * @param {String} locale - Optional locale to use for the date string. Defaults to user's locale.
133
+ * @returns {String} Returns the date as a string.
134
+ */
135
+ function getDateAsString(date, locale = undefined) {
136
+ return date.toLocaleDateString(locale, {
137
+ year: "numeric",
138
+ month: "2-digit",
139
+ day: "2-digit",
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Converts a date string to a North American date format.
145
+ * @param {String} dateStr - Date to validate.
146
+ * @param {String} format - Date format to validate against.
147
+ * @returns {String}
148
+ */
149
+ function toNorthAmericanFormat(dateStr, format) {
150
+ if (format === "mm/dd/yyyy") {
151
+ return dateStr;
152
+ }
153
+
154
+ const parsedDate = parseDate(dateStr, format);
155
+
156
+ if (!parsedDate) {
157
+ throw new Error(
158
+ "AuroDatepickerUtilities | toNorthAmericanFormat: Unable to parse date string",
159
+ );
160
+ }
161
+
162
+ const { month, day, year } = parsedDate;
163
+
164
+ return [month, day, year].filter(Boolean).join("/");
165
+ }
166
+
167
+ /**
168
+ * Validates that a date string matches the provided format and represents a real calendar date.
169
+ *
170
+ * @param {string} dateStr - Date string to validate.
171
+ * @param {string} [format="yyyy-mm-dd"] - Format of the date string.
172
+ * @returns {boolean} True when the date string is valid for the provided format, otherwise false.
173
+ */
174
+ function isValidDate(dateStr, format = "yyyy-mm-dd") {
175
+ try {
176
+ if (typeof dateStr !== "string" || !dateStr || format?.length < 8) {
177
+ return false;
178
+ }
179
+
180
+ if (parseDate(dateStr, format)) {
181
+ return true;
182
+ }
183
+ } catch (error) {
184
+ return false;
185
+ }
186
+ return false;
187
+ }
188
+
189
+ /**
190
+ * Converts a JavaScript Date instance to a simple ISO-like date string. This returns only the calendar date portion without any time or timezone information.
191
+ *
192
+ * @param {Date} date - Date instance to convert to an ISO-like string.
193
+ * @returns {string} A string in the format "yyyy-mm-dd" representing the provided date.
194
+ * @throws {Error} Throws an error when the input is not a valid Date instance.
195
+ */
196
+ function toISOFormatString(date) {
197
+ if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
198
+ throw new Error(
199
+ "AuroDatepickerUtilities | toISOFormatString: Input must be a valid Date instance",
200
+ );
201
+ }
202
+ return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
203
+ }
204
+
205
+ /**
206
+ * Converts a date string into a JavaScript Date instance. This method supports ISO formatted strings and other formats that can be parsed by the formatter.
207
+ *
208
+ * @param {String} dateStr - Date string to convert into a Date object.
209
+ * @param {String} format - Date format used to parse the string when it is not in ISO format.
210
+ * @returns {Date|null} Returns a Date instance for valid input or null for non-string input.
211
+ * @throws {Error} Throws when parsing fails for non-ISO string input.
212
+ */
213
+ function stringToDateInstance(dateStr, format = "yyyy-mm-dd") {
214
+ if (typeof dateStr !== "string") {
215
+ return null;
216
+ }
217
+
218
+ const { month, day, year } = parseDate(dateStr, format);
219
+ return new Date(`${year}/${month}/${day}`);
220
+ }
221
+
222
+ export const dateFormatter = {
223
+ parseDate,
224
+ getDateParts,
225
+ getDateAsString,
226
+ toNorthAmericanFormat,
227
+ isValidDate,
228
+ toISOFormatString,
229
+ stringToDateInstance,
230
+ };