Skip to content

Commit

Permalink
feat(itemSelection): add support for items getter
Browse files Browse the repository at this point in the history
Sometimes, we need to retrieve the item list dynamically rather than
from a constant array we passed at constructor time.

This adds support for passing a function as the items array (`() =>
T[]`).

For example:

```ts
let items = [];
new ItemSelectionController(this, items);

// re-assign items. this wouldn't affect the controller previously. it
// will still see the original array
items = [a, b];

// instead, we can do this:
new ItemSelectionController(this, () => items);
```
  • Loading branch information
43081j committed Jun 11, 2024
1 parent 326ddeb commit 7608e4e
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 8 deletions.
29 changes: 21 additions & 8 deletions src/controllers/itemSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,23 @@ export class ItemSelectionController<T> {
* @return {T[]}
*/
public get selectedItems(): T[] {
return this.__items.filter((_item, idx) => this.__selectedIndices.has(idx));
return this.items.filter((_item, idx) => this.__selectedIndices.has(idx));
}

/**
* Gets the current set of items
* @return {T[]}
*/
public get items(): T[] {
if (this.__itemsGetter) {
return this.__itemsGetter();
}
return this.__items;
}

private __selectedIndices: Set<number> = new Set<number>();
private __items: T[] = [];
private __itemsGetter?: () => T[];
private __host: ReactiveControllerHost;
private __options?: ItemSelectionOptions<T>;

Expand All @@ -38,11 +42,15 @@ export class ItemSelectionController<T> {
*/
public constructor(
host: ReactiveControllerHost,
items: T[],
items: T[] | (() => T[]),
options?: ItemSelectionOptions<T>
) {
this.__host = host;
this.__items = items;
if (typeof items === 'function') {
this.__itemsGetter = items;
} else {
this.__items = items;
}

if (options) {
this.__options = options;
Expand Down Expand Up @@ -99,15 +107,16 @@ export class ItemSelectionController<T> {
}

const [currentSelection] = [...this.__selectedIndices];
const items = this.items;
let newIndex = 0;

if (currentSelection !== undefined) {
newIndex = currentSelection + offset;

if (newIndex < 0) {
newIndex = 0;
} else if (newIndex >= this.__items.length) {
newIndex = this.__items.length - 1;
} else if (newIndex >= items.length) {
newIndex = items.length - 1;
}
}

Expand Down Expand Up @@ -142,7 +151,9 @@ export class ItemSelectionController<T> {
throw new Error('selectAll() cannot be used when in single-select mode');
}

for (const item of this.__items) {
const items = this.items;

for (const item of items) {
this.select(item);
}
}
Expand Down Expand Up @@ -185,7 +196,9 @@ export class ItemSelectionController<T> {
throw new Error('Index cannot be below 0');
}

if (idx >= this.__items.length) {
const items = this.items;

if (idx >= items.length) {
throw new Error('Index cannot be greater than size of items array');
}

Expand Down Expand Up @@ -213,7 +226,7 @@ export class ItemSelectionController<T> {
* @return {void}
*/
public toggle(item: T, selected?: boolean): void {
const idx = this.__items.indexOf(item);
const idx = this.items.indexOf(item);

if (idx === -1) {
throw new Error('Item was not in items array');
Expand Down
14 changes: 14 additions & 0 deletions src/test/controllers/itemSelection_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,18 @@ suite('ItemSelectionController', () => {
assert.equal(element.shadowRoot!.textContent, 'Selected: b');
});
});

suite('with items function', () => {
setup(async () => {
const itemsFn = () => items;

Check failure on line 414 in src/test/controllers/itemSelection_test.ts

View workflow job for this annotation

GitHub Actions / test (18.x)

Missing return type on function

Check failure on line 414 in src/test/controllers/itemSelection_test.ts

View workflow job for this annotation

GitHub Actions / test (19.x)

Missing return type on function
controller = new ItemSelectionController<string>(element, itemsFn);
element.controllers.push(controller as ReactiveController);
document.body.appendChild(element);
});

test('initialises to default selection', () => {
assert.is(controller.selectedItems.length, 0);
assert.is(controller.items, items);
});
});
});

0 comments on commit 7608e4e

Please sign in to comment.