@egose/shadcn-theme-ng-tw 0.6.0 → 0.6.2

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 (50) hide show
  1. package/README.md +119 -1
  2. package/autocomplete/fesm2022/autocomplete.mjs +14 -4
  3. package/autocomplete/types/autocomplete.d.ts +4 -1
  4. package/checkbox/README.md +6 -2
  5. package/checkbox/fesm2022/checkbox.mjs +15 -9
  6. package/checkbox/types/checkbox.d.ts +6 -4
  7. package/combobox/fesm2022/combobox.mjs +22 -6
  8. package/combobox/types/combobox.d.ts +8 -2
  9. package/data-table/README.md +11 -1
  10. package/data-table/fesm2022/data-table.mjs +76 -26
  11. package/data-table/types/data-table.d.ts +25 -6
  12. package/date-picker/README.md +41 -9
  13. package/date-picker/fesm2022/date-picker.mjs +265 -59
  14. package/date-picker/types/date-picker.d.ts +58 -27
  15. package/form-autocomplete/README.md +4 -2
  16. package/form-autocomplete/fesm2022/form-autocomplete.mjs +33 -8
  17. package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
  18. package/form-combobox/README.md +4 -2
  19. package/form-combobox/fesm2022/form-combobox.mjs +65 -14
  20. package/form-combobox/types/form-combobox.d.ts +10 -3
  21. package/form-input-otp/README.md +7 -5
  22. package/form-input-otp/fesm2022/form-input-otp.mjs +28 -14
  23. package/form-input-otp/types/form-input-otp.d.ts +9 -3
  24. package/form-searchable-multiselect/README.md +15 -1
  25. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +23 -3
  26. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
  27. package/form-slider/README.md +3 -1
  28. package/form-slider/fesm2022/form-slider.mjs +21 -6
  29. package/form-slider/types/form-slider.d.ts +9 -3
  30. package/form-toggle/README.md +22 -18
  31. package/form-toggle/fesm2022/form-toggle.mjs +25 -8
  32. package/form-toggle/types/form-toggle.d.ts +6 -2
  33. package/input-otp/README.md +33 -9
  34. package/input-otp/fesm2022/input-otp.mjs +127 -11
  35. package/input-otp/types/input-otp.d.ts +24 -4
  36. package/package.json +1 -1
  37. package/pagination/README.md +21 -12
  38. package/pagination/fesm2022/pagination.mjs +83 -75
  39. package/pagination/types/pagination.d.ts +21 -8
  40. package/phone-input/README.md +16 -0
  41. package/phone-input/fesm2022/phone-input.mjs +41 -3
  42. package/phone-input/types/phone-input.d.ts +5 -2
  43. package/searchable-multiselect/README.md +62 -40
  44. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +97 -36
  45. package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
  46. package/slider/fesm2022/slider.mjs +15 -5
  47. package/slider/types/slider.d.ts +6 -2
  48. package/stepper/README.md +39 -19
  49. package/stepper/fesm2022/stepper.mjs +71 -17
  50. package/stepper/types/stepper.d.ts +12 -1
package/stepper/README.md CHANGED
@@ -123,20 +123,38 @@ Lazy body (attached only when the step is first selected):
123
123
 
124
124
  ### `HlmStepper` — selector `hlm-stepper` (component, extends `CdkStepper`)
125
125
 
126
- | Input | Type | Default | Description |
127
- | ----------------------- | ------------------------------- | ----------------------- | ---------------------------------------------------------------- |
128
- | `orientation` | forwarded to `CdkStepper` | — | `'horizontal' \| 'vertical'`. |
129
- | `linear` | forwarded to `CdkStepper` | — | Current step must be valid (via `stepControl`) before advancing. |
130
- | `selectedIndex` | forwarded to `CdkStepper` | — | Controlled selected-step index. |
131
- | `labelPosition` | `'end' \| 'bottom'` | `'end'` | Horizontal layout: label beside vs. below the indicator. |
132
- | `headerPosition` | `'top' \| 'bottom'` | `'top'` | Horizontal layout: headers above vs. below the content panel. |
133
- | `indicatorMode` | `'number' \| 'state' \| 'icon'` | from config (`'state'`) | Indicator rendering (see below). |
134
- | `stepperAriaLabel` | `string \| null` | `'Progress'` | `aria-label` for the tablist (ignored when labelledby is set). |
135
- | `stepperAriaLabelledby` | `string \| null` | `null` | `aria-labelledby` for the tablist. |
136
- | `animationsEnabled` | `boolean` | from config (`true`) | Toggle step transitions per instance. |
137
- | `animationDuration` | `number` (ms) | from config (`300`) | Transition duration per instance. |
138
-
139
- `next()` is overridden to `markAllAsTouched()` + `updateValueAndValidity()` on the current `stepControl` first, so linear steppers surface validation errors before denying the transition.
126
+ | Input | Type | Default | Description |
127
+ | ----------------------- | ------------------------------- | ----------------------- | ------------------------------------------------------------------- |
128
+ | `orientation` | forwarded to `CdkStepper` | — | `'horizontal' \| 'vertical'`. |
129
+ | `linear` | forwarded to `CdkStepper` | — | CDK checks preceding steps, honoring optional/completion overrides. |
130
+ | `selectedIndex` | forwarded to `CdkStepper` | — | Controlled selected-step index. |
131
+ | `labelPosition` | `'end' \| 'bottom'` | `'end'` | Horizontal layout: label beside vs. below the indicator. |
132
+ | `headerPosition` | `'top' \| 'bottom'` | `'top'` | Horizontal layout: headers above vs. below the content panel. |
133
+ | `indicatorMode` | `'number' \| 'state' \| 'icon'` | from config (`'state'`) | Indicator rendering (see below). |
134
+ | `stepperAriaLabel` | `string \| null` | `'Progress'` | `aria-label` for the tablist (ignored when labelledby is set). |
135
+ | `stepperAriaLabelledby` | `string \| null` | `null` | `aria-labelledby` for the tablist. |
136
+ | `animationsEnabled` | `boolean` | from config (`true`) | Toggle step transitions per instance. |
137
+ | `animationDuration` | `number` (ms) | from config (`300`) | Transition duration per instance. |
138
+
139
+ `next()` and `button[hlmStepperNext]` call `markAllAsTouched()` + `updateValueAndValidity()` on the current classic reactive-forms `stepControl` before delegating navigation to CDK. This preserves touch/validation feedback even when navigation is denied, and also runs for optional, completed, and non-linear steps. Signal-form Fields are passed through to CDK without this classic-control touch pass.
140
+
141
+ CDK determines eligibility for Next, header selection, and direct index changes:
142
+
143
+ - In linear mode, all preceding required steps must satisfy CDK's interaction and validation rules. Invalid or pending controls block advancement unless their step is optional or explicitly `[completed]="true"`.
144
+ - Optional steps can be skipped while invalid or pending. Explicit completion also permits advancement in those states.
145
+ - Without a `stepControl`, CDK uses `completed` (by default, whether the step was interacted with). Explicit `[completed]="false"` blocks a required control-less step. With a control, CDK uses its validation state; `[completed]="false"` does not veto an otherwise valid, interacted control.
146
+ - Non-linear navigation is not blocked by invalid/pending forms. CDK's backward-navigation `editable` rule still applies.
147
+
148
+ Header/direct selection retains CDK's interaction behavior and does not run Next's form-touch/revalidation pass. Next revalidation can restart a validator attached to the current control, so pending eligibility is evaluated after that pass.
149
+
150
+ Header `aria-disabled`, disabled styling and active state preview the same transition that CDK will attempt, in both orientations:
151
+
152
+ - An unvisited destination is enabled when its predecessors permit entry; the destination's own incomplete/invalid control does not block entry. A completed destination cannot bypass a blocked predecessor.
153
+ - The preview accounts for CDK marking the **current** step interacted on an attempt to leave it. It does not mark steps interacted while rendering. Other unvisited required predecessors still block skipping ahead unless optional or explicitly completed.
154
+ - Backward selection requires the destination's `editable` input and, in linear mode, eligible predecessors **before that destination**. The current step's invalid/pending state does not itself prevent going back.
155
+ - The selected header stays enabled as a no-op, including vertically; selecting it neither collapses the panel nor marks the step interacted.
156
+
157
+ Actual clicks and Enter/Space selection remain delegated to CDK. Disabled headers remain discoverable by keyboard focus; attempted keyboard selection cannot change the selected index, but retains CDK's current-step interaction notification. Header attempts do not touch or revalidate form controls. Classic-form status notifications (including async validator completion) refresh the affordance; Signal Field validity is read reactively.
140
158
 
141
159
  ### `HlmStep` — selector `hlm-step` (component, extends `CdkStep`)
142
160
 
@@ -145,7 +163,9 @@ Lazy body (attached only when the step is first selected):
145
163
  | `label` | forwarded to `CdkStep` | — | Step title (string, or rich template via `hlmStepLabel`). |
146
164
  | `icon` | `string \| null` | `null` | Icon name rendered when `indicatorMode="icon"`. |
147
165
  | `stepControl` | forwarded to `CdkStep` | — | Form group driving `linear` validation. |
148
- | `optional` | forwarded to `CdkStep` | — | Shows an "Optional" caption in the header. |
166
+ | `optional` | forwarded to `CdkStep` | — | Permits skipping in linear mode and shows an "Optional" caption. |
167
+ | `completed` | forwarded to `CdkStep` | — | Explicit completion override; see CDK navigation rules above. |
168
+ | `editable` | forwarded to `CdkStep` | `true` | Allows returning to this step; enforced in linear and non-linear modes. |
149
169
  | `errorMessage` | forwarded to `CdkStep` | — | Header error caption when the step state is `error`. |
150
170
  | `hasError` | forwarded to `CdkStep` | — | Forces the error state (pair with `STEPPER_GLOBAL_OPTIONS` `showError`). |
151
171
  | `tooltip` | `string \| TemplateRef<unknown> \| null` | `null` (falls back to the full string label) | Styled header tooltip override; rich templates allowed. |
@@ -263,7 +283,7 @@ export class DemoResponsive {
263
283
 
264
284
  ### 4. Linear stepper with validation
265
285
 
266
- With `linear`, the current step must be valid before advancing — `HlmStepper.next()` marks the step form touched first:
286
+ With `linear`, required steps normally need valid, non-pending controls before advancing. `HlmStepper.next()` marks the current step form touched first; optional and explicitly completed steps follow the exceptions above:
267
287
 
268
288
  ```ts
269
289
  import { Component, inject } from '@angular/core';
@@ -361,9 +381,9 @@ Steps using `<ng-template hlmStepLabel>` have no string to fall back to — pass
361
381
 
362
382
  - Horizontal headers get `role="tab"` semantics inside a `role="tablist"` (labelled `Progress` by default — override with `stepperAriaLabel` / `stepperAriaLabelledby`); panels get `role="tabpanel"` wired via `aria-labelledby` / `aria-controls`.
363
383
  - Vertical headers use `role="button"` with `aria-expanded` / `aria-current="step"`.
364
- - Keyboard: `HlmStepper` inherits CDK arrow-key navigation (`_onKeydown`). Headers are focusable via roving `tabIndex`.
365
- - Disabled steps (`linear` + not navigable) get `aria-disabled` and `data-disabled` styling — explain in the label why a step is unavailable.
366
- - In `linear` mode, failed validation marks fields touched so screen readers announce the errors.
384
+ - Keyboard: CDK manages roving `tabIndex`, horizontal Left/Right (direction-aware), vertical Up/Down, Home/End, wrapping, and Enter/Space activation. Focus movement does not select a step. Disabled headers can receive keyboard focus so their labels and unavailable state remain discoverable.
385
+ - Headers that CDK cannot select get matching `aria-disabled="true"` and `data-disabled="true"` pointer/opacity styling, including non-editable backward destinations in non-linear mode. Explain in the label why a step is unavailable. Eligible unvisited destinations and the selected header are not disabled.
386
+ - Next marks classic form controls touched so applications can display validation feedback. Associate error text with the relevant controls; touching alone does not guarantee a screen-reader announcement.
367
387
 
368
388
  ## Theming / CSS variables
369
389
 
@@ -1,14 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, TemplateRef, Directive, ViewContainerRef, DestroyRef, input, booleanAttribute, contentChild, effect, untracked, ChangeDetectionStrategy, Component, InjectionToken, numberAttribute, computed, signal, NgModule } from '@angular/core';
2
+ import { inject, TemplateRef, Directive, ViewContainerRef, DestroyRef, input, booleanAttribute, contentChild, effect, untracked, ChangeDetectionStrategy, Component, InjectionToken, numberAttribute, computed, QueryList, signal, ViewChildren, NgModule } from '@angular/core';
3
3
  import { TemplatePortal, CdkPortalOutlet } from '@angular/cdk/portal';
4
4
  import * as i1 from '@angular/cdk/stepper';
5
5
  import { CdkStepLabel, CdkStep, CdkStepHeader, CdkStepper, CdkStepperModule, CdkStepperNext, CdkStepperPrevious } from '@angular/cdk/stepper';
6
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
7
  import { NgTemplateOutlet } from '@angular/common';
7
8
  import { NgIcon, provideIcons } from '@ng-icons/core';
8
9
  import { lucideCircleAlert, lucideCheck } from '@ng-icons/lucide';
9
10
  import { buttonVariants } from '@egose/shadcn-theme-ng-tw/button';
10
11
  import { classes, hlm } from '@egose/shadcn-theme-ng-tw/utils';
11
- import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
12
12
  import { HlmTooltip } from '@egose/shadcn-theme-ng-tw/tooltip';
13
13
  import { cva } from 'class-variance-authority';
14
14
  import { tap } from 'rxjs/operators';
@@ -42,6 +42,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
42
42
  class HlmStep extends CdkStep {
43
43
  _viewContainerRef = inject(ViewContainerRef);
44
44
  _destroyRef = inject(DestroyRef);
45
+ _hasCompletionOverride = false;
46
+ _controlSubscription;
47
+ ngOnChanges() {
48
+ super.ngOnChanges();
49
+ this._controlSubscription?.unsubscribe();
50
+ const control = this.stepControl;
51
+ if (control && typeof control !== 'function') {
52
+ // Classic form status getters are not reactive template dependencies, unlike Signal Fields.
53
+ this._controlSubscription = control.statusChanges
54
+ .pipe(takeUntilDestroyed(this._destroyRef))
55
+ .subscribe(() => this._stepper._stateChanged());
56
+ }
57
+ }
58
+ get completed() {
59
+ return super.completed;
60
+ }
61
+ set completed(value) {
62
+ // Track input presence through the supported accessor, not CDK's internal override field.
63
+ this._hasCompletionOverride = value != null;
64
+ super.completed = value;
65
+ }
66
+ /** @internal Read-only preview of CDK's predecessor check after leaving the current step. */
67
+ _canExit(leaving) {
68
+ if (this.optional || this.completed) {
69
+ return true;
70
+ }
71
+ const interacted = this.interacted || leaving;
72
+ const control = this.stepControl;
73
+ if (!control) {
74
+ return interacted && !this._hasCompletionOverride;
75
+ }
76
+ const invalid = typeof control === 'function' ? control().invalid() : control.invalid;
77
+ const pending = typeof control === 'function' ? control().pending() : control.pending;
78
+ return interacted && !invalid && !pending;
79
+ }
45
80
  icon = input(null, /* @ts-ignore */
46
81
  ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
47
82
  /** Styled header tooltip override. Falls back to the full string label when unset. */
@@ -81,7 +116,7 @@ class HlmStep extends CdkStep {
81
116
  provide: CdkStep,
82
117
  useExisting: HlmStep,
83
118
  },
84
- ], queries: [{ propertyName: "stepLabelContent", first: true, predicate: HlmStepLabel, descendants: true, isSignal: true }, { propertyName: "_lazyContent", first: true, predicate: HlmStepContent, descendants: true, isSignal: true }], exportAs: ["hlmStep"], usesInheritance: true, ngImport: i0, template: `
119
+ ], queries: [{ propertyName: "stepLabelContent", first: true, predicate: HlmStepLabel, descendants: true, isSignal: true }, { propertyName: "_lazyContent", first: true, predicate: HlmStepContent, descendants: true, isSignal: true }], exportAs: ["hlmStep"], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `
85
120
  <ng-template>
86
121
  <ng-content></ng-content>
87
122
  <ng-template [cdkPortalOutlet]="portal"></ng-template>
@@ -292,6 +327,8 @@ const stepTransition = cva('', {
292
327
  });
293
328
  class HlmStepper extends CdkStepper {
294
329
  _config = injectHlmStepperConfig();
330
+ // Headers are rendered in this component's view, not in CDK's projected content query.
331
+ _stepHeader = new QueryList();
295
332
  labelPosition = input('end', /* @ts-ignore */
296
333
  ...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
297
334
  headerPosition = input('top', /* @ts-ignore */
@@ -319,23 +356,35 @@ class HlmStepper extends CdkStepper {
319
356
  }), takeUntilDestroyed())
320
357
  .subscribe();
321
358
  }
359
+ /** Touch and revalidate the current reactive form, then let CDK decide whether navigation is allowed. */
322
360
  next() {
323
361
  const stepControl = this.selected?.stepControl;
324
362
  if (stepControl && typeof stepControl !== 'function') {
325
- // Classic reactive-forms control: surface validation errors before a denied linear transition.
326
- // Signal `Field` controls expose validity through invocation and need no touch pass.
363
+ // Preserve validation feedback for classic controls. Signal Fields are handled by CDK.
327
364
  stepControl.markAllAsTouched();
328
365
  stepControl.updateValueAndValidity();
329
366
  }
330
- const invalid = !!stepControl && (typeof stepControl === 'function' ? stepControl().invalid() : stepControl.invalid);
331
- if (this.linear && invalid) {
332
- return;
333
- }
367
+ // CDK applies optional/completion overrides and checks all preceding controls, including pending ones.
334
368
  super.next();
335
369
  }
336
370
  stepIcon(step) {
337
371
  return step instanceof HlmStep ? step.icon() : null;
338
372
  }
373
+ /** Preview selection without touching forms or marking steps interacted; activation stays with CDK. */
374
+ _isHeaderEnabled(step) {
375
+ const index = step.index();
376
+ if (index === this.selectedIndex) {
377
+ return true;
378
+ }
379
+ if (index < this.selectedIndex && !step.editable) {
380
+ return false;
381
+ }
382
+ return (!this.linear ||
383
+ this.steps
384
+ .toArray()
385
+ .slice(0, index)
386
+ .every((previous) => previous instanceof HlmStep && previous._canExit(previous === this.selected)));
387
+ }
339
388
  _headerTooltip(step) {
340
389
  const override = step instanceof HlmStep ? step.tooltip() : null;
341
390
  if (override === null || override === undefined) {
@@ -375,7 +424,7 @@ class HlmStepper extends CdkStepper {
375
424
  return hlm('tw:ms-4 tw:overflow-hidden tw:ps-6', showConnector && 'tw:border-s', this.animationsEnabled() && this.selectedIndex === index && stepTransition({ motion: 'enter-bottom' }));
376
425
  }
377
426
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmStepper, deps: [], target: i0.ɵɵFactoryTarget.Component });
378
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmStepper, isStandalone: true, selector: "hlm-stepper", inputs: { labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, headerPosition: { classPropertyName: "headerPosition", publicName: "headerPosition", isSignal: true, isRequired: false, transformFunction: null }, indicatorMode: { classPropertyName: "indicatorMode", publicName: "indicatorMode", isSignal: true, isRequired: false, transformFunction: null }, stepperAriaLabel: { classPropertyName: "stepperAriaLabel", publicName: "stepperAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, stepperAriaLabelledby: { classPropertyName: "stepperAriaLabelledby", publicName: "stepperAriaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, animationsEnabled: { classPropertyName: "animationsEnabled", publicName: "animationsEnabled", isSignal: true, isRequired: false, transformFunction: null }, animationDuration: { classPropertyName: "animationDuration", publicName: "animationDuration", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: CdkStepper, useExisting: HlmStepper }], usesInheritance: true, ngImport: i0, template: `
427
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmStepper, isStandalone: true, selector: "hlm-stepper", inputs: { labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, headerPosition: { classPropertyName: "headerPosition", publicName: "headerPosition", isSignal: true, isRequired: false, transformFunction: null }, indicatorMode: { classPropertyName: "indicatorMode", publicName: "indicatorMode", isSignal: true, isRequired: false, transformFunction: null }, stepperAriaLabel: { classPropertyName: "stepperAriaLabel", publicName: "stepperAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, stepperAriaLabelledby: { classPropertyName: "stepperAriaLabelledby", publicName: "stepperAriaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, animationsEnabled: { classPropertyName: "animationsEnabled", publicName: "animationsEnabled", isSignal: true, isRequired: false, transformFunction: null }, animationDuration: { classPropertyName: "animationDuration", publicName: "animationDuration", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: CdkStepper, useExisting: HlmStepper }], viewQueries: [{ propertyName: "_stepHeader", predicate: CdkStepHeader, descendants: true }], usesInheritance: true, ngImport: i0, template: `
379
428
  @switch (orientation) {
380
429
  @case ('horizontal') {
381
430
  <div class="tw:flex tw:w-full tw:flex-col tw:gap-6">
@@ -421,6 +470,7 @@ class HlmStepper extends CdkStepper {
421
470
  }
422
471
 
423
472
  <ng-template #stepHeaderTemplate let-step="step">
473
+ @let enabled = _isHeaderEnabled(step);
424
474
  <hlm-step-header
425
475
  cdkStepHeader
426
476
  class="tw:min-w-0"
@@ -437,16 +487,16 @@ class HlmStepper extends CdkStepper {
437
487
  [attr.aria-controls]="_getStepContentId(step.index())"
438
488
  [attr.aria-label]="step.ariaLabel || null"
439
489
  [attr.aria-labelledby]="!step.ariaLabel && step.ariaLabelledby ? step.ariaLabelledby : null"
440
- [attr.aria-disabled]="!step.isNavigable() || (orientation === 'vertical' && step.isSelected()) ? 'true' : null"
490
+ [attr.aria-disabled]="enabled ? null : 'true'"
441
491
  [index]="step.index()"
442
492
  [state]="step.indicatorType()"
443
493
  [label]="step.stepLabelContent() || step.label"
444
494
  [selected]="step.isSelected()"
445
495
  [reached]="step.index() < selectedIndex"
446
- [active]="step.isNavigable()"
496
+ [active]="enabled"
447
497
  [optional]="step.optional"
448
498
  [errorMessage]="step.errorMessage"
449
- [disabled]="linear && !step.isNavigable()"
499
+ [disabled]="!enabled"
450
500
  [labelPosition]="orientation === 'horizontal' ? labelPosition() : 'end'"
451
501
  [indicatorMode]="indicatorMode()"
452
502
  [icon]="stepIcon(step)"
@@ -554,6 +604,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
554
604
  }
555
605
 
556
606
  <ng-template #stepHeaderTemplate let-step="step">
607
+ @let enabled = _isHeaderEnabled(step);
557
608
  <hlm-step-header
558
609
  cdkStepHeader
559
610
  class="tw:min-w-0"
@@ -570,16 +621,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
570
621
  [attr.aria-controls]="_getStepContentId(step.index())"
571
622
  [attr.aria-label]="step.ariaLabel || null"
572
623
  [attr.aria-labelledby]="!step.ariaLabel && step.ariaLabelledby ? step.ariaLabelledby : null"
573
- [attr.aria-disabled]="!step.isNavigable() || (orientation === 'vertical' && step.isSelected()) ? 'true' : null"
624
+ [attr.aria-disabled]="enabled ? null : 'true'"
574
625
  [index]="step.index()"
575
626
  [state]="step.indicatorType()"
576
627
  [label]="step.stepLabelContent() || step.label"
577
628
  [selected]="step.isSelected()"
578
629
  [reached]="step.index() < selectedIndex"
579
- [active]="step.isNavigable()"
630
+ [active]="enabled"
580
631
  [optional]="step.optional"
581
632
  [errorMessage]="step.errorMessage"
582
- [disabled]="linear && !step.isNavigable()"
633
+ [disabled]="!enabled"
583
634
  [labelPosition]="orientation === 'horizontal' ? labelPosition() : 'end'"
584
635
  [indicatorMode]="indicatorMode()"
585
636
  [icon]="stepIcon(step)"
@@ -634,7 +685,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
634
685
  </ng-template>
635
686
  `,
636
687
  }]
637
- }], ctorParameters: () => [], propDecorators: { labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], headerPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerPosition", required: false }] }], indicatorMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "indicatorMode", required: false }] }], stepperAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepperAriaLabel", required: false }] }], stepperAriaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepperAriaLabelledby", required: false }] }], animationsEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "animationsEnabled", required: false }] }], animationDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "animationDuration", required: false }] }] } });
688
+ }], ctorParameters: () => [], propDecorators: { _stepHeader: [{
689
+ type: ViewChildren,
690
+ args: [CdkStepHeader]
691
+ }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], headerPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerPosition", required: false }] }], indicatorMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "indicatorMode", required: false }] }], stepperAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepperAriaLabel", required: false }] }], stepperAriaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepperAriaLabelledby", required: false }] }], animationsEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "animationsEnabled", required: false }] }], animationDuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "animationDuration", required: false }] }] } });
638
692
 
639
693
  /** Button that moves to the next step in a stepper workflow. */
640
694
  class HlmStepperNext extends CdkStepperNext {
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { ValueProvider, TemplateRef } from '@angular/core';
2
+ import { ValueProvider, TemplateRef, QueryList } from '@angular/core';
3
3
  import { TemplatePortal } from '@angular/cdk/portal';
4
4
  import { CdkStepLabel, CdkStep, CdkStepHeader, CdkStepper, CdkStepperNext, CdkStepperPrevious } from '@angular/cdk/stepper';
5
5
 
@@ -23,6 +23,13 @@ declare function injectHlmStepperConfig(): HlmStepperConfig;
23
23
  declare class HlmStep extends CdkStep {
24
24
  private readonly _viewContainerRef;
25
25
  private readonly _destroyRef;
26
+ private _hasCompletionOverride;
27
+ private _controlSubscription?;
28
+ ngOnChanges(): void;
29
+ get completed(): boolean;
30
+ set completed(value: boolean);
31
+ /** @internal Read-only preview of CDK's predecessor check after leaving the current step. */
32
+ _canExit(leaving: boolean): boolean;
26
33
  readonly icon: _angular_core.InputSignal<string | null>;
27
34
  /** Styled header tooltip override. Falls back to the full string label when unset. */
28
35
  readonly tooltip: _angular_core.InputSignal<string | TemplateRef<unknown> | null>;
@@ -77,6 +84,7 @@ declare class HlmStepHeader extends CdkStepHeader {
77
84
 
78
85
  declare class HlmStepper extends CdkStepper {
79
86
  private readonly _config;
87
+ _stepHeader: QueryList<CdkStepHeader>;
80
88
  readonly labelPosition: _angular_core.InputSignal<HlmStepperLabelPosition>;
81
89
  readonly headerPosition: _angular_core.InputSignal<HlmStepperHeaderPosition>;
82
90
  readonly indicatorMode: _angular_core.InputSignal<HlmStepperIndicatorMode>;
@@ -87,8 +95,11 @@ declare class HlmStepper extends CdkStepper {
87
95
  protected readonly _animationDirection: _angular_core.WritableSignal<"forward" | "backward">;
88
96
  protected readonly _hasSelectionChanged: _angular_core.WritableSignal<boolean>;
89
97
  constructor();
98
+ /** Touch and revalidate the current reactive form, then let CDK decide whether navigation is allowed. */
90
99
  next(): void;
91
100
  protected stepIcon(step: CdkStep): string | null;
101
+ /** Preview selection without touching forms or marking steps interacted; activation stays with CDK. */
102
+ protected _isHeaderEnabled(step: HlmStep): boolean;
92
103
  protected _headerTooltip(step: CdkStep): string | TemplateRef<void> | null;
93
104
  protected _headerTooltipPosition(step: CdkStep): HlmStepTooltipPosition;
94
105
  protected _headerTooltipDisabled(step: CdkStep): boolean;