@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
@@ -44,6 +44,414 @@ const t={ATTRIBUTE:1},e$1=t=>(...e)=>({_$litDirective$:t,values:e});let i$1 = cl
44
44
  */
45
45
  const a=Symbol.for(""),o$1=t=>{if(t?.r===a)return t?._$litStatic$},s=t=>({_$litStatic$:t,r:a}),i=(t,...r)=>({_$litStatic$:r.reduce(((r,e,a)=>r+(t=>{if(void 0!==t._$litStatic$)return t._$litStatic$;throw Error(`Value passed to 'literal' function must be a 'literal' result: ${t}. Use 'unsafeStatic' to pass non-literal values, but\n take care to ensure page security.`)})(e)+t[a+1]),t[0]),r:a}),l=new Map,n=t=>(r,...e)=>{const a=e.length;let s,i;const n=[],u=[];let c,$=0,f=false;for(;$<a;){for(c=r[$];$<a&&void 0!==(i=e[$],s=o$1(i));)c+=s+r[++$],f=true;$!==a&&u.push(i),n.push(c),$++;}if($===a&&n.push(r[a]),f){const t=n.join("$$lit$$");void 0===(r=l.get(t))&&(n.raw=n,l.set(t,r=n)),e=u;}return t(r,...e)},u=n(x);
46
46
 
47
+ class DateFormatter {
48
+
49
+ constructor() {
50
+
51
+ /**
52
+ * @description Parses a date string into its components.
53
+ * @param {string} dateStr - Date string to parse.
54
+ * @param {string} format - Date format to parse.
55
+ * @returns {Object<key["month" | "day" | "year"]: number>|undefined}
56
+ */
57
+ this.parseDate = (dateStr, format = 'mm/dd/yyyy') => {
58
+
59
+ // Guard Clause: Date string is defined
60
+ if (!dateStr) {
61
+ return undefined;
62
+ }
63
+
64
+ // Assume the separator is a "/" a defined in our code base
65
+ const separator = '/';
66
+
67
+ // Get the parts of the date and format
68
+ const valueParts = dateStr.split(separator);
69
+ const formatParts = format.split(separator);
70
+
71
+ // Check if the value and format have the correct number of parts
72
+ if (valueParts.length !== formatParts.length) {
73
+ throw new Error('AuroDatepickerUtilities | parseDate: Date string and format length do not match');
74
+ }
75
+
76
+ // Holds the result to be returned
77
+ const result = formatParts.reduce((acc, part, index) => {
78
+ const value = valueParts[index];
79
+
80
+ if ((/m/iu).test(part)) {
81
+ acc.month = value;
82
+ } else if ((/d/iu).test(part)) {
83
+ acc.day = value;
84
+ } else if ((/y/iu).test(part)) {
85
+ acc.year = value;
86
+ }
87
+
88
+ return acc;
89
+ }, {});
90
+
91
+ // If we found all the parts, return the result
92
+ if (result.month && result.year) {
93
+ return result;
94
+ }
95
+
96
+ // Throw an error to let the dev know we were unable to parse the date string
97
+ throw new Error('AuroDatepickerUtilities | parseDate: Unable to parse date string');
98
+ };
99
+
100
+ /**
101
+ * Convert a date object to string format.
102
+ * @param {Object} date - Date to convert to string.
103
+ * @returns {Object} Returns the date as a string.
104
+ */
105
+ this.getDateAsString = (date) => date.toLocaleDateString(undefined, {
106
+ year: "numeric",
107
+ month: "2-digit",
108
+ day: "2-digit",
109
+ });
110
+
111
+ /**
112
+ * Converts a date string to a North American date format.
113
+ * @param {String} dateStr - Date to validate.
114
+ * @param {String} format - Date format to validate against.
115
+ * @returns {Boolean}
116
+ */
117
+ this.toNorthAmericanFormat = (dateStr, format) => {
118
+
119
+ if (format === 'mm/dd/yyyy') {
120
+ return dateStr;
121
+ }
122
+
123
+ const parsedDate = this.parseDate(dateStr, format);
124
+
125
+ if (!parsedDate) {
126
+ throw new Error('AuroDatepickerUtilities | toNorthAmericanFormat: Unable to parse date string');
127
+ }
128
+
129
+ const { month, day, year } = parsedDate;
130
+
131
+ const dateParts = [];
132
+ if (month) {
133
+ dateParts.push(month);
134
+ }
135
+
136
+ if (day) {
137
+ dateParts.push(day);
138
+ }
139
+
140
+ if (year) {
141
+ dateParts.push(year);
142
+ }
143
+
144
+ return dateParts.join('/');
145
+ };
146
+ }
147
+ }
148
+ const dateFormatter = new DateFormatter();
149
+
150
+ // filepath: dateConstraints.mjs
151
+ const DATE_UTIL_CONSTRAINTS = {
152
+ maxDay: 31,
153
+ maxMonth: 12,
154
+ maxYear: 2400,
155
+ minDay: 1,
156
+ minMonth: 1,
157
+ minYear: 1900,
158
+ };
159
+
160
+ class AuroDateUtilitiesBase {
161
+
162
+ /**
163
+ * @description The maximum day value allowed by the various utilities in this class.
164
+ * @readonly
165
+ * @type {Number}
166
+ */
167
+ get maxDay() {
168
+ return DATE_UTIL_CONSTRAINTS.maxDay;
169
+ }
170
+
171
+ /**
172
+ * @description The maximum month value allowed by the various utilities in this class.
173
+ * @readonly
174
+ * @type {Number}
175
+ */
176
+ get maxMonth() {
177
+ return DATE_UTIL_CONSTRAINTS.maxMonth;
178
+ }
179
+
180
+ /**
181
+ * @description The maximum year value allowed by the various utilities in this class.
182
+ * @readonly
183
+ * @type {Number}
184
+ */
185
+ get maxYear() {
186
+ return DATE_UTIL_CONSTRAINTS.maxYear;
187
+ }
188
+
189
+ /**
190
+ * @description The minimum day value allowed by the various utilities in this class.
191
+ * @readonly
192
+ * @type {Number}
193
+ */
194
+ get minDay() {
195
+ return DATE_UTIL_CONSTRAINTS.minDay;
196
+ }
197
+
198
+ /**
199
+ * @description The minimum month value allowed by the various utilities in this class.
200
+ * @readonly
201
+ * @type {Number}
202
+ */
203
+ get minMonth() {
204
+ return DATE_UTIL_CONSTRAINTS.minMonth;
205
+ }
206
+
207
+ /**
208
+ * @description The minimum year value allowed by the various utilities in this class.
209
+ * @readonly
210
+ * @type {Number}
211
+ */
212
+ get minYear() {
213
+ return DATE_UTIL_CONSTRAINTS.minYear;
214
+ }
215
+ }
216
+
217
+ /* eslint-disable no-magic-numbers */
218
+
219
+ class AuroDateUtilities extends AuroDateUtilitiesBase {
220
+
221
+ /**
222
+ * Returns the current century.
223
+ * @returns {String} The current century.
224
+ */
225
+ getCentury () {
226
+ return String(new Date().getFullYear()).slice(0, 2);
227
+ }
228
+
229
+ /**
230
+ * Returns a four digit year.
231
+ * @param {String} year - The year to convert to four digits.
232
+ * @returns {String} The four digit year.
233
+ */
234
+ getFourDigitYear (year) {
235
+
236
+ const strYear = String(year).trim();
237
+ return strYear.length <= 2 ? this.getCentury() + strYear : strYear;
238
+ }
239
+
240
+ constructor() {
241
+
242
+ super();
243
+
244
+ /**
245
+ * Compares two dates to see if they match.
246
+ * @param {Object} date1 - First date to compare.
247
+ * @param {Object} date2 - Second date to compare.
248
+ * @returns {Boolean} Returns true if the dates match.
249
+ */
250
+ this.datesMatch = (date1, date2) => new Date(date1).getTime() === new Date(date2).getTime();
251
+
252
+ /**
253
+ * Returns true if value passed in is a valid date.
254
+ * @param {String} date - Date to validate.
255
+ * @param {String} format - Date format to validate against.
256
+ * @returns {Boolean}
257
+ */
258
+ this.validDateStr = (date, format) => {
259
+
260
+ // The length we expect the date string to be
261
+ const dateStrLength = format.length;
262
+
263
+ // Guard Clause: Date and format are defined
264
+ if (typeof date === "undefined" || typeof format === "undefined") {
265
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date and format are required');
266
+ }
267
+
268
+ // Guard Clause: Date should be of type string
269
+ if (typeof date !== "string") {
270
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date must be a string');
271
+ }
272
+
273
+ // Guard Clause: Format should be of type string
274
+ if (typeof format !== "string") {
275
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Format must be a string');
276
+ }
277
+
278
+ // Guard Clause: Length is what we expect it to be
279
+ if (date.length !== dateStrLength) {
280
+ return false;
281
+ }
282
+ // Get a formatted date string and parse it
283
+ const dateParts = dateFormatter.parseDate(date, format);
284
+
285
+ // Guard Clause: Date parse succeeded
286
+ if (!dateParts) {
287
+ return false;
288
+ }
289
+
290
+ // Create the expected date string based on the date parts
291
+ const expectedDateStr = `${dateParts.month}/${dateParts.day || "01"}/${this.getFourDigitYear(dateParts.year)}`;
292
+
293
+ // Generate a date object that we will extract a string date from to compare to the passed in date string
294
+ const dateObj = new Date(this.getFourDigitYear(dateParts.year), dateParts.month - 1, dateParts.day || 1);
295
+
296
+ // Get the date string of the date object we created from the string date
297
+ const actualDateStr = dateFormatter.getDateAsString(dateObj);
298
+
299
+ // Guard Clause: Generated date matches date string input
300
+ if (expectedDateStr !== actualDateStr) {
301
+ return false;
302
+ }
303
+
304
+ // If we passed all other checks, we can assume the date is valid
305
+ return true;
306
+ };
307
+
308
+ /**
309
+ * Determines if a string date value matches the format provided.
310
+ * @param {string} value = The date string value.
311
+ * @param { string} format = The date format to match against.
312
+ * @returns {boolean}
313
+ */
314
+ this.dateAndFormatMatch = (value, format) => {
315
+
316
+ // Ensure we have both values we need to do the comparison
317
+ if (!value || !format) {
318
+ throw new Error('AuroFormValidation | dateFormatMatch: value and format are required');
319
+ }
320
+
321
+ // If the lengths are different, they cannot match
322
+ if (value.length !== format.length) {
323
+ return false;
324
+ }
325
+
326
+ // Get the parts of the date
327
+ const dateParts = dateFormatter.parseDate(value, format);
328
+
329
+ // Validator for day
330
+ const dayValueIsValid = (day) => {
331
+
332
+ // Guard clause: if there is no day in the dateParts, we can ignore this check.
333
+ if (!dateParts.day) {
334
+ return true;
335
+ }
336
+
337
+ // Guard clause: ensure day exists.
338
+ if (!day) {
339
+ return false;
340
+ }
341
+
342
+ // Convert day to number
343
+ const numDay = Number.parseInt(day, 10);
344
+
345
+ // Guard clause: ensure day is a valid integer
346
+ if (Number.isNaN(numDay)) {
347
+ throw new Error('AuroDatepickerUtilities | dayValueIsValid: Unable to parse day value integer');
348
+ }
349
+
350
+ // Guard clause: ensure day is within the valid range
351
+ if (numDay < this.minDay || numDay > this.maxDay) {
352
+ return false;
353
+ }
354
+
355
+ // Default return
356
+ return true;
357
+ };
358
+
359
+ // Validator for month
360
+ const monthValueIsValid = (month) => {
361
+
362
+ // Guard clause: ensure month exists.
363
+ if (!month) {
364
+ return false;
365
+ }
366
+
367
+ // Convert month to number
368
+ const numMonth = Number.parseInt(month, 10);
369
+
370
+ // Guard clause: ensure month is a valid integer
371
+ if (Number.isNaN(numMonth)) {
372
+ throw new Error('AuroDatepickerUtilities | monthValueIsValid: Unable to parse month value integer');
373
+ }
374
+
375
+ // Guard clause: ensure month is within the valid range
376
+ if (numMonth < this.minMonth || numMonth > this.maxMonth) {
377
+ return false;
378
+ }
379
+
380
+ // Default return
381
+ return true;
382
+ };
383
+
384
+ // Validator for year
385
+ const yearIsValid = (_year) => {
386
+
387
+ // Guard clause: ensure year exists.
388
+ if (!_year) {
389
+ return false;
390
+ }
391
+
392
+ // Get the full year
393
+ const year = this.getFourDigitYear(_year);
394
+
395
+ // Convert year to number
396
+ const numYear = Number.parseInt(year, 10);
397
+
398
+ // Guard clause: ensure year is a valid integer
399
+ if (Number.isNaN(numYear)) {
400
+ throw new Error('AuroDatepickerUtilities | yearValueIsValid: Unable to parse year value integer');
401
+ }
402
+
403
+ // Guard clause: ensure year is within the valid range
404
+ if (numYear < this.minYear || numYear > this.maxYear) {
405
+ return false;
406
+ }
407
+
408
+ // Default return
409
+ return true;
410
+ };
411
+
412
+ // Self-contained checks for month, day, and year
413
+ const checks = [
414
+ monthValueIsValid(dateParts.month),
415
+ dayValueIsValid(dateParts.day),
416
+ yearIsValid(dateParts.year)
417
+ ];
418
+
419
+ // If any of the checks failed, the date format does not match and the result is invalid
420
+ const isValid = checks.every((check) => check === true);
421
+
422
+ // If the check is invalid, return false
423
+ if (!isValid) {
424
+ return false;
425
+ }
426
+
427
+ // Default case
428
+ return true;
429
+ };
430
+ }
431
+ }
432
+
433
+ // Export a class instance
434
+ const dateUtilities = new AuroDateUtilities();
435
+
436
+ // Export the class instance methods individually
437
+ const {
438
+ datesMatch,
439
+ validDateStr,
440
+ dateAndFormatMatch,
441
+ minDay,
442
+ minMonth,
443
+ minYear,
444
+ maxDay,
445
+ maxMonth,
446
+ maxYear
447
+ } = dateUtilities;
448
+
449
+ const {
450
+ toNorthAmericanFormat,
451
+ parseDate,
452
+ getDateAsString
453
+ } = dateFormatter;
454
+
47
455
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
48
456
  // See LICENSE in the project root for license information.
49
457
 
@@ -119,6 +527,7 @@ let AuroLibraryRuntimeUtils$3 = class AuroLibraryRuntimeUtils {
119
527
 
120
528
 
121
529
  class AuroFormValidation {
530
+
122
531
  constructor() {
123
532
  this.runtimeUtils = new AuroLibraryRuntimeUtils$3();
124
533
  }
@@ -210,17 +619,17 @@ class AuroFormValidation {
210
619
  ]
211
620
  }
212
621
  };
213
-
622
+
214
623
  let elementType;
215
624
  if (this.runtimeUtils.elementMatch(elem, 'auro-input')) {
216
625
  elementType = 'input';
217
626
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-counter') || this.runtimeUtils.elementMatch(elem, 'auro-counter-group')) {
218
627
  elementType = 'counter';
219
628
  }
220
-
629
+
221
630
  if (elementType) {
222
631
  const rules = validationRules[elementType];
223
-
632
+
224
633
  if (rules) {
225
634
  Object.values(rules).flat().forEach(rule => {
226
635
  if (rule.check(elem)) {
@@ -246,48 +655,82 @@ class AuroFormValidation {
246
655
  if (!elem.value.match(emailRegex)) {
247
656
  elem.validity = 'patternMismatch';
248
657
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
658
+ return;
249
659
  }
250
660
  } else if (elem.type === 'credit-card') {
251
661
  if (elem.value.length > 0 && elem.value.length < elem.validationCCLength) {
252
662
  elem.validity = 'tooShort';
253
663
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
664
+ return;
254
665
  }
255
666
  } else if (elem.type === 'number') {
256
667
  if (elem.max !== undefined && Number(elem.max) < Number(elem.value)) {
257
668
  elem.validity = 'rangeOverflow';
258
669
  elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
670
+ return;
259
671
  }
260
672
 
261
673
  if (elem.min !== undefined && elem.value?.length > 0 && Number(elem.min) > Number(elem.value)) {
262
674
  elem.validity = 'rangeUnderflow';
263
675
  elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
676
+ return;
264
677
  }
265
- } else if (elem.type === 'date') {
266
- if (elem.value?.length > 0 && elem.value?.length < elem.lengthForType) {
678
+ } else if (elem.type === 'date' && elem.value?.length > 0) {
679
+
680
+ // Guard Clause: if the value is too short
681
+ if (elem.value.length < elem.lengthForType) {
682
+
267
683
  elem.validity = 'tooShort';
268
684
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
269
- } else if (elem.value?.length === elem.lengthForType && elem.util.toNorthAmericanFormat(elem.value, elem.format)) {
270
- const formattedValue = elem.util.toNorthAmericanFormat(elem.value, elem.format);
271
- const valueDate = new Date(formattedValue.dateForComparison);
685
+ return;
686
+ }
687
+
688
+ // Guard Clause: If the value is too long for the type
689
+ if (elem.value?.length > elem.lengthForType) {
272
690
 
273
- // validate max
274
- if (elem.max?.length === elem.lengthForType) {
275
- const maxDate = new Date(elem.util.toNorthAmericanFormat(elem.max, elem.format).dateForComparison);
691
+ elem.validity = 'tooLong';
692
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
693
+ return;
694
+ }
695
+
696
+ // Validate that the date passed was the correct format
697
+ if (!dateAndFormatMatch(elem.value, elem.format)) {
698
+ elem.validity = 'patternMismatch';
699
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || 'Invalid Date Format Entered';
700
+ return;
701
+ }
702
+
703
+ // Validate that the date passed was a valid date
704
+ if (!validDateStr(elem.value, elem.format)) {
705
+ elem.validity = 'invalidDate';
706
+ elem.errorMessage = elem.setCustomValidityInvalidDate || elem.setCustomValidity || 'Invalid Date Entered';
707
+ return;
708
+ }
276
709
 
277
- if (valueDate > maxDate) {
278
- elem.validity = 'rangeOverflow';
279
- elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
280
- }
710
+ // Perform the rest of the validation
711
+ const formattedValue = toNorthAmericanFormat(elem.value, elem.format);
712
+ const valueDate = new Date(formattedValue);
713
+
714
+ // // Validate max date
715
+ if (elem.max?.length === elem.lengthForType) {
716
+
717
+ const maxDate = new Date(toNorthAmericanFormat(elem.max, elem.format));
718
+
719
+ if (valueDate > maxDate) {
720
+ elem.validity = 'rangeOverflow';
721
+ elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
722
+ return;
281
723
  }
724
+ }
282
725
 
283
- // validate min
284
- if (elem.min?.length === elem.lengthForType) {
285
- const minDate = new Date(elem.util.toNorthAmericanFormat(elem.min, elem.format).dateForComparison);
726
+ // Validate min date
727
+ if (elem.min?.length === elem.lengthForType) {
728
+ const minDate = new Date(toNorthAmericanFormat(elem.min, elem.format));
286
729
 
287
- if (valueDate < minDate) {
288
- elem.validity = 'rangeUnderflow';
289
- elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
290
- }
730
+ if (valueDate < minDate) {
731
+ elem.validity = 'rangeUnderflow';
732
+ elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
733
+ return;
291
734
  }
292
735
  }
293
736
  }
@@ -406,7 +849,7 @@ class AuroFormValidation {
406
849
  if (input.validationMessage.length > 0) {
407
850
  elem.errorMessage = input.validationMessage;
408
851
  }
409
- } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
852
+ } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
410
853
  const firstInput = this.inputElements[0];
411
854
 
412
855
  if (firstInput.validationMessage.length > 0) {
@@ -2314,7 +2757,7 @@ class AuroFloatingUI {
2314
2757
  /**
2315
2758
  * @private
2316
2759
  * getting called on 'blur' in trigger or `focusin` in document
2317
- *
2760
+ *
2318
2761
  * Hides the bib if focus moves outside of the trigger or bib, unless a 'noHideOnThisFocusLoss' flag is set.
2319
2762
  * This method checks if the currently active element is still within the trigger or bib.
2320
2763
  * If not, and if the bib isn't in fullscreen mode with focus lost, it hides the bib.
@@ -2430,7 +2873,7 @@ class AuroFloatingUI {
2430
2873
  // Close any other dropdown that is already open
2431
2874
  const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
2432
2875
  if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this &&
2433
- existedVisibleFloatingUI.isPopoverVisible &&
2876
+ existedVisibleFloatingUI.element.isPopoverVisible &&
2434
2877
  document.expandedAuroFloater.eventPrefix === this.eventPrefix) {
2435
2878
  document.expandedAuroFloater.hideBib();
2436
2879
  }
@@ -2606,7 +3049,7 @@ class AuroFloatingUI {
2606
3049
  this.id = window.crypto.randomUUID();
2607
3050
  this.element.setAttribute('id', this.id);
2608
3051
  }
2609
-
3052
+
2610
3053
  this.element.bib.setAttribute("id", `${this.id}-floater-bib`);
2611
3054
  }
2612
3055
 
@@ -2659,7 +3102,7 @@ class AuroFloatingUI {
2659
3102
  if (this.element.bib) {
2660
3103
  this.element.shadowRoot.append(this.element.bib);
2661
3104
  }
2662
-
3105
+
2663
3106
  // Remove event & keyboard listeners
2664
3107
  if (this.element?.trigger) {
2665
3108
  this.element.trigger.removeEventListener('keydown', this.handleEvent);
@@ -3388,6 +3831,7 @@ var helpTextVersion = '1.0.0';
3388
3831
  * @csspart helpText - The helpText content container.
3389
3832
  * @event auroDropdown-triggerClick - Notifies that the trigger has been clicked.
3390
3833
  * @event auroDropdown-toggled - Notifies that the visibility of the dropdown bib has changed.
3834
+ * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
3391
3835
  */
3392
3836
  class AuroDropdown extends r {
3393
3837
  constructor() {
@@ -3433,7 +3877,9 @@ class AuroDropdown extends r {
3433
3877
  this.rounded = false;
3434
3878
  this.tabIndex = 0;
3435
3879
  this.noToggle = false;
3880
+ this.a11yAutocomplete = 'none';
3436
3881
  this.labeled = true;
3882
+ this.a11yRole = 'combobox';
3437
3883
  this.onDark = false;
3438
3884
 
3439
3885
  // floaterConfig
@@ -3569,6 +4015,16 @@ class AuroDropdown extends r {
3569
4015
  type: Number
3570
4016
  },
3571
4017
 
4018
+ /**
4019
+ * The unique ID for the dropdown bib element.
4020
+ * @private
4021
+ */
4022
+ dropdownId: {
4023
+ type: String,
4024
+ reflect: false,
4025
+ attribute: false
4026
+ },
4027
+
3572
4028
  /**
3573
4029
  * If declared in combination with `bordered` property or `helpText` slot content, will apply red color to both.
3574
4030
  */
@@ -3731,6 +4187,23 @@ class AuroDropdown extends r {
3731
4187
  */
3732
4188
  tabIndex: {
3733
4189
  type: Number
4190
+ },
4191
+
4192
+ /**
4193
+ * The value for the role attribute of the trigger element.
4194
+ */
4195
+ a11yRole: {
4196
+ type: String || undefined,
4197
+ attribute: false,
4198
+ reflect: false
4199
+ },
4200
+
4201
+ /**
4202
+ * The value for the aria-autocomplete attribute of the trigger element.
4203
+ */
4204
+ a11yAutocomplete: {
4205
+ type: String,
4206
+ attribute: false,
3734
4207
  }
3735
4208
  };
3736
4209
  }
@@ -3781,7 +4254,22 @@ class AuroDropdown extends r {
3781
4254
  }
3782
4255
 
3783
4256
  firstUpdated() {
4257
+
4258
+ // Configure the floater to, this will generate the ID for the bib
3784
4259
  this.floater.configure(this, 'auroDropdown');
4260
+
4261
+ /**
4262
+ * @description Let subscribers know that the dropdown ID ha been generated and added.
4263
+ * @event auroDropdown-idAdded
4264
+ * @type {Object<key: 'id', value: string>} - The ID of the dropdown bib element.
4265
+ */
4266
+ this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
4267
+
4268
+ // Set the bib ID locally if the user hasn't provided a focusable trigger
4269
+ if (!this.triggerContentFocusable) {
4270
+ this.dropdownId = this.floater.element.id;
4271
+ }
4272
+
3785
4273
  this.bibContent = this.floater.element.bib;
3786
4274
 
3787
4275
  // Add the tag name as an attribute if it is different than the component name
@@ -3933,6 +4421,30 @@ class AuroDropdown extends r {
3933
4421
  });
3934
4422
  }
3935
4423
 
4424
+ /*
4425
+ * Sets aria attributes for the trigger element if a custom one is passed in.
4426
+ * @private
4427
+ * @method setTriggerAriaAttributes
4428
+ * @param { HTMLElement } triggerElement - The custom trigger element.
4429
+ */
4430
+ clearTriggerA11yAttributes(triggerElement) {
4431
+
4432
+ if (!triggerElement || !triggerElement.removeAttribute) {
4433
+ return;
4434
+ }
4435
+
4436
+ // Reset appropriate attributes for a11y
4437
+ triggerElement.removeAttribute('aria-labelledby');
4438
+ if (triggerElement.getAttribute('id') === `${this.id}-trigger-element`) {
4439
+ triggerElement.removeAttribute('id');
4440
+ }
4441
+ triggerElement.removeAttribute('role');
4442
+ triggerElement.removeAttribute('aria-expanded');
4443
+
4444
+ triggerElement.removeAttribute('aria-controls');
4445
+ triggerElement.removeAttribute('aria-autocomplete');
4446
+ }
4447
+
3936
4448
  /**
3937
4449
  * Handles changes to the trigger content slot and updates related properties.
3938
4450
  *
@@ -3946,32 +4458,41 @@ class AuroDropdown extends r {
3946
4458
  * @returns {void}
3947
4459
  */
3948
4460
  handleTriggerContentSlotChange(event) {
4461
+
3949
4462
  this.floater.handleTriggerTabIndex();
3950
4463
 
4464
+ // Get the trigger
4465
+ const trigger = this.shadowRoot.querySelector('#trigger');
4466
+
4467
+ // Get the trigger slot
3951
4468
  const triggerSlot = this.shadowRoot.querySelector('.triggerContent slot');
3952
4469
 
4470
+ // If there's a trigger slot
3953
4471
  if (triggerSlot) {
3954
4472
 
4473
+ // Get the content nodes to see if there are any children
3955
4474
  const triggerContentNodes = triggerSlot.assignedNodes();
3956
4475
 
4476
+ // If there are children
3957
4477
  if (triggerContentNodes) {
3958
4478
 
3959
- triggerContentNodes.forEach((node) => {
3960
- if (!this.triggerContentFocusable) {
3961
- this.triggerContentFocusable = this.containsFocusableElement(node);
3962
- }
3963
- });
3964
- }
3965
- }
4479
+ // See if any of them are focusable elemeents
4480
+ this.triggerContentFocusable = triggerContentNodes.some((node) => this.containsFocusableElement(node));
3966
4481
 
3967
- const trigger = this.shadowRoot.querySelector('#trigger');
4482
+ // If any of them are focusable elements
4483
+ if (this.triggerContentFocusable) {
3968
4484
 
3969
- if (!this.triggerContentFocusable) {
3970
- trigger.setAttribute('tabindex', '0');
3971
- trigger.setAttribute('role', 'button');
3972
- } else {
3973
- trigger.removeAttribute('tabindex');
3974
- trigger.removeAttribute('role');
4485
+ // Assume the consumer will be providing their own a11y in whatever they passed in
4486
+ this.clearTriggerA11yAttributes(trigger);
4487
+
4488
+ // Remove the tabindex from the trigger so it doesn't interrupt focus flow
4489
+ trigger.removeAttribute('tabindex');
4490
+ } else {
4491
+
4492
+ // Add the tabindex to the trigger so that it's in the focus flow
4493
+ trigger.setAttribute('tabindex', '0');
4494
+ }
4495
+ }
3975
4496
  }
3976
4497
 
3977
4498
  if (event) {
@@ -3981,6 +4502,7 @@ class AuroDropdown extends r {
3981
4502
 
3982
4503
  if (this.triggerContentSlot) {
3983
4504
  this.setupTriggerFocusEventBinding();
4505
+
3984
4506
  this.hasTriggerContent = this.triggerContentSlot.some((slot) => {
3985
4507
  if (slot.textContent.trim()) {
3986
4508
  return true;
@@ -4048,10 +4570,13 @@ class AuroDropdown extends r {
4048
4570
  id="trigger"
4049
4571
  class="trigger"
4050
4572
  part="trigger"
4051
- aria-labelledby="triggerLabel"
4052
4573
  tabindex="${this.tabIndex}"
4053
4574
  ?showBorder="${this.showTriggerBorders}"
4054
- >
4575
+ role="${o(this.triggerContentFocusable ? undefined : this.a11yRole)}"
4576
+ aria-expanded="${o(this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
4577
+ aria-controls="${o(this.triggerContentFocusable ? undefined : this.dropdownId)}"
4578
+ aria-labelledby="${o(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
4579
+ >
4055
4580
  <div class="triggerContentWrapper">
4056
4581
  <label class="label" id="triggerLabel" hasTrigger=${this.hasTriggerContent}>
4057
4582
  <slot name="label" @slotchange="${this.handleLabelSlotChange}"></slot>
@@ -4085,12 +4610,12 @@ class AuroDropdown extends r {
4085
4610
  <div id="bibSizer" part="size"></div>
4086
4611
  <${this.dropdownBibTag}
4087
4612
  id="bib"
4088
- role="tooltip"
4089
4613
  ?data-show="${this.isPopoverVisible}"
4090
4614
  ?isfullscreen="${this.isBibFullscreen}"
4091
4615
  ?common="${this.common}"
4092
4616
  ?rounded="${this.common || this.rounded}"
4093
- ?inset="${this.common || this.inset}">
4617
+ ?inset="${this.common || this.inset}"
4618
+ >
4094
4619
  </${this.dropdownBibTag}>
4095
4620
  </div>
4096
4621
  `;