From 9c78759ab9106f145d8762121ee2e9f0cafaa708 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 3 Jan 2020 14:25:07 -0500 Subject: [PATCH 1/8] chore(stencil): update to latest --- core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/package.json b/core/package.json index ca2bdf48df..3de5a7b1c5 100644 --- a/core/package.json +++ b/core/package.json @@ -34,7 +34,7 @@ "tslib": "^1.10.0" }, "devDependencies": { - "@stencil/core": "1.8.2-3", + "@stencil/core": "1.8.3", "@stencil/sass": "1.0.1", "@types/jest": "24.0.21", "@types/node": "12.12.3", From c48d96f4eae1a22d7187923c1b0876a2211f2d41 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 3 Jan 2020 14:28:26 -0500 Subject: [PATCH 2/8] chore(scripts): include files to verify dist build for angular packages --- .scripts/test-dist.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.scripts/test-dist.js b/.scripts/test-dist.js index da94cb1c62..845c0b25fa 100644 --- a/.scripts/test-dist.js +++ b/.scripts/test-dist.js @@ -11,7 +11,22 @@ const fs = require('fs'); }, // angular { - files: ['../angular/dist/fesm5.cjs.js'] + files: [ + '../angular/dist/schematics/collection.json', + '../angular/dist/fesm5/ionic-angular.js', + '../angular/dist/fesm2015/ionic-angular.js', + '../angular/dist/ionic-angular.d.ts', + '../angular/dist/ionic-angular.metadata.json' + ] + }, + // angular-server + { + files: [ + '../packages/angular-server/dist/fesm5/ionic-angular-server.js', + '../packages/angular-server/dist/fesm2015/ionic-angular-server.js', + '../packages/angular-server/dist/ionic-angular-server.d.ts', + '../packages/angular-server/dist/ionic-angular-server.metadata.json' + ] }, // react { From cb94d8d97889cbd0154352a9e55be1ca0199dd32 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 3 Jan 2020 14:31:25 -0500 Subject: [PATCH 3/8] chore(scripts): comment out tests for release, remove core update --- .scripts/common.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.scripts/common.js b/.scripts/common.js index b8eff52c36..be2a21604d 100644 --- a/.scripts/common.js +++ b/.scripts/common.js @@ -158,17 +158,11 @@ function preparePackage(tasks, package, version, install) { title: `${pkg.name}: lint`, task: () => execa('npm', ['run', 'lint'], { cwd: projectRoot }) }); - projectTasks.push({ - title: `${pkg.name}: test`, - task: async () => await execa('npm', ['test'], { cwd: projectRoot }) - }); - projectTasks.push({ - title: `${pkg.name}: update ionic/core dep to ${version}`, - task: () => { - updateDependency(pkg, '@ionic/core', version); - writePkg(package, pkg); - } - }); + // TODO will not work due to https://github.com/ionic-team/ionic/issues/20136 + // projectTasks.push({ + // title: `${pkg.name}: test`, + // task: async () => await execa('npm', ['test'], { cwd: projectRoot }) + // }); } // Build From 0feace4f89f3b2e927f148fa7288238a0d9d558c Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 3 Jan 2020 16:58:29 -0500 Subject: [PATCH 4/8] chore(scripts): update main and dist package files for angular --- .scripts/common.js | 23 ++++++++++++++++++----- .scripts/copy-package.js | 12 ++++++++++++ .scripts/prepare.js | 3 +++ 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 .scripts/copy-package.js diff --git a/.scripts/common.js b/.scripts/common.js index be2a21604d..1a93fb4f07 100644 --- a/.scripts/common.js +++ b/.scripts/common.js @@ -268,11 +268,7 @@ function updatePackageVersions(tasks, packages, version) { } function updatePackageVersion(tasks, package, version) { - let projectRoot = projectPath(package); - - if (package === 'packages/angular-server' || package === 'angular') { - projectRoot = path.join(projectRoot, 'dist') - } + const projectRoot = projectPath(package); tasks.push({ title: `${package}: update package.json ${tc.dim(`(${version})`)}`, @@ -282,6 +278,22 @@ function updatePackageVersion(tasks, package, version) { }); } +function copyPackageToDist(tasks, packages) { + packages.forEach(package => { + const projectRoot = projectPath(package); + + // angular and angular-server are the only packages that publish dist + if (package !== 'angular' && package !== 'packages/angular-server') { + return; + } + + tasks.push({ + title: `${package}: Copy package.json to dist`, + task: () => execa('node', ['copy-package.js', package], { cwd: path.join(rootDir, '.scripts') }) + }); + }); +} + function publishPackages(tasks, packages, version, tag = 'latest') { // first verify version packages.forEach(package => { @@ -351,6 +363,7 @@ module.exports = { isValidVersion, isVersionGreater, copyCDNLoader, + copyPackageToDist, packages, packagePath, prepareDevPackage, diff --git a/.scripts/copy-package.js b/.scripts/copy-package.js new file mode 100644 index 0000000000..34e2010976 --- /dev/null +++ b/.scripts/copy-package.js @@ -0,0 +1,12 @@ + +// copy the package.json to the directory to dist to publish it + +const fs = require('fs'); +const path = require('path'); + +const package = process.argv[2]; + +const srcPath = path.join(__dirname, '..', package, 'package.json'); +let packageContent = fs.readFileSync(srcPath, 'utf-8'); + +fs.writeFileSync(path.join(__dirname, '..', package, 'dist', 'package.json'), packageContent); diff --git a/.scripts/prepare.js b/.scripts/prepare.js index 0d37e4bbce..fe17a73959 100644 --- a/.scripts/prepare.js +++ b/.scripts/prepare.js @@ -113,6 +113,9 @@ async function preparePackages(packages, version, install) { // add update package.json of each project common.updatePackageVersions(tasks, packages, version); + // copy package.json to dist + common.copyPackageToDist(tasks, packages); + // generate changelog generateChangeLog(tasks); From a5ba0c81b69d01c5f8e9df78e6926376880e3bbb Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Fri, 3 Jan 2020 17:19:45 -0500 Subject: [PATCH 5/8] 5.0.0-beta.3 --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++ angular/package.json | 4 ++-- core/package.json | 2 +- docs/package.json | 2 +- packages/angular-server/package.json | 4 ++-- packages/react-router/package.json | 10 ++++----- packages/react/package.json | 4 ++-- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 587c014e23..bafd623006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,34 @@ +# [5.0.0-beta.3](https://github.com/ionic-team/ionic/compare/v4.11.7...v5.0.0-beta.3) (2020-01-03) + + +### Bug Fixes + +* **animation:** support css animation on older devices ([#20020](https://github.com/ionic-team/ionic/issues/20020)) ([49c394c](https://github.com/ionic-team/ionic/commit/49c394c3d335795fd100f54a5b29db009d413dff)), closes [#20017](https://github.com/ionic-team/ionic/issues/20017) +* **icons:** fix the ellipsis fill in ionicons ([#20137](https://github.com/ionic-team/ionic/issues/20137)) ([9318d24](https://github.com/ionic-team/ionic/commit/9318d2418ec144bbce4a3b7ead33cf099d6ec25b)) +* **modal:** account for safe area on devices with a notch ([#20072](https://github.com/ionic-team/ionic/issues/20072)) ([1cabb53](https://github.com/ionic-team/ionic/commit/1cabb5365097d0675447a36223583824a58a140c)) +* **react:** fire lifecycle events on initial render, fixes [#20071](https://github.com/ionic-team/ionic/issues/20071) ([2dcf3ee](https://github.com/ionic-team/ionic/commit/2dcf3ee7b570be73be35c52f03ccfa09baf5d830)) + + +### Code Refactoring + +* **searchbar:** set inputmode default to undefined ([#20080](https://github.com/ionic-team/ionic/issues/20080)) ([6612604](https://github.com/ionic-team/ionic/commit/6612604733ac1b3e46546625f24ef6efa5be1538)), closes [#20074](https://github.com/ionic-team/ionic/issues/20074) + + +### Features + +* **angular:** angular 9 support ([#19515](https://github.com/ionic-team/ionic/issues/19515)) ([2344d0b](https://github.com/ionic-team/ionic/commit/2344d0b272105e368c00ef611f28909215162f7c)) +* **checkbox:** add --checkmark-width variable ([#19933](https://github.com/ionic-team/ionic/issues/19933)) ([c32a7bc](https://github.com/ionic-team/ionic/commit/c32a7bcd202993056923857a5d9eed14f5be8580)), closes [#16803](https://github.com/ionic-team/ionic/issues/16803) +* **radio:** add --border-radius and --inner-border-radius variables ([#20140](https://github.com/ionic-team/ionic/issues/20140)) ([a01c102](https://github.com/ionic-team/ionic/commit/a01c10267e18a48f30af2f552c556d31dad582e9)) +* **refresher:** add iOS native refresher ([#20037](https://github.com/ionic-team/ionic/issues/20037)) ([04e7c03](https://github.com/ionic-team/ionic/commit/04e7c031326ec551531af291ef1a03878d168378)), closes [#18664](https://github.com/ionic-team/ionic/issues/18664) +* **toggle:** add --border-radius and --handle-border-radius variables ([#20141](https://github.com/ionic-team/ionic/issues/20141)) ([02a46a1](https://github.com/ionic-team/ionic/commit/02a46a1007dde820cb158d34d4e3f243c07251dc)) + + +### BREAKING CHANGES + +* **searchbar:** The `inputmode` property for `ion-searchbar` now defaults to `undefined`. To get the old behavior, set the `inputmode` property to `"search"`. + + + ## [4.11.7](https://github.com/ionic-team/ionic/compare/v4.11.6...v4.11.7) (2019-12-12) diff --git a/angular/package.json b/angular/package.json index 04257a1549..a9bed913cc 100644 --- a/angular/package.json +++ b/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -42,7 +42,7 @@ "validate": "npm i && npm run lint && npm run test && npm run build" }, "dependencies": { - "@ionic/core": "5.0.0-beta.2", + "@ionic/core": "5.0.0-beta.3", "tslib": "^1.9.3" }, "peerDependencies": { diff --git a/core/package.json b/core/package.json index 3de5a7b1c5..5c3454100d 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "description": "Base components for Ionic", "keywords": [ "ionic", diff --git a/docs/package.json b/docs/package.json index 32a99535e6..4e1bbe182f 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index 4c9e734b7d..9436ddebb6 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "0.0.2", + "version": "5.0.0-beta.3", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -49,7 +49,7 @@ "@angular/core": "8.2.13", "@angular/platform-browser": "8.2.13", "@angular/platform-server": "8.2.13", - "@ionic/core": "*", + "@ionic/core": "5.0.0-beta.3", "ng-packagr": "5.7.1", "tslint": "^5.12.1", "tslint-ionic-rules": "0.0.21", diff --git a/packages/react-router/package.json b/packages/react-router/package.json index c23105b95a..174a69f12a 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -39,16 +39,16 @@ "tslib": "*" }, "peerDependencies": { - "@ionic/core": "5.0.0-beta.2", - "@ionic/react": "5.0.0-beta.2", + "@ionic/core": "5.0.0-beta.3", + "@ionic/react": "5.0.0-beta.3", "react": "^16.8.6", "react-dom": "^16.8.6", "react-router": "^5.0.1", "react-router-dom": "^5.0.1" }, "devDependencies": { - "@ionic/core": "5.0.0-beta.2", - "@ionic/react": "5.0.0-beta.2", + "@ionic/core": "5.0.0-beta.3", + "@ionic/react": "5.0.0-beta.3", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/packages/react/package.json b/packages/react/package.json index 12f34b09b8..88887201b4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -39,7 +39,7 @@ "css/" ], "dependencies": { - "@ionic/core": "5.0.0-beta.2", + "@ionic/core": "5.0.0-beta.3", "ionicons": "^5.0.0-13", "tslib": "*" }, From 5b81bdfcf18ed182bde14bbea4957b49ea886322 Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Fri, 3 Jan 2020 14:56:26 -0500 Subject: [PATCH 6/8] feat(refresher): add MD native refresher (#20096) resolves #17316 --- .../refresher-content/refresher-content.tsx | 17 +- core/src/components/refresher/readme.md | 12 +- .../components/refresher/refresher.md.scss | 71 +++++++ .../refresher/refresher.md.vars.scss | 16 +- core/src/components/refresher/refresher.scss | 13 ++ core/src/components/refresher/refresher.tsx | 188 ++++++++++++++---- .../components/refresher/refresher.utils.ts | 158 +++++++++++++-- .../components/refresher/test/spec/index.html | 34 +++- core/src/components/refresher/usage/react.md | 1 - .../src/components/spinner/spinner-configs.ts | 6 +- core/src/components/spinner/spinner.scss | 2 +- 11 files changed, 445 insertions(+), 73 deletions(-) diff --git a/core/src/components/refresher-content/refresher-content.tsx b/core/src/components/refresher-content/refresher-content.tsx index 078908f141..6e0a15b205 100644 --- a/core/src/components/refresher-content/refresher-content.tsx +++ b/core/src/components/refresher-content/refresher-content.tsx @@ -51,14 +51,14 @@ export class RefresherContent implements ComponentInterface { const mode = getIonMode(this); this.pullingIcon = config.get( 'refreshingIcon', - mode === 'ios' && isPlatform('mobile') ? config.get('spinner', 'lines') : 'arrow-down' + mode === 'ios' && isPlatform('mobile') ? config.get('spinner', 'lines') : 'circular' ); } if (this.refreshingSpinner === undefined) { const mode = getIonMode(this); this.refreshingSpinner = config.get( 'refreshingSpinner', - config.get('spinner', mode === 'ios' ? 'lines' : 'crescent') + config.get('spinner', mode === 'ios' ? 'lines' : 'circular') ); } } @@ -66,12 +66,21 @@ export class RefresherContent implements ComponentInterface { render() { const pullingIcon = this.pullingIcon; const hasSpinner = pullingIcon != null && SPINNERS[pullingIcon] as any !== undefined; + const mode = getIonMode(this); + return ( - +
{this.pullingIcon && hasSpinner &&
- +
+ + {mode === 'md' && this.pullingIcon === 'circular' && +
+ +
+ } +
} {this.pullingIcon && !hasSpinner && diff --git a/core/src/components/refresher/readme.md b/core/src/components/refresher/readme.md index 10f249f2cc..765cc26598 100644 --- a/core/src/components/refresher/readme.md +++ b/core/src/components/refresher/readme.md @@ -10,14 +10,19 @@ refresher. ### Native Refreshers -Both iOS and Android platforms provide refreshers that take advantage of properties exposed by their respective devices that give pull to refresh a fluid, native-like feel. One of the limitations of this is that the refreshers only work on their respective platform devices. For example, the iOS native `ion-refresher` works on an iPhone in iOS mode, but does not work on an Android device in iOS mode. In order for the refresher to work on an unsupported device, we provide a fallback refresher. This can also be set manually by overriding the `pullingIcon` property. +Both iOS and Android platforms provide refreshers that take advantage of properties exposed by their respective devices that give pull to refresh a fluid, native-like feel. -Because much of the native refreshers are based on scrolling, certain properties such as `pullMin` and `snapbackDuration` are not compatible. See [ion-refresher Properties](#properties) for more information. +Certain properties such as `pullMin` and `snapbackDuration` are not compatible because much of the native refreshers are scroll-based. See [Refresher Properties](#properties) for more information. #### iOS Usage -Using the iOS native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [ion-spinner Documentation](../spinner#properties) for accepted values. The `pullingIcon` defaults to the `lines` spinner on iOS. The spinner tick marks will be progressively shown as the user pulls down on the page. In order for the refresher to work on a device that isn't an iOS mobile device, the `pullingIcon` should be set to an icon. +Using the iOS native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [Spinner Documentation](../spinner#properties) for accepted values. The `pullingIcon` defaults to the `lines` spinner on iOS. The spinner tick marks will be progressively shown as the user pulls down on the page. +The iOS native `ion-refresher` relies on rubber band scrolling in order to work properly and is only compatible with iOS devices as a result. We provide a fallback refresher for apps running in iOS mode on devices that do not support rubber band scrolling. + +#### Android Usage + +Using the MD native `ion-refresher` requires setting the `pullingIcon` property on `ion-refresher-content` to the value of one of the available spinners. See the [ion-spinner Documentation](../spinner#properties) for accepted values. `pullingIcon` defaults to the `circular` spinner on MD. @@ -154,7 +159,6 @@ export const RefresherExample: React.FC = () => ( ); - ``` diff --git a/core/src/components/refresher/refresher.md.scss b/core/src/components/refresher/refresher.md.scss index 334734a17d..cff93bb394 100644 --- a/core/src/components/refresher/refresher.md.scss +++ b/core/src/components/refresher/refresher.md.scss @@ -25,3 +25,74 @@ .refresher-md .refresher-refreshing .spinner-dots circle { fill: $refresher-md-icon-color; } + +ion-refresher.refresher-native { + display: block; + + z-index: 1; + + ion-spinner { + @include margin(0, auto, 0, auto); + + width: 24px; + height: 24px; + + color: $refresher-md-native-spinner-color; + } + + .spinner-arrow-container { + display: inherit; + } + + .arrow-container { + display: block; + position: absolute; + + width: 24px; + height: 24px; + ion-icon { + @include margin(0, auto, 0, auto); + @include position(null, 0, -4px, 0); + + position: absolute; + + color: $refresher-md-native-spinner-color; + + font-size: 12px; + } + } + + &.refresher-pulling ion-refresher-content, + &.refresher-ready ion-refresher-content { + .refresher-pulling { + display: flex; + } + } + + &.refresher-refreshing ion-refresher-content, + &.refresher-completing ion-refresher-content, + &.refresher-cancelling ion-refresher-content { + .refresher-refreshing { + display: flex; + } + } + + .refresher-pulling-icon { + transform: translateY(calc(-100% - 10px)); + } + + .refresher-pulling-icon, + .refresher-refreshing-icon { + @include margin(0, auto, 0, auto); + @include border-radius(100%); + @include padding(8px, 8px, 8px, 8px); + + display: flex; + + border: $refresher-md-native-spinner-border; + + background: $refresher-md-native-spinner-background; + + box-shadow: $refresher-md-native-spinner-box-shadow; + } +} \ No newline at end of file diff --git a/core/src/components/refresher/refresher.md.vars.scss b/core/src/components/refresher/refresher.md.vars.scss index 87e74088f6..11d5ac73c7 100644 --- a/core/src/components/refresher/refresher.md.vars.scss +++ b/core/src/components/refresher/refresher.md.vars.scss @@ -1,7 +1,19 @@ @import "../../themes/ionic.globals.md"; /// @prop - Color of the refresher icon -$refresher-md-icon-color: $text-color !default; +$refresher-md-icon-color: $text-color !default; /// @prop - Text color of the refresher content -$refresher-md-text-color: $text-color !default; +$refresher-md-text-color: $text-color !default; + +/// @prop - Color of the native refresher spinner +$refresher-md-native-spinner-color: #{ion-color(primary, base)} !default; + +/// @prop - Border of the native refresher spinner +$refresher-md-native-spinner-border: 1px solid #ececec !default; + +/// @prop - Background of the native refresher spinner +$refresher-md-native-spinner-background: white !default; + +/// @prop - Box shadow of the native refresher spinner +$refresher-md-native-spinner-box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.1) !default; diff --git a/core/src/components/refresher/refresher.scss b/core/src/components/refresher/refresher.scss index 855e246b21..b0b4c5febc 100644 --- a/core/src/components/refresher/refresher.scss +++ b/core/src/components/refresher/refresher.scss @@ -56,6 +56,10 @@ ion-refresher-content { text-align: center; } +ion-refresher-content .arrow-container { + display: none; +} + // Refresher Content States // -------------------------------------------------- @@ -100,3 +104,12 @@ ion-refresher-content { transform: scale(0); } } + +// Refresher Native +// -------------------------------------------------- + +.refresher-native { + .refresher-pulling-text, .refresher-refreshing-text { + display: none; + } +} \ No newline at end of file diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index f4bbae1b55..191edc53ac 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -1,11 +1,12 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask, writeTask } from '@stencil/core'; +import { getTimeGivenProgression } from '../../'; import { getIonMode } from '../../global/ionic-global'; -import { Gesture, GestureDetail, RefresherEventDetail } from '../../interface'; +import { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface'; import { clamp } from '../../utils/helpers'; import { hapticImpact } from '../../utils/native/haptic'; -import { handleScrollWhilePulling, handleScrollWhileRefreshing, setSpinnerOpacity, shouldUseNativeRefresher, translateElement } from './refresher.utils'; +import { createPullingAnimation, createSnapBackAnimation, getRefresherAnimationType, handleScrollWhilePulling, handleScrollWhileRefreshing, setSpinnerOpacity, shouldUseNativeRefresher, transitionEndAsync, translateElement } from './refresher.utils'; @Component({ tag: 'ion-refresher', @@ -28,6 +29,7 @@ export class Refresher implements ComponentInterface { private didRefresh = false; private lastVelocityY = 0; private elementToTransform?: HTMLElement; + private animations: Animation[] = []; @State() private nativeRefresher = false; @@ -142,26 +144,24 @@ export class Refresher implements ComponentInterface { private async resetNativeRefresher(el: HTMLElement | undefined, state: RefresherState) { this.state = state; - if (el !== undefined) { + if (getIonMode(this) === 'ios') { await translateElement(el, undefined); + } else { + await transitionEndAsync(this.el.querySelector('.refresher-refreshing-icon')); } this.didRefresh = false; this.needsCompletion = false; this.pointerDown = false; + this.animations.forEach(ani => ani.destroy()); + this.animations = []; + this.progress = 0; + this.state = RefresherState.Inactive; } - private async setupNativeRefresher(contentEl: HTMLIonContentElement | null) { - if (this.scrollListenerCallback || !contentEl) { - return; - } - - const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLElement; - const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner') as HTMLElement; - + private async setupiOSNativeRefresher(pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) { this.elementToTransform = this.scrollEl!.querySelector(`#scroll-content`) as HTMLElement | undefined; - this.nativeRefresher = true; const ticks = pullingSpinner.shadowRoot!.querySelectorAll('svg'); const MAX_PULL = this.scrollEl!.clientHeight * 0.16; const NUM_TICKS = ticks.length; @@ -198,7 +198,10 @@ export class Refresher implements ComponentInterface { this.ionStart.emit(); } - this.ionPull.emit(); + // emit "pulling" on every move + if (this.pointerDown) { + this.ionPull.emit(); + } } // delay showing the next tick marks until user has pulled 30px @@ -235,39 +238,144 @@ export class Refresher implements ComponentInterface { this.scrollEl!.addEventListener('scroll', this.scrollListenerCallback); this.gesture = (await import('../../utils/gesture')).createGesture({ - el: this.scrollEl!, - gestureName: 'refresher', - gesturePriority: 10, - direction: 'y', - threshold: 0, - onStart: () => { - this.pointerDown = true; + el: this.scrollEl!, + gestureName: 'refresher', + gesturePriority: 10, + direction: 'y', + threshold: 0, + onStart: () => { + this.pointerDown = true; - if (!this.didRefresh) { - translateElement(this.elementToTransform, '0px'); - } - }, - onMove: ev => { - this.lastVelocityY = ev.velocityY; - }, - onEnd: () => { - this.pointerDown = false; - this.didStart = false; + if (!this.didRefresh) { + translateElement(this.elementToTransform, '0px'); + } + }, + onMove: ev => { + this.lastVelocityY = ev.velocityY; + }, + onEnd: () => { + this.pointerDown = false; + this.didStart = false; - if (this.needsCompletion) { - this.resetNativeRefresher(this.elementToTransform, RefresherState.Completing); - this.needsCompletion = false; - } else if (this.didRefresh) { - readTask(() => { - translateElement(this.elementToTransform, `${this.el.clientHeight}px`); - }); - } - }, - }); + if (this.needsCompletion) { + this.resetNativeRefresher(this.elementToTransform, RefresherState.Completing); + this.needsCompletion = false; + } else if (this.didRefresh) { + readTask(() => translateElement(this.elementToTransform, `${this.el.clientHeight}px`)); + } + }, + }); this.disabledChanged(); } + private async setupMDNativeRefresher(contentEl: HTMLIonContentElement, pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) { + const circle = pullingSpinner.shadowRoot!.querySelector('circle'); + const pullingRefresherIcon = this.el.querySelector('ion-refresher-content .refresher-pulling-icon') as HTMLElement; + const refreshingCircle = refreshingSpinner.shadowRoot!.querySelector('circle'); + + if (circle !== null && refreshingCircle !== null) { + writeTask(() => { + circle.style.setProperty('animation', 'none'); + + // This lines up the animation on the refreshing spinner with the pulling spinner + refreshingSpinner.style.setProperty('animation-delay', '-655ms'); + refreshingCircle.style.setProperty('animation-delay', '-655ms'); + }); + } + + this.gesture = (await import('../../utils/gesture')).createGesture({ + el: this.scrollEl!, + gestureName: 'refresher', + gesturePriority: 10, + direction: 'y', + threshold: 0, + canStart: () => this.state !== RefresherState.Refreshing && this.state !== RefresherState.Completing && this.scrollEl!.scrollTop === 0, + onStart: (ev: GestureDetail) => { + ev.data = { animation: undefined, didStart: false, cancelled: false }; + }, + onMove: (ev: GestureDetail) => { + if ((ev.velocityY < 0 && this.progress === 0 && !ev.data.didStart) || ev.data.cancelled) { + ev.data.cancelled = true; + return; + } + + if (!ev.data.didStart) { + ev.data.didStart = true; + + this.state = RefresherState.Pulling; + + writeTask(() => { + const animationType = getRefresherAnimationType(contentEl); + const animation = createPullingAnimation(animationType, pullingRefresherIcon); + ev.data.animation = animation; + + this.scrollEl!.style.setProperty('--overflow', 'hidden'); + + animation.progressStart(false, 0); + this.ionStart.emit(); + this.animations.push(animation); + }); + + return; + } + + // Since we are using an easing curve, slow the gesture tracking down a bit + this.progress = clamp(0, (ev.deltaY / 180) * 0.5, 1); + ev.data.animation.progressStep(this.progress); + this.ionPull.emit(); + }, + onEnd: (ev: GestureDetail) => { + if (!ev.data.didStart) { return; } + + writeTask(() => this.scrollEl!.style.removeProperty('--overflow')); + if (this.progress <= 0.4) { + this.gesture!.enable(false); + + ev.data.animation + .progressEnd(0, this.progress, 500) + .onFinish(() => { + this.animations.forEach(ani => ani.destroy()); + this.animations = []; + this.gesture!.enable(true); + this.state = RefresherState.Inactive; + }); + return; + } + + const progress = getTimeGivenProgression([0, 0], [0, 0], [1, 1], [1, 1], this.progress)[0]; + const snapBackAnimation = createSnapBackAnimation(pullingRefresherIcon); + this.animations.push(snapBackAnimation); + writeTask(async () => { + pullingRefresherIcon.style.setProperty('--ion-pulling-refresher-translate', `${(progress * 100)}px`); + ev.data.animation.progressEnd(); + await snapBackAnimation.play(); + this.beginRefresh(); + ev.data.animation.destroy(); + }); + } + }); + + this.disabledChanged(); + } + + private async setupNativeRefresher(contentEl: HTMLIonContentElement | null) { + if (this.scrollListenerCallback || !contentEl || this.nativeRefresher) { + return; + } + + this.nativeRefresher = true; + + const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLIonSpinnerElement; + const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner') as HTMLIonSpinnerElement; + + if (getIonMode(this) === 'ios') { + this.setupiOSNativeRefresher(pullingSpinner, refreshingSpinner); + } else { + this.setupMDNativeRefresher(contentEl, pullingSpinner, refreshingSpinner); + } + } + componentDidUpdate() { this.checkNativeRefresher(); } diff --git a/core/src/components/refresher/refresher.utils.ts b/core/src/components/refresher/refresher.utils.ts index c0bbfae032..8dcda4d266 100644 --- a/core/src/components/refresher/refresher.utils.ts +++ b/core/src/components/refresher/refresher.utils.ts @@ -1,7 +1,123 @@ import { writeTask } from '@stencil/core'; +import { createAnimation } from '../../'; import { isPlatform } from '../../utils/platform'; +// MD Native Refresher +// ----------------------------- +type RefresherAnimationType = 'scale' | 'translate'; + +export const getRefresherAnimationType = (contentEl: HTMLIonContentElement): RefresherAnimationType => { + const previousSibling = contentEl.previousElementSibling; + const hasHeader = previousSibling !== null && previousSibling.tagName === 'ION-HEADER'; + + return hasHeader ? 'translate' : 'scale'; +}; + +export const createPullingAnimation = (type: RefresherAnimationType, pullingSpinner: HTMLElement) => { + return type === 'scale' ? createScaleAnimation(pullingSpinner) : createTranslateAnimation(pullingSpinner); +}; + +const createBaseAnimation = (pullingRefresherIcon: HTMLElement) => { + const spinner = pullingRefresherIcon.querySelector('ion-spinner') as HTMLElement; + const circle = spinner!.shadowRoot!.querySelector('circle') as any; + const spinnerArrowContainer = pullingRefresherIcon.querySelector('.spinner-arrow-container') as HTMLElement; + const arrowContainer = pullingRefresherIcon!.querySelector('.arrow-container'); + const arrow = (arrowContainer) ? arrowContainer!.querySelector('ion-icon') as HTMLElement : null; + + const baseAnimation = createAnimation() + .duration(1000) + .easing('ease-out'); + + const spinnerArrowContainerAnimation = createAnimation() + .addElement(spinnerArrowContainer) + .keyframes([ + { offset: 0, opacity: '0.3' }, + { offset: 0.45, opacity: '0.3' }, + { offset: 0.55, opacity: '1' }, + { offset: 1, opacity: '1' } + ]); + + const circleInnerAnimation = createAnimation() + .addElement(circle) + .keyframes([ + { offset: 0, 'stroke-dasharray': '1px, 200px' }, + { offset: 0.20, 'stroke-dasharray': '1px, 200px' }, + { offset: 0.55, 'stroke-dasharray': '100px, 200px' }, + { offset: 1, 'stroke-dasharray': '100px, 200px' } + ]); + + const circleOuterAnimation = createAnimation() + .addElement(spinner) + .keyframes([ + { offset: 0, transform: 'rotate(-90deg)' }, + { offset: 1, transform: 'rotate(210deg)' } + ]); + + /** + * Only add arrow animation if present + * this allows users to customize the spinners + * without errors being thrown + */ + if (arrowContainer && arrow) { + const arrowContainerAnimation = createAnimation() + .addElement(arrowContainer) + .keyframes([ + { offset: 0, transform: 'rotate(0deg)' }, + { offset: 0.30, transform: 'rotate(0deg)' }, + { offset: 0.55, transform: 'rotate(280deg)' }, + { offset: 1, transform: 'rotate(400deg)' } + ]); + + const arrowAnimation = createAnimation() + .addElement(arrow) + .keyframes([ + { offset: 0, transform: 'translateX(2px) scale(0)' }, + { offset: 0.30, transform: 'translateX(2px) scale(0)' }, + { offset: 0.55, transform: 'translateX(-1.5px) scale(1)' }, + { offset: 1, transform: 'translateX(-1.5px) scale(1)' } + ]); + + baseAnimation.addAnimation([arrowContainerAnimation, arrowAnimation]); + } + + return baseAnimation.addAnimation([spinnerArrowContainerAnimation, circleInnerAnimation, circleOuterAnimation]); +}; + +const createScaleAnimation = (pullingRefresherIcon: HTMLElement) => { + const height = pullingRefresherIcon.clientHeight; + const spinnerAnimation = createAnimation() + .addElement(pullingRefresherIcon) + .keyframes([ + { offset: 0, transform: `scale(0) translateY(-${height + 20}px)` }, + { offset: 1, transform: 'scale(1) translateY(100px)' } + ]); + + return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]); +}; + +const createTranslateAnimation = (pullingRefresherIcon: HTMLElement) => { + const height = pullingRefresherIcon.clientHeight; + const spinnerAnimation = createAnimation() + .addElement(pullingRefresherIcon) + .keyframes([ + { offset: 0, transform: `translateY(-${height + 20}px)` }, + { offset: 1, transform: 'translateY(100px)' } + ]); + + return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]); +}; + +export const createSnapBackAnimation = (pullingRefresherIcon: HTMLElement) => { + return createAnimation() + .duration(125) + .addElement(pullingRefresherIcon) + .fromTo('transform', 'translateY(var(--ion-pulling-refresher-translate, 100px))', 'translateY(0px)'); +}; + +// iOS Native Refresher +// ----------------------------- + export const setSpinnerOpacity = (spinner: HTMLElement, opacity: number) => { spinner.style.setProperty('opacity', opacity.toString()); }; @@ -29,6 +145,27 @@ export const handleScrollWhileRefreshing = ( }); }; +export const translateElement = (el?: HTMLElement, value?: string) => { + if (!el) { return Promise.resolve(); } + + const trans = transitionEndAsync(el); + + writeTask(() => { + el.style.setProperty('transition', '0.2s all ease-out'); + + if (value === undefined) { + el.style.removeProperty('transform'); + } else { + el.style.setProperty('transform', `translate3d(0px, ${value}, 0px)`); + } + }); + + return trans; +}; + +// Utils +// ----------------------------- + export const shouldUseNativeRefresher = (referenceEl: HTMLIonRefresherElement, mode: string) => { const pullingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-pulling ion-spinner'); const refreshingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-refreshing ion-spinner'); @@ -36,26 +173,17 @@ export const shouldUseNativeRefresher = (referenceEl: HTMLIonRefresherElement, m return ( pullingSpinner !== null && refreshingSpinner !== null && - mode === 'ios' && - isPlatform('mobile') + ( + (mode === 'ios' && isPlatform('mobile')) || + mode === 'md' + ) + ); }; -export const translateElement = (el?: HTMLElement, value?: string) => { +export const transitionEndAsync = (el: HTMLElement | null) => { return new Promise(resolve => { - if (!el) { return resolve(); } - transitionEnd(el, resolve); - - writeTask(() => { - el.style.setProperty('transition', '0.2s all ease-out'); - - if (value === undefined) { - el.style.removeProperty('transform'); - } else { - el.style.setProperty('transform', `translate3d(0px, ${value}, 0px)`); - } - }); }); }; diff --git a/core/src/components/refresher/test/spec/index.html b/core/src/components/refresher/test/spec/index.html index 3a96dd98de..8c188ddeab 100644 --- a/core/src/components/refresher/test/spec/index.html +++ b/core/src/components/refresher/test/spec/index.html @@ -44,11 +44,40 @@ + + + + + + - - + \ No newline at end of file diff --git a/core/src/components/refresher/usage/react.md b/core/src/components/refresher/usage/react.md index 4fe3fc1c60..a2cc671666 100644 --- a/core/src/components/refresher/usage/react.md +++ b/core/src/components/refresher/usage/react.md @@ -41,5 +41,4 @@ export const RefresherExample: React.FC = () => ( ); - ``` \ No newline at end of file diff --git a/core/src/components/spinner/spinner-configs.ts b/core/src/components/spinner/spinner-configs.ts index 07ac173fbf..632842f47a 100644 --- a/core/src/components/spinner/spinner-configs.ts +++ b/core/src/components/spinner/spinner-configs.ts @@ -44,10 +44,10 @@ const spinners = { fn: () => { return { r: 20, - cx: 44, - cy: 44, + cx: 48, + cy: 48, fill: 'none', - viewBox: '22 22 44 44', + viewBox: '24 24 48 48', transform: 'translate(0,0)', style: {} }; diff --git a/core/src/components/spinner/spinner.scss b/core/src/components/spinner/spinner.scss index ea1b75b4a3..eddfb393c8 100644 --- a/core/src/components/spinner/spinner.scss +++ b/core/src/components/spinner/spinner.scss @@ -111,7 +111,7 @@ svg { stroke: currentColor; stroke-dasharray: 80px, 200px; stroke-dashoffset: 0px; - stroke-width: 3.6; + stroke-width: 5.6; fill: none; } From fc35dd3efe92ff9cdcc8f43b2d483dd91f0ea791 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 6 Jan 2020 11:22:02 -0500 Subject: [PATCH 7/8] chore(scripts): update angular versions in dist directly --- .scripts/common.js | 16 ++++++++++++++++ .scripts/copy-package.js | 12 ------------ .scripts/prepare.js | 3 --- 3 files changed, 16 insertions(+), 15 deletions(-) delete mode 100644 .scripts/copy-package.js diff --git a/.scripts/common.js b/.scripts/common.js index 1a93fb4f07..0966c72191 100644 --- a/.scripts/common.js +++ b/.scripts/common.js @@ -254,6 +254,22 @@ function updatePackageVersions(tasks, packages, version) { } }); + // angular & angular-server need to update their dist versions + if (package === 'angular' || package === 'packages/angular-server') { + const distPackage = path.join(package, 'dist'); + + updatePackageVersion(tasks, distPackage, version); + + tasks.push({ + title: `${package} update @ionic/core dependency, if present ${tc.dim(`(${version})`)}`, + task: async () => { + const pkg = readPkg(distPackage); + updateDependency(pkg, '@ionic/core', version); + writePkg(distPackage, pkg); + } + }); + } + if (package === 'packages/react-router') { tasks.push({ title: `${package} update @ionic/react dependency, if present ${tc.dim(`(${version})`)}`, diff --git a/.scripts/copy-package.js b/.scripts/copy-package.js deleted file mode 100644 index 34e2010976..0000000000 --- a/.scripts/copy-package.js +++ /dev/null @@ -1,12 +0,0 @@ - -// copy the package.json to the directory to dist to publish it - -const fs = require('fs'); -const path = require('path'); - -const package = process.argv[2]; - -const srcPath = path.join(__dirname, '..', package, 'package.json'); -let packageContent = fs.readFileSync(srcPath, 'utf-8'); - -fs.writeFileSync(path.join(__dirname, '..', package, 'dist', 'package.json'), packageContent); diff --git a/.scripts/prepare.js b/.scripts/prepare.js index fe17a73959..0d37e4bbce 100644 --- a/.scripts/prepare.js +++ b/.scripts/prepare.js @@ -113,9 +113,6 @@ async function preparePackages(packages, version, install) { // add update package.json of each project common.updatePackageVersions(tasks, packages, version); - // copy package.json to dist - common.copyPackageToDist(tasks, packages); - // generate changelog generateChangeLog(tasks); From dae5c5d28c2b3ae8966ec6a7f8b2c83f0ce7e4f5 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Mon, 6 Jan 2020 12:04:40 -0500 Subject: [PATCH 8/8] 5.0.0-beta.4 --- CHANGELOG.md | 9 +++++++++ angular/package.json | 4 ++-- core/package.json | 2 +- docs/package.json | 2 +- packages/angular-server/package.json | 4 ++-- packages/react-router/package.json | 10 +++++----- packages/react/package.json | 4 ++-- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bafd623006..685e001355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# [5.0.0-beta.4](https://github.com/ionic-team/ionic/compare/v5.0.0-beta.3...v5.0.0-beta.4) (2020-01-06) + + +### Features + +* **refresher:** add MD native refresher ([#20096](https://github.com/ionic-team/ionic/issues/20096)) ([5b81bdf](https://github.com/ionic-team/ionic/commit/5b81bdfcf18ed182bde14bbea4957b49ea886322)), closes [#17316](https://github.com/ionic-team/ionic/issues/17316) + + + # [5.0.0-beta.3](https://github.com/ionic-team/ionic/compare/v4.11.7...v5.0.0-beta.3) (2020-01-03) diff --git a/angular/package.json b/angular/package.json index a9bed913cc..6792e2b5bc 100644 --- a/angular/package.json +++ b/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -42,7 +42,7 @@ "validate": "npm i && npm run lint && npm run test && npm run build" }, "dependencies": { - "@ionic/core": "5.0.0-beta.3", + "@ionic/core": "5.0.0-beta.4", "tslib": "^1.9.3" }, "peerDependencies": { diff --git a/core/package.json b/core/package.json index 5c3454100d..e4c30c6830 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "description": "Base components for Ionic", "keywords": [ "ionic", diff --git a/docs/package.json b/docs/package.json index 4e1bbe182f..59d42f7a96 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index 9436ddebb6..d33c6773b7 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -49,7 +49,7 @@ "@angular/core": "8.2.13", "@angular/platform-browser": "8.2.13", "@angular/platform-server": "8.2.13", - "@ionic/core": "5.0.0-beta.3", + "@ionic/core": "5.0.0-beta.4", "ng-packagr": "5.7.1", "tslint": "^5.12.1", "tslint-ionic-rules": "0.0.21", diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 174a69f12a..bacd55b289 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -39,16 +39,16 @@ "tslib": "*" }, "peerDependencies": { - "@ionic/core": "5.0.0-beta.3", - "@ionic/react": "5.0.0-beta.3", + "@ionic/core": "5.0.0-beta.4", + "@ionic/react": "5.0.0-beta.4", "react": "^16.8.6", "react-dom": "^16.8.6", "react-router": "^5.0.1", "react-router-dom": "^5.0.1" }, "devDependencies": { - "@ionic/core": "5.0.0-beta.3", - "@ionic/react": "5.0.0-beta.3", + "@ionic/core": "5.0.0-beta.4", + "@ionic/react": "5.0.0-beta.4", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/packages/react/package.json b/packages/react/package.json index 88887201b4..73cf613a8d 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -39,7 +39,7 @@ "css/" ], "dependencies": { - "@ionic/core": "5.0.0-beta.3", + "@ionic/core": "5.0.0-beta.4", "ionicons": "^5.0.0-13", "tslib": "*" },