@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
@@ -962,11 +962,420 @@ let AuroIcon$2 = class AuroIcon extends BaseIcon$2 {
962
962
 
963
963
  var iconVersion$2 = '8.0.1';
964
964
 
965
+ class DateFormatter {
966
+
967
+ constructor() {
968
+
969
+ /**
970
+ * @description Parses a date string into its components.
971
+ * @param {string} dateStr - Date string to parse.
972
+ * @param {string} format - Date format to parse.
973
+ * @returns {Object<key["month" | "day" | "year"]: number>|undefined}
974
+ */
975
+ this.parseDate = (dateStr, format = 'mm/dd/yyyy') => {
976
+
977
+ // Guard Clause: Date string is defined
978
+ if (!dateStr) {
979
+ return undefined;
980
+ }
981
+
982
+ // Assume the separator is a "/" a defined in our code base
983
+ const separator = '/';
984
+
985
+ // Get the parts of the date and format
986
+ const valueParts = dateStr.split(separator);
987
+ const formatParts = format.split(separator);
988
+
989
+ // Check if the value and format have the correct number of parts
990
+ if (valueParts.length !== formatParts.length) {
991
+ throw new Error('AuroDatepickerUtilities | parseDate: Date string and format length do not match');
992
+ }
993
+
994
+ // Holds the result to be returned
995
+ const result = formatParts.reduce((acc, part, index) => {
996
+ const value = valueParts[index];
997
+
998
+ if ((/m/iu).test(part)) {
999
+ acc.month = value;
1000
+ } else if ((/d/iu).test(part)) {
1001
+ acc.day = value;
1002
+ } else if ((/y/iu).test(part)) {
1003
+ acc.year = value;
1004
+ }
1005
+
1006
+ return acc;
1007
+ }, {});
1008
+
1009
+ // If we found all the parts, return the result
1010
+ if (result.month && result.year) {
1011
+ return result;
1012
+ }
1013
+
1014
+ // Throw an error to let the dev know we were unable to parse the date string
1015
+ throw new Error('AuroDatepickerUtilities | parseDate: Unable to parse date string');
1016
+ };
1017
+
1018
+ /**
1019
+ * Convert a date object to string format.
1020
+ * @param {Object} date - Date to convert to string.
1021
+ * @returns {Object} Returns the date as a string.
1022
+ */
1023
+ this.getDateAsString = (date) => date.toLocaleDateString(undefined, {
1024
+ year: "numeric",
1025
+ month: "2-digit",
1026
+ day: "2-digit",
1027
+ });
1028
+
1029
+ /**
1030
+ * Converts a date string to a North American date format.
1031
+ * @param {String} dateStr - Date to validate.
1032
+ * @param {String} format - Date format to validate against.
1033
+ * @returns {Boolean}
1034
+ */
1035
+ this.toNorthAmericanFormat = (dateStr, format) => {
1036
+
1037
+ if (format === 'mm/dd/yyyy') {
1038
+ return dateStr;
1039
+ }
1040
+
1041
+ const parsedDate = this.parseDate(dateStr, format);
1042
+
1043
+ if (!parsedDate) {
1044
+ throw new Error('AuroDatepickerUtilities | toNorthAmericanFormat: Unable to parse date string');
1045
+ }
1046
+
1047
+ const { month, day, year } = parsedDate;
1048
+
1049
+ const dateParts = [];
1050
+ if (month) {
1051
+ dateParts.push(month);
1052
+ }
1053
+
1054
+ if (day) {
1055
+ dateParts.push(day);
1056
+ }
1057
+
1058
+ if (year) {
1059
+ dateParts.push(year);
1060
+ }
1061
+
1062
+ return dateParts.join('/');
1063
+ };
1064
+ }
1065
+ }
1066
+ const dateFormatter = new DateFormatter();
1067
+
1068
+ // filepath: dateConstraints.mjs
1069
+ const DATE_UTIL_CONSTRAINTS = {
1070
+ maxDay: 31,
1071
+ maxMonth: 12,
1072
+ maxYear: 2400,
1073
+ minDay: 1,
1074
+ minMonth: 1,
1075
+ minYear: 1900,
1076
+ };
1077
+
1078
+ class AuroDateUtilitiesBase {
1079
+
1080
+ /**
1081
+ * @description The maximum day value allowed by the various utilities in this class.
1082
+ * @readonly
1083
+ * @type {Number}
1084
+ */
1085
+ get maxDay() {
1086
+ return DATE_UTIL_CONSTRAINTS.maxDay;
1087
+ }
1088
+
1089
+ /**
1090
+ * @description The maximum month value allowed by the various utilities in this class.
1091
+ * @readonly
1092
+ * @type {Number}
1093
+ */
1094
+ get maxMonth() {
1095
+ return DATE_UTIL_CONSTRAINTS.maxMonth;
1096
+ }
1097
+
1098
+ /**
1099
+ * @description The maximum year value allowed by the various utilities in this class.
1100
+ * @readonly
1101
+ * @type {Number}
1102
+ */
1103
+ get maxYear() {
1104
+ return DATE_UTIL_CONSTRAINTS.maxYear;
1105
+ }
1106
+
1107
+ /**
1108
+ * @description The minimum day value allowed by the various utilities in this class.
1109
+ * @readonly
1110
+ * @type {Number}
1111
+ */
1112
+ get minDay() {
1113
+ return DATE_UTIL_CONSTRAINTS.minDay;
1114
+ }
1115
+
1116
+ /**
1117
+ * @description The minimum month value allowed by the various utilities in this class.
1118
+ * @readonly
1119
+ * @type {Number}
1120
+ */
1121
+ get minMonth() {
1122
+ return DATE_UTIL_CONSTRAINTS.minMonth;
1123
+ }
1124
+
1125
+ /**
1126
+ * @description The minimum year value allowed by the various utilities in this class.
1127
+ * @readonly
1128
+ * @type {Number}
1129
+ */
1130
+ get minYear() {
1131
+ return DATE_UTIL_CONSTRAINTS.minYear;
1132
+ }
1133
+ }
1134
+
1135
+ /* eslint-disable no-magic-numbers */
1136
+
1137
+ class AuroDateUtilities extends AuroDateUtilitiesBase {
1138
+
1139
+ /**
1140
+ * Returns the current century.
1141
+ * @returns {String} The current century.
1142
+ */
1143
+ getCentury () {
1144
+ return String(new Date().getFullYear()).slice(0, 2);
1145
+ }
1146
+
1147
+ /**
1148
+ * Returns a four digit year.
1149
+ * @param {String} year - The year to convert to four digits.
1150
+ * @returns {String} The four digit year.
1151
+ */
1152
+ getFourDigitYear (year) {
1153
+
1154
+ const strYear = String(year).trim();
1155
+ return strYear.length <= 2 ? this.getCentury() + strYear : strYear;
1156
+ }
1157
+
1158
+ constructor() {
1159
+
1160
+ super();
1161
+
1162
+ /**
1163
+ * Compares two dates to see if they match.
1164
+ * @param {Object} date1 - First date to compare.
1165
+ * @param {Object} date2 - Second date to compare.
1166
+ * @returns {Boolean} Returns true if the dates match.
1167
+ */
1168
+ this.datesMatch = (date1, date2) => new Date(date1).getTime() === new Date(date2).getTime();
1169
+
1170
+ /**
1171
+ * Returns true if value passed in is a valid date.
1172
+ * @param {String} date - Date to validate.
1173
+ * @param {String} format - Date format to validate against.
1174
+ * @returns {Boolean}
1175
+ */
1176
+ this.validDateStr = (date, format) => {
1177
+
1178
+ // The length we expect the date string to be
1179
+ const dateStrLength = format.length;
1180
+
1181
+ // Guard Clause: Date and format are defined
1182
+ if (typeof date === "undefined" || typeof format === "undefined") {
1183
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date and format are required');
1184
+ }
1185
+
1186
+ // Guard Clause: Date should be of type string
1187
+ if (typeof date !== "string") {
1188
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Date must be a string');
1189
+ }
1190
+
1191
+ // Guard Clause: Format should be of type string
1192
+ if (typeof format !== "string") {
1193
+ throw new Error('AuroDatepickerUtilities | validateDateStr: Format must be a string');
1194
+ }
1195
+
1196
+ // Guard Clause: Length is what we expect it to be
1197
+ if (date.length !== dateStrLength) {
1198
+ return false;
1199
+ }
1200
+ // Get a formatted date string and parse it
1201
+ const dateParts = dateFormatter.parseDate(date, format);
1202
+
1203
+ // Guard Clause: Date parse succeeded
1204
+ if (!dateParts) {
1205
+ return false;
1206
+ }
1207
+
1208
+ // Create the expected date string based on the date parts
1209
+ const expectedDateStr = `${dateParts.month}/${dateParts.day || "01"}/${this.getFourDigitYear(dateParts.year)}`;
1210
+
1211
+ // Generate a date object that we will extract a string date from to compare to the passed in date string
1212
+ const dateObj = new Date(this.getFourDigitYear(dateParts.year), dateParts.month - 1, dateParts.day || 1);
1213
+
1214
+ // Get the date string of the date object we created from the string date
1215
+ const actualDateStr = dateFormatter.getDateAsString(dateObj);
1216
+
1217
+ // Guard Clause: Generated date matches date string input
1218
+ if (expectedDateStr !== actualDateStr) {
1219
+ return false;
1220
+ }
1221
+
1222
+ // If we passed all other checks, we can assume the date is valid
1223
+ return true;
1224
+ };
1225
+
1226
+ /**
1227
+ * Determines if a string date value matches the format provided.
1228
+ * @param {string} value = The date string value.
1229
+ * @param { string} format = The date format to match against.
1230
+ * @returns {boolean}
1231
+ */
1232
+ this.dateAndFormatMatch = (value, format) => {
1233
+
1234
+ // Ensure we have both values we need to do the comparison
1235
+ if (!value || !format) {
1236
+ throw new Error('AuroFormValidation | dateFormatMatch: value and format are required');
1237
+ }
1238
+
1239
+ // If the lengths are different, they cannot match
1240
+ if (value.length !== format.length) {
1241
+ return false;
1242
+ }
1243
+
1244
+ // Get the parts of the date
1245
+ const dateParts = dateFormatter.parseDate(value, format);
1246
+
1247
+ // Validator for day
1248
+ const dayValueIsValid = (day) => {
1249
+
1250
+ // Guard clause: if there is no day in the dateParts, we can ignore this check.
1251
+ if (!dateParts.day) {
1252
+ return true;
1253
+ }
1254
+
1255
+ // Guard clause: ensure day exists.
1256
+ if (!day) {
1257
+ return false;
1258
+ }
1259
+
1260
+ // Convert day to number
1261
+ const numDay = Number.parseInt(day, 10);
1262
+
1263
+ // Guard clause: ensure day is a valid integer
1264
+ if (Number.isNaN(numDay)) {
1265
+ throw new Error('AuroDatepickerUtilities | dayValueIsValid: Unable to parse day value integer');
1266
+ }
1267
+
1268
+ // Guard clause: ensure day is within the valid range
1269
+ if (numDay < this.minDay || numDay > this.maxDay) {
1270
+ return false;
1271
+ }
1272
+
1273
+ // Default return
1274
+ return true;
1275
+ };
1276
+
1277
+ // Validator for month
1278
+ const monthValueIsValid = (month) => {
1279
+
1280
+ // Guard clause: ensure month exists.
1281
+ if (!month) {
1282
+ return false;
1283
+ }
1284
+
1285
+ // Convert month to number
1286
+ const numMonth = Number.parseInt(month, 10);
1287
+
1288
+ // Guard clause: ensure month is a valid integer
1289
+ if (Number.isNaN(numMonth)) {
1290
+ throw new Error('AuroDatepickerUtilities | monthValueIsValid: Unable to parse month value integer');
1291
+ }
1292
+
1293
+ // Guard clause: ensure month is within the valid range
1294
+ if (numMonth < this.minMonth || numMonth > this.maxMonth) {
1295
+ return false;
1296
+ }
1297
+
1298
+ // Default return
1299
+ return true;
1300
+ };
1301
+
1302
+ // Validator for year
1303
+ const yearIsValid = (_year) => {
1304
+
1305
+ // Guard clause: ensure year exists.
1306
+ if (!_year) {
1307
+ return false;
1308
+ }
1309
+
1310
+ // Get the full year
1311
+ const year = this.getFourDigitYear(_year);
1312
+
1313
+ // Convert year to number
1314
+ const numYear = Number.parseInt(year, 10);
1315
+
1316
+ // Guard clause: ensure year is a valid integer
1317
+ if (Number.isNaN(numYear)) {
1318
+ throw new Error('AuroDatepickerUtilities | yearValueIsValid: Unable to parse year value integer');
1319
+ }
1320
+
1321
+ // Guard clause: ensure year is within the valid range
1322
+ if (numYear < this.minYear || numYear > this.maxYear) {
1323
+ return false;
1324
+ }
1325
+
1326
+ // Default return
1327
+ return true;
1328
+ };
1329
+
1330
+ // Self-contained checks for month, day, and year
1331
+ const checks = [
1332
+ monthValueIsValid(dateParts.month),
1333
+ dayValueIsValid(dateParts.day),
1334
+ yearIsValid(dateParts.year)
1335
+ ];
1336
+
1337
+ // If any of the checks failed, the date format does not match and the result is invalid
1338
+ const isValid = checks.every((check) => check === true);
1339
+
1340
+ // If the check is invalid, return false
1341
+ if (!isValid) {
1342
+ return false;
1343
+ }
1344
+
1345
+ // Default case
1346
+ return true;
1347
+ };
1348
+ }
1349
+ }
1350
+
1351
+ // Export a class instance
1352
+ const dateUtilities = new AuroDateUtilities();
1353
+
1354
+ // Export the class instance methods individually
1355
+ const {
1356
+ datesMatch,
1357
+ validDateStr,
1358
+ dateAndFormatMatch,
1359
+ minDay,
1360
+ minMonth,
1361
+ minYear,
1362
+ maxDay,
1363
+ maxMonth,
1364
+ maxYear
1365
+ } = dateUtilities;
1366
+
1367
+ const {
1368
+ toNorthAmericanFormat,
1369
+ parseDate,
1370
+ getDateAsString
1371
+ } = dateFormatter;
1372
+
965
1373
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
966
1374
  // See LICENSE in the project root for license information.
967
1375
 
968
1376
 
969
1377
  class AuroFormValidation {
1378
+
970
1379
  constructor() {
971
1380
  this.runtimeUtils = new AuroLibraryRuntimeUtils$3();
972
1381
  }
@@ -1058,17 +1467,17 @@ class AuroFormValidation {
1058
1467
  ]
1059
1468
  }
1060
1469
  };
1061
-
1470
+
1062
1471
  let elementType;
1063
1472
  if (this.runtimeUtils.elementMatch(elem, 'auro-input')) {
1064
1473
  elementType = 'input';
1065
1474
  } else if (this.runtimeUtils.elementMatch(elem, 'auro-counter') || this.runtimeUtils.elementMatch(elem, 'auro-counter-group')) {
1066
1475
  elementType = 'counter';
1067
1476
  }
1068
-
1477
+
1069
1478
  if (elementType) {
1070
1479
  const rules = validationRules[elementType];
1071
-
1480
+
1072
1481
  if (rules) {
1073
1482
  Object.values(rules).flat().forEach(rule => {
1074
1483
  if (rule.check(elem)) {
@@ -1094,48 +1503,82 @@ class AuroFormValidation {
1094
1503
  if (!elem.value.match(emailRegex)) {
1095
1504
  elem.validity = 'patternMismatch';
1096
1505
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1506
+ return;
1097
1507
  }
1098
1508
  } else if (elem.type === 'credit-card') {
1099
1509
  if (elem.value.length > 0 && elem.value.length < elem.validationCCLength) {
1100
1510
  elem.validity = 'tooShort';
1101
1511
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1512
+ return;
1102
1513
  }
1103
1514
  } else if (elem.type === 'number') {
1104
1515
  if (elem.max !== undefined && Number(elem.max) < Number(elem.value)) {
1105
1516
  elem.validity = 'rangeOverflow';
1106
1517
  elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
1518
+ return;
1107
1519
  }
1108
1520
 
1109
1521
  if (elem.min !== undefined && elem.value?.length > 0 && Number(elem.min) > Number(elem.value)) {
1110
1522
  elem.validity = 'rangeUnderflow';
1111
1523
  elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
1524
+ return;
1112
1525
  }
1113
- } else if (elem.type === 'date') {
1114
- if (elem.value?.length > 0 && elem.value?.length < elem.lengthForType) {
1526
+ } else if (elem.type === 'date' && elem.value?.length > 0) {
1527
+
1528
+ // Guard Clause: if the value is too short
1529
+ if (elem.value.length < elem.lengthForType) {
1530
+
1115
1531
  elem.validity = 'tooShort';
1116
1532
  elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1117
- } else if (elem.value?.length === elem.lengthForType && elem.util.toNorthAmericanFormat(elem.value, elem.format)) {
1118
- const formattedValue = elem.util.toNorthAmericanFormat(elem.value, elem.format);
1119
- const valueDate = new Date(formattedValue.dateForComparison);
1533
+ return;
1534
+ }
1535
+
1536
+ // Guard Clause: If the value is too long for the type
1537
+ if (elem.value?.length > elem.lengthForType) {
1120
1538
 
1121
- // validate max
1122
- if (elem.max?.length === elem.lengthForType) {
1123
- const maxDate = new Date(elem.util.toNorthAmericanFormat(elem.max, elem.format).dateForComparison);
1539
+ elem.validity = 'tooLong';
1540
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || '';
1541
+ return;
1542
+ }
1543
+
1544
+ // Validate that the date passed was the correct format
1545
+ if (!dateAndFormatMatch(elem.value, elem.format)) {
1546
+ elem.validity = 'patternMismatch';
1547
+ elem.errorMessage = elem.setCustomValidityForType || elem.setCustomValidity || 'Invalid Date Format Entered';
1548
+ return;
1549
+ }
1550
+
1551
+ // Validate that the date passed was a valid date
1552
+ if (!validDateStr(elem.value, elem.format)) {
1553
+ elem.validity = 'invalidDate';
1554
+ elem.errorMessage = elem.setCustomValidityInvalidDate || elem.setCustomValidity || 'Invalid Date Entered';
1555
+ return;
1556
+ }
1124
1557
 
1125
- if (valueDate > maxDate) {
1126
- elem.validity = 'rangeOverflow';
1127
- elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
1128
- }
1558
+ // Perform the rest of the validation
1559
+ const formattedValue = toNorthAmericanFormat(elem.value, elem.format);
1560
+ const valueDate = new Date(formattedValue);
1561
+
1562
+ // // Validate max date
1563
+ if (elem.max?.length === elem.lengthForType) {
1564
+
1565
+ const maxDate = new Date(toNorthAmericanFormat(elem.max, elem.format));
1566
+
1567
+ if (valueDate > maxDate) {
1568
+ elem.validity = 'rangeOverflow';
1569
+ elem.errorMessage = elem.setCustomValidityRangeOverflow || elem.setCustomValidity || '';
1570
+ return;
1129
1571
  }
1572
+ }
1130
1573
 
1131
- // validate min
1132
- if (elem.min?.length === elem.lengthForType) {
1133
- const minDate = new Date(elem.util.toNorthAmericanFormat(elem.min, elem.format).dateForComparison);
1574
+ // Validate min date
1575
+ if (elem.min?.length === elem.lengthForType) {
1576
+ const minDate = new Date(toNorthAmericanFormat(elem.min, elem.format));
1134
1577
 
1135
- if (valueDate < minDate) {
1136
- elem.validity = 'rangeUnderflow';
1137
- elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
1138
- }
1578
+ if (valueDate < minDate) {
1579
+ elem.validity = 'rangeUnderflow';
1580
+ elem.errorMessage = elem.setCustomValidityRangeUnderflow || elem.setCustomValidity || '';
1581
+ return;
1139
1582
  }
1140
1583
  }
1141
1584
  }
@@ -1254,7 +1697,7 @@ class AuroFormValidation {
1254
1697
  if (input.validationMessage.length > 0) {
1255
1698
  elem.errorMessage = input.validationMessage;
1256
1699
  }
1257
- } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
1700
+ } else if (this.inputElements?.length > 0 && elem.errorMessage === '') {
1258
1701
  const firstInput = this.inputElements[0];
1259
1702
 
1260
1703
  if (firstInput.validationMessage.length > 0) {
@@ -3482,7 +3925,7 @@ class AuroFloatingUI {
3482
3925
  /**
3483
3926
  * @private
3484
3927
  * getting called on 'blur' in trigger or `focusin` in document
3485
- *
3928
+ *
3486
3929
  * Hides the bib if focus moves outside of the trigger or bib, unless a 'noHideOnThisFocusLoss' flag is set.
3487
3930
  * This method checks if the currently active element is still within the trigger or bib.
3488
3931
  * If not, and if the bib isn't in fullscreen mode with focus lost, it hides the bib.
@@ -3598,7 +4041,7 @@ class AuroFloatingUI {
3598
4041
  // Close any other dropdown that is already open
3599
4042
  const existedVisibleFloatingUI = document.expandedAuroFormkitDropdown || document.expandedAuroFloater;
3600
4043
  if (existedVisibleFloatingUI && existedVisibleFloatingUI !== this &&
3601
- existedVisibleFloatingUI.isPopoverVisible &&
4044
+ existedVisibleFloatingUI.element.isPopoverVisible &&
3602
4045
  document.expandedAuroFloater.eventPrefix === this.eventPrefix) {
3603
4046
  document.expandedAuroFloater.hideBib();
3604
4047
  }
@@ -3774,7 +4217,7 @@ class AuroFloatingUI {
3774
4217
  this.id = window.crypto.randomUUID();
3775
4218
  this.element.setAttribute('id', this.id);
3776
4219
  }
3777
-
4220
+
3778
4221
  this.element.bib.setAttribute("id", `${this.id}-floater-bib`);
3779
4222
  }
3780
4223
 
@@ -3827,7 +4270,7 @@ class AuroFloatingUI {
3827
4270
  if (this.element.bib) {
3828
4271
  this.element.shadowRoot.append(this.element.bib);
3829
4272
  }
3830
-
4273
+
3831
4274
  // Remove event & keyboard listeners
3832
4275
  if (this.element?.trigger) {
3833
4276
  this.element.trigger.removeEventListener('keydown', this.handleEvent);
@@ -4556,6 +4999,7 @@ var helpTextVersion = '1.0.0';
4556
4999
  * @csspart helpText - The helpText content container.
4557
5000
  * @event auroDropdown-triggerClick - Notifies that the trigger has been clicked.
4558
5001
  * @event auroDropdown-toggled - Notifies that the visibility of the dropdown bib has changed.
5002
+ * @event auroDropdown-idAdded - Notifies consumers that the unique ID for the dropdown bib has been generated.
4559
5003
  */
4560
5004
  class AuroDropdown extends r {
4561
5005
  constructor() {
@@ -4601,7 +5045,9 @@ class AuroDropdown extends r {
4601
5045
  this.rounded = false;
4602
5046
  this.tabIndex = 0;
4603
5047
  this.noToggle = false;
5048
+ this.a11yAutocomplete = 'none';
4604
5049
  this.labeled = true;
5050
+ this.a11yRole = 'combobox';
4605
5051
  this.onDark = false;
4606
5052
 
4607
5053
  // floaterConfig
@@ -4737,6 +5183,16 @@ class AuroDropdown extends r {
4737
5183
  type: Number
4738
5184
  },
4739
5185
 
5186
+ /**
5187
+ * The unique ID for the dropdown bib element.
5188
+ * @private
5189
+ */
5190
+ dropdownId: {
5191
+ type: String,
5192
+ reflect: false,
5193
+ attribute: false
5194
+ },
5195
+
4740
5196
  /**
4741
5197
  * If declared in combination with `bordered` property or `helpText` slot content, will apply red color to both.
4742
5198
  */
@@ -4899,6 +5355,23 @@ class AuroDropdown extends r {
4899
5355
  */
4900
5356
  tabIndex: {
4901
5357
  type: Number
5358
+ },
5359
+
5360
+ /**
5361
+ * The value for the role attribute of the trigger element.
5362
+ */
5363
+ a11yRole: {
5364
+ type: String || undefined,
5365
+ attribute: false,
5366
+ reflect: false
5367
+ },
5368
+
5369
+ /**
5370
+ * The value for the aria-autocomplete attribute of the trigger element.
5371
+ */
5372
+ a11yAutocomplete: {
5373
+ type: String,
5374
+ attribute: false,
4902
5375
  }
4903
5376
  };
4904
5377
  }
@@ -4949,7 +5422,22 @@ class AuroDropdown extends r {
4949
5422
  }
4950
5423
 
4951
5424
  firstUpdated() {
5425
+
5426
+ // Configure the floater to, this will generate the ID for the bib
4952
5427
  this.floater.configure(this, 'auroDropdown');
5428
+
5429
+ /**
5430
+ * @description Let subscribers know that the dropdown ID ha been generated and added.
5431
+ * @event auroDropdown-idAdded
5432
+ * @type {Object<key: 'id', value: string>} - The ID of the dropdown bib element.
5433
+ */
5434
+ this.dispatchEvent(new CustomEvent('auroDropdown-idAdded', {detail: {id: this.floater.element.id}}));
5435
+
5436
+ // Set the bib ID locally if the user hasn't provided a focusable trigger
5437
+ if (!this.triggerContentFocusable) {
5438
+ this.dropdownId = this.floater.element.id;
5439
+ }
5440
+
4953
5441
  this.bibContent = this.floater.element.bib;
4954
5442
 
4955
5443
  // Add the tag name as an attribute if it is different than the component name
@@ -5101,6 +5589,30 @@ class AuroDropdown extends r {
5101
5589
  });
5102
5590
  }
5103
5591
 
5592
+ /*
5593
+ * Sets aria attributes for the trigger element if a custom one is passed in.
5594
+ * @private
5595
+ * @method setTriggerAriaAttributes
5596
+ * @param { HTMLElement } triggerElement - The custom trigger element.
5597
+ */
5598
+ clearTriggerA11yAttributes(triggerElement) {
5599
+
5600
+ if (!triggerElement || !triggerElement.removeAttribute) {
5601
+ return;
5602
+ }
5603
+
5604
+ // Reset appropriate attributes for a11y
5605
+ triggerElement.removeAttribute('aria-labelledby');
5606
+ if (triggerElement.getAttribute('id') === `${this.id}-trigger-element`) {
5607
+ triggerElement.removeAttribute('id');
5608
+ }
5609
+ triggerElement.removeAttribute('role');
5610
+ triggerElement.removeAttribute('aria-expanded');
5611
+
5612
+ triggerElement.removeAttribute('aria-controls');
5613
+ triggerElement.removeAttribute('aria-autocomplete');
5614
+ }
5615
+
5104
5616
  /**
5105
5617
  * Handles changes to the trigger content slot and updates related properties.
5106
5618
  *
@@ -5114,32 +5626,41 @@ class AuroDropdown extends r {
5114
5626
  * @returns {void}
5115
5627
  */
5116
5628
  handleTriggerContentSlotChange(event) {
5629
+
5117
5630
  this.floater.handleTriggerTabIndex();
5118
5631
 
5632
+ // Get the trigger
5633
+ const trigger = this.shadowRoot.querySelector('#trigger');
5634
+
5635
+ // Get the trigger slot
5119
5636
  const triggerSlot = this.shadowRoot.querySelector('.triggerContent slot');
5120
5637
 
5638
+ // If there's a trigger slot
5121
5639
  if (triggerSlot) {
5122
5640
 
5641
+ // Get the content nodes to see if there are any children
5123
5642
  const triggerContentNodes = triggerSlot.assignedNodes();
5124
5643
 
5644
+ // If there are children
5125
5645
  if (triggerContentNodes) {
5126
5646
 
5127
- triggerContentNodes.forEach((node) => {
5128
- if (!this.triggerContentFocusable) {
5129
- this.triggerContentFocusable = this.containsFocusableElement(node);
5130
- }
5131
- });
5132
- }
5133
- }
5647
+ // See if any of them are focusable elemeents
5648
+ this.triggerContentFocusable = triggerContentNodes.some((node) => this.containsFocusableElement(node));
5134
5649
 
5135
- const trigger = this.shadowRoot.querySelector('#trigger');
5650
+ // If any of them are focusable elements
5651
+ if (this.triggerContentFocusable) {
5136
5652
 
5137
- if (!this.triggerContentFocusable) {
5138
- trigger.setAttribute('tabindex', '0');
5139
- trigger.setAttribute('role', 'button');
5140
- } else {
5141
- trigger.removeAttribute('tabindex');
5142
- trigger.removeAttribute('role');
5653
+ // Assume the consumer will be providing their own a11y in whatever they passed in
5654
+ this.clearTriggerA11yAttributes(trigger);
5655
+
5656
+ // Remove the tabindex from the trigger so it doesn't interrupt focus flow
5657
+ trigger.removeAttribute('tabindex');
5658
+ } else {
5659
+
5660
+ // Add the tabindex to the trigger so that it's in the focus flow
5661
+ trigger.setAttribute('tabindex', '0');
5662
+ }
5663
+ }
5143
5664
  }
5144
5665
 
5145
5666
  if (event) {
@@ -5149,6 +5670,7 @@ class AuroDropdown extends r {
5149
5670
 
5150
5671
  if (this.triggerContentSlot) {
5151
5672
  this.setupTriggerFocusEventBinding();
5673
+
5152
5674
  this.hasTriggerContent = this.triggerContentSlot.some((slot) => {
5153
5675
  if (slot.textContent.trim()) {
5154
5676
  return true;
@@ -5216,10 +5738,13 @@ class AuroDropdown extends r {
5216
5738
  id="trigger"
5217
5739
  class="trigger"
5218
5740
  part="trigger"
5219
- aria-labelledby="triggerLabel"
5220
5741
  tabindex="${this.tabIndex}"
5221
5742
  ?showBorder="${this.showTriggerBorders}"
5222
- >
5743
+ role="${o(this.triggerContentFocusable ? undefined : this.a11yRole)}"
5744
+ aria-expanded="${o(this.triggerContentFocusable ? undefined : this.isPopoverVisible)}"
5745
+ aria-controls="${o(this.triggerContentFocusable ? undefined : this.dropdownId)}"
5746
+ aria-labelledby="${o(this.triggerContentFocusable ? undefined : 'triggerLabel')}"
5747
+ >
5223
5748
  <div class="triggerContentWrapper">
5224
5749
  <label class="label" id="triggerLabel" hasTrigger=${this.hasTriggerContent}>
5225
5750
  <slot name="label" @slotchange="${this.handleLabelSlotChange}"></slot>
@@ -5253,12 +5778,12 @@ class AuroDropdown extends r {
5253
5778
  <div id="bibSizer" part="size"></div>
5254
5779
  <${this.dropdownBibTag}
5255
5780
  id="bib"
5256
- role="tooltip"
5257
5781
  ?data-show="${this.isPopoverVisible}"
5258
5782
  ?isfullscreen="${this.isBibFullscreen}"
5259
5783
  ?common="${this.common}"
5260
5784
  ?rounded="${this.common || this.rounded}"
5261
- ?inset="${this.common || this.inset}">
5785
+ ?inset="${this.common || this.inset}"
5786
+ >
5262
5787
  </${this.dropdownBibTag}>
5263
5788
  </div>
5264
5789
  `;