Skip to content

Commit

Permalink
feat(select): add updateCustomRenderer method in place of watch
Browse files Browse the repository at this point in the history
  • Loading branch information
dpellier committed Nov 28, 2024
1 parent ed9d0d3 commit 9a18d8a
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('ods-phone-number behaviour', () => {
?.shadowRoot?.querySelector<HTMLElement>('.option')?.click();
});
await page.waitForChanges();
await page.waitForChanges();

expect(await page.evaluate(() => document.querySelector('ods-phone-number')?.getAttribute('value'))).toBe('');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ export class OdsSelect {
this.updateValue(this.defaultValue ?? null);
}

@Method()
public async updateCustomRenderer(): Promise<void> {
// TomSelect lib does not provide an easy way to update template
// So we need to recreate a new instance with the updated template
if (this.selectElement) {
// First we save the current select option nodes as recreating the instance will clean them
const options = [...this.selectElement.children] as HTMLOptionElement[];

// Then we recreate the instance
this.createTomSelect(this.selectElement);

// And we put back the options
this.onSlotChange(options);
}
}

@Method()
public async willValidate(): Promise<boolean> {
return this.internals.willValidate;
Expand All @@ -139,17 +155,6 @@ export class OdsSelect {
}
}

@Watch('customRenderer')
onCustomRendererChange(): void {
if (this.selectElement) {
// The option needs to be moved to ODS-select as it was destroyed by Tom Select.
const options = [...this.selectElement.children] as HTMLOptionElement[];
moveSlottedElements(this.el, options, hasNoValueOption(options));

this.createTomSelect(this.selectElement);
}
}

@Watch('multipleSelectionLabel')
onMultipleSelectionLabelChange(newValue: string): void {
this.select?.control.dispatchEvent(new CustomEvent('ods-select-multiple-selection-label-change', {
Expand Down Expand Up @@ -331,7 +336,7 @@ export class OdsSelect {
this.onIsReadonlyChange(this.isReadonly);
}

private onSlotChange(event: Event): void {
private onSlotChange(optionNodes: HTMLOptionElement[]): void {
// The initial slot nodes move will trigger this callback again
// but we want to avoid a second select initialisation
if (this.hasMovedNodes) {
Expand All @@ -343,7 +348,6 @@ export class OdsSelect {
this.select?.clear(); // reset the current selection
this.select?.clearOptions(); // reset the tom-select options

const optionNodes = (event.currentTarget as HTMLSlotElement).assignedElements() as HTMLOptionElement[];
moveSlottedElements(this.selectElement, optionNodes, hasNoValueOption(optionNodes));
this.hasMovedNodes = true;

Expand Down Expand Up @@ -407,7 +411,7 @@ export class OdsSelect {
required={ this.isRequired }>
</select>

<slot onSlotchange={ (e) => this.onSlotChange(e) }></slot>
<slot onSlotchange={ (e) => this.onSlotChange((e.currentTarget as HTMLSlotElement).assignedElements() as HTMLOptionElement[]) }></slot>
</Host>
);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ods/src/components/select/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

<body>
<p>Default</p>
<ods-select allow-multiple is-required>
<!-- <ods-select allow-multiple is-required>-->
<!-- <ods-select value="">-->
<!-- <ods-select value="dog">-->
<ods-select value="dog">
<!-- <ods-select default-value="cat">-->
<option value="dog">Dog</option>
<option value="cat">Cat</option>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type E2EElement, type E2EPage, newE2EPage } from '@stencil/core/testing';
import { type OdsSelectChangeEventDetail } from '../../src';
import { type OdsSelect, type OdsSelectChangeEventDetail } from '../../src';

describe('ods-select behaviour', () => {
let el: E2EElement;
Expand Down Expand Up @@ -361,6 +361,36 @@ describe('ods-select behaviour', () => {
});
});
});

describe('updateCustomRenderer', () => {
it('should update custom renderer', async() => {
await setup('<ods-select><option value="1">1</option></ods-select>');
await page.evaluate(() => {
const select = document.querySelector<OdsSelect & HTMLElement>('ods-select');
select!.customRenderer = {
option: ({ text }: { text: string }): string => {
return `<div>>>> ${text} <<<</div>`;
},
};
});

await el.callMethod('open');
await page.waitForChanges();

expect(await page.evaluate(() => {
return document.querySelector('ods-select')?.shadowRoot?.querySelector<HTMLElement>('.ts-wrapper .option')?.innerText;
})).toBe('1');

await el.callMethod('updateCustomRenderer');
await page.waitForChanges();
await el.callMethod('open');
await page.waitForChanges();

expect(await page.evaluate(() => {
return document.querySelector('ods-select')?.shadowRoot?.querySelector<HTMLElement>('.ts-wrapper .option')?.innerText;
})).toBe('>>> 1 <<<');
});
});
});

describe('watchers', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ describe('ods-select rendering', () => {
});

it('should render with a custom renderer', async() => {
await setup('<ods-select></ods-select>');
await setup('<ods-select><option value="1">1</option></ods-select>');
await page.evaluate(() => {
const select = document.querySelector<OdsSelect & HTMLElement>('ods-select');
select!.customRenderer = {
option: ({ text }: { text: string }): string => {
return `<div>>>> ${text} <<<</div>`;
},
};
select!.innerHTML = '<option value="1">1</option>';
select!.updateCustomRenderer();
});

await el.callMethod('open');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ describe('ods-select validity', () => {
it('should submit the form if input is valid', async() => {
await setup('<form method="get" onsubmit="return false"><ods-select is-required value="1"><option value="1">1</option></ods-select></form>');

await el.type('abcd');
const formValidity = await page.evaluate(() => {
const form = document.querySelector<HTMLFormElement>('form');
form?.requestSubmit();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Canvas, Meta } from '@storybook/blocks';
import { OdsMessage } from '@ovhcloud/ods-components/react';
import SpecificationsSelect from '@ovhcloud/ods-components/src/components/select/documentation/custom-elements.json';
import { Banner } from '../../../src/components/banner/Banner';
import { Heading } from '../../../src/components/heading/Heading';
Expand Down Expand Up @@ -69,6 +70,8 @@ By default, each render function will receive as argument an object containing t
If you need more data per option, you can attach a `data-attribute` to each option, the value will be available
on the render function as well.

<OdsMessage color="warning" isDismissible={ false }>Updating the "customRenderer" attribute after the initial render will not update the component. You need to explicitly call the "updateCustomRenderer" method to apply the change.</OdsMessage>

Here is an example of the customization of each elements using specific data:

<Canvas of={ SelectStories.CustomRenderer } sourceState="shown" />
Expand Down

0 comments on commit 9a18d8a

Please sign in to comment.