Merge branch 'next' into chore-merge-main-into-next

This commit is contained in:
Brandy Smith
2025-03-19 10:47:25 -04:00
24 changed files with 288 additions and 63 deletions

View File

@ -1895,7 +1895,7 @@ 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
ion-searchbar,prop,searchIcon,string | undefined,undefined,false,false
ion-searchbar,prop,searchIcon,boolean | string | undefined,undefined,false,false
ion-searchbar,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
ion-searchbar,prop,showCancelButton,"always" | "focus" | "never",'never',false,false
ion-searchbar,prop,showClearButton,"always" | "focus" | "never",'always',false,false

View File

@ -3077,7 +3077,7 @@ export namespace Components {
*/
"cancelButtonIcon"?: string;
/**
* Set the the cancel button text. Only available when the theme is `"ios"`.
* Set the cancel button text. Only available when the theme is `"ios"`.
*/
"cancelButtonText": string;
/**
@ -3129,9 +3129,9 @@ export namespace Components {
*/
"placeholder": string;
/**
* The icon to use as the search icon. Defaults to `"search-outline"` in the `"ios"` theme and `"search-sharp"` in the `"md"` and `"ionic"` themes.
* The icon to use as the search icon. Defaults to `"search-outline"` in the `"ios"` theme and `"search-sharp"` in the `"md"` and `"ionic"` themes. If `false`, no search icon will be displayed.
*/
"searchIcon"?: string;
"searchIcon"?: string | boolean;
/**
* Sets focus on the native `input` in `ion-searchbar`. Use this method instead of the global `input.focus()`. Developers who wish to focus an input when a page enters should call `setFocus()` in the `ionViewDidEnter()` lifecycle method. Developers who wish to focus an input when an overlay is presented should call `setFocus` after `didPresent` has resolved. See [managing focus](/docs/developing/managing-focus) for more information.
*/
@ -8524,7 +8524,7 @@ declare namespace LocalJSX {
*/
"cancelButtonIcon"?: string;
/**
* Set the the cancel button text. Only available when the theme is `"ios"`.
* Set the cancel button text. Only available when the theme is `"ios"`.
*/
"cancelButtonText"?: string;
/**
@ -8600,9 +8600,9 @@ declare namespace LocalJSX {
*/
"placeholder"?: string;
/**
* The icon to use as the search icon. Defaults to `"search-outline"` in the `"ios"` theme and `"search-sharp"` in the `"md"` and `"ionic"` themes.
* The icon to use as the search icon. Defaults to `"search-outline"` in the `"ios"` theme and `"search-sharp"` in the `"md"` and `"ionic"` themes. If `false`, no search icon will be displayed.
*/
"searchIcon"?: string;
"searchIcon"?: string | boolean;
/**
* Set to `"soft"` for a searchbar with slightly rounded corners, `"round"` for a searchbar with fully rounded corners, or `"rectangular"` for a searchbar without rounded corners. Defaults to `"round"` for the ionic theme, and `undefined` for all other themes.
*/

View File

@ -9,14 +9,14 @@
* @prop --focus-ring-color: The color of the ring around the focused element.
* @prop --focus-ring-width: The width of the ring around the focused element.
*/
--background: #{globals.$ion-primitives-neutral-100};
--background: #{globals.$ion-bg-neutral-subtle-default};
--border-radius: #{globals.$ion-border-radius-400};
--box-shadow: none;
--cancel-button-color: #{globals.$ion-primitives-neutral-800};
--clear-button-color: #{globals.$ion-primitives-neutral-1000};
--color: #{globals.$ion-primitives-neutral-1200};
--icon-color: #{globals.$ion-primitives-neutral-800};
--placeholder-color: #{globals.$ion-primitives-neutral-800};
--icon-color: #{globals.$ion-icon-subtlest};
--placeholder-color: #{globals.$ion-text-subtlest};
--focus-ring-color: #{globals.$ion-border-focus-default};
--focus-ring-width: #{globals.$ion-border-size-050};
@ -38,6 +38,8 @@
.searchbar-input {
@include globals.padding(globals.$ion-space-300, null);
text-overflow: ellipsis;
contain: strict;
}
@ -67,7 +69,7 @@
font-size: globals.$ion-font-size-400;
}
// Searchbar Search & Clear Icon & Cancel Icon
// Searchbar Search Icon & Clear Icon & Cancel Icon
// -----------------------------------------
.searchbar-search-icon,
@ -80,7 +82,7 @@
transform: translateY(-50%);
}
// Clear Icon & Cancel Icon
// Searchbar Clear Icon & Cancel Icon
// -----------------------------------------
.searchbar-clear-button:focus-visible,
@ -92,10 +94,20 @@
opacity: 1;
}
// Searchbar Search Icon & Cancel Icon
// -----------------------------------------
:host(:not(.searchbar-has-leading-icons)) {
$leading-icon-padding: #{globals.$ion-scale-0};
}
:host(.searchbar-has-value) {
--icon-color: #{globals.$ion-icon-default};
}
// Searchbar in Toolbar
// -----------------------------------------
:host-context(ion-toolbar).searchbar-should-show-cancel .searchbar-cancel-button {
:host(.in-toolbar.searchbar-should-show-cancel) .searchbar-cancel-button {
/**
* The left edge of the cancel button
* should align with the left edge
@ -106,16 +118,6 @@
@include globals.position-horizontal(9px, null);
}
:host-context(ion-toolbar).searchbar-should-show-cancel .searchbar-input {
/**
* Padding start is based on
* the alignment of the back button,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*/
padding-inline-start: calc(9px + globals.$ion-scale-400 + globals.$ion-space-200);
}
// Searchbar States
// --------------------------------------------------
@ -143,8 +145,8 @@
/* Disabled */
:host(.searchbar-disabled) {
--color: #{globals.$ion-primitives-neutral-500};
--icon-color: #{globals.$ion-primitives-neutral-500};
--placeholder-color: #{globals.$ion-primitives-neutral-500};
--icon-color: #{globals.$ion-icon-disabled};
--placeholder-color: #{globals.$ion-text-disabled};
cursor: default;
pointer-events: none;
@ -170,6 +172,42 @@
/* Small */
:host(.searchbar-size-small) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* no leading icons.
*
* Padding end is based on
* desired padding from design,
* no trailing icons.
*/
@include globals.padding-horizontal(globals.$ion-space-300);
height: globals.$ion-scale-1000;
}
/* Small with Leading Icons */
:host(.searchbar-size-small.searchbar-should-show-search-icon) .searchbar-input,
:host(.searchbar-size-small.searchbar-should-show-cancel) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* no trailing icons.
*/
@include globals.padding-horizontal(
calc(globals.$ion-space-300 + globals.$ion-scale-400 + globals.$ion-space-200),
globals.$ion-space-300
);
}
/* Small with Leading Icons and Trailing Icons */
:host(.searchbar-size-small.searchbar-should-show-search-icon.searchbar-should-show-clear) .searchbar-input,
:host(.searchbar-size-small.searchbar-should-show-cancel.searchbar-should-show-clear) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
@ -182,8 +220,24 @@
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(calc(globals.$ion-space-300 + globals.$ion-scale-400 + globals.$ion-space-200));
}
height: globals.$ion-scale-1000;
/* Small with Trailing Icons */
:host(.searchbar-size-small.searchbar-should-show-clear) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* no leading icons.
*
* Padding end is based on
* desired padding from design,
* the size of the trailing icon (clear),
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(
globals.$ion-space-300,
calc(globals.$ion-space-300 + globals.$ion-scale-400 + globals.$ion-space-200)
);
}
:host(.searchbar-size-small) .searchbar-search-icon,
@ -199,6 +253,42 @@
/* Medium */
:host(.searchbar-size-medium) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* no leading icons.
*
* Padding end is based on
* desired padding from design,
* no trailing icons.
*/
@include globals.padding-horizontal(globals.$ion-space-400);
height: globals.$ion-scale-1200;
}
/* Medium with Leading Icons */
:host(.searchbar-size-medium.searchbar-should-show-search-icon) .searchbar-input,
:host(.searchbar-size-medium.searchbar-should-show-cancel) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* no trailing icons.
*/
@include globals.padding-horizontal(
calc(globals.$ion-space-400 + globals.$ion-scale-400 + globals.$ion-space-200),
globals.$ion-space-400
);
}
/* Medium with Leading Icons and Trailing Icons */
:host(.searchbar-size-medium.searchbar-should-show-search-icon.searchbar-should-show-clear) .searchbar-input,
:host(.searchbar-size-medium.searchbar-should-show-cancel.searchbar-should-show-clear) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
@ -211,8 +301,24 @@
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(calc(globals.$ion-space-400 + globals.$ion-scale-400 + globals.$ion-space-200));
}
height: globals.$ion-scale-1200;
/* Medium with Trailing Icons */
:host(.searchbar-size-medium.searchbar-should-show-clear) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* no leading icons.
*
* Padding end is based on
* desired padding from design,
* the size of the trailing icon (clear),
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(
globals.$ion-space-400,
calc(globals.$ion-space-400 + globals.$ion-scale-400 + globals.$ion-space-200)
);
}
:host(.searchbar-size-medium) .searchbar-search-icon,
@ -226,6 +332,17 @@
@include globals.position-horizontal(null, globals.$ion-space-400);
}
:host(.in-toolbar.searchbar-size-small.searchbar-should-show-cancel) .searchbar-input,
:host(.in-toolbar.searchbar-size-medium.searchbar-should-show-cancel) .searchbar-input {
/**
* Padding start is based on
* the alignment of the back button,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*/
padding-inline-start: calc(9px + globals.$ion-scale-400 + globals.$ion-space-200);
}
/* Large */
:host(.searchbar-size-large) .searchbar-search-icon,
:host(.searchbar-size-large) .searchbar-cancel-button {
@ -237,19 +354,47 @@
font-size: globals.$ion-font-size-500;
}
:host-context(ion-toolbar).searchbar-size-large.searchbar-should-show-cancel .searchbar-input {
/**
* Padding start is based on
* the alignment of the back button,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*/
padding-inline-start: calc(9px + globals.$ion-scale-500 + globals.$ion-space-200);
}
:host(.searchbar-size-large) .searchbar-input {
@include globals.padding(globals.$ion-space-400, null);
/**
* Padding start is based on
* desired padding from design,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* the size of the trailing icon (clear),
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(globals.$ion-space-500);
height: globals.$ion-scale-1400;
}
/* Large with Leading Icons */
:host(.searchbar-size-large.searchbar-should-show-search-icon) .searchbar-input,
:host(.searchbar-size-large.searchbar-should-show-cancel) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*
* Padding end is based on
* desired padding from design,
* no trailing icons.
*/
@include globals.padding-horizontal(
calc(globals.$ion-space-500 + globals.$ion-scale-500 + globals.$ion-space-200),
globals.$ion-space-500
);
}
/* Large with Leading Icons and Trailing Icons */
:host(.searchbar-size-large.searchbar-should-show-search-icon.searchbar-should-show-clear) .searchbar-input,
:host(.searchbar-size-large.searchbar-should-show-cancel.searchbar-should-show-clear) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
@ -265,8 +410,34 @@
calc(globals.$ion-space-500 + globals.$ion-scale-500 + globals.$ion-space-200),
calc(globals.$ion-space-500 + globals.$ion-scale-400 + globals.$ion-space-200)
);
}
height: globals.$ion-scale-1400;
/* Large with Trailing Icons */
:host(.searchbar-size-large.searchbar-should-show-clear) .searchbar-input {
/**
* Padding start is based on
* desired padding from design,
* no leading icons.
*
* Padding end is based on
* desired padding from design,
* the size of the trailing icon (clear),
* and the gap between the icon and the input.
*/
@include globals.padding-horizontal(
globals.$ion-space-500,
calc(globals.$ion-space-500 + globals.$ion-scale-400 + globals.$ion-space-200)
);
}
:host(.in-toolbar.searchbar-size-large.searchbar-should-show-cancel) .searchbar-input {
/**
* Padding start is based on
* the alignment of the back button,
* the size of the leading icon (search or cancel),
* and the gap between the icon and the input.
*/
padding-inline-start: calc(9px + globals.$ion-scale-500 + globals.$ion-space-200);
}
:host(.searchbar-size-large) .searchbar-search-icon,

View File

@ -6,7 +6,7 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, forceUpdat
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 { createColorClasses, hostContext } from '@utils/theme';
import { arrowBackSharp, closeCircle, closeSharp, searchOutline, searchSharp } from 'ionicons/icons';
import { config } from '../../global/config';
@ -106,7 +106,7 @@ export class Searchbar implements ComponentInterface {
@Prop() cancelButtonIcon?: string;
/**
* Set the the cancel button text. Only available when the theme is `"ios"`.
* Set the cancel button text. Only available when the theme is `"ios"`.
*/
@Prop() cancelButtonText = 'Cancel';
@ -179,8 +179,9 @@ export class Searchbar implements ComponentInterface {
/**
* The icon to use as the search icon. Defaults to `"search-outline"` in
* the `"ios"` theme and `"search-sharp"` in the `"md"` and `"ionic"` themes.
* If `false`, no search icon will be displayed.
*/
@Prop() searchIcon?: string;
@Prop() searchIcon?: string | boolean;
/**
* Sets the behavior for the cancel button. Defaults to `"never"`.
@ -601,6 +602,14 @@ export class Searchbar implements ComponentInterface {
return this.getValue() !== '';
}
private shouldShowSearchIcon(): boolean {
if (this.searchIcon === false || this.searchIcon === 'false') {
return false;
}
return true;
}
/**
* Determines whether or not the cancel button should be visible onscreen.
* Cancel button should be shown if one of two conditions applies:
@ -620,12 +629,19 @@ export class Searchbar implements ComponentInterface {
* Clear button should be shown if one of two conditions applies:
* 1. `showClearButton` is set to `always`.
* 2. `showClearButton` is set to `focus`, and the searchbar has been focused.
* Unless the `theme` is `ionic` and the searchbar is disabled.
*/
private shouldShowClearButton(): boolean {
const theme = getIonTheme(this);
if (this.showClearButton === 'never' || (this.showClearButton === 'focus' && !this.focused)) {
return false;
}
if (theme === 'ionic' && this.disabled) {
return false;
}
return true;
}
@ -695,8 +711,9 @@ export class Searchbar implements ComponentInterface {
* If no icon is set in the config, use the default icon.
*/
get searchbarSearchIcon(): string {
// Return the icon if it is explicitly set
if (this.searchIcon != null) {
// Return the icon if it is explicitly set.
// If the icon is set to a boolean or string 'true', use the default icon.
if (this.searchIcon != null && this.searchIcon !== 'true' && typeof this.searchIcon !== 'boolean') {
return this.searchIcon;
}
@ -783,10 +800,12 @@ export class Searchbar implements ComponentInterface {
'searchbar-has-value': this.hasValue(),
'searchbar-left-aligned': this.shouldAlignLeft,
'searchbar-has-focus': this.focused,
'searchbar-should-show-search-icon': this.shouldShowSearchIcon(),
'searchbar-should-show-clear': this.shouldShowClearButton(),
'searchbar-should-show-cancel': this.shouldShowCancelButton(),
[`searchbar-shape-${shape}`]: shape !== undefined,
[`searchbar-size-${size}`]: size !== undefined,
'in-toolbar': hostContext('ion-toolbar', this.el),
})}
>
<div class="searchbar-input-container">
@ -816,25 +835,39 @@ export class Searchbar implements ComponentInterface {
{(theme === 'md' || theme === 'ionic') && cancelButton}
<ion-icon aria-hidden="true" icon={searchbarSearchIcon} lazy={false} class="searchbar-search-icon"></ion-icon>
{this.shouldShowSearchIcon() && (
<ion-icon
aria-hidden="true"
icon={searchbarSearchIcon}
lazy={false}
class="searchbar-search-icon"
></ion-icon>
)}
<button
aria-label="reset"
type="button"
no-blur
class="searchbar-clear-button"
onPointerDown={(ev) => {
/**
* This prevents mobile browsers from
* blurring the input when the clear
* button is activated.
*/
ev.preventDefault();
}}
onClick={() => this.onClearInput(true)}
>
<ion-icon aria-hidden="true" icon={searchbarClearIcon} lazy={false} class="searchbar-clear-icon"></ion-icon>
</button>
{this.shouldShowClearButton() && (
<button
aria-label="reset"
type="button"
no-blur
class="searchbar-clear-button"
onPointerDown={(ev) => {
/**
* This prevents mobile browsers from
* blurring the input when the clear
* button is activated.
*/
ev.preventDefault();
}}
onClick={() => this.onClearInput(true)}
>
<ion-icon
aria-hidden="true"
icon={searchbarClearIcon}
lazy={false}
className="searchbar-clear-icon"
></ion-icon>
</button>
)}
</div>
{theme === 'ios' && cancelButton}
</Host>

View File

@ -158,8 +158,8 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
});
});
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('searchbar: ios clear button text cut off'), () => {
configs({ modes: ['ios', 'ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('searchbar: clear button text cut off'), () => {
test('text should not be cut off when clear button is hidden', async ({ page }) => {
await page.setContent(
`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -21,6 +21,27 @@ configs({ modes: ['md', 'ios', 'ionic-md'], directions: ['ltr'] }).forEach(({ ti
});
});
/**
* This behavior is only applicable to the `ionic-md` mode.
* This behavior does not vary across directions.
*/
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('searchbar: disabled'), () => {
test('should not show clear button', async ({ page }) => {
await page.setContent(
`
<ion-searchbar disabled="true" value="Some text"></ion-searchbar>
`,
config
);
const clearButton = page.locator('.searchbar-clear-button');
await expect(clearButton).toBeHidden();
});
});
});
/**
* This behavior is only applicable to the `ionic-md` mode.
* This behavior does not vary across directions.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB