From 504fb6a25fddd4095b4ce69f3f0d38ccefae9dc7 Mon Sep 17 00:00:00 2001 From: Mehran Poursadeghi Date: Tue, 11 Mar 2025 15:50:07 -0700 Subject: [PATCH] feat(input, textarea): dir is inherited to native form control (#30102) Issue number: resolves #30193 resolves #29577 Co-authored-by: Maria Hutt --- core/src/components/input/input.tsx | 18 +++++++++++++++++- core/src/components/input/test/input.spec.ts | 18 ++++++++++++++++++ .../components/textarea/test/textarea.spec.ts | 18 ++++++++++++++++++ core/src/components/textarea/textarea.tsx | 18 +++++++++++++++++- 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 2a9b9ec34e..c70b2bb400 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 245e7dd492..af9faac9f3 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 5925a19c01..f1611a3e29 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 3349f0c1a8..7764dfba8b 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 `