Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(list): add selected property to the list-item component #15414

Merged
merged 5 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Ignite UI for Angular Change Log

All notable changes for each version of this project will be documented in this file.
## 19.1.1
### New Features
- IgxListItem
- Added a new `selected` input property, making it easier to indicate when a list item is selected by applying styling responsible for that state.

## 19.1.0
### General
- `IgxCarousel`
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"@types/source-map": "0.5.2",
"express": "^4.21.1",
"fflate": "^0.8.1",
"igniteui-theming": "^15.0.0",
"igniteui-theming": "^15.1.1",
"igniteui-trial-watermark": "^3.0.2",
"lodash-es": "^4.17.21",
"rxjs": "^7.8.0",
Expand Down
2 changes: 1 addition & 1 deletion projects/igniteui-angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"tslib": "^2.3.0",
"igniteui-trial-watermark": "^3.0.2",
"lodash-es": "^4.17.21",
"igniteui-theming": "^15.0.0",
"igniteui-theming": "^15.1.1",
"@igniteui/material-icons-extended": "^3.1.0"
},
"peerDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
@extend %igx-list-item-base--active !optional;
}

// css class `igx-list__item-base--selected
@include e(item-base, $m: selected) {
@extend %igx-list-item-base--selected !optional;
}

// css class 'igx-list__item-right' applied to the panning container shown when the list item is panned left
@include e(item-right) {
@extend %igx-list-item-pan !optional;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,27 @@
/// @param {Color} $item-background [null] - The list item background color.
/// @param {Color} $item-background-hover [null] - The list item hover background color.
/// @param {Color} $item-background-active [null] - The active list item background color.
/// @param {Color} $item-background-selected [null] - The selected list item background color.
/// @param {Color} $item-text-color [null] - The list item text color.
/// @param {Color} $item-text-color-hover [null] - The list item hover text color.
/// @param {Color} $item-text-color-active [null] - The active list item text color.
/// @param {Color} $item-text-color-selected [null] - The selected list item text color.
/// @param {Color} $item-title-color [null] - The list item title color.
/// @param {Color} $item-title-color-hover [null] - The list item hover title color.
/// @param {Color} $item-title-color-active [null] - The active list item title color.
/// @param {Color} $item-title-color-selected [null] - The selected list item title color.
/// @param {Color} $item-subtitle-color [null] - The list item subtitle color.
/// @param {Color} $item-subtitle-color-hover [null] - The list item hover subtitle color.
/// @param {Color} $item-subtitle-color-active [null] - The active list item subtitle color.
/// @param {Color} $item-subtitle-color-selected [null] - The selected list item subtitle color.
/// @param {Color} $item-action-color [null] - The list item action color.
/// @param {Color} $item-action-color-hover [null] - The list item hover action color.
/// @param {Color} $item-action-color-active [null] - The active list item action color.
/// @param {Color} $item-action-color-selected [null] - The selected list item action color.
/// @param {Color} $item-thumbnail-color [null] - The list item thumbnail color.
/// @param {Color} $item-thumbnail-color-hover [null] - The list item hover thumbnail color.
/// @param {Color} $item-thumbnail-color-active [null] - The active list item thumbnail color.
/// @param {Color} $item-thumbnail-color-selected [null] - The selected list item thumbnail color.
/// @param {List} $border-radius [null] - The border radius used for list component.
/// @param {List} $item-border-radius [null] - The border radius used for list item.
/// @param {Color} $border-width [null] - The list border width.
Expand All @@ -54,21 +60,27 @@
$item-background: null,
$item-background-hover: null,
$item-background-active: null,
$item-background-selected: null,
$item-text-color: null,
$item-text-color-hover: null,
$item-text-color-active: null,
$item-text-color-selected: null,
$item-title-color: null,
$item-title-color-hover: null,
$item-title-color-active: null,
$item-title-color-selected: null,
$item-subtitle-color: null,
$item-subtitle-color-hover: null,
$item-subtitle-color-active: null,
$item-subtitle-color-selected: null,
$item-action-color: null,
$item-action-color-hover: null,
$item-action-color-active: null,
$item-action-color-selected: null,
$item-thumbnail-color: null,
$item-thumbnail-color-hover: null,
$item-thumbnail-color-active: null,
$item-thumbnail-color-selected: null,
$border-color: null,
$border-width: null,
) {
Expand Down Expand Up @@ -120,6 +132,16 @@
}
}

@if not($item-background-selected) and $item-background {
@if meta.type-of($item-background) == 'color' {
@if luminance($item-background) < .5 {
$item-background-selected: color.scale($item-background, $lightness: 8%);
} @else {
$item-background-selected: color.scale($item-background, $lightness: -8%);
}
}
}

@if not($header-text-color) and $header-background {
$header-text-color: text-contrast($header-background);
}
Expand Down Expand Up @@ -180,28 +202,54 @@
$item-text-color-active: text-contrast($item-background-active);
}

@if not($item-text-color-selected) and $item-background-selected {
$item-text-color-selected: text-contrast($item-background-selected);
}

@if not($item-title-color-active) and $item-background-active {
$item-title-color-active: text-contrast($item-background-active);
}

@if not($item-title-color-selected) and $item-background-selected {
$item-title-color-selected: text-contrast($item-background-selected);
}

@if not($item-action-color-active) and $item-background-active {
$item-action-color-active: text-contrast($item-background-active);
}

@if not($item-action-color-selected) and $item-background-selected {
$item-action-color-selected: text-contrast($item-background-selected);
}

@if not($item-thumbnail-color-active) and $item-background-active {
$item-thumbnail-color-active: text-contrast($item-background-active);
}

@if not($item-thumbnail-color-selected) and $item-background-selected {
$item-thumbnail-color-selected: text-contrast($item-background-selected);
}

@if not($item-subtitle-color-active) and $item-background-active {
@if meta.type-of($item-background-active) == 'color' {
$item-subtitle-color-active: rgba(text-contrast($item-background-active), .74);
}
}

@if not($item-subtitle-color-selected) and $item-background-selected {
@if meta.type-of($item-background-selected) == 'color' {
$item-subtitle-color-selected: rgba(text-contrast($item-background-selected), .74);
}
}

@if not($item-subtitle-color-active) and $item-text-color-active {
$item-subtitle-color-active: $item-text-color-active;
}

@if not($item-subtitle-color-selected) and $item-text-color-selected {
$item-subtitle-color-selected: $item-text-color-selected;
}

@return extend($theme, (
name: $name,
border-radius: $border-radius,
Expand All @@ -212,21 +260,27 @@
item-background: $item-background,
item-background-hover: $item-background-hover,
item-background-active: $item-background-active,
item-background-selected: $item-background-selected,
item-text-color: $item-text-color,
item-text-color-hover: $item-text-color-hover,
item-text-color-active: $item-text-color-active,
item-text-color-selected: $item-text-color-selected,
item-title-color:$item-title-color,
item-title-color-hover:$item-title-color-hover,
item-title-color-active:$item-title-color-active,
item-title-color-selected:$item-title-color-selected,
item-subtitle-color: $item-subtitle-color,
item-subtitle-color-hover: $item-subtitle-color-hover,
item-subtitle-color-active: $item-subtitle-color-active,
item-subtitle-color-selected: $item-subtitle-color-selected,
item-action-color: $item-action-color,
item-action-color-hover: $item-action-color-hover,
item-action-color-active: $item-action-color-active,
item-action-color-selected: $item-action-color-selected,
item-thumbnail-color: $item-thumbnail-color,
item-thumbnail-color-hover: $item-thumbnail-color-hover,
item-thumbnail-color-active: $item-thumbnail-color-active,
item-thumbnail-color-selected: $item-thumbnail-color-selected,
border-color: $border-color,
border-width: $border-width,
theme: map.get($schema, '_meta', 'theme'),
Expand Down Expand Up @@ -423,6 +477,12 @@
}
}

%igx-list-item-base--selected {
%igx-list-item-content {
@extend %igx-list-item-content--selected;
}
}

%igx-list-item-pan {
position: absolute;
visibility: hidden;
Expand Down Expand Up @@ -550,6 +610,37 @@
}
}

%igx-list-item-content--selected {
color: var-get($theme, 'item-text-color-selected');
background: var-get($theme, 'item-background-selected');
z-index: 3;

%igx-list__item-line-title {
color: var-get($theme, 'item-title-color-selected')
}

%igx-list__item-line-subtitle {
color: var-get($theme, 'item-subtitle-color-selected')
}

%igx-list__item-actions {
color: var-get($theme, 'item-action-color-selected');

igx-icon,
igc-icon {
color: var-get($theme, 'item-action-color-selected')}
}

%igx-list__item-thumbnail {
color: var-get($theme, 'item-thumbnail-color-selected');

igx-icon,
igc-icon {
color: var-get($theme, 'item-thumbnail-color-selected')
}
}
}

%igx-list-item-content--inactive {
transition: transform .3s $out-quad;
}
Expand Down
25 changes: 25 additions & 0 deletions projects/igniteui-angular/src/lib/list/list-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class IgxListItemComponent implements IListChild {
private lastPanDir = IgxListPanState.NONE;

private _role: string = '';
private _selected = false;;

/**
* Gets the `panState` of a `list item`.
Expand Down Expand Up @@ -281,6 +282,30 @@ export class IgxListItemComponent implements IListChild {
this._role = val;
}

/**
* Sets/gets whether the `list item` is selected.
* Selection is only applied to non-header items.
* When selected, the CSS class 'igx-list__item-base--selected' is added to the item.
* ```html
* <igx-list-item [selected]="true">Selected Item</igx-list-item>
* ```
* ```typescript
* let isSelected = this.listItem.selected;
* this.listItem.selected = true;
* ```
*
* @memberof IgxListItemComponent
*/
@HostBinding('class.igx-list__item-base--selected')
@Input({ transform: booleanAttribute })
public get selected() {
return this._selected && !this.isHeader;
}

public set selected(value: boolean) {
this._selected = value;
}

/**
* Indicates whether `list item` should have header style.
* ```typescript
Expand Down
40 changes: 38 additions & 2 deletions projects/igniteui-angular/src/lib/list/list.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
ListWithIgxForAndScrollingComponent,
TwoHeadersListComponent,
TwoHeadersListNoPanningComponent,
ListDirectivesComponent
ListDirectivesComponent,
ListWithSelectedItemComponent
} from '../test-utils/list-components.spec';
import { configureTestSuite } from '../test-utils/configure-suite';
import { wait } from '../test-utils/ui-interactions.spec';
Expand All @@ -44,7 +45,8 @@ describe('List', () => {
TwoHeadersListNoPanningComponent,
ListWithPanningTemplatesComponent,
ListWithIgxForAndScrollingComponent,
ListDirectivesComponent
ListDirectivesComponent,
ListWithSelectedItemComponent
]
});
});
Expand Down Expand Up @@ -661,6 +663,40 @@ describe('List', () => {
}
}));

it('should properly set and get the selected property of list items', () => {
const fixture = TestBed.createComponent(ListWithSelectedItemComponent);
const list = fixture.componentInstance.list;
fixture.detectChanges();

// Get all list items
const items = list.children.toArray();
const headerItem = items[0];
const firstItem = items[1];
const secondItem = items[2];

// Verify initial selected state
expect(headerItem.selected).toBe(false); // Headers should never be selected even if selected=true
expect(firstItem.selected).toBe(true);
expect(secondItem.selected).toBe(false);

// Check if the selected class is applied correctly
expect(headerItem.element.classList.contains('igx-list__item-base--selected')).toBe(false);
expect(firstItem.element.classList.contains('igx-list__item-base--selected')).toBe(true);
expect(secondItem.element.classList.contains('igx-list__item-base--selected')).toBe(false);

// Change selected state programmatically
secondItem.selected = true;
fixture.detectChanges();
expect(secondItem.selected).toBe(true);
expect(secondItem.element.classList.contains('igx-list__item-base--selected')).toBe(true);

// Try to select a header item (should not apply)
headerItem.selected = true;
fixture.detectChanges();
expect(headerItem.selected).toBe(false);
expect(headerItem.element.classList.contains('igx-list__item-base--selected')).toBe(false);
});

it('Initializes igxListThumbnail directive', () => {
const fixture = TestBed.createComponent(ListDirectivesComponent);
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ export class BasicListComponent {
export class ListWithHeaderComponent extends BasicListComponent {
}

@Component({
template: `
<div #wrapper>
<igx-list>
<igx-list-item selected [isHeader]="true">Header</igx-list-item>
<igx-list-item selected>Item 1</igx-list-item>
<igx-list-item>Item 2</igx-list-item>
<igx-list-item>Item 3</igx-list-item>
</igx-list>
</div>`,
imports: [IgxListComponent, IgxListItemComponent]
})
export class ListWithSelectedItemComponent extends BasicListComponent {
}

@Component({
template: `
<div #wrapper>
Expand Down
Loading
Loading