Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@ ion-row,shadow

ion-searchbar,scoped
ion-searchbar,prop,animated,boolean,false,false,false
ion-searchbar,prop,autocapitalize,string,undefined,true,false
ion-searchbar,prop,autocomplete,"name" | "email" | "tel" | "url" | "on" | "off" | "honorific-prefix" | "given-name" | "additional-name" | "family-name" | "honorific-suffix" | "nickname" | "username" | "new-password" | "current-password" | "one-time-code" | "organization-title" | "organization" | "street-address" | "address-line1" | "address-line2" | "address-line3" | "address-level4" | "address-level3" | "address-level2" | "address-level1" | "country" | "country-name" | "postal-code" | "cc-name" | "cc-given-name" | "cc-additional-name" | "cc-family-name" | "cc-number" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-csc" | "cc-type" | "transaction-currency" | "transaction-amount" | "language" | "bday" | "bday-day" | "bday-month" | "bday-year" | "sex" | "tel-country-code" | "tel-national" | "tel-area-code" | "tel-local" | "tel-extension" | "impp" | "photo",'off',false,false
ion-searchbar,prop,autocorrect,"off" | "on",'off',false,false
ion-searchbar,prop,cancelButtonIcon,string,config.get('backButtonIcon', arrowBackSharp) as string,false,false
Expand All @@ -1168,6 +1169,8 @@ ion-searchbar,prop,debounce,number | undefined,undefined,false,false
ion-searchbar,prop,disabled,boolean,false,false,false
ion-searchbar,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false
ion-searchbar,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false
ion-searchbar,prop,maxlength,number | undefined,undefined,false,false
ion-searchbar,prop,minlength,number | undefined,undefined,false,false
ion-searchbar,prop,mode,"ios" | "md",undefined,false,false
ion-searchbar,prop,name,string,this.inputId,false,false
ion-searchbar,prop,placeholder,string,'Search',false,false
Expand Down
24 changes: 24 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2552,6 +2552,10 @@ export namespace Components {
* If `true`, enable searchbar animation.
*/
"animated": boolean;
/**
* 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"`.
*/
"autocapitalize": string;
/**
* Set the input's autocomplete property.
*/
Expand Down Expand Up @@ -2596,6 +2600,14 @@ export namespace Components {
* A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`.
*/
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
/**
* This attribute specifies the maximum number of characters that the user can enter.
*/
"maxlength"?: number;
/**
* This attribute specifies the minimum number of characters that the user can enter.
*/
"minlength"?: number;
/**
* The mode determines which platform styles to use.
*/
Expand Down Expand Up @@ -7280,6 +7292,10 @@ declare namespace LocalJSX {
* If `true`, enable searchbar animation.
*/
"animated"?: boolean;
/**
* 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"`.
*/
"autocapitalize": string;
/**
* Set the input's autocomplete property.
*/
Expand Down Expand Up @@ -7320,6 +7336,14 @@ declare namespace LocalJSX {
* A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`.
*/
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
/**
* This attribute specifies the maximum number of characters that the user can enter.
*/
"maxlength"?: number;
/**
* This attribute specifies the minimum number of characters that the user can enter.
*/
"minlength"?: number;
/**
* The mode determines which platform styles to use.
*/
Expand Down
70 changes: 69 additions & 1 deletion core/src/components/searchbar/searchbar.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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.
Expand All @@ -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"`.
Expand All @@ -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.
Comment on lines +92 to +93
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make a tech debt ticket for this, but my plan was to make the change once this is merged since Ionic 8 is in beta now (and we can still make breaking changes).

*/

/**
* 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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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}
Expand Down
28 changes: 26 additions & 2 deletions core/src/components/searchbar/test/searchbar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Copy link
Contributor Author

@liamdebeasi liamdebeasi Feb 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not test updating the properties because that would be testing Stencil behavior. I am, however, testing updating attributes to verify that I configured the attribute watchers correctly.

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');
});
});
4 changes: 2 additions & 2 deletions packages/angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1788,15 +1788,15 @@ export declare interface IonRow extends Components.IonRow {}


@ProxyCmp({
inputs: ['animated', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'],
inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'],
methods: ['setFocus', 'getInputElement']
})
@Component({
selector: 'ion-searchbar',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['animated', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'],
inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'showCancelButton', 'showClearButton', 'spellcheck', 'type', 'value'],
})
export class IonSearchbar {
protected el: HTMLElement;
Expand Down
3 changes: 3 additions & 0 deletions packages/vue/src/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ export const IonRow = /*@__PURE__*/ defineContainer<JSX.IonRow>('ion-row', defin
export const IonSearchbar = /*@__PURE__*/ defineContainer<JSX.IonSearchbar, JSX.IonSearchbar["value"]>('ion-searchbar', defineIonSearchbar, [
'color',
'animated',
'autocapitalize',
'autocomplete',
'autocorrect',
'cancelButtonIcon',
Expand All @@ -685,6 +686,8 @@ export const IonSearchbar = /*@__PURE__*/ defineContainer<JSX.IonSearchbar, JSX.
'disabled',
'inputmode',
'enterkeyhint',
'maxlength',
'minlength',
'name',
'placeholder',
'searchIcon',
Expand Down