diff --git a/.github/workflows/assign-issues.yml b/.github/workflows/assign-issues.yml index d06c1f52e10..4608d2323dd 100644 --- a/.github/workflows/assign-issues.yml +++ b/.github/workflows/assign-issues.yml @@ -13,6 +13,6 @@ jobs: - name: 'Auto-assign issue' uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0 with: - assignees: brandyscarney, thetaPC, ShaneK + assignees: brandyscarney, ShaneK numOfAssignee: 1 allowSelfAssign: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 144a9ce9cd3..a7120fcc50c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + + +### Bug Fixes + +* **input-otp:** improve autofill detection and invalid character handling ([#30541](https://github.com/ionic-team/ionic-framework/issues/30541)) ([8b4023d](https://github.com/ionic-team/ionic-framework/commit/8b4023d520212c254395a5be6d3a76dcbee6f2da)), closes [#30459](https://github.com/ionic-team/ionic-framework/issues/30459) +* **input:** prevent layout shift when hiding password toggle ([#30533](https://github.com/ionic-team/ionic-framework/issues/30533)) ([f1defba](https://github.com/ionic-team/ionic-framework/commit/f1defba2acb417c6f243b2902923d85efbb6f879)), closes [#29562](https://github.com/ionic-team/ionic-framework/issues/29562) +* **item:** allow nested content to be conditionally interactive ([#30519](https://github.com/ionic-team/ionic-framework/issues/30519)) ([3f730ab](https://github.com/ionic-team/ionic-framework/commit/3f730ab1d77be54d1faf14168eee9e9dc41002d6)), closes [#29763](https://github.com/ionic-team/ionic-framework/issues/29763) +* **modal:** dismiss child modals when parent is dismissed ([#30540](https://github.com/ionic-team/ionic-framework/issues/30540)) ([9b0099f](https://github.com/ionic-team/ionic-framework/commit/9b0099f462fda6d40b49dde1a1c97afbbbee2287)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389) +* **modal:** dismiss modal when parent element is removed from DOM ([#30544](https://github.com/ionic-team/ionic-framework/issues/30544)) ([850338c](https://github.com/ionic-team/ionic-framework/commit/850338cbd5c76addbc2cc3068b93071dea14c0af)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389) +* **modal:** improve card modal background transition from portrait to landscape ([#30551](https://github.com/ionic-team/ionic-framework/issues/30551)) ([d37b9b8](https://github.com/ionic-team/ionic-framework/commit/d37b9b8e468b7b2c9cda8b27fe7019bb905ad2bf)) +* **segment-view:** scroll to correct content when height is not set ([#30547](https://github.com/ionic-team/ionic-framework/issues/30547)) ([d14311f](https://github.com/ionic-team/ionic-framework/commit/d14311fb65ae3de7ba7578791ce1ea44f186c413)), closes [#30543](https://github.com/ionic-team/ionic-framework/issues/30543) + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 3c8d4dca3f6..0bd1d8a9288 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + + +### Bug Fixes + +* **input-otp:** improve autofill detection and invalid character handling ([#30541](https://github.com/ionic-team/ionic-framework/issues/30541)) ([8b4023d](https://github.com/ionic-team/ionic-framework/commit/8b4023d520212c254395a5be6d3a76dcbee6f2da)), closes [#30459](https://github.com/ionic-team/ionic-framework/issues/30459) +* **input:** prevent layout shift when hiding password toggle ([#30533](https://github.com/ionic-team/ionic-framework/issues/30533)) ([f1defba](https://github.com/ionic-team/ionic-framework/commit/f1defba2acb417c6f243b2902923d85efbb6f879)), closes [#29562](https://github.com/ionic-team/ionic-framework/issues/29562) +* **item:** allow nested content to be conditionally interactive ([#30519](https://github.com/ionic-team/ionic-framework/issues/30519)) ([3f730ab](https://github.com/ionic-team/ionic-framework/commit/3f730ab1d77be54d1faf14168eee9e9dc41002d6)), closes [#29763](https://github.com/ionic-team/ionic-framework/issues/29763) +* **modal:** dismiss child modals when parent is dismissed ([#30540](https://github.com/ionic-team/ionic-framework/issues/30540)) ([9b0099f](https://github.com/ionic-team/ionic-framework/commit/9b0099f462fda6d40b49dde1a1c97afbbbee2287)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389) +* **modal:** dismiss modal when parent element is removed from DOM ([#30544](https://github.com/ionic-team/ionic-framework/issues/30544)) ([850338c](https://github.com/ionic-team/ionic-framework/commit/850338cbd5c76addbc2cc3068b93071dea14c0af)), closes [#30389](https://github.com/ionic-team/ionic-framework/issues/30389) +* **modal:** improve card modal background transition from portrait to landscape ([#30551](https://github.com/ionic-team/ionic-framework/issues/30551)) ([d37b9b8](https://github.com/ionic-team/ionic-framework/commit/d37b9b8e468b7b2c9cda8b27fe7019bb905ad2bf)) +* **segment-view:** scroll to correct content when height is not set ([#30547](https://github.com/ionic-team/ionic-framework/issues/30547)) ([d14311f](https://github.com/ionic-team/ionic-framework/commit/d14311fb65ae3de7ba7578791ce1ea44f186c413)), closes [#30543](https://github.com/ionic-team/ionic-framework/issues/30543) + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) diff --git a/core/Dockerfile b/core/Dockerfile index 50bce82ff07..5f24265654f 100644 --- a/core/Dockerfile +++ b/core/Dockerfile @@ -1,5 +1,5 @@ # Get Playwright -FROM mcr.microsoft.com/playwright:v1.53.1 +FROM mcr.microsoft.com/playwright:v1.54.1 # Set the working directory WORKDIR /ionic diff --git a/core/package-lock.json b/core/package-lock.json index 2af538f7f89..14b15f2b403 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.6.3", + "version": "8.6.5", "license": "MIT", "dependencies": { "@phosphor-icons/core": "^2.1.1", @@ -23,7 +23,7 @@ "@clack/prompts": "^0.11.0", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", - "@playwright/test": "^1.53.2", + "@playwright/test": "^1.54.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", "@stencil/angular-output-target": "^0.10.0", @@ -1933,12 +1933,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.2.tgz", - "integrity": "sha512-tEB2U5z74ebBeyfGNZ3Jfg29AnW+5HlWhvHtb/Mqco9pFdZU1ZLNdVb2UtB5CvmiilNr2ZfVH/qMmAROG/XTzw==", + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz", + "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==", "dev": true, "dependencies": { - "playwright": "1.53.2" + "playwright": "1.54.1" }, "bin": { "playwright": "cli.js" @@ -9680,12 +9680,12 @@ } }, "node_modules/playwright": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", - "integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==", + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz", + "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==", "dev": true, "dependencies": { - "playwright-core": "1.53.2" + "playwright-core": "1.54.1" }, "bin": { "playwright": "cli.js" @@ -9698,9 +9698,9 @@ } }, "node_modules/playwright-core": { - "version": "1.53.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz", - "integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==", + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz", + "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==", "dev": true, "bin": { "playwright-core": "cli.js" diff --git a/core/package.json b/core/package.json index 6e50a52641b..11cfa4f1265 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.6.4", + "version": "8.6.5", "description": "Base components for Ionic", "keywords": [ "ionic", @@ -45,7 +45,7 @@ "@clack/prompts": "^0.11.0", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", - "@playwright/test": "^1.53.2", + "@playwright/test": "^1.54.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", "@stencil/angular-output-target": "^0.10.0", diff --git a/core/src/components/input-otp/input-otp.tsx b/core/src/components/input-otp/input-otp.tsx index 7c3de512860..3e8684505ab 100644 --- a/core/src/components/input-otp/input-otp.tsx +++ b/core/src/components/input-otp/input-otp.tsx @@ -49,6 +49,7 @@ export class InputOTP implements ComponentInterface { @State() private inputValues: string[] = []; @State() hasFocus = false; + @State() private previousInputValues: string[] = []; /** * Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user. @@ -337,6 +338,7 @@ export class InputOTP implements ComponentInterface { }); // Update the value without emitting events this.value = this.inputValues.join(''); + this.previousInputValues = [...this.inputValues]; } /** @@ -526,19 +528,12 @@ export class InputOTP implements ComponentInterface { } /** - * Handles keyboard navigation and input for the OTP component. + * Handles keyboard navigation for the OTP component. * * Navigation: * - Backspace: Clears current input and moves to previous box if empty * - Arrow Left/Right: Moves focus between input boxes * - Tab: Allows normal tab navigation between components - * - * Input Behavior: - * - Validates input against the allowed pattern - * - When entering a key in a filled box: - * - Shifts existing values right if there is room - * - Updates the value of the input group - * - Prevents default behavior to avoid automatic focus shift */ private onKeyDown = (index: number) => (event: KeyboardEvent) => { const { length } = this; @@ -596,34 +591,32 @@ export class InputOTP implements ComponentInterface { // Let all tab events proceed normally return; } - - // If the input box contains a value and the key being - // entered is a valid key for the input box update the value - // and shift the values to the right if there is room. - if (this.inputValues[index] && this.validKeyPattern.test(event.key)) { - if (!this.inputValues[length - 1]) { - for (let i = length - 1; i > index; i--) { - this.inputValues[i] = this.inputValues[i - 1]; - this.inputRefs[i].value = this.inputValues[i] || ''; - } - } - this.inputValues[index] = event.key; - this.inputRefs[index].value = event.key; - this.updateValue(event); - - // Prevent default to avoid the browser from - // automatically moving the focus to the next input - event.preventDefault(); - } }; + /** + * Processes all input scenarios for each input box. + * + * This function manages: + * 1. Autofill handling + * 2. Input validation + * 3. Full selection replacement or typing in an empty box + * 4. Inserting in the middle with available space (shifting) + * 5. Single character replacement + */ private onInput = (index: number) => (event: InputEvent) => { const { length, validKeyPattern } = this; - const value = (event.target as HTMLInputElement).value; - - // If the value is longer than 1 character (autofill), split it into - // characters and filter out invalid ones - if (value.length > 1) { + const input = event.target as HTMLInputElement; + const value = input.value; + const previousValue = this.previousInputValues[index] || ''; + + // 1. Autofill handling + // If the length of the value increases by more than 1 from the previous + // value, treat this as autofill. This is to prevent the case where the + // user is typing a single character into an input box containing a value + // as that will trigger this function with a value length of 2 characters. + const isAutofill = value.length - previousValue.length > 1; + if (isAutofill) { + // Distribute valid characters across input boxes const validChars = value .split('') .filter((char) => validKeyPattern.test(char)) @@ -640,8 +633,10 @@ export class InputOTP implements ComponentInterface { }); } - // Update the value of the input group and emit the input change event - this.value = validChars.join(''); + for (let i = 0; i < length; i++) { + this.inputValues[i] = validChars[i] || ''; + this.inputRefs[i].value = validChars[i] || ''; + } this.updateValue(event); // Focus the first empty input box or the last input box if all boxes @@ -652,23 +647,85 @@ export class InputOTP implements ComponentInterface { this.inputRefs[nextIndex]?.focus(); }, 20); + this.previousInputValues = [...this.inputValues]; return; } - // Only allow input if it matches the pattern - if (value.length > 0 && !validKeyPattern.test(value)) { - this.inputRefs[index].value = ''; - this.inputValues[index] = ''; + // 2. Input validation + // If the character entered is invalid (does not match the pattern), + // restore the previous value and exit + if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) { + input.value = this.inputValues[index] || ''; + this.previousInputValues = [...this.inputValues]; return; } - // For single character input, fill the current box - this.inputValues[index] = value; - this.updateValue(event); - - if (value.length > 0) { + // 3. Full selection replacement or typing in an empty box + // If the user selects all text in the input box and types, or if the + // input box is empty, replace only this input box. If the box is empty, + // move to the next box, otherwise stay focused on this box. + const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length; + const isEmpty = !this.inputValues[index]; + if (isAllSelected || isEmpty) { + this.inputValues[index] = value; + input.value = value; + this.updateValue(event); this.focusNext(index); + this.previousInputValues = [...this.inputValues]; + return; } + + // 4. Inserting in the middle with available space (shifting) + // If typing in a filled input box and there are empty boxes at the end, + // shift all values starting at the current box to the right, and insert + // the new character at the current box. + const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === ''; + if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) { + // Get the inserted character (from event or by diffing value/previousValue) + let newChar = (event as InputEvent).data; + if (!newChar) { + newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1]; + } + // Validate the new character before shifting + if (!validKeyPattern.test(newChar)) { + input.value = this.inputValues[index] || ''; + this.previousInputValues = [...this.inputValues]; + return; + } + // Shift values right from the end to the insertion point + for (let i = this.inputValues.length - 1; i > index; i--) { + this.inputValues[i] = this.inputValues[i - 1]; + this.inputRefs[i].value = this.inputValues[i] || ''; + } + this.inputValues[index] = newChar; + this.inputRefs[index].value = newChar; + this.updateValue(event); + this.previousInputValues = [...this.inputValues]; + return; + } + + // 5. Single character replacement + // Handles replacing a single character in a box containing a value based + // on the cursor position. We need the cursor position to determine which + // character was the last character typed. For example, if the user types "2" + // in an input box with the cursor at the beginning of the value of "6", + // the value will be "26", but we want to grab the "2" as the last character + // typed. + const cursorPos = input.selectionStart ?? value.length; + const newCharIndex = cursorPos - 1; + const newChar = value[newCharIndex] ?? value[0]; + + // Check if the new character is valid before updating the value + if (!validKeyPattern.test(newChar)) { + input.value = this.inputValues[index] || ''; + this.previousInputValues = [...this.inputValues]; + return; + } + + this.inputValues[index] = newChar; + input.value = newChar; + this.updateValue(event); + this.previousInputValues = [...this.inputValues]; }; /** @@ -712,12 +769,8 @@ export class InputOTP implements ComponentInterface { // Focus the next empty input after pasting // If all boxes are filled, focus the last input - const nextEmptyIndex = validChars.length; - if (nextEmptyIndex < length) { - inputRefs[nextEmptyIndex]?.focus(); - } else { - inputRefs[length - 1]?.focus(); - } + const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1; + inputRefs[nextEmptyIndex]?.focus(); }; /** diff --git a/core/src/components/input-otp/test/basic/input-otp.e2e.ts b/core/src/components/input-otp/test/basic/input-otp.e2e.ts index 2a50c1abd5c..2067a000209 100644 --- a/core/src/components/input-otp/test/basic/input-otp.e2e.ts +++ b/core/src/components/input-otp/test/basic/input-otp.e2e.ts @@ -442,6 +442,67 @@ configs({ modes: ['ios'] }).forEach(({ title, config }) => { await verifyInputValues(inputOtp, ['1', '9', '3', '']); }); + + test('should replace the last value when typing one more than the length', async ({ page }) => { + await page.setContent(`Description`, config); + + const inputOtp = page.locator('ion-input-otp'); + const firstInput = inputOtp.locator('input').first(); + await firstInput.focus(); + + await page.keyboard.type('12345'); + + await verifyInputValues(inputOtp, ['1', '2', '3', '5']); + }); + + test('should replace the last value when typing one more than the length and the type is text', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30459', + }); + + await page.setContent(`Description`, config); + + const inputOtp = page.locator('ion-input-otp'); + const firstInput = inputOtp.locator('input').first(); + await firstInput.focus(); + + await page.keyboard.type('abcde'); + + await verifyInputValues(inputOtp, ['a', 'b', 'c', 'e']); + }); + + test('should not insert or shift when typing an invalid character before a number', async ({ page }) => { + await page.setContent(`Description`, config); + + const inputOtp = page.locator('ion-input-otp'); + const firstInput = inputOtp.locator('input').first(); + await firstInput.focus(); + + // Move cursor to the start of the first input + await firstInput.evaluate((el: HTMLInputElement) => el.setSelectionRange(0, 0)); + + await page.keyboard.type('w'); + + await verifyInputValues(inputOtp, ['1', '2', '', '']); + }); + + test('should not insert or shift when typing an invalid character after a number', async ({ page }) => { + await page.setContent(`Description`, config); + + const inputOtp = page.locator('ion-input-otp'); + const firstInput = inputOtp.locator('input').first(); + await firstInput.focus(); + + // Move cursor to the end of the first input + await firstInput.evaluate((el: HTMLInputElement) => el.setSelectionRange(1, 1)); + + await page.keyboard.type('w'); + + await verifyInputValues(inputOtp, ['1', '2', '', '']); + }); }); test.describe(title('input-otp: autofill functionality'), () => { @@ -460,6 +521,53 @@ configs({ modes: ['ios'] }).forEach(({ title, config }) => { await expect(lastInput).toBeFocused(); }); + test('should handle autofill correctly when all characters are the same', async ({ page }) => { + await page.setContent(`Description`, config); + + const firstInput = page.locator('ion-input-otp input').first(); + await firstInput.focus(); + + await simulateAutofill(firstInput, '1111'); + + const inputOtp = page.locator('ion-input-otp'); + await verifyInputValues(inputOtp, ['1', '1', '1', '1']); + + const lastInput = page.locator('ion-input-otp input').last(); + await expect(lastInput).toBeFocused(); + }); + + test('should handle autofill correctly when length is 2', async ({ page }) => { + await page.setContent(`Description`, config); + + const firstInput = page.locator('ion-input-otp input').first(); + await firstInput.focus(); + + await simulateAutofill(firstInput, '12'); + + const inputOtp = page.locator('ion-input-otp'); + await verifyInputValues(inputOtp, ['1', '2']); + + const lastInput = page.locator('ion-input-otp input').last(); + await expect(lastInput).toBeFocused(); + }); + + test('should handle autofill correctly when length is 2 after typing 1 character', async ({ page }) => { + await page.setContent(`Description`, config); + + await page.keyboard.type('1'); + + const secondInput = page.locator('ion-input-otp input').nth(1); + await secondInput.focus(); + + await simulateAutofill(secondInput, '22'); + + const inputOtp = page.locator('ion-input-otp'); + await verifyInputValues(inputOtp, ['2', '2']); + + const lastInput = page.locator('ion-input-otp input').last(); + await expect(lastInput).toBeFocused(); + }); + test('should handle autofill correctly when it exceeds the length', async ({ page }) => { await page.setContent(`Description`, config); diff --git a/core/src/components/input/input.common.scss b/core/src/components/input/input.common.scss index 24da3c4c9e8..6cb8e8605d9 100644 --- a/core/src/components/input/input.common.scss +++ b/core/src/components/input/input.common.scss @@ -508,5 +508,5 @@ */ :host([disabled]) ::slotted(ion-input-password-toggle), :host([readonly]) ::slotted(ion-input-password-toggle) { - display: none; + visibility: hidden; } diff --git a/core/src/components/input/test/states/input.e2e.ts b/core/src/components/input/test/states/input.e2e.ts index 0f41e197d7f..a35e1527011 100644 --- a/core/src/components/input/test/states/input.e2e.ts +++ b/core/src/components/input/test/states/input.e2e.ts @@ -26,6 +26,70 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { const input = page.locator('ion-input'); await expect(input).toHaveScreenshot(screenshot(`input-disabled`)); }); + + test('should maintain consistent height when password toggle is hidden on disabled input', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/29562', + }); + await page.setContent( + ` + + + + `, + config + ); + + const input = page.locator('ion-input'); + + // Get the height when input is enabled + const enabledHeight = await input.boundingBox().then((box) => box?.height); + + // Disable the input + await input.evaluate((el) => el.setAttribute('disabled', 'true')); + await page.waitForChanges(); + + // Get the height when input is disabled + const disabledHeight = await input.boundingBox().then((box) => box?.height); + + // Verify heights are the same + expect(enabledHeight).toBe(disabledHeight); + }); + + test('should maintain consistent height when password toggle is hidden on readonly input', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/29562', + }); + await page.setContent( + ` + + + + `, + config + ); + + const input = page.locator('ion-input'); + + // Get the height when input is enabled + const enabledHeight = await input.boundingBox().then((box) => box?.height); + + // Make the input readonly + await input.evaluate((el) => el.setAttribute('readonly', 'true')); + await page.waitForChanges(); + + // Get the height when input is readonly + const readonlyHeight = await input.boundingBox().then((box) => box?.height); + + // Verify heights are the same + expect(enabledHeight).toBe(readonlyHeight); + }); }); }); diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx index 1b7e29dcf26..e7c95b4ade9 100644 --- a/core/src/components/item/item.tsx +++ b/core/src/components/item/item.tsx @@ -41,6 +41,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac @State() multipleInputs = false; @State() focusable = true; + @State() isInteractive = false; /** * The color to use from your application's color palette. @@ -176,14 +177,12 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac componentDidLoad() { raf(() => { this.setMultipleInputs(); + this.setIsInteractive(); this.focusable = this.isFocusable(); }); } - // If the item contains multiple clickable elements and/or inputs, then the item - // should not have a clickable input cover over the entire item to prevent - // interfering with their individual click events - private setMultipleInputs() { + private totalNestedInputs() { // The following elements have a clickable cover that is relative to the entire item const covers = this.el.querySelectorAll('ion-checkbox, ion-datetime, ion-select, ion-radio'); @@ -197,6 +196,19 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac // The following elements should also stay clickable when an input with cover is present const clickables = this.el.querySelectorAll('ion-router-link, ion-button, a, button'); + return { + covers, + inputs, + clickables, + }; + } + + // If the item contains multiple clickable elements and/or inputs, then the item + // should not have a clickable input cover over the entire item to prevent + // interfering with their individual click events + private setMultipleInputs() { + const { covers, inputs, clickables } = this.totalNestedInputs(); + // Check for multiple inputs to change the position of the input cover to relative // for all of the covered inputs above this.multipleInputs = @@ -205,6 +217,19 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac (covers.length > 0 && this.isClickable()); } + private setIsInteractive() { + // If item contains any interactive children, set isInteractive to `true` + const { covers, inputs, clickables } = this.totalNestedInputs(); + + this.isInteractive = covers.length > 0 || inputs.length > 0 || clickables.length > 0; + } + + // slot change listener updates state to reflect how/if item should be interactive + private updateInteractivityOnSlotChange = () => { + this.setIsInteractive(); + this.setMultipleInputs(); + }; + // If the item contains an input including a checkbox, datetime, select, or radio // then the item will have a clickable input cover that covers the item // that should get the hover, focused and activated states UNLESS it has multiple @@ -398,12 +423,12 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac disabled={disabled} {...clickFn} > - +
- +
- + {showDetail && ( { await expect(list).toHaveScreenshot(screenshot(`item-inputs-div-with-inputs`)); }); + + test('should update interactivity state when elements are conditionally rendered', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/29763', + }); + + await page.setContent( + ` + + + Conditional Checkbox + + + `, + config + ); + + const item = page.locator('ion-item'); + + await page.evaluate(() => { + const item = document.querySelector('ion-item'); + const checkbox = document.createElement('ion-checkbox'); + item?.appendChild(checkbox); + }); + + await page.waitForChanges(); + + const checkbox = page.locator('ion-checkbox'); + await expect(checkbox).not.toBeChecked(); + + // Test that clicking on the left edge of the item toggles the checkbox + await item.click({ + position: { + x: 5, + y: 5, + }, + }); + + await expect(checkbox).toBeChecked(); + }); }); }); diff --git a/core/src/components/modal/animations/ios.transition.ts b/core/src/components/modal/animations/ios.transition.ts index 6ce2cd75e16..264286db6bf 100644 --- a/core/src/components/modal/animations/ios.transition.ts +++ b/core/src/components/modal/animations/ios.transition.ts @@ -72,22 +72,22 @@ export const portraitToLandscapeTransition = ( // need to care about layering and modal-specific styles. const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE; const fromTransform = `translateY(-10px) scale(${toPresentingScale})`; - const toTransform = `translateY(-10px) scale(${toPresentingScale})`; + const toTransform = `translateY(0px) scale(1)`; presentingAnimation - .addElement(presentingElRoot.querySelector('.modal-wrapper')!) + .addElement(presentingEl) .afterStyles({ transform: toTransform, }) .fromTo('transform', fromTransform, toTransform) - .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card + .fromTo('filter', 'contrast(0.85)', 'contrast(1)'); const shadowAnimation = createAnimation() .addElement(presentingElRoot.querySelector('.modal-shadow')!) .afterStyles({ transform: toTransform, + opacity: '0', }) - .fromTo('opacity', '0', '0') // Shadow stays hidden in landscape for card modals .fromTo('transform', fromTransform, toTransform); baseAnimation.addAnimation([presentingAnimation, shadowAnimation]); @@ -148,17 +148,8 @@ export const landscapeToPortraitTransition = ( presentingAnimation .addElement(presentingEl) - .beforeStyles({ - transform: 'translateY(0px) scale(1)', - 'transform-origin': 'top center', - overflow: 'hidden', - }) .afterStyles({ transform: toTransform, - 'border-radius': '10px 10px 0 0', - filter: 'contrast(0.85)', - overflow: 'hidden', - 'transform-origin': 'top center', }) .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black')) .keyframes([ @@ -173,22 +164,21 @@ export const landscapeToPortraitTransition = ( // to handle layering and modal-specific styles. const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE; const fromTransform = `translateY(-10px) scale(${toPresentingScale})`; - const toTransform = `translateY(-10px) scale(${toPresentingScale})`; + const toTransform = `translateY(0) scale(1)`; presentingAnimation - .addElement(presentingElRoot.querySelector('.modal-wrapper')!) + .addElement(presentingEl) .afterStyles({ transform: toTransform, }) - .fromTo('transform', fromTransform, toTransform) - .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card + .fromTo('transform', fromTransform, toTransform); const shadowAnimation = createAnimation() .addElement(presentingElRoot.querySelector('.modal-shadow')!) .afterStyles({ transform: toTransform, + opacity: '0', }) - .fromTo('opacity', '0', '0') // Shadow stays hidden .fromTo('transform', fromTransform, toTransform); baseAnimation.addAnimation([presentingAnimation, shadowAnimation]); diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 46d176d101e..8dd6acffa8c 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -98,6 +98,11 @@ export class Modal implements ComponentInterface, OverlayInterface { private viewTransitionAnimation?: Animation; private resizeTimeout?: any; + // Mutation observer to watch for parent removal + private parentRemovalObserver?: MutationObserver; + // Cached original parent from before modal is moved to body during presentation + private cachedOriginalParent?: HTMLElement; + lastFocus?: HTMLElement; animation?: Animation; @@ -409,6 +414,7 @@ export class Modal implements ComponentInterface, OverlayInterface { disconnectedCallback() { this.triggerController.removeClickListener(); this.cleanupViewTransitionListener(); + this.cleanupParentRemovalObserver(); } componentWillLoad() { @@ -418,6 +424,11 @@ export class Modal implements ComponentInterface, OverlayInterface { const attributesToInherit = ['aria-label', 'role']; this.inheritedAttributes = inheritAttributes(el, attributesToInherit); + // Cache original parent before modal gets moved to body during presentation + if (el.parentNode) { + this.cachedOriginalParent = el.parentNode as HTMLElement; + } + /** * When using a controller modal you can set attributes * using the htmlAttributes property. Since the above attributes @@ -654,6 +665,9 @@ export class Modal implements ComponentInterface, OverlayInterface { // Initialize view transition listener for iOS card modals this.initViewTransitionListener(); + // Initialize parent removal observer + this.initParentRemovalObserver(); + unlock(); } @@ -798,6 +812,13 @@ export class Modal implements ComponentInterface, OverlayInterface { */ const unlock = await this.lockController.lock(); + /** + * Dismiss all child modals. This is especially important in + * Angular and React because it's possible to lose control of a child + * modal when the parent modal is dismissed. + */ + await this.dismissNestedModals(); + /** * If a canDismiss handler is responsible * for calling the dismiss method, we should @@ -854,6 +875,7 @@ export class Modal implements ComponentInterface, OverlayInterface { this.gesture.destroy(); } this.cleanupViewTransitionListener(); + this.cleanupParentRemovalObserver(); } this.currentBreakpoint = undefined; this.animation = undefined; @@ -1130,7 +1152,7 @@ export class Modal implements ComponentInterface, OverlayInterface { wrapperEl.style.opacity = '1'; } - if (presentingElement) { + if (presentingElement?.tagName === 'ION-MODAL') { const isPortrait = window.innerWidth < 768; if (isPortrait) { @@ -1145,6 +1167,89 @@ export class Modal implements ComponentInterface, OverlayInterface { } } + /** + * When the slot changes, we need to find all the modals in the slot + * and set the data-parent-ion-modal attribute on them so we can find them + * and dismiss them when we get dismissed. + * We need to do it this way because when a modal is opened, it's moved to + * the end of the body and is no longer an actual child of the modal. + */ + private onSlotChange = ({ target }: Event) => { + const slot = target as HTMLSlotElement; + slot.assignedElements().forEach((el) => { + el.querySelectorAll('ion-modal').forEach((childModal) => { + // We don't need to write to the DOM if the modal is already tagged + // If this is a deeply nested modal, this effect should cascade so we don't + // need to worry about another modal claiming the same child. + if (childModal.getAttribute('data-parent-ion-modal') === null) { + childModal.setAttribute('data-parent-ion-modal', this.el.id); + } + }); + }); + }; + + private async dismissNestedModals(): Promise { + const nestedModals = document.querySelectorAll(`ion-modal[data-parent-ion-modal="${this.el.id}"]`); + nestedModals?.forEach(async (modal) => { + await (modal as HTMLIonModalElement).dismiss(undefined, 'parent-dismissed'); + }); + } + + private initParentRemovalObserver() { + if (typeof MutationObserver === 'undefined') { + return; + } + + // Only observe if we have a cached parent and are in browser environment + if (typeof window === 'undefined' || !this.cachedOriginalParent) { + return; + } + + // Don't observe document or fragment nodes as they can't be "removed" + if ( + this.cachedOriginalParent.nodeType === Node.DOCUMENT_NODE || + this.cachedOriginalParent.nodeType === Node.DOCUMENT_FRAGMENT_NODE + ) { + return; + } + + this.parentRemovalObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'childList' && mutation.removedNodes.length > 0) { + // Check if our cached original parent was removed + const cachedParentWasRemoved = Array.from(mutation.removedNodes).some((node) => { + const isDirectMatch = node === this.cachedOriginalParent; + const isContainedMatch = this.cachedOriginalParent + ? (node as HTMLElement).contains?.(this.cachedOriginalParent) + : false; + return isDirectMatch || isContainedMatch; + }); + + // Also check if parent is no longer connected to DOM + const cachedParentDisconnected = this.cachedOriginalParent && !this.cachedOriginalParent.isConnected; + + if (cachedParentWasRemoved || cachedParentDisconnected) { + this.dismiss(undefined, 'parent-removed'); + // Release the reference to the cached original parent + // so we don't have a memory leak + this.cachedOriginalParent = undefined; + } + } + }); + }); + + // Observe document body with subtree to catch removals at any level + this.parentRemovalObserver.observe(document.body, { + childList: true, + subtree: true, + }); + } + + private cleanupParentRemovalObserver() { + this.parentRemovalObserver?.disconnect(); + this.parentRemovalObserver = undefined; + } + render() { const { handle, @@ -1224,7 +1329,7 @@ export class Modal implements ComponentInterface, OverlayInterface { ref={(el) => (this.dragHandleEl = el)} > )} - +
); diff --git a/core/src/components/modal/test/inline/index.html b/core/src/components/modal/test/inline/index.html index 726b682bd86..40a8eadb1a9 100644 --- a/core/src/components/modal/test/inline/index.html +++ b/core/src/components/modal/test/inline/index.html @@ -22,31 +22,91 @@ - - - - - - Modal - - - This is my inline modal content! - + diff --git a/core/src/components/modal/test/inline/modal.e2e.ts b/core/src/components/modal/test/inline/modal.e2e.ts index 05276722d95..2f6ef95070a 100644 --- a/core/src/components/modal/test/inline/modal.e2e.ts +++ b/core/src/components/modal/test/inline/modal.e2e.ts @@ -7,7 +7,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => await page.goto('/src/components/modal/test/inline', config); const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); - const modal = page.locator('ion-modal'); + const modal = page.locator('ion-modal').first(); await page.click('#open-inline-modal'); @@ -22,6 +22,67 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => await expect(modal).toBeHidden(); }); + test('it should dismiss child modals when parent modal is dismissed', async ({ page }) => { + await page.goto('/src/components/modal/test/inline', config); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + const parentModal = page.locator('ion-modal').first(); + const childModal = page.locator('#child-modal'); + + // Open the parent modal + await page.click('#open-inline-modal'); + await ionModalDidPresent.next(); + await expect(parentModal).toBeVisible(); + + // Open the child modal + await page.click('#open-child-modal'); + await ionModalDidPresent.next(); + await expect(childModal).toBeVisible(); + + // Both modals should be visible + await expect(parentModal).toBeVisible(); + await expect(childModal).toBeVisible(); + + // Dismiss the parent modal + await page.click('#dismiss-parent'); + + // Wait for both modals to be dismissed + await ionModalDidDismiss.next(); // child modal dismissed + await ionModalDidDismiss.next(); // parent modal dismissed + + // Both modals should be hidden + await expect(parentModal).toBeHidden(); + await expect(childModal).toBeHidden(); + }); + + test('it should only dismiss child modal when child dismiss button is clicked', async ({ page }) => { + await page.goto('/src/components/modal/test/inline', config); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + const parentModal = page.locator('ion-modal').first(); + const childModal = page.locator('#child-modal'); + + // Open the parent modal + await page.click('#open-inline-modal'); + await ionModalDidPresent.next(); + await expect(parentModal).toBeVisible(); + + // Open the child modal + await page.click('#open-child-modal'); + await ionModalDidPresent.next(); + await expect(childModal).toBeVisible(); + + // Dismiss only the child modal + await page.click('#dismiss-child'); + await ionModalDidDismiss.next(); + + // Parent modal should still be visible, child modal should be hidden + await expect(parentModal).toBeVisible(); + await expect(childModal).toBeHidden(); + }); + test('presenting should create a single root element with the ion-page class', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'issue', @@ -61,5 +122,152 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => await modal.evaluate((el: HTMLIonModalElement) => el.firstElementChild!.firstElementChild!.className) ).not.toContain('ion-page'); }); + + test('it should dismiss modal when parent container is removed from DOM', async ({ page }) => { + await page.goto('/src/components/modal/test/inline', config); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + const modal = page.locator('ion-modal').first(); + const modalContainer = page.locator('#modal-container'); + + // Open the modal + await page.click('#open-inline-modal'); + await ionModalDidPresent.next(); + await expect(modal).toBeVisible(); + + // Remove the modal container from DOM + await page.click('#remove-modal-container'); + + // Wait for modal to be dismissed + const dismissEvent = await ionModalDidDismiss.next(); + + // Verify the modal was dismissed with the correct role + expect(dismissEvent.detail.role).toBe('parent-removed'); + + // Verify the modal is no longer visible + await expect(modal).toBeHidden(); + + // Verify the container was actually removed + await expect(modalContainer).not.toBeAttached(); + }); + + test('it should dismiss both parent and child modals when parent container is removed from DOM', async ({ + page, + }) => { + await page.goto('/src/components/modal/test/inline', config); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + const parentModal = page.locator('ion-modal').first(); + const childModal = page.locator('#child-modal'); + const modalContainer = page.locator('#modal-container'); + + // Open the parent modal + await page.click('#open-inline-modal'); + await ionModalDidPresent.next(); + await expect(parentModal).toBeVisible(); + + // Open the child modal + await page.click('#open-child-modal'); + await ionModalDidPresent.next(); + await expect(childModal).toBeVisible(); + + // Remove the modal container from DOM + await page.click('#child-remove-modal-container'); + + // Wait for both modals to be dismissed + const firstDismissEvent = await ionModalDidDismiss.next(); + const secondDismissEvent = await ionModalDidDismiss.next(); + + // Verify at least one modal was dismissed with 'parent-removed' role + const dismissRoles = [firstDismissEvent.detail.role, secondDismissEvent.detail.role]; + expect(dismissRoles).toContain('parent-removed'); + + // Verify both modals are no longer visible + await expect(parentModal).toBeHidden(); + await expect(childModal).toBeHidden(); + + // Verify the container was actually removed + await expect(modalContainer).not.toBeAttached(); + }); + + test('it should dismiss modals when top-level ancestor is removed', async ({ page }) => { + // We need to make sure we can close a modal when a much higher + // element is removed from the DOM. This will be a common + // use case in frameworks like Angular and React, where an entire + // page container for much more than the modal might be swapped out. + await page.setContent( + ` + +
+ + + Top Level Removal Test + + + +
+
+ + + + + Nested Modal + + + +

This modal's original parent is deeply nested

+ +
+
+
+
+
+
+
+ + + `, + config + ); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + const nestedModal = page.locator('#nested-modal'); + const topLevelContainer = page.locator('#top-level-container'); + + // Open the nested modal + await page.click('#open-nested-modal'); + await ionModalDidPresent.next(); + await expect(nestedModal).toBeVisible(); + + // Remove the top-level container + await page.click('#remove-top-level'); + + // Wait for modal to be dismissed + const dismissEvent = await ionModalDidDismiss.next(); + + // Verify the modal was dismissed with the correct role + expect(dismissEvent.detail.role).toBe('parent-removed'); + + // Verify the modal is no longer visible + await expect(nestedModal).toBeHidden(); + + // Verify the container was actually removed + await expect(topLevelContainer).not.toBeAttached(); + }); }); }); diff --git a/core/src/components/segment-content/segment-content.scss b/core/src/components/segment-content/segment-content.scss index 38616a7d90c..1abfe6171f3 100644 --- a/core/src/components/segment-content/segment-content.scss +++ b/core/src/components/segment-content/segment-content.scss @@ -9,6 +9,12 @@ width: 100%; + // Workaround for a Safari/WebKit bug where flexbox children with dynamic + // height (e.g., height: fit-content) are not included in the scrollable area + // when using horizontal scrolling. This is needed to make the segment view + // scroll to the correct content. + min-height: 1px; + overflow-y: scroll; /* Hide scrollbar in Firefox */ diff --git a/core/src/components/segment-view/test/dynamic-height/index.html b/core/src/components/segment-view/test/dynamic-height/index.html new file mode 100644 index 00000000000..86c64db0182 --- /dev/null +++ b/core/src/components/segment-view/test/dynamic-height/index.html @@ -0,0 +1,75 @@ + + + + + Segment View - Dynamic Height + + + + + + + + + + + + + + + Segment View - Dynamic Height + + + + + + + First + + + Second + + + Third + + + + + Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora + quaeritis. Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. + Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat + cerebella viventium. Qui animated corpse, cricket bat max brucks terribilem incessu zomby. The voodoo + sacerdos flesh eater, suscitat mortuos comedere carnem virus. Zonbi tattered for solum oculi eorum defunctis + go lum cerebro. Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv + + + + + + + + + + + + diff --git a/core/src/components/segment-view/test/dynamic-height/segment-view.e2e.ts b/core/src/components/segment-view/test/dynamic-height/segment-view.e2e.ts new file mode 100644 index 00000000000..add7d587e73 --- /dev/null +++ b/core/src/components/segment-view/test/dynamic-height/segment-view.e2e.ts @@ -0,0 +1,85 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('segment-view: dynamic height'), () => { + test('should show the third content when clicking the third button', async ({ page, skip }) => { + // Skip this test on Chrome and Firefox + skip.browser('firefox', 'Original issue only happens on Safari.'); + skip.browser('chromium', 'Original issue only happens on Safari.'); + + await page.setContent( + ` + + + + + First + + + Second + + + Third + + + + + Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora + quaeritis. Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum + mauris. Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus + comedat cerebella viventium. Qui animated corpse, cricket bat max brucks terribilem incessu zomby. The + voodoo sacerdos flesh eater, suscitat mortuos comedere carnem virus. Zonbi tattered for solum oculi eorum + defunctis go lum cerebro. Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv + + + + + + + + + `, + config + ); + + // Click the third button + await page.locator('ion-segment-button[value="third"]').click(); + + // Wait for the content to be scrolled + await page.waitForChanges(); + + // Wait for the image to load and be visible + const imgLocator = page.locator('ion-segment-content#third ion-img'); + await imgLocator.waitFor({ state: 'visible', timeout: 10000 }); + + // Wait for any layout adjustments + await page.waitForChanges(); + + // Check that the third content is visible + const segmentView = page.locator('ion-segment-view'); + const thirdContent = page.locator('ion-segment-content#third'); + + const viewBox = await segmentView.boundingBox(); + const contentBox = await thirdContent.boundingBox(); + + if (!viewBox || !contentBox) throw new Error('Bounding box not found'); + + // Allow a small tolerance to account for subpixel rendering, + // scrollbars, or layout rounding differences + const tolerance = 10; + expect(contentBox.x).toBeGreaterThanOrEqual(viewBox.x); + expect(contentBox.x + contentBox.width).toBeLessThanOrEqual(viewBox.x + viewBox.width + tolerance); + }); + }); +}); diff --git a/lerna.json b/lerna.json index 4d75984f281..63ab582860d 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "core", "packages/*" ], - "version": "8.6.4" + "version": "8.6.5" } \ No newline at end of file diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index 69cdfcf6370..8add3f58094 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + +**Note:** Version bump only for package @ionic/angular-server + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) **Note:** Version bump only for package @ionic/angular-server diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json index 1d5d308d627..c336702d600 100644 --- a/packages/angular-server/package-lock.json +++ b/packages/angular-server/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular-server", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular-server", - "version": "8.6.4", + "version": "8.6.5", "license": "MIT", "dependencies": { - "@ionic/core": "^8.6.4" + "@ionic/core": "^8.6.5" }, "devDependencies": { "@angular-eslint/eslint-plugin": "^16.0.0", @@ -1031,9 +1031,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "license": "MIT", "dependencies": { "@stencil/core": "4.33.1", @@ -7305,9 +7305,9 @@ "dev": true }, "@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "requires": { "@stencil/core": "4.33.1", "ionicons": "^7.2.2", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index fa5b3edc354..9c02d228b9a 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "8.6.4", + "version": "8.6.5", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -62,6 +62,6 @@ }, "prettier": "@ionic/prettier-config", "dependencies": { - "@ionic/core": "^8.6.4" + "@ionic/core": "^8.6.5" } } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index 26827e175ec..9a393339b3d 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + +**Note:** Version bump only for package @ionic/angular + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) **Note:** Version bump only for package @ionic/angular diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json index 72858620884..0c385bc117c 100644 --- a/packages/angular/package-lock.json +++ b/packages/angular/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular", - "version": "8.6.4", + "version": "8.6.5", "license": "MIT", "dependencies": { - "@ionic/core": "^8.6.4", + "@ionic/core": "^8.6.5", "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -1398,9 +1398,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "license": "MIT", "dependencies": { "@stencil/core": "4.33.1", @@ -9936,9 +9936,9 @@ "dev": true }, "@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "requires": { "@stencil/core": "4.33.1", "ionicons": "^7.2.2", diff --git a/packages/angular/package.json b/packages/angular/package.json index ecaf224de24..aa2beee1d86 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "8.6.4", + "version": "8.6.5", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -47,7 +47,7 @@ } }, "dependencies": { - "@ionic/core": "^8.6.4", + "@ionic/core": "^8.6.5", "ionicons": "^7.0.0", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md index fe7792cba5d..44fa2927edd 100644 --- a/packages/docs/CHANGELOG.md +++ b/packages/docs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) **Note:** Version bump only for package @ionic/docs diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json index da0689b4eb4..857cd50c5b4 100644 --- a/packages/docs/package-lock.json +++ b/packages/docs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/docs", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/docs", - "version": "8.6.4", + "version": "8.6.5", "license": "MIT" } } diff --git a/packages/docs/package.json b/packages/docs/package.json index 7e1538c8f62..beb4fc44203 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "8.6.4", + "version": "8.6.5", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index 6df92fff6c9..87d74151bda 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) **Note:** Version bump only for package @ionic/react-router diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index 596ad244e08..5f9442038b9 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react-router", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react-router", - "version": "8.6.4", + "version": "8.6.5", "license": "MIT", "dependencies": { - "@ionic/react": "^8.6.4", + "@ionic/react": "^8.6.5", "tslib": "*" }, "devDependencies": { @@ -238,9 +238,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "license": "MIT", "dependencies": { "@stencil/core": "4.33.1", @@ -415,12 +415,12 @@ } }, "node_modules/@ionic/react": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.4.tgz", - "integrity": "sha512-X2jIi4TN/u9hlsy/BrubyJbIZ4Pn8cnbBFu/emQ1y7VH0rpVVWPgeHb8cKMJPNbKzszuvO+f5huGliNIYFIQ8A==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.5.tgz", + "integrity": "sha512-reUKqlU3cIJoHuDibB8WUd32a7nqg5aMsIfPnXOVydIUsJdvQnwjACbYiP0g+4AFzTVPsw/Cmyqh85GhXGw4WA==", "license": "MIT", "dependencies": { - "@ionic/core": "8.6.4", + "@ionic/core": "8.6.5", "ionicons": "^7.0.0", "tslib": "*" }, @@ -4175,9 +4175,9 @@ "dev": true }, "@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "requires": { "@stencil/core": "4.33.1", "ionicons": "^7.2.2", @@ -4281,11 +4281,11 @@ "requires": {} }, "@ionic/react": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.4.tgz", - "integrity": "sha512-X2jIi4TN/u9hlsy/BrubyJbIZ4Pn8cnbBFu/emQ1y7VH0rpVVWPgeHb8cKMJPNbKzszuvO+f5huGliNIYFIQ8A==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.6.5.tgz", + "integrity": "sha512-reUKqlU3cIJoHuDibB8WUd32a7nqg5aMsIfPnXOVydIUsJdvQnwjACbYiP0g+4AFzTVPsw/Cmyqh85GhXGw4WA==", "requires": { - "@ionic/core": "8.6.4", + "@ionic/core": "8.6.5", "ionicons": "^7.0.0", "tslib": "*" } diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 677e566bc4a..1915e30fa9b 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "8.6.4", + "version": "8.6.5", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -36,7 +36,7 @@ "dist/" ], "dependencies": { - "@ionic/react": "^8.6.4", + "@ionic/react": "^8.6.5", "tslib": "*" }, "peerDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index b419bceaad9..01eb2a3b4cf 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + +**Note:** Version bump only for package @ionic/react + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) **Note:** Version bump only for package @ionic/react diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index 73be803f045..728dafcfddd 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react", - "version": "8.6.4", + "version": "8.6.5", "license": "MIT", "dependencies": { - "@ionic/core": "^8.6.4", + "@ionic/core": "^8.6.5", "ionicons": "^7.0.0", "tslib": "*" }, @@ -736,9 +736,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "license": "MIT", "dependencies": { "@stencil/core": "4.33.1", @@ -12431,9 +12431,9 @@ "dev": true }, "@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "requires": { "@stencil/core": "4.33.1", "ionicons": "^7.2.2", diff --git a/packages/react/package.json b/packages/react/package.json index 7b43e6b5f9b..e6a69b35437 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "8.6.4", + "version": "8.6.5", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -39,7 +39,7 @@ "css/" ], "dependencies": { - "@ionic/core": "^8.6.4", + "@ionic/core": "^8.6.5", "ionicons": "^7.0.0", "tslib": "*" }, diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index a37711ac220..aaf738c0760 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + +**Note:** Version bump only for package @ionic/vue-router + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) **Note:** Version bump only for package @ionic/vue-router diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index 6b2b1b240da..46267ecd640 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue-router", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue-router", - "version": "8.6.4", + "version": "8.6.5", "license": "MIT", "dependencies": { - "@ionic/vue": "^8.6.4" + "@ionic/vue": "^8.6.5" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", @@ -673,9 +673,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "license": "MIT", "dependencies": { "@stencil/core": "4.33.1", @@ -865,12 +865,12 @@ } }, "node_modules/@ionic/vue": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.4.tgz", - "integrity": "sha512-vhFxCUk2hwPbJS1uTcZkVFB+9eFfzeis5TyL1mDmlULFhbGI/YTLTcWcXWSdG/myg4yPeb8brObWpMq36StJVw==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.5.tgz", + "integrity": "sha512-hmvH8y9QRbfWchRBJJoSHHoAHpcOMo942B1/4xZQwgkujRtpLTIoWG9eGNxvbMpV0c11CyUX9R2++MHpVdLKDw==", "license": "MIT", "dependencies": { - "@ionic/core": "8.6.4", + "@ionic/core": "8.6.5", "@stencil/vue-output-target": "0.10.7", "ionicons": "^7.0.0" } @@ -8041,9 +8041,9 @@ "dev": true }, "@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "requires": { "@stencil/core": "4.33.1", "ionicons": "^7.2.2", @@ -8156,11 +8156,11 @@ "requires": {} }, "@ionic/vue": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.4.tgz", - "integrity": "sha512-vhFxCUk2hwPbJS1uTcZkVFB+9eFfzeis5TyL1mDmlULFhbGI/YTLTcWcXWSdG/myg4yPeb8brObWpMq36StJVw==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.6.5.tgz", + "integrity": "sha512-hmvH8y9QRbfWchRBJJoSHHoAHpcOMo942B1/4xZQwgkujRtpLTIoWG9eGNxvbMpV0c11CyUX9R2++MHpVdLKDw==", "requires": { - "@ionic/core": "8.6.4", + "@ionic/core": "8.6.5", "@stencil/vue-output-target": "0.10.7", "ionicons": "^7.0.0" } diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index 0b87ea46864..40f7523ab5b 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue-router", - "version": "8.6.4", + "version": "8.6.5", "description": "Vue Router integration for @ionic/vue", "scripts": { "test.spec": "jest", @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/ionic-team/ionic-framework#readme", "dependencies": { - "@ionic/vue": "^8.6.4" + "@ionic/vue": "^8.6.5" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 57bceea1707..cd57e419d75 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.6.5](https://github.com/ionic-team/ionic-framework/compare/v8.6.4...v8.6.5) (2025-07-16) + +**Note:** Version bump only for package @ionic/vue + + + + + ## [8.6.4](https://github.com/ionic-team/ionic-framework/compare/v8.6.3...v8.6.4) (2025-07-09) **Note:** Version bump only for package @ionic/vue diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index ee18699d73c..40e32bcaf97 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue", - "version": "8.6.4", + "version": "8.6.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue", - "version": "8.6.4", + "version": "8.6.5", "license": "MIT", "dependencies": { - "@ionic/core": "^8.6.4", + "@ionic/core": "^8.6.5", "@stencil/vue-output-target": "0.10.7", "ionicons": "^7.0.0" }, @@ -222,9 +222,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "license": "MIT", "dependencies": { "@stencil/core": "4.33.1", @@ -4167,9 +4167,9 @@ "dev": true }, "@ionic/core": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.4.tgz", - "integrity": "sha512-6kOx0yQAkXkMvhe6fQPA034LgmCh4aL0nJ+GwzNMwLYAe2fVq6mRdM37jNldGiGIZ0Q9Te2sHTFTM/IGItuIyQ==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.6.5.tgz", + "integrity": "sha512-HN+6/Q67fEEpRA86QzXSrCahuHwaTPBsa910RuvY0pIYuoY4rpzGPU9ZOQ5q2wBsrln921rroEPU1xdpPKIH8Q==", "requires": { "@stencil/core": "4.33.1", "ionicons": "^7.2.2", diff --git a/packages/vue/package.json b/packages/vue/package.json index 5c3ed43a6b7..4a14236cfda 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue", - "version": "8.6.4", + "version": "8.6.5", "description": "Vue specific wrapper for @ionic/core", "scripts": { "eslint": "eslint src", @@ -67,7 +67,7 @@ "vue-router": "^4.0.16" }, "dependencies": { - "@ionic/core": "^8.6.4", + "@ionic/core": "^8.6.5", "@stencil/vue-output-target": "0.10.7", "ionicons": "^7.0.0" },