feat(searchbar): autocapitalize, dir, lang, maxlength, and minlength are inherited to native input (#29098)

Issue number: resolves #27606

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Certain attributes are not be inherited to the inner searchbar.
Developers need control over these attributes to provide important
context to users for things like language and text direction.
Additionally, being able to control things like autocapitalize,
maxlength, and minlength can help improve the user experience by a)
guiding what should be entered into an input and b) removing
autocapitalize where it's not appropriate.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Added autocapitalize, maxlength, and minlength properties
- lang and dir are global attributes, so adding them as properties will
cause issues. However, developers can still set them as attributes and
they will be inherited to the native `input` element. We also watch them
so any changes to the attributes are also inherited to the native
`input`.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Note: We expanded the scope of this work to also include input and
textarea, and this work will be handled separately. However, the
original request was only for searchbar so that's why I associated this
PR with the linked issue.

Dev build: `7.7.3-dev.11709159644.114cd8b1`
This commit is contained in:
Liam DeBeasi
2024-03-01 17:12:05 -05:00
committed by GitHub
parent 7cdbc1b5ad
commit a0a77f799d
6 changed files with 127 additions and 5 deletions

View File

@ -1,6 +1,7 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
import { debounceEvent, raf, componentOnReady } from '@utils/helpers';
import { debounceEvent, raf, componentOnReady, inheritAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
import { isRTL } from '@utils/rtl';
import { createColorClasses } from '@utils/theme';
import { arrowBackSharp, closeCircle, closeSharp, searchOutline, searchSharp } from 'ionicons/icons';
@ -28,6 +29,7 @@ export class Searchbar implements ComponentInterface {
private shouldAlignLeft = true;
private originalIonInput?: EventEmitter<SearchbarInputEventDetail>;
private inputId = `ion-searchbar-${searchbarIds++}`;
private inheritedAttributes: Attributes = {};
/**
* The value of the input when the textarea is focused.
@ -39,6 +41,31 @@ export class Searchbar implements ComponentInterface {
@State() focused = false;
@State() noAnimate = true;
/**
* lang and dir are globally enumerated attributes.
* As a result, creating these as properties
* can have unintended side effects. Instead, we
* listen for attribute changes and inherit them
* to the inner `<input>` element.
*/
@Watch('lang')
onLangChanged(newValue: string) {
this.inheritedAttributes = {
...this.inheritedAttributes,
lang: newValue,
};
forceUpdate(this);
}
@Watch('dir')
onDirChanged(newValue: string) {
this.inheritedAttributes = {
...this.inheritedAttributes,
dir: newValue,
};
forceUpdate(this);
}
/**
* The color to use from your application's color palette.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
@ -51,6 +78,27 @@ export class Searchbar implements ComponentInterface {
*/
@Prop() animated = false;
/**
* Prior to the addition of this property
* autocapitalize was enabled by default on iOS
* and disabled by default on Android
* for Searchbar. The autocapitalize type on HTMLElement
* requires that it be a string and never undefined.
* However, setting it to a string value would be a breaking change
* in behavior, so we use "!" to tell TypeScript that this property
* is always defined so we can rely on the browser defaults. Browsers
* will automatically set a default value if the developer does not set one.
*
* In the future, this property will default to "off" to align with
* Input and Textarea, and the "!" will not be needed.
*/
/**
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
*/
@Prop() autocapitalize!: string;
/**
* Set the input's autocomplete property.
*/
@ -112,6 +160,16 @@ export class Searchbar implements ComponentInterface {
*/
@Prop() enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';
/**
* This attribute specifies the maximum number of characters that the user can enter.
*/
@Prop() maxlength?: number;
/**
* This attribute specifies the minimum number of characters that the user can enter.
*/
@Prop() minlength?: number;
/**
* If used in a form, set the name of the control, which is submitted with the form data.
*/
@ -232,6 +290,12 @@ export class Searchbar implements ComponentInterface {
this.emitStyle();
}
componentWillLoad() {
this.inheritedAttributes = {
...inheritAttributes(this.el, ['lang', 'dir']),
};
}
componentDidLoad() {
this.originalIonInput = this.ionInput;
this.positionElements();
@ -614,12 +678,16 @@ export class Searchbar implements ComponentInterface {
onChange={this.onChange}
onBlur={this.onBlur}
onFocus={this.onFocus}
minLength={this.minlength}
maxLength={this.maxlength}
placeholder={this.placeholder}
type={this.type}
value={this.getValue()}
autoCapitalize={this.autocapitalize}
autoComplete={this.autocomplete}
autoCorrect={this.autocorrect}
spellcheck={this.spellcheck}
{...this.inheritedAttributes}
/>
{mode === 'md' && cancelButton}

View File

@ -3,13 +3,37 @@ import { newSpecPage } from '@stencil/core/testing';
import { Searchbar } from '../searchbar';
describe('searchbar: rendering', () => {
it('should inherit attributes', async () => {
it('should inherit properties on load', async () => {
const page = await newSpecPage({
components: [Searchbar],
html: '<ion-searchbar name="search"></ion-searchbar>',
html: '<ion-searchbar autocapitalize="off" maxlength="4" minlength="2" name="search"></ion-searchbar>',
});
const nativeEl = page.body.querySelector('ion-searchbar input')!;
expect(nativeEl.getAttribute('name')).toBe('search');
expect(nativeEl.getAttribute('maxlength')).toBe('4');
expect(nativeEl.getAttribute('minlength')).toBe('2');
expect(nativeEl.getAttribute('autocapitalize')).toBe('off');
});
it('should inherit watched attributes', async () => {
const page = await newSpecPage({
components: [Searchbar],
html: '<ion-searchbar dir="ltr" lang="en-US"></ion-searchbar>',
});
const searchbarEl = page.body.querySelector('ion-searchbar')!;
const nativeEl = searchbarEl.querySelector('input')!;
expect(nativeEl.getAttribute('lang')).toBe('en-US');
expect(nativeEl.getAttribute('dir')).toBe('ltr');
searchbarEl.setAttribute('lang', 'es-ES');
searchbarEl.setAttribute('dir', 'rtl');
await page.waitForChanges();
expect(nativeEl.getAttribute('lang')).toBe('es-ES');
expect(nativeEl.getAttribute('dir')).toBe('rtl');
});
});