Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
8 changes: 8 additions & 0 deletions docs/api/select.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ import StartEndSlots from '@site/static/usage/v9/select/start-end-slots/index.md

<StartEndSlots />

## Rich Content Options

TODO

import RichContentOptions from '@site/static/usage/v9/select/rich-content-options/index.md';

<RichContentOptions />

## 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.
Expand Down
2 changes: 1 addition & 1 deletion docs/developing/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ Below are the config options that Ionic uses.
| `backButtonDefaultHref` | `string` | Overrides the default value for the `defaultHref` property in all `<ion-back-button>` components. |
| `backButtonIcon` | `string` | Overrides the default icon in all `<ion-back-button>` components. |
| `backButtonText` | `string` | Overrides the default text in all `<ion-back-button>` 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 `<ion-infinite-scroll-content>` components. |
| `loadingEnter` | `AnimationBuilder` | Provides a custom enter animation for all `ion-loading`, overriding the default "animation". |
Expand Down
2 changes: 1 addition & 1 deletion docs/techniques/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion plugins/docusaurus-plugin-ionic-component-api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ module.exports = function (context, options) {
await generateMarkdownForVersion(version, npmTag, context.i18n.currentLocale, false);
}

let npmTag = 'latest';
let npmTag = '8.8.7-dev.11779221548.1d38f927';
if (currentVersion.banner === 'unreleased') {
npmTag = 'next';
} else if (currentVersion.path !== undefined) {
Expand Down
8 changes: 8 additions & 0 deletions src/components/global/Playground/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,11 @@ To opt-out of this behavior, set `includeIonContent={false}` to disable this wra
```tsx
<Playground includeIonContent={false} />
```

## 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:
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I chose this rather than adding an entire file for each framework to pass the config like Vue but if we prefer that approach I can revert this.


```tsx
<Playground version="9" ionicConfig={{ innerHTMLTemplatesEnabled: true }} />
```
16 changes: 15 additions & 1 deletion src/components/global/Playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -133,6 +139,7 @@ export default function Playground({
devicePreview,
showConsole,
includeIonContent = true,
ionicConfig,
version,
defaultFramework,
}: {
Expand All @@ -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;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I decided to add this as a config object instead of adding innerHTMLTemplatesEnabled as a boolean because this will allow us to enhance the Config documentation if desired by adding playgrounds passing different config options.

/**
* The major version of Ionic to use in the generated StackBlitz examples.
* This will also load assets for StackBlitz from the specified version directory.
Expand Down Expand Up @@ -551,6 +564,7 @@ export default function Playground({
title,
description,
includeIonContent,
ionicConfig,
mode: isIOS ? 'ios' : 'md',
version,
};
Expand Down
6 changes: 6 additions & 0 deletions src/components/global/Playground/playground.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was going to import the type from Ionic but then we would have a dependency on @ionic/core and I don't know if we want that.

69 changes: 56 additions & 13 deletions src/components/global/Playground/stackblitz.utils.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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}`)));
Expand Down Expand Up @@ -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(
'</head>',
`
<script>
window.Ionic = {
config: {
mode: '${options?.mode}'
}
}
config: ${ionicConfig}
};
</script>
</head>
`
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions static/code/stackblitz/v9/angular/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions static/code/stackblitz/v9/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.7-dev.11779221548.1d38f927",
"@ionic/core": "8.8.7-dev.11779221548.1d38f927",
"ionicons": "8.0.13",
"rxjs": "^7.8.1",
"tslib": "^2.5.0",
Expand Down
6 changes: 3 additions & 3 deletions static/code/stackblitz/v9/html/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion static/code/stackblitz/v9/html/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"start": "vite preview"
},
"dependencies": {
"@ionic/core": "8.7.11",
"@ionic/core": "8.8.7-dev.11779221548.1d38f927",
"ionicons": "8.0.13"
},
"devDependencies": {
Expand Down
20 changes: 10 additions & 10 deletions static/code/stackblitz/v9/react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions static/code/stackblitz/v9/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.7-dev.11779221548.1d38f927",
"@ionic/react-router": "8.8.7-dev.11779221548.1d38f927",
"@types/node": "^24.0.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
Expand Down
Loading
Loading