@aurodesignsystem/auro-formkit 3.1.0-beta.1 → 3.2.0-beta.1

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 (59) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/components/checkbox/README.md +1 -1
  3. package/components/checkbox/demo/api.min.js +468 -25
  4. package/components/checkbox/demo/index.min.js +468 -25
  5. package/components/checkbox/demo/readme.md +1 -1
  6. package/components/checkbox/dist/index.js +468 -25
  7. package/components/checkbox/dist/registered.js +468 -25
  8. package/components/combobox/README.md +1 -1
  9. package/components/combobox/demo/api.min.js +1125 -74
  10. package/components/combobox/demo/index.min.js +1125 -74
  11. package/components/combobox/demo/readme.md +1 -1
  12. package/components/combobox/dist/auro-combobox.d.ts +30 -0
  13. package/components/combobox/dist/index.js +1125 -74
  14. package/components/combobox/dist/registered.js +1125 -74
  15. package/components/counter/README.md +1 -1
  16. package/components/counter/demo/api.min.js +570 -45
  17. package/components/counter/demo/index.min.js +570 -45
  18. package/components/counter/demo/readme.md +1 -1
  19. package/components/counter/dist/index.js +570 -45
  20. package/components/counter/dist/registered.js +570 -45
  21. package/components/datepicker/README.md +1 -1
  22. package/components/datepicker/demo/api.min.js +1073 -70
  23. package/components/datepicker/demo/index.min.js +1073 -70
  24. package/components/datepicker/demo/readme.md +1 -1
  25. package/components/datepicker/dist/index.js +1073 -70
  26. package/components/datepicker/dist/registered.js +1073 -70
  27. package/components/dropdown/README.md +1 -1
  28. package/components/dropdown/demo/api.md +8 -5
  29. package/components/dropdown/demo/api.min.js +104 -22
  30. package/components/dropdown/demo/index.min.js +104 -22
  31. package/components/dropdown/demo/readme.md +1 -1
  32. package/components/dropdown/dist/auro-dropdown.d.ts +29 -0
  33. package/components/dropdown/dist/index.js +104 -22
  34. package/components/dropdown/dist/registered.js +104 -22
  35. package/components/form/README.md +1 -1
  36. package/components/form/demo/readme.md +1 -1
  37. package/components/input/README.md +1 -1
  38. package/components/input/demo/api.md +4 -1
  39. package/components/input/demo/api.min.js +503 -25
  40. package/components/input/demo/index.min.js +503 -25
  41. package/components/input/demo/readme.md +1 -1
  42. package/components/input/dist/base-input.d.ts +24 -0
  43. package/components/input/dist/index.js +503 -25
  44. package/components/input/dist/registered.js +503 -25
  45. package/components/menu/README.md +1 -1
  46. package/components/menu/demo/readme.md +1 -1
  47. package/components/radio/README.md +1 -1
  48. package/components/radio/demo/api.min.js +468 -25
  49. package/components/radio/demo/index.min.js +468 -25
  50. package/components/radio/demo/readme.md +1 -1
  51. package/components/radio/dist/index.js +468 -25
  52. package/components/radio/dist/registered.js +468 -25
  53. package/components/select/README.md +1 -1
  54. package/components/select/demo/api.min.js +570 -45
  55. package/components/select/demo/index.min.js +570 -45
  56. package/components/select/demo/readme.md +1 -1
  57. package/components/select/dist/index.js +570 -45
  58. package/components/select/dist/registered.js +570 -45
  59. package/package.json +2 -2
@@ -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) {
231
649
 
232
- // validate max
233
- if (elem.max?.length === elem.lengthForType) {
234
- const maxDate = new Date(elem.util.toNorthAmericanFormat(elem.max, elem.format).dateForComparison);
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
+ }
235
668
 
236
- if (valueDate > maxDate) {
237
- elem.validity = 'rangeOverflow';
238
- elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
239
- }
669
+ // Perform the rest of the validation
670
+ const formattedValue = toNorthAmericanFormat(elem.value, elem.format);
671
+ const valueDate = new Date(formattedValue);
672
+
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);
@@ -3343,6 +3786,7 @@ var helpTextVersion = '1.0.0';
3343
3786
  * @csspart helpText - The helpText content container.
3344
3787
  * @event auroDropdown-triggerClick - Notifies that the trigger has been clicked.
3345
3788
  * @event auroDropdown-toggled - Notifies that the visibility of the dropdown bib has changed.
3789
+ * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
3346
3790
  */
3347
3791
  class AuroDropdown extends LitElement {
3348
3792
  constructor() {
@@ -3388,7 +3832,9 @@ class AuroDropdown extends LitElement {
3388
3832
  this.rounded = false;
3389
3833
  this.tabIndex = 0;
3390
3834
  this.noToggle = false;
3835
+ this.a11yAutocomplete = 'none';
3391
3836
  this.labeled = true;
3837
+ this.a11yRole = 'combobox';
3392
3838
  this.onDark = false;
3393
3839
 
3394
3840
  // floaterConfig
@@ -3524,6 +3970,16 @@ class AuroDropdown extends LitElement {
3524
3970
  type: Number
3525
3971
  },
3526
3972
 
3973
+ /**
3974
+ * The unique ID for the dropdown bib element.
3975
+ * @private
3976
+ */
3977
+ dropdownId: {
3978
+ type: String,
3979
+ reflect: false,
3980
+ attribute: false
3981
+ },
3982
+
3527
3983
  /**
3528
3984
  * If declared in combination with `bordered` property or `helpText` slot content, will apply red color to both.
3529
3985
  */
@@ -3691,6 +4147,23 @@ class AuroDropdown extends LitElement {
3691
4147
  */
3692
4148
  tabIndex: {
3693
4149
  type: Number
4150
+ },
4151
+
4152
+ /**
4153
+ * The value for the role attribute of the trigger element.
4154
+ */
4155
+ a11yRole: {
4156
+ type: String || undefined,
4157
+ attribute: false,
4158
+ reflect: false
4159
+ },
4160
+
4161
+ /**
4162
+ * The value for the aria-autocomplete attribute of the trigger element.
4163
+ */
4164
+ a11yAutocomplete: {
4165
+ type: String,
4166
+ attribute: false,
3694
4167
  }
3695
4168
  };
3696
4169
  }
@@ -3752,7 +4225,22 @@ class AuroDropdown extends LitElement {
3752
4225
  }
3753
4226
 
3754
4227
  firstUpdated() {
4228
+
4229
+ // Configure the floater to, this will generate the ID for the bib
3755
4230
  this.floater.configure(this, 'auroDropdown');
4231
+
4232
+ /**
4233
+ * @description Let subscribers know that the dropdown ID ha been generated and added.
4234
+ * @event auroDropdown-idAdded
4235
+ * @type {Object<key: 'id', value: string>} - The ID of the dropdown bib element.
4236
+ */
4237
+ this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
4238
+
4239
+ // Set the bib ID locally if the user hasn't provided a focusable trigger
4240
+ if (!this.triggerContentFocusable) {
4241
+ this.dropdownId = this.floater.element.id;
4242
+ }
4243
+
3756
4244
  this.bibContent = this.floater.element.bib;
3757
4245
 
3758
4246
  // Add the tag name as an attribute if it is different than the component name
@@ -3904,6 +4392,30 @@ class AuroDropdown extends LitElement {
3904
4392
  });
3905
4393
  }
3906
4394
 
4395
+ /*
4396
+ * Sets aria attributes for the trigger element if a custom one is passed in.
4397
+ * @private
4398
+ * @method setTriggerAriaAttributes
4399
+ * @param { HTMLElement } triggerElement - The custom trigger element.
4400
+ */
4401
+ clearTriggerA11yAttributes(triggerElement) {
4402
+
4403
+ if (!triggerElement || !triggerElement.removeAttribute) {
4404
+ return;
4405
+ }
4406
+
4407
+ // Reset appropriate attributes for a11y
4408
+ triggerElement.removeAttribute('aria-labelledby');
4409
+ if (triggerElement.getAttribute('id') === `${this.id}-trigger-element`) {
4410
+ triggerElement.removeAttribute('id');
4411
+ }
4412
+ triggerElement.removeAttribute('role');
4413
+ triggerElement.removeAttribute('aria-expanded');
4414
+
4415
+ triggerElement.removeAttribute('aria-controls');
4416
+ triggerElement.removeAttribute('aria-autocomplete');
4417
+ }
4418
+
3907
4419
  /**
3908
4420
  * Handles changes to the trigger content slot and updates related properties.
3909
4421
  *
@@ -3917,32 +4429,41 @@ class AuroDropdown extends LitElement {
3917
4429
  * @returns {void}
3918
4430
  */
3919
4431
  handleTriggerContentSlotChange(event) {
4432
+
3920
4433
  this.floater.handleTriggerTabIndex();
3921
4434
 
4435
+ // Get the trigger
4436
+ const trigger = this.shadowRoot.querySelector('#trigger');
4437
+
4438
+ // Get the trigger slot
3922
4439
  const triggerSlot = this.shadowRoot.querySelector('.triggerContent slot');
3923
4440
 
4441
+ // If there's a trigger slot
3924
4442
  if (triggerSlot) {
3925
4443
 
4444
+ // Get the content nodes to see if there are any children
3926
4445
  const triggerContentNodes = triggerSlot.assignedNodes();
3927
4446
 
4447
+ // If there are children
3928
4448
  if (triggerContentNodes) {
3929
4449
 
3930
- triggerContentNodes.forEach((node) => {
3931
- if (!this.triggerContentFocusable) {
3932
- this.triggerContentFocusable = this.containsFocusableElement(node);
3933
- }
3934
- });
3935
- }
3936
- }
4450
+ // See if any of them are focusable elemeents
4451
+ this.triggerContentFocusable = triggerContentNodes.some((node) => this.containsFocusableElement(node));
3937
4452
 
3938
- const trigger = this.shadowRoot.querySelector('#trigger');
4453
+ // If any of them are focusable elements
4454
+ if (this.triggerContentFocusable) {
3939
4455
 
3940
- if (!this.triggerContentFocusable) {
3941
- trigger.setAttribute('tabindex', '0');
3942
- trigger.setAttribute('role', 'button');
3943
- } else {
3944
- trigger.removeAttribute('tabindex');
3945
- trigger.removeAttribute('role');
4456
+ // Assume the consumer will be providing their own a11y in whatever they passed in
4457
+ this.clearTriggerA11yAttributes(trigger);
4458
+
4459
+ // Remove the tabindex from the trigger so it doesn't interrupt focus flow
4460
+ trigger.removeAttribute('tabindex');
4461
+ } else {
4462
+
4463
+ // Add the tabindex to the trigger so that it's in the focus flow
4464
+ trigger.setAttribute('tabindex', '0');
4465
+ }
4466
+ }
3946
4467
  }
3947
4468
 
3948
4469
  if (event) {
@@ -3952,6 +4473,7 @@ class AuroDropdown extends LitElement {
3952
4473
 
3953
4474
  if (this.triggerContentSlot) {
3954
4475
  this.setupTriggerFocusEventBinding();
4476
+
3955
4477
  this.hasTriggerContent = this.triggerContentSlot.some((slot) => {
3956
4478
  if (slot.textContent.trim()) {
3957
4479
  return true;
@@ -4019,10 +4541,13 @@ class AuroDropdown extends LitElement {
4019
4541
  id="trigger"
4020
4542
  class="trigger"
4021
4543
  part="trigger"
4022
- aria-labelledby="triggerLabel"
4023
4544
  tabindex="${this.tabIndex}"
4024
4545
  ?showBorder="${this.showTriggerBorders}"
4025
- >
4546
+ role="${ifDefined(this.triggerContentFocusable ? undefined : this.a11yRole)}"
4547
+ aria-expanded="${ifDefined(this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
4548
+ aria-controls="${ifDefined(this.triggerContentFocusable ? undefined : this.dropdownId)}"
4549
+ aria-labelledby="${ifDefined(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
4550
+ >
4026
4551
  <div class="triggerContentWrapper">
4027
4552
  <label class="label" id="triggerLabel" hasTrigger=${this.hasTriggerContent}>
4028
4553
  <slot name="label" @slotchange="${this.handleLabelSlotChange}"></slot>
@@ -4056,12 +4581,12 @@ class AuroDropdown extends LitElement {
4056
4581
  <div id="bibSizer" part="size"></div>
4057
4582
  <${this.dropdownBibTag}
4058
4583
  id="bib"
4059
- role="tooltip"
4060
4584
  ?data-show="${this.isPopoverVisible}"
4061
4585
  ?isfullscreen="${this.isBibFullscreen}"
4062
4586
  ?common="${this.common}"
4063
4587
  ?rounded="${this.common || this.rounded}"
4064
- ?inset="${this.common || this.inset}">
4588
+ ?inset="${this.common || this.inset}"
4589
+ >
4065
4590
  </${this.dropdownBibTag}>
4066
4591
  </div>
4067
4592
  `;