diff --git a/.github/ionic-issue-bot.yml b/.github/ionic-issue-bot.yml index fad26e0fce..c62a98b9e5 100644 --- a/.github/ionic-issue-bot.yml +++ b/.github/ionic-issue-bot.yml @@ -41,8 +41,7 @@ closeAndLock: - label: "ionitron: support" message: > Thanks for the issue! This issue appears to be a support request. We use this issue tracker exclusively for - bug reports and feature requests. Please use our [forum](https://forum.ionicframework.com) or our - [slack channel](https://ionicworldwide.herokuapp.com/) for questions about the framework. + bug reports and feature requests. Please use our [forum](https://forum.ionicframework.com) for questions about the framework. Thank you for using Ionic! diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c98dcb722..f9c2304d99 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: test-core-screenshot: needs: [build-core] runs-on: ubuntu-latest - if: github.ref != 'refs/heads/main' + if: github.ref != 'refs/heads/main' && !github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v2 - uses: ./.github/workflows/actions/test-core-screenshot diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml new file mode 100644 index 0000000000..c9ed9dccc7 --- /dev/null +++ b/.github/workflows/dev-build.yml @@ -0,0 +1,75 @@ +name: 'Ionic Dev Build' + +on: + workflow_dispatch + +jobs: + dev-build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.devBuild.outputs.version }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 15.x + registry-url: 'https://registry.npmjs.org' + - name: Install Dependencies + run: npm install + shell: bash + - name: Create Dev Build + run: npm run release.dev -- --skip-prompt + shell: bash + - name: Publish Core + run: npm publish --tag dev + working-directory: ./core + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Docs + run: npm publish --tag dev + working-directory: ./docs + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Angular + run: npm publish --tag dev + working-directory: ./angular + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Angular Server + run: npm publish --tag dev + working-directory: ./packages/angular-server + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish React + run: npm publish --tag dev + working-directory: ./packages/react + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish React Router + run: npm publish --tag dev + working-directory: ./packages/react-router + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Vue + run: npm publish --tag dev + working-directory: ./packages/vue + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Vue Router + run: npm publish --tag dev + working-directory: ./packages/vue-router + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Wrap Up + id: devBuild + run: | + val=$(grep version package.json | sed 's/.*"version": "\(.*\)".*/\1/') + echo "::set-output name=version::$val" + working-directory: ./core + get-version: + name: Get your dev build! + runs-on: ubuntu-latest + needs: [dev-build] + steps: + - run: echo ${{ needs.dev-build.outputs.version }} + diff --git a/.scripts/common.js b/.scripts/common.js index f43d416397..e8ddfad4b4 100644 --- a/.scripts/common.js +++ b/.scripts/common.js @@ -199,6 +199,16 @@ function preparePackage(tasks, package, version, install) { }); } +function installDevPackage(tasks, package) { + const projectRoot = projectPath(package); + const pkg = readPkg(package); + + tasks.push({ + title: `${pkg.name}: npm install`, + task: () => execa('npm', ['install', '--legacy-peer-deps'], { cwd: projectRoot }) + }); +} + function prepareDevPackage(tasks, package, version) { const projectRoot = projectPath(package); const pkg = readPkg(package); @@ -211,20 +221,20 @@ function prepareDevPackage(tasks, package, version) { title: `${pkg.name}: npm link @ionic/core`, task: () => execa('npm', ['link', '@ionic/core', '--legacy-peer-deps'], { cwd: projectRoot }) }); - - if (package === 'packages/react-router') { - projectTasks.push({ - title: `${pkg.name}: npm link @ionic/react`, - task: () => execa('npm', ['link', '@ionic/react', '--legacy-peer-deps'], { cwd: projectRoot }) - }); - } } projectTasks.push({ title: `${pkg.name}: update ionic/core dep to ${version}`, task: () => { - updateDependency(pkg, '@ionic/core', version); - writePkg(package, pkg); + + /** + * At this point, the version for the package + * has been changed so we need to re-fetch the + * package contents so we do not overwrite it. + */ + const updatedPackage = readPkg(package); + updateDependency(updatedPackage, '@ionic/core', version); + writePkg(package, updatedPackage); } }); @@ -233,7 +243,7 @@ function prepareDevPackage(tasks, package, version) { task: () => execa('npm', ['run', 'build'], { cwd: projectRoot }) }); - if (package === 'core' || package === 'packages/react') { + if (package === 'core') { projectTasks.push({ title: `${pkg.name}: npm link`, task: () => execa('npm', ['link'], { cwd: projectRoot }) @@ -278,17 +288,6 @@ function updatePackageVersions(tasks, packages, version) { } }); } - - if (package === 'packages/react-router') { - tasks.push({ - title: `${package} update @ionic/react dependency, if present ${dim(`(${version})`)}`, - task: async () => { - const pkg = readPkg(package); - updateDependency(pkg, '@ionic/react', version); - writePkg(package, pkg); - } - }); - } }); } @@ -320,7 +319,7 @@ function copyPackageToDist(tasks, packages) { } function publishPackages(tasks, packages, version, npmTag = 'latest') { - // first verify version + // verify version packages.forEach(package => { if (package === 'core') { return; @@ -337,22 +336,6 @@ function publishPackages(tasks, packages, version, npmTag = 'latest') { } }); }); - - // Publish - packages.forEach(package => { - let projectRoot = projectPath(package); - - if (package === 'packages/angular-server' || package === 'angular') { - projectRoot = path.join(projectRoot, 'dist') - } - - tasks.push({ - title: `${package}: publish to ${npmTag} tag`, - task: async () => { - await execa('npm', ['publish', '--tag', npmTag], { cwd: projectRoot }); - } - }); - }); } function updateDependency(pkg, dependency, version) { @@ -382,6 +365,7 @@ function copyCDNLoader(tasks, version) { } module.exports = { + installDevPackage, checkTestDist, checkGit, askNpmTag, diff --git a/.scripts/release-dev.js b/.scripts/release-dev.js index a5812596ae..1bf90dccf7 100644 --- a/.scripts/release-dev.js +++ b/.scripts/release-dev.js @@ -33,7 +33,14 @@ async function main() { const tasks = []; - await setPackageVersionChanges(packages, devVersion); + packages.forEach(package => { + common.installDevPackage(tasks, package); + }); + + tasks.push({ + title: 'Set package version changes', + task: async () => await setPackageVersionChanges(packages, devVersion) + }) packages.forEach(package => { common.prepareDevPackage(tasks, package, devVersion); @@ -50,13 +57,14 @@ async function main() { console.log('\n', red(err), '\n'); process.exit(1); } - - orgPkg.forEach(pkg => { - fs.writeFileSync(pkg.filePath, pkg.packageContent); - }); } async function askDevVersion(devVersion) { + const skipPrompt = process.argv.find(arg => arg === '--skip-prompt'); + if (skipPrompt) { + console.log(`\n--skip-prompt was passed. Skipping dev build prompt.`) + return true; + } const prompts = [ { @@ -78,9 +86,6 @@ async function setPackageVersionChanges(packages, version) { if (package !== 'core') { const pkg = common.readPkg(package); common.updateDependency(pkg, '@ionic/core', version); - if(package === 'packages/react-router') { - common.updateDependency(pkg, '@ionic/react', version); - } common.writePkg(package, pkg); } const projectRoot = common.projectPath(package); diff --git a/angular/src/index.ts b/angular/src/index.ts index c95f811ff4..bbcce8c154 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -52,6 +52,7 @@ export { mdTransitionAnimation, setupConfig, IonicSwiper, + IonicSlides, getPlatforms, isPlatform, getTimeGivenProgression, diff --git a/core/src/components/action-sheet/action-sheet.ios.scss b/core/src/components/action-sheet/action-sheet.ios.scss index 4bd0540db8..d2c06d96bf 100644 --- a/core/src/components/action-sheet/action-sheet.ios.scss +++ b/core/src/components/action-sheet/action-sheet.ios.scss @@ -22,11 +22,6 @@ text-align: $action-sheet-ios-text-align; } -.action-sheet-wrapper { - @include margin(var(--ion-safe-area-top, 0), auto, var(--ion-safe-area-bottom, 0), auto); -} - - // iOS Action Sheet Container // --------------------------------------------------- diff --git a/core/src/components/action-sheet/action-sheet.scss b/core/src/components/action-sheet/action-sheet.scss index a19e11be19..bdc813571a 100644 --- a/core/src/components/action-sheet/action-sheet.scss +++ b/core/src/components/action-sheet/action-sheet.scss @@ -67,7 +67,7 @@ .action-sheet-wrapper { @include position(null, 0, 0, 0); - @include margin(auto); + @include margin(var(--ion-safe-area-top, 0), auto, var(--ion-safe-area-bottom, 0), auto); @include transform(translate3d(0, 100%, 0)); display: block; diff --git a/core/src/components/slides/IonicSlides.ts b/core/src/components/slides/IonicSlides.ts new file mode 100644 index 0000000000..3184f0e2e4 --- /dev/null +++ b/core/src/components/slides/IonicSlides.ts @@ -0,0 +1,102 @@ +export const IonicSlides = (opts: any) => { + const { swiper, extendParams } = opts; + const slidesParams: any = { + effect: undefined, + direction: 'horizontal', + initialSlide: 0, + loop: false, + parallax: false, + slidesPerView: 1, + spaceBetween: 0, + speed: 300, + slidesPerColumn: 1, + slidesPerColumnFill: 'column', + slidesPerGroup: 1, + centeredSlides: false, + slidesOffsetBefore: 0, + slidesOffsetAfter: 0, + touchEventsTarget: 'container', + autoplay: false, + freeMode: false, + freeModeMomentum: true, + freeModeMomentumRatio: 1, + freeModeMomentumBounce: true, + freeModeMomentumBounceRatio: 1, + freeModeMomentumVelocityRatio: 1, + freeModeSticky: false, + freeModeMinimumVelocity: 0.02, + autoHeight: false, + setWrapperSize: false, + zoom: { + maxRatio: 3, + minRatio: 1, + toggle: false, + }, + touchRatio: 1, + touchAngle: 45, + simulateTouch: true, + touchStartPreventDefault: false, + shortSwipes: true, + longSwipes: true, + longSwipesRatio: 0.5, + longSwipesMs: 300, + followFinger: true, + threshold: 0, + touchMoveStopPropagation: true, + touchReleaseOnEdges: false, + iOSEdgeSwipeDetection: false, + iOSEdgeSwipeThreshold: 20, + resistance: true, + resistanceRatio: 0.85, + watchSlidesProgress: false, + watchSlidesVisibility: false, + preventClicks: true, + preventClicksPropagation: true, + slideToClickedSlide: false, + loopAdditionalSlides: 0, + noSwiping: true, + runCallbacksOnInit: true, + coverflowEffect: { + rotate: 50, + stretch: 0, + depth: 100, + modifier: 1, + slideShadows: true + }, + flipEffect: { + slideShadows: true, + limitRotation: true + }, + cubeEffect: { + slideShadows: true, + shadow: true, + shadowOffset: 20, + shadowScale: 0.94 + }, + fadeEffect: { + crossFade: false + }, + a11y: { + prevSlideMessage: 'Previous slide', + nextSlideMessage: 'Next slide', + firstSlideMessage: 'This is the first slide', + lastSlideMessage: 'This is the last slide' + } + } + + if (swiper.pagination) { + slidesParams.pagination = { + type: 'bullets', + clickable: false, + hideOnClick: false, + } + } + + if (swiper.scrollbar) { + slidesParams.scrollbar = { + hide: true + } + } + + extendParams(slidesParams); +} diff --git a/core/src/components/slides/IonicSwiper.ts b/core/src/components/slides/IonicSwiper.ts index da107b5af1..0c3b918110 100644 --- a/core/src/components/slides/IonicSwiper.ts +++ b/core/src/components/slides/IonicSwiper.ts @@ -120,6 +120,8 @@ export const IonicSwiper = { name: 'ionic', on: { afterInit(swiper: any) { + console.warn('[Deprecation Warning]: The IonicSwiper module has been deprecated in favor of the IonSlides module. This change was made to better support the Swiper 7 release. The IonicSwiper module will be removed in Ionic 7.0. See https://ionicframework.com/docs/api/slides#migration for revised migration steps.'); + setupSwiperInIonic(swiper); } } diff --git a/core/src/components/virtual-scroll/readme.md b/core/src/components/virtual-scroll/readme.md index 826ddf8a7f..b19d60d6b2 100644 --- a/core/src/components/virtual-scroll/readme.md +++ b/core/src/components/virtual-scroll/readme.md @@ -11,15 +11,15 @@ This guide will go over the recommended virtual scrolling packages for each fram ## Angular -For virtual scrolling options in Ionic Angular, please see [Angular Virtual Scroll Guide](../angular/virtual-scroll). +For virtual scrolling options in Ionic Angular, please see [Angular Virtual Scroll Guide](../../angular/virtual-scroll). ## React -For virtual scrolling options in Ionic React, please see [React Virtual Scroll Guide](../react/virtual-scroll). +For virtual scrolling options in Ionic React, please see [React Virtual Scroll Guide](../../react/virtual-scroll). ## Vue -For virtual scrolling options in Ionic Vue, please see [Vue Virtual Scroll Guide](../vue/virtual-scroll). +For virtual scrolling options in Ionic Vue, please see [Vue Virtual Scroll Guide](../../vue/virtual-scroll). ------ diff --git a/core/src/css/ionic-swiper.scss b/core/src/css/ionic-swiper.scss index 8ebba40c5b..24e5eaaf4e 100644 --- a/core/src/css/ionic-swiper.scss +++ b/core/src/css/ionic-swiper.scss @@ -4,7 +4,7 @@ // Slides // -------------------------------------------------- -.swiper-container { +.swiper { // These values are the same for iOS and MD // We just do not add a .md or .ios class beforehand @@ -33,11 +33,11 @@ // Pagination Bullets // -------------------------------------------------- -.swiper-pagination-bullet { +.swiper .swiper-pagination-bullet { background: var(--bullet-background); } -.swiper-pagination-bullet-active { +.swiper .swiper-pagination-bullet-active { background: var(--bullet-background-active); } @@ -45,36 +45,29 @@ // Pagination Progress Bar // -------------------------------------------------- -.swiper-pagination-progressbar { +.swiper .swiper-pagination-progressbar { background: var(--progress-bar-background); } -.swiper-pagination-progressbar .swiper-pagination-progressbar-fill { +.swiper .swiper-pagination-progressbar .swiper-pagination-progressbar-fill { background: var(--progress-bar-background-active); } // Scrollbar // -------------------------------------------------- -.swiper-scrollbar { +.swiper .swiper-scrollbar { background: var(--scroll-bar-background); } -.swiper-scrollbar-drag { +.swiper .swiper-scrollbar-drag { background: var(--scroll-bar-background-active); } // Slide // -------------------------------------------------- -ion-slide { - display: block; - - width: 100%; - height: 100%; -} - -.slide-zoom { +.swiper .slide-zoom { display: block; width: 100%; @@ -82,7 +75,7 @@ ion-slide { text-align: center; } -.swiper-slide { +.swiper .swiper-slide { // Center slide text vertically display: flex; @@ -101,7 +94,7 @@ ion-slide { box-sizing: border-box; } -.swiper-slide img { +.swiper .swiper-slide img { width: auto; max-width: 100%; height: auto; diff --git a/core/src/index.ts b/core/src/index.ts index d6940bd4e9..a550c74161 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -14,3 +14,4 @@ export { LIFECYCLE_WILL_ENTER, LIFECYCLE_DID_ENTER, LIFECYCLE_WILL_LEAVE, LIFECY export { menuController } from './utils/menu-controller'; export { alertController, actionSheetController, modalController, loadingController, pickerController, popoverController, toastController } from './utils/overlays'; export { IonicSwiper } from './components/slides/IonicSwiper'; +export { IonicSlides } from './components/slides/IonicSlides'; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index fa7f705611..caf32399e8 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -25,6 +25,7 @@ export { mdTransitionAnimation, setupConfig, IonicSwiper, + IonicSlides, getTimeGivenProgression, // TYPES diff --git a/packages/vue-router/__tests__/locationHistory.spec.ts b/packages/vue-router/__tests__/locationHistory.spec.ts index f451e0eb35..aacb8a31a9 100644 --- a/packages/vue-router/__tests__/locationHistory.spec.ts +++ b/packages/vue-router/__tests__/locationHistory.spec.ts @@ -84,7 +84,7 @@ describe('Location History', () => { locationHistory.add({ pathname: '/home' }); locationHistory.add({ pathname: '/login' }); - expect(locationHistory.canGoBack(1)).toEqual(true); - expect(locationHistory.canGoBack(2)).toEqual(false); + expect(locationHistory.canGoBack(1, 0, 1)).toEqual(true); + expect(locationHistory.canGoBack(2, 0, 1)).toEqual(false); }); }); diff --git a/packages/vue-router/src/locationHistory.ts b/packages/vue-router/src/locationHistory.ts index ac3d8ac6f6..879994eb02 100644 --- a/packages/vue-router/src/locationHistory.ts +++ b/packages/vue-router/src/locationHistory.ts @@ -133,7 +133,18 @@ export const createLocationHistory = () => { } const previous = () => locationHistory[locationHistory.length - 2] || last(); const last = () => locationHistory[locationHistory.length - 1]; - const canGoBack = (deep: number = 1) => locationHistory.length > deep; + + /** + * With the introduction of router.go support, we no longer remove + * items from locationHistory as they may be needed again in the future. + * As a result, we need to look at the current position in location history + * to see if users can navigate back n pages. Previously we were checking + * the length of locationHistory, but that only worked since we were pruning + * the array. + */ + const canGoBack = (deep: number = 1, initialHistory: number, currentHistory: number) => { + return currentHistory - deep >= initialHistory; + } const getFirstRouteInfoForTab = (tab: string): RouteInfo | undefined => { const tabHistory = getTabsHistory(tab); diff --git a/packages/vue-router/src/router.ts b/packages/vue-router/src/router.ts index f9c3a3b37d..415155bd2f 100644 --- a/packages/vue-router/src/router.ts +++ b/packages/vue-router/src/router.ts @@ -301,7 +301,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) => const getCurrentRouteInfo = () => currentRouteInfo; - const canGoBack = (deep: number = 1) => locationHistory.canGoBack(deep); + const canGoBack = (deep: number = 1) => locationHistory.canGoBack(deep, initialHistoryPosition, currentHistoryPosition); const navigate = (navigationOptions: ExternalNavigationOptions) => { const { routerAnimation, routerDirection, routerLink } = navigationOptions; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 0ebfe7f089..f2cab23242 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -42,6 +42,7 @@ export { mdTransitionAnimation, setupConfig, IonicSwiper, + IonicSlides, getPlatforms, isPlatform, menuController, diff --git a/packages/vue/test-app/src/App.vue b/packages/vue/test-app/src/App.vue index 1e01091702..0f088ec216 100644 --- a/packages/vue/test-app/src/App.vue +++ b/packages/vue/test-app/src/App.vue @@ -15,4 +15,4 @@ export default defineComponent({ IonRouterOutlet } }); - \ No newline at end of file + diff --git a/packages/vue/test-app/tests/unit/routing.spec.ts b/packages/vue/test-app/tests/unit/routing.spec.ts index cbd42e4bc8..d793cd1bae 100644 --- a/packages/vue/test-app/tests/unit/routing.spec.ts +++ b/packages/vue/test-app/tests/unit/routing.spec.ts @@ -8,7 +8,8 @@ import { IonTabs, IonTabBar, IonTabButton, - IonLabel + IonLabel, + useIonRouter } from '@ionic/vue'; import { onBeforeRouteLeave } from 'vue-router'; import { waitForRouter } from './utils'; @@ -548,4 +549,117 @@ describe('Routing', () => { expect(wrapper.findComponent(Page2).exists()).toBe(false); expect(wrapper.findComponent(Page3).exists()).toBe(false); }); + + // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/24109 + it('canGoBack() should return the correct value', async () => { + const Page = { + components: { IonPage }, + template: `` + } + const Page2 = { + components: { IonPage }, + template: `` + } + const AppWithInject = { + components: { IonApp, IonRouterOutlet }, + template: '', + setup() { + const ionRouter = useIonRouter(); + return { ionRouter } + } + } + + const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: [ + { path: '/', component: Page } + { path: '/page2', component: Page2 } + ] + }); + + router.push('/'); + await router.isReady(); + const wrapper = mount(AppWithInject, { + global: { + plugins: [router, IonicVue] + } + }); + + const ionRouter = wrapper.vm.ionRouter; + expect(ionRouter.canGoBack()).toEqual(false); + + router.push('/page2'); + await waitForRouter(); + + expect(ionRouter.canGoBack()).toEqual(true); + + router.back(); + await waitForRouter(); + + expect(ionRouter.canGoBack()).toEqual(false); + }); + + // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/24109 + it('canGoBack() should return the correct value when using router.go', async () => { + const Page = { + components: { IonPage }, + template: `` + } + const Page2 = { + components: { IonPage }, + template: `` + } + const Page3 = { + components: { IonPage }, + template: `` + } + const AppWithInject = { + components: { IonApp, IonRouterOutlet }, + template: '', + setup() { + const ionRouter = useIonRouter(); + return { ionRouter } + } + } + + const router = createRouter({ + history: createWebHistory(process.env.BASE_URL), + routes: [ + { path: '/', component: Page } + { path: '/page2', component: Page2 }, + { path: '/page3', component: Page3 }, + ] + }); + + router.push('/'); + await router.isReady(); + const wrapper = mount(AppWithInject, { + global: { + plugins: [router, IonicVue] + } + }); + + const ionRouter = wrapper.vm.ionRouter; + expect(ionRouter.canGoBack()).toEqual(false); + + router.push('/page2'); + await waitForRouter(); + + expect(ionRouter.canGoBack()).toEqual(true); + + router.push('/page3'); + await waitForRouter(); + + expect(ionRouter.canGoBack()).toEqual(true); + + router.go(-2); + await waitForRouter(); + + expect(ionRouter.canGoBack()).toEqual(false); + + router.go(2); + await waitForRouter(); + + expect(ionRouter.canGoBack()).toEqual(true); + }); });