mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-09 08:09:32 +08:00
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:
@ -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}
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user