@egose/shadcn-theme-ng-tw 0.5.1 → 0.5.3

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.
@@ -12,7 +12,7 @@ import { HlmBtn, provideBrnButtonConfig } from '@egose/shadcn-theme-ng-tw/button
12
12
  class HlmAlertDialogOverlay {
13
13
  _classSettable = injectCustomClassSettable({ optional: true, host: true });
14
14
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
15
- _computedClass = computed(() => hlm('tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:isolate tw:bg-black/10 tw:duration-100 tw:supports-backdrop-filter:backdrop-blur-xs', this.userClass()), /* @ts-ignore */
15
+ _computedClass = computed(() => hlm("tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:isolate tw:bg-black/10 tw:duration-100 tw:supports-backdrop-filter:backdrop-blur-xs", this.userClass()), /* @ts-ignore */
16
16
  ...(ngDevMode ? [{ debugName: "_computedClass" }] : /* istanbul ignore next */ []));
17
17
  constructor() {
18
18
  effect(() => {
@@ -106,7 +106,7 @@ class HlmAlertDialogContent {
106
106
  size = input('default', /* @ts-ignore */
107
107
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
108
108
  constructor() {
109
- classes(() => 'tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 tw:bg-popover tw:text-popover-foreground tw:ring-foreground/10 tw:gap-6 tw:rounded-xl tw:p-6 tw:ring-1 tw:duration-100 tw:data-[size=default]:max-w-xs tw:data-[size=sm]:max-w-xs tw:data-[size=default]:sm:max-w-lg tw:group/alert-dialog-content tw:grid tw:w-full tw:outline-none');
109
+ classes(() => "tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 tw:bg-popover tw:text-popover-foreground tw:ring-foreground/10 tw:gap-6 tw:rounded-xl tw:p-6 tw:ring-1 tw:duration-100 tw:data-[size=default]:max-w-xs tw:data-[size=sm]:max-w-xs tw:data-[size=default]:sm:max-w-lg tw:group/alert-dialog-content tw:grid tw:w-full tw:outline-none");
110
110
  }
111
111
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmAlertDialogContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
112
112
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.3", type: HlmAlertDialogContent, isStandalone: true, selector: "[hlmAlertDialogContent],hlm-alert-dialog-content", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "alert-dialog-content" }, properties: { "attr.data-state": "state()", "attr.data-size": "size()" } }, ngImport: i0 });
@@ -52,7 +52,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
52
52
 
53
53
  class HlmAutocompleteContent {
54
54
  constructor() {
55
- classes(() => 'tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:max-h-72 tw:min-w-36 tw:overflow-hidden tw:rounded-md tw:shadow-md tw:ring-1 tw:duration-100 tw:group/autocomplete-content tw:flex tw:w-(--brn-autocomplete-width) tw:flex-col tw:p-0');
55
+ classes(() => "tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:max-h-72 tw:min-w-36 tw:overflow-hidden tw:rounded-md tw:shadow-md tw:ring-1 tw:duration-100 tw:group/autocomplete-content tw:flex tw:w-(--brn-autocomplete-width) tw:flex-col tw:p-0");
56
56
  }
57
57
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmAutocompleteContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
58
58
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: HlmAutocompleteContent, isStandalone: true, selector: "[hlmAutocompleteContent],hlm-autocomplete-content", hostDirectives: [{ directive: i1.BrnAutocompleteContent }], ngImport: i0 });
@@ -159,7 +159,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
159
159
  class HlmComboboxContent {
160
160
  constructor() {
161
161
  classes(() => [
162
- 'tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:**:has-[[data-slot=input-group-control]:focus-visible]:border-input tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:**:data-[slot=input-group]:bg-input/30 tw:**:data-[slot=input-group]:border-input/30 tw:max-h-72 tw:min-w-36 tw:overflow-hidden tw:rounded-md tw:shadow-md tw:ring-1 tw:duration-100 tw:**:has-[[data-slot=input-group-control]:focus-visible]:ring-0 tw:**:data-[slot=input-group]:m-1 tw:**:data-[slot=input-group]:mb-0 tw:**:data-[slot=input-group]:h-8 tw:**:data-[slot=input-group]:shadow-none tw:group/combobox-content tw:relative tw:flex tw:w-(--brn-combobox-width) tw:flex-col tw:p-0',
162
+ "tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:**:has-[[data-slot=input-group-control]:focus-visible]:border-input tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:**:data-[slot=input-group]:bg-input/30 tw:**:data-[slot=input-group]:border-input/30 tw:max-h-72 tw:min-w-36 tw:overflow-hidden tw:rounded-md tw:shadow-md tw:ring-1 tw:duration-100 tw:**:has-[[data-slot=input-group-control]:focus-visible]:ring-0 tw:**:data-[slot=input-group]:m-1 tw:**:data-[slot=input-group]:mb-0 tw:**:data-[slot=input-group]:h-8 tw:**:data-[slot=input-group]:shadow-none tw:group/combobox-content tw:relative tw:flex tw:w-(--brn-combobox-width) tw:flex-col tw:p-0",
163
163
  ]);
164
164
  }
165
165
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmComboboxContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -9,7 +9,7 @@ import { NgIcon, provideIcons } from '@ng-icons/core';
9
9
  import { lucideX } from '@ng-icons/lucide';
10
10
  import { HlmBtn } from '@egose/shadcn-theme-ng-tw/button';
11
11
 
12
- const hlmDialogOverlayClass = hlm('tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:isolate tw:bg-black/10 tw:duration-100 tw:supports-backdrop-filter:backdrop-blur-xs');
12
+ const hlmDialogOverlayClass = hlm("tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:isolate tw:bg-black/10 tw:duration-100 tw:supports-backdrop-filter:backdrop-blur-xs");
13
13
  class HlmDialogOverlay {
14
14
  _classSettable = injectCustomClassSettable({ optional: true, host: true });
15
15
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
@@ -93,7 +93,7 @@ class HlmDialogContent {
93
93
  _dynamicComponentClass = this._dialogContext?.$dynamicComponentClass;
94
94
  constructor() {
95
95
  classes(() => [
96
- 'tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 tw:ring-foreground/10 tw:grid tw:max-w-[calc(100%-2rem)] tw:gap-6 tw:rounded-xl tw:p-6 tw:text-sm tw:ring-1 tw:duration-100 tw:sm:max-w-md tw:relative tw:mx-auto tw:w-full tw:outline-none tw:sm:mx-0',
96
+ "tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 tw:ring-foreground/10 tw:grid tw:max-w-[calc(100%-2rem)] tw:gap-6 tw:rounded-xl tw:p-6 tw:text-sm tw:ring-1 tw:duration-100 tw:sm:max-w-md tw:relative tw:mx-auto tw:w-full tw:outline-none tw:sm:mx-0",
97
97
  this._dynamicComponentClass,
98
98
  ]);
99
99
  }
@@ -9,7 +9,7 @@ import { hlm, classes } from '@egose/shadcn-theme-ng-tw/utils';
9
9
  class HlmDrawerOverlay {
10
10
  _classSettable = injectCustomClassSettable({ optional: true, host: true });
11
11
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
12
- _computedClass = computed(() => hlm('tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:bg-black/10 tw:supports-backdrop-filter:backdrop-blur-xs tw:transition-opacity tw:duration-150 tw:data-ending-style:opacity-0 tw:data-starting-style:opacity-0', this.userClass()), /* @ts-ignore */
12
+ _computedClass = computed(() => hlm("tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:bg-black/10 tw:supports-backdrop-filter:backdrop-blur-xs tw:transition-opacity tw:duration-150 tw:data-ending-style:opacity-0 tw:data-starting-style:opacity-0", this.userClass()), /* @ts-ignore */
13
13
  ...(ngDevMode ? [{ debugName: "_computedClass" }] : /* istanbul ignore next */ []));
14
14
  constructor() {
15
15
  effect(() => {
@@ -22,7 +22,7 @@ class HlmDropdownMenu {
22
22
  ...(ngDevMode ? [{ debugName: "_side" }] : /* istanbul ignore next */ []));
23
23
  sideOffset = input(1, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
24
24
  constructor() {
25
- classes(() => 'tw:motion-safe:data-open:animate-in tw:motion-safe:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:bg-popover tw:text-popover-foreground tw:min-w-32 tw:rounded-md tw:p-1 tw:shadow-md tw:ring-1 tw:duration-100 tw:my-[--spacing(var(--side-offset))] tw:overflow-x-hidden tw:overflow-y-auto tw:outline-none');
25
+ classes(() => "tw:motion-safe:data-open:animate-in tw:motion-safe:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:bg-popover tw:text-popover-foreground tw:min-w-32 tw:rounded-md tw:p-1 tw:shadow-md tw:ring-1 tw:duration-100 tw:my-[--spacing(var(--side-offset))] tw:overflow-x-hidden tw:overflow-y-auto tw:outline-none");
26
26
  this.setSideFromTransformOrigin();
27
27
  // this is a best effort, but does not seem to work currently
28
28
  // TODO: figure out a way for us to know the host is about to be closed. might not be possible with CDK
@@ -369,7 +369,7 @@ class HlmDropdownMenuSub {
369
369
  // this is a best effort, but does not seem to work currently
370
370
  // TODO: figure out a way for us to know the host is about to be closed. might not be possible with CDK
371
371
  this._host.closed.pipe(takeUntilDestroyed()).subscribe(() => this._state.set('closed'));
372
- classes(() => 'tw:motion-safe:data-open:animate-in tw:motion-safe:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:bg-popover tw:text-popover-foreground tw:min-w-[96px] tw:rounded-md tw:p-1 tw:shadow-lg tw:ring-1 tw:duration-100 tw:w-auto');
372
+ classes(() => "tw:motion-safe:data-open:animate-in tw:motion-safe:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:bg-popover tw:text-popover-foreground tw:min-w-[96px] tw:rounded-md tw:p-1 tw:shadow-lg tw:ring-1 tw:duration-100 tw:w-auto");
373
373
  }
374
374
  setSideFromTransformOrigin() {
375
375
  const side = this._menuSide?.side() ?? 'right';
@@ -83,29 +83,29 @@ Selector (from source): `eg-form-checkbox` (standalone component).
83
83
 
84
84
  ### `EgFormCheckbox` — `eg-form-checkbox`
85
85
 
86
- | Input | Type | Default | Description |
87
- | --------------- | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
88
- | `controlName` | `string` | `''` | `formControlName` key inside the parent `FormGroup`. **Required.** |
89
- | `label` | `string` | `''` | Label text next to the checkbox. |
90
- | `error` | `string \| undefined` | — | Error text shown when the control is invalid + touched/dirty/submitted (via `EgFormField` switching). |
91
- | `autoError` | `boolean` | `true` | Auto-resolve the message from the control's `ValidationErrors` when `error` is unset. Explicit `error` always wins; `error=""` counts as unset. Global wording via `provideEgFormErrorMessages`. |
92
- | `hint` | `string \| undefined` | — | Hint text shown when there is no error to display. |
93
- | `controlId` | `string \| undefined` | — | Explicit control id (first priority for `effectiveId`). |
94
- | `id` | `string \| undefined` | — | Fallback id (second priority). |
95
- | `name` | `string \| undefined` | — | Checkbox `name` attribute (defaults to `controlName`). |
96
- | `checked` | `boolean` | `false` | Initial checked state passed to `hlm-checkbox`. |
86
+ | Input | Type | Default | Description |
87
+ | --------------- | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
88
+ | `controlName` | `string` | `''` | `formControlName` key inside the parent `FormGroup`. **Required.** |
89
+ | `label` | `string` | `''` | Label text next to the checkbox. |
90
+ | `error` | `string \| undefined` | — | Error text shown when the control is invalid + touched/dirty/submitted (via `EgFormField` switching). |
91
+ | `autoError` | `boolean` | `true` | Auto-resolve the message from the control's `ValidationErrors` when `error` is unset. Explicit `error` always wins; `error=""` counts as unset. Global wording via `provideEgFormErrorMessages`. |
92
+ | `hint` | `string \| undefined` | — | Hint text shown when there is no error to display. |
93
+ | `controlId` | `string \| undefined` | — | Explicit control id (first priority for `effectiveId`). |
94
+ | `id` | `string \| undefined` | — | Fallback id (second priority). |
95
+ | `name` | `string \| undefined` | — | Checkbox `name` attribute (defaults to `controlName`). |
96
+ | `checked` | `boolean` | `false` | Initial checked state passed to `hlm-checkbox`. |
97
97
  | `required` | `boolean` | `false` | Shows a red `*` and sets checkbox `required` (boolean-coerced, so bare `required` works). Presentation only — it does not validate by itself; add `Validators.requiredTrue` to the `FormControl` or the control is never invalid and no error is shown. |
98
- | `disabled` | `boolean` | `false` | Locks interaction via checkbox `wrapperDisabled` (does not write to the form control). |
99
- | `class` | `ClassValue` | `''` | Extra host classes. |
100
- | `checkboxClass` | `string` | `''` | Extra classes for `hlm-checkbox`. |
101
- | `labelClass` | `string` | `''` | Extra classes for the label. |
102
- | `errorClass` | `string` | `''` | Extra classes for `hlm-error`. |
103
- | `hintClass` | `string` | `''` | Extra classes for `hlm-hint`. |
104
-
105
- | Member | Description |
106
- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
107
- | `effectiveId` | `controlId() \|\| id() \|\| generated` — wired to checkbox `id` and label `for`. |
108
- | `errorId` / `hintId` | `effectiveId + '-error' / '-hint'` for `aria-describedby`. |
98
+ | `disabled` | `boolean` | `false` | Locks interaction via checkbox `wrapperDisabled` (does not write to the form control). |
99
+ | `class` | `ClassValue` | `''` | Extra host classes. |
100
+ | `checkboxClass` | `string` | `''` | Extra classes for `hlm-checkbox`. |
101
+ | `labelClass` | `string` | `''` | Extra classes for the label. |
102
+ | `errorClass` | `string` | `''` | Extra classes for `hlm-error`. |
103
+ | `hintClass` | `string` | `''` | Extra classes for `hlm-hint`. |
104
+
105
+ | Member | Description |
106
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
107
+ | `effectiveId` | `controlId() \|\| id() \|\| generated` — wired to checkbox `id` and label `for`. |
108
+ | `errorId` / `hintId` | `effectiveId + '-error' / '-hint'` for `aria-describedby`. |
109
109
  | `describedBy()` | Returns `errorId` when `error()` is set and the control is invalid + dirty/touched/submitted, else `hintId` when `hint()` is set, else `null`. |
110
110
 
111
111
  Requires a `[formGroup]` ancestor: the component injects `FormGroupDirective` and provides
@@ -36,6 +36,14 @@ class EgFormCheckbox {
36
36
  ...(ngDevMode ? [{ debugName: "_status" }] : /* istanbul ignore next */ []));
37
37
  _submitted = signal(false, /* @ts-ignore */
38
38
  ...(ngDevMode ? [{ debugName: "_submitted" }] : /* istanbul ignore next */ []));
39
+ /**
40
+ * `touched`/`dirty` are plain flags with no observable: a stepper surfacing
41
+ * errors via `markAllAsTouched()` (no status change, no submit) would
42
+ * otherwise stay invisible under OnPush. Polled in `ngDoCheck` so the
43
+ * computed below recomputes on interaction changes too.
44
+ */
45
+ _interacted = signal(false, /* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "_interacted" }] : /* istanbul ignore next */ []));
39
47
  _trackedControl = null;
40
48
  _controlSub = null;
41
49
  // Classes
@@ -107,10 +115,9 @@ class EgFormCheckbox {
107
115
  showError = computed(() => {
108
116
  this._status();
109
117
  this._submitted();
118
+ this._interacted();
110
119
  const control = this.formGroupDirective.form.get(this.controlName());
111
- return (!!this.resolvedError() &&
112
- !!control?.invalid &&
113
- (control.dirty || control.touched || this._submitted()));
120
+ return !!this.resolvedError() && !!control?.invalid && (this._interacted() || this._submitted());
114
121
  }, /* @ts-ignore */
115
122
  ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
116
123
  describedBy() {
@@ -136,11 +143,14 @@ class EgFormCheckbox {
136
143
  const control = this.formGroupDirective.form.get(this.controlName()) ?? null;
137
144
  if (control !== this._trackedControl) {
138
145
  this._trackControl();
146
+ return;
139
147
  }
140
- else if (control && this._status() !== control.status) {
141
- // Fallback poll: catches status changes missed by the subscription
142
- // (e.g. control replaced without re-init).
143
- this._status.set(control.status);
148
+ if (control) {
149
+ if (this._status() !== control.status)
150
+ this._status.set(control.status);
151
+ const interacted = control.touched || control.dirty;
152
+ if (this._interacted() !== interacted)
153
+ this._interacted.set(interacted);
144
154
  }
145
155
  }
146
156
  ngOnDestroy() {
@@ -153,6 +163,7 @@ class EgFormCheckbox {
153
163
  const control = this.formGroupDirective.form.get(this.controlName()) ?? null;
154
164
  this._trackedControl = control;
155
165
  this._status.set(control?.status ?? null);
166
+ this._interacted.set(!!control && (control.touched || control.dirty));
156
167
  if (control) {
157
168
  this._controlSub = control.statusChanges.subscribe((status) => {
158
169
  if (this._status() !== status)
@@ -20,6 +20,13 @@ declare class EgFormCheckbox implements DoCheck, AfterContentInit, OnDestroy {
20
20
  */
21
21
  private readonly _status;
22
22
  private readonly _submitted;
23
+ /**
24
+ * `touched`/`dirty` are plain flags with no observable: a stepper surfacing
25
+ * errors via `markAllAsTouched()` (no status change, no submit) would
26
+ * otherwise stay invisible under OnPush. Polled in `ngDoCheck` so the
27
+ * computed below recomputes on interaction changes too.
28
+ */
29
+ private readonly _interacted;
23
30
  private _trackedControl;
24
31
  private _controlSub;
25
32
  readonly userClass: _angular_core.InputSignal<ClassValue>;
@@ -21,6 +21,14 @@ class EgFormField {
21
21
  */
22
22
  submittedSignal = signal(false, /* @ts-ignore */
23
23
  ...(ngDevMode ? [{ debugName: "submittedSignal" }] : /* istanbul ignore next */ []));
24
+ /**
25
+ * `touched`/`dirty` are plain flags with no observable: surfacing errors via
26
+ * `markAllAsTouched()` (e.g. a stepper blocking `Next` without changing the
27
+ * control status) would otherwise stay invisible under OnPush. Polled in
28
+ * `ngDoCheck` so `hasError` recomputes on interaction changes too.
29
+ */
30
+ interactedSignal = signal(false, /* @ts-ignore */
31
+ ...(ngDevMode ? [{ debugName: "interactedSignal" }] : /* istanbul ignore next */ []));
24
32
  sub = new Subscription();
25
33
  ngAfterContentInit() {
26
34
  const ctrlDir = this.control();
@@ -29,32 +37,41 @@ class EgFormField {
29
37
  this.statusSignal.set(status);
30
38
  }));
31
39
  this.statusSignal.set(ctrlDir.control.status);
40
+ this.interactedSignal.set(ctrlDir.control.touched || ctrlDir.control.dirty);
32
41
  }
33
42
  }
34
43
  ngDoCheck() {
35
44
  const submitted = !!this.formGroupDirective?.submitted;
36
45
  if (this.submittedSignal() !== submitted)
37
46
  this.submittedSignal.set(submitted);
47
+ // NOTE: `control` itself may still be unset on the first pass (this hook
48
+ // runs before the content child's setup), so optional-chain everything.
49
+ const ctrl = this.control()?.control ?? null;
50
+ if (ctrl) {
51
+ const interacted = ctrl.touched || ctrl.dirty;
52
+ if (this.interactedSignal() !== interacted)
53
+ this.interactedSignal.set(interacted);
54
+ }
38
55
  }
39
56
  ngOnDestroy() {
40
57
  this.sub.unsubscribe();
41
58
  }
42
59
  hasError = computed(() => {
43
- const ctrlDir = this.control();
44
- if (!ctrlDir)
60
+ const ctrl = this.control()?.control ?? null;
61
+ if (!ctrl)
45
62
  return false;
46
63
  this.statusSignal();
47
64
  this.submittedSignal();
48
- return (!!ctrlDir.control.errors &&
49
- (ctrlDir.control.touched || ctrlDir.control.dirty || !!this.formGroupDirective?.submitted));
65
+ this.interactedSignal();
66
+ return !!ctrl.errors && (this.interactedSignal() || this.submittedSignal());
50
67
  }, /* @ts-ignore */
51
68
  ...(ngDevMode ? [{ debugName: "hasError" }] : /* istanbul ignore next */ []));
52
69
  firstErrorKey = computed(() => {
53
- const ctrlDir = this.control();
54
- if (!ctrlDir || !ctrlDir.control.errors)
70
+ const ctrl = this.control()?.control ?? null;
71
+ if (!ctrl?.errors)
55
72
  return null;
56
73
  this.statusSignal();
57
- return Object.keys(ctrlDir.control.errors)[0] ?? null;
74
+ return Object.keys(ctrl.errors)[0] ?? null;
58
75
  }, /* @ts-ignore */
59
76
  ...(ngDevMode ? [{ debugName: "firstErrorKey" }] : /* istanbul ignore next */ []));
60
77
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgFormField, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -16,6 +16,13 @@ declare class EgFormField implements AfterContentInit, DoCheck, OnDestroy {
16
16
  * even when the control was never touched/dirty.
17
17
  */
18
18
  private submittedSignal;
19
+ /**
20
+ * `touched`/`dirty` are plain flags with no observable: surfacing errors via
21
+ * `markAllAsTouched()` (e.g. a stepper blocking `Next` without changing the
22
+ * control status) would otherwise stay invisible under OnPush. Polled in
23
+ * `ngDoCheck` so `hasError` recomputes on interaction changes too.
24
+ */
25
+ private interactedSignal;
19
26
  private sub;
20
27
  ngAfterContentInit(): void;
21
28
  ngDoCheck(): void;
@@ -31,7 +31,7 @@ class HlmHoverCardContent {
31
31
  this._renderer.setAttribute(this._element.nativeElement, 'data-side', this.side());
32
32
  });
33
33
  classes(() => [
34
- 'tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:bg-popover tw:text-popover-foreground tw:w-64 tw:rounded-lg tw:p-4 tw:text-sm tw:shadow-md tw:ring-1 tw:duration-100 tw:z-50 tw:outline-none',
34
+ "tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:bg-popover tw:text-popover-foreground tw:w-64 tw:rounded-lg tw:p-4 tw:text-sm tw:shadow-md tw:ring-1 tw:duration-100 tw:z-50 tw:outline-none",
35
35
  'tw:data-[state=open]:animate-in tw:data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 tw:data-[state=closed]:zoom-out-95 tw:data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
36
36
  ]);
37
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@egose/shadcn-theme-ng-tw",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Angular shadcn UI theme",
5
5
  "author": "Junmin Ahn",
6
6
  "bundles": [
@@ -33,7 +33,7 @@ class HlmPopoverContent {
33
33
  effect(() => {
34
34
  this._renderer.setAttribute(this._element.nativeElement, 'data-state', this.state());
35
35
  });
36
- classes(() => 'tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:gap-4 tw:rounded-md tw:p-4 tw:text-sm tw:shadow-md tw:ring-1 tw:duration-100 tw:relative tw:flex tw:w-72 tw:flex-col tw:outline-none');
36
+ classes(() => "tw:bg-popover tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:gap-4 tw:rounded-md tw:p-4 tw:text-sm tw:shadow-md tw:ring-1 tw:duration-100 tw:relative tw:flex tw:w-72 tw:flex-col tw:outline-none");
37
37
  }
38
38
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmPopoverContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
39
39
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: HlmPopoverContent, isStandalone: true, selector: "[hlmPopoverContent],hlm-popover-content", host: { attributes: { "data-slot": "popover-content" } }, ngImport: i0 });
@@ -92,7 +92,7 @@ class HlmSelectContent {
92
92
  ...(ngDevMode ? [{ debugName: "_computedListboxClasses" }] : /* istanbul ignore next */ []));
93
93
  showScroll = input(false, { ...(ngDevMode ? { debugName: "showScroll" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
94
94
  constructor() {
95
- classes(() => 'tw:bg-popover no-scrollbar tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:max-h-72 tw:min-w-36 tw:flex-col tw:rounded-md tw:shadow-md tw:ring-1 tw:duration-100 tw:relative tw:flex tw:w-(--brn-select-width) tw:overflow-x-hidden tw:overflow-y-auto');
95
+ classes(() => "tw:bg-popover no-scrollbar tw:text-popover-foreground tw:data-open:animate-in tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-open:fade-in-0 tw:data-closed:zoom-out-95 tw:data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:ring-foreground/10 tw:max-h-72 tw:min-w-36 tw:flex-col tw:rounded-md tw:shadow-md tw:ring-1 tw:duration-100 tw:relative tw:flex tw:w-(--brn-select-width) tw:overflow-x-hidden tw:overflow-y-auto");
96
96
  }
97
97
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmSelectContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
98
98
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: HlmSelectContent, isStandalone: true, selector: "hlm-select-content", inputs: { showScroll: { classPropertyName: "showScroll", publicName: "showScroll", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.BrnSelectContent }], ngImport: i0, template: `
package/stepper/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Stepper (`@egose/shadcn-theme-ng/stepper`)
2
2
 
3
- Multi-step workflow navigation, ported from the spartan stepper blocks. This subpath ships seven pieces — `HlmStepper` (root), `HlmStep` (step), `HlmStepHeader` (clickable header with indicator), `HlmStepLabel` (rich label template), `HlmStepContent` (lazy body), `HlmStepperNext` / `HlmStepperPrevious` (navigation buttons) — plus `provideHlmStepperConfig` / `injectHlmStepperConfig` for global defaults. All are thin shadcn-styled components over Angular CDK's `@angular/cdk/stepper` primitives (`CdkStepper`, `CdkStep`, `CdkStepHeader`, `CdkStepLabel`).
3
+ Multi-step workflow navigation, ported from the spartan stepper blocks. This subpath ships seven pieces — `HlmStepper` (root), `HlmStep` (step), `HlmStepHeader` (clickable header with indicator), `HlmStepLabel` (rich label template), `HlmStepContent` (lazy body), `HlmStepperNext` / `HlmStepperPrevious` (navigation buttons) — plus `provideHlmStepperConfig` / `injectHlmStepperConfig` for global defaults. Headers automatically show the full label in a styled tooltip on hover/focus (overridable per step via `tooltip`). All are thin shadcn-styled components over Angular CDK's `@angular/cdk/stepper` primitives (`CdkStepper`, `CdkStep`, `CdkStepHeader`, `CdkStepLabel`).
4
4
 
5
5
  > **Ships as:** `@egose/shadcn-theme-ng/stepper` and `@egose/shadcn-theme-ng-tw/stepper` (the `tw:`-prefixed Tailwind variant). See the [package README](../../README.md) for install steps, peer dependencies, Tailwind setup, and testing/release guidance. Do not publish this project directory independently.
6
6
 
@@ -33,6 +33,7 @@ import {
33
33
  HlmStepperPrevious,
34
34
  injectHlmStepperConfig,
35
35
  provideHlmStepperConfig,
36
+ type HlmStepTooltipPosition,
36
37
  } from '@egose/shadcn-theme-ng/stepper';
37
38
  // tw variant: swap to '@egose/shadcn-theme-ng-tw/stepper'
38
39
  ```
@@ -62,19 +63,19 @@ import { HlmStepperModule } from '@egose/shadcn-theme-ng/stepper';
62
63
  export class FeatureModule {}
63
64
  ```
64
65
 
65
- | Symbol | Kind | Description |
66
- | ------------------------ | ------------- | ------------------------------------------------------------------------ |
67
- | `HlmStepper` | Component | Root: `<hlm-stepper>` — extends `CdkStepper`. |
68
- | `HlmStep` | Component | Step: `<hlm-step label="…">` — extends `CdkStep`, adds `icon` input. |
69
- | `HlmStepHeader` | Component | Header: `<hlm-step-header>` — extends `CdkStepHeader`, renders indicator. |
70
- | `HlmStepLabel` | Directive | Rich label: `<ng-template hlmStepLabel>` — extends `CdkStepLabel`. |
71
- | `HlmStepContent` | Directive | Lazy body: `<ng-template hlmStepContent>` — attached on first selection. |
72
- | `HlmStepperNext` | Directive | Next button: `button[hlmStepperNext]` — extends `CdkStepperNext`. |
73
- | `HlmStepperPrevious` | Directive | Back button: `button[hlmStepperPrevious]` — extends `CdkStepperPrevious`. |
74
- | `HlmStepperImports` | `const` array | All seven, spread into `imports: [...]`. |
75
- | `HlmStepperModule` | NgModule | Imports + re-exports all seven. |
76
- | `provideHlmStepperConfig`| Function | Global defaults for animations + indicator mode. |
77
- | `injectHlmStepperConfig` | Function | Reads the (optionally provided) global config. |
66
+ | Symbol | Kind | Description |
67
+ | ------------------------- | ------------- | ------------------------------------------------------------------------- |
68
+ | `HlmStepper` | Component | Root: `<hlm-stepper>` — extends `CdkStepper`. |
69
+ | `HlmStep` | Component | Step: `<hlm-step label="…">` — extends `CdkStep`, adds `icon` input. |
70
+ | `HlmStepHeader` | Component | Header: `<hlm-step-header>` — extends `CdkStepHeader`, renders indicator. |
71
+ | `HlmStepLabel` | Directive | Rich label: `<ng-template hlmStepLabel>` — extends `CdkStepLabel`. |
72
+ | `HlmStepContent` | Directive | Lazy body: `<ng-template hlmStepContent>` — attached on first selection. |
73
+ | `HlmStepperNext` | Directive | Next button: `button[hlmStepperNext]` — extends `CdkStepperNext`. |
74
+ | `HlmStepperPrevious` | Directive | Back button: `button[hlmStepperPrevious]` — extends `CdkStepperPrevious`. |
75
+ | `HlmStepperImports` | `const` array | All seven, spread into `imports: [...]`. |
76
+ | `HlmStepperModule` | NgModule | Imports + re-exports all seven. |
77
+ | `provideHlmStepperConfig` | Function | Global defaults for animations + indicator mode. |
78
+ | `injectHlmStepperConfig` | Function | Reads the (optionally provided) global config. |
78
79
 
79
80
  ## Anatomy / Structure
80
81
 
@@ -122,47 +123,50 @@ Lazy body (attached only when the step is first selected):
122
123
 
123
124
  ### `HlmStepper` — selector `hlm-stepper` (component, extends `CdkStepper`)
124
125
 
125
- | Input | Type | Default | Description |
126
- | -------------------- | ----------------------------- | ---------- | ------------------------------------------------------------------ |
127
- | `orientation` | forwarded to `CdkStepper` | — | `'horizontal' \| 'vertical'`. |
128
- | `linear` | forwarded to `CdkStepper` | — | Current step must be valid (via `stepControl`) before advancing. |
129
- | `selectedIndex` | forwarded to `CdkStepper` | — | Controlled selected-step index. |
130
- | `labelPosition` | `'end' \| 'bottom'` | `'end'` | Horizontal layout: label beside vs. below the indicator. |
131
- | `headerPosition` | `'top' \| 'bottom'` | `'top'` | Horizontal layout: headers above vs. below the content panel. |
132
- | `indicatorMode` | `'number' \| 'state' \| 'icon'` | from config (`'state'`) | Indicator rendering (see below). |
133
- | `stepperAriaLabel` | `string \| null` | `'Progress'` | `aria-label` for the tablist (ignored when labelledby is set). |
134
- | `stepperAriaLabelledby` | `string \| null` | `null` | `aria-labelledby` for the tablist. |
135
- | `animationsEnabled` | `boolean` | from config (`true`) | Toggle step transitions per instance. |
136
- | `animationDuration` | `number` (ms) | from config (`300`) | Transition duration per instance. |
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. |
137
138
 
138
139
  `next()` is overridden to `markAllAsTouched()` + `updateValueAndValidity()` on the current `stepControl` first, so linear steppers surface validation errors before denying the transition.
139
140
 
140
141
  ### `HlmStep` — selector `hlm-step` (component, extends `CdkStep`)
141
142
 
142
- | Input | Type | Default | Description |
143
- | ------------- | ---------------- | ------- | -------------------------------------------------------- |
144
- | `label` | forwarded to `CdkStep` | — | Step title (string, or rich template via `hlmStepLabel`). |
145
- | `icon` | `string \| null` | `null` | Icon name rendered when `indicatorMode="icon"`. |
146
- | `stepControl` | forwarded to `CdkStep` | — | Form group driving `linear` validation. |
147
- | `optional` | forwarded to `CdkStep` | — | Shows an "Optional" caption in the header. |
148
- | `errorMessage`| forwarded to `CdkStep` | — | Header error caption when the step state is `error`. |
149
- | `hasError` | forwarded to `CdkStep` | — | Forces the error state (pair with `STEPPER_GLOBAL_OPTIONS` `showError`). |
143
+ | Input | Type | Default | Description |
144
+ | ----------------- | ---------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------ |
145
+ | `label` | forwarded to `CdkStep` | — | Step title (string, or rich template via `hlmStepLabel`). |
146
+ | `icon` | `string \| null` | `null` | Icon name rendered when `indicatorMode="icon"`. |
147
+ | `stepControl` | forwarded to `CdkStep` | — | Form group driving `linear` validation. |
148
+ | `optional` | forwarded to `CdkStep` | — | Shows an "Optional" caption in the header. |
149
+ | `errorMessage` | forwarded to `CdkStep` | — | Header error caption when the step state is `error`. |
150
+ | `hasError` | forwarded to `CdkStep` | — | Forces the error state (pair with `STEPPER_GLOBAL_OPTIONS` `showError`). |
151
+ | `tooltip` | `string \| TemplateRef<unknown> \| null` | `null` (falls back to the full string label) | Styled header tooltip override; rich templates allowed. |
152
+ | `tooltipPosition` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` | Tooltip side. |
153
+ | `tooltipDisabled` | `boolean` | `false` | Disables the header tooltip for this step. |
150
154
 
151
155
  ### `HlmStepHeader` — selector `hlm-step-header` (component, extends `CdkStepHeader`)
152
156
 
153
157
  Usually rendered by `HlmStepper` itself; use directly only for custom layouts.
154
158
 
155
- | Input | Type | Default | Description |
156
- | --------------- | ------------------------------------------- | ------- | -------------------------------------------- |
157
- | `index` | `number` | `0` | Step index (drives the number indicator). |
158
- | `state` | `StepState` | `'number'` | CDK step state (`number`, `edit`, `done`, `error`, …). |
159
- | `label` | `HlmStepLabel \| string \| null` | `null` | Resolved label content. |
160
- | `selected` / `reached` / `active` | `boolean` | `false` | Visual state flags. |
161
- | `optional` / `disabled` | `boolean` | `false` | Optional caption / disabled styling. |
162
- | `icon` | `string \| null` | `null` | Icon name for `indicatorMode="icon"`. |
163
- | `indicatorMode` | `'number' \| 'state' \| 'icon'` | from config (`'state'`) | Indicator rendering. |
164
- | `labelPosition` | `'end' \| 'bottom'` | `'end'` | Label beside vs. below the indicator. |
165
- | `errorMessage` | `string` | `''` | Error caption shown when `state === 'error'`.|
159
+ | Input | Type | Default | Description |
160
+ | --------------------------------- | -------------------------------- | ----------------------- | ------------------------------------------------------ |
161
+ | `index` | `number` | `0` | Step index (drives the number indicator). |
162
+ | `state` | `StepState` | `'number'` | CDK step state (`number`, `edit`, `done`, `error`, …). |
163
+ | `label` | `HlmStepLabel \| string \| null` | `null` | Resolved label content. |
164
+ | `selected` / `reached` / `active` | `boolean` | `false` | Visual state flags. |
165
+ | `optional` / `disabled` | `boolean` | `false` | Optional caption / disabled styling. |
166
+ | `icon` | `string \| null` | `null` | Icon name for `indicatorMode="icon"`. |
167
+ | `indicatorMode` | `'number' \| 'state' \| 'icon'` | from config (`'state'`) | Indicator rendering. |
168
+ | `labelPosition` | `'end' \| 'bottom'` | `'end'` | Label beside vs. below the indicator. |
169
+ | `errorMessage` | `string` | `''` | Error caption shown when `state === 'error'`. |
166
170
 
167
171
  Indicator modes: `number` always renders `1, 2, 3`; `state` (default) renders a check icon for selected/reached steps and an alert icon for errors; `icon` renders the per-step `icon` name (provide icons via `@ng-icons/core` `provideIcons` in your app).
168
172
 
@@ -177,7 +181,9 @@ import { provideHlmStepperConfig } from '@egose/shadcn-theme-ng/stepper';
177
181
 
178
182
  @Component({
179
183
  // ...
180
- providers: [provideHlmStepperConfig({ animationEnabled: true, animationDuration: 300, defaultIndicatorMode: 'state' })],
184
+ providers: [
185
+ provideHlmStepperConfig({ animationEnabled: true, animationDuration: 300, defaultIndicatorMode: 'state' }),
186
+ ],
181
187
  })
182
188
  export class FeatureComponent {}
183
189
  ```
@@ -239,13 +245,14 @@ import { Component, computed, inject } from '@angular/core';
239
245
  import { toSignal } from '@angular/core/rxjs-interop';
240
246
  import { map } from 'rxjs/operators';
241
247
 
242
- @Component({ /* … */ })
248
+ @Component({
249
+ /* … */
250
+ })
243
251
  export class DemoResponsive {
244
252
  private readonly _breakpoints = inject(BreakpointObserver);
245
- private readonly _isSmall = toSignal(
246
- this._breakpoints.observe('(max-width: 767.98px)').pipe(map((s) => s.matches)),
247
- { initialValue: false },
248
- );
253
+ private readonly _isSmall = toSignal(this._breakpoints.observe('(max-width: 767.98px)').pipe(map((s) => s.matches)), {
254
+ initialValue: false,
255
+ });
249
256
  protected readonly orientation = computed(() => (this._isSmall() ? 'vertical' : 'horizontal'));
250
257
  }
251
258
  ```
@@ -262,7 +269,9 @@ With `linear`, the current step must be valid before advancing — `HlmStepper.n
262
269
  import { Component, inject } from '@angular/core';
263
270
  import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
264
271
 
265
- @Component({ /* …, imports: [ReactiveFormsModule, ...HlmStepperImports, ...HlmButtonImports] */ })
272
+ @Component({
273
+ /* …, imports: [ReactiveFormsModule, ...HlmStepperImports, ...HlmButtonImports] */
274
+ })
266
275
  export class DemoLinear {
267
276
  private readonly _fb = inject(FormBuilder);
268
277
  protected readonly identity = this._fb.group({ name: ['', Validators.required] });
@@ -334,6 +343,20 @@ Or set global defaults with `provideHlmStepperConfig` and override per instance.
334
343
 
335
344
  Per-step `icon` names resolve through `@ng-icons/core` — register them with `provideIcons` in your app.
336
345
 
346
+ ### 9. Header tooltips
347
+
348
+ Headers carry the styled `HlmTooltip` automatically: hovering or focusing a header shows the step's full string label (useful when labels truncate). Override per step with `tooltip` (plain text or a `TemplateRef`), adjust the side with `tooltipPosition`, or opt out with `tooltipDisabled`:
349
+
350
+ ```html
351
+ <hlm-stepper>
352
+ <hlm-step label="An especially long account setup label that truncates"><!-- falls back to the label --></hlm-step>
353
+ <hlm-step label="Profile" tooltip="Add a photo so teammates recognize you"><!-- … --></hlm-step>
354
+ <hlm-step label="Review" [tooltipDisabled]="true"><!-- … --></hlm-step>
355
+ </hlm-stepper>
356
+ ```
357
+
358
+ Steps using `<ng-template hlmStepLabel>` have no string to fall back to — pass `tooltip` explicitly if they need one. Custom `HlmStepHeader` layouts can apply `hlmTooltip` directly.
359
+
337
360
  ## Accessibility notes
338
361
 
339
362
  - 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`.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, TemplateRef, Directive, ViewContainerRef, DestroyRef, input, contentChild, effect, untracked, ChangeDetectionStrategy, Component, InjectionToken, numberAttribute, booleanAttribute, computed, signal, NgModule } 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';
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';
@@ -9,6 +9,7 @@ import { lucideCircleAlert, lucideCheck } from '@ng-icons/lucide';
9
9
  import { buttonVariants } from '@egose/shadcn-theme-ng-tw/button';
10
10
  import { classes, hlm } from '@egose/shadcn-theme-ng-tw/utils';
11
11
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
12
+ import { HlmTooltip } from '@egose/shadcn-theme-ng-tw/tooltip';
12
13
  import { cva } from 'class-variance-authority';
13
14
  import { tap } from 'rxjs/operators';
14
15
 
@@ -43,6 +44,12 @@ class HlmStep extends CdkStep {
43
44
  _destroyRef = inject(DestroyRef);
44
45
  icon = input(null, /* @ts-ignore */
45
46
  ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
47
+ /** Styled header tooltip override. Falls back to the full string label when unset. */
48
+ tooltip = input(null, /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []));
50
+ tooltipPosition = input('bottom', /* @ts-ignore */
51
+ ...(ngDevMode ? [{ debugName: "tooltipPosition" }] : /* istanbul ignore next */ []));
52
+ tooltipDisabled = input(false, { ...(ngDevMode ? { debugName: "tooltipDisabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
46
53
  /** Content for step label given by `<ng-template hlmStepLabel>`. */
47
54
  stepLabelContent = contentChild(HlmStepLabel, /* @ts-ignore */
48
55
  ...(ngDevMode ? [{ debugName: "stepLabelContent" }] : /* istanbul ignore next */ []));
@@ -69,7 +76,7 @@ class HlmStep extends CdkStep {
69
76
  });
70
77
  }
71
78
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmStep, deps: [], target: i0.ɵɵFactoryTarget.Component });
72
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.3", type: HlmStep, isStandalone: true, selector: "hlm-step", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "hidden": "" } }, providers: [
79
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.3", type: HlmStep, isStandalone: true, selector: "hlm-step", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tooltip: { classPropertyName: "tooltip", publicName: "tooltip", isSignal: true, isRequired: false, transformFunction: null }, tooltipPosition: { classPropertyName: "tooltipPosition", publicName: "tooltipPosition", isSignal: true, isRequired: false, transformFunction: null }, tooltipDisabled: { classPropertyName: "tooltipDisabled", publicName: "tooltipDisabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "hidden": "" } }, providers: [
73
80
  {
74
81
  provide: CdkStep,
75
82
  useExisting: HlmStep,
@@ -104,7 +111,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
104
111
  </ng-template>
105
112
  `,
106
113
  }]
107
- }], ctorParameters: () => [], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], stepLabelContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HlmStepLabel), { isSignal: true }] }], _lazyContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HlmStepContent), { isSignal: true }] }] } });
114
+ }], ctorParameters: () => [], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltip", required: false }] }], tooltipPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipPosition", required: false }] }], tooltipDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipDisabled", required: false }] }], stepLabelContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HlmStepLabel), { isSignal: true }] }], _lazyContent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HlmStepContent), { isSignal: true }] }] } });
108
115
 
109
116
  const defaultConfig = {
110
117
  animationEnabled: true,
@@ -274,11 +281,11 @@ const stepTransition = cva('', {
274
281
  variants: {
275
282
  motion: {
276
283
  none: '',
277
- 'enter-forward': 'tw:animate-in fade-in slide-in-from-right-8 tw:fill-mode-both',
278
- 'enter-backward': 'tw:animate-in fade-in slide-in-from-left-8 tw:fill-mode-both',
279
- 'leave-forward': 'tw:animate-out fade-out slide-out-to-left-8 tw:fill-mode-both tw:absolute tw:top-0 tw:right-0 tw:left-0',
280
- 'leave-backward': 'tw:animate-out fade-out slide-out-to-right-8 tw:fill-mode-both tw:absolute tw:top-0 tw:right-0 tw:left-0',
281
- 'enter-bottom': 'tw:animate-in fade-in slide-in-from-bottom-2 tw:fill-mode-both tw:duration-400 tw:[animation-timing-function:cubic-bezier(0.34,1.56,0.64,1)]',
284
+ 'enter-forward': 'tw:animate-in tw:fade-in tw:slide-in-from-right-8 tw:fill-mode-both',
285
+ 'enter-backward': 'tw:animate-in tw:fade-in tw:slide-in-from-left-8 tw:fill-mode-both',
286
+ 'leave-forward': 'tw:animate-out tw:fade-out tw:slide-out-to-left-8 tw:fill-mode-both tw:absolute tw:top-0 tw:right-0 tw:left-0',
287
+ 'leave-backward': 'tw:animate-out tw:fade-out tw:slide-out-to-right-8 tw:fill-mode-both tw:absolute tw:top-0 tw:right-0 tw:left-0',
288
+ 'enter-bottom': 'tw:animate-in tw:fade-in tw:slide-in-from-bottom-2 tw:fill-mode-both tw:duration-400 tw:[animation-timing-function:cubic-bezier(0.34,1.56,0.64,1)]',
282
289
  },
283
290
  },
284
291
  defaultVariants: { motion: 'none' },
@@ -329,6 +336,23 @@ class HlmStepper extends CdkStepper {
329
336
  stepIcon(step) {
330
337
  return step instanceof HlmStep ? step.icon() : null;
331
338
  }
339
+ _headerTooltip(step) {
340
+ const override = step instanceof HlmStep ? step.tooltip() : null;
341
+ if (override === null || override === undefined) {
342
+ return step.label || null;
343
+ }
344
+ // BrnTooltip types templates as TemplateRef<void>; any template renders the same at runtime.
345
+ return override;
346
+ }
347
+ _headerTooltipPosition(step) {
348
+ return step instanceof HlmStep ? step.tooltipPosition() : 'bottom';
349
+ }
350
+ _headerTooltipDisabled(step) {
351
+ if (step instanceof HlmStep && step.tooltipDisabled()) {
352
+ return true;
353
+ }
354
+ return !this._headerTooltip(step);
355
+ }
332
356
  _enterAnimationClass() {
333
357
  if (!this._hasSelectionChanged()) {
334
358
  return '';
@@ -426,6 +450,9 @@ class HlmStepper extends CdkStepper {
426
450
  [labelPosition]="orientation === 'horizontal' ? labelPosition() : 'end'"
427
451
  [indicatorMode]="indicatorMode()"
428
452
  [icon]="stepIcon(step)"
453
+ [hlmTooltip]="_headerTooltip(step)"
454
+ [position]="_headerTooltipPosition(step)"
455
+ [tooltipDisabled]="_headerTooltipDisabled(step)"
429
456
  />
430
457
  </ng-template>
431
458
 
@@ -472,13 +499,13 @@ class HlmStepper extends CdkStepper {
472
499
  }
473
500
  </div>
474
501
  </ng-template>
475
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CdkStepperModule }, { kind: "directive", type: i1.CdkStepHeader, selector: "[cdkStepHeader]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: HlmStepHeader, selector: "hlm-step-header", inputs: ["state", "label", "errorMessage", "index", "selected", "reached", "active", "optional", "disabled", "icon", "indicatorMode", "labelPosition"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
502
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CdkStepperModule }, { kind: "directive", type: i1.CdkStepHeader, selector: "[cdkStepHeader]" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: HlmStepHeader, selector: "hlm-step-header", inputs: ["state", "label", "errorMessage", "index", "selected", "reached", "active", "optional", "disabled", "icon", "indicatorMode", "labelPosition"] }, { kind: "directive", type: HlmTooltip, selector: "[hlmTooltip]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
476
503
  }
477
504
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: HlmStepper, decorators: [{
478
505
  type: Component,
479
506
  args: [{
480
507
  selector: 'hlm-stepper',
481
- imports: [CdkStepperModule, NgTemplateOutlet, HlmStepHeader],
508
+ imports: [CdkStepperModule, NgTemplateOutlet, HlmStepHeader, HlmTooltip],
482
509
  providers: [{ provide: CdkStepper, useExisting: HlmStepper }],
483
510
  changeDetection: ChangeDetectionStrategy.OnPush,
484
511
  template: `
@@ -556,6 +583,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
556
583
  [labelPosition]="orientation === 'horizontal' ? labelPosition() : 'end'"
557
584
  [indicatorMode]="indicatorMode()"
558
585
  [icon]="stepIcon(step)"
586
+ [hlmTooltip]="_headerTooltip(step)"
587
+ [position]="_headerTooltipPosition(step)"
588
+ [tooltipDisabled]="_headerTooltipDisabled(step)"
559
589
  />
560
590
  </ng-template>
561
591
 
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { TemplateRef, ValueProvider } from '@angular/core';
2
+ import { ValueProvider, TemplateRef } 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
 
@@ -8,10 +8,26 @@ declare class HlmStepLabel extends CdkStepLabel {
8
8
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HlmStepLabel, "[hlmStepLabel]", never, {}, {}, never, never, true, never>;
9
9
  }
10
10
 
11
+ type HlmStepperLabelPosition = 'end' | 'bottom';
12
+ type HlmStepperHeaderPosition = 'top' | 'bottom';
13
+ type HlmStepperIndicatorMode = 'number' | 'state' | 'icon';
14
+ type HlmStepTooltipPosition = 'top' | 'right' | 'bottom' | 'left';
15
+ interface HlmStepperConfig {
16
+ animationEnabled: boolean;
17
+ animationDuration: number;
18
+ defaultIndicatorMode: HlmStepperIndicatorMode;
19
+ }
20
+ declare function provideHlmStepperConfig(config: Partial<HlmStepperConfig>): ValueProvider;
21
+ declare function injectHlmStepperConfig(): HlmStepperConfig;
22
+
11
23
  declare class HlmStep extends CdkStep {
12
24
  private readonly _viewContainerRef;
13
25
  private readonly _destroyRef;
14
26
  readonly icon: _angular_core.InputSignal<string | null>;
27
+ /** Styled header tooltip override. Falls back to the full string label when unset. */
28
+ readonly tooltip: _angular_core.InputSignal<string | TemplateRef<unknown> | null>;
29
+ readonly tooltipPosition: _angular_core.InputSignal<HlmStepTooltipPosition>;
30
+ readonly tooltipDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
15
31
  /** Content for step label given by `<ng-template hlmStepLabel>`. */
16
32
  readonly stepLabelContent: _angular_core.Signal<HlmStepLabel | undefined>;
17
33
  /** Content that will be rendered lazily. */
@@ -20,7 +36,7 @@ declare class HlmStep extends CdkStep {
20
36
  portal: TemplatePortal<unknown> | null;
21
37
  constructor();
22
38
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<HlmStep, never>;
23
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<HlmStep, "hlm-step", ["hlmStep"], { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; }, {}, ["stepLabelContent", "_lazyContent"], ["*"], true, never>;
39
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<HlmStep, "hlm-step", ["hlmStep"], { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "tooltipPosition": { "alias": "tooltipPosition"; "required": false; "isSignal": true; }; "tooltipDisabled": { "alias": "tooltipDisabled"; "required": false; "isSignal": true; }; }, {}, ["stepLabelContent", "_lazyContent"], ["*"], true, never>;
24
40
  }
25
41
 
26
42
  /**
@@ -32,17 +48,6 @@ declare class HlmStepContent<C> {
32
48
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HlmStepContent<any>, "ng-template[hlmStepContent]", never, {}, {}, never, never, true, never>;
33
49
  }
34
50
 
35
- type HlmStepperLabelPosition = 'end' | 'bottom';
36
- type HlmStepperHeaderPosition = 'top' | 'bottom';
37
- type HlmStepperIndicatorMode = 'number' | 'state' | 'icon';
38
- interface HlmStepperConfig {
39
- animationEnabled: boolean;
40
- animationDuration: number;
41
- defaultIndicatorMode: HlmStepperIndicatorMode;
42
- }
43
- declare function provideHlmStepperConfig(config: Partial<HlmStepperConfig>): ValueProvider;
44
- declare function injectHlmStepperConfig(): HlmStepperConfig;
45
-
46
51
  declare class HlmStepHeader extends CdkStepHeader {
47
52
  private readonly _config;
48
53
  readonly state: _angular_core.InputSignal<string>;
@@ -84,6 +89,9 @@ declare class HlmStepper extends CdkStepper {
84
89
  constructor();
85
90
  next(): void;
86
91
  protected stepIcon(step: CdkStep): string | null;
92
+ protected _headerTooltip(step: CdkStep): string | TemplateRef<void> | null;
93
+ protected _headerTooltipPosition(step: CdkStep): HlmStepTooltipPosition;
94
+ protected _headerTooltipDisabled(step: CdkStep): boolean;
87
95
  protected _enterAnimationClass(): string;
88
96
  protected _leaveAnimationClass(): string;
89
97
  protected _gridContainerClass(expanded: boolean): string;
@@ -113,4 +121,4 @@ declare class HlmStepperModule {
113
121
  }
114
122
 
115
123
  export { HlmStep, HlmStepContent, HlmStepHeader, HlmStepLabel, HlmStepper, HlmStepperImports, HlmStepperModule, HlmStepperNext, HlmStepperPrevious, injectHlmStepperConfig, provideHlmStepperConfig };
116
- export type { HlmStepperConfig, HlmStepperHeaderPosition, HlmStepperIndicatorMode, HlmStepperLabelPosition };
124
+ export type { HlmStepTooltipPosition, HlmStepperConfig, HlmStepperHeaderPosition, HlmStepperIndicatorMode, HlmStepperLabelPosition };
@@ -6,7 +6,7 @@ import { hlm } from '@egose/shadcn-theme-ng-tw/utils';
6
6
  import { cva } from 'class-variance-authority';
7
7
 
8
8
  const DEFAULT_TOOLTIP_SVG_CLASS = 'bg-foreground fill-foreground z-50 block size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px]';
9
- const DEFAULT_TOOLTIP_CONTENT_CLASSES = hlm('tw:data-open:animate-in data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 tw:data-[state=delayed-open]:zoom-in-95 tw:data-closed:animate-out data-closed:fade-out-0 tw:data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:rounded-md tw:px-3 tw:py-1.5 tw:text-xs tw:bg-foreground tw:text-background tw:data-open:animate-in data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 tw:data-[state=delayed-open]:zoom-in-95 tw:data-closed:animate-out data-closed:fade-out-0 tw:data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:w-fit tw:origin-(--radix-tooltip-content-transform-origin) tw:text-balance');
9
+ const DEFAULT_TOOLTIP_CONTENT_CLASSES = hlm("tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 tw:data-[state=delayed-open]:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:rounded-md tw:px-3 tw:py-1.5 tw:text-xs tw:bg-foreground tw:text-background tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 tw:data-[state=delayed-open]:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 tw:z-50 tw:w-fit tw:origin-(--radix-tooltip-content-transform-origin) tw:text-balance");
10
10
  const tooltipPositionVariants = cva('tw:absolute', {
11
11
  variants: {
12
12
  position: {