@vectoriox/iox-ui 4.41.0 → 4.43.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.
@@ -4048,6 +4048,20 @@ class IoxFormComponent {
4048
4048
  this.name = '';
4049
4049
  this.renderState = FormRenderState.Idle;
4050
4050
  this.feedback = FeedbackTiming.Smart;
4051
+ /**
4052
+ * Move focus to the first field that failed, instead of leaving the visitor to hunt for red
4053
+ * text. On by default — the single most valuable thing a failed submit can do.
4054
+ */
4055
+ this.focusFirstProblem = true;
4056
+ /**
4057
+ * Speak the failure the moment it happens. Without this a screen-reader user only learns a
4058
+ * submit failed if they happen to tab back into the message — the error text is rendered, but
4059
+ * nothing directs them to it.
4060
+ */
4061
+ this.announceFailure = true;
4062
+ /** What the live region is currently saying. Empty between attempts so the same failure
4063
+ * announces again rather than being swallowed as an unchanged value. */
4064
+ this.announcement = '';
4051
4065
  /**
4052
4066
  * Emitted ONLY when client-side validation passes, carrying the collected values. The host
4053
4067
  * (builder canvas or engine runtime) owns the transport — posting, retries and the server's
@@ -4080,9 +4094,9 @@ class IoxFormComponent {
4080
4094
  event.preventDefault();
4081
4095
  this.runtime.configure(this.runtime.specNow(), this.feedback);
4082
4096
  if (!this.runtime.validateAll()) {
4083
- // Move the visitor TO the problem rather than leaving them to hunt for red text —
4084
- // the single most valuable thing a failed submit can do.
4085
- this.focusFirstInvalid();
4097
+ this.announce(this.runtime.state.errors);
4098
+ if (this.focusFirstProblem)
4099
+ this.focusFirstInvalid();
4086
4100
  this.validationFailed.emit(this.runtime.state.errors);
4087
4101
  return;
4088
4102
  }
@@ -4126,10 +4140,13 @@ class IoxFormComponent {
4126
4140
  if (response?.errors) {
4127
4141
  this.runtime.applyErrors(response.errors);
4128
4142
  this.renderState = FormRenderState.Idle;
4129
- this.focusFirstInvalid();
4143
+ this.announce(response.errors);
4144
+ if (this.focusFirstProblem)
4145
+ this.focusFirstInvalid();
4130
4146
  return;
4131
4147
  }
4132
4148
  this.renderState = FormRenderState.Error;
4149
+ this.announce(null);
4133
4150
  return;
4134
4151
  }
4135
4152
  this.renderState = FormRenderState.Success;
@@ -4152,6 +4169,22 @@ class IoxFormComponent {
4152
4169
  if (typeof form.reset === 'function')
4153
4170
  form.reset();
4154
4171
  }
4172
+ /**
4173
+ * Put the failure into the live region. Cleared first so two identical failures in a row are
4174
+ * two announcements, not one — a live region only speaks on CHANGE, and "same error again"
4175
+ * is exactly when a visitor most needs telling.
4176
+ */
4177
+ announce(errors) {
4178
+ if (!this.announceFailure)
4179
+ return;
4180
+ const count = errors ? Object.keys(errors).length : 0;
4181
+ const next = count
4182
+ ? `${count} ${count === 1 ? 'field needs' : 'fields need'} attention.`
4183
+ : 'That did not send.';
4184
+ this.announcement = '';
4185
+ // A microtask is enough for the empty value to be committed before the new one lands.
4186
+ Promise.resolve().then(() => { this.announcement = next; });
4187
+ }
4155
4188
  focusFirstInvalid() {
4156
4189
  const name = this.runtime.firstInvalidName();
4157
4190
  if (!name)
@@ -4168,12 +4201,20 @@ class IoxFormComponent {
4168
4201
  }
4169
4202
  }
4170
4203
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxFormComponent, deps: [{ token: FormRuntimeService }, { token: i0.ElementRef }, { token: IOX_FORM_TRANSPORT, optional: true }, { token: IOX_FORM_NAVIGATOR, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
4171
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: IoxFormComponent, isStandalone: false, selector: "form[iox-form]", inputs: { nodeId: "nodeId", formId: "formId", name: "name", renderState: "renderState", feedback: "feedback" }, outputs: { formSubmit: "formSubmit", validationFailed: "validationFailed" }, host: { listeners: { "submit": "onSubmit($event)" }, properties: { "class": "'iox-form iox-node-' + nodeId", "attr.novalidate": "''", "attr.data-iox-form": "formId || null", "attr.data-iox-state": "renderState", "attr.aria-busy": "renderState === 'submitting' ? 'true' : null" } }, providers: [FormRuntimeService], viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
4204
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.31", type: IoxFormComponent, isStandalone: false, selector: "form[iox-form]", inputs: { nodeId: "nodeId", formId: "formId", name: "name", renderState: "renderState", feedback: "feedback", focusFirstProblem: "focusFirstProblem", announceFailure: "announceFailure" }, outputs: { formSubmit: "formSubmit", validationFailed: "validationFailed" }, host: { listeners: { "submit": "onSubmit($event)" }, properties: { "class": "'iox-form iox-node-' + nodeId", "attr.novalidate": "''", "attr.data-iox-form": "formId || null", "attr.data-iox-state": "renderState", "attr.aria-busy": "renderState === 'submitting' ? 'true' : null" } }, providers: [FormRuntimeService], viewQueries: [{ propertyName: "childrenHost", first: true, predicate: ["childrenHost"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `
4172
4205
  <div class="iox-form__hp" aria-hidden="true">
4173
4206
  <input type="text" [attr.name]="honeypotField" tabindex="-1" autocomplete="off" />
4174
4207
  </div>
4208
+ <!-- Failure announcement (board 11). Visually hidden and always PRESENT — an aria-live
4209
+ region added to the DOM at the moment it gains text is not reliably announced; the
4210
+ region has to be there first and only its content change. -->
4211
+ <div *ngIf="announceFailure"
4212
+ class="iox-form__hp"
4213
+ role="status"
4214
+ aria-live="assertive"
4215
+ aria-atomic="true">{{ announcement }}</div>
4175
4216
  <ng-container #childrenHost></ng-container>
4176
- `, isInline: true, styles: [":host{box-sizing:border-box}.iox-form__hp{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;overflow:hidden}\n"] }); }
4217
+ `, isInline: true, styles: [":host{box-sizing:border-box}.iox-form__hp{position:absolute;inset-inline-start:-9999px;width:1px;height:1px;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
4177
4218
  }
4178
4219
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImport: i0, type: IoxFormComponent, decorators: [{
4179
4220
  type: Component,
@@ -4181,6 +4222,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
4181
4222
  <div class="iox-form__hp" aria-hidden="true">
4182
4223
  <input type="text" [attr.name]="honeypotField" tabindex="-1" autocomplete="off" />
4183
4224
  </div>
4225
+ <!-- Failure announcement (board 11). Visually hidden and always PRESENT — an aria-live
4226
+ region added to the DOM at the moment it gains text is not reliably announced; the
4227
+ region has to be there first and only its content change. -->
4228
+ <div *ngIf="announceFailure"
4229
+ class="iox-form__hp"
4230
+ role="status"
4231
+ aria-live="assertive"
4232
+ aria-atomic="true">{{ announcement }}</div>
4184
4233
  <ng-container #childrenHost></ng-container>
4185
4234
  `, host: {
4186
4235
  '[class]': "'iox-form iox-node-' + nodeId",
@@ -4210,6 +4259,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.31", ngImpo
4210
4259
  type: Input
4211
4260
  }], feedback: [{
4212
4261
  type: Input
4262
+ }], focusFirstProblem: [{
4263
+ type: Input
4264
+ }], announceFailure: [{
4265
+ type: Input
4213
4266
  }], formSubmit: [{
4214
4267
  type: Output
4215
4268
  }], validationFailed: [{
@@ -5191,6 +5244,40 @@ function buildPartSelector(nodeId, partClass, validationClass) {
5191
5244
  const base = validationClass ? `iox-node-${nodeId}.${validationClass}` : `iox-node-${nodeId}`;
5192
5245
  return partClass ? `${base} .${partClass}` : base;
5193
5246
  }
5247
+ /**
5248
+ * Design-time validation "states" (wrong/accepted) — not real CSS pseudo-classes. `IoxFieldComponent`
5249
+ * already carries `.iox-invalid`/`.iox-valid` host classes it sets from `forceInvalid`/`showValid`
5250
+ * (decision D5), so authoring "Wrong"/"Accepted" styles compiles to a CLASS-compound selector via
5251
+ * `buildPartSelector`, never a `:pseudo-class` (`:wrong` isn't real CSS). Lives here (not
5252
+ * `@vectoriox/iox-builder`'s `ElementState`/`VALIDATION_STATE_CLASS`, which re-export these same
5253
+ * values) because the SSR engine needs the mapping too and does not depend on iox-builder.
5254
+ */
5255
+ const VALIDATION_STATES = ['wrong', 'accepted'];
5256
+ const VALIDATION_STATE_CLASS = {
5257
+ wrong: 'iox-invalid',
5258
+ accepted: 'iox-valid',
5259
+ };
5260
+ /**
5261
+ * type -> part key -> the stable class that part's own template already renders. The single
5262
+ * source of truth for which classes a part maps to, so the builder (render.directive.ts) and the
5263
+ * SSR engine (layout-renderer.service.ts) resolve a part's selector identically without either
5264
+ * duplicating the string literals. `iox-builder`'s `FieldComponentConfig.parts` (the picker's
5265
+ * display labels) is built FROM this map, not the other way round.
5266
+ */
5267
+ const PART_CLASS_NAMES = {
5268
+ Field: {
5269
+ label: 'iox-field__label',
5270
+ control: 'iox-field__control',
5271
+ help: 'iox-field__help',
5272
+ error: 'iox-field__error',
5273
+ required: 'iox-field__required',
5274
+ },
5275
+ };
5276
+ /** The class a declared part compiles against, e.g. ('Field', 'error') -> 'iox-field__error'.
5277
+ * Undefined when the type declares no such part (or no parts at all). */
5278
+ function partClassName(type, partKey) {
5279
+ return PART_CLASS_NAMES[type]?.[partKey];
5280
+ }
5194
5281
 
5195
5282
  /**
5196
5283
  * page-style — the ONE compiler that turns a page's style settings into CSS.
@@ -7029,5 +7116,5 @@ function effectiveTransitionId(anim) {
7029
7116
  * Generated bundle index. Do not edit.
7030
7117
  */
7031
7118
 
7032
- export { AOSService, AnalyticsService, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CMSClientInfraModule, CONTEXT_ACTION_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ConsentService, ContainerComponent, ContentService, ContextStore, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DeclarationContext, ENVIRONMENT, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, PrivacyModalComponent, PrivacyModalService, PrivacyModelEvent, RESERVED_ALIAS_PREFIX, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_RESPONSIVE_TRAITS, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, WebSettingsService, activeActions, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, composeVirtualTraits, computeRelative, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, defaultMessageResolver, deriveSeoFromContent, describeSeoFields, effectiveTransitionId, excludeSelf, feelFor, getByPath, getSeoValue, initialSlotValues, isActionEnabled, isContextAction, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, nearestCommonAncestorId, nodeCssId, nodeUsesContext, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, parentBreakpoint, parseUrlList, planDataSource, planResponsiveRules, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveValueChain, rewriteViewportUnits, safeSetInput, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, stripSuppressedProps, styleKeyToKebab, variableAliases, variableItemsFrom, withContextSlots, wrapInBreakpointQuery, wrapInMediaQuery };
7119
+ export { AOSService, AnalyticsService, BREAKPOINTS, Breakpoint, BreakpointQueryKind, BuilderButtonComponent, BuilderDividerComponent, BuilderHeadingComponent, BuilderIconComponent, BuilderImageComponent, BuilderLinkComponent, BuilderSpacerComponent, ButtonBlockComponent, CMSClientInfraModule, CONTEXT_ACTION_TYPES, CardComponent, ClientGalleryComponent, ClientListComponent, ClientListItemComponent, ClientRepeaterComponent, ClientSliderContainerComponent, ClientSliderSlideComponent, ComponentInstanceRegistryService, ConsentService, ContainerComponent, ContentService, ContextStore, DEFAULT_GALLERY_SCHEDULER, DEFAULT_PAGE_TRANSITION, DEFAULT_PAGE_TRANSITION_DURATION, DEFAULT_PAGE_TRANSITION_EASING, DEFAULT_PAGE_TRANSITION_PERSPECTIVE, DEFAULT_PHOSPHOR_WEIGHT, DEFAULT_WIDTH_BY_TYPE, DeclarationContext, ENVIRONMENT, FormRenderState, FormRuntimeService, GALLERY_EFFECTS, GALLERY_EFFECT_DESCRIPTORS, GalleryLoop, IOX_BUILDER_EVENTS, IOX_FORM_MESSAGES, IOX_FORM_NAVIGATOR, IOX_FORM_TRANSPORT, IS_PREVIEW, IoxAnimateContainer, IoxAosDirective, IoxAosModule, IoxBuilderComponentsModule, IoxComponentRegistryService, IoxFieldComponent, IoxFormComponent, IoxFormErrorComponent, IoxFormSuccessComponent, IoxPageComponent, IoxPageModule, IoxSubmitButtonComponent, IoxUiModule, LinkedContainerComponent, PAGE_TRANSITION_CATEGORIES, PAGE_TRANSITION_PRESETS, PART_CLASS_NAMES, PIPE_DESCRIPTORS, PIPE_REGISTRY, PageScrollProvider, PrivacyModalComponent, PrivacyModalService, PrivacyModelEvent, RESERVED_ALIAS_PREFIX, SUPPRESSED_STYLE_PROPS_BY_TYPE, SWIPER_RESPONSIVE_TRAITS, SectionComponent, TRANSITION_FEELS, TextBlockComponent, VALIDATION_STATES, VALIDATION_STATE_CLASS, VIRTUAL_TRAIT_GROUPS, VIRTUAL_TRAIT_KEYS, ViewPositionDirective, ViewPositionModule, WebSettingsService, activeActions, applyBindingPipes, applyFeel, breakpointDef, breakpointForWidth, breakpointQueryFor, buildDataSourceFilter, buildPartSelector, buildRelatedFilter, buildSwiperBreakpoints, buildTargetedStateSelector, buildTransitionTimeline, cascadeChain, clearInputOverride, clearOverride, collectContextSlotKeys, collectReferencedAliases, compileDeclarations, compilePageStyle, composeVirtualTraits, computeRelative, contextActionEffect, contextSlotKeys, countOverrides, dataSourcePlanToRequest, declaredInputNames, defaultMessageResolver, deriveSeoFromContent, describeSeoFields, effectiveTransitionId, excludeSelf, feelFor, getByPath, getSeoValue, initialSlotValues, isActionEnabled, isContextAction, isInputOverriddenAt, isOverriddenAt, isReservedAlias, isValidSlotKey, legacyEnterToTransition, matchRouteParams, mediaQueryFor, mergeSwiperBreakpoints, nearestCommonAncestorId, nodeCssId, nodeUsesContext, normalizeCollectionResult, normalizeGalleryImage, normalizeIconClass, parentBreakpoint, parseUrlList, partClassName, planDataSource, planResponsiveRules, referencedDataSources, referencedDataSourcesDeep, renderDefaultDeclarations, resetSeoField, resolveBoundValue, resolveContextValue, resolveDerivedInto, resolveGalleryEffect, resolveGalleryUrls, resolveInputChain, resolveInputs, resolvePageTransition, resolvePath, resolveRelativeInto, resolveRepeaterItems, resolveSeo, resolveSiblings, resolveSingleItemId, resolveStateStyles, resolveStyleProps, resolveValueChain, rewriteViewportUnits, safeSetInput, setInputOverride, setOverride, setSeoAuto, setSeoBinding, setSeoOverride, setSeoValue, stateNamesIn, stripSuppressedProps, styleKeyToKebab, variableAliases, variableItemsFrom, withContextSlots, wrapInBreakpointQuery, wrapInMediaQuery };
7033
7120
  //# sourceMappingURL=vectoriox-iox-ui.mjs.map