diff --git a/docs/angular/your-first-app.md b/docs/angular/your-first-app.md index 739a9c3d29b..0f14b8a0e2e 100644 --- a/docs/angular/your-first-app.md +++ b/docs/angular/your-first-app.md @@ -110,10 +110,16 @@ npm install @ionic/pwa-elements Next, import `@ionic/pwa-elements` by editing `src/main.ts`. ```tsx -import { defineCustomElements } from '@ionic/pwa-elements/loader'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; +import { defineCustomElements } from '@ionic/pwa-elements/loader'; // Added import // Call the element loader before the bootstrapModule/bootstrapApplication call defineCustomElements(window); + +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.log(err)); ``` That’s it! Now for the fun part - let’s see the app in action. @@ -137,18 +143,20 @@ There are three tabs. Click on the Tab2 tab. It’s a blank canvas, aka the perf Open the photo-gallery app folder in your code editor of choice, then navigate to `/src/app/tab2/tab2.page.html`. We see: ```html - + - Tab 2 + Tab 2 - + Tab 2 + + ``` @@ -161,22 +169,54 @@ Open the photo-gallery app folder in your code editor of choice, then navigate t We put the visual aspects of our app into ``. In this case, it’s where we’ll add a button that opens the device’s camera as well as displays the image captured by the camera. Start by adding a [floating action button](https://ionicframework.com/docs/api/fab) (FAB) to the bottom of the page and set the camera image as the icon. ```html + + + Tab 2 + + + + + + Tab 2 + + + + + + + ``` Next, open `src/app/tabs/tabs.page.html`. Change the label to “Photos” and the icon name to “images”: ```html - - - Photos - + + + + + Tab 1 + + + + + + + Photos + + + + + Tab 3 + + + ``` Save all changes to see them automatically applied in the browser. That’s just the start of all the cool things we can do with Ionic. Up next, implement camera taking functionality on the web, then build it for iOS and Android. diff --git a/docs/angular/your-first-app/2-taking-photos.md b/docs/angular/your-first-app/2-taking-photos.md index 0e0cd078a70..3389362c2f5 100644 --- a/docs/angular/your-first-app/2-taking-photos.md +++ b/docs/angular/your-first-app/2-taking-photos.md @@ -44,23 +44,72 @@ public async addNewToGallery() { Notice the magic here: there's no platform-specific code (web, iOS, or Android)! The Capacitor Camera plugin abstracts that away for us, leaving just one method call - `Camera.getPhoto()` - that will open up the device's camera and allow us to take photos. +Your updated `photo.service.ts` should now look like this: + +```tsx +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; + +@Injectable({ + providedIn: 'root', +}) +export class PhotoService { + constructor() {} + + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + } +} +``` + Next, open up `tab2.page.ts` and import the PhotoService class and add a method that calls the `addNewToGallery` method on the imported service: ```tsx +import { Component } from '@angular/core'; import { PhotoService } from '../services/photo.service'; -constructor(public photoService: PhotoService) { } - -addPhotoToGallery() { - this.photoService.addNewToGallery(); +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + // update constructor to include photoService + constructor(public photoService: PhotoService) {} + + // add addNewToGallery method + addPhotoToGallery() { + this.photoService.addNewToGallery(); + } } ``` Then, open `tab2.page.html` and call the `addPhotoToGallery()` function when the FAB is tapped/clicked: ```html + + + Tab 2 + + + + + + Tab 2 + + + + @@ -78,7 +127,7 @@ After taking a photo, it disappears right away. We need to display it within our ## Displaying Photos -Outside of the `PhotoService` class definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata: +Return to `photo.service.ts`. Outside of the `PhotoService` class definition (the very bottom of the file), create a new interface, `UserPhoto`, to hold our photo metadata: ```tsx export interface UserPhoto { @@ -87,12 +136,14 @@ export interface UserPhoto { } ``` -Back at the top of the file, define an array of Photos, which will contain a reference to each photo captured with the Camera. +Back at the top of the `PhotoService` class definition, define an array of Photos, which will contain a reference to each photo captured with the Camera. ```tsx export class PhotoService { public photos: UserPhoto[] = []; + constructor() {} + // other code } ``` @@ -100,12 +151,14 @@ export class PhotoService { Over in the `addNewToGallery` function, add the newly captured photo to the beginning of the Photos array. ```tsx +public async addNewToGallery() { const capturedPhoto = await Camera.getPhoto({ resultType: CameraResultType.Uri, source: CameraSource.Camera, quality: 100 }); + // add new photo to photos array this.photos.unshift({ filepath: "soon...", webviewPath: capturedPhoto.webPath! @@ -113,22 +166,80 @@ Over in the `addNewToGallery` function, add the newly captured photo to the begi } ``` +`photo.service.ts` should now look like this: + +```tsx +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; + +@Injectable({ + providedIn: 'root', +}) +export class PhotoService { + public photos: UserPhoto[] = []; + constructor() {} + + public async addNewToGallery() { + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + + // add new photo to photos array + this.photos.unshift({ + filepath: 'soon...', + webviewPath: capturedPhoto.webPath!, + }); + } +} + +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} +``` + Next, move over to `tab2.page.html` so we can display the image on the screen. Add a [Grid component](https://ionicframework.com/docs/api/grid) so that each photo will display nicely as photos are added to the gallery, and loop through each photo in the `PhotoServices`'s Photos array, adding an Image component (``) for each. Point the `src` (source) at the photo’s path: ```html + + + Tab 2 + + + + + + Tab 2 + + + + + - + + + + + ``` +:::note +Learn more about the [ngFor core directive](https://blog.angular-university.io/angular-2-ngfor/). +::: + Save all files. Within the web browser, click the Camera button and take another photo. This time, the photo is displayed in the Photo Gallery! Up next, we’ll add support for saving the photos to the filesystem, so they can be retrieved and displayed in our app at a later time. diff --git a/docs/angular/your-first-app/3-saving-photos.md b/docs/angular/your-first-app/3-saving-photos.md index c1e013a8bec..4eef54f42d4 100644 --- a/docs/angular/your-first-app/3-saving-photos.md +++ b/docs/angular/your-first-app/3-saving-photos.md @@ -27,6 +27,7 @@ public async addNewToGallery() { // Save the picture and add it to photo collection const savedImageFile = await this.savePicture(capturedPhoto); + // update argument to unshift array method this.photos.unshift(savedImageFile); } ``` @@ -55,7 +56,7 @@ private async savePicture(photo: Photo) { } ``` -`readAsBase64()` is a helper function we’ll define next. It's useful to organize via a separate method since it requires a small amount of platform-specific (web vs. mobile) logic - more on that in a bit. For now, implement the logic for running on the web: +`readAsBase64()` is a helper function we’ll define next. It's useful to organize via a separate method since it requires a small amount of platform-specific (web vs. mobile) logic - more on that in a bit. For now, we'll create two new helper functions, `readAsBase64()` and `convertBlobToBase64()`, to implement the logic for running on the web: ```tsx private async readAsBase64(photo: Photo) { @@ -76,6 +77,80 @@ private convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { }); ``` +`photo.service.ts` should now look like this: + +```tsx +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; + +@Injectable({ + providedIn: 'root', +}) +export class PhotoService { + public photos: UserPhoto[] = []; + constructor() {} + + public async addNewToGallery() { + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + + // Save the picture and add it to photo collection + const savedImageFile = await this.savePicture(capturedPhoto); + // update argument to unshift array method + this.photos.unshift(savedImageFile); + } + + private async savePicture(photo: Photo) { + // Convert photo to base64 format, required by Filesystem API to save + const base64Data = await this.readAsBase64(photo); + + // Write the file to the data directory + const fileName = Date.now() + '.jpeg'; + const savedFile = await Filesystem.writeFile({ + path: fileName, + data: base64Data, + directory: Directory.Data, + }); + + // Use webPath to display the new image instead of base64 since it's + // already loaded into memory + return { + filepath: fileName, + webviewPath: photo.webPath, + }; + } + + private async readAsBase64(photo: Photo) { + // Fetch the photo, read as a blob, then convert to base64 format + const response = await fetch(photo.webPath!); + const blob = await response.blob(); + + return (await this.convertBlobToBase64(blob)) as string; + } + + private convertBlobToBase64 = (blob: Blob) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); +} + +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} +``` + Obtaining the camera photo as base64 format on the web appears to be a bit trickier than on mobile. In reality, we’re just using built-in web APIs: [fetch()](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) as a neat way to read the file into blob format, then FileReader’s [readAsDataURL()](https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL) to convert the photo blob to base64. -There we go! Each time a new photo is taken, it’s now automatically saved to the filesystem. +There we go! Each time a new photo is taken, it’s now automatically saved to the filesystem. Next up, we'll load and display our saved images +when the user navigates to the Photos tab. diff --git a/docs/angular/your-first-app/4-loading-photos.md b/docs/angular/your-first-app/4-loading-photos.md index 6f59d3f951f..15ba3338fcd 100644 --- a/docs/angular/your-first-app/4-loading-photos.md +++ b/docs/angular/your-first-app/4-loading-photos.md @@ -10,27 +10,44 @@ Fortunately, this is easy: we’ll leverage the Capacitor [Preferences API](http ## Preferences API -Begin by defining a constant variable that will act as the key for the store: +Open `photo.service.ts` and begin by defining a new property in the `PhotoService` class that will act as the key for the store: ```tsx export class PhotoService { public photos: UserPhoto[] = []; + + // add key for photo store private PHOTO_STORAGE: string = 'photos'; - // other code + constructor() {} + + // other code... } ``` Next, at the end of the `addNewToGallery` function, add a call to `Preferences.set()` to save the Photos array. By adding it here, the Photos array is stored each time a new photo is taken. This way, it doesn’t matter when the app user closes or switches to a different app - all photo data is saved. ```tsx -Preferences.set({ - key: this.PHOTO_STORAGE, - value: JSON.stringify(this.photos), -}); +public async addNewToGallery() { + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, + source: CameraSource.Camera, + quality: 100, + }); + + const savedImageFile = await this.savePicture(capturedPhoto); + + this.photos.unshift(savedImageFile); + + // Add call to set() method to cache all photo data for future retrieval + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }); +} ``` -With the photo array data saved, create a function called `loadSaved()` that can retrieve that data. We use the same key to retrieve the photos array in JSON format, then parse it into an array: +With the photo array data saved, create a new public method in the `PhotoService` class called `loadSaved()` that can retrieve the photo data. We use the same key to retrieve the photos array in JSON format, then parse it into an array: ```tsx public async loadSaved() { @@ -42,7 +59,7 @@ public async loadSaved() { } ``` -On mobile (coming up next!), we can directly set the source of an image tag - `` - to each photo file on the Filesystem, displaying them automatically. On the web, however, we must read each image from the Filesystem into base64 format, using a new `base64` property on the `Photo` object. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Below is the code you need to add in the `loadSaved()` function you just added: +On mobile (coming up next!), we can directly set the source of an image tag - `` - to each photo file on the Filesystem, displaying them automatically. On the web, however, we must read each image from the Filesystem into base64 format, using a new `base64` property on the `Photo` object. This is because the Filesystem API uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) under the hood. Add the following code to complete the `loadSaved()` function: ```tsx // Display the photo by reading into base64 format @@ -58,12 +75,134 @@ for (let photo of this.photos) { } ``` -After, call this new method in `tab2.page.ts` so that when the user first navigates to Tab 2 (the Photo Gallery), all photos are loaded and displayed on the screen. +After these updates to the `PhotoService` class, your `photos.service.ts` file should look like this: + +```tsx +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; + +@Injectable({ + providedIn: 'root', +}) +export class PhotoService { + public photos: UserPhoto[] = []; + private PHOTO_STORAGE = 'photos'; + + constructor() {} + + public async addNewToGallery() { + // Take a photo + const capturedPhoto = await Camera.getPhoto({ + resultType: CameraResultType.Uri, // file-based data; provides best performance + source: CameraSource.Camera, // automatically take a new photo with the camera + quality: 100, // highest quality (0 to 100) + }); + + const savedImageFile = await this.savePicture(capturedPhoto); + + // Add new photo to Photos array + this.photos.unshift(savedImageFile); + + // Cache all photo data for future retrieval + Preferences.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(this.photos), + }); + } + + public async loadSaved() { + // Retrieve cached photo array data + const { value } = await Preferences.get({ key: this.PHOTO_STORAGE }); + this.photos = (value ? JSON.parse(value) : []) as UserPhoto[]; + + // Display the photo by reading into base64 format + for (let photo of this.photos) { + // Read each saved photo's data from the Filesystem + const readFile = await Filesystem.readFile({ + path: photo.filepath, + directory: Directory.Data, + }); + + // Web platform only: Load the photo as base64 data + photo.webviewPath = `data:image/jpeg;base64,${readFile.data}`; + } + } + + private async savePicture(photo: Photo) { + // Convert photo to base64 format, required by Filesystem API to save + const base64Data = await this.readAsBase64(photo); + + // Write the file to the data directory + const fileName = Date.now() + '.jpeg'; + const savedFile = await Filesystem.writeFile({ + path: fileName, + data: base64Data, + directory: Directory.Data, + }); + + // Use webPath to display the new image instead of base64 since it's + // already loaded into memory + return { + filepath: fileName, + webviewPath: photo.webPath, + }; + } + + private async readAsBase64(photo: Photo) { + // Fetch the photo, read as a blob, then convert to base64 format + const response = await fetch(photo.webPath!); + const blob = await response.blob(); + + return (await this.convertBlobToBase64(blob)) as string; + } + + private convertBlobToBase64 = (blob: Blob) => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onerror = reject; + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); +} + +export interface UserPhoto { + filepath: string; + webviewPath?: string; +} +``` + +Our `PhotoService` can now load the saved images, but we'll need to update `tab2.page.ts` to put that new code to work. We'll call `loadSaved` within the [ngOnInit](https://angular.dev/guide/components/lifecycle#ngoninit) lifecycle method so that when the user first navigates to Tab 2 (the Photo Gallery), all photos are loaded and displayed on the screen. Update `tab2.page.ts` to look like the following: ```tsx -async ngOnInit() { - await this.photoService.loadSaved(); +import { Component } from '@angular/core'; +import { PhotoService } from '../services/photo.service'; + +@Component({ + selector: 'app-tab2', + templateUrl: 'tab2.page.html', + styleUrls: ['tab2.page.scss'], + standalone: false, +}) +export class Tab2Page { + constructor(public photoService: PhotoService) {} + + // add call to loadSaved on navigation to Photos tab + async ngOnInit() { + await this.photoService.loadSaved(); + } + + addPhotoToGallery() { + this.photoService.addNewToGallery(); + } } ``` +:::note +If you're seeing broken image links or missing photos after following these steps, you may need to open your browser's +dev tools and clear both [localStorage](https://developer.chrome.com/docs/devtools/storage/localstorage) and [IndexedDB](https://developer.chrome.com/docs/devtools/storage/indexeddb). +::: That’s it! We’ve built a complete Photo Gallery feature in our Ionic app that works on the web. Next up, we’ll transform it into a mobile app for iOS and Android! diff --git a/docs/angular/your-first-app/5-adding-mobile.md b/docs/angular/your-first-app/5-adding-mobile.md index ce7f60e75c5..e1d4b508b62 100644 --- a/docs/angular/your-first-app/5-adding-mobile.md +++ b/docs/angular/your-first-app/5-adding-mobile.md @@ -10,27 +10,38 @@ Our photo gallery app won’t be complete until it runs on iOS, Android, and the Let’s start with making some small code changes - then our app will “just work” when we deploy it to a device. -Import the Ionic [Platform API](https://ionicframework.com/docs/angular/platform) into `photo.service.ts`, which is used to retrieve information about the current device. In this case, it’s useful for selecting which code to execute based on the platform the app is running on (web or mobile): +Import the Ionic [Platform API](https://ionicframework.com/docs/angular/platform) into `photo.service.ts`, which is used to retrieve information about the current device. In this case, it’s useful for selecting which code to execute based on the platform the app is running on (web or mobile). + +Add `Platform` to the imports at the top of the file and a new property `platform` to the `PhotoService` class. We'll also need to update the constructor to set the user's platform: ```tsx +import { Injectable } from '@angular/core'; +import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera'; +import { Filesystem, Directory } from '@capacitor/filesystem'; +import { Preferences } from '@capacitor/preferences'; + +// add Platform import import { Platform } from '@ionic/angular'; export class PhotoService { public photos: UserPhoto[] = []; private PHOTO_STORAGE: string = 'photos'; + + // add property platform to store which platform app is running on private platform: Platform; + // update constructor to set platform property constructor(platform: Platform) { this.platform = platform; } - // other code + // other code... } ``` ## Platform-specific Logic -First, we’ll update the photo saving functionality to support mobile. In the `readAsBase64()` function, check which platform the app is running on. If it’s “hybrid” (Capacitor or Cordova, two native runtimes), then read the photo file into base64 format using the Filesystem `readFile()` method. Otherwise, use the same logic as before when running the app on the web: +First, we’ll update the photo saving functionality to support mobile. In the `readAsBase64()` function, check which platform the app is running on. If it’s “hybrid” (Capacitor or Cordova, two native runtimes), then read the photo file into base64 format using the Filesystem `readFile()` method. Otherwise, use the same logic as before when running the app on the web. Update `readAsBase64()` to look like the following: ```tsx private async readAsBase64(photo: Photo) { @@ -53,7 +64,14 @@ private async readAsBase64(photo: Photo) { } ``` -Next, update the `savePicture()` method. When running on mobile, set `filepath` to the result of the `writeFile()` operation - `savedFile.uri`. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc()` method ([details here](https://ionicframework.com/docs/core-concepts/webview#file-protocol)). +Next, update the `savePicture()` method. When running on mobile, set `filepath` to the result of the `writeFile()` operation - `savedFile.uri`. When setting the `webviewPath`, use the special `Capacitor.convertFileSrc()` method ([details on the File Protocol](https://ionicframework.com/docs/core-concepts/webview#file-protocol)). To use this method, we'll need to import Capacitor at the +top of `photo.service.ts`. + +```tsx +import { Capacitor } from '@capacitor/core'; +``` + +Then update `savePicture()` to look like the following: ```tsx // Save picture to file on device diff --git a/plugins/docusaurus-plugin-ionic-component-api/index.js b/plugins/docusaurus-plugin-ionic-component-api/index.js index d6908d4a0d3..d3b3d2d7255 100644 --- a/plugins/docusaurus-plugin-ionic-component-api/index.js +++ b/plugins/docusaurus-plugin-ionic-component-api/index.js @@ -173,6 +173,25 @@ function renderEvents({ events }) { ${events.map((event) => `| \`${event.event}\` | ${formatMultiline(event.docs)} | \`${event.bubbles}\` |`).join('\n')}`; } +/** + * Formats method parameters for the optional Parameters row of each method table + * @param {*} paramsArr Array of method parameters + * @returns formatted parameters for methods table + */ +function renderParameters(paramsArr) { + if (!paramsArr.some((param) => param.docs)) { + return ''; + } + + const documentedParams = paramsArr.filter((param) => param.docs); + const formattedParams = documentedParams + .map((param) => { + return `**${param.name}**: ${formatMultiline(param.docs)}`; + }) + .join('
'); + return `| **Parameters** | ${formattedParams} |`; +} + function renderMethods({ methods }) { if (methods.length === 0) { return 'No public methods available for this component.'; @@ -189,6 +208,7 @@ ${methods | --- | --- | | **Description** | ${formatMultiline(method.docs)} | | **Signature** | \`${method.signature.replace(/\|/g, '\uff5c')}\` | +${method.parameters.length !== 0 ? renderParameters(method.parameters) : ''} ` ) .join('\n')} diff --git a/static/code/stackblitz/v7/angular/package.json b/static/code/stackblitz/v7/angular/package.json index 439076f494a..295ed77a940 100644 --- a/static/code/stackblitz/v7/angular/package.json +++ b/static/code/stackblitz/v7/angular/package.json @@ -17,7 +17,7 @@ "@angular/router": "^19.0.0", "@ionic/angular": "^7.0.0", "@ionic/core": "^7.0.0", - "ionicons": "8.0.8", + "ionicons": "8.0.9", "rxjs": "^7.8.1", "tslib": "^2.5.0", "zone.js": "~0.15.0" diff --git a/static/code/stackblitz/v7/html/package.json b/static/code/stackblitz/v7/html/package.json index c002c4a5631..7b9f1727b1c 100644 --- a/static/code/stackblitz/v7/html/package.json +++ b/static/code/stackblitz/v7/html/package.json @@ -1,6 +1,6 @@ { "dependencies": { "@ionic/core": "^7.0.0", - "ionicons": "8.0.8" + "ionicons": "8.0.9" } } diff --git a/static/code/stackblitz/v7/react/package-lock.json b/static/code/stackblitz/v7/react/package-lock.json index 3721f5dedb4..850022cfe69 100644 --- a/static/code/stackblitz/v7/react/package-lock.json +++ b/static/code/stackblitz/v7/react/package-lock.json @@ -1047,9 +1047,9 @@ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" }, "node_modules/@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", "dependencies": { "undici-types": "~6.21.0" } @@ -1063,9 +1063,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", - "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "peerDependencies": { "@types/react": "^19.0.0" } @@ -1090,9 +1090,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", - "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.1.tgz", + "integrity": "sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==", "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", @@ -2349,9 +2349,9 @@ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" }, "@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", "requires": { "undici-types": "~6.21.0" } @@ -2365,9 +2365,9 @@ } }, "@types/react-dom": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", - "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "requires": {} }, "@types/react-router": { @@ -2390,9 +2390,9 @@ } }, "@vitejs/plugin-react": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", - "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.1.tgz", + "integrity": "sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==", "requires": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", diff --git a/static/code/stackblitz/v8/angular/package.json b/static/code/stackblitz/v8/angular/package.json index 7047ff2b60b..d11eda773bb 100644 --- a/static/code/stackblitz/v8/angular/package.json +++ b/static/code/stackblitz/v8/angular/package.json @@ -17,7 +17,7 @@ "@angular/router": "^19.0.0", "@ionic/angular": "8.6.0", "@ionic/core": "8.6.0", - "ionicons": "8.0.8", + "ionicons": "8.0.9", "rxjs": "^7.8.1", "tslib": "^2.5.0", "zone.js": "~0.15.0" diff --git a/static/code/stackblitz/v8/html/package.json b/static/code/stackblitz/v8/html/package.json index 1459153ed8d..ad228f0ae4b 100644 --- a/static/code/stackblitz/v8/html/package.json +++ b/static/code/stackblitz/v8/html/package.json @@ -1,6 +1,6 @@ { "dependencies": { "@ionic/core": "8.6.0", - "ionicons": "8.0.8" + "ionicons": "8.0.9" } } diff --git a/static/code/stackblitz/v8/react/package-lock.json b/static/code/stackblitz/v8/react/package-lock.json index c880b254b9a..0f24714695e 100644 --- a/static/code/stackblitz/v8/react/package-lock.json +++ b/static/code/stackblitz/v8/react/package-lock.json @@ -1153,9 +1153,9 @@ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" }, "node_modules/@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", "dependencies": { "undici-types": "~6.21.0" } @@ -1169,9 +1169,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", - "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "peerDependencies": { "@types/react": "^19.0.0" } @@ -1196,9 +1196,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", - "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.1.tgz", + "integrity": "sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==", "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", @@ -2515,9 +2515,9 @@ "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" }, "@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", "requires": { "undici-types": "~6.21.0" } @@ -2531,9 +2531,9 @@ } }, "@types/react-dom": { - "version": "19.1.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.5.tgz", - "integrity": "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==", + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "requires": {} }, "@types/react-router": { @@ -2556,9 +2556,9 @@ } }, "@vitejs/plugin-react": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", - "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.1.tgz", + "integrity": "sha512-uPZBqSI0YD4lpkIru6M35sIfylLGTyhGHvDZbNLuMA73lMlwJKz5xweH7FajfcCAc2HnINciejA9qTz0dr0M7A==", "requires": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", diff --git a/static/usage/v7/infinite-scroll/basic/vue.md b/static/usage/v7/infinite-scroll/basic/vue.md index 61c08173971..cea52dd957c 100644 --- a/static/usage/v7/infinite-scroll/basic/vue.md +++ b/static/usage/v7/infinite-scroll/basic/vue.md @@ -23,7 +23,6 @@ IonList, IonItem, IonAvatar, - IonImg, IonLabel, InfiniteScrollCustomEvent, } from '@ionic/vue'; @@ -31,14 +30,12 @@ export default defineComponent({ components: { - IonContent, IonContent, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonItem, IonAvatar, - IonImg, IonLabel, }, setup() { diff --git a/static/usage/v7/infinite-scroll/custom-infinite-scroll-content/vue.md b/static/usage/v7/infinite-scroll/custom-infinite-scroll-content/vue.md index 85954295280..b5fd3bad6e8 100644 --- a/static/usage/v7/infinite-scroll/custom-infinite-scroll-content/vue.md +++ b/static/usage/v7/infinite-scroll/custom-infinite-scroll-content/vue.md @@ -89,7 +89,6 @@ IonList, IonItem, IonAvatar, - IonImg, IonLabel, InfiniteScrollCustomEvent, } from '@ionic/vue'; @@ -97,14 +96,12 @@ export default defineComponent({ components: { - IonContent, IonContent, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonItem, IonAvatar, - IonImg, IonLabel, }, setup() { diff --git a/static/usage/v7/infinite-scroll/infinite-scroll-content/vue.md b/static/usage/v7/infinite-scroll/infinite-scroll-content/vue.md index f00b47e1fb8..6facce2be59 100644 --- a/static/usage/v7/infinite-scroll/infinite-scroll-content/vue.md +++ b/static/usage/v7/infinite-scroll/infinite-scroll-content/vue.md @@ -26,7 +26,6 @@ IonList, IonItem, IonAvatar, - IonImg, IonLabel, InfiniteScrollCustomEvent, } from '@ionic/vue'; @@ -34,14 +33,12 @@ export default defineComponent({ components: { - IonContent, IonContent, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonItem, IonAvatar, - IonImg, IonLabel, }, setup() { diff --git a/static/usage/v8/infinite-scroll/basic/vue.md b/static/usage/v8/infinite-scroll/basic/vue.md index 61c08173971..cea52dd957c 100644 --- a/static/usage/v8/infinite-scroll/basic/vue.md +++ b/static/usage/v8/infinite-scroll/basic/vue.md @@ -23,7 +23,6 @@ IonList, IonItem, IonAvatar, - IonImg, IonLabel, InfiniteScrollCustomEvent, } from '@ionic/vue'; @@ -31,14 +30,12 @@ export default defineComponent({ components: { - IonContent, IonContent, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonItem, IonAvatar, - IonImg, IonLabel, }, setup() { diff --git a/static/usage/v8/infinite-scroll/custom-infinite-scroll-content/vue.md b/static/usage/v8/infinite-scroll/custom-infinite-scroll-content/vue.md index 85954295280..b5fd3bad6e8 100644 --- a/static/usage/v8/infinite-scroll/custom-infinite-scroll-content/vue.md +++ b/static/usage/v8/infinite-scroll/custom-infinite-scroll-content/vue.md @@ -89,7 +89,6 @@ IonList, IonItem, IonAvatar, - IonImg, IonLabel, InfiniteScrollCustomEvent, } from '@ionic/vue'; @@ -97,14 +96,12 @@ export default defineComponent({ components: { - IonContent, IonContent, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonItem, IonAvatar, - IonImg, IonLabel, }, setup() { diff --git a/static/usage/v8/infinite-scroll/infinite-scroll-content/vue.md b/static/usage/v8/infinite-scroll/infinite-scroll-content/vue.md index f00b47e1fb8..6facce2be59 100644 --- a/static/usage/v8/infinite-scroll/infinite-scroll-content/vue.md +++ b/static/usage/v8/infinite-scroll/infinite-scroll-content/vue.md @@ -26,7 +26,6 @@ IonList, IonItem, IonAvatar, - IonImg, IonLabel, InfiniteScrollCustomEvent, } from '@ionic/vue'; @@ -34,14 +33,12 @@ export default defineComponent({ components: { - IonContent, IonContent, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonItem, IonAvatar, - IonImg, IonLabel, }, setup() {