@aurodesignsystem/auro-formkit 3.1.0-beta.1 → 3.1.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +9 -3
  2. package/components/checkbox/demo/api.min.js +468 -25
  3. package/components/checkbox/demo/index.min.js +468 -25
  4. package/components/checkbox/dist/index.js +468 -25
  5. package/components/checkbox/dist/registered.js +468 -25
  6. package/components/combobox/demo/api.md +1 -1
  7. package/components/combobox/demo/api.min.js +1265 -235
  8. package/components/combobox/demo/index.min.js +1265 -235
  9. package/components/combobox/dist/auro-combobox.d.ts +32 -5
  10. package/components/combobox/dist/index.js +1130 -100
  11. package/components/combobox/dist/registered.js +1130 -100
  12. package/components/counter/demo/api.md +1 -1
  13. package/components/counter/demo/api.min.js +575 -71
  14. package/components/counter/demo/index.min.js +575 -71
  15. package/components/counter/dist/auro-counter-group.d.ts +2 -5
  16. package/components/counter/dist/index.js +575 -71
  17. package/components/counter/dist/registered.js +575 -71
  18. package/components/datepicker/demo/api.md +0 -1
  19. package/components/datepicker/demo/api.min.js +1077 -106
  20. package/components/datepicker/demo/index.min.js +1077 -106
  21. package/components/datepicker/dist/auro-datepicker.d.ts +0 -13
  22. package/components/datepicker/dist/index.js +1077 -106
  23. package/components/datepicker/dist/registered.js +1077 -106
  24. package/components/dropdown/demo/api.md +9 -6
  25. package/components/dropdown/demo/api.min.js +107 -43
  26. package/components/dropdown/demo/index.md +0 -83
  27. package/components/dropdown/demo/index.min.js +107 -43
  28. package/components/dropdown/dist/auro-dropdown.d.ts +30 -12
  29. package/components/dropdown/dist/index.js +107 -43
  30. package/components/dropdown/dist/registered.js +107 -43
  31. package/components/input/demo/api.md +4 -1
  32. package/components/input/demo/api.min.js +503 -25
  33. package/components/input/demo/index.min.js +503 -25
  34. package/components/input/dist/base-input.d.ts +24 -0
  35. package/components/input/dist/index.js +503 -25
  36. package/components/input/dist/registered.js +503 -25
  37. package/components/radio/demo/api.min.js +468 -25
  38. package/components/radio/demo/index.min.js +468 -25
  39. package/components/radio/dist/index.js +468 -25
  40. package/components/radio/dist/registered.js +468 -25
  41. package/components/select/demo/api.md +1 -1
  42. package/components/select/demo/api.min.js +575 -71
  43. package/components/select/demo/index.md +1 -46
  44. package/components/select/demo/index.min.js +575 -71
  45. package/components/select/dist/auro-select.d.ts +2 -5
  46. package/components/select/dist/index.js +575 -71
  47. package/components/select/dist/registered.js +575 -71
  48. package/package.json +2 -2
  49. package/components/form/demo/autocomplete.html +0 -15
@@ -3,6 +3,414 @@ import { classMap } from 'lit/directives/class-map.js';
3
3
  import { unsafeStatic, literal, html } from 'lit/static-html.js';
4
4
  import { ifDefined } from 'lit/directives/if-defined.js';
5
5
 
6
+ class DateFormatter {
7
+
8
+ constructor() {
9
+
10
+ /**
11
+ * @description Parses a date string into its components.
12
+ * @param {string} dateStr - Date string to parse.
13
+ * @param {string} format - Date format to parse.
14
+ * @returns {Object<key["month" | "day" | "year"]: number>|undefined}
15
+ */
16
+ this.parseDate = (dateStr, format = 'mm/dd/yyyy') => {
17
+
18
+ // Guard Clause: Date string is defined
19
+ if (!dateStr) {
20
+ return undefined;
21
+ }
22
+
23
+ // Assume the separator is a "/" a defined in our code base
24
+ const separator = '/';
25
+
26
+ // Get the parts of the date and format
27
+ const valueParts = dateStr.split(separator);
28
+ const formatParts = format.split(separator);
29
+
30
+ // Check if the value and format have the correct number of parts
31
+ if (valueParts.length !== formatParts.length) {
32
+ throw new Error('AuroDatepickerUtilities | parseDate: Date string and format length do not match');
33
+ }
34
+
35
+ // Holds the result to be returned
36
+ const result = formatParts.reduce((acc, part, index) => {
37
+ const value = valueParts[index];
38
+
39
+ if ((/m/iu).test(part)) {
40
+ acc.month = value;
41
+ } else if ((/d/iu).test(part)) {
42
+ acc.day = value;
43
+ } else if ((/y/iu).test(part)) {
44
+ acc.year = value;
45
+ }
46
+
47
+ return acc;
48
+ }, {});
49
+
50
+ // If we found all the parts, return the result
51
+ if (result.month && result.year) {
52
+ return result;
53
+ }
54
+
55
+ // Throw an error to let the dev know we were unable to parse the date string
56
+ throw new Error('AuroDatepickerUtilities | parseDate: Unable to parse date string');
57
+ };
58
+
59
+ /**
60
+ * Convert a date object to string format.
61
+ * @param {Object} date - Date to convert to string.
62
+ * @returns {Object} Returns the date as a string.
63
+ */
64
+ this.getDateAsString = (date) => date.toLocaleDateString(undefined, {
65
+ year: "numeric",
66
+ month: "2-digit",
67
+ day: "2-digit",
68
+ });
69
+
70
+ /**
71
+ * Converts a date string to a North American date format.
72
+ * @param {String} dateStr - Date to validate.
73
+ * @param {String} format - Date format to validate against.
74
+ * @returns {Boolean}
75
+ */
76
+ this.toNorthAmericanFormat = (dateStr, format) => {
77
+
78
+ if (format === 'mm/dd/yyyy') {
79
+ return dateStr;
80
+ }
81
+
82
+ const parsedDate = this.parseDate(dateStr, format);
83
+
84
+ if (!parsedDate) {
85
+ throw new Error('AuroDatepickerUtilities | toNorthAmericanFormat: Unable to parse date string');
86
+ }
87
+
88
+ const { month, day, year } = parsedDate;
89
+
90
+ const dateParts = [];
91
+ if (month) {
92
+ dateParts.push(month);
93
+ }
94
+
95
+ if (day) {
96
+ dateParts.push(day);
97
+ }
98
+
99
+ if (year) {
100
+ dateParts.push(year);
101
+ }
102
+
103
+ return dateParts.join('/');
104
+ };
105
+ }
106
+ }
107
+ const dateFormatter = new DateFormatter();
108
+
109
+ // filepath: dateConstraints.mjs
110
+ const DATE_UTIL_CONSTRAINTS = {
111
+ maxDay: 31,
112
+ maxMonth: 12,
113
+ maxYear: 2400,
114
+ minDay: 1,
115
+ minMonth: 1,
116
+ minYear: 1900,
117
+ };
118
+
119
+ class AuroDateUtilitiesBase {
120
+
121
+ /**
122
+ * @description The maximum day value allowed by the various utilities in this class.
123
+ * @readonly
124
+ * @type {Number}
125
+ */
126
+ get maxDay() {
127
+ return DATE_UTIL_CONSTRAINTS.maxDay;
128
+ }
129
+
130
+ /**
131
+ * @description The maximum month value allowed by the various utilities in this class.
132
+ * @readonly
133
+ * @type {Number}
134
+ */
135
+ get maxMonth() {
136
+ return DATE_UTIL_CONSTRAINTS.maxMonth;
137
+ }
138
+
139
+ /**
140
+ * @description The maximum year value allowed by the various utilities in this class.
141
+ * @readonly
142
+ * @type {Number}
143
+ */
144
+ get maxYear() {
145
+ return DATE_UTIL_CONSTRAINTS.maxYear;
146
+ }
147
+
148
+ /**
149
+ * @description The minimum day value allowed by the various utilities in this class.
150
+ * @readonly
151
+ * @type {Number}
152
+ */
153
+ get minDay() {
154
+ return DATE_UTIL_CONSTRAINTS.minDay;
155
+ }
156
+
157
+ /**
158
+ * @description The minimum month value allowed by the various utilities in this class.
159
+ * @readonly
160
+ * @type {Number}
161
+ */
162
+ get minMonth() {
163
+ return DATE_UTIL_CONSTRAINTS.minMonth;
164
+ }
165
+
166
+ /**
167
+ * @description The minimum year value allowed by the various utilities in this class.
168
+ * @readonly
169
+ * @type {Number}
170
+ */
171
+ get minYear() {
172
+ return DATE_UTIL_CONSTRAINTS.minYear;
173
+ }
174
+ }
175
+
176
+ /* eslint-disable no-magic-numbers */
177
+
178
+ class AuroDateUtilities extends AuroDateUtilitiesBase {
179
+
180
+ /**
181
+ * Returns the current century.
182
+ * @returns {String} The current century.
183
+ */
184
+ getCentury () {
185
+ return String(new Date().getFullYear()).slice(0, 2);
186
+ }
187
+
188
+ /**
189
+ * Returns a four digit year.
190
+ * @param {String} year - The year to convert to four digits.
191
+ * @returns {String} The four digit year.
192
+ */
193
+ getFourDigitYear (year) {
194
+
195
+ const strYear = String(year).trim();
196
+ return strYear.length <= 2 ? this.getCentury() + strYear : strYear;
197
+ }
198
+
199
+ constructor() {
200
+
201
+ super();
202
+
203
+ /**
204
+ * Compares two dates to see if they match.
205
+ * @param {Object} date1 - First date to compare.
206
+ * @param {Object} date2 - Second date to compare.
207
+ * @returns {Boolean} Returns true if the dates match.
208
+ */
209
+ this.datesMatch = (date1, date2) => new Date(date1).getTime() === new Date(date2).getTime();
210
+
211
+ /**
212
+ * Returns true if value passed in is a valid date.
213
+ * @param {String} date - Date to validate.
214
+ * @param {String} format - Date format to validate against.
215
+ * @returns {Boolean}
216
+ */
217
+ this.validDateStr = (date, format) => {
218
+
219
+ // The length we expect the date string to be
220
+ const dateStrLength = format.length;
221
+
222
+ // Guard Clause: Date and format are defined
223
+ if (typeof date === "undefined" || typeof format === "undefined") {
224
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date and format are required');
225
+ }
226
+
227
+ // Guard Clause: Date should be of type string
228
+ if (typeof date !== "string") {
229
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date must be a string');
230
+ }
231
+
232
+ // Guard Clause: Format should be of type string
233
+ if (typeof format !== "string") {
234
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Format must be a string');
235
+ }
236
+
237
+ // Guard Clause: Length is what we expect it to be
238
+ if (date.length !== dateStrLength) {
239
+ return false;
240
+ }
241
+ // Get a formatted date string and parse it
242
+ const dateParts = dateFormatter.parseDate(date, format);
243
+
244
+ // Guard Clause: Date parse succeeded
245
+ if (!dateParts) {
246
+ return false;
247
+ }
248
+
249
+ // Create the expected date string based on the date parts
250
+ const expectedDateStr = `${dateParts.month}/${dateParts.day || "01"}/${this.getFourDigitYear(dateParts.year)}`;
251
+
252
+ // Generate a date object that we will extract a string date from to compare to the passed in date string
253
+ const dateObj = new Date(this.getFourDigitYear(dateParts.year), dateParts.month - 1, dateParts.day || 1);
254
+
255
+ // Get the date string of the date object we created from the string date
256
+ const actualDateStr = dateFormatter.getDateAsString(dateObj);
257
+
258
+ // Guard Clause: Generated date matches date string input
259
+ if (expectedDateStr !== actualDateStr) {
260
+ return false;
261
+ }
262
+
263
+ // If we passed all other checks, we can assume the date is valid
264
+ return true;
265
+ };
266
+
267
+ /**
268
+ * Determines if a string date value matches the format provided.
269
+ * @param {string} value = The date string value.
270
+ * @param { string} format = The date format to match against.
271
+ * @returns {boolean}
272
+ */
273
+ this.dateAndFormatMatch = (value, format) => {
274
+
275
+ // Ensure we have both values we need to do the comparison
276
+ if (!value || !format) {
277
+ throw new Error('AuroFormValidation | dateFormatMatch: value and format are required');
278
+ }
279
+
280
+ // If the lengths are different, they cannot match
281
+ if (value.length !== format.length) {
282
+ return false;
283
+ }
284
+
285
+ // Get the parts of the date
286
+ const dateParts = dateFormatter.parseDate(value, format);
287
+
288
+ // Validator for day
289
+ const dayValueIsValid = (day) => {
290
+
291
+ // Guard clause: if there is no day in the dateParts, we can ignore this check.
292
+ if (!dateParts.day) {
293
+ return true;
294
+ }
295
+
296
+ // Guard clause: ensure day exists.
297
+ if (!day) {
298
+ return false;
299
+ }
300
+
301
+ // Convert day to number
302
+ const numDay = Number.parseInt(day, 10);
303
+
304
+ // Guard clause: ensure day is a valid integer
305
+ if (Number.isNaN(numDay)) {
306
+ throw new Error('AuroDatepickerUtilities | dayValueIsValid: Unable to parse day value integer');
307
+ }
308
+
309
+ // Guard clause: ensure day is within the valid range
310
+ if (numDay < this.minDay || numDay > this.maxDay) {
311
+ return false;
312
+ }
313
+
314
+ // Default return
315
+ return true;
316
+ };
317
+
318
+ // Validator for month
319
+ const monthValueIsValid = (month) => {
320
+
321
+ // Guard clause: ensure month exists.
322
+ if (!month) {
323
+ return false;
324
+ }
325
+
326
+ // Convert month to number
327
+ const numMonth = Number.parseInt(month, 10);
328
+
329
+ // Guard clause: ensure month is a valid integer
330
+ if (Number.isNaN(numMonth)) {
331
+ throw new Error('AuroDatepickerUtilities | monthValueIsValid: Unable to parse month value integer');
332
+ }
333
+
334
+ // Guard clause: ensure month is within the valid range
335
+ if (numMonth < this.minMonth || numMonth > this.maxMonth) {
336
+ return false;
337
+ }
338
+
339
+ // Default return
340
+ return true;
341
+ };
342
+
343
+ // Validator for year
344
+ const yearIsValid = (_year) => {
345
+
346
+ // Guard clause: ensure year exists.
347
+ if (!_year) {
348
+ return false;
349
+ }
350
+
351
+ // Get the full year
352
+ const year = this.getFourDigitYear(_year);
353
+
354
+ // Convert year to number
355
+ const numYear = Number.parseInt(year, 10);
356
+
357
+ // Guard clause: ensure year is a valid integer
358
+ if (Number.isNaN(numYear)) {
359
+ throw new Error('AuroDatepickerUtilities | yearValueIsValid: Unable to parse year value integer');
360
+ }
361
+
362
+ // Guard clause: ensure year is within the valid range
363
+ if (numYear < this.minYear || numYear > this.maxYear) {
364
+ return false;
365
+ }
366
+
367
+ // Default return
368
+ return true;
369
+ };
370
+
371
+ // Self-contained checks for month, day, and year
372
+ const checks = [
373
+ monthValueIsValid(dateParts.month),
374
+ dayValueIsValid(dateParts.day),
375
+ yearIsValid(dateParts.year)
376
+ ];
377
+
378
+ // If any of the checks failed, the date format does not match and the result is invalid
379
+ const isValid = checks.every((check) => check === true);
380
+
381
+ // If the check is invalid, return false
382
+ if (!isValid) {
383
+ return false;
384
+ }
385
+
386
+ // Default case
387
+ return true;
388
+ };
389
+ }
390
+ }
391
+
392
+ // Export a class instance
393
+ const dateUtilities = new AuroDateUtilities();
394
+
395
+ // Export the class instance methods individually
396
+ const {
397
+ datesMatch,
398
+ validDateStr,
399
+ dateAndFormatMatch,
400
+ minDay,
401
+ minMonth,
402
+ minYear,
403
+ maxDay,
404
+ maxMonth,
405
+ maxYear
406
+ } = dateUtilities;
407
+
408
+ const {
409
+ toNorthAmericanFormat,
410
+ parseDate,
411
+ getDateAsString
412
+ } = dateFormatter;
413
+
6
414
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
7
415
  // See LICENSE in the project root for license information.
8
416
 
@@ -78,6 +486,7 @@ let AuroLibraryRuntimeUtils$3 = class AuroLibraryRuntimeUtils {
78
486
 
79
487
 
80
488
  class AuroFormValidation {
489
+
81
490
  constructor() {
82
491
  this.runtimeUtils = new AuroLibraryRuntimeUtils$3();
83
492
  }
@@ -169,17 +578,17 @@ class AuroFormValidation {
169
578
  ]
170
579
  }
171
580
  };
172
-
581
+
173
582
  let elementType;
174
583
  if (this.runtimeUtils.elementMatch(elem, 'auro-input')) {
175
584
  elementType = 'input';
176
585
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-counter') || this.runtimeUtils.elementMatch(elem, 'auro-counter-group')) {
177
586
  elementType = 'counter';
178
587
  }
179
-
588
+
180
589
  if (elementType) {
181
590
  const rules = validationRules[elementType];
182
-
591
+
183
592
  if (rules) {
184
593
  Object.values(rules).flat().forEach(rule => {
185
594
  if (rule.check(elem)) {
@@ -205,48 +614,82 @@ class AuroFormValidation {
205
614
  if (!elem.value.match(emailRegex)) {
206
615
  elem.validity = 'patternMismatch';
207
616
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
617
+ return;
208
618
  }
209
619
  } else if (elem.type === 'credit-card') {
210
620
  if (elem.value.length > 0 && elem.value.length < elem.validationCCLength) {
211
621
  elem.validity = 'tooShort';
212
622
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
623
+ return;
213
624
  }
214
625
  } else if (elem.type === 'number') {
215
626
  if (elem.max !== undefined && Number(elem.max) < Number(elem.value)) {
216
627
  elem.validity = 'rangeOverflow';
217
628
  elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
629
+ return;
218
630
  }
219
631
 
220
632
  if (elem.min !== undefined && elem.value?.length > 0 && Number(elem.min) > Number(elem.value)) {
221
633
  elem.validity = 'rangeUnderflow';
222
634
  elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
635
+ return;
223
636
  }
224
- } else if (elem.type === 'date') {
225
- if (elem.value?.length > 0 && elem.value?.length < elem.lengthForType) {
637
+ } else if (elem.type === 'date' && elem.value?.length > 0) {
638
+
639
+ // Guard Clause: if the value is too short
640
+ if (elem.value.length < elem.lengthForType) {
641
+
226
642
  elem.validity = 'tooShort';
227
643
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
228
- } else if (elem.value?.length === elem.lengthForType && elem.util.toNorthAmericanFormat(elem.value, elem.format)) {
229
- const formattedValue = elem.util.toNorthAmericanFormat(elem.value, elem.format);
230
- const valueDate = new Date(formattedValue.dateForComparison);
644
+ return;
645
+ }
646
+
647
+ // Guard Clause: If the value is too long for the type
648
+ if (elem.value?.length > elem.lengthForType) {
649
+
650
+ elem.validity = 'tooLong';
651
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
652
+ return;
653
+ }
654
+
655
+ // Validate that the date passed was the correct format
656
+ if (!dateAndFormatMatch(elem.value, elem.format)) {
657
+ elem.validity = 'patternMismatch';
658
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || 'Invalid Date Format Entered';
659
+ return;
660
+ }
661
+
662
+ // Validate that the date passed was a valid date
663
+ if (!validDateStr(elem.value, elem.format)) {
664
+ elem.validity = 'invalidDate';
665
+ elem.errorMessage = elem.setCustomValidityInvalidDate || elem.setCustomValidity || 'Invalid Date Entered';
666
+ return;
667
+ }
231
668
 
232
- // validate max
233
- if (elem.max?.length === elem.lengthForType) {
234
- const maxDate = new Date(elem.util.toNorthAmericanFormat(elem.max, elem.format).dateForComparison);
669
+ // Perform the rest of the validation
670
+ const formattedValue = toNorthAmericanFormat(elem.value, elem.format);
671
+ const valueDate = new Date(formattedValue);
235
672
 
236
- if (valueDate > maxDate) {
237
- elem.validity = 'rangeOverflow';
238
- elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
239
- }
673
+ // // Validate max date
674
+ if (elem.max?.length === elem.lengthForType) {
675
+
676
+ const maxDate = new Date(toNorthAmericanFormat(elem.max, elem.format));
677
+
678
+ if (valueDate > maxDate) {
679
+ elem.validity = 'rangeOverflow';
680
+ elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
681
+ return;
240
682
  }
683
+ }
241
684
 
242
- // validate min
243
- if (elem.min?.length === elem.lengthForType) {
244
- const minDate = new Date(elem.util.toNorthAmericanFormat(elem.min, elem.format).dateForComparison);
685
+ // Validate min date
686
+ if (elem.min?.length === elem.lengthForType) {
687
+ const minDate = new Date(toNorthAmericanFormat(elem.min, elem.format));
245
688
 
246
- if (valueDate < minDate) {
247
- elem.validity = 'rangeUnderflow';
248
- elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
249
- }
689
+ if (valueDate < minDate) {
690
+ elem.validity = 'rangeUnderflow';
691
+ elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
692
+ return;
250
693
  }
251
694
  }
252
695
  }
@@ -365,7 +808,7 @@ class AuroFormValidation {
365
808
  if (input.validationMessage.length > 0) {
366
809
  elem.errorMessage = input.validationMessage;
367
810
  }
368
- } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
811
+ } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
369
812
  const firstInput = this.inputElements[0];
370
813
 
371
814
  if (firstInput.validationMessage.length > 0) {
@@ -2267,7 +2710,7 @@ class AuroFloatingUI {
2267
2710
  /**
2268
2711
  * @private
2269
2712
  * getting called on 'blur' in trigger or `focusin` in document
2270
- *
2713
+ *
2271
2714
  * Hides the bib if focus moves outside of the trigger or bib, unless a 'noHideOnThisFocusLoss' flag is set.
2272
2715
  * This method checks if the currently active element is still within the trigger or bib.
2273
2716
  * If not, and if the bib isn't in fullscreen mode with focus lost, it hides the bib.
@@ -2383,7 +2826,7 @@ class AuroFloatingUI {
2383
2826
  // Close any other dropdown that is already open
2384
2827
  const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
2385
2828
  if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this &&
2386
- existedVisibleFloatingUI.isPopoverVisible &&
2829
+ existedVisibleFloatingUI.element.isPopoverVisible &&
2387
2830
  document.expandedAuroFloater.eventPrefix === this.eventPrefix) {
2388
2831
  document.expandedAuroFloater.hideBib();
2389
2832
  }
@@ -2559,7 +3002,7 @@ class AuroFloatingUI {
2559
3002
  this.id = window.crypto.randomUUID();
2560
3003
  this.element.setAttribute('id', this.id);
2561
3004
  }
2562
-
3005
+
2563
3006
  this.element.bib.setAttribute("id", `${this.id}-floater-bib`);
2564
3007
  }
2565
3008
 
@@ -2612,7 +3055,7 @@ class AuroFloatingUI {
2612
3055
  if (this.element.bib) {
2613
3056
  this.element.shadowRoot.append(this.element.bib);
2614
3057
  }
2615
-
3058
+
2616
3059
  // Remove event & keyboard listeners
2617
3060
  if (this.element?.trigger) {
2618
3061
  this.element.trigger.removeEventListener('keydown', this.handleEvent);
@@ -3002,7 +3445,6 @@ var tokensCss$1 = css`:host{--ds-auro-dropdown-label-text-color: var(--ds-basic-
3002
3445
 
3003
3446
  const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-';
3004
3447
  const DESIGN_TOKEN_BREAKPOINT_OPTIONS = [
3005
- 'xl',
3006
3448
  'lg',
3007
3449
  'md',
3008
3450
  'sm',
@@ -3074,7 +3516,6 @@ class AuroDropdownBib extends LitElement {
3074
3516
 
3075
3517
  set mobileFullscreenBreakpoint(value) {
3076
3518
  // verify the defined breakpoint is valid and exit out if not
3077
- // 'disabled' is a design token breakpoint so it acts as our "undefined" value
3078
3519
  const validatedValue = DESIGN_TOKEN_BREAKPOINT_OPTIONS.includes(value) ? value : undefined;
3079
3520
  if (!validatedValue) {
3080
3521
  this._mobileBreakpointValue = undefined;
@@ -3343,6 +3784,7 @@ var helpTextVersion = '1.0.0';
3343
3784
  * @csspart helpText - The helpText content container.
3344
3785
  * @event auroDropdown-triggerClick - Notifies that the trigger has been clicked.
3345
3786
  * @event auroDropdown-toggled - Notifies that the visibility of the dropdown bib has changed.
3787
+ * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
3346
3788
  */
3347
3789
  class AuroDropdown extends LitElement {
3348
3790
  constructor() {
@@ -3388,7 +3830,9 @@ class AuroDropdown extends LitElement {
3388
3830
  this.rounded = false;
3389
3831
  this.tabIndex = 0;
3390
3832
  this.noToggle = false;
3833
+ this.a11yAutocomplete = 'none';
3391
3834
  this.labeled = true;
3835
+ this.a11yRole = 'combobox';
3392
3836
  this.onDark = false;
3393
3837
 
3394
3838
  // floaterConfig
@@ -3524,6 +3968,16 @@ class AuroDropdown extends LitElement {
3524
3968
  type: Number
3525
3969
  },
3526
3970
 
3971
+ /**
3972
+ * The unique ID for the dropdown bib element.
3973
+ * @private
3974
+ */
3975
+ dropdownId: {
3976
+ type: String,
3977
+ reflect: false,
3978
+ attribute: false
3979
+ },
3980
+
3527
3981
  /**
3528
3982
  * If declared in combination with `bordered` property or `helpText` slot content, will apply red color to both.
3529
3983
  */
@@ -3587,12 +4041,7 @@ class AuroDropdown extends LitElement {
3587
4041
  },
3588
4042
 
3589
4043
  /**
3590
- * Defines the screen size breakpoint (`xs`, `sm`, `md`, `lg`, `xl`, `disabled`)
3591
- * at which the dropdown switches to fullscreen mode on mobile. `disabled` indicates a dropdown should _never_ enter fullscreen.
3592
- *
3593
- * When expanded, the dropdown will automatically display in fullscreen mode
3594
- * if the screen size is equal to or smaller than the selected breakpoint.
3595
- * @default sm
4044
+ * Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint.
3596
4045
  */
3597
4046
  fullscreenBreakpoint: {
3598
4047
  type: String,
@@ -3691,6 +4140,23 @@ class AuroDropdown extends LitElement {
3691
4140
  */
3692
4141
  tabIndex: {
3693
4142
  type: Number
4143
+ },
4144
+
4145
+ /**
4146
+ * The value for the role attribute of the trigger element.
4147
+ */
4148
+ a11yRole: {
4149
+ type: String || undefined,
4150
+ attribute: false,
4151
+ reflect: false
4152
+ },
4153
+
4154
+ /**
4155
+ * The value for the aria-autocomplete attribute of the trigger element.
4156
+ */
4157
+ a11yAutocomplete: {
4158
+ type: String,
4159
+ attribute: false,
3694
4160
  }
3695
4161
  };
3696
4162
  }
@@ -3715,15 +4181,6 @@ class AuroDropdown extends LitElement {
3715
4181
  AuroLibraryRuntimeUtils$1$1.prototype.registerComponent(name, AuroDropdown);
3716
4182
  }
3717
4183
 
3718
- /**
3719
- * Accessor for reusing the focusable entity query string.
3720
- * @private
3721
- * @returns {string}
3722
- */
3723
- get focusableEntityQuery () {
3724
- return 'auro-input, [auro-input], auro-button, [auro-button], button, input';
3725
- }
3726
-
3727
4184
  connectedCallback() {
3728
4185
  super.connectedCallback();
3729
4186
  }
@@ -3737,8 +4194,6 @@ class AuroDropdown extends LitElement {
3737
4194
  updated(changedProperties) {
3738
4195
  this.floater.handleUpdate(changedProperties);
3739
4196
 
3740
- // Note: `disabled` is not a breakpoint (it is not a screen size),
3741
- // so it looks like we never consume this - however, dropdownBib handles this in the setter as "undefined"
3742
4197
  if (changedProperties.has('fullscreenBreakpoint')) {
3743
4198
  this.bibContent.mobileFullscreenBreakpoint = this.fullscreenBreakpoint;
3744
4199
  }
@@ -3752,7 +4207,22 @@ class AuroDropdown extends LitElement {
3752
4207
  }
3753
4208
 
3754
4209
  firstUpdated() {
4210
+
4211
+ // Configure the floater to, this will generate the ID for the bib
3755
4212
  this.floater.configure(this, 'auroDropdown');
4213
+
4214
+ /**
4215
+ * @description Let subscribers know that the dropdown ID ha been generated and added.
4216
+ * @event auroDropdown-idAdded
4217
+ * @type {Object<key: 'id', value: string>} - The ID of the dropdown bib element.
4218
+ */
4219
+ this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
4220
+
4221
+ // Set the bib ID locally if the user hasn't provided a focusable trigger
4222
+ if (!this.triggerContentFocusable) {
4223
+ this.dropdownId = this.floater.element.id;
4224
+ }
4225
+
3756
4226
  this.bibContent = this.floater.element.bib;
3757
4227
 
3758
4228
  // Add the tag name as an attribute if it is different than the component name
@@ -3874,7 +4344,7 @@ class AuroDropdown extends LitElement {
3874
4344
 
3875
4345
  this.triggerContentSlot.forEach((node) => {
3876
4346
  if (node.querySelectorAll) {
3877
- const auroElements = node.querySelectorAll(this.focusableEntityQuery);
4347
+ const auroElements = node.querySelectorAll('auro-input, [auro-input], auro-button, [auro-button], button, input');
3878
4348
  auroElements.forEach((auroEl) => {
3879
4349
  auroEl.addEventListener('focus', this.bindFocusEventToTrigger);
3880
4350
  auroEl.addEventListener('blur', this.bindFocusEventToTrigger);
@@ -3895,7 +4365,7 @@ class AuroDropdown extends LitElement {
3895
4365
 
3896
4366
  this.triggerContentSlot.forEach((node) => {
3897
4367
  if (node.querySelectorAll) {
3898
- const auroElements = node.querySelectorAll(this.focusableEntityQuery);
4368
+ const auroElements = node.querySelectorAll('auro-input, [auro-input], auro-button, [auro-button], button, input');
3899
4369
  auroElements.forEach((auroEl) => {
3900
4370
  auroEl.removeEventListener('focus', this.bindFocusEventToTrigger);
3901
4371
  auroEl.removeEventListener('blur', this.bindFocusEventToTrigger);
@@ -3904,6 +4374,30 @@ class AuroDropdown extends LitElement {
3904
4374
  });
3905
4375
  }
3906
4376
 
4377
+ /*
4378
+ * Sets aria attributes for the trigger element if a custom one is passed in.
4379
+ * @private
4380
+ * @method setTriggerAriaAttributes
4381
+ * @param { HTMLElement } triggerElement - The custom trigger element.
4382
+ */
4383
+ clearTriggerA11yAttributes(triggerElement) {
4384
+
4385
+ if (!triggerElement || !triggerElement.removeAttribute) {
4386
+ return;
4387
+ }
4388
+
4389
+ // Reset appropriate attributes for a11y
4390
+ triggerElement.removeAttribute('aria-labelledby');
4391
+ if (triggerElement.getAttribute('id') === `${this.id}-trigger-element`) {
4392
+ triggerElement.removeAttribute('id');
4393
+ }
4394
+ triggerElement.removeAttribute('role');
4395
+ triggerElement.removeAttribute('aria-expanded');
4396
+
4397
+ triggerElement.removeAttribute('aria-controls');
4398
+ triggerElement.removeAttribute('aria-autocomplete');
4399
+ }
4400
+
3907
4401
  /**
3908
4402
  * Handles changes to the trigger content slot and updates related properties.
3909
4403
  *
@@ -3917,32 +4411,41 @@ class AuroDropdown extends LitElement {
3917
4411
  * @returns {void}
3918
4412
  */
3919
4413
  handleTriggerContentSlotChange(event) {
4414
+
3920
4415
  this.floater.handleTriggerTabIndex();
3921
4416
 
4417
+ // Get the trigger
4418
+ const trigger = this.shadowRoot.querySelector('#trigger');
4419
+
4420
+ // Get the trigger slot
3922
4421
  const triggerSlot = this.shadowRoot.querySelector('.triggerContent slot');
3923
4422
 
4423
+ // If there's a trigger slot
3924
4424
  if (triggerSlot) {
3925
4425
 
4426
+ // Get the content nodes to see if there are any children
3926
4427
  const triggerContentNodes = triggerSlot.assignedNodes();
3927
4428
 
4429
+ // If there are children
3928
4430
  if (triggerContentNodes) {
3929
4431
 
3930
- triggerContentNodes.forEach((node) => {
3931
- if (!this.triggerContentFocusable) {
3932
- this.triggerContentFocusable = this.containsFocusableElement(node);
3933
- }
3934
- });
3935
- }
3936
- }
4432
+ // See if any of them are focusable elemeents
4433
+ this.triggerContentFocusable = triggerContentNodes.some((node) => this.containsFocusableElement(node));
3937
4434
 
3938
- const trigger = this.shadowRoot.querySelector('#trigger');
4435
+ // If any of them are focusable elements
4436
+ if (this.triggerContentFocusable) {
3939
4437
 
3940
- if (!this.triggerContentFocusable) {
3941
- trigger.setAttribute('tabindex', '0');
3942
- trigger.setAttribute('role', 'button');
3943
- } else {
3944
- trigger.removeAttribute('tabindex');
3945
- trigger.removeAttribute('role');
4438
+ // Assume the consumer will be providing their own a11y in whatever they passed in
4439
+ this.clearTriggerA11yAttributes(trigger);
4440
+
4441
+ // Remove the tabindex from the trigger so it doesn't interrupt focus flow
4442
+ trigger.removeAttribute('tabindex');
4443
+ } else {
4444
+
4445
+ // Add the tabindex to the trigger so that it's in the focus flow
4446
+ trigger.setAttribute('tabindex', '0');
4447
+ }
4448
+ }
3946
4449
  }
3947
4450
 
3948
4451
  if (event) {
@@ -3952,6 +4455,7 @@ class AuroDropdown extends LitElement {
3952
4455
 
3953
4456
  if (this.triggerContentSlot) {
3954
4457
  this.setupTriggerFocusEventBinding();
4458
+
3955
4459
  this.hasTriggerContent = this.triggerContentSlot.some((slot) => {
3956
4460
  if (slot.textContent.trim()) {
3957
4461
  return true;
@@ -4019,10 +4523,13 @@ class AuroDropdown extends LitElement {
4019
4523
  id="trigger"
4020
4524
  class="trigger"
4021
4525
  part="trigger"
4022
- aria-labelledby="triggerLabel"
4023
4526
  tabindex="${this.tabIndex}"
4024
4527
  ?showBorder="${this.showTriggerBorders}"
4025
- >
4528
+ role="${ifDefined(this.triggerContentFocusable ? undefined : this.a11yRole)}"
4529
+ aria-expanded="${ifDefined(this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
4530
+ aria-controls="${ifDefined(this.triggerContentFocusable ? undefined : this.dropdownId)}"
4531
+ aria-labelledby="${ifDefined(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
4532
+ >
4026
4533
  <div class="triggerContentWrapper">
4027
4534
  <label class="label" id="triggerLabel" hasTrigger=${this.hasTriggerContent}>
4028
4535
  <slot name="label" @slotchange="${this.handleLabelSlotChange}"></slot>
@@ -4056,12 +4563,12 @@ class AuroDropdown extends LitElement {
4056
4563
  <div id="bibSizer" part="size"></div>
4057
4564
  <${this.dropdownBibTag}
4058
4565
  id="bib"
4059
- role="tooltip"
4060
4566
  ?data-show="${this.isPopoverVisible}"
4061
4567
  ?isfullscreen="${this.isBibFullscreen}"
4062
4568
  ?common="${this.common}"
4063
4569
  ?rounded="${this.common || this.rounded}"
4064
- ?inset="${this.common || this.inset}">
4570
+ ?inset="${this.common || this.inset}"
4571
+ >
4065
4572
  </${this.dropdownBibTag}>
4066
4573
  </div>
4067
4574
  `;
@@ -5082,11 +5589,8 @@ class AuroSelect extends LitElement {
5082
5589
  },
5083
5590
 
5084
5591
  /**
5085
- * Defines the screen size breakpoint (`xs`, `sm`, `md`, `lg`, `xl`, `disabled`)
5086
- * at which the dropdown switches to fullscreen mode on mobile. `disabled` indicates a dropdown should _never_ enter fullscreen.
5087
- *
5088
- * When expanded, the dropdown will automatically display in fullscreen mode
5089
- * if the screen size is equal to or smaller than the selected breakpoint.
5592
+ * Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile.
5593
+ * When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint.
5090
5594
  * @default sm
5091
5595
  */
5092
5596
  fullscreenBreakpoint: {