Skip to content

fix(autocomplete)!: errors following a full review of the component (#DS-5482) - #1985

Open
artembelik wants to merge 4 commits into
mainfrom
fix/autocomplete-signals
Open

fix(autocomplete)!: errors following a full review of the component (#DS-5482)#1985
artembelik wants to merge 4 commits into
mainfrom
fix/autocomplete-signals

Conversation

@artembelik

Copy link
Copy Markdown
Contributor

What

A full review of autocomplete, in the same shape as the 20.3.0 component reviews. Four accessor inputs and one write-target input survived the automated signal migration, across the panel and its trigger.

classList was an @Input('class') that accumulated

set classList(value: string) {
    if (value && value.length) {
        const classList = { ...this._classList };
        value.split(' ').forEach((c) => (classList[c.trim()] = true));
        this._classList = classList;
        this.elementRef.nativeElement.className = '';
    }
}

It mixed three concerns: the consumer's classes, the internal kbq-autocomplete_visible / _hidden pair, and cdk-keyboard-focused. Those are three signals and one computed now, behind a class signal input. class="…" on <kbq-autocomplete> keeps working exactly as before, including the host-clearing side effect.

isOpen disagreed with itself

get isOpen() { return this._isOpen && this.showPanel; }
set isOpen(value) { this._isOpen = value; }

Writing true and reading it back returned false whenever the panel had no options. It is a computed now — attached() && showPanel() — with attached as the writable half the trigger owns, which is what the setter always meant.

The rest

  • displayWith, autoActiveFirstOption, openOnFocus and the trigger's kbqAutocompleteDisabled are signal inputs. The last three gained booleanAttribute in place of coerceBooleanProperty or nothing.
  • The panel id comes from the CDK _IdGenerator instead of a module-level counter. It is the value of the trigger's aria-owns, so the shape changes from kbq-autocomplete-1 to kbq-autocomplete-a1.
  • setVisibility, emitSelectEvent, onKeydown, setScrollTop, getScrollTop, keyManager and showPanel are marked @docs-private: they are the trigger's interface to the panel, not the consumer's.

Deliberately left alone

options stays a QueryList content query. ActiveDescendantKeyManager takes one directly, and subscribeToClosingActions rebuilds the panel-closing stream off options.changes — a signal query would mean a toObservable per panel open, with the effect outliving the subscription. Not worth the risk in this PR.

Migration

autocomplete-signals runs from ng update @koobiq/components@20. It rewrites reads on receivers typed KbqAutocomplete or KbqAutocompleteTrigger and reports the rest.

It also reports a gotcha worth calling out: binding [autoActiveFirstOption] overrides KBQ_AUTOCOMPLETE_DEFAULT_OPTIONS even when the bound value is undefined. The token default only applies to an input nobody bound. This one is easy to hit when converting a field write into a binding — it caught me while updating the spec.

Documented in docs/guides/migration.{en,ru}.md, section 18.

Testing

  • autocomplete.spec.ts: 122 → 123 tests, all existing ones kept.
    • Six tests mutated inputs on the instance. Each now binds from its host — and deliberately not on the shared SimpleAutocomplete, because a bound [autoActiveFirstOption] there silently defeats the test that checks KBQ_AUTOCOMPLETE_DEFAULT_OPTIONS. Preselection has its own host now, plus a new test that the input really does override the token.
    • Fixed a pre-existing type error the migration surfaced: the openOnFocus describe declared ComponentFixture<AutocompleteWithDisabledItems> while creating AutocompleteWithOpenOnFocus.
  • autocomplete-signals/index.spec.ts: 16 tests, covering both receiver types.
  • Full packages/components (4994 tests) and packages/schematics (450 tests) suites pass.
  • check-api is in sync.

BREAKING CHANGE

🤖 Generated with Claude Code

Four accessor inputs and one write-target input survived the automated signal
migration, on the panel and its trigger.

`classList` was the odd one: declared `@Input('class')`, its setter accumulated
class names into an object the panel template binds, and cleared the host's
`className` as a side effect. It is an internal `computed` now, fed by a `class`
signal input — `class="…"` on `<kbq-autocomplete>` keeps working as before.

`isOpen` was asymmetric: the setter stored a flag while the getter returned that
flag *and* `showPanel`, so writing `true` and reading it back returned `false`
whenever the panel had no options. It is a `computed` over `attached()` and
`showPanel()` now, with `attached` as the writable half the trigger owns.

`autoActiveFirstOption`, `openOnFocus` and `kbqAutocompleteDisabled` gained
`booleanAttribute`, and the panel id comes from the CDK `_IdGenerator` — it is the
value of the trigger's `aria-owns`, so its shape changes from
`kbq-autocomplete-1` to `kbq-autocomplete-a1`.

`options` stays a `QueryList` content query: `ActiveDescendantKeyManager` and the
panel-closing stream both rely on its `changes` semantics.

BREAKING CHANGE: `KbqAutocomplete.displayWith`, `autoActiveFirstOption`,
`openOnFocus`, `showPanel` and `isOpen` are signals, `isOpen` is read-only,
`classList` is internal, and `KbqAutocompleteTrigger.autocompleteDisabled` is a
signal input. Generated panel ids changed shape. Reported and partly rewritten by
the `autocomplete-signals` schematic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added bug Something isn't working breaking changes labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 4c25493):

https://koobiq-next--prs-1985-khe5ircb.web.app

(expires Mon, 07 Sep 2026 12:58:10 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c

@lskramarov lskramarov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code review — max effort

Ten finder angles plus a gap sweep over the component, the trigger, the spec and the new autocomplete-signals schematic. 15 findings, most severe first. Verified against the installed @angular/core 20.3.29 / @angular/cdk sources and the PR’s own CI logs.

Headline: the api check is red because tools/check-public-api-any/baseline.json was not re-approved (autocomplete: 9 → 12), and KbqAutocomplete.classList now throws on a non-string [class] binding that the old accessor silently ignored. The rest are in the schematic — it rewrites consumers’ source, so a wrong rewrite is worth more than a missed one.

The unit_tests failure is not this PR: tree-selection.component.spec.ts:1666 is already fixed on main by 0f6a3d9. Rebase.

🤖 Generated with Claude Code

Comment thread packages/components/autocomplete/autocomplete.component.ts
Comment thread tools/public_api_guard/components/autocomplete.api.md
Comment thread packages/schematics/src/migrations/autocomplete-signals/index.ts Outdated
Comment thread packages/schematics/src/migrations/autocomplete-signals/index.ts Outdated
Comment thread packages/schematics/src/migrations/autocomplete-signals/data.ts Outdated
Comment thread packages/schematics/src/migrations/autocomplete-signals/index.ts Outdated
Comment thread packages/schematics/src/migrations/autocomplete-signals/index.ts Outdated
Comment thread packages/schematics/src/migrations/autocomplete-signals/index.ts Outdated
Comment thread packages/schematics/src/migrations/autocomplete-signals/data.ts
Comment thread packages/schematics/src/migrations/autocomplete-signals/index.ts Outdated

@lskramarov lskramarov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up: gap sweep

A final pass over the diff, run against a real build of the PR head, turned up three more. Two of them land in the same SUMMARY array as earlier comments — with this one, all three notes ng update prints are factually wrong.

Things the sweep checked and found clean, for the record: api-extractor onlyCheck (the .api.md guard itself is up to date — the red api job is the separate check-public-api-any ratchet), prettier, cspell and eslint --max-warnings=0 on all 15 changed files, and jest on autocomplete.spec.ts (123), tags (222) and autocomplete-signals/index.spec.ts (16). should let the input override the token is not vacuous, zone is correctly reassigned after TestBed.resetTestingModule(), panel ids are unique and stable per instance, and dynamic [class] does correctly add and remove classes on the panel.

🤖 Generated with Claude Code

Comment thread packages/schematics/src/migrations/autocomplete-signals/data.ts Outdated
Comment thread packages/schematics/src/migrations/autocomplete-signals/data.ts Outdated
Comment thread docs/guides/migration.en.md Outdated
Classes on the panel used to accumulate: the old setter merged each `class` value
into an object it never cleared, so a `[class]` binding that changed from "a" to
"b" left the panel with both. The computed rebuilds the map from the current
value, which is right — but the change was undocumented and untested, because the
existing test binds a fixed class that passes either way.

The schematic never counted a template-only consumer, so none of the three
summary notes reached the audience they were written for.

`attached.set((this.overlayAttached = …))` hid an assignment inside an argument,
twice, with opposite values.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@artembelik artembelik self-assigned this Sep 4, 2026
artembelik and others added 2 commits September 4, 2026 15:33
Same four registry conflicts as the other review branches, at the append point
main's six component-review PRs share with this one:

- migrations.json / collection.json: kept main's entries and appended
  autocomplete-signals after them.
- migration.en.md / migration.ru.md: kept main's Popover subsection and put
  Autocomplete before it, so section 18 stays alphabetical.

Two things the merge surfaced rather than caused:

- The section intro carried the "two waves" wording that the badge review
  rejected: it listed search-expandable, split-button and title as first-wave
  while they also have subsections below. Replaced with the wording agreed
  there.
- `check-public-api-any` counted autocomplete 9 -> 12. The three are the
  `unknown` transform type of the `booleanAttribute` inputs autoActiveFirstOption,
  openOnFocus and autocompleteDisabled, so the baseline records them.

main's Safari fix (preventScroll on the input refocus) merged into the trigger
cleanly; its `isOpen` chained assignment is this branch's `attached` signal now,
and the 124 autocomplete specs pass on the result.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@artembelik
artembelik marked this pull request as ready for review September 4, 2026 13:07
Copilot AI lite review requested due to automatic review settings September 4, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new autocomplete-signals migration can silently miss namespace-qualified type references (e.g. ns.KbqAutocomplete) and also fail to report them as unresolved, leaving broken consumer code without warnings.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR completes a full “component review” pass for autocomplete, finishing the signal-based API migration for both KbqAutocomplete (panel) and KbqAutocompleteTrigger (trigger), and adds an autocomplete-signals schematic to migrate consumer code (TS + templates) automatically as part of ng update.

Changes:

  • Finalizes the KbqAutocomplete/KbqAutocompleteTrigger public surface: remaining accessor inputs become signal inputs; isOpen becomes a computed over new trigger-owned attached state; panel IDs switch to CDK _IdGenerator.
  • Reworks how host class is handled: accepts full Angular [class] input shapes via transform, computes internal class map, and preserves host-class clearing behavior.
  • Adds a new autocomplete-signals migration schematic + tests, updates docs and API snapshots accordingly.
File summaries
File Description
tools/public_api_guard/components/autocomplete.api.md Updates public API snapshot for signalized inputs and new attached/isOpen/hostClass surface.
tools/check-public-api-any/baseline.json Adjusts any-usage baseline for the updated autocomplete API surface.
packages/schematics/src/migrations/autocomplete-signals/schema.ts Adds schematic option typings (project + fix).
packages/schematics/src/migrations/autocomplete-signals/schema.json Adds schematic schema (including fix defaulting behavior).
packages/schematics/src/migrations/autocomplete-signals/README.md Documents what the migration rewrites, warns about, and how to run it.
packages/schematics/src/migrations/autocomplete-signals/index.ts Implements the migration: TS receiver-based rewrites + template ref rewrites + warnings/summary.
packages/schematics/src/migrations/autocomplete-signals/index.spec.ts Adds tests covering migration rewriting and warning behavior.
packages/schematics/src/migrations/autocomplete-signals/data.ts Centralizes migration member lists, warning patterns, and summary notes.
packages/schematics/src/migrations.json Registers autocomplete-signals for ng update @koobiq/components@20 (20.3.0-0).
packages/schematics/src/collection.json Exposes autocomplete-signals as a runnable schematic with schema.
packages/components/autocomplete/autocomplete.spec.ts Updates tests for new signals API, adds coverage for new class-handling semantics and token-vs-binding behavior.
packages/components/autocomplete/autocomplete.html Updates panel template binding to use computed classList() signal result.
packages/components/autocomplete/autocomplete.component.ts Implements reviewed autocomplete panel changes: signal inputs, computed state, _IdGenerator id, class normalization/transfer.
packages/components/autocomplete/autocomplete-trigger.directive.ts Migrates trigger to signal inputs, uses attached.set(...), and updates reads (openOnFocus(), showPanel(), etc.).
docs/guides/migration.ru.md Adds an Autocomplete subsection to the 20.3.0 component review migration guide (RU).
docs/guides/migration.en.md Adds an Autocomplete subsection to the 20.3.0 component review migration guide (EN).
Review details

Suppressed comments (1)

packages/schematics/src/migrations/autocomplete-signals/index.ts:436

  • collectUnresolvedMentions only reports unresolved TypeReferenceNodes when the type name is a bare Identifier. Namespace-qualified mentions like ns.KbqAutocomplete | undefined won’t be reported, so the migration can leave broken reads behind without any warning.
        if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && typeNames.includes(node.typeName.text)) {
            if (!resolved.has(node)) report(node);
        } else if (
  • Files reviewed: 16/16 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +122 to +125
/** Whether a type annotation refers to `typeName` directly (not through a union, array or type argument). */
function isTypeReference(type: ts.TypeNode | undefined, typeName: string): boolean {
return !!type && ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName) && type.typeName.text === typeName;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking changes bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants