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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/components/checkbox/README.md +1 -1
  3. package/components/checkbox/demo/api.min.js +468 -25
  4. package/components/checkbox/demo/index.min.js +468 -25
  5. package/components/checkbox/demo/readme.md +1 -1
  6. package/components/checkbox/dist/index.js +468 -25
  7. package/components/checkbox/dist/registered.js +468 -25
  8. package/components/combobox/README.md +1 -1
  9. package/components/combobox/demo/api.min.js +1125 -74
  10. package/components/combobox/demo/index.min.js +1125 -74
  11. package/components/combobox/demo/readme.md +1 -1
  12. package/components/combobox/dist/auro-combobox.d.ts +30 -0
  13. package/components/combobox/dist/index.js +1125 -74
  14. package/components/combobox/dist/registered.js +1125 -74
  15. package/components/counter/README.md +1 -1
  16. package/components/counter/demo/api.min.js +570 -45
  17. package/components/counter/demo/index.min.js +570 -45
  18. package/components/counter/demo/readme.md +1 -1
  19. package/components/counter/dist/index.js +570 -45
  20. package/components/counter/dist/registered.js +570 -45
  21. package/components/datepicker/README.md +1 -1
  22. package/components/datepicker/demo/api.min.js +1073 -70
  23. package/components/datepicker/demo/index.min.js +1073 -70
  24. package/components/datepicker/demo/readme.md +1 -1
  25. package/components/datepicker/dist/index.js +1073 -70
  26. package/components/datepicker/dist/registered.js +1073 -70
  27. package/components/dropdown/README.md +1 -1
  28. package/components/dropdown/demo/api.md +8 -5
  29. package/components/dropdown/demo/api.min.js +104 -22
  30. package/components/dropdown/demo/index.min.js +104 -22
  31. package/components/dropdown/demo/readme.md +1 -1
  32. package/components/dropdown/dist/auro-dropdown.d.ts +29 -0
  33. package/components/dropdown/dist/index.js +104 -22
  34. package/components/dropdown/dist/registered.js +104 -22
  35. package/components/form/README.md +1 -1
  36. package/components/form/demo/readme.md +1 -1
  37. package/components/input/README.md +1 -1
  38. package/components/input/demo/api.md +4 -1
  39. package/components/input/demo/api.min.js +503 -25
  40. package/components/input/demo/index.min.js +503 -25
  41. package/components/input/demo/readme.md +1 -1
  42. package/components/input/dist/base-input.d.ts +24 -0
  43. package/components/input/dist/index.js +503 -25
  44. package/components/input/dist/registered.js +503 -25
  45. package/components/menu/README.md +1 -1
  46. package/components/menu/demo/readme.md +1 -1
  47. package/components/radio/README.md +1 -1
  48. package/components/radio/demo/api.min.js +468 -25
  49. package/components/radio/demo/index.min.js +468 -25
  50. package/components/radio/demo/readme.md +1 -1
  51. package/components/radio/dist/index.js +468 -25
  52. package/components/radio/dist/registered.js +468 -25
  53. package/components/select/README.md +1 -1
  54. package/components/select/demo/api.min.js +570 -45
  55. package/components/select/demo/index.min.js +570 -45
  56. package/components/select/demo/readme.md +1 -1
  57. package/components/select/dist/index.js +570 -45
  58. package/components/select/dist/registered.js +570 -45
  59. package/package.json +2 -2
@@ -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);
@@ -4511,6 +4954,7 @@ var helpTextVersion = '1.0.0';
4511
4954
  * @csspart helpText - The helpText content container.
4512
4955
  * @event auroDropdown-triggerClick - Notifies that the trigger has been clicked.
4513
4956
  * @event auroDropdown-toggled - Notifies that the visibility of the dropdown bib has changed.
4957
+ * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
4514
4958
  */
4515
4959
  class AuroDropdown extends LitElement {
4516
4960
  constructor() {
@@ -4556,7 +5000,9 @@ class AuroDropdown extends LitElement {
4556
5000
  this.rounded = false;
4557
5001
  this.tabIndex = 0;
4558
5002
  this.noToggle = false;
5003
+ this.a11yAutocomplete = 'none';
4559
5004
  this.labeled = true;
5005
+ this.a11yRole = 'combobox';
4560
5006
  this.onDark = false;
4561
5007
 
4562
5008
  // floaterConfig
@@ -4692,6 +5138,16 @@ class AuroDropdown extends LitElement {
4692
5138
  type: Number
4693
5139
  },
4694
5140
 
5141
+ /**
5142
+ * The unique ID for the dropdown bib element.
5143
+ * @private
5144
+ */
5145
+ dropdownId: {
5146
+ type: String,
5147
+ reflect: false,
5148
+ attribute: false
5149
+ },
5150
+
4695
5151
  /**
4696
5152
  * If declared in combination with `bordered` property or `helpText` slot content, will apply red color to both.
4697
5153
  */
@@ -4859,6 +5315,23 @@ class AuroDropdown extends LitElement {
4859
5315
  */
4860
5316
  tabIndex: {
4861
5317
  type: Number
5318
+ },
5319
+
5320
+ /**
5321
+ * The value for the role attribute of the trigger element.
5322
+ */
5323
+ a11yRole: {
5324
+ type: String || undefined,
5325
+ attribute: false,
5326
+ reflect: false
5327
+ },
5328
+
5329
+ /**
5330
+ * The value for the aria-autocomplete attribute of the trigger element.
5331
+ */
5332
+ a11yAutocomplete: {
5333
+ type: String,
5334
+ attribute: false,
4862
5335
  }
4863
5336
  };
4864
5337
  }
@@ -4920,7 +5393,22 @@ class AuroDropdown extends LitElement {
4920
5393
  }
4921
5394
 
4922
5395
  firstUpdated() {
5396
+
5397
+ // Configure the floater to, this will generate the ID for the bib
4923
5398
  this.floater.configure(this, 'auroDropdown');
5399
+
5400
+ /**
5401
+ * @description Let subscribers know that the dropdown ID ha been generated and added.
5402
+ * @event auroDropdown-idAdded
5403
+ * @type {Object<key: 'id', value: string>} - The ID of the dropdown bib element.
5404
+ */
5405
+ this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
5406
+
5407
+ // Set the bib ID locally if the user hasn't provided a focusable trigger
5408
+ if (!this.triggerContentFocusable) {
5409
+ this.dropdownId = this.floater.element.id;
5410
+ }
5411
+
4924
5412
  this.bibContent = this.floater.element.bib;
4925
5413
 
4926
5414
  // Add the tag name as an attribute if it is different than the component name
@@ -5072,6 +5560,30 @@ class AuroDropdown extends LitElement {
5072
5560
  });
5073
5561
  }
5074
5562
 
5563
+ /*
5564
+ * Sets aria attributes for the trigger element if a custom one is passed in.
5565
+ * @private
5566
+ * @method setTriggerAriaAttributes
5567
+ * @param { HTMLElement } triggerElement - The custom trigger element.
5568
+ */
5569
+ clearTriggerA11yAttributes(triggerElement) {
5570
+
5571
+ if (!triggerElement || !triggerElement.removeAttribute) {
5572
+ return;
5573
+ }
5574
+
5575
+ // Reset appropriate attributes for a11y
5576
+ triggerElement.removeAttribute('aria-labelledby');
5577
+ if (triggerElement.getAttribute('id') === `${this.id}-trigger-element`) {
5578
+ triggerElement.removeAttribute('id');
5579
+ }
5580
+ triggerElement.removeAttribute('role');
5581
+ triggerElement.removeAttribute('aria-expanded');
5582
+
5583
+ triggerElement.removeAttribute('aria-controls');
5584
+ triggerElement.removeAttribute('aria-autocomplete');
5585
+ }
5586
+
5075
5587
  /**
5076
5588
  * Handles changes to the trigger content slot and updates related properties.
5077
5589
  *
@@ -5085,32 +5597,41 @@ class AuroDropdown extends LitElement {
5085
5597
  * @returns {void}
5086
5598
  */
5087
5599
  handleTriggerContentSlotChange(event) {
5600
+
5088
5601
  this.floater.handleTriggerTabIndex();
5089
5602
 
5603
+ // Get the trigger
5604
+ const trigger = this.shadowRoot.querySelector('#trigger');
5605
+
5606
+ // Get the trigger slot
5090
5607
  const triggerSlot = this.shadowRoot.querySelector('.triggerContent slot');
5091
5608
 
5609
+ // If there's a trigger slot
5092
5610
  if (triggerSlot) {
5093
5611
 
5612
+ // Get the content nodes to see if there are any children
5094
5613
  const triggerContentNodes = triggerSlot.assignedNodes();
5095
5614
 
5615
+ // If there are children
5096
5616
  if (triggerContentNodes) {
5097
5617
 
5098
- triggerContentNodes.forEach((node) => {
5099
- if (!this.triggerContentFocusable) {
5100
- this.triggerContentFocusable = this.containsFocusableElement(node);
5101
- }
5102
- });
5103
- }
5104
- }
5618
+ // See if any of them are focusable elemeents
5619
+ this.triggerContentFocusable = triggerContentNodes.some((node) => this.containsFocusableElement(node));
5105
5620
 
5106
- const trigger = this.shadowRoot.querySelector('#trigger');
5621
+ // If any of them are focusable elements
5622
+ if (this.triggerContentFocusable) {
5107
5623
 
5108
- if (!this.triggerContentFocusable) {
5109
- trigger.setAttribute('tabindex', '0');
5110
- trigger.setAttribute('role', 'button');
5111
- } else {
5112
- trigger.removeAttribute('tabindex');
5113
- trigger.removeAttribute('role');
5624
+ // Assume the consumer will be providing their own a11y in whatever they passed in
5625
+ this.clearTriggerA11yAttributes(trigger);
5626
+
5627
+ // Remove the tabindex from the trigger so it doesn't interrupt focus flow
5628
+ trigger.removeAttribute('tabindex');
5629
+ } else {
5630
+
5631
+ // Add the tabindex to the trigger so that it's in the focus flow
5632
+ trigger.setAttribute('tabindex', '0');
5633
+ }
5634
+ }
5114
5635
  }
5115
5636
 
5116
5637
  if (event) {
@@ -5120,6 +5641,7 @@ class AuroDropdown extends LitElement {
5120
5641
 
5121
5642
  if (this.triggerContentSlot) {
5122
5643
  this.setupTriggerFocusEventBinding();
5644
+
5123
5645
  this.hasTriggerContent = this.triggerContentSlot.some((slot) => {
5124
5646
  if (slot.textContent.trim()) {
5125
5647
  return true;
@@ -5187,10 +5709,13 @@ class AuroDropdown extends LitElement {
5187
5709
  id="trigger"
5188
5710
  class="trigger"
5189
5711
  part="trigger"
5190
- aria-labelledby="triggerLabel"
5191
5712
  tabindex="${this.tabIndex}"
5192
5713
  ?showBorder="${this.showTriggerBorders}"
5193
- >
5714
+ role="${ifDefined(this.triggerContentFocusable ? undefined : this.a11yRole)}"
5715
+ aria-expanded="${ifDefined(this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
5716
+ aria-controls="${ifDefined(this.triggerContentFocusable ? undefined : this.dropdownId)}"
5717
+ aria-labelledby="${ifDefined(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
5718
+ >
5194
5719
  <div class="triggerContentWrapper">
5195
5720
  <label class="label" id="triggerLabel" hasTrigger=${this.hasTriggerContent}>
5196
5721
  <slot name="label" @slotchange="${this.handleLabelSlotChange}"></slot>
@@ -5224,12 +5749,12 @@ class AuroDropdown extends LitElement {
5224
5749
  <div id="bibSizer" part="size"></div>
5225
5750
  <${this.dropdownBibTag}
5226
5751
  id="bib"
5227
- role="tooltip"
5228
5752
  ?data-show="${this.isPopoverVisible}"
5229
5753
  ?isfullscreen="${this.isBibFullscreen}"
5230
5754
  ?common="${this.common}"
5231
5755
  ?rounded="${this.common || this.rounded}"
5232
- ?inset="${this.common || this.inset}">
5756
+ ?inset="${this.common || this.inset}"
5757
+ >
5233
5758
  </${this.dropdownBibTag}>
5234
5759
  </div>
5235
5760
  `;