diff --git a/BREAKING.md b/BREAKING.md
index d225f544f5e..9d14348f30a 100644
--- a/BREAKING.md
+++ b/BREAKING.md
@@ -24,6 +24,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- [Nav](#version-8x-nav)
- [Picker](#version-8x-picker)
- [Progress bar](#version-8x-progress-bar)
+ - [Textarea](#version-8x-textarea)
@@ -162,7 +163,7 @@ For more information on the dynamic font, refer to the [Dynamic Font Scaling doc
- `ion-picker` and `ion-picker-column` have been renamed to `ion-picker-legacy` and `ion-picker-legacy-column`, respectively. This change was made to accommodate the new inline picker component while allowing developers to continue to use the legacy picker during this migration period.
- Only the component names have been changed. Usages such as `ion-picker` or `IonPicker` should be changed to `ion-picker-legacy` and `IonPickerLegacy`, respectively.
- - Non-component usages such as `pickerController` or `useIonPicker` remain unchanged. The new picker displays inline with your page content and does not have equivalents for these non-component usages.
+ - Non-component usages such as `pickerController` or `useIonPicker` remain unchanged. The new picker displays inline with your page content and does not have equivalents for these non-component usages.
Progress bar
@@ -172,4 +173,8 @@ For more information on the dynamic font, refer to the [Dynamic Font Scaling doc
- `cssClass` has been removed from the `ToastButton` interface. This was previously used to apply a custom class to the toast buttons. Developers can use the "button" shadow part to style the buttons.
-For more information on styling toast buttons, refer to the [Toast Theming documentation](https://ionicframework.com/docs/api/toast#theming).
\ No newline at end of file
+For more information on styling toast buttons, refer to the [Toast Theming documentation](https://ionicframework.com/docs/api/toast#theming).
+
+Textarea
+
+- The `legacy` property and support for the legacy syntax, which involved placing an `ion-textarea` inside of an `ion-item` with an `ion-label`, have been removed. For more information on migrating from the legacy textarea syntax, refer to the [Textarea documentation](https://ionicframework.com/docs/api/textarea#migrating-from-legacy-textarea-syntax).
diff --git a/core/api.txt b/core/api.txt
index c0f6440d583..2ac742620f6 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -1406,7 +1406,6 @@ ion-textarea,prop,helperText,string | undefined,undefined,false,false
ion-textarea,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false
ion-textarea,prop,label,string | undefined,undefined,false,false
ion-textarea,prop,labelPlacement,"end" | "fixed" | "floating" | "stacked" | "start",'start',false,false
-ion-textarea,prop,legacy,boolean | undefined,undefined,false,false
ion-textarea,prop,maxlength,number | undefined,undefined,false,false
ion-textarea,prop,minlength,number | undefined,undefined,false,false
ion-textarea,prop,mode,"ios" | "md",undefined,false,false
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index e4d9bef8702..668cb2d85fa 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -3034,10 +3034,6 @@ export namespace Components {
* Where to place the label relative to the textarea. `"start"`: The label will appear to the left of the textarea in LTR and to the right in RTL. `"end"`: The label will appear to the right of the textarea in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the textarea when the textarea is focused or it has a value. Otherwise it will appear on top of the textarea. `"stacked"`: The label will appear smaller and above the textarea regardless even when the textarea is blurred or has no value. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
*/
"labelPlacement": 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
- /**
- * Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
- */
- "legacy"?: boolean;
/**
* This attribute specifies the maximum number of characters that the user can enter.
*/
@@ -7821,10 +7817,6 @@ declare namespace LocalJSX {
* Where to place the label relative to the textarea. `"start"`: The label will appear to the left of the textarea in LTR and to the right in RTL. `"end"`: The label will appear to the right of the textarea in LTR and to the left in RTL. `"floating"`: The label will appear smaller and above the textarea when the textarea is focused or it has a value. Otherwise it will appear on top of the textarea. `"stacked"`: The label will appear smaller and above the textarea regardless even when the textarea is blurred or has no value. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
*/
"labelPlacement"?: 'start' | 'end' | 'floating' | 'stacked' | 'fixed';
- /**
- * Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the default slot that contains the label text. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup.
- */
- "legacy"?: boolean;
/**
* This attribute specifies the maximum number of characters that the user can enter.
*/
diff --git a/core/src/components/textarea/textarea.ios.scss b/core/src/components/textarea/textarea.ios.scss
index 3c8ec47ffcf..ffbc4264f1c 100644
--- a/core/src/components/textarea/textarea.ios.scss
+++ b/core/src/components/textarea/textarea.ios.scss
@@ -16,14 +16,6 @@
font-size: $textarea-ios-font-size;
}
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea) {
- --padding-top: #{$textarea-ios-padding-top};
- --padding-end: #{$textarea-ios-padding-end};
- --padding-bottom: #{$textarea-ios-padding-bottom};
- --padding-start: #{$textarea-ios-padding-start};
-}
-
:host-context(.item-label-stacked),
:host-context(.item-label-floating) {
--padding-top: 8px;
diff --git a/core/src/components/textarea/textarea.md.scss b/core/src/components/textarea/textarea.md.scss
index 16dfa2e147a..16f14cb978a 100644
--- a/core/src/components/textarea/textarea.md.scss
+++ b/core/src/components/textarea/textarea.md.scss
@@ -18,24 +18,6 @@
font-size: $textarea-md-font-size;
}
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea) {
- --padding-top: #{$textarea-md-padding-top};
- --padding-end: #{$textarea-md-padding-end};
- --padding-bottom: #{$textarea-md-padding-bottom};
- --padding-start: #{$textarea-md-padding-start};
-
- @include margin(8px, 0, 0, 0);
-}
-
-// TODO: FW-2876 - Re-evaluate this selector, it may not be needed
-:host-context(.item-label-stacked),
-:host-context(.item-label-floating) {
- --padding-top: 8px;
- --padding-bottom: 8px;
- --padding-start: 0;
-}
-
// Textarea Max Length Counter
// ----------------------------------------------------------------
.textarea-bottom .counter {
diff --git a/core/src/components/textarea/textarea.scss b/core/src/components/textarea/textarea.scss
index 31b080036c7..8cfcb10447c 100644
--- a/core/src/components/textarea/textarea.scss
+++ b/core/src/components/textarea/textarea.scss
@@ -37,7 +37,7 @@
--placeholder-opacity: #{$placeholder-opacity};
--padding-top: 0;
--padding-end: 0;
- --padding-bottom: 0;
+ --padding-bottom: #{$textarea-padding-bottom};
--padding-start: 0;
--border-radius: 0;
--border-style: solid;
@@ -58,6 +58,8 @@
width: 100%;
+ min-height: 44px;
+
color: var(--color);
font-family: $font-family-base;
@@ -70,11 +72,6 @@
// Textarea Wrapper
// ----------------------------------------------------------------
-// TODO: FW-2876 - Make this style a :host style, remove :not selector
-:host(:not(.legacy-textarea)) {
- min-height: 44px;
-}
-
/**
* Since the label sits on top of the element,
* the component needs to be taller otherwise the
@@ -106,25 +103,6 @@
width: fit-content;
}
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea) {
- flex: 1;
-
- background: var(--background);
-
- white-space: pre-wrap;
-}
-
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea.ion-color) {
- color: current-color(base);
-}
-
-// TODO: FW-2876 - Remove this selector, move styles to :host
-:host(:not(.legacy-textarea)) {
- --padding-bottom: #{$textarea-padding-bottom};
-}
-
:host(.ion-color) {
--highlight-color-focused: #{current-color(base)};
@@ -200,18 +178,6 @@
}
}
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea) .native-textarea {
- white-space: inherit;
-}
-
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea) .native-textarea,
-:host(.legacy-textarea) .textarea-legacy-wrapper::after {
- @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
- @include border-radius(var(--border-radius));
-}
-
.native-textarea {
color: inherit;
@@ -233,15 +199,6 @@
word-break: break-word;
}
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea) .textarea-legacy-wrapper::after {
- @include text-inherit();
-
- grid-area: 1 / 1 / 2 / 2;
-
- word-break: break-word;
-}
-
// Input Cover: Unfocused
// --------------------------------------------------
// The input cover is the div that actually receives the
@@ -267,11 +224,6 @@
opacity: 1;
}
-// TODO: FW-2876 - Remove this selector
-:host(.legacy-textarea[auto-grow]) .cloned-input {
- @include margin(0, 0, 0, 0);
-}
-
:host([auto-grow]) .cloned-input {
// Workaround for webkit rendering issue with scroll assist.
// When cloning the textarea and scrolling into view,
@@ -348,9 +300,7 @@
@include padding(var(--padding-top), 0px, var(--padding-bottom), 0px);
}
-.native-wrapper,
-// TODO: FW-2876 - Remove this selector, keep .native-wrapper
-.textarea-legacy-wrapper {
+.native-wrapper {
display: grid;
min-width: inherit;
diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx
index 6684d09906b..f958b4fb130 100644
--- a/core/src/components/textarea/textarea.tsx
+++ b/core/src/components/textarea/textarea.tsx
@@ -13,17 +13,10 @@ import {
h,
writeTask,
} from '@stencil/core';
-import type { LegacyFormController, NotchController } from '@utils/forms';
-import { createLegacyFormController, createNotchController } from '@utils/forms';
+import type { NotchController } from '@utils/forms';
+import { createNotchController } from '@utils/forms';
import type { Attributes } from '@utils/helpers';
-import {
- inheritAriaAttributes,
- debounceEvent,
- findItemLabel,
- inheritAttributes,
- componentOnReady,
-} from '@utils/helpers';
-import { printIonWarning } from '@utils/logging';
+import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from '@utils/helpers';
import { createSlotMutationController } from '@utils/slot-mutation-controller';
import type { SlotMutationController } from '@utils/slot-mutation-controller';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -62,16 +55,12 @@ export class Textarea implements ComponentInterface {
private textareaWrapper?: HTMLElement;
private inheritedAttributes: Attributes = {};
private originalIonInput?: EventEmitter;
- private legacyFormController!: LegacyFormController;
private notchSpacerEl: HTMLElement | undefined;
private slotMutationController?: SlotMutationController;
private notchController?: NotchController;
- // This flag ensures we log the deprecation warning at most once.
- private hasLoggedDeprecationWarning = false;
-
/**
* The value of the textarea when the textarea is focused.
*/
@@ -127,11 +116,6 @@ export class Textarea implements ComponentInterface {
*/
@Prop() disabled = false;
- @Watch('disabled')
- protected disabledChanged() {
- this.emitStyle();
- }
-
/**
* The fill for the item. If `"solid"` the item will have a background. If
* `"outline"` the item will be transparent with a border. Only available in `md` mode.
@@ -257,17 +241,6 @@ export class Textarea implements ComponentInterface {
*/
@Prop() labelPlacement: 'start' | 'end' | 'floating' | 'stacked' | 'fixed' = 'start';
- /**
- * Set the `legacy` property to `true` to forcibly use the legacy form control markup.
- * Ionic will only opt components in to the modern form markup when they are
- * using either the `aria-label` attribute or the default slot that contains
- * the label text. As a result, the `legacy` property should only be used as
- * an escape hatch when you want to avoid this automatic opt-in behavior.
- * Note that this property will be removed in an upcoming major release
- * of Ionic, and all form components will be opted-in to using the modern form markup.
- */
- @Prop() legacy?: boolean;
-
/**
* The shape of the textarea. If "round" it will have an increased border radius.
*/
@@ -284,7 +257,6 @@ export class Textarea implements ComponentInterface {
nativeInput.value = value;
}
this.runAutoGrow();
- this.emitStyle();
}
/**
@@ -322,14 +294,12 @@ export class Textarea implements ComponentInterface {
connectedCallback() {
const { el } = this;
- this.legacyFormController = createLegacyFormController(el);
this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this));
this.notchController = createNotchController(
el,
() => this.notchSpacerEl,
() => this.labelSlot
);
- this.emitStyle();
this.debounceChanged();
if (Build.isBrowser) {
document.dispatchEvent(
@@ -404,22 +374,6 @@ export class Textarea implements ComponentInterface {
return Promise.resolve(this.nativeInput!);
}
- private emitStyle() {
- if (this.legacyFormController.hasLegacyControl()) {
- this.ionStyle.emit({
- interactive: true,
- textarea: true,
- input: true,
- 'interactive-disabled': this.disabled,
- 'has-placeholder': this.placeholder !== undefined,
- 'has-value': this.hasValue(),
- 'has-focus': this.hasFocus,
- // TODO(FW-2876): remove this
- legacy: !!this.legacy,
- });
- }
- }
-
/**
* Emits an `ionChange` event.
*
@@ -499,10 +453,6 @@ export class Textarea implements ComponentInterface {
}
}
- private focusChange() {
- this.emitStyle();
- }
-
private hasValue(): boolean {
return this.getValue() !== '';
}
@@ -531,14 +481,12 @@ export class Textarea implements ComponentInterface {
private onFocus = (ev: FocusEvent) => {
this.hasFocus = true;
this.focusedValue = this.value;
- this.focusChange();
this.ionFocus.emit(ev);
};
private onBlur = (ev: FocusEvent) => {
this.hasFocus = false;
- this.focusChange();
if (this.focusedValue !== this.value) {
/**
@@ -555,73 +503,6 @@ export class Textarea implements ComponentInterface {
this.checkClearOnEdit(ev);
};
- // TODO: FW-2876 - Remove this render function
- private renderLegacyTextarea() {
- if (!this.hasLoggedDeprecationWarning) {
- printIonWarning(
- `ion-textarea now requires providing a label with either the "label" property or the "aria-label" attribute. To migrate, remove any usage of "ion-label" and pass the label text to either the "label" property or the "aria-label" attribute.
-
-Example:
-Example with aria-label:
-
-For textareas that do not render the label immediately next to the input, developers may continue to use "ion-label" but must manually associate the label with the textarea by using "aria-labelledby".
-
-Developers can use the "legacy" property to continue using the legacy form markup. This property will be removed in an upcoming major release of Ionic where this form control will use the modern form markup.`,
- this.el
- );
- this.hasLoggedDeprecationWarning = true;
- }
-
- const mode = getIonMode(this);
- const value = this.getValue();
- const labelId = this.inputId + '-lbl';
- const label = findItemLabel(this.el);
- if (label) {
- label.id = labelId;
- }
-
- return (
-
- (this.textareaWrapper = el)}>
-
-
-
- );
- }
-
private renderLabel() {
const { label } = this;
@@ -739,7 +620,7 @@ Developers can use the "legacy" property to continue using the legacy form marku
);
}
- private renderTextarea() {
+ render() {
const { inputId, disabled, fill, shape, labelPlacement, el, hasFocus } = this;
const mode = getIonMode(this);
const value = this.getValue();
@@ -841,12 +722,6 @@ Developers can use the "legacy" property to continue using the legacy form marku
);
}
-
- render() {
- const { legacyFormController } = this;
-
- return legacyFormController.hasLegacyControl() ? this.renderLegacyTextarea() : this.renderTextarea();
- }
}
let textareaIds = 0;
diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts
index cb76952cf39..d038c6f8700 100644
--- a/packages/angular/src/directives/proxies.ts
+++ b/packages/angular/src/directives/proxies.ts
@@ -2182,7 +2182,7 @@ export declare interface IonText extends Components.IonText {}
@ProxyCmp({
- inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'legacy', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'value', 'wrap'],
+ inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'value', 'wrap'],
methods: ['setFocus', 'getInputElement']
})
@Component({
@@ -2190,7 +2190,7 @@ export declare interface IonText extends Components.IonText {}
changeDetection: ChangeDetectionStrategy.OnPush,
template: '',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
- inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'legacy', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'value', 'wrap'],
+ inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'spellcheck', 'value', 'wrap'],
})
export class IonTextarea {
protected el: HTMLElement;
diff --git a/packages/angular/standalone/src/directives/textarea.ts b/packages/angular/standalone/src/directives/textarea.ts
index f538bfefec2..243137fda64 100644
--- a/packages/angular/standalone/src/directives/textarea.ts
+++ b/packages/angular/standalone/src/directives/textarea.ts
@@ -34,7 +34,6 @@ const TEXTAREA_INPUTS = [
'inputmode',
'label',
'labelPlacement',
- 'legacy',
'maxlength',
'minlength',
'mode',
diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts
index fe1ec92ed52..910a609e349 100644
--- a/packages/vue/src/proxies.ts
+++ b/packages/vue/src/proxies.ts
@@ -840,7 +840,6 @@ export const IonTextarea = /*@__PURE__*/ defineContainer