diff --git a/docs/api/select.md b/docs/api/select.md
index ca3ef27b1be..c71914c22e0 100644
--- a/docs/api/select.md
+++ b/docs/api/select.md
@@ -201,6 +201,20 @@ import StartEndSlots from '@site/static/usage/v9/select/start-end-slots/index.md
+## Rich Content Options
+
+:::important
+Rich content in select options is disabled by default. Set [`innerHTMLTemplatesEnabled`](/docs/developing/config#ionicconfig) to `true` in your [global Ionic config](/docs/developing/config#global-config). Markup inside options is treated as plain text when it is disabled. Refer to [Security](/docs/techniques/security.md) for sanitization guidance when enabling custom HTML.
+:::
+
+In addition to single text labels, [Select Options](./select-option.md) can include HTML rich content in the select interface. Elements added inside of an option without a named slot will go into the default label. The `start` and `end` slots will place elements on either side of the default slot. The `description` attribute can be used for additional supporting text displayed under the label.
+
+This is separate from [Start and End Slots](#start-and-end-slots) on `ion-select`, which decorate the closed field. The rich content options display in the interface after opening the select.
+
+import RichContentOptions from '@site/static/usage/v9/select/rich-content-options/index.md';
+
+
+
## Customization
There are two units that make up the Select component and each need to be styled separately. The `ion-select` element is represented on the view by the selected value(s), or placeholder if there is none, and dropdown icon. The interface, which is defined in the [Interfaces](#interfaces) section above, is the dialog that opens when clicking on the `ion-select`. The interface contains all of the options defined by adding `ion-select-option` elements. The following sections will go over the differences between styling these.
diff --git a/docs/developing/config.md b/docs/developing/config.md
index 2fdef3f0e48..494c55d64cb 100644
--- a/docs/developing/config.md
+++ b/docs/developing/config.md
@@ -180,7 +180,7 @@ Below are the config options that Ionic uses.
| `backButtonDefaultHref` | `string` | Overrides the default value for the `defaultHref` property in all `` components. |
| `backButtonIcon` | `string` | Overrides the default icon in all `` components. |
| `backButtonText` | `string` | Overrides the default text in all `` components. |
-| `innerHTMLTemplatesEnabled` | `boolean` | Relevant Components: `ion-alert`, `ion-infinite-scroll-content`, `ion-loading`, `ion-refresher-content`, `ion-toast`. If `true`, content passed to the relevant components will be parsed as HTML instead of plaintext. Defaults to `false`. |
+| `innerHTMLTemplatesEnabled` | `boolean` | Relevant Components: `ion-alert`, `ion-infinite-scroll-content`, `ion-loading`, `ion-refresher-content`, `ion-select-option`, `ion-toast`. If `true`, content passed to the relevant components will be parsed as HTML instead of plaintext. Defaults to `false`. |
| `hardwareBackButton` | `boolean` | If `true`, Ionic will respond to the hardware back button in an Android device. |
| `infiniteLoadingSpinner` | `SpinnerTypes` | Overrides the default spinner type in all `` components. |
| `loadingEnter` | `AnimationBuilder` | Provides a custom enter animation for all `ion-loading`, overriding the default "animation". |
diff --git a/docs/techniques/security.md b/docs/techniques/security.md
index 03268c4363a..e9886f488f2 100644
--- a/docs/techniques/security.md
+++ b/docs/techniques/security.md
@@ -61,7 +61,7 @@ To learn more about the security recommendations for binding to directives such
## Enabling Custom HTML Parsing via `innerHTML`
-`ion-alert`, `ion-infinite-scroll-content`, `ion-loading`, `ion-refresher-content`, and `ion-toast` can accept custom HTML as strings for certain properties. These strings are added to the DOM using `innerHTML` and must be properly sanitized by the developer. This behavior is disabled by default which means values passed to the affected components will always be interpreted as plaintext. Developers can enable this custom HTML behavior by setting `innerHTMLTemplatesEnabled: true` in the [IonicConfig](../developing/config#ionicconfig).
+`ion-alert`, `ion-infinite-scroll-content`, `ion-loading`, `ion-refresher-content`, `ion-select-option`, and `ion-toast` can accept custom HTML as strings for certain properties. These strings are added to the DOM using `innerHTML` and must be properly sanitized by the developer. This behavior is disabled by default which means values passed to the affected components will always be interpreted as plaintext. Developers can enable this custom HTML behavior by setting `innerHTMLTemplatesEnabled: true` in the [IonicConfig](../developing/config#ionicconfig).
## Ejecting from the built-in sanitizer
diff --git a/plugins/docusaurus-plugin-ionic-component-api/index.js b/plugins/docusaurus-plugin-ionic-component-api/index.js
index 7bfad796faf..703afc8af4f 100644
--- a/plugins/docusaurus-plugin-ionic-component-api/index.js
+++ b/plugins/docusaurus-plugin-ionic-component-api/index.js
@@ -57,7 +57,7 @@ module.exports = function (context, options) {
// TODO(FW-7097): Replace this with `latest` when v9 is released.
// Dev build based on the `next` branch of `ionic-framework`.
// This must be used to build the docs with the new components.
- let npmTag = '8.8.7-dev.11779221548.1d38f927';
+ let npmTag = '8.8.9-dev.11779411201.1a483e09';
if (currentVersion.banner === 'unreleased') {
npmTag = 'next';
} else if (currentVersion.path !== undefined) {
diff --git a/src/components/global/Playground/README.md b/src/components/global/Playground/README.md
index 22c475fafe8..99aac9d13eb 100644
--- a/src/components/global/Playground/README.md
+++ b/src/components/global/Playground/README.md
@@ -71,3 +71,11 @@ To opt-out of this behavior, set `includeIonContent={false}` to disable this wra
```tsx
```
+
+## Ionic Config
+
+Pass an [IonicConfig](/docs/developing/config#ionicconfig) object so generated StackBlitz projects bootstrap with the same settings. This is merged with the active preview `mode` when opening the editor. Only string, number, and boolean values are injected into generated code:
+
+```tsx
+
+```
diff --git a/src/components/global/Playground/index.tsx b/src/components/global/Playground/index.tsx
index 69c1bd2b00e..56b9f12cc0b 100644
--- a/src/components/global/Playground/index.tsx
+++ b/src/components/global/Playground/index.tsx
@@ -4,7 +4,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
import './playground.css';
import { EditorOptions, openAngularEditor, openHtmlEditor, openReactEditor, openVueEditor } from './stackblitz.utils';
import { useColorMode } from '@docusaurus/theme-common';
-import { ConsoleItem, Mode, UsageTarget } from './playground.types';
+import { ConsoleItem, IonicConfig, Mode, UsageTarget } from './playground.types';
import Tippy from '@tippyjs/react';
import 'tippy.js/dist/tippy.css';
@@ -120,8 +120,14 @@ interface UsageTargetOptions {
* @param description Optional description of the generated playground example. Specify to customize the StackBlitz description.
* @param src The absolute path to the playground demo. For example: `/usage/button/basic/demo.html`
* @param size The height of the playground. Supports `xsmall`, `small`, `medium`, `large`, 'xlarge' or any string value.
+ * @param mode Restricts the playground to a single specified mode. Acceptable values are: `ios` or `md`.
* @param devicePreview `true` if the playground example should render in a device frame (iOS/MD).
* @param showConsole `true` if the playground should render a console UI that reflects console logs, warnings, and errors.
+ * @param includeIonContent Whether to include the `ion-app` and `ion-content` elements in the generated StackBlitz example.
+ * @param ionicConfig Ionic config values to inject into generated StackBlitz examples.
+ * @param version The major version of Ionic to use in the generated StackBlitz example.
+ * @param defaultFramework The framework to select by default when no user preference is stored.
+ * @returns The generated StackBlitz example.
*/
export default function Playground({
code,
@@ -133,6 +139,7 @@ export default function Playground({
devicePreview,
showConsole,
includeIonContent = true,
+ ionicConfig,
version,
defaultFramework,
}: {
@@ -150,6 +157,12 @@ export default function Playground({
devicePreview?: boolean;
showConsole?: boolean;
includeIonContent: boolean;
+ /**
+ * Ionic config values to inject into generated StackBlitz examples. For
+ * example: `{ innerHTMLTemplatesEnabled: true }`. Merges with the active
+ * preview `mode` when opening the editor.
+ */
+ ionicConfig?: IonicConfig;
/**
* The major version of Ionic to use in the generated StackBlitz examples.
* This will also load assets for StackBlitz from the specified version directory.
@@ -551,6 +564,7 @@ export default function Playground({
title,
description,
includeIonContent,
+ ionicConfig,
mode: isIOS ? 'ios' : 'md',
version,
};
diff --git a/src/components/global/Playground/playground.types.ts b/src/components/global/Playground/playground.types.ts
index c6a14b8e585..9e69fb032dd 100644
--- a/src/components/global/Playground/playground.types.ts
+++ b/src/components/global/Playground/playground.types.ts
@@ -14,3 +14,9 @@ export interface ConsoleItem {
type: 'log' | 'warning' | 'error';
message: string;
}
+
+/**
+ * Ionic app configuration. See [IonicConfig](/docs/developing/config#ionicconfig).
+ * Playground only injects serializable values (string, number, boolean) into StackBlitz.
+ */
+export type IonicConfig = Record;
diff --git a/src/components/global/Playground/stackblitz.utils.ts b/src/components/global/Playground/stackblitz.utils.ts
index d04378f74f7..651714eaded 100644
--- a/src/components/global/Playground/stackblitz.utils.ts
+++ b/src/components/global/Playground/stackblitz.utils.ts
@@ -1,5 +1,7 @@
import sdk from '@stackblitz/sdk';
+import type { IonicConfig } from './playground.types';
+
// The default title to use for StackBlitz examples (when not overwritten)
const DEFAULT_EDITOR_TITLE = 'Ionic Docs Example';
// The default description to use for StackBlitz examples (when not overwritten)
@@ -38,9 +40,46 @@ export interface EditorOptions {
*/
mode?: string;
+ /**
+ * Ionic config values to inject into the generated StackBlitz bootstrap.
+ */
+ ionicConfig?: IonicConfig;
+
+ /**
+ * The major version of Ionic to use in the generated StackBlitz example.
+ * For example: `9` for Ionic 9.
+ */
version?: string;
}
+/**
+ * Formats the Ionic config object for the generated StackBlitz example.
+ * @param options The editor options.
+ * @param indent The number of spaces to indent the formatted Ionic config object.
+ * @returns The formatted Ionic config object.
+ */
+const getFormattedIonicConfig = (options?: EditorOptions, indent = 0) => {
+ const config: IonicConfig = {
+ ...options?.ionicConfig,
+ ...(options?.mode ? { mode: options.mode } : {}),
+ };
+
+ const entries = Object.entries(config).filter(
+ (entry): entry is [string, boolean | number | string] =>
+ typeof entry[1] === 'boolean' || typeof entry[1] === 'number' || typeof entry[1] === 'string'
+ );
+
+ if (entries.length === 0) {
+ return '{}';
+ }
+
+ const pad = ' '.repeat(indent);
+ const propertyPad = ' '.repeat(indent + 2);
+ const lines = entries.map(([key, value]) => `${propertyPad}${JSON.stringify(key)}: ${JSON.stringify(value)}`);
+
+ return `{\n${lines.join(',\n')}\n${pad}}`;
+};
+
const loadSourceFiles = async (files: string[], version: string) => {
const versionDir = `v${version}`;
const sourceFiles = await Promise.all(files.map((f) => fetch(`/docs/code/stackblitz/${versionDir}/${f}`)));
@@ -82,15 +121,15 @@ const openHtmlEditor = async (code: string, options?: EditorOptions) => {
...options?.files,
};
+ const ionicConfig = getFormattedIonicConfig(options, 6);
+
files[indexHtml] = defaultFiles[1].replace(/{{ TEMPLATE }}/g, code).replace(
'',
`
`
@@ -159,13 +198,18 @@ const openAngularEditor = async (code: string, options?: EditorOptions) => {
...options?.files,
};
+ const ionicConfig = getFormattedIonicConfig(options, 4);
+
if (options?.version === '6') {
files[main] = files[main].replace(
'importProvidersFrom(IonicModule.forRoot({ }))',
- `importProvidersFrom(IonicModule.forRoot({ mode: '${options?.mode}' }))`
+ `importProvidersFrom(IonicModule.forRoot(${ionicConfig}))`
);
} else {
- files[main] = files[main].replace('provideIonicAngular()', `provideIonicAngular({ mode: '${options?.mode}' })`);
+ files[main] = files[main].replace(
+ /provideIonicAngular\(\s*(\{[^}]*\})?\s*\)/s,
+ `provideIonicAngular(${ionicConfig})`
+ );
}
sdk.openProject({
@@ -221,7 +265,9 @@ const openReactEditor = async (code: string, options?: EditorOptions) => {
}`,
};
- files[appTsx] = files[appTsx].replace('setupIonicReact()', `setupIonicReact({ mode: '${options?.mode}' })`);
+ const ionicConfig = getFormattedIonicConfig(options);
+
+ files[appTsx] = files[appTsx].replace(/setupIonicReact\(\s*(\{[^}]*\})?\s*\)/s, `setupIonicReact(${ionicConfig})`);
sdk.openProject({
template: 'node',
@@ -274,12 +320,9 @@ const openVueEditor = async (code: string, options?: EditorOptions) => {
}`,
};
- files[mainTs] = files[mainTs].replace(
- '.use(IonicVue)',
- `.use(IonicVue, {
- mode: '${options?.mode}'
-})`
- );
+ const ionicConfig = getFormattedIonicConfig(options);
+
+ files[mainTs] = files[mainTs].replace(/\.use\(IonicVue(?:,\s*\{[^}]*\})?\)/s, `.use(IonicVue, ${ionicConfig})`);
/**
* We have to use StackBlitz web containers here (node template), due
diff --git a/static/code/stackblitz/v9/angular/package-lock.json b/static/code/stackblitz/v9/angular/package-lock.json
index 335c0192276..d6d6e13da71 100644
--- a/static/code/stackblitz/v9/angular/package-lock.json
+++ b/static/code/stackblitz/v9/angular/package-lock.json
@@ -14,8 +14,8 @@
"@angular/platform-browser": "^20.0.0",
"@angular/platform-browser-dynamic": "^20.0.0",
"@angular/router": "^20.0.0",
- "@ionic/angular": "8.7.11",
- "@ionic/core": "8.7.11",
+ "@ionic/angular": "8.8.9-dev.11779411201.1a483e09",
+ "@ionic/core": "8.8.9-dev.11779411201.1a483e09",
"ionicons": "8.0.13",
"rxjs": "^7.8.1",
"tslib": "^2.5.0",
@@ -3217,12 +3217,12 @@
}
},
"node_modules/@ionic/angular": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-W2/mmrL/RTwlDrFyOmRukTz6x0DLl905XwVjIIMeGgu/IV3dbHbzHmFj6VwdhdxW13T9kLOrzLqPRri1KQtdCw==",
"license": "MIT",
"dependencies": {
- "@ionic/core": "8.7.11",
+ "@ionic/core": "8.8.9-dev.11779411201.1a483e09",
"ionicons": "^8.0.13",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -3236,8 +3236,8 @@
}
},
"node_modules/@ionic/core": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==",
"license": "MIT",
"dependencies": {
diff --git a/static/code/stackblitz/v9/angular/package.json b/static/code/stackblitz/v9/angular/package.json
index ff788c8514e..96b73306f96 100644
--- a/static/code/stackblitz/v9/angular/package.json
+++ b/static/code/stackblitz/v9/angular/package.json
@@ -15,8 +15,8 @@
"@angular/platform-browser": "^20.0.0",
"@angular/platform-browser-dynamic": "^20.0.0",
"@angular/router": "^20.0.0",
- "@ionic/angular": "8.7.11",
- "@ionic/core": "8.7.11",
+ "@ionic/angular": "8.8.9-dev.11779411201.1a483e09",
+ "@ionic/core": "8.8.9-dev.11779411201.1a483e09",
"ionicons": "8.0.13",
"rxjs": "^7.8.1",
"tslib": "^2.5.0",
diff --git a/static/code/stackblitz/v9/html/package-lock.json b/static/code/stackblitz/v9/html/package-lock.json
index d054b91cf01..12361b63dd9 100644
--- a/static/code/stackblitz/v9/html/package-lock.json
+++ b/static/code/stackblitz/v9/html/package-lock.json
@@ -6,7 +6,7 @@
"": {
"name": "html-starter",
"dependencies": {
- "@ionic/core": "8.7.11",
+ "@ionic/core": "8.8.9-dev.11779411201.1a483e09",
"ionicons": "8.0.13"
},
"devDependencies": {
@@ -458,8 +458,8 @@
}
},
"node_modules/@ionic/core": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==",
"license": "MIT",
"dependencies": {
diff --git a/static/code/stackblitz/v9/html/package.json b/static/code/stackblitz/v9/html/package.json
index 22a2047e413..e8036d28121 100644
--- a/static/code/stackblitz/v9/html/package.json
+++ b/static/code/stackblitz/v9/html/package.json
@@ -9,7 +9,7 @@
"start": "vite preview"
},
"dependencies": {
- "@ionic/core": "8.7.11",
+ "@ionic/core": "8.8.9-dev.11779411201.1a483e09",
"ionicons": "8.0.13"
},
"devDependencies": {
diff --git a/static/code/stackblitz/v9/react/package-lock.json b/static/code/stackblitz/v9/react/package-lock.json
index 5bdf823b03c..d29d9c8d150 100644
--- a/static/code/stackblitz/v9/react/package-lock.json
+++ b/static/code/stackblitz/v9/react/package-lock.json
@@ -8,8 +8,8 @@
"name": "vite-react-typescript",
"version": "0.1.0",
"dependencies": {
- "@ionic/react": "8.7.11",
- "@ionic/react-router": "8.7.11",
+ "@ionic/react": "8.8.9-dev.11779411201.1a483e09",
+ "@ionic/react-router": "8.8.9-dev.11779411201.1a483e09",
"@types/node": "^24.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
@@ -716,8 +716,8 @@
}
},
"node_modules/@ionic/core": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==",
"license": "MIT",
"dependencies": {
@@ -727,12 +727,12 @@
}
},
"node_modules/@ionic/react": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-h4j2SVRMgoxZBdr1bluKGrb0xNYEqEDcjHDuHsok669tKH3RnTMfD276zytfhFh3R8gIKWIqxb76NIsW/hfZcQ==",
"license": "MIT",
"dependencies": {
- "@ionic/core": "8.7.11",
+ "@ionic/core": "8.8.9-dev.11779411201.1a483e09",
"ionicons": "^8.0.13",
"tslib": "*"
},
@@ -742,12 +742,12 @@
}
},
"node_modules/@ionic/react-router": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-ZpJxx9WjprNngRaVEUvy1k5S22P0/BNfXNKpqqFci/JDJL5uPArLaevwXAuOzdIf+EknpG+34IIW6PBme5cPAQ==",
"license": "MIT",
"dependencies": {
- "@ionic/react": "8.7.11",
+ "@ionic/react": "8.8.9-dev.11779411201.1a483e09",
"tslib": "*"
},
"peerDependencies": {
diff --git a/static/code/stackblitz/v9/react/package.json b/static/code/stackblitz/v9/react/package.json
index 8b0deae5c16..612064c55a4 100644
--- a/static/code/stackblitz/v9/react/package.json
+++ b/static/code/stackblitz/v9/react/package.json
@@ -3,8 +3,8 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@ionic/react": "8.7.11",
- "@ionic/react-router": "8.7.11",
+ "@ionic/react": "8.8.9-dev.11779411201.1a483e09",
+ "@ionic/react-router": "8.8.9-dev.11779411201.1a483e09",
"@types/node": "^24.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
diff --git a/static/code/stackblitz/v9/vue/package-lock.json b/static/code/stackblitz/v9/vue/package-lock.json
index ebf2a96fd89..1c80916ee9b 100644
--- a/static/code/stackblitz/v9/vue/package-lock.json
+++ b/static/code/stackblitz/v9/vue/package-lock.json
@@ -8,8 +8,8 @@
"name": "vite-vue-starter",
"version": "0.0.0",
"dependencies": {
- "@ionic/vue": "8.7.11",
- "@ionic/vue-router": "8.7.11",
+ "@ionic/vue": "8.8.9-dev.11779411201.1a483e09",
+ "@ionic/vue-router": "8.8.9-dev.11779411201.1a483e09",
"vue": "^3.2.25",
"vue-router": "4.6.3"
},
@@ -509,8 +509,8 @@
}
},
"node_modules/@ionic/core": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==",
"license": "MIT",
"dependencies": {
@@ -520,23 +520,23 @@
}
},
"node_modules/@ionic/vue": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-HDEcjhxWfimVQxvXfghrqlAWpXnJvcUDTIVE2Mvq8ul+s7gL/OZCpXTAODJOfFCBAGJ0o9QXyC7OPjyN4UbO8Q==",
"license": "MIT",
"dependencies": {
- "@ionic/core": "8.7.11",
+ "@ionic/core": "8.8.9-dev.11779411201.1a483e09",
"@stencil/vue-output-target": "0.10.7",
"ionicons": "^8.0.13"
}
},
"node_modules/@ionic/vue-router": {
- "version": "8.7.11",
- "resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-8.7.11.tgz",
+ "version": "8.8.9-dev.11779411201.1a483e09",
+ "resolved": "https://registry.npmjs.org/@ionic/vue-router/-/vue-router-8.8.9-dev.11779411201.1a483e09.tgz",
"integrity": "sha512-6k/bWLORJucLIPYqcrXnSs3KEI69qaWo6V4bGAEOSkt9dISdTy65gafi4gtFFyV+n81LIU00WnajJYLadDG3Cg==",
"license": "MIT",
"dependencies": {
- "@ionic/vue": "8.7.11"
+ "@ionic/vue": "8.8.9-dev.11779411201.1a483e09"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
diff --git a/static/code/stackblitz/v9/vue/package.json b/static/code/stackblitz/v9/vue/package.json
index db6efac6dda..1554f4ada51 100644
--- a/static/code/stackblitz/v9/vue/package.json
+++ b/static/code/stackblitz/v9/vue/package.json
@@ -8,8 +8,8 @@
"preview": "vite preview"
},
"dependencies": {
- "@ionic/vue": "8.7.11",
- "@ionic/vue-router": "8.7.11",
+ "@ionic/vue": "8.8.9-dev.11779411201.1a483e09",
+ "@ionic/vue-router": "8.8.9-dev.11779411201.1a483e09",
"vue": "^3.2.25",
"vue-router": "4.6.3"
},
diff --git a/static/usage/v9/select/rich-content-options/angular/example_component_html.md b/static/usage/v9/select/rich-content-options/angular/example_component_html.md
new file mode 100644
index 00000000000..f8431fb7164
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/angular/example_component_html.md
@@ -0,0 +1,39 @@
+```html
+
+ @for (selectInterface of selectInterfaces; track selectInterface) {
+
+
+
+
+ Flight
+ 2h 15m · Nonstop
+ $279
+
+
+
+ Bus
+ 6h 40m · 1 transfer
+ $39
+
+
+
+ Rental Car
+ 5h 10m · Flexible route
+ $92
+
+
+
+ Train
+ 4h 55m · Direct
+ $64
+
+
+
+ }
+
+```
diff --git a/static/usage/v9/select/rich-content-options/angular/example_component_ts.md b/static/usage/v9/select/rich-content-options/angular/example_component_ts.md
new file mode 100644
index 00000000000..05f1327a5d9
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/angular/example_component_ts.md
@@ -0,0 +1,30 @@
+```ts
+import { Component } from '@angular/core';
+import { IonBadge, IonIcon, IonItem, IonList, IonSelect, IonSelectOption } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { airplane, bus, car, train } from 'ionicons/icons';
+
+@Component({
+ selector: 'app-example',
+ templateUrl: 'example.component.html',
+ imports: [IonBadge, IonIcon, IonItem, IonList, IonSelect, IonSelectOption],
+})
+export class ExampleComponent {
+ readonly selectInterfaces = ['alert', 'action-sheet', 'modal', 'popover'];
+
+ /* The interfaceOptions property is for demonstration purposes only. */
+ /* It is not required for the rich options to work. */
+ readonly interfaceOptions = { header: 'Travel mode' };
+
+ constructor() {
+ addIcons({ airplane, bus, car, train });
+ }
+
+ formatInterfaceLabel(selectInterface: string): string {
+ return selectInterface
+ .split('-')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+ }
+}
+```
diff --git a/static/usage/v9/select/rich-content-options/angular/global_css.md b/static/usage/v9/select/rich-content-options/angular/global_css.md
new file mode 100644
index 00000000000..2d733405508
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/angular/global_css.md
@@ -0,0 +1,47 @@
+```css
+/* These styles are for demonstration purposes only. */
+/* They are not required for the rich options to work. */
+
+/* Alert Interface */
+ion-alert.select-alert {
+ --min-width: 350px;
+}
+
+/* Action Sheet Interface */
+.action-sheet-button-label {
+ flex: 1 1 auto;
+ text-align: start;
+}
+
+/* Modal, Popover Interfaces */
+ion-popover.select-popover ion-list ion-radio.in-item::part(label),
+ion-modal.select-modal ion-list ion-radio.in-item::part(label) {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+/* All Interfaces */
+.alert-radio-label-text,
+.action-sheet-button-label-text,
+.select-option-label-text,
+.select-option-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+/* Custom Select Option Title */
+.select-option-content .option-title {
+ font-size: 1rem;
+ font-weight: 600;
+ line-height: initial;
+}
+
+/* Custom Select Option Subtitle */
+.select-option-content .option-subtitle {
+ display: block;
+ font-size: 0.8125rem;
+ font-weight: 500;
+ line-height: initial;
+}
+```
diff --git a/static/usage/v9/select/rich-content-options/demo.html b/static/usage/v9/select/rich-content-options/demo.html
new file mode 100644
index 00000000000..ce4b85da93d
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/demo.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+ Select - Rich Content Option
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/usage/v9/select/rich-content-options/index.md b/static/usage/v9/select/rich-content-options/index.md
new file mode 100644
index 00000000000..b4013a0eb4a
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/index.md
@@ -0,0 +1,42 @@
+import Playground from '@site/src/components/global/Playground';
+
+import javascript_index_html from './javascript/index_html.md';
+import javascript_index_ts from './javascript/index_ts.md';
+
+import react_main_tsx from './react/main_tsx.md';
+import react_main_css from './react/main_css.md';
+
+import vue from './vue.md';
+
+import angular_example_component_html from './angular/example_component_html.md';
+import angular_example_component_ts from './angular/example_component_ts.md';
+import angular_global_css from './angular/global_css.md';
+
+
diff --git a/static/usage/v9/select/rich-content-options/javascript/index_html.md b/static/usage/v9/select/rich-content-options/javascript/index_html.md
new file mode 100644
index 00000000000..3c7431c5fc9
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/javascript/index_html.md
@@ -0,0 +1,112 @@
+```html
+
+
+
+
+
+
+
+```
diff --git a/static/usage/v9/select/rich-content-options/javascript/index_ts.md b/static/usage/v9/select/rich-content-options/javascript/index_ts.md
new file mode 100644
index 00000000000..01ce02c278a
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/javascript/index_ts.md
@@ -0,0 +1,46 @@
+```ts
+import { defineCustomElements } from '@ionic/core/loader';
+
+import { addIcons } from 'ionicons';
+import { airplane, bus, car, train } from 'ionicons/icons';
+
+/* Core CSS required for Ionic components to work properly */
+import '@ionic/core/css/core.css';
+
+/* Basic CSS for apps built with Ionic */
+import '@ionic/core/css/normalize.css';
+import '@ionic/core/css/structure.css';
+import '@ionic/core/css/typography.css';
+
+/* Optional CSS utils that can be commented out */
+import '@ionic/core/css/padding.css';
+import '@ionic/core/css/float-elements.css';
+import '@ionic/core/css/text-alignment.css';
+import '@ionic/core/css/text-transformation.css';
+import '@ionic/core/css/flex-utils.css';
+import '@ionic/core/css/display.css';
+
+/**
+ * Ionic Dark Palette
+ * -----------------------------------------------------
+ * For more information, please see:
+ * https://ionicframework.com/docs/theming/dark-mode
+ */
+
+// import '@ionic/core/css/palettes/dark.always.css';
+// import '@ionic/core/css/palettes/dark.class.css';
+import '@ionic/core/css/palettes/dark.system.css';
+
+/* Theme variables */
+import './theme/variables.css';
+
+/**
+ * On Ionicons 7.2+ these icons
+ * get mapped to a kebab-case key.
+ * Alternatively, developers can do:
+ * addIcons({ 'airplane': airplane, 'bus': bus, 'car': car, 'train', train });
+ */
+addIcons({ airplane, bus, car, train });
+
+defineCustomElements();
+```
diff --git a/static/usage/v9/select/rich-content-options/react/main_css.md b/static/usage/v9/select/rich-content-options/react/main_css.md
new file mode 100644
index 00000000000..2d733405508
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/react/main_css.md
@@ -0,0 +1,47 @@
+```css
+/* These styles are for demonstration purposes only. */
+/* They are not required for the rich options to work. */
+
+/* Alert Interface */
+ion-alert.select-alert {
+ --min-width: 350px;
+}
+
+/* Action Sheet Interface */
+.action-sheet-button-label {
+ flex: 1 1 auto;
+ text-align: start;
+}
+
+/* Modal, Popover Interfaces */
+ion-popover.select-popover ion-list ion-radio.in-item::part(label),
+ion-modal.select-modal ion-list ion-radio.in-item::part(label) {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+/* All Interfaces */
+.alert-radio-label-text,
+.action-sheet-button-label-text,
+.select-option-label-text,
+.select-option-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+/* Custom Select Option Title */
+.select-option-content .option-title {
+ font-size: 1rem;
+ font-weight: 600;
+ line-height: initial;
+}
+
+/* Custom Select Option Subtitle */
+.select-option-content .option-subtitle {
+ display: block;
+ font-size: 0.8125rem;
+ font-weight: 500;
+ line-height: initial;
+}
+```
diff --git a/static/usage/v9/select/rich-content-options/react/main_tsx.md b/static/usage/v9/select/rich-content-options/react/main_tsx.md
new file mode 100644
index 00000000000..1497ab84dac
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/react/main_tsx.md
@@ -0,0 +1,71 @@
+```tsx
+import React from 'react';
+import { SelectInterface } from '@ionic/core';
+import { IonBadge, IonIcon, IonItem, IonList, IonSelect, IonSelectOption } from '@ionic/react';
+import { airplane, bus, car, train } from 'ionicons/icons';
+
+import './main.css';
+
+const selectInterfaces: SelectInterface[] = ['alert', 'action-sheet', 'modal', 'popover'];
+
+/* The interfaceOptions property is for demonstration purposes only. */
+/* It is not required for the rich options to work. */
+const interfaceOptions = { header: 'Travel mode' };
+
+const formatInterfaceLabel = (selectInterface: string) =>
+ selectInterface
+ .split('-')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+
+function Example() {
+ return (
+
+ {selectInterfaces.map((selectInterface) => (
+
+
+
+
+ Flight
+ 2h 15m · Nonstop
+
+ $279
+
+
+
+
+ Bus
+ 6h 40m · 1 transfer
+
+ $39
+
+
+
+
+ Rental Car
+ 5h 10m · Flexible route
+
+ $92
+
+
+
+
+ Train
+ 4h 55m · Direct
+
+ $64
+
+
+
+
+ ))}
+
+ );
+}
+export default Example;
+```
diff --git a/static/usage/v9/select/rich-content-options/vue.md b/static/usage/v9/select/rich-content-options/vue.md
new file mode 100644
index 00000000000..4c109a47dd0
--- /dev/null
+++ b/static/usage/v9/select/rich-content-options/vue.md
@@ -0,0 +1,104 @@
+```vue
+
+
+
+
+
+
+ Flight
+ 2h 15m · Nonstop
+ $279
+
+
+
+ Bus
+ 6h 40m · 1 transfer
+ $39
+
+
+
+ Rental Car
+ 5h 10m · Flexible route
+ $92
+
+
+
+ Train
+ 4h 55m · Direct
+ $64
+
+
+
+
+
+
+
+
+
+```