-
Notifications
You must be signed in to change notification settings - Fork 13.4k
feat(toast): allow custom positioning relative to specific element #28248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
a9bb198
initial setup
036b584
log a warning when used with position="middle"
81a3c64
calculate animation positions based on anchor
428563c
add tests
d5a1093
chore(): add updated snapshots
Ionitron 42b182d
add new prop to ToastOptions interface
0c25cda
add screenshot for el ref anchor
07743ec
lint
10449e5
chore(): add updated snapshots
Ionitron 92db953
run Vue build
56b0124
revert flaky screenshot
42a343f
move beforeEach inside describe
f5cd473
chore(): add updated snapshots
Ionitron 9381de9
lint
b4159b7
revert flaky screenshot
323de22
Merge branch 'feature-7.5' into FW-4520
averyjohnston c14cd73
pull anchor el grab into separate function
4f6bdd5
make double sure positionAnchor is the right type
1e2a8b7
warn if anchor appears to be hidden on present
9bfa69c
pull animation position logic into separate util
95d1bfb
calculate animation position in toast component and cache result for …
10fb4c2
lint
a92a4a9
chore(): add updated snapshots
Ionitron 5ded707
revert flaky screenshot
liamdebeasi 82ac509
add comment to warnIfAnchorIsHidden
120937a
add comment to lastPresentedPosition
a5d85d7
clear lastPresentedPosition on dismiss
ba77bd9
use getIonMode instead of config getMode
b064f3a
add param comments to getAnimationPosition
8125555
lint
412f88a
Merge branch 'feature-7.5' into FW-4520
averyjohnston File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { win } from '@utils/browser'; | ||
import { printIonWarning } from '@utils/logging'; | ||
import type { Mode } from 'src/interface'; | ||
|
||
import type { ToastAnimationPosition, ToastPosition } from '../toast-interface'; | ||
|
||
/** | ||
* Calculate the CSS top and bottom position of the toast, to be used | ||
* as starting points for the animation keyframes. | ||
* | ||
* Note that MD animates bottom-positioned toasts using style.bottom, | ||
* which calculates from the bottom edge of the screen, while iOS uses | ||
* translateY, which calculates from the top edge of the screen. This | ||
* is why the bottom calculates differ slightly between modes. | ||
* | ||
* @param position The value of the toast's position prop. | ||
* @param positionAnchor The element the toast should be anchored to, | ||
* if applicable. | ||
* @param mode The toast component's mode (md, ios, etc). | ||
* @param toast A reference to the toast element itself. | ||
*/ | ||
export function getAnimationPosition( | ||
position: ToastPosition, | ||
positionAnchor: HTMLElement | undefined, | ||
mode: Mode, | ||
toast: HTMLElement | ||
): ToastAnimationPosition { | ||
/** | ||
* Start with a predefined offset from the edge the toast will be | ||
* positioned relative to, whether on the screen or anchor element. | ||
*/ | ||
let offset: number; | ||
if (mode === 'md') { | ||
offset = 8; | ||
} else { | ||
offset = position === 'top' ? 10 : -10; | ||
} | ||
|
||
/** | ||
* If positionAnchor is defined, add in the distance from the target | ||
* screen edge to the target anchor edge. For position="top", the | ||
* bottom anchor edge is targeted. For position="bottom", the top | ||
* anchor edge is targeted. | ||
*/ | ||
if (positionAnchor && win) { | ||
warnIfAnchorIsHidden(positionAnchor, toast); | ||
|
||
const box = positionAnchor.getBoundingClientRect(); | ||
if (position === 'top') { | ||
offset += box.bottom; | ||
} else if (position === 'bottom') { | ||
/** | ||
* Just box.top is the distance from the top edge of the screen | ||
* to the top edge of the anchor. We want to calculate from the | ||
* bottom edge of the screen instead. | ||
*/ | ||
if (mode === 'md') { | ||
offset += win.innerHeight - box.top; | ||
} else { | ||
offset -= win.innerHeight - box.top; | ||
} | ||
} | ||
|
||
/** | ||
* We don't include safe area here because that should already be | ||
* accounted for when checking the position of the anchor. | ||
*/ | ||
return { | ||
top: `${offset}px`, | ||
bottom: `${offset}px`, | ||
}; | ||
} else { | ||
return { | ||
top: `calc(${offset}px + var(--ion-safe-area-top, 0px))`, | ||
bottom: | ||
mode === 'md' | ||
? `calc(${offset}px + var(--ion-safe-area-bottom, 0px))` | ||
: `calc(${offset}px - var(--ion-safe-area-bottom, 0px))`, | ||
}; | ||
} | ||
} | ||
|
||
/** | ||
* If the anchor element is hidden, getBoundingClientRect() | ||
* will return all 0s for it, which can cause unexpected | ||
* results in the position calculation when animating. | ||
*/ | ||
function warnIfAnchorIsHidden(positionAnchor: HTMLElement, toast: HTMLElement) { | ||
liamdebeasi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (positionAnchor.offsetParent === null) { | ||
printIonWarning( | ||
'The positionAnchor element for ion-toast was found in the DOM, but appears to be hidden. This may lead to unexpected positioning of the toast.', | ||
toast | ||
); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
<!DOCTYPE html> | ||
<html lang="en" dir="ltr"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<title>Toast - positionAnchor</title> | ||
<meta | ||
name="viewport" | ||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" | ||
/> | ||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" /> | ||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" /> | ||
<script src="../../../../../scripts/testing/scripts.js"></script> | ||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script> | ||
|
||
<style> | ||
html { | ||
--ion-safe-area-top: 30px; | ||
--ion-safe-area-bottom: 30px; | ||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<ion-app> | ||
<ion-header id="header"> | ||
<ion-toolbar> | ||
<ion-title>Toast - positionAnchor</ion-title> | ||
</ion-toolbar> | ||
</ion-header> | ||
|
||
<ion-content class="ion-padding"> | ||
<ion-button id="headerAnchor">Anchor to Header</ion-button> | ||
<ion-button id="footerAnchor">Anchor to Footer</ion-button> | ||
<ion-button id="middleAnchor">Anchor to Header (Middle Position)</ion-button> | ||
<ion-button id="headerElAnchor">Anchor to Header (Element Ref)</ion-button> | ||
<ion-button id="hiddenElAnchor">Anchor to Hidden Element</ion-button> | ||
|
||
<ion-toast | ||
id="headerToast" | ||
trigger="headerAnchor" | ||
position="top" | ||
position-anchor="header" | ||
message="Hello World" | ||
duration="2000" | ||
></ion-toast> | ||
<ion-toast | ||
id="footerToast" | ||
trigger="footerAnchor" | ||
position="bottom" | ||
position-anchor="footer" | ||
message="Hello World" | ||
duration="2000" | ||
></ion-toast> | ||
<ion-toast | ||
id="middleToast" | ||
trigger="middleAnchor" | ||
position="middle" | ||
position-anchor="header" | ||
message="Hello World" | ||
duration="2000" | ||
></ion-toast> | ||
<ion-toast | ||
id="headerElToast" | ||
trigger="headerElAnchor" | ||
position="top" | ||
message="Hello World" | ||
duration="2000" | ||
></ion-toast> | ||
<ion-toast | ||
id="hiddenElToast" | ||
trigger="hiddenElAnchor" | ||
position="bottom" | ||
position-anchor="hiddenEl" | ||
message="Hello World" | ||
duration="2000" | ||
></ion-toast> | ||
|
||
<div id="hiddenEl" style="display: none">Shh I'm hiding</div> | ||
</ion-content> | ||
|
||
<ion-footer id="footer"> | ||
<ion-toolbar> | ||
<ion-title>Footer</ion-title> | ||
</ion-toolbar> | ||
</ion-footer> | ||
</ion-app> | ||
|
||
<script> | ||
const headerElToast = document.querySelector('#headerElToast'); | ||
const header = document.querySelector('ion-header'); | ||
headerElToast.positionAnchor = header; | ||
</script> | ||
</body> | ||
</html> |
56 changes: 56 additions & 0 deletions
56
core/src/components/toast/test/position-anchor/toast.e2e.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { expect } from '@playwright/test'; | ||
import { configs, test } from '@utils/test/playwright'; | ||
|
||
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { | ||
test.describe(title('toast: positionAnchor'), () => { | ||
test.beforeEach(async ({ page }) => { | ||
await page.goto('/src/components/toast/test/position-anchor', config); | ||
|
||
/** | ||
* We need to screenshot the whole page to ensure the toasts are positioned | ||
* correctly, but we don't need much extra white space between the header | ||
* and footer. | ||
*/ | ||
await page.setViewportSize({ | ||
width: 425, | ||
height: 425, | ||
}); | ||
}); | ||
|
||
test('should place top-position toast underneath anchor', async ({ page }) => { | ||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); | ||
|
||
await page.click('#headerAnchor'); | ||
await ionToastDidPresent.next(); | ||
|
||
await expect(page).toHaveScreenshot(screenshot(`toast-header-anchor`)); | ||
}); | ||
|
||
test('should place bottom-position toast above anchor', async ({ page }) => { | ||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); | ||
|
||
await page.click('#footerAnchor'); | ||
await ionToastDidPresent.next(); | ||
|
||
await expect(page).toHaveScreenshot(screenshot(`toast-footer-anchor`)); | ||
}); | ||
|
||
test('should ignore anchor for middle-position toast', async ({ page }) => { | ||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); | ||
|
||
await page.click('#middleAnchor'); | ||
await ionToastDidPresent.next(); | ||
|
||
await expect(page).toHaveScreenshot(screenshot(`toast-middle-anchor`)); | ||
}); | ||
|
||
test('should correctly anchor toast when using an element reference', async ({ page }) => { | ||
const ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); | ||
|
||
await page.click('#headerElAnchor'); | ||
await ionToastDidPresent.next(); | ||
|
||
await expect(page).toHaveScreenshot(screenshot(`toast-header-el-anchor`)); | ||
}); | ||
}); | ||
}); |
Binary file added
BIN
+17.7 KB
...chor/toast.e2e.ts-snapshots/toast-footer-anchor-ios-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+26.1 KB
...hor/toast.e2e.ts-snapshots/toast-footer-anchor-ios-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+17 KB
...chor/toast.e2e.ts-snapshots/toast-footer-anchor-ios-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+18.1 KB
...nchor/toast.e2e.ts-snapshots/toast-footer-anchor-md-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+29.8 KB
...chor/toast.e2e.ts-snapshots/toast-footer-anchor-md-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+17.6 KB
...nchor/toast.e2e.ts-snapshots/toast-footer-anchor-md-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.2 KB
...chor/toast.e2e.ts-snapshots/toast-header-anchor-ios-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+23.3 KB
...hor/toast.e2e.ts-snapshots/toast-header-anchor-ios-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.5 KB
...chor/toast.e2e.ts-snapshots/toast-header-anchor-ios-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.2 KB
...nchor/toast.e2e.ts-snapshots/toast-header-anchor-md-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.2 KB
...chor/toast.e2e.ts-snapshots/toast-header-anchor-md-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.6 KB
...nchor/toast.e2e.ts-snapshots/toast-header-anchor-md-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.2 KB
...r/toast.e2e.ts-snapshots/toast-header-el-anchor-ios-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+23.3 KB
.../toast.e2e.ts-snapshots/toast-header-el-anchor-ios-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.5 KB
...r/toast.e2e.ts-snapshots/toast-header-el-anchor-ios-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.2 KB
...or/toast.e2e.ts-snapshots/toast-header-el-anchor-md-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.2 KB
...r/toast.e2e.ts-snapshots/toast-header-el-anchor-md-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.6 KB
...or/toast.e2e.ts-snapshots/toast-header-el-anchor-md-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16.5 KB
...chor/toast.e2e.ts-snapshots/toast-middle-anchor-ios-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+25.1 KB
...hor/toast.e2e.ts-snapshots/toast-middle-anchor-ios-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+16 KB
...chor/toast.e2e.ts-snapshots/toast-middle-anchor-ios-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.9 KB
...nchor/toast.e2e.ts-snapshots/toast-middle-anchor-md-ltr-Mobile-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+26.1 KB
...chor/toast.e2e.ts-snapshots/toast-middle-anchor-md-ltr-Mobile-Firefox-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.3 KB
...nchor/toast.e2e.ts-snapshots/toast-middle-anchor-md-ltr-Mobile-Safari-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.