@aurodesignsystem/auro-formkit 3.0.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 (59) hide show
  1. package/CHANGELOG.md +18 -0
  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);
@@ -3341,6 +3784,7 @@ var helpTextVersion = '1.0.0';
3341
3784
  * @csspart helpText - The helpText content container.
3342
3785
  * @event auroDropdown-triggerClick - Notifies that the trigger has been clicked.
3343
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.
3344
3788
  */
3345
3789
  class AuroDropdown extends LitElement {
3346
3790
  constructor() {
@@ -3386,7 +3830,9 @@ class AuroDropdown extends LitElement {
3386
3830
  this.rounded = false;
3387
3831
  this.tabIndex = 0;
3388
3832
  this.noToggle = false;
3833
+ this.a11yAutocomplete = 'none';
3389
3834
  this.labeled = true;
3835
+ this.a11yRole = 'combobox';
3390
3836
  this.onDark = false;
3391
3837
 
3392
3838
  // floaterConfig
@@ -3522,6 +3968,16 @@ class AuroDropdown extends LitElement {
3522
3968
  type: Number
3523
3969
  },
3524
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
+
3525
3981
  /**
3526
3982
  * If declared in combination with `bordered` property or `helpText` slot content, will apply red color to both.
3527
3983
  */
@@ -3684,6 +4140,23 @@ class AuroDropdown extends LitElement {
3684
4140
  */
3685
4141
  tabIndex: {
3686
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,
3687
4160
  }
3688
4161
  };
3689
4162
  }
@@ -3734,7 +4207,22 @@ class AuroDropdown extends LitElement {
3734
4207
  }
3735
4208
 
3736
4209
  firstUpdated() {
4210
+
4211
+ // Configure the floater to, this will generate the ID for the bib
3737
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
+
3738
4226
  this.bibContent = this.floater.element.bib;
3739
4227
 
3740
4228
  // Add the tag name as an attribute if it is different than the component name
@@ -3886,6 +4374,30 @@ class AuroDropdown extends LitElement {
3886
4374
  });
3887
4375
  }
3888
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
+
3889
4401
  /**
3890
4402
  * Handles changes to the trigger content slot and updates related properties.
3891
4403
  *
@@ -3899,32 +4411,41 @@ class AuroDropdown extends LitElement {
3899
4411
  * @returns {void}
3900
4412
  */
3901
4413
  handleTriggerContentSlotChange(event) {
4414
+
3902
4415
  this.floater.handleTriggerTabIndex();
3903
4416
 
4417
+ // Get the trigger
4418
+ const trigger = this.shadowRoot.querySelector('#trigger');
4419
+
4420
+ // Get the trigger slot
3904
4421
  const triggerSlot = this.shadowRoot.querySelector('.triggerContent slot');
3905
4422
 
4423
+ // If there's a trigger slot
3906
4424
  if (triggerSlot) {
3907
4425
 
4426
+ // Get the content nodes to see if there are any children
3908
4427
  const triggerContentNodes = triggerSlot.assignedNodes();
3909
4428
 
4429
+ // If there are children
3910
4430
  if (triggerContentNodes) {
3911
4431
 
3912
- triggerContentNodes.forEach((node) => {
3913
- if (!this.triggerContentFocusable) {
3914
- this.triggerContentFocusable = this.containsFocusableElement(node);
3915
- }
3916
- });
3917
- }
3918
- }
4432
+ // See if any of them are focusable elemeents
4433
+ this.triggerContentFocusable = triggerContentNodes.some((node) => this.containsFocusableElement(node));
3919
4434
 
3920
- const trigger = this.shadowRoot.querySelector('#trigger');
4435
+ // If any of them are focusable elements
4436
+ if (this.triggerContentFocusable) {
3921
4437
 
3922
- if (!this.triggerContentFocusable) {
3923
- trigger.setAttribute('tabindex', '0');
3924
- trigger.setAttribute('role', 'button');
3925
- } else {
3926
- trigger.removeAttribute('tabindex');
3927
- 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
+ }
3928
4449
  }
3929
4450
 
3930
4451
  if (event) {
@@ -3934,6 +4455,7 @@ class AuroDropdown extends LitElement {
3934
4455
 
3935
4456
  if (this.triggerContentSlot) {
3936
4457
  this.setupTriggerFocusEventBinding();
4458
+
3937
4459
  this.hasTriggerContent = this.triggerContentSlot.some((slot) => {
3938
4460
  if (slot.textContent.trim()) {
3939
4461
  return true;
@@ -4001,10 +4523,13 @@ class AuroDropdown extends LitElement {
4001
4523
  id="trigger"
4002
4524
  class="trigger"
4003
4525
  part="trigger"
4004
- aria-labelledby="triggerLabel"
4005
4526
  tabindex="${this.tabIndex}"
4006
4527
  ?showBorder="${this.showTriggerBorders}"
4007
- >
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
+ >
4008
4533
  <div class="triggerContentWrapper">
4009
4534
  <label class="label" id="triggerLabel" hasTrigger=${this.hasTriggerContent}>
4010
4535
  <slot name="label" @slotchange="${this.handleLabelSlotChange}"></slot>
@@ -4038,12 +4563,12 @@ class AuroDropdown extends LitElement {
4038
4563
  <div id="bibSizer" part="size"></div>
4039
4564
  <${this.dropdownBibTag}
4040
4565
  id="bib"
4041
- role="tooltip"
4042
4566
  ?data-show="${this.isPopoverVisible}"
4043
4567
  ?isfullscreen="${this.isBibFullscreen}"
4044
4568
  ?common="${this.common}"
4045
4569
  ?rounded="${this.common || this.rounded}"
4046
- ?inset="${this.common || this.inset}">
4570
+ ?inset="${this.common || this.inset}"
4571
+ >
4047
4572
  </${this.dropdownBibTag}>
4048
4573
  </div>
4049
4574
  `;