Skip to content

Commit

Permalink
feat(tree-select): add max property (#915)
Browse files Browse the repository at this point in the history
Co-authored-by: Udompanish, Sarin <[email protected]>
  • Loading branch information
Sarin-Udompanish and Udompanish, Sarin authored Aug 21, 2023
1 parent 5499d31 commit 72b4e6e
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 4 deletions.
67 changes: 67 additions & 0 deletions documents/src/pages/elements/tree-select.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,73 @@ By clicking the `Selected` button, Tree Select allows the items to be filtered b

For custom filtering, Tree Select provides an identical interface as Combo Box. You provide a predicate function that tests an item. Please consult the [Combo Box docs](./elements/combo-box) for details on how to construct a compatible filter.

## Limiting Selected Items
Tree Select offers a convenient way to limit the number of selected items using `max` property. If users attempt to select more items than the specified limit, "Done" button will be automatically disabled.

::
```javascript
::tree-select::
const el = document.querySelector("ef-tree-select");
el.data = [{
'value': 'AFR',
'label': 'Africa',
'expanded': true,
'items': [{
'value': 'DZA',
'label': 'Algeria',
'expanded': true,
'items': [{
'value': 'ADR',
'label': 'Adrar',
'selected': true,
'items': []
}, {
'value': 'TAM',
'label': 'Tamanghasset',
'selected': true,
'items': []
}, {
'value': 'GUE',
'label': 'Guelma',
'selected': false,
'items': []
}]
}, {
'value': 'AGO',
'label': 'Angola',
'selected': false,
'items': []
}, {
'value': 'BEN',
'label': 'Benin',
'selected': false,
'items': []
}, {
'value': 'BWA',
'label': 'Botswana',
'selected': false,
'items': []
}]
}];
setTimeout(() => { el.opened = true; }, 1000);
```
```css
.wrapper {
padding: 5px;
height: 450px;
}
```
```html
<div class="wrapper">
<ef-tree-select max="3" opened></ef-tree-select>
</div>
```
::

```html
<ef-tree-select max="3" opened></ef-tree-select>
```

## UI Controls
Tree Select has several controls.

Expand Down
15 changes: 15 additions & 0 deletions packages/elements/src/tree-select/__demo__/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@
<ef-tree-select show-pills></ef-tree-select>
</demo-block>

<demo-block header="With max" layout="normal" tags="max">
<p>
<span>max = 0</span>
<ef-tree-select aria-label="Choose Country" max="0"></ef-tree-select>
</p>
<p>
<span>max = 2</span>
<ef-tree-select aria-label="Choose Country" max="2" show-pills></ef-tree-select>
</p>
<p>
<span>max = 10</span>
<ef-tree-select aria-label="Choose Country" max="10" show-pills></ef-tree-select>
</p>
</demo-block>

<demo-block header="No Relation" layout="normal" tags="individual">
<ef-tree-select no-relation></ef-tree-select>
</demo-block>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,5 +303,26 @@ describe('tree-select/Interaction', function () {
);
expect(el.shadowRoot.querySelector('[part="pills"]')).to.equal(null, 'pills should hide');
});

it('has correct disabled state on confirm button when select an item', async function () {
const el = await fixture('<ef-tree-select lang="en-gb" max="4"></ef-tree-select>');
el.data = flatData;
el.opened = true;
await elementUpdated(el);
const treeItems = el.treeEl.querySelectorAll('[role=treeitem]');
const confirmButton = el.popupEl.querySelector('#done');
treeItems[0].click();
treeItems[1].click();
treeItems[2].click();
treeItems[3].click();
await elementUpdated(el);
expect(confirmButton.disabled).to.equal(false);
treeItems[4].click();
await elementUpdated(el);
expect(confirmButton.disabled).to.equal(true);
treeItems[4].click(); // uncheck item
await elementUpdated(el);
expect(confirmButton.disabled).to.equal(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import '@refinitiv-ui/elements/tree-select';
import '@refinitiv-ui/elemental-theme/light/ef-tree-select';
import { elementUpdated, expect, fixture } from '@refinitiv-ui/test-helpers';

const data1 = [{ items: [{ selected: true, value: '1' }] }];
const data1 = [{ items: [{ selected: true, value: '1', label: '1' }] }];
const data2 = [
{
items: [
{ selected: true, value: '1' },
{ selected: true, value: '2' }
{ selected: true, value: '1', label: '1' },
{ selected: true, value: '2', label: '2' }
]
}
];
Expand Down Expand Up @@ -53,4 +53,35 @@ describe('tree-select/Value', function () {
expect(el.values).to.deep.equal([]);
});
});
describe('max', function () {
it('has correct disabled state on confirm button when values changed', async function () {
const el = await fixture('<ef-tree-select lang="en-gb" max="1"></ef-tree-select>');
el.data = data2;
el.opened = true;
await elementUpdated(el);
const confirmButton = el.popupEl.querySelector('#done');
expect(confirmButton.disabled).to.equal(true);
el.values = [];
await elementUpdated(el);
expect(confirmButton.disabled).to.equal(false);
});
it('has correct disabled state on confirm button when max value changed', async function () {
const el = await fixture('<ef-tree-select lang="en-gb" max="1"></ef-tree-select>');
el.data = data2;
el.opened = true;
await elementUpdated(el);
const confirmButton = el.popupEl.querySelector('#done');
expect(confirmButton.disabled).to.equal(true);
el.max = '2';
await elementUpdated(el);
expect(confirmButton.disabled).to.equal(false);
});
it('Should reset max to null when define negative max value', async function () {
const el = await fixture('<ef-tree-select lang="en-gb" max="-1"></ef-tree-select>');
await elementUpdated(el);
expect(el.max).to.equal(null);
el.max = '2';
expect(el.max).to.equal('2');
});
});
});
43 changes: 42 additions & 1 deletion packages/elements/src/tree-select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { VERSION } from '../version.js';
import type { CheckChangedEvent } from '../events';
import type { Overlay } from '../overlay';
import type { Pill } from '../pill';
import type { Tree } from '../tree/index.js';
import type { TreeSelectData, TreeSelectDataItem } from './helpers/types';

export { TreeSelectRenderer };
Expand Down Expand Up @@ -202,12 +203,41 @@ export class TreeSelect extends ComboBox<TreeSelectDataItem> {
@property({ type: Function, attribute: false })
public override renderer = new TreeSelectRenderer(this);

private _max: string | null = null;
/**
* Set maximum number of selected items
* @param value max value
* @default -
*/
@property({ type: String })
public set max(value: string | null) {
value = Number(value) >= 0 ? value : null;
const oldValue = this._max;
if (oldValue !== value) {
this._max = value;
this.requestUpdate('max', oldValue);
}
}
/**
* Set maximum number of selected items
* @returns max value
*/
public get max(): string | null {
return this._max;
}

/**
* Internal reference to popup element
*/
@query('[part=list]')
protected popupEl?: Overlay;

/**
* Internal reference to tree element
*/
@query('[part=tree]')
protected treeEl?: Tree;

/**
* Set resolved data
* @param value resolved data
Expand Down Expand Up @@ -364,6 +394,15 @@ export class TreeSelect extends ComboBox<TreeSelectDataItem> {
return checkedGroupItems;
}

/**
* Determines whether the "Done" button element should be disabled,
* based on the current state and certain conditions.
* @returns {boolean} True if the "Done" button should be disabled, false otherwise.
*/
protected get isConfirmDisabled(): boolean {
return Boolean(this.treeEl && this.max && this.treeEl.values.length > Number(this.max));
}

/**
* Persist the current selection
* Takes the current selection and uses it for {@link TreeSelect.values}
Expand Down Expand Up @@ -926,7 +965,9 @@ export class TreeSelect extends ComboBox<TreeSelectDataItem> {
*/
protected get commitControlsTemplate(): TemplateResult {
return html`
<ef-button id="done" part="done-button" cta @tap="${this.save}">${this.t('DONE')}</ef-button>
<ef-button id="done" part="done-button" cta @tap="${this.save}" .disabled="${this.isConfirmDisabled}"
>${this.t('DONE')}</ef-button
>
<ef-button id="cancel" part="cancel-button" @tap="${this.cancel}">${this.t('CANCEL')}</ef-button>
`;
}
Expand Down

0 comments on commit 72b4e6e

Please sign in to comment.