diff --git a/stencil-workspace/src/components/modus-button-group/modus-button-group.constants.ts b/stencil-workspace/src/components/modus-button-group/modus-button-group.constants.ts index 719d7c22d..45dd8e5b7 100644 --- a/stencil-workspace/src/components/modus-button-group/modus-button-group.constants.ts +++ b/stencil-workspace/src/components/modus-button-group/modus-button-group.constants.ts @@ -1,3 +1,4 @@ export const SINGLE_SELECTION_TYPE = 'single'; export const MULTIPLE_SELECTION_TYPE = 'multiple'; export const DEFAULT_SELECTION_TYPE = 'none'; +export const SELECTION_ATTRIBUTE = 'selected'; diff --git a/stencil-workspace/src/components/modus-button-group/modus-button-group.e2e.ts b/stencil-workspace/src/components/modus-button-group/modus-button-group.e2e.ts index fcb7fd048..c676e1460 100644 --- a/stencil-workspace/src/components/modus-button-group/modus-button-group.e2e.ts +++ b/stencil-workspace/src/components/modus-button-group/modus-button-group.e2e.ts @@ -245,4 +245,28 @@ describe('modus-button-group', () => { expect(buttonGroup.getAttribute('aria-label')).toBe('Group Label'); expect(buttonGroup.getAttribute('aria-disabled')).toBe('true'); }); + + it('should remove active state from buttons when selected attribute is removed or set to false', async () => { + const page = await newE2EPage(); + + await page.setContent(` + + Button 1 + + `); + const modusButton = await page.find('modus-button'); + const buttonElement = await page.find('modus-button >>> button'); + + modusButton.removeAttribute('selected'); + await page.waitForChanges(); + expect(buttonElement).not.toHaveClass('active'); + + modusButton.setAttribute('selected', 'true'); + await page.waitForChanges(); + expect(buttonElement).toHaveClass('active'); + + modusButton.setAttribute('selected', 'false'); + await page.waitForChanges(); + expect(buttonElement).not.toHaveClass('active'); + }); }); diff --git a/stencil-workspace/src/components/modus-button-group/modus-button-group.spec.ts b/stencil-workspace/src/components/modus-button-group/modus-button-group.spec.ts index 84c62f6dd..970085254 100644 --- a/stencil-workspace/src/components/modus-button-group/modus-button-group.spec.ts +++ b/stencil-workspace/src/components/modus-button-group/modus-button-group.spec.ts @@ -2,6 +2,21 @@ import { newSpecPage } from '@stencil/core/testing'; import { ModusButtonGroup } from './modus-button-group'; describe('modus-button-group', () => { + beforeAll(() => { + class MutationObserverMock { + observe() {} + disconnect() {} + takeRecords() { + return []; + } + } + global.MutationObserver = MutationObserverMock; + }); + + afterAll(() => { + delete global.MutationObserver; + }); + it('renders', async () => { const { root } = await newSpecPage({ components: [ModusButtonGroup], diff --git a/stencil-workspace/src/components/modus-button-group/modus-button-group.tsx b/stencil-workspace/src/components/modus-button-group/modus-button-group.tsx index 79db3a680..c71af605d 100644 --- a/stencil-workspace/src/components/modus-button-group/modus-button-group.tsx +++ b/stencil-workspace/src/components/modus-button-group/modus-button-group.tsx @@ -1,8 +1,13 @@ // eslint-disable-next-line -import { Component, h, Prop, Element, Event, EventEmitter, Host, Listen, Watch } from '@stencil/core'; -import { ButtonGroupSelectionType } from './modus-button-group.models'; -import { DEFAULT_SELECTION_TYPE, SINGLE_SELECTION_TYPE, MULTIPLE_SELECTION_TYPE } from './modus-button-group.constants'; +import { Component, Element, Event, EventEmitter, h, Host, Listen, Prop, Watch } from '@stencil/core'; import { ButtonColor, ButtonSize, ButtonStyle, ButtonType } from '../modus-button/modus-button.models'; +import { + DEFAULT_SELECTION_TYPE, + MULTIPLE_SELECTION_TYPE, + SELECTION_ATTRIBUTE, + SINGLE_SELECTION_TYPE, +} from './modus-button-group.constants'; +import { ButtonGroupSelectionType } from './modus-button-group.models'; @Component({ tag: 'modus-button-group', @@ -12,6 +17,7 @@ import { ButtonColor, ButtonSize, ButtonStyle, ButtonType } from '../modus-butto export class ModusButtonGroup { /** Array to store selected buttons */ selectedButtons: HTMLModusButtonElement[] = []; + private observer: MutationObserver | null = null; @Element() host: HTMLElement; @@ -65,6 +71,15 @@ export class ModusButtonGroup { this.setupButtons(); } + componentDidLoad() { + this.observer = new MutationObserver(this.handleMutations.bind(this)); + this.observer.observe(this.host, { subtree: true, attributes: true, attributeFilter: [SELECTION_ATTRIBUTE] }); + } + + disconnectedCallback() { + this.observer?.disconnect(); + } + @Listen('slotchange') handleSlotChange() { this.setupButtons(); @@ -90,9 +105,31 @@ export class ModusButtonGroup { this.buttonGroupClick.emit({ button: clickedButton, isSelected: this.selectedButtons.includes(clickedButton) }); } + handleMutations(mutationList: MutationRecord[]) { + for (const mutation of mutationList) { + if (mutation.type === 'attributes' && mutation.attributeName === SELECTION_ATTRIBUTE) { + this.setupButtons(); + } + } + } + + handleButtonSelection(button, isSelected) { + if (isSelected) { + button.setActive(true); + this.selectedButtons.push(button); + } else { + button.setActive(false); + if (this.selectedButtons.includes(button)) { + this.selectedButtons = this.selectedButtons.filter((selectedButton) => selectedButton !== button); + } + } + } + setupButtons(reset?: boolean) { - const buttons = this.host.querySelectorAll('modus-button'); - this.renderButtons(buttons, reset); + customElements.whenDefined('modus-button').then(() => { + const buttons = this.host.querySelectorAll('modus-button'); + this.renderButtons(buttons, reset); + }); } renderButtons(buttons: NodeListOf, reset: boolean) { @@ -111,15 +148,13 @@ export class ModusButtonGroup { button.color = this.color; button.size = this.size; button.type = buttonType; - if (button.hasAttribute('selected') && !foundSelected && this.selectionType == SINGLE_SELECTION_TYPE) { - button.setActive(true); - this.selectedButtons.push(button); + if (button.hasAttribute(SELECTION_ATTRIBUTE) && !foundSelected && this.selectionType == SINGLE_SELECTION_TYPE) { + this.handleButtonSelection(button, button.getAttribute(SELECTION_ATTRIBUTE) !== 'false'); foundSelected = true; - } else if (button.hasAttribute('selected') && this.selectionType == MULTIPLE_SELECTION_TYPE) { - button.setActive(true); - if (!this.selectedButtons.includes(button)) { - this.selectedButtons.push(button); - } + } else if (button.hasAttribute(SELECTION_ATTRIBUTE) && this.selectionType == MULTIPLE_SELECTION_TYPE) { + this.handleButtonSelection(button, button.getAttribute(SELECTION_ATTRIBUTE) !== 'false'); + } else { + this.handleButtonSelection(button, false); } }); } diff --git a/stencil-workspace/src/components/modus-number-input/readme.md b/stencil-workspace/src/components/modus-number-input/readme.md index 2bc66099d..54e860574 100644 --- a/stencil-workspace/src/components/modus-number-input/readme.md +++ b/stencil-workspace/src/components/modus-number-input/readme.md @@ -10,10 +10,12 @@ | Property | Attribute | Description | Type | Default | | ------------- | ------------- | ------------------------------------------------------------- | --------------------- | ----------- | | `ariaLabel` | `aria-label` | (optional) The input's aria-label. | `string` | `undefined` | +| `currency` | `currency` | (optional) The input's currency | `string` | `undefined` | | `disabled` | `disabled` | (optional) Whether the input is disabled. | `boolean` | `undefined` | | `errorText` | `error-text` | (optional) The input's error state text. | `string` | `undefined` | | `helperText` | `helper-text` | (optional) The input's helper text displayed below the input. | `string` | `undefined` | | `label` | `label` | (optional) The input's label. | `string` | `undefined` | +| `locale` | `locale` | (optional) The input's locale | `string` | `undefined` | | `maxValue` | `max-value` | (optional) The input's maximum value. | `number` | `undefined` | | `minValue` | `min-value` | (optional) The input's minimum value. | `number` | `undefined` | | `placeholder` | `placeholder` | (optional) The input's placeholder text. | `string` | `undefined` |