@aurodesignsystem-dev/auro-formkit 0.0.0-pr1619.0 → 0.0.0-pr1621.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 (48) hide show
  1. package/components/checkbox/demo/customize.min.js +1 -1
  2. package/components/checkbox/demo/getting-started.min.js +1 -1
  3. package/components/checkbox/demo/index.min.js +1 -1
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/customize.min.js +3 -3
  7. package/components/combobox/demo/getting-started.min.js +3 -3
  8. package/components/combobox/demo/index.min.js +3 -3
  9. package/components/combobox/dist/index.js +3 -3
  10. package/components/combobox/dist/registered.js +3 -3
  11. package/components/counter/demo/api.md +4 -3
  12. package/components/counter/demo/customize.min.js +85 -50
  13. package/components/counter/demo/getting-started.min.js +85 -50
  14. package/components/counter/demo/index.min.js +85 -50
  15. package/components/counter/dist/auro-counter-group.d.ts +11 -8
  16. package/components/counter/dist/index.js +85 -50
  17. package/components/counter/dist/registered.js +85 -50
  18. package/components/datepicker/demo/customize.min.js +3 -3
  19. package/components/datepicker/demo/getting-started.min.js +3 -3
  20. package/components/datepicker/demo/index.min.js +3 -3
  21. package/components/datepicker/dist/index.js +3 -3
  22. package/components/datepicker/dist/registered.js +3 -3
  23. package/components/dropdown/demo/customize.min.js +1 -1
  24. package/components/dropdown/demo/getting-started.min.js +2 -2
  25. package/components/dropdown/demo/index.min.js +1 -1
  26. package/components/dropdown/dist/index.js +1 -1
  27. package/components/dropdown/dist/registered.js +1 -1
  28. package/components/form/demo/customize.min.js +96 -61
  29. package/components/form/demo/getting-started.min.js +96 -61
  30. package/components/form/demo/index.min.js +96 -61
  31. package/components/form/demo/registerDemoDeps.min.js +96 -61
  32. package/components/input/demo/customize.min.js +1 -1
  33. package/components/input/demo/getting-started.min.js +1 -1
  34. package/components/input/demo/index.min.js +1 -1
  35. package/components/input/dist/index.js +1 -1
  36. package/components/input/dist/registered.js +1 -1
  37. package/components/radio/demo/customize.min.js +1 -1
  38. package/components/radio/demo/getting-started.min.js +1 -1
  39. package/components/radio/demo/index.min.js +1 -1
  40. package/components/radio/dist/index.js +1 -1
  41. package/components/radio/dist/registered.js +1 -1
  42. package/components/select/demo/customize.min.js +2 -2
  43. package/components/select/demo/getting-started.min.js +2 -2
  44. package/components/select/demo/index.min.js +2 -2
  45. package/components/select/dist/index.js +2 -2
  46. package/components/select/dist/registered.js +2 -2
  47. package/custom-elements.json +1549 -1551
  48. package/package.json +1 -1
@@ -1127,7 +1127,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$2 {
1127
1127
  }
1128
1128
  };
1129
1129
 
1130
- var formkitVersion$1 = '202609231954';
1130
+ var formkitVersion$1 = '202609232032';
1131
1131
 
1132
1132
  // Copyright (c) 2026 Alaska Airlines. All rights reserved. Licensed under the Apache-2.0 license
1133
1133
  // See LICENSE in the project root for license information.
@@ -5487,7 +5487,7 @@ class AuroHelpText extends i$2 {
5487
5487
  }
5488
5488
  }
5489
5489
 
5490
- var formkitVersion = '202609231954';
5490
+ var formkitVersion = '202609232032';
5491
5491
 
5492
5492
  /**
5493
5493
  * AuroElement is the shared base class for layout-aware Auro form components.
@@ -7883,6 +7883,7 @@ const counterGroupKeyboardStrategy = {
7883
7883
  * @csspart dropdown - The dropdown element rendered when `isDropdown` is true.
7884
7884
  * @csspart helpText - The help text and error message container.
7885
7885
  *
7886
+ * @event auroFormElement-validated - Notifies that the `validity` and `errorMessage` values have changed.
7886
7887
  * @event input - Notifies that the counter group's value has changed.
7887
7888
  */
7888
7889
  class AuroCounterGroup extends AuroElement {
@@ -8233,21 +8234,6 @@ class AuroCounterGroup extends AuroElement {
8233
8234
  this.updateValidity();
8234
8235
  }
8235
8236
 
8236
- /**
8237
- * Renders help text error messages.
8238
- * @param {Array<string>} messages - The error messages to render.
8239
- * @returns {TemplateResult[]} - The rendered error messages rendered in a TemplateResult.
8240
- * @private
8241
- */
8242
- renderHelpTextErrors(messages) {
8243
-
8244
- // Return empty template if no messages are provided
8245
- if (!messages || messages.length === 0) return u$5``;
8246
-
8247
- // Return messages as a TemplateResult
8248
- return messages.map(message => u$5`<p>${message}</p>`);
8249
- }
8250
-
8251
8237
  /**
8252
8238
  * Gets and returns an array of counters in an invalid state.
8253
8239
  * @returns {Array<HTMLElement>} - Returns an array of invalid counters.
@@ -8271,55 +8257,70 @@ class AuroCounterGroup extends AuroElement {
8271
8257
  };
8272
8258
 
8273
8259
  /**
8274
- * Updates the validity of the counter group based on the validity of its counters.
8275
- * This method checks all counters within the group, determines if any are invalid, and updates the group's validity state and error message accordingly.
8276
- * If any counter is invalid, it generates a combined error message from all invalid counters.
8260
+ * Synchronously recomputes this.validity/this.errorMessage from the current
8261
+ * validity of the child counters. Extracted from updateValidity() so that
8262
+ * validate() can re-apply this aggregation immediately after the shared
8263
+ * validation engine runs, instead of waiting on a deferred timer.
8277
8264
  * @returns {void}
8278
8265
  * @private
8279
8266
  */
8280
- updateValidity () {
8267
+ computeValidityFromCounters() {
8281
8268
 
8282
8269
  // We don't need to do anything if there are no counters
8283
8270
  if (!this.counters) return;
8284
8271
 
8285
- // Wait for initial validation to complete before updating validity and error message
8286
- // This is necessary because we need the initial validation to know when to reset the validity and error message
8287
- setTimeout(() => {
8272
+ // Get any invalid counters
8273
+ const invalidCounters = this.getInvalidCounters(this.counters);
8274
+
8275
+ // Determine if we are in an invalid state based on the presence of invalid counters
8276
+ const isInvalid = invalidCounters.length > 0;
8288
8277
 
8289
- // Get any invalid counters
8290
- const invalidCounters = this.getInvalidCounters(this.counters);
8278
+ // If we are in an invalid state
8279
+ if (isInvalid) {
8291
8280
 
8292
- // Determine if we are in an invalid state based on the presence of invalid counters
8293
- const isInvalid = invalidCounters.length > 0;
8281
+ // Generate the error messages as plain text, joined with sentence-ending
8282
+ // punctuation so multiple messages don't run together into one clause.
8283
+ const errorMessages = this.getErrorMessages(invalidCounters);
8284
+ const joinedMessage = errorMessages.join('. ');
8294
8285
 
8295
- // If we are in an invalid state
8296
- if (isInvalid) {
8286
+ // Set the validity and error message
8287
+ // This needs to allow for the initial validation to come through
8288
+ this.validity =
8289
+ invalidCounters[0].validity || // The first invalid counter's validity
8290
+ this.validity || // incoming validity from validation
8291
+ undefined; // fallback
8297
8292
 
8298
- // Generate the error messages
8299
- const errorMessages = this.getErrorMessages(invalidCounters);
8293
+ // Always reflect the current invalid counters' messages, even when that
8294
+ // means clearing to undefined -- falling back to the previous
8295
+ // this.errorMessage here would let a stale message from a prior,
8296
+ // different invalid cause survive a transition into a cause with no
8297
+ // message of its own (e.g. customError -> rangeOverflow).
8298
+ this.errorMessage = joinedMessage || undefined;
8299
+ }
8300
8300
 
8301
- const errorMessage = isInvalid ? this.renderHelpTextErrors(errorMessages) : this.errorMessage;
8301
+ if (!isInvalid && this.validity !== 'valid') {
8302
8302
 
8303
- // Set the validity and error message
8304
- // This needs to allow for the initial validation to come through
8305
- this.validity =
8306
- invalidCounters[0].validity || // The first invalid counter's validity
8307
- this.validity || // incoming validity from validation
8308
- undefined; // fallback
8303
+ // If there are no invalid counters, reset validity and error message
8304
+ this.validity = 'valid';
8305
+ this.errorMessage = undefined;
8306
+ }
8307
+ }
8309
8308
 
8310
- this.errorMessage =
8311
- errorMessage || // our message
8312
- this.errorMessage || // incoming message from validation
8313
- undefined; // fallback
8314
- }
8309
+ /**
8310
+ * Updates the validity of the counter group based on the validity of its counters.
8311
+ * This method checks all counters within the group, determines if any are invalid, and updates the group's validity state and error message accordingly.
8312
+ * If any counter is invalid, it generates a combined error message from all invalid counters.
8313
+ * @returns {void}
8314
+ * @private
8315
+ */
8316
+ updateValidity () {
8315
8317
 
8316
- if (!isInvalid && this.validity !== 'valid') {
8318
+ // We don't need to do anything if there are no counters
8319
+ if (!this.counters) return;
8317
8320
 
8318
- // If there are no invalid counters, reset validity and error message
8319
- this.validity = 'valid';
8320
- this.errorMessage = undefined;
8321
- }
8322
- });
8321
+ // Wait for initial validation to complete before updating validity and error message
8322
+ // This is necessary because we need the initial validation to know when to reset the validity and error message
8323
+ setTimeout(() => this.computeValidityFromCounters());
8323
8324
  }
8324
8325
 
8325
8326
  /**
@@ -8487,6 +8488,40 @@ class AuroCounterGroup extends AuroElement {
8487
8488
  */
8488
8489
  validate(force = false) {
8489
8490
  this.validation.validate(this, force);
8491
+
8492
+ // AuroFormValidation doesn't know how to derive validity from our object-valued
8493
+ // `value`, so it always resolves an otherwise-error-free group to 'valid' -- even
8494
+ // while a child counter is still invalid. Re-derive from the children's current
8495
+ // state immediately afterward so that invalid state survives forced validation,
8496
+ // and re-dispatch the validated event when the correction changes the outcome so
8497
+ // listeners like auro-form's submit path see the corrected result.
8498
+ //
8499
+ // This intentionally also runs for unforced calls (every child value change),
8500
+ // even though updateValidity()'s deferred, listener-driven aggregation computes
8501
+ // the same result a tick later: that deferred path only updates this.validity/
8502
+ // this.errorMessage, it never re-dispatches the event, so auro-form's formState
8503
+ // (and therefore the submit button's disabled state) would otherwise only ever
8504
+ // learn about a child-derived invalid state at the next forced validation --
8505
+ // not at initial load or on ordinary interaction. See docs/post-mortem/1642340.md,
8506
+ // "Iterations That Didn't Work", for the attempt to scope this to `force` and
8507
+ // why it broke that behavior.
8508
+ if (this.counters && !this.hasAttribute('error')) {
8509
+ const previousValidity = this.validity;
8510
+ const previousMessage = this.errorMessage;
8511
+
8512
+ this.computeValidityFromCounters();
8513
+
8514
+ if (this.validity !== previousValidity || this.errorMessage !== previousMessage) {
8515
+ this.dispatchEvent(new CustomEvent('auroFormElement-validated', {
8516
+ bubbles: true,
8517
+ composed: true,
8518
+ detail: {
8519
+ validity: this.validity,
8520
+ message: this.errorMessage
8521
+ }
8522
+ }));
8523
+ }
8524
+ }
8490
8525
  }
8491
8526
 
8492
8527
  /**
@@ -13,6 +13,7 @@
13
13
  * @csspart dropdown - The dropdown element rendered when `isDropdown` is true.
14
14
  * @csspart helpText - The help text and error message container.
15
15
  *
16
+ * @event auroFormElement-validated - Notifies that the `validity` and `errorMessage` values have changed.
16
17
  * @event input - Notifies that the counter group's value has changed.
17
18
  */
18
19
  export class AuroCounterGroup extends AuroElement {
@@ -277,13 +278,6 @@ export class AuroCounterGroup extends AuroElement {
277
278
  * @private
278
279
  */
279
280
  private configureCounters;
280
- /**
281
- * Renders help text error messages.
282
- * @param {Array<string>} messages - The error messages to render.
283
- * @returns {TemplateResult[]} - The rendered error messages rendered in a TemplateResult.
284
- * @private
285
- */
286
- private renderHelpTextErrors;
287
281
  /**
288
282
  * Gets and returns an array of counters in an invalid state.
289
283
  * @returns {Array<HTMLElement>} - Returns an array of invalid counters.
@@ -298,7 +292,16 @@ export class AuroCounterGroup extends AuroElement {
298
292
  * @private
299
293
  */
300
294
  private getErrorMessages;
301
- errorMessage: any;
295
+ /**
296
+ * Synchronously recomputes this.validity/this.errorMessage from the current
297
+ * validity of the child counters. Extracted from updateValidity() so that
298
+ * validate() can re-apply this aggregation immediately after the shared
299
+ * validation engine runs, instead of waiting on a deferred timer.
300
+ * @returns {void}
301
+ * @private
302
+ */
303
+ private computeValidityFromCounters;
304
+ errorMessage: string | undefined;
302
305
  /**
303
306
  * Hides the dropdown bib if its open.
304
307
  * @returns {void}
@@ -1077,7 +1077,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
1077
1077
  }
1078
1078
  };
1079
1079
 
1080
- var formkitVersion$1 = '202609231954';
1080
+ var formkitVersion$1 = '202609232032';
1081
1081
 
1082
1082
  // Copyright (c) 2026 Alaska Airlines. All rights reserved. Licensed under the Apache-2.0 license
1083
1083
  // See LICENSE in the project root for license information.
@@ -5419,7 +5419,7 @@ class AuroHelpText extends LitElement {
5419
5419
  }
5420
5420
  }
5421
5421
 
5422
- var formkitVersion = '202609231954';
5422
+ var formkitVersion = '202609232032';
5423
5423
 
5424
5424
  /**
5425
5425
  * AuroElement is the shared base class for layout-aware Auro form components.
@@ -7815,6 +7815,7 @@ const counterGroupKeyboardStrategy = {
7815
7815
  * @csspart dropdown - The dropdown element rendered when `isDropdown` is true.
7816
7816
  * @csspart helpText - The help text and error message container.
7817
7817
  *
7818
+ * @event auroFormElement-validated - Notifies that the `validity` and `errorMessage` values have changed.
7818
7819
  * @event input - Notifies that the counter group's value has changed.
7819
7820
  */
7820
7821
  class AuroCounterGroup extends AuroElement {
@@ -8165,21 +8166,6 @@ class AuroCounterGroup extends AuroElement {
8165
8166
  this.updateValidity();
8166
8167
  }
8167
8168
 
8168
- /**
8169
- * Renders help text error messages.
8170
- * @param {Array<string>} messages - The error messages to render.
8171
- * @returns {TemplateResult[]} - The rendered error messages rendered in a TemplateResult.
8172
- * @private
8173
- */
8174
- renderHelpTextErrors(messages) {
8175
-
8176
- // Return empty template if no messages are provided
8177
- if (!messages || messages.length === 0) return html``;
8178
-
8179
- // Return messages as a TemplateResult
8180
- return messages.map(message => html`<p>${message}</p>`);
8181
- }
8182
-
8183
8169
  /**
8184
8170
  * Gets and returns an array of counters in an invalid state.
8185
8171
  * @returns {Array<HTMLElement>} - Returns an array of invalid counters.
@@ -8203,55 +8189,70 @@ class AuroCounterGroup extends AuroElement {
8203
8189
  };
8204
8190
 
8205
8191
  /**
8206
- * Updates the validity of the counter group based on the validity of its counters.
8207
- * This method checks all counters within the group, determines if any are invalid, and updates the group's validity state and error message accordingly.
8208
- * If any counter is invalid, it generates a combined error message from all invalid counters.
8192
+ * Synchronously recomputes this.validity/this.errorMessage from the current
8193
+ * validity of the child counters. Extracted from updateValidity() so that
8194
+ * validate() can re-apply this aggregation immediately after the shared
8195
+ * validation engine runs, instead of waiting on a deferred timer.
8209
8196
  * @returns {void}
8210
8197
  * @private
8211
8198
  */
8212
- updateValidity () {
8199
+ computeValidityFromCounters() {
8213
8200
 
8214
8201
  // We don't need to do anything if there are no counters
8215
8202
  if (!this.counters) return;
8216
8203
 
8217
- // Wait for initial validation to complete before updating validity and error message
8218
- // This is necessary because we need the initial validation to know when to reset the validity and error message
8219
- setTimeout(() => {
8204
+ // Get any invalid counters
8205
+ const invalidCounters = this.getInvalidCounters(this.counters);
8206
+
8207
+ // Determine if we are in an invalid state based on the presence of invalid counters
8208
+ const isInvalid = invalidCounters.length > 0;
8220
8209
 
8221
- // Get any invalid counters
8222
- const invalidCounters = this.getInvalidCounters(this.counters);
8210
+ // If we are in an invalid state
8211
+ if (isInvalid) {
8223
8212
 
8224
- // Determine if we are in an invalid state based on the presence of invalid counters
8225
- const isInvalid = invalidCounters.length > 0;
8213
+ // Generate the error messages as plain text, joined with sentence-ending
8214
+ // punctuation so multiple messages don't run together into one clause.
8215
+ const errorMessages = this.getErrorMessages(invalidCounters);
8216
+ const joinedMessage = errorMessages.join('. ');
8226
8217
 
8227
- // If we are in an invalid state
8228
- if (isInvalid) {
8218
+ // Set the validity and error message
8219
+ // This needs to allow for the initial validation to come through
8220
+ this.validity =
8221
+ invalidCounters[0].validity || // The first invalid counter's validity
8222
+ this.validity || // incoming validity from validation
8223
+ undefined; // fallback
8229
8224
 
8230
- // Generate the error messages
8231
- const errorMessages = this.getErrorMessages(invalidCounters);
8225
+ // Always reflect the current invalid counters' messages, even when that
8226
+ // means clearing to undefined -- falling back to the previous
8227
+ // this.errorMessage here would let a stale message from a prior,
8228
+ // different invalid cause survive a transition into a cause with no
8229
+ // message of its own (e.g. customError -> rangeOverflow).
8230
+ this.errorMessage = joinedMessage || undefined;
8231
+ }
8232
8232
 
8233
- const errorMessage = isInvalid ? this.renderHelpTextErrors(errorMessages) : this.errorMessage;
8233
+ if (!isInvalid && this.validity !== 'valid') {
8234
8234
 
8235
- // Set the validity and error message
8236
- // This needs to allow for the initial validation to come through
8237
- this.validity =
8238
- invalidCounters[0].validity || // The first invalid counter's validity
8239
- this.validity || // incoming validity from validation
8240
- undefined; // fallback
8235
+ // If there are no invalid counters, reset validity and error message
8236
+ this.validity = 'valid';
8237
+ this.errorMessage = undefined;
8238
+ }
8239
+ }
8241
8240
 
8242
- this.errorMessage =
8243
- errorMessage || // our message
8244
- this.errorMessage || // incoming message from validation
8245
- undefined; // fallback
8246
- }
8241
+ /**
8242
+ * Updates the validity of the counter group based on the validity of its counters.
8243
+ * This method checks all counters within the group, determines if any are invalid, and updates the group's validity state and error message accordingly.
8244
+ * If any counter is invalid, it generates a combined error message from all invalid counters.
8245
+ * @returns {void}
8246
+ * @private
8247
+ */
8248
+ updateValidity () {
8247
8249
 
8248
- if (!isInvalid && this.validity !== 'valid') {
8250
+ // We don't need to do anything if there are no counters
8251
+ if (!this.counters) return;
8249
8252
 
8250
- // If there are no invalid counters, reset validity and error message
8251
- this.validity = 'valid';
8252
- this.errorMessage = undefined;
8253
- }
8254
- });
8253
+ // Wait for initial validation to complete before updating validity and error message
8254
+ // This is necessary because we need the initial validation to know when to reset the validity and error message
8255
+ setTimeout(() => this.computeValidityFromCounters());
8255
8256
  }
8256
8257
 
8257
8258
  /**
@@ -8419,6 +8420,40 @@ class AuroCounterGroup extends AuroElement {
8419
8420
  */
8420
8421
  validate(force = false) {
8421
8422
  this.validation.validate(this, force);
8423
+
8424
+ // AuroFormValidation doesn't know how to derive validity from our object-valued
8425
+ // `value`, so it always resolves an otherwise-error-free group to 'valid' -- even
8426
+ // while a child counter is still invalid. Re-derive from the children's current
8427
+ // state immediately afterward so that invalid state survives forced validation,
8428
+ // and re-dispatch the validated event when the correction changes the outcome so
8429
+ // listeners like auro-form's submit path see the corrected result.
8430
+ //
8431
+ // This intentionally also runs for unforced calls (every child value change),
8432
+ // even though updateValidity()'s deferred, listener-driven aggregation computes
8433
+ // the same result a tick later: that deferred path only updates this.validity/
8434
+ // this.errorMessage, it never re-dispatches the event, so auro-form's formState
8435
+ // (and therefore the submit button's disabled state) would otherwise only ever
8436
+ // learn about a child-derived invalid state at the next forced validation --
8437
+ // not at initial load or on ordinary interaction. See docs/post-mortem/1642340.md,
8438
+ // "Iterations That Didn't Work", for the attempt to scope this to `force` and
8439
+ // why it broke that behavior.
8440
+ if (this.counters && !this.hasAttribute('error')) {
8441
+ const previousValidity = this.validity;
8442
+ const previousMessage = this.errorMessage;
8443
+
8444
+ this.computeValidityFromCounters();
8445
+
8446
+ if (this.validity !== previousValidity || this.errorMessage !== previousMessage) {
8447
+ this.dispatchEvent(new CustomEvent('auroFormElement-validated', {
8448
+ bubbles: true,
8449
+ composed: true,
8450
+ detail: {
8451
+ validity: this.validity,
8452
+ message: this.errorMessage
8453
+ }
8454
+ }));
8455
+ }
8456
+ }
8422
8457
  }
8423
8458
 
8424
8459
  /**
@@ -1077,7 +1077,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
1077
1077
  }
1078
1078
  };
1079
1079
 
1080
- var formkitVersion$1 = '202609231954';
1080
+ var formkitVersion$1 = '202609232032';
1081
1081
 
1082
1082
  // Copyright (c) 2026 Alaska Airlines. All rights reserved. Licensed under the Apache-2.0 license
1083
1083
  // See LICENSE in the project root for license information.
@@ -5419,7 +5419,7 @@ class AuroHelpText extends LitElement {
5419
5419
  }
5420
5420
  }
5421
5421
 
5422
- var formkitVersion = '202609231954';
5422
+ var formkitVersion = '202609232032';
5423
5423
 
5424
5424
  /**
5425
5425
  * AuroElement is the shared base class for layout-aware Auro form components.
@@ -7815,6 +7815,7 @@ const counterGroupKeyboardStrategy = {
7815
7815
  * @csspart dropdown - The dropdown element rendered when `isDropdown` is true.
7816
7816
  * @csspart helpText - The help text and error message container.
7817
7817
  *
7818
+ * @event auroFormElement-validated - Notifies that the `validity` and `errorMessage` values have changed.
7818
7819
  * @event input - Notifies that the counter group's value has changed.
7819
7820
  */
7820
7821
  class AuroCounterGroup extends AuroElement {
@@ -8165,21 +8166,6 @@ class AuroCounterGroup extends AuroElement {
8165
8166
  this.updateValidity();
8166
8167
  }
8167
8168
 
8168
- /**
8169
- * Renders help text error messages.
8170
- * @param {Array<string>} messages - The error messages to render.
8171
- * @returns {TemplateResult[]} - The rendered error messages rendered in a TemplateResult.
8172
- * @private
8173
- */
8174
- renderHelpTextErrors(messages) {
8175
-
8176
- // Return empty template if no messages are provided
8177
- if (!messages || messages.length === 0) return html``;
8178
-
8179
- // Return messages as a TemplateResult
8180
- return messages.map(message => html`<p>${message}</p>`);
8181
- }
8182
-
8183
8169
  /**
8184
8170
  * Gets and returns an array of counters in an invalid state.
8185
8171
  * @returns {Array<HTMLElement>} - Returns an array of invalid counters.
@@ -8203,55 +8189,70 @@ class AuroCounterGroup extends AuroElement {
8203
8189
  };
8204
8190
 
8205
8191
  /**
8206
- * Updates the validity of the counter group based on the validity of its counters.
8207
- * This method checks all counters within the group, determines if any are invalid, and updates the group's validity state and error message accordingly.
8208
- * If any counter is invalid, it generates a combined error message from all invalid counters.
8192
+ * Synchronously recomputes this.validity/this.errorMessage from the current
8193
+ * validity of the child counters. Extracted from updateValidity() so that
8194
+ * validate() can re-apply this aggregation immediately after the shared
8195
+ * validation engine runs, instead of waiting on a deferred timer.
8209
8196
  * @returns {void}
8210
8197
  * @private
8211
8198
  */
8212
- updateValidity () {
8199
+ computeValidityFromCounters() {
8213
8200
 
8214
8201
  // We don't need to do anything if there are no counters
8215
8202
  if (!this.counters) return;
8216
8203
 
8217
- // Wait for initial validation to complete before updating validity and error message
8218
- // This is necessary because we need the initial validation to know when to reset the validity and error message
8219
- setTimeout(() => {
8204
+ // Get any invalid counters
8205
+ const invalidCounters = this.getInvalidCounters(this.counters);
8206
+
8207
+ // Determine if we are in an invalid state based on the presence of invalid counters
8208
+ const isInvalid = invalidCounters.length > 0;
8220
8209
 
8221
- // Get any invalid counters
8222
- const invalidCounters = this.getInvalidCounters(this.counters);
8210
+ // If we are in an invalid state
8211
+ if (isInvalid) {
8223
8212
 
8224
- // Determine if we are in an invalid state based on the presence of invalid counters
8225
- const isInvalid = invalidCounters.length > 0;
8213
+ // Generate the error messages as plain text, joined with sentence-ending
8214
+ // punctuation so multiple messages don't run together into one clause.
8215
+ const errorMessages = this.getErrorMessages(invalidCounters);
8216
+ const joinedMessage = errorMessages.join('. ');
8226
8217
 
8227
- // If we are in an invalid state
8228
- if (isInvalid) {
8218
+ // Set the validity and error message
8219
+ // This needs to allow for the initial validation to come through
8220
+ this.validity =
8221
+ invalidCounters[0].validity || // The first invalid counter's validity
8222
+ this.validity || // incoming validity from validation
8223
+ undefined; // fallback
8229
8224
 
8230
- // Generate the error messages
8231
- const errorMessages = this.getErrorMessages(invalidCounters);
8225
+ // Always reflect the current invalid counters' messages, even when that
8226
+ // means clearing to undefined -- falling back to the previous
8227
+ // this.errorMessage here would let a stale message from a prior,
8228
+ // different invalid cause survive a transition into a cause with no
8229
+ // message of its own (e.g. customError -> rangeOverflow).
8230
+ this.errorMessage = joinedMessage || undefined;
8231
+ }
8232
8232
 
8233
- const errorMessage = isInvalid ? this.renderHelpTextErrors(errorMessages) : this.errorMessage;
8233
+ if (!isInvalid && this.validity !== 'valid') {
8234
8234
 
8235
- // Set the validity and error message
8236
- // This needs to allow for the initial validation to come through
8237
- this.validity =
8238
- invalidCounters[0].validity || // The first invalid counter's validity
8239
- this.validity || // incoming validity from validation
8240
- undefined; // fallback
8235
+ // If there are no invalid counters, reset validity and error message
8236
+ this.validity = 'valid';
8237
+ this.errorMessage = undefined;
8238
+ }
8239
+ }
8241
8240
 
8242
- this.errorMessage =
8243
- errorMessage || // our message
8244
- this.errorMessage || // incoming message from validation
8245
- undefined; // fallback
8246
- }
8241
+ /**
8242
+ * Updates the validity of the counter group based on the validity of its counters.
8243
+ * This method checks all counters within the group, determines if any are invalid, and updates the group's validity state and error message accordingly.
8244
+ * If any counter is invalid, it generates a combined error message from all invalid counters.
8245
+ * @returns {void}
8246
+ * @private
8247
+ */
8248
+ updateValidity () {
8247
8249
 
8248
- if (!isInvalid && this.validity !== 'valid') {
8250
+ // We don't need to do anything if there are no counters
8251
+ if (!this.counters) return;
8249
8252
 
8250
- // If there are no invalid counters, reset validity and error message
8251
- this.validity = 'valid';
8252
- this.errorMessage = undefined;
8253
- }
8254
- });
8253
+ // Wait for initial validation to complete before updating validity and error message
8254
+ // This is necessary because we need the initial validation to know when to reset the validity and error message
8255
+ setTimeout(() => this.computeValidityFromCounters());
8255
8256
  }
8256
8257
 
8257
8258
  /**
@@ -8419,6 +8420,40 @@ class AuroCounterGroup extends AuroElement {
8419
8420
  */
8420
8421
  validate(force = false) {
8421
8422
  this.validation.validate(this, force);
8423
+
8424
+ // AuroFormValidation doesn't know how to derive validity from our object-valued
8425
+ // `value`, so it always resolves an otherwise-error-free group to 'valid' -- even
8426
+ // while a child counter is still invalid. Re-derive from the children's current
8427
+ // state immediately afterward so that invalid state survives forced validation,
8428
+ // and re-dispatch the validated event when the correction changes the outcome so
8429
+ // listeners like auro-form's submit path see the corrected result.
8430
+ //
8431
+ // This intentionally also runs for unforced calls (every child value change),
8432
+ // even though updateValidity()'s deferred, listener-driven aggregation computes
8433
+ // the same result a tick later: that deferred path only updates this.validity/
8434
+ // this.errorMessage, it never re-dispatches the event, so auro-form's formState
8435
+ // (and therefore the submit button's disabled state) would otherwise only ever
8436
+ // learn about a child-derived invalid state at the next forced validation --
8437
+ // not at initial load or on ordinary interaction. See docs/post-mortem/1642340.md,
8438
+ // "Iterations That Didn't Work", for the attempt to scope this to `force` and
8439
+ // why it broke that behavior.
8440
+ if (this.counters && !this.hasAttribute('error')) {
8441
+ const previousValidity = this.validity;
8442
+ const previousMessage = this.errorMessage;
8443
+
8444
+ this.computeValidityFromCounters();
8445
+
8446
+ if (this.validity !== previousValidity || this.errorMessage !== previousMessage) {
8447
+ this.dispatchEvent(new CustomEvent('auroFormElement-validated', {
8448
+ bubbles: true,
8449
+ composed: true,
8450
+ detail: {
8451
+ validity: this.validity,
8452
+ message: this.errorMessage
8453
+ }
8454
+ }));
8455
+ }
8456
+ }
8422
8457
  }
8423
8458
 
8424
8459
  /**