diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 2a9b9ec34e7..c70b2bb4009 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -338,10 +338,26 @@ export class Input implements ComponentInterface { } } + /** + * dir is a globally enumerated attribute. + * As a result, creating these as properties + * can have unintended side effects. Instead, we + * listen for attribute changes and inherit them + * to the inner `` element. + */ + @Watch('dir') + onDirChanged(newValue: string) { + this.inheritedAttributes = { + ...this.inheritedAttributes, + dir: newValue, + }; + forceUpdate(this); + } + componentWillLoad() { this.inheritedAttributes = { ...inheritAriaAttributes(this.el), - ...inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type']), + ...inheritAttributes(this.el, ['tabindex', 'title', 'data-form-type', 'dir']), }; } diff --git a/core/src/components/input/test/input.spec.ts b/core/src/components/input/test/input.spec.ts index 245e7dd492f..af9faac9f3c 100644 --- a/core/src/components/input/test/input.spec.ts +++ b/core/src/components/input/test/input.spec.ts @@ -44,6 +44,24 @@ describe('input: rendering', () => { const bottomContent = page.body.querySelector('ion-input .input-bottom'); expect(bottomContent).toBe(null); }); + + it('should inherit watched attributes', async () => { + const page = await newSpecPage({ + components: [Input], + html: '', + }); + + const inputEl = page.body.querySelector('ion-input')!; + const nativeEl = inputEl.querySelector('input')!; + + expect(nativeEl.getAttribute('dir')).toBe('ltr'); + + inputEl.setAttribute('dir', 'rtl'); + + await page.waitForChanges(); + + expect(nativeEl.getAttribute('dir')).toBe('rtl'); + }); }); /** diff --git a/core/src/components/textarea/test/textarea.spec.ts b/core/src/components/textarea/test/textarea.spec.ts index 5925a19c011..f1611a3e291 100644 --- a/core/src/components/textarea/test/textarea.spec.ts +++ b/core/src/components/textarea/test/textarea.spec.ts @@ -14,6 +14,24 @@ it('should inherit attributes', async () => { expect(nativeEl.getAttribute('data-form-type')).toBe('password'); }); +it('should inherit watched attributes', async () => { + const page = await newSpecPage({ + components: [Textarea], + html: '', + }); + + const textareaEl = page.body.querySelector('ion-textarea')!; + const nativeEl = textareaEl.querySelector('textarea')!; + + expect(nativeEl.getAttribute('dir')).toBe('ltr'); + + textareaEl.setAttribute('dir', 'rtl'); + + await page.waitForChanges(); + + expect(nativeEl.getAttribute('dir')).toBe('rtl'); +}); + /** * Textarea uses emulated slots, so the internal * behavior will not exactly match IonSelect's slots. diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx index 3349f0c1a85..7764dfba8b5 100644 --- a/core/src/components/textarea/textarea.tsx +++ b/core/src/components/textarea/textarea.tsx @@ -261,6 +261,22 @@ export class Textarea implements ComponentInterface { this.runAutoGrow(); } + /** + * dir is a globally enumerated attribute. + * As a result, creating these as properties + * can have unintended side effects. Instead, we + * listen for attribute changes and inherit them + * to the inner `