Skip to content

🐛 fix(ui): rename disabled input to zDisabled and implement setDisabledState#494

Merged
Luizgomess merged 3 commits intomasterfrom
fix/disabled-state
Mar 24, 2026
Merged

🐛 fix(ui): rename disabled input to zDisabled and implement setDisabledState#494
Luizgomess merged 3 commits intomasterfrom
fix/disabled-state

Conversation

@mikij
Copy link
Copy Markdown
Contributor

@mikij mikij commented Mar 22, 2026

What was done? 📝

Rename the disabled input property to zDisabled across all form components
to avoid conflicts with native HTML disabled attribute. Implement proper
setDisabledState method in ControlValueAccessor interface to properly handle
disabled state changes from Angular forms.

This ensures proper integration with Angular reactive forms and prevents
issues with form validation and state management.

Screenshots or GIFs 📸

|-----Figma-----|
|-----Implementation-----|

Link to Issue 🔗

Closes #488

Type of change 🏗

  • New feature (non-breaking change that adds functionality)
  • Bug fix (non-breaking change that fixes an issue)
  • Refactor (non-breaking change that improves the code or technical debt)
  • Chore (none of the above, such as upgrading libraries)

Breaking change 🚨

Input property disabled has been renamed to zDisabled across combobox, radio, segmented, select, and toggle-group.

Checklist 🧐

  • Tested on Chrome
  • Tested on Safari
  • Tested on Firefox
  • No errors in the console

Summary by CodeRabbit

  • Breaking Changes

    • The public disabled input is renamed to zDisabled across multiple components — update templates and usages.
  • Behavior Changes

    • Disabled state now consistently blocks interaction, prevents opening, and responds to runtime enable/disable and form-control changes.
  • Documentation

    • API docs updated to reflect the zDisabled property.
  • Tests

    • Tests added/updated to cover disabled-state transitions and form-control integration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Standardizes disabled-state handling across multiple UI components by renaming public input disabledzDisabled, introducing disabledState (linkedSignal), and wiring ControlValueAccessor setDisabledState(isDisabled: boolean) so Reactive Forms-driven disabled updates are honored; tests and docs updated accordingly. (32 words)

Changes

Cohort / File(s) Summary
Combobox
libs/zard/src/lib/shared/components/combobox/combobox.component.ts, .../combobox/demo/disabled.ts, .../combobox/combobox.component.spec.ts, .../combobox/doc/api.md
Renamed input to zDisabled; added disabledState = linkedSignal(() => this.zDisabled()); updated template/tests to [zDisabled]/zDisabled(); implemented setDisabledState(isDisabled); updated demo and API doc.
Select
libs/zard/src/lib/shared/components/select/select.component.ts, .../select/select.component.spec.ts
Introduced disabledState linked signal; UI bindings and host data-disabled read from it; interaction handlers (onTriggerKeydown, toggle, open/close, selectItem) guard on disabledState(); setDisabledState(isDisabled) implemented and closes when disabling; added FormControl enable/disable tests.
Radio
libs/zard/src/lib/shared/components/radio/radio.component.ts, .../radio/doc/api.md
Input renamed to zDisabled (with booleanAttribute); template and interaction checks use disabledState(); setDisabledState updated; API doc adjusted.
Segmented / Segmented Item
libs/zard/src/lib/shared/components/segmented/segmented.component.ts, .../segmented/segmented.component.spec.ts
Per-item input renamed to zDisabled (with booleanAttribute); parent uses disabledState linkedSignal; selection and CVA setDisabledState(isDisabled) updated; tests updated to assert new signal and rendered disabled states.
Toggle Group
libs/zard/src/lib/shared/components/toggle-group/toggle-group.component.ts, .../toggle-group/toggle-group.component.spec.ts, .../toggle-group/doc/api.md
Replaced disabled with zDisabled (coerced via booleanAttribute); added disabledState = linkedSignal(() => this.zDisabled()); toggle guards and setDisabledState(isDisabled) implemented; tests/docs updated.
Tests & Docs (misc)
libs/zard/src/lib/shared/components/select/..., .../segmented/..., .../toggle-group/...
Multiple tests updated to use zDisabled and to assert reactive-form disable/enable flows; API docs updated to reflect zDisabled.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • srizzon
  • ribeiromatheuss
  • Luizgomess

Poem

⚙️ Signals hum and inputs realign,
zDisabled rises, guarding each design,
Forms whisper halt — components comply,
Tests and docs agree beneath the sky,
TypeScript sings, and buttons sleep nearby.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: renaming disabled to zDisabled and implementing setDisabledState across form components.
Description check ✅ Passed The PR description covers the main objective, links issue #488, documents the breaking change, and includes testing notes, though screenshots/GIFs are referenced but missing.
Linked Issues check ✅ Passed The PR successfully addresses issue #488 by implementing setDisabledState, introducing disabledState linked signals, and ensuring form-driven disabled state consistency across all form components.
Out of Scope Changes check ✅ Passed All changes are within scope: renaming disabled to zDisabled, implementing setDisabledState, and updating tests/docs consistently across combobox, radio, segmented, select, and toggle-group.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/disabled-state

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
libs/zard/src/lib/shared/components/select/select.component.ts (2)

109-115: ⚠️ Potential issue | 🟡 Minor

Host data-disabled binding inconsistent with button disabled state.

The trigger button uses disabledState() (line 61), but the host [attr.data-disabled] binding (line 111) still uses zDisabled(). This means when a reactive form disables the control via FormControl.disable(), the button becomes disabled but data-disabled attribute won't be set.

Per issue #488, the combined disabled state should be used consistently for the host data-disabled attribute.

🔧 Proposed fix
   host: {
     '[attr.data-active]': 'isFocus() ? "" : null',
-    '[attr.data-disabled]': 'zDisabled() ? "" : null',
+    '[attr.data-disabled]': 'disabledState() ? "" : null',
     '[attr.data-state]': 'isOpen() ? "open" : "closed"',
     '[class]': 'classes()',
     '(keydown.{enter,space,arrowdown,arrowup,escape}.prevent)': 'onTriggerKeydown($event)',
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/zard/src/lib/shared/components/select/select.component.ts` around lines
109 - 115, The host binding for data-disabled is using zDisabled() but the
component uses disabledState() for the trigger button, so update the host
attribute binding '[attr.data-disabled]' to use disabledState() instead of
zDisabled() (ensure the expression mirrors the button's disabledState() usage so
reactive form disables reflect on the host attribute); locate the host config in
the SelectComponent (the host object with '[attr.data-active]' etc.) and replace
the reference to zDisabled() with disabledState().

207-224: ⚠️ Potential issue | 🟠 Major

Missing disabled guard in onTriggerKeydown.

Per issue #488 requirements, interaction guards should use the combined disabled state. Currently, onTriggerKeydown doesn't check disabledState(), so keyboard navigation (Enter, Space, ArrowDown, ArrowUp) can still open the dropdown when the control is disabled via FormControl.disable().

🛡️ Proposed fix
   onTriggerKeydown(event: Event) {
+    if (this.disabledState()) {
+      return;
+    }
+
     const { key } = event as KeyboardEvent;
     switch (key) {
       case 'Enter':
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/zard/src/lib/shared/components/select/select.component.ts` around lines
207 - 224, Add a disabled-state guard to onTriggerKeydown so keyboard events
don't open the dropdown when the control is disabled: in the onTriggerKeydown
method, call this.disabledState() and return early if true (so
Enter/Space/ArrowDown/ArrowUp won't trigger this.open()); leave Escape handling
intact or handle it as needed after the guard. This ensures onTriggerKeydown
respects the combined disabled state.
libs/zard/src/lib/shared/components/radio/radio.component.ts (1)

1-13: ⚠️ Potential issue | 🟠 Major

Remove the linkedSignal pattern in disabledState—external disabled state must not override explicit [zDisabled] binding.

The current implementation uses linkedSignal(() => this.zDisabled()) at line 83, which tracks zDisabled() initially but breaks the link once setDisabledState(isDisabled) calls .set() at line 102. This allows form control disabled state to permanently override the component's explicit [zDisabled]="true" binding—a violation of the ControlValueAccessor contract.

Replace with a computed state that merges both sources:

  • Introduce a private disabledByForm signal to hold form-driven disabled state
  • Compute disabledState from both zDisabled() and disabledByForm() with OR logic
  • Update setDisabledState() to only mutate disabledByForm
  • Add signal to imports and remove linkedSignal (not currently imported)
Suggested fix
   forwardRef,
   inject,
   input,
-  linkedSignal,
   output,
+  signal,
   ViewEncapsulation,
   
+  private readonly disabledByForm = signal(false);
-  protected readonly disabledState = linkedSignal(() => this.zDisabled());
+  protected readonly disabledState = computed(() => this.zDisabled() || this.disabledByForm());
   
   setDisabledState(isDisabled: boolean): void {
-    this.disabledState.set(isDisabled);
+    this.disabledByForm.set(isDisabled);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/zard/src/lib/shared/components/radio/radio.component.ts` around lines 1
- 13, The linkedSignal usage for disabledState must be replaced so form-driven
disabling does not permanently override an explicit [zDisabled] binding: add a
private signal disabledByForm (use signal in imports), change disabledState to a
computed that returns this.zDisabled() || this.disabledByForm(), remove
linkedSignal and its import, and update setDisabledState(isDisabled) to only
call this.disabledByForm.set(isDisabled) (leaving zDisabled untouched); ensure
any references to disabledState, zDisabled, and setDisabledState remain
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts`:
- Around line 288-307: The tests call comboboxComponent.setDisabledState
directly which tests implementation rather than public behavior; change both
specs to drive disable/enable through the host contract (toggle the host
template's [zDisabled] input or a host-bound property) and then assert DOM-level
behavior: query the trigger button via 'button[z-button]' to assert it
becomes/clears the disabled attribute and verify overlay/trigger behavior (e.g.,
clicking the trigger does not open the popover when disabled and does open when
enabled). Replace direct calls to setDisabledState and keep using
hostFixture.detectChanges() to reflect the host property changes; reference
comboboxComponent only to locate the fixture if needed but rely on the
host-bound property and DOM interactions for assertions.

In `@libs/zard/src/lib/shared/components/combobox/combobox.component.ts`:
- Around line 12-16: The component currently breaks the linkedSignal by calling
set() on disabledState in setDisabledState(), so instead keep the linkedSignal
(disabledState) as the external/form-controlled signal from zDisabled() and add
a separate internal writable signal (e.g., inputDisabled or explicitDisabled) to
track the `@Input`() value; derive an effectiveDisabled computed signal that ORs
disabledState and inputDisabled and use that everywhere; remove any set() calls
on the linkedSignal in setDisabledState(). Also add a guard at the start of
handleSelect() to return early when effectiveDisabled is true, and ensure the
popover is closed and made non-interactive when effectiveDisabled becomes true
(e.g., close popover/open state in setDisabledState or a watcher on
effectiveDisabled) so selections and UI interaction are prevented while
disabled.

In `@libs/zard/src/lib/shared/components/segmented/segmented.component.ts`:
- Around line 38-42: Rename the input property disabled to zDisabled on the
ZardSegmentedItemComponent: change the class field from "disabled" to
"zDisabled" (preserve the same input initializer: input(false, { transform:
booleanAttribute })) and update all internal references in this file (templates,
bindings) to use zDisabled; also update any external usages, tests, and the
parent ZardSegmentedComponent or consumers that currently rely on the old
"disabled" input to use "zDisabled" so the API is consistent with
select-item/command-option conventions.

In `@libs/zard/src/lib/shared/components/select/select.component.ts`:
- Around line 633-635: Update setDisabledState in select.component.ts to close
any open dropdown when disabling: when isDisabled is true, call the component's
existing overlay/close method or detach the overlayRef (e.g., invoke the
component's close(), closeOverlay(), closePanel(), or
overlayRef.detach()/overlayRef.dispose() path) after setting disabledState so
any open overlay is closed immediately; do nothing special when isDisabled is
false. Ensure you reference and use the component's existing overlay/close APIs
rather than adding a new UI toggle.

In `@libs/zard/src/lib/shared/components/toggle-group/toggle-group.component.ts`:
- Around line 2-10: Replace linkedSignal usage with a computed combination so
form disables don't permanently override the zDisabled input: introduce a signal
like disabledByForm in the component and update setDisabledState() to set
disabledByForm, then define the component's public disabled computed via
computed(() => zDisabled() || disabledByForm()). Remove linkedSignal references
and use this computed disabled value wherever the template or internal logic
currently reads linkedSignal/disabled; keep the zDisabled input and
ControlValueAccessor.setDisabledState() behavior but ensure disabledByForm only
reflects form calls and the final state is derived by computed().

---

Outside diff comments:
In `@libs/zard/src/lib/shared/components/radio/radio.component.ts`:
- Around line 1-13: The linkedSignal usage for disabledState must be replaced so
form-driven disabling does not permanently override an explicit [zDisabled]
binding: add a private signal disabledByForm (use signal in imports), change
disabledState to a computed that returns this.zDisabled() ||
this.disabledByForm(), remove linkedSignal and its import, and update
setDisabledState(isDisabled) to only call this.disabledByForm.set(isDisabled)
(leaving zDisabled untouched); ensure any references to disabledState,
zDisabled, and setDisabledState remain consistent.

In `@libs/zard/src/lib/shared/components/select/select.component.ts`:
- Around line 109-115: The host binding for data-disabled is using zDisabled()
but the component uses disabledState() for the trigger button, so update the
host attribute binding '[attr.data-disabled]' to use disabledState() instead of
zDisabled() (ensure the expression mirrors the button's disabledState() usage so
reactive form disables reflect on the host attribute); locate the host config in
the SelectComponent (the host object with '[attr.data-active]' etc.) and replace
the reference to zDisabled() with disabledState().
- Around line 207-224: Add a disabled-state guard to onTriggerKeydown so
keyboard events don't open the dropdown when the control is disabled: in the
onTriggerKeydown method, call this.disabledState() and return early if true (so
Enter/Space/ArrowDown/ArrowUp won't trigger this.open()); leave Escape handling
intact or handle it as needed after the guard. This ensures onTriggerKeydown
respects the combined disabled state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 76cab59d-0683-43e2-a666-1367d9080f96

📥 Commits

Reviewing files that changed from the base of the PR and between a41fdd1 and e991b2e.

⛔ Files ignored due to path filters (16)
  • .gitignore is excluded by none and included by none
  • apps/web/public/components/combobox/demo/disabled.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/components/combobox/doc/api.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/components/radio/doc/api.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/components/toggle-group/doc/api.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/combobox.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/radio.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/segmented.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/select.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/toggle-group.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/combobox.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/radio.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/registry.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/segmented.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/select.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/toggle-group.json is excluded by !apps/web/public/** and included by apps/**
📒 Files selected for processing (12)
  • libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts
  • libs/zard/src/lib/shared/components/combobox/combobox.component.ts
  • libs/zard/src/lib/shared/components/combobox/demo/disabled.ts
  • libs/zard/src/lib/shared/components/combobox/doc/api.md
  • libs/zard/src/lib/shared/components/radio/doc/api.md
  • libs/zard/src/lib/shared/components/radio/radio.component.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.ts
  • libs/zard/src/lib/shared/components/select/select.component.spec.ts
  • libs/zard/src/lib/shared/components/select/select.component.ts
  • libs/zard/src/lib/shared/components/toggle-group/doc/api.md
  • libs/zard/src/lib/shared/components/toggle-group/toggle-group.component.spec.ts
  • libs/zard/src/lib/shared/components/toggle-group/toggle-group.component.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
libs/zard/src/lib/shared/components/select/select.component.ts (1)

633-637: ⚠️ Potential issue | 🟠 Major

Avoid calling close() when already closed in setDisabledState.

Line 635-637 currently calls close() unconditionally on disable; close() invokes onTouched(), which can mark the control touched even when no user interaction occurred.

🔧 Proposed fix
   setDisabledState(isDisabled: boolean): void {
     this.disabledState.set(isDisabled);
-    if (isDisabled) {
+    if (isDisabled && this.isOpen()) {
       this.close();
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/zard/src/lib/shared/components/select/select.component.ts` around lines
633 - 637, The current setDisabledState method calls close() whenever disabled,
which triggers onTouched() and can mark the control touched even if it was
already closed; update setDisabledState (which uses disabledState.set) to only
call close() when transitioning to disabled AND the component is currently open
(e.g., check the component's open state via its open flag or isOpen/isOpened
method/property) so you avoid invoking close()/onTouched unnecessarily.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts`:
- Around line 288-319: There are duplicate tests for the disabled behavior: keep
the comprehensive "disabled state" describe block (with its two it tests) and
remove the older simpler test titled "disables interaction when disabled is
true" to avoid redundancy; locate the older test by its it description and
delete it so only the newer describe('disabled state') tests remain exercising
hostComponent.disabled.set(), button.disabled, aria-expanded, and click
interaction.

In `@libs/zard/src/lib/shared/components/select/select.component.ts`:
- Line 147: The new linked signal disabledState() is not used consistently:
replace direct zDisabled() usages (including the host binding that sets
data-disabled) with disabledState() so host state reflects the linked signal,
and add guards at the start of interaction methods (onTriggerKeydown, open,
selectItem and any other user-interaction handlers) to early-return when
disabledState() is true; ensure any code paths that programmatically open/select
also check disabledState() before mutating state so reactive-form disables are
fully respected.

---

Duplicate comments:
In `@libs/zard/src/lib/shared/components/select/select.component.ts`:
- Around line 633-637: The current setDisabledState method calls close()
whenever disabled, which triggers onTouched() and can mark the control touched
even if it was already closed; update setDisabledState (which uses
disabledState.set) to only call close() when transitioning to disabled AND the
component is currently open (e.g., check the component's open state via its open
flag or isOpen/isOpened method/property) so you avoid invoking close()/onTouched
unnecessarily.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8394c2a3-984f-4735-a6c5-486c4f7decca

📥 Commits

Reviewing files that changed from the base of the PR and between e991b2e and ae510e0.

📒 Files selected for processing (4)
  • libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.ts
  • libs/zard/src/lib/shared/components/select/select.component.ts

@mikij mikij force-pushed the fix/disabled-state branch 2 times, most recently from a222de4 to 9f79739 Compare March 22, 2026 16:02
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts`:
- Around line 280-311: Add tests covering the form-driven disabled path by
creating a FormControl and binding it to the combobox via the formControl
attribute in the existing spec; call control.disable() and control.enable(), run
hostFixture.detectChanges(), and assert that the component's setDisabledState
(ControlValueAccessor) is invoked by verifying the button[z-button] becomes
disabled/undisabled and that aria-expanded stays false when disabled and becomes
true after clicking when enabled; place these cases alongside the existing
"disabled state" describe block using the same DOM queries used in the current
tests to locate the button and simulate clicks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 15d39c9c-19e6-4aa2-89e9-03b35faa8322

📥 Commits

Reviewing files that changed from the base of the PR and between ae510e0 and a222de4.

⛔ Files ignored due to path filters (4)
  • apps/web/public/installation/manual/segmented.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/select.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/segmented.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/select.json is excluded by !apps/web/public/** and included by apps/**
📒 Files selected for processing (4)
  • libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.ts
  • libs/zard/src/lib/shared/components/select/select.component.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/zard/src/lib/shared/components/select/select.component.ts (1)

382-389: ⚠️ Potential issue | 🟠 Major

Prevent form control from marking as touched when programmatically disabled.

Line 644 calls close() from setDisabledState(), which invokes onTouched() at line 388. Disabling an open select programmatically should not mark the form control as touched since no user interaction occurred.

The proposed fix is correct: add an optional markAsTouched parameter (defaulting to true) so user-triggered closures (Escape, toggle, selection, outside click) continue marking touched, while setDisabledState() calls close(false) to prevent unwanted touched state changes.

🔧 Proposed fix
-  private close() {
+  private close(markAsTouched = true) {
     if (this.overlayRef?.hasAttached()) {
       this.overlayRef.detach();
     }
     this.isOpen.set(false);
     this.focusedIndex.set(-1);
-    this.onTouched();
+    if (markAsTouched) {
+      this.onTouched();
+    }
     this.updateFocusWhenNormalMode();
   }
@@
   setDisabledState(isDisabled: boolean): void {
     this.disabledState.set(isDisabled);
     if (isDisabled && this.isOpen()) {
-      this.close();
+      this.close(false);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/zard/src/lib/shared/components/select/select.component.ts` around lines
382 - 389, The close() method currently always calls onTouched(), which causes
programmatic disabling via setDisabledState() to mark the control as touched;
change close(signature) to accept an optional boolean parameter markAsTouched =
true and only call this.onTouched() when markAsTouched is true, update
setDisabledState() to call close(false) to avoid marking touched when disabling
programmatically, and ensure all user-triggered callers of close (Escape
handler, toggle, selection, outside-click handlers) either omit the parameter or
pass true so their behavior remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/zard/src/lib/shared/components/segmented/segmented.component.spec.ts`:
- Around line 240-247: The current spec only asserts the signal value via
component.zDisabled() — update the test for ZardSegmentedComponent to exercise
behavior: disable the component via fixture.componentRef.setInput('zDisabled',
true) (and also call component.setDisabledState(true) if present) then
fixture.detectChanges(), query the rendered tab elements in the template (the
DOM nodes representing tabs) and assert they reflect the disabled state (e.g.,
have the disabled attribute or disabled CSS class) rather than only checking the
zDisabled signal; likewise add the inverse check when enabling to ensure
toggling works.

---

Outside diff comments:
In `@libs/zard/src/lib/shared/components/select/select.component.ts`:
- Around line 382-389: The close() method currently always calls onTouched(),
which causes programmatic disabling via setDisabledState() to mark the control
as touched; change close(signature) to accept an optional boolean parameter
markAsTouched = true and only call this.onTouched() when markAsTouched is true,
update setDisabledState() to call close(false) to avoid marking touched when
disabling programmatically, and ensure all user-triggered callers of close
(Escape handler, toggle, selection, outside-click handlers) either omit the
parameter or pass true so their behavior remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: de85d6dd-cb45-4dfe-a861-bfa64756af6f

📥 Commits

Reviewing files that changed from the base of the PR and between a222de4 and 9f79739.

⛔ Files ignored due to path filters (4)
  • apps/web/public/installation/manual/segmented.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/select.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/segmented.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/select.json is excluded by !apps/web/public/** and included by apps/**
📒 Files selected for processing (4)
  • libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.ts
  • libs/zard/src/lib/shared/components/select/select.component.ts

@mikij mikij force-pushed the fix/disabled-state branch 2 times, most recently from 11b8a3b to 17e9c18 Compare March 22, 2026 16:37
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/zard/src/lib/shared/components/segmented/segmented.component.spec.ts`:
- Around line 243-271: Test misplacement: the spec is exercising
ZardSegmentedComponent APIs (zOptions input, setDisabledState(), querying
button[role="tab"]) while the file is for ZardSegmentedItemComponent which only
exposes item-level inputs and ng-content. Fix by either moving this entire
"should handle disabled state" test into the ZardSegmentedComponent spec suite
(so it can use zOptions, setDisabledState, and assert rendered buttons) or
rewrite the test to only assert ZardSegmentedItemComponent behavior: set item
inputs (value, label, zDisabled), render the component (or projected content via
ng-content), assert zDisabled() state and that the component's template/host
reflects the disabled input appropriately; remove any references to zOptions,
setDisabledState, and button[role="tab"] when keeping the test in the item spec.

In `@libs/zard/src/lib/shared/components/select/select.component.ts`:
- Line 147: Add an effect that watches the combined disabled signal
(disabledState) and, when it becomes true, closes the open overlay using the
internal/non-touching close path rather than the public close() so onTouched()
is not fired; locate where setDisabledState() currently closes the panel and
mirror that non-touch close behavior in the new effect (subscribe to
disabledState and invoke the same internal close helper used by setDisabledState
instead of close()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e2b68315-e923-4942-bf1f-f06d99851440

📥 Commits

Reviewing files that changed from the base of the PR and between 9f79739 and 11b8a3b.

⛔ Files ignored due to path filters (5)
  • .gitignore is excluded by none and included by none
  • apps/web/public/installation/manual/segmented.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/installation/manual/select.md is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/segmented.json is excluded by !apps/web/public/** and included by apps/**
  • apps/web/public/r/select.json is excluded by !apps/web/public/** and included by apps/**
📒 Files selected for processing (4)
  • libs/zard/src/lib/shared/components/combobox/combobox.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.spec.ts
  • libs/zard/src/lib/shared/components/segmented/segmented.component.ts
  • libs/zard/src/lib/shared/components/select/select.component.ts

@mikij mikij force-pushed the fix/disabled-state branch from 17e9c18 to 7584ad1 Compare March 22, 2026 16:51
Signed-off-by: Mickey Lazarevic <lazarevic.miroslav@gmail.com>
@Luizgomess Luizgomess merged commit 2b347af into master Mar 24, 2026
9 checks passed
@Luizgomess Luizgomess deleted the fix/disabled-state branch March 24, 2026 23:25
Luizgomess pushed a commit that referenced this pull request Mar 27, 2026
…edState (#494)

Signed-off-by: Mickey Lazarevic <lazarevic.miroslav@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] z-select remains interactive when disabled via formControlName Reactive Forms

2 participants