@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
@@ -915,11 +915,420 @@ let AuroIcon$2 = class AuroIcon extends BaseIcon$2 {
915
915
 
916
916
  var iconVersion$2 = '8.0.1';
917
917
 
918
+ class DateFormatter {
919
+
920
+ constructor() {
921
+
922
+ /**
923
+ * @description Parses a date string into its components.
924
+ * @param {string} dateStr - Date string to parse.
925
+ * @param {string} format - Date format to parse.
926
+ * @returns {Object<key["month" | "day" | "year"]: number>|undefined}
927
+ */
928
+ this.parseDate = (dateStr, format = 'mm/dd/yyyy') => {
929
+
930
+ // Guard Clause: Date string is defined
931
+ if (!dateStr) {
932
+ return undefined;
933
+ }
934
+
935
+ // Assume the separator is a "/" a defined in our code base
936
+ const separator = '/';
937
+
938
+ // Get the parts of the date and format
939
+ const valueParts = dateStr.split(separator);
940
+ const formatParts = format.split(separator);
941
+
942
+ // Check if the value and format have the correct number of parts
943
+ if (valueParts.length !== formatParts.length) {
944
+ throw new Error('AuroDatepickerUtilities | parseDate: Date string and format length do not match');
945
+ }
946
+
947
+ // Holds the result to be returned
948
+ const result = formatParts.reduce((acc, part, index) => {
949
+ const value = valueParts[index];
950
+
951
+ if ((/m/iu).test(part)) {
952
+ acc.month = value;
953
+ } else if ((/d/iu).test(part)) {
954
+ acc.day = value;
955
+ } else if ((/y/iu).test(part)) {
956
+ acc.year = value;
957
+ }
958
+
959
+ return acc;
960
+ }, {});
961
+
962
+ // If we found all the parts, return the result
963
+ if (result.month && result.year) {
964
+ return result;
965
+ }
966
+
967
+ // Throw an error to let the dev know we were unable to parse the date string
968
+ throw new Error('AuroDatepickerUtilities | parseDate: Unable to parse date string');
969
+ };
970
+
971
+ /**
972
+ * Convert a date object to string format.
973
+ * @param {Object} date - Date to convert to string.
974
+ * @returns {Object} Returns the date as a string.
975
+ */
976
+ this.getDateAsString = (date) => date.toLocaleDateString(undefined, {
977
+ year: "numeric",
978
+ month: "2-digit",
979
+ day: "2-digit",
980
+ });
981
+
982
+ /**
983
+ * Converts a date string to a North American date format.
984
+ * @param {String} dateStr - Date to validate.
985
+ * @param {String} format - Date format to validate against.
986
+ * @returns {Boolean}
987
+ */
988
+ this.toNorthAmericanFormat = (dateStr, format) => {
989
+
990
+ if (format === 'mm/dd/yyyy') {
991
+ return dateStr;
992
+ }
993
+
994
+ const parsedDate = this.parseDate(dateStr, format);
995
+
996
+ if (!parsedDate) {
997
+ throw new Error('AuroDatepickerUtilities | toNorthAmericanFormat: Unable to parse date string');
998
+ }
999
+
1000
+ const { month, day, year } = parsedDate;
1001
+
1002
+ const dateParts = [];
1003
+ if (month) {
1004
+ dateParts.push(month);
1005
+ }
1006
+
1007
+ if (day) {
1008
+ dateParts.push(day);
1009
+ }
1010
+
1011
+ if (year) {
1012
+ dateParts.push(year);
1013
+ }
1014
+
1015
+ return dateParts.join('/');
1016
+ };
1017
+ }
1018
+ }
1019
+ const dateFormatter = new DateFormatter();
1020
+
1021
+ // filepath: dateConstraints.mjs
1022
+ const DATE_UTIL_CONSTRAINTS = {
1023
+ maxDay: 31,
1024
+ maxMonth: 12,
1025
+ maxYear: 2400,
1026
+ minDay: 1,
1027
+ minMonth: 1,
1028
+ minYear: 1900,
1029
+ };
1030
+
1031
+ class AuroDateUtilitiesBase {
1032
+
1033
+ /**
1034
+ * @description The maximum day value allowed by the various utilities in this class.
1035
+ * @readonly
1036
+ * @type {Number}
1037
+ */
1038
+ get maxDay() {
1039
+ return DATE_UTIL_CONSTRAINTS.maxDay;
1040
+ }
1041
+
1042
+ /**
1043
+ * @description The maximum month value allowed by the various utilities in this class.
1044
+ * @readonly
1045
+ * @type {Number}
1046
+ */
1047
+ get maxMonth() {
1048
+ return DATE_UTIL_CONSTRAINTS.maxMonth;
1049
+ }
1050
+
1051
+ /**
1052
+ * @description The maximum year value allowed by the various utilities in this class.
1053
+ * @readonly
1054
+ * @type {Number}
1055
+ */
1056
+ get maxYear() {
1057
+ return DATE_UTIL_CONSTRAINTS.maxYear;
1058
+ }
1059
+
1060
+ /**
1061
+ * @description The minimum day value allowed by the various utilities in this class.
1062
+ * @readonly
1063
+ * @type {Number}
1064
+ */
1065
+ get minDay() {
1066
+ return DATE_UTIL_CONSTRAINTS.minDay;
1067
+ }
1068
+
1069
+ /**
1070
+ * @description The minimum month value allowed by the various utilities in this class.
1071
+ * @readonly
1072
+ * @type {Number}
1073
+ */
1074
+ get minMonth() {
1075
+ return DATE_UTIL_CONSTRAINTS.minMonth;
1076
+ }
1077
+
1078
+ /**
1079
+ * @description The minimum year value allowed by the various utilities in this class.
1080
+ * @readonly
1081
+ * @type {Number}
1082
+ */
1083
+ get minYear() {
1084
+ return DATE_UTIL_CONSTRAINTS.minYear;
1085
+ }
1086
+ }
1087
+
1088
+ /* eslint-disable no-magic-numbers */
1089
+
1090
+ class AuroDateUtilities extends AuroDateUtilitiesBase {
1091
+
1092
+ /**
1093
+ * Returns the current century.
1094
+ * @returns {String} The current century.
1095
+ */
1096
+ getCentury () {
1097
+ return String(new Date().getFullYear()).slice(0, 2);
1098
+ }
1099
+
1100
+ /**
1101
+ * Returns a four digit year.
1102
+ * @param {String} year - The year to convert to four digits.
1103
+ * @returns {String} The four digit year.
1104
+ */
1105
+ getFourDigitYear (year) {
1106
+
1107
+ const strYear = String(year).trim();
1108
+ return strYear.length <= 2 ? this.getCentury() + strYear : strYear;
1109
+ }
1110
+
1111
+ constructor() {
1112
+
1113
+ super();
1114
+
1115
+ /**
1116
+ * Compares two dates to see if they match.
1117
+ * @param {Object} date1 - First date to compare.
1118
+ * @param {Object} date2 - Second date to compare.
1119
+ * @returns {Boolean} Returns true if the dates match.
1120
+ */
1121
+ this.datesMatch = (date1, date2) => new Date(date1).getTime() === new Date(date2).getTime();
1122
+
1123
+ /**
1124
+ * Returns true if value passed in is a valid date.
1125
+ * @param {String} date - Date to validate.
1126
+ * @param {String} format - Date format to validate against.
1127
+ * @returns {Boolean}
1128
+ */
1129
+ this.validDateStr = (date, format) => {
1130
+
1131
+ // The length we expect the date string to be
1132
+ const dateStrLength = format.length;
1133
+
1134
+ // Guard Clause: Date and format are defined
1135
+ if (typeof date === "undefined" || typeof format === "undefined") {
1136
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date and format are required');
1137
+ }
1138
+
1139
+ // Guard Clause: Date should be of type string
1140
+ if (typeof date !== "string") {
1141
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date must be a string');
1142
+ }
1143
+
1144
+ // Guard Clause: Format should be of type string
1145
+ if (typeof format !== "string") {
1146
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Format must be a string');
1147
+ }
1148
+
1149
+ // Guard Clause: Length is what we expect it to be
1150
+ if (date.length !== dateStrLength) {
1151
+ return false;
1152
+ }
1153
+ // Get a formatted date string and parse it
1154
+ const dateParts = dateFormatter.parseDate(date, format);
1155
+
1156
+ // Guard Clause: Date parse succeeded
1157
+ if (!dateParts) {
1158
+ return false;
1159
+ }
1160
+
1161
+ // Create the expected date string based on the date parts
1162
+ const expectedDateStr = `${dateParts.month}/${dateParts.day || "01"}/${this.getFourDigitYear(dateParts.year)}`;
1163
+
1164
+ // Generate a date object that we will extract a string date from to compare to the passed in date string
1165
+ const dateObj = new Date(this.getFourDigitYear(dateParts.year), dateParts.month - 1, dateParts.day || 1);
1166
+
1167
+ // Get the date string of the date object we created from the string date
1168
+ const actualDateStr = dateFormatter.getDateAsString(dateObj);
1169
+
1170
+ // Guard Clause: Generated date matches date string input
1171
+ if (expectedDateStr !== actualDateStr) {
1172
+ return false;
1173
+ }
1174
+
1175
+ // If we passed all other checks, we can assume the date is valid
1176
+ return true;
1177
+ };
1178
+
1179
+ /**
1180
+ * Determines if a string date value matches the format provided.
1181
+ * @param {string} value = The date string value.
1182
+ * @param { string} format = The date format to match against.
1183
+ * @returns {boolean}
1184
+ */
1185
+ this.dateAndFormatMatch = (value, format) => {
1186
+
1187
+ // Ensure we have both values we need to do the comparison
1188
+ if (!value || !format) {
1189
+ throw new Error('AuroFormValidation | dateFormatMatch: value and format are required');
1190
+ }
1191
+
1192
+ // If the lengths are different, they cannot match
1193
+ if (value.length !== format.length) {
1194
+ return false;
1195
+ }
1196
+
1197
+ // Get the parts of the date
1198
+ const dateParts = dateFormatter.parseDate(value, format);
1199
+
1200
+ // Validator for day
1201
+ const dayValueIsValid = (day) => {
1202
+
1203
+ // Guard clause: if there is no day in the dateParts, we can ignore this check.
1204
+ if (!dateParts.day) {
1205
+ return true;
1206
+ }
1207
+
1208
+ // Guard clause: ensure day exists.
1209
+ if (!day) {
1210
+ return false;
1211
+ }
1212
+
1213
+ // Convert day to number
1214
+ const numDay = Number.parseInt(day, 10);
1215
+
1216
+ // Guard clause: ensure day is a valid integer
1217
+ if (Number.isNaN(numDay)) {
1218
+ throw new Error('AuroDatepickerUtilities | dayValueIsValid: Unable to parse day value integer');
1219
+ }
1220
+
1221
+ // Guard clause: ensure day is within the valid range
1222
+ if (numDay < this.minDay || numDay > this.maxDay) {
1223
+ return false;
1224
+ }
1225
+
1226
+ // Default return
1227
+ return true;
1228
+ };
1229
+
1230
+ // Validator for month
1231
+ const monthValueIsValid = (month) => {
1232
+
1233
+ // Guard clause: ensure month exists.
1234
+ if (!month) {
1235
+ return false;
1236
+ }
1237
+
1238
+ // Convert month to number
1239
+ const numMonth = Number.parseInt(month, 10);
1240
+
1241
+ // Guard clause: ensure month is a valid integer
1242
+ if (Number.isNaN(numMonth)) {
1243
+ throw new Error('AuroDatepickerUtilities | monthValueIsValid: Unable to parse month value integer');
1244
+ }
1245
+
1246
+ // Guard clause: ensure month is within the valid range
1247
+ if (numMonth < this.minMonth || numMonth > this.maxMonth) {
1248
+ return false;
1249
+ }
1250
+
1251
+ // Default return
1252
+ return true;
1253
+ };
1254
+
1255
+ // Validator for year
1256
+ const yearIsValid = (_year) => {
1257
+
1258
+ // Guard clause: ensure year exists.
1259
+ if (!_year) {
1260
+ return false;
1261
+ }
1262
+
1263
+ // Get the full year
1264
+ const year = this.getFourDigitYear(_year);
1265
+
1266
+ // Convert year to number
1267
+ const numYear = Number.parseInt(year, 10);
1268
+
1269
+ // Guard clause: ensure year is a valid integer
1270
+ if (Number.isNaN(numYear)) {
1271
+ throw new Error('AuroDatepickerUtilities | yearValueIsValid: Unable to parse year value integer');
1272
+ }
1273
+
1274
+ // Guard clause: ensure year is within the valid range
1275
+ if (numYear < this.minYear || numYear > this.maxYear) {
1276
+ return false;
1277
+ }
1278
+
1279
+ // Default return
1280
+ return true;
1281
+ };
1282
+
1283
+ // Self-contained checks for month, day, and year
1284
+ const checks = [
1285
+ monthValueIsValid(dateParts.month),
1286
+ dayValueIsValid(dateParts.day),
1287
+ yearIsValid(dateParts.year)
1288
+ ];
1289
+
1290
+ // If any of the checks failed, the date format does not match and the result is invalid
1291
+ const isValid = checks.every((check) => check === true);
1292
+
1293
+ // If the check is invalid, return false
1294
+ if (!isValid) {
1295
+ return false;
1296
+ }
1297
+
1298
+ // Default case
1299
+ return true;
1300
+ };
1301
+ }
1302
+ }
1303
+
1304
+ // Export a class instance
1305
+ const dateUtilities = new AuroDateUtilities();
1306
+
1307
+ // Export the class instance methods individually
1308
+ const {
1309
+ datesMatch,
1310
+ validDateStr,
1311
+ dateAndFormatMatch,
1312
+ minDay,
1313
+ minMonth,
1314
+ minYear,
1315
+ maxDay,
1316
+ maxMonth,
1317
+ maxYear
1318
+ } = dateUtilities;
1319
+
1320
+ const {
1321
+ toNorthAmericanFormat,
1322
+ parseDate,
1323
+ getDateAsString
1324
+ } = dateFormatter;
1325
+
918
1326
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
919
1327
  // See LICENSE in the project root for license information.
920
1328
 
921
1329
 
922
1330
  class AuroFormValidation {
1331
+
923
1332
  constructor() {
924
1333
  this.runtimeUtils = new AuroLibraryRuntimeUtils$3();
925
1334
  }
@@ -1011,17 +1420,17 @@ class AuroFormValidation {
1011
1420
  ]
1012
1421
  }
1013
1422
  };
1014
-
1423
+
1015
1424
  let elementType;
1016
1425
  if (this.runtimeUtils.elementMatch(elem, 'auro-input')) {
1017
1426
  elementType = 'input';
1018
1427
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-counter') || this.runtimeUtils.elementMatch(elem, 'auro-counter-group')) {
1019
1428
  elementType = 'counter';
1020
1429
  }
1021
-
1430
+
1022
1431
  if (elementType) {
1023
1432
  const rules = validationRules[elementType];
1024
-
1433
+
1025
1434
  if (rules) {
1026
1435
  Object.values(rules).flat().forEach(rule => {
1027
1436
  if (rule.check(elem)) {
@@ -1047,48 +1456,82 @@ class AuroFormValidation {
1047
1456
  if (!elem.value.match(emailRegex)) {
1048
1457
  elem.validity = 'patternMismatch';
1049
1458
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1459
+ return;
1050
1460
  }
1051
1461
  } else if (elem.type === 'credit-card') {
1052
1462
  if (elem.value.length > 0 && elem.value.length < elem.validationCCLength) {
1053
1463
  elem.validity = 'tooShort';
1054
1464
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1465
+ return;
1055
1466
  }
1056
1467
  } else if (elem.type === 'number') {
1057
1468
  if (elem.max !== undefined && Number(elem.max) < Number(elem.value)) {
1058
1469
  elem.validity = 'rangeOverflow';
1059
1470
  elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
1471
+ return;
1060
1472
  }
1061
1473
 
1062
1474
  if (elem.min !== undefined && elem.value?.length > 0 && Number(elem.min) > Number(elem.value)) {
1063
1475
  elem.validity = 'rangeUnderflow';
1064
1476
  elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
1477
+ return;
1065
1478
  }
1066
- } else if (elem.type === 'date') {
1067
- if (elem.value?.length > 0 && elem.value?.length < elem.lengthForType) {
1479
+ } else if (elem.type === 'date' && elem.value?.length > 0) {
1480
+
1481
+ // Guard Clause: if the value is too short
1482
+ if (elem.value.length < elem.lengthForType) {
1483
+
1068
1484
  elem.validity = 'tooShort';
1069
1485
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1070
- } else if (elem.value?.length === elem.lengthForType && elem.util.toNorthAmericanFormat(elem.value, elem.format)) {
1071
- const formattedValue = elem.util.toNorthAmericanFormat(elem.value, elem.format);
1072
- const valueDate = new Date(formattedValue.dateForComparison);
1486
+ return;
1487
+ }
1488
+
1489
+ // Guard Clause: If the value is too long for the type
1490
+ if (elem.value?.length > elem.lengthForType) {
1073
1491
 
1074
- // validate max
1075
- if (elem.max?.length === elem.lengthForType) {
1076
- const maxDate = new Date(elem.util.toNorthAmericanFormat(elem.max, elem.format).dateForComparison);
1492
+ elem.validity = 'tooLong';
1493
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1494
+ return;
1495
+ }
1496
+
1497
+ // Validate that the date passed was the correct format
1498
+ if (!dateAndFormatMatch(elem.value, elem.format)) {
1499
+ elem.validity = 'patternMismatch';
1500
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || 'Invalid Date Format Entered';
1501
+ return;
1502
+ }
1503
+
1504
+ // Validate that the date passed was a valid date
1505
+ if (!validDateStr(elem.value, elem.format)) {
1506
+ elem.validity = 'invalidDate';
1507
+ elem.errorMessage = elem.setCustomValidityInvalidDate || elem.setCustomValidity || 'Invalid Date Entered';
1508
+ return;
1509
+ }
1077
1510
 
1078
- if (valueDate > maxDate) {
1079
- elem.validity = 'rangeOverflow';
1080
- elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
1081
- }
1511
+ // Perform the rest of the validation
1512
+ const formattedValue = toNorthAmericanFormat(elem.value, elem.format);
1513
+ const valueDate = new Date(formattedValue);
1514
+
1515
+ // // Validate max date
1516
+ if (elem.max?.length === elem.lengthForType) {
1517
+
1518
+ const maxDate = new Date(toNorthAmericanFormat(elem.max, elem.format));
1519
+
1520
+ if (valueDate > maxDate) {
1521
+ elem.validity = 'rangeOverflow';
1522
+ elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
1523
+ return;
1082
1524
  }
1525
+ }
1083
1526
 
1084
- // validate min
1085
- if (elem.min?.length === elem.lengthForType) {
1086
- const minDate = new Date(elem.util.toNorthAmericanFormat(elem.min, elem.format).dateForComparison);
1527
+ // Validate min date
1528
+ if (elem.min?.length === elem.lengthForType) {
1529
+ const minDate = new Date(toNorthAmericanFormat(elem.min, elem.format));
1087
1530
 
1088
- if (valueDate < minDate) {
1089
- elem.validity = 'rangeUnderflow';
1090
- elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
1091
- }
1531
+ if (valueDate < minDate) {
1532
+ elem.validity = 'rangeUnderflow';
1533
+ elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
1534
+ return;
1092
1535
  }
1093
1536
  }
1094
1537
  }
@@ -1207,7 +1650,7 @@ class AuroFormValidation {
1207
1650
  if (input.validationMessage.length > 0) {
1208
1651
  elem.errorMessage = input.validationMessage;
1209
1652
  }
1210
- } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
1653
+ } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
1211
1654
  const firstInput = this.inputElements[0];
1212
1655
 
1213
1656
  if (firstInput.validationMessage.length > 0) {
@@ -3435,7 +3878,7 @@ class AuroFloatingUI {
3435
3878
  /**
3436
3879
  * @private
3437
3880
  * getting called on 'blur' in trigger or `focusin` in document
3438
- *
3881
+ *
3439
3882
  * Hides the bib if focus moves outside of the trigger or bib, unless a 'noHideOnThisFocusLoss' flag is set.
3440
3883
  * This method checks if the currently active element is still within the trigger or bib.
3441
3884
  * If not, and if the bib isn't in fullscreen mode with focus lost, it hides the bib.
@@ -3551,7 +3994,7 @@ class AuroFloatingUI {
3551
3994
  // Close any other dropdown that is already open
3552
3995
  const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3553
3996
  if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this &&
3554
- existedVisibleFloatingUI.isPopoverVisible &&
3997
+ existedVisibleFloatingUI.element.isPopoverVisible &&
3555
3998
  document.expandedAuroFloater.eventPrefix === this.eventPrefix) {
3556
3999
  document.expandedAuroFloater.hideBib();
3557
4000
  }
@@ -3727,7 +4170,7 @@ class AuroFloatingUI {
3727
4170
  this.id = window.crypto.randomUUID();
3728
4171
  this.element.setAttribute('id', this.id);
3729
4172
  }
3730
-
4173
+
3731
4174
  this.element.bib.setAttribute("id", `${this.id}-floater-bib`);
3732
4175
  }
3733
4176
 
@@ -3780,7 +4223,7 @@ class AuroFloatingUI {
3780
4223
  if (this.element.bib) {
3781
4224
  this.element.shadowRoot.append(this.element.bib);
3782
4225
  }
3783
-
4226
+
3784
4227
  // Remove event & keyboard listeners
3785
4228
  if (this.element?.trigger) {
3786
4229
  this.element.trigger.removeEventListener('keydown', this.handleEvent);
@@ -4509,6 +4952,7 @@ var helpTextVersion = '1.0.0';
4509
4952
  * @csspart helpText - The helpText content container.
4510
4953
  * @event auroDropdown-triggerClick - Notifies that the trigger has been clicked.
4511
4954
  * @event auroDropdown-toggled - Notifies that the visibility of the dropdown bib has changed.
4955
+ * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
4512
4956
  */
4513
4957
  class AuroDropdown extends LitElement {
4514
4958
  constructor() {
@@ -4554,7 +4998,9 @@ class AuroDropdown extends LitElement {
4554
4998
  this.rounded = false;
4555
4999
  this.tabIndex = 0;
4556
5000
  this.noToggle = false;
5001
+ this.a11yAutocomplete = 'none';
4557
5002
  this.labeled = true;
5003
+ this.a11yRole = 'combobox';
4558
5004
  this.onDark = false;
4559
5005
 
4560
5006
  // floaterConfig
@@ -4690,6 +5136,16 @@ class AuroDropdown extends LitElement {
4690
5136
  type: Number
4691
5137
  },
4692
5138
 
5139
+ /**
5140
+ * The unique ID for the dropdown bib element.
5141
+ * @private
5142
+ */
5143
+ dropdownId: {
5144
+ type: String,
5145
+ reflect: false,
5146
+ attribute: false
5147
+ },
5148
+
4693
5149
  /**
4694
5150
  * If declared in combination with `bordered` property or `helpText` slot content, will apply red color to both.
4695
5151
  */
@@ -4852,6 +5308,23 @@ class AuroDropdown extends LitElement {
4852
5308
  */
4853
5309
  tabIndex: {
4854
5310
  type: Number
5311
+ },
5312
+
5313
+ /**
5314
+ * The value for the role attribute of the trigger element.
5315
+ */
5316
+ a11yRole: {
5317
+ type: String || undefined,
5318
+ attribute: false,
5319
+ reflect: false
5320
+ },
5321
+
5322
+ /**
5323
+ * The value for the aria-autocomplete attribute of the trigger element.
5324
+ */
5325
+ a11yAutocomplete: {
5326
+ type: String,
5327
+ attribute: false,
4855
5328
  }
4856
5329
  };
4857
5330
  }
@@ -4902,7 +5375,22 @@ class AuroDropdown extends LitElement {
4902
5375
  }
4903
5376
 
4904
5377
  firstUpdated() {
5378
+
5379
+ // Configure the floater to, this will generate the ID for the bib
4905
5380
  this.floater.configure(this, 'auroDropdown');
5381
+
5382
+ /**
5383
+ * @description Let subscribers know that the dropdown ID ha been generated and added.
5384
+ * @event auroDropdown-idAdded
5385
+ * @type {Object<key: 'id', value: string>} - The ID of the dropdown bib element.
5386
+ */
5387
+ this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
5388
+
5389
+ // Set the bib ID locally if the user hasn't provided a focusable trigger
5390
+ if (!this.triggerContentFocusable) {
5391
+ this.dropdownId = this.floater.element.id;
5392
+ }
5393
+
4906
5394
  this.bibContent = this.floater.element.bib;
4907
5395
 
4908
5396
  // Add the tag name as an attribute if it is different than the component name
@@ -5054,6 +5542,30 @@ class AuroDropdown extends LitElement {
5054
5542
  });
5055
5543
  }
5056
5544
 
5545
+ /*
5546
+ * Sets aria attributes for the trigger element if a custom one is passed in.
5547
+ * @private
5548
+ * @method setTriggerAriaAttributes
5549
+ * @param { HTMLElement } triggerElement - The custom trigger element.
5550
+ */
5551
+ clearTriggerA11yAttributes(triggerElement) {
5552
+
5553
+ if (!triggerElement || !triggerElement.removeAttribute) {
5554
+ return;
5555
+ }
5556
+
5557
+ // Reset appropriate attributes for a11y
5558
+ triggerElement.removeAttribute('aria-labelledby');
5559
+ if (triggerElement.getAttribute('id') === `${this.id}-trigger-element`) {
5560
+ triggerElement.removeAttribute('id');
5561
+ }
5562
+ triggerElement.removeAttribute('role');
5563
+ triggerElement.removeAttribute('aria-expanded');
5564
+
5565
+ triggerElement.removeAttribute('aria-controls');
5566
+ triggerElement.removeAttribute('aria-autocomplete');
5567
+ }
5568
+
5057
5569
  /**
5058
5570
  * Handles changes to the trigger content slot and updates related properties.
5059
5571
  *
@@ -5067,32 +5579,41 @@ class AuroDropdown extends LitElement {
5067
5579
  * @returns {void}
5068
5580
  */
5069
5581
  handleTriggerContentSlotChange(event) {
5582
+
5070
5583
  this.floater.handleTriggerTabIndex();
5071
5584
 
5585
+ // Get the trigger
5586
+ const trigger = this.shadowRoot.querySelector('#trigger');
5587
+
5588
+ // Get the trigger slot
5072
5589
  const triggerSlot = this.shadowRoot.querySelector('.triggerContent slot');
5073
5590
 
5591
+ // If there's a trigger slot
5074
5592
  if (triggerSlot) {
5075
5593
 
5594
+ // Get the content nodes to see if there are any children
5076
5595
  const triggerContentNodes = triggerSlot.assignedNodes();
5077
5596
 
5597
+ // If there are children
5078
5598
  if (triggerContentNodes) {
5079
5599
 
5080
- triggerContentNodes.forEach((node) => {
5081
- if (!this.triggerContentFocusable) {
5082
- this.triggerContentFocusable = this.containsFocusableElement(node);
5083
- }
5084
- });
5085
- }
5086
- }
5600
+ // See if any of them are focusable elemeents
5601
+ this.triggerContentFocusable = triggerContentNodes.some((node) => this.containsFocusableElement(node));
5087
5602
 
5088
- const trigger = this.shadowRoot.querySelector('#trigger');
5603
+ // If any of them are focusable elements
5604
+ if (this.triggerContentFocusable) {
5089
5605
 
5090
- if (!this.triggerContentFocusable) {
5091
- trigger.setAttribute('tabindex', '0');
5092
- trigger.setAttribute('role', 'button');
5093
- } else {
5094
- trigger.removeAttribute('tabindex');
5095
- trigger.removeAttribute('role');
5606
+ // Assume the consumer will be providing their own a11y in whatever they passed in
5607
+ this.clearTriggerA11yAttributes(trigger);
5608
+
5609
+ // Remove the tabindex from the trigger so it doesn't interrupt focus flow
5610
+ trigger.removeAttribute('tabindex');
5611
+ } else {
5612
+
5613
+ // Add the tabindex to the trigger so that it's in the focus flow
5614
+ trigger.setAttribute('tabindex', '0');
5615
+ }
5616
+ }
5096
5617
  }
5097
5618
 
5098
5619
  if (event) {
@@ -5102,6 +5623,7 @@ class AuroDropdown extends LitElement {
5102
5623
 
5103
5624
  if (this.triggerContentSlot) {
5104
5625
  this.setupTriggerFocusEventBinding();
5626
+
5105
5627
  this.hasTriggerContent = this.triggerContentSlot.some((slot) => {
5106
5628
  if (slot.textContent.trim()) {
5107
5629
  return true;
@@ -5169,10 +5691,13 @@ class AuroDropdown extends LitElement {
5169
5691
  id="trigger"
5170
5692
  class="trigger"
5171
5693
  part="trigger"
5172
- aria-labelledby="triggerLabel"
5173
5694
  tabindex="${this.tabIndex}"
5174
5695
  ?showBorder="${this.showTriggerBorders}"
5175
- >
5696
+ role="${ifDefined(this.triggerContentFocusable ? undefined : this.a11yRole)}"
5697
+ aria-expanded="${ifDefined(this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
5698
+ aria-controls="${ifDefined(this.triggerContentFocusable ? undefined : this.dropdownId)}"
5699
+ aria-labelledby="${ifDefined(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
5700
+ >
5176
5701
  <div class="triggerContentWrapper">
5177
5702
  <label class="label" id="triggerLabel" hasTrigger=${this.hasTriggerContent}>
5178
5703
  <slot name="label" @slotchange="${this.handleLabelSlotChange}"></slot>
@@ -5206,12 +5731,12 @@ class AuroDropdown extends LitElement {
5206
5731
  <div id="bibSizer" part="size"></div>
5207
5732
  <${this.dropdownBibTag}
5208
5733
  id="bib"
5209
- role="tooltip"
5210
5734
  ?data-show="${this.isPopoverVisible}"
5211
5735
  ?isfullscreen="${this.isBibFullscreen}"
5212
5736
  ?common="${this.common}"
5213
5737
  ?rounded="${this.common || this.rounded}"
5214
- ?inset="${this.common || this.inset}">
5738
+ ?inset="${this.common || this.inset}"
5739
+ >
5215
5740
  </${this.dropdownBibTag}>
5216
5741
  </div>
5217
5742
  `;