From 99573762f367d20b04261dcaa6f53b3847bf0571 Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Fri, 19 Dec 2025 15:22:22 -0500
Subject: [PATCH 1/7] feat(input-otp): convert to shadow
---
BREAKING.md | 7 +++++
core/api.txt | 9 ++++++-
core/src/components.d.ts | 16 +++++++++++
.../input-otp/input-otp.common.scss | 3 +++
.../input-otp/input-otp.native.scss | 2 ++
core/src/components/input-otp/input-otp.tsx | 27 ++++++++++++++++---
packages/angular/src/directives/proxies.ts | 4 +--
7 files changed, 61 insertions(+), 7 deletions(-)
diff --git a/BREAKING.md b/BREAKING.md
index 2bbb979de6a..1b138cc012b 100644
--- a/BREAKING.md
+++ b/BREAKING.md
@@ -19,6 +19,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- [Card](#version-9x-card)
- [Chip](#version-9x-chip)
- [Grid](#version-9x-grid)
+ - [Input Otp](#version-9x-input-otp)
- [Radio Group](#version-9x-radio-group)
- [Textarea](#version-9x-textarea)
@@ -149,6 +150,12 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has `
```
+
Input Otp
+
+Converted `ion-input-otp` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
+
+If you were targeting the internals of `ion-input-otp` in your CSS, you will need to target the `group`, `container`, `native`, `separator` or `description` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables.
+
Radio Group
Converted `ion-radio-group` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
diff --git a/core/api.txt b/core/api.txt
index dd7f9195e18..b82f0a090b2 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -1035,18 +1035,20 @@ ion-input,css-prop,--placeholder-opacity,ionic
ion-input,css-prop,--placeholder-opacity,ios
ion-input,css-prop,--placeholder-opacity,md
-ion-input-otp,scoped
+ion-input-otp,shadow
ion-input-otp,prop,autocapitalize,string,'off',false,false
ion-input-otp,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true
ion-input-otp,prop,disabled,boolean,false,false,true
ion-input-otp,prop,fill,"outline" | "solid" | undefined,'outline',false,false
ion-input-otp,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false
ion-input-otp,prop,length,number,4,false,false
+ion-input-otp,prop,mode,"ios" | "md",undefined,false,false
ion-input-otp,prop,pattern,string | undefined,undefined,false,false
ion-input-otp,prop,readonly,boolean,false,false,true
ion-input-otp,prop,separators,number[] | string | undefined,undefined,false,false
ion-input-otp,prop,shape,"rectangular" | "round" | "soft",'round',false,false
ion-input-otp,prop,size,"large" | "medium" | "small",'medium',false,false
+ion-input-otp,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-input-otp,prop,type,"number" | "text",'number',false,false
ion-input-otp,prop,value,null | number | string | undefined,'',false,false
ion-input-otp,method,setFocus,setFocus(index?: number) => Promise
@@ -1127,6 +1129,11 @@ ion-input-otp,css-prop,--separator-width,md
ion-input-otp,css-prop,--width,ionic
ion-input-otp,css-prop,--width,ios
ion-input-otp,css-prop,--width,md
+ion-input-otp,part,container
+ion-input-otp,part,description
+ion-input-otp,part,group
+ion-input-otp,part,native
+ion-input-otp,part,separator
ion-input-password-toggle,shadow
ion-input-password-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index 5119829a658..a4f8833d199 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -1779,6 +1779,10 @@ export namespace Components {
* @default 4
*/
"length": number;
+ /**
+ * The mode determines the platform behaviors of the component.
+ */
+ "mode"?: "ios" | "md";
/**
* A regex pattern string for allowed characters. Defaults based on type. For numbers (`type="number"`): `"[\p{N}]"` For text (`type="text"`): `"[\p{L}\p{N}]"`
*/
@@ -1807,6 +1811,10 @@ export namespace Components {
* @default 'medium'
*/
"size": 'small' | 'medium' | 'large';
+ /**
+ * The theme determines the visual appearance of the component.
+ */
+ "theme"?: "ios" | "md" | "ionic";
/**
* The type of input allowed in the input boxes.
* @default 'number'
@@ -7762,6 +7770,10 @@ declare namespace LocalJSX {
* @default 4
*/
"length"?: number;
+ /**
+ * The mode determines the platform behaviors of the component.
+ */
+ "mode"?: "ios" | "md";
/**
* Emitted when the input group loses focus.
*/
@@ -7805,6 +7817,10 @@ declare namespace LocalJSX {
* @default 'medium'
*/
"size"?: 'small' | 'medium' | 'large';
+ /**
+ * The theme determines the visual appearance of the component.
+ */
+ "theme"?: "ios" | "md" | "ionic";
/**
* The type of input allowed in the input boxes.
* @default 'number'
diff --git a/core/src/components/input-otp/input-otp.common.scss b/core/src/components/input-otp/input-otp.common.scss
index 6af7c8cee63..74ae4ea2821 100644
--- a/core/src/components/input-otp/input-otp.common.scss
+++ b/core/src/components/input-otp/input-otp.common.scss
@@ -94,10 +94,13 @@
background: var(--background);
color: var(--color);
+ font-family: inherit;
font-size: inherit;
text-align: center;
appearance: none;
+
+ box-sizing: border-box;
}
:host(.has-focus) .native-input {
diff --git a/core/src/components/input-otp/input-otp.native.scss b/core/src/components/input-otp/input-otp.native.scss
index daf2b3ff877..f2fe5b5ec89 100644
--- a/core/src/components/input-otp/input-otp.native.scss
+++ b/core/src/components/input-otp/input-otp.native.scss
@@ -19,6 +19,8 @@
--highlight-color-valid: #{ion-color(success, base)};
--highlight-color-invalid: #{ion-color(danger, base)};
+ font-family: $font-family-base;
+
font-size: dynamic-font(14px);
}
diff --git a/core/src/components/input-otp/input-otp.tsx b/core/src/components/input-otp/input-otp.tsx
index 3e8684505ab..20c0471d9e7 100644
--- a/core/src/components/input-otp/input-otp.tsx
+++ b/core/src/components/input-otp/input-otp.tsx
@@ -16,6 +16,16 @@ import type {
InputOtpInputEventDetail,
} from './input-otp-interface';
+/**
+ * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
+ * @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component.
+ *
+ * @part group - The container element that wraps all input boxes.
+ * @part container - The wrapper element for each individual input box.
+ * @part native - The native input element.
+ * @part separator - The separator element displayed between input boxes.
+ * @part description - The container element for the description text.
+ */
@Component({
tag: 'ion-input-otp',
styleUrls: {
@@ -23,7 +33,8 @@ import type {
md: 'input-otp.md.scss',
ionic: 'input-otp.ionic.scss',
},
- scoped: true,
+ shadow: true,
+ formAssociated: true,
})
export class InputOTP implements ComponentInterface {
private inheritedAttributes: Attributes = {};
@@ -817,12 +828,19 @@ export class InputOTP implements ComponentInterface {
'input-otp-readonly': readonly,
})}
>
-