Skip to content

fix: correct chip behaviour in MultiSelect and AutoComplete - #1652

Open
Ionaru wants to merge 6 commits into
openng-org:mainfrom
Ionaru:fix/chip-consumer-bugs
Open

Ionaru wants to merge 6 commits into
openng-org:mainfrom
Ionaru:fix/chip-consumer-bugs

Conversation

@Ionaru

@Ionaru Ionaru commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes 4 chip-related bugs in MultiSelect and AutoComplete, plus 1 bug found while working on the code:

  • MultiSelect chip icons (MultiSelect: Incorrect Chip Icons #399). #chipicon was projected into the chip's remove slot, so an icon meant for the label replaced the close button, and the chip's own icon slot was unreachable. #chipicon now renders as chip content and a new #chipremoveicon owns the remove slot. New chipRemoveIcon input deprecates the misnamed chipIcon, whose behaviour is unchanged. removetokenicon still works; its deprecation now points at chipremoveicon instead of chipicon.
  • AutoComplete separator (Autocomplete - Inconsistencies with Chips features #267). The separator key was only consumed on the success path and the input was never split. Pressing , on an empty field inserted the character (repeats built ,, ,,, ,,,), pressing it on an existing chip left the text in the field, and ,foo became one chip named ",foo". The separator is now always swallowed, the text is split and trimmed, and a batch is deduplicated against itself when unique is set. No new input; unique already covers this.
  • AutoComplete readonly chips (found bug). [removable]="true" was hardcoded, so readonly fields rendered a remove icon that hid the chip while the model kept the value. Now derived from readonly and the disabled state, matching MultiSelect.
  • AutoComplete chip list name (Autocomplete multiple accessibility #625). The multiple-mode <ul role="listbox"> had no accessible name (axe: "ARIA input fields must have an accessible name"). Adds an aria.selectedItems locale key defaulting to "Selected Items".
  • MultiSelect selection limit (Multiselect: When using Chip and hitting the limit of selectionLimit, Chip close/unselect button becomes disabled #810). Already correct, added a test to lock it down.

Related issues

Fixes #399
Fixes #267
Closes #810 (already correct; adds the regression test)
Relates to #625. The fix doesn't fully cover the reported bug, but that would require sweeping changes to ARIA in the library,

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes the public API)
  • Documentation only
  • Refactor, test, or chore (no user-facing change)

Breaking changes

#chipicon / pTemplate="chipicon" on MultiSelect now render inside the chip next to the label instead of replacing the remove icon. Fails silently: anyone using it as a close button gets two icons per chip.

Migration is a rename, not a rewrite. Rename rather than duplicate, or the icon renders twice:

  • <ng-template #chipicon> to <ng-template #chipremoveicon>
  • pTemplate="chipicon" to pTemplate="chipremoveicon"

Smaller behaviour changes: readonly/disabled AutoComplete now renders zero .p-chip-remove-icon nodes; under [unstyled], the chip-icon template context class is now empty instead of leaking p-multiselect-chip-icon, so [class]="class + ' pi pi-times'" should become class="pi pi-times" [ngClass]="class".

None

Test plan

  • pnpm run build:lib
  • pnpm run test:unit (vitest)
  • pnpm run lint (broken repo-wide, unrelated to this PR)
  • pnpm run format:check
  • Verified in the demo app

Checklist

  • Issue discussed or bug clearly described (link issue when applicable)
  • Tests added or updated for behavioral changes
  • Documentation updated (README, JSDoc, migration notes as needed)
  • Public API changes documented; breaking changes called out
  • CHANGELOG updated (repository has no CHANGELOG)
  • Commit messages follow Conventional Commits
  • I agree to follow the OpenNG Foundation Code of Conduct

Ionaru added 6 commits August 29, 2026 00:21
Reaching `selectionLimit` used to disable the close button on every rendered
chip, so a user could not undo a selection without first clearing the field.
That is no longer the case here: `maxSelectionLimitReached()` feeds only
`isOptionDisabled()`, which gates unselected options in the dropdown, and the
chip is bound to `[removable]="!$disabled() && !readonly"` with no limit check
anywhere near it or in `removeOption()`.

Nothing guarded that, though. The existing coverage only asserts what
`maxSelectionLimitReached()` returns and never renders a chip, so wiring the
limit back into the chip's `removable` or `disabled` binding would go
unnoticed. The new test fills the field to the limit, checks the remove icons
are still rendered and focusable, and removes one through a real click.

Two selections rather than three: the shared host binds `maxSelectedLabels` to
3, at which point the chips collapse into the summary label.

Fixes openng-org#810
The chip hardcoded `[removable]="true"`, so the remove icon rendered even when
the component was readonly. Clicking it discarded the chip without discarding
the value: the projected icon's own handler is guarded by `!readonly`, so it
no-ops and never calls `stopPropagation()`, the click then reaches the wrapper
Chip renders around the remove slot, and `close()` sets `visible` to false. The
chip style turns that signal into `display: none`, so the entry vanished from
the field while the model kept it.

`removable` is now derived from `readonly` and the disabled state, which is
what MultiSelect already does. The `(onRemove)` guard goes with it: the output
can no longer fire in either state.

Only reachable when readonly. Disabled was already safe, because `.p-disabled`
sets `pointer-events: none` on its descendants.
In multiple mode the chips live in a `<ul role="listbox">` that carried no
`aria-label` and no `aria-labelledby`, so axe reported "ARIA input fields must
have an accessible name" and screen readers announced it as an unnamed listbox.
The suggestions list next to it has been labelled with `aria.listLabel` all
along; only the chip container was missed.

It gets its own key rather than borrowing one. Reusing `ariaLabel` would give
the combobox and the chip list the same name, and reusing `aria.listLabel`
would put "Option List" on two different listboxes in one component, so
`aria.selectedItems` is added to the locale API and defaults to "Selected
Items".

The nested-interactive half of the report is untouched: the text input carries
`role="combobox"` inside an `<li role="option">`, and separating them means
giving up either the chip `option` semantics or the single listbox that
`aria-activedescendant` navigation is built on. That needs an ARIA pattern
rework, not an attribute.

Fixes openng-org#625
The separator is a delimiter, but it was only treated as one on the path that
actually added a value. `handleSeparatorKey` called `preventDefault()` and
cleared the input inside `if (inputValue && !isSelected(inputValue))`, so
pressing it on an empty field or on a value that is already a chip let the
character through into the input. `onInputPaste` had the same shape, guarded on
whether the model grew, so pasting only-duplicate text left the raw string with
its separators sitting in the field.

Neither entry point split on the separator either. Both added the trimmed input
verbatim, which is why a field reading `,foo` became one chip `",foo"`, and why
repeatedly pressing the separator on an empty field produced `","`, `",,"`,
`",,,"` instead of nothing.

Both now bail out early when this is not separator mode, then unconditionally
swallow the event, split the text, add whatever is new, and clear the input.
Splitting and adding is shared by the key and paste paths, so a paste of `a,a`
is deduplicated within the batch as well as against the model when `unique` is
set, which it was not before.

No new input: `unique` already expresses whether duplicates are allowed.

Fixes openng-org#267
…tely

A `#chipicon` template was projected into the chip's remove slot, so an icon
meant to sit next to the label replaced the close button instead. The name
suggested one thing and the markup did another, and there was no way to reach
the chip's own icon slot at all.

`chipicon` now renders as chip content and a new `chipremoveicon` template
feeds the remove slot. `removetokenicon` keeps working and is re-pointed at
`chipremoveicon`; its deprecation used to name `chipicon`, which is the
opposite of what it should have said.

The `chipIcon` string input keeps styling the remove icon so nothing breaks
today, but it is deprecated in favour of the new `chipRemoveIcon`, which wins
when both are set. Re-pointing `chipIcon` at the chip's icon is left for a
major.

Two things go with the rewrite. The projected remove icon no longer carries its
own click handler: the wrapper Chip already renders calls `close()`, which
emits `onRemove`, so removal still happens exactly once and now takes the same
path as the keyboard. And the outlet context passes `cx('chipIcon')` instead of
a hardcoded class, so it survives `unstyled`.

The pass-through options gain the `chipIcon` entry the template has been using
untyped.

BREAKING CHANGE: `#chipicon` and `pTemplate="chipicon"` now render inside the
chip next to its label instead of replacing the remove icon. Use
`chipremoveicon` to customize the remove icon.

Fixes openng-org#399
The separator rewrite cleared the multiple-mode input on every paste, so a
paste whose values were all duplicates discarded whatever the user had
already typed. Pasting is not a commit: only the pasted text is parsed, so
there is no reason for it to consume unrelated input.

The input is now cleared only once the paste has actually added something.
`preventDefault` stays unconditional, so pasted separators still never reach
the field, which is what the original fix was for.

The existing test set the field to the same string it pasted, which made the
wipe look correct; it now pastes into an empty field, and two new tests cover
a paste that adds nothing next to one that does.

Fixes openng-org#267
@Ionaru Ionaru changed the title Fix/chip consumer bugs fix!: correct chip behaviour in MultiSelect and AutoComplete Aug 31, 2026
@Ionaru Ionaru changed the title fix!: correct chip behaviour in MultiSelect and AutoComplete fix: correct chip behaviour in MultiSelect and AutoComplete Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant