mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 05:58:26 +08:00
Merge branch 'core/overlay' into mono-refactor
This commit is contained in:
@ -162,8 +162,8 @@
|
||||
|
||||
<ion-button>Default</ion-button>
|
||||
|
||||
<ion-loading-ctrl></ion-loading-ctrl>
|
||||
<ion-modal-ctrl></ion-modal-ctrl>
|
||||
<ion-loading-controller></ion-loading-controller>
|
||||
<ion-modal-controller></ion-modal-controller>
|
||||
|
||||
<script>
|
||||
|
||||
@ -219,8 +219,8 @@
|
||||
|
||||
console.log('open profile modal for userId:', userId);
|
||||
|
||||
var modalCtrl = document.querySelector('ion-modal-ctrl');
|
||||
modalCtrl.create({ component: 'profile-page', componentProps: { userId: userId } }).then(modal => {
|
||||
var modalController = document.querySelector('ion-modal-controller');
|
||||
modalController.create({ component: 'profile-page', componentProps: { userId: userId } }).then(modal => {
|
||||
console.log('start presenting modal, userId:', modal.componentProps.userId);
|
||||
|
||||
modal.present().then(() => {
|
||||
@ -271,8 +271,8 @@
|
||||
document.getElementById('presentLoading').addEventListener('click', function() {
|
||||
console.log('open loading');
|
||||
|
||||
var loadingCtrl = document.querySelector('ion-loading-ctrl');
|
||||
loadingCtrl.create({ duration: 1000 }).then(loading => {
|
||||
var loadingController = document.querySelector('ion-loading-controller');
|
||||
loadingController.create({ duration: 1000 }).then(loading => {
|
||||
console.log('start presenting loading');
|
||||
|
||||
loading.present().then(() => {
|
||||
@ -285,10 +285,10 @@
|
||||
document.getElementById('toggleMenu').addEventListener('click', function() {
|
||||
console.log('toggle menu');
|
||||
|
||||
Ionic.controller('menu').then(menuCtrl => {
|
||||
Ionic.controller('menu').then(menucontroller => {
|
||||
console.log('start menu toggle');
|
||||
|
||||
menuCtrl.toggle().then(() => {
|
||||
menucontroller.toggle().then(() => {
|
||||
console.log('finished menu toggle');
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,68 @@
|
||||
import { Component, Listen, Method } from '@stencil/core';
|
||||
import { ActionSheet, ActionSheetEvent, ActionSheetOptions } from '../../index';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-action-sheet-controller'
|
||||
})
|
||||
export class ActionSheetController {
|
||||
private ids = 0;
|
||||
private actionSheetResolves: { [actionSheetId: string]: Function } = {};
|
||||
private actionSheets: ActionSheet[] = [];
|
||||
|
||||
@Method()
|
||||
create(opts?: ActionSheetOptions) {
|
||||
// create ionic's wrapping ion-action-sheet component
|
||||
const actionSheet = document.createElement('ion-action-sheet');
|
||||
|
||||
const id = this.ids++;
|
||||
|
||||
// give this action sheet a unique id
|
||||
actionSheet.id = `action-sheet-${id}`;
|
||||
actionSheet.style.zIndex = (20000 + id).toString();
|
||||
|
||||
// convert the passed in action sheet options into props
|
||||
// that get passed down into the new action sheet
|
||||
Object.assign(actionSheet, opts);
|
||||
|
||||
// append the action sheet element to the document body
|
||||
const appRoot = document.querySelector('ion-app') || document.body;
|
||||
appRoot.appendChild(actionSheet as any);
|
||||
|
||||
// store the resolve function to be called later up when the action sheet loads
|
||||
return new Promise<ActionSheet>(resolve => {
|
||||
this.actionSheetResolves[actionSheet.id] = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
@Listen('body:ionActionSheetDidLoad')
|
||||
protected viewDidLoad(ev: ActionSheetEvent) {
|
||||
const actionSheet = ev.detail.actionSheet;
|
||||
const actionSheetResolve = this.actionSheetResolves[actionSheet.id];
|
||||
if (actionSheetResolve) {
|
||||
actionSheetResolve(actionSheet);
|
||||
delete this.actionSheetResolves[actionSheet.id];
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('body:ionActionSheetWillPresent')
|
||||
protected willPresent(ev: ActionSheetEvent) {
|
||||
this.actionSheets.push(ev.detail.actionSheet);
|
||||
}
|
||||
|
||||
@Listen('body:ionActionSheetWillDismiss, body:ionActionSheetDidUnload')
|
||||
protected willDismiss(ev: ActionSheetEvent) {
|
||||
const index = this.actionSheets.indexOf(ev.detail.actionSheet);
|
||||
if (index > -1) {
|
||||
this.actionSheets.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('body:keyup.escape')
|
||||
protected escapeKeyUp() {
|
||||
const lastActionSheet = this.actionSheets[this.actionSheets.length - 1];
|
||||
if (lastActionSheet) {
|
||||
lastActionSheet.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
189
packages/core/src/components/action-sheet/action-sheet.ios.scss
Normal file
189
packages/core/src/components/action-sheet/action-sheet.ios.scss
Normal file
@ -0,0 +1,189 @@
|
||||
@import "../../themes/ionic.globals.ios";
|
||||
@import "./action-sheet";
|
||||
|
||||
// iOS Action Sheet
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Text align of the action sheet
|
||||
$action-sheet-ios-text-align: center !default;
|
||||
|
||||
// deprecated
|
||||
$action-sheet-ios-padding: null !default;
|
||||
|
||||
/// @prop - Padding top of the action sheet
|
||||
$action-sheet-ios-padding-top: 0 !default;
|
||||
|
||||
/// @prop - Padding end of the action sheet
|
||||
$action-sheet-ios-padding-end: 10px !default;
|
||||
|
||||
/// @prop - Padding bottom of the action sheet
|
||||
$action-sheet-ios-padding-bottom: $action-sheet-ios-padding-top !default;
|
||||
|
||||
/// @prop - Padding start of the action sheet
|
||||
$action-sheet-ios-padding-start: $action-sheet-ios-padding-end !default;
|
||||
|
||||
/// @prop - Bottom margin of the action sheet button group
|
||||
$action-sheet-ios-group-margin-bottom: 10px !default;
|
||||
|
||||
/// @prop - Background color of the action sheet
|
||||
$action-sheet-ios-background: #f9f9f9 !default;
|
||||
|
||||
/// @prop - Border color of the action sheet
|
||||
$action-sheet-ios-border-color: #d6d6da !default;
|
||||
|
||||
/// @prop - Border radius of the action sheet
|
||||
$action-sheet-ios-border-radius: 13px !default;
|
||||
|
||||
/// @prop - Padding of the action sheet title
|
||||
$action-sheet-ios-title-padding: 1.5rem !default;
|
||||
|
||||
/// @prop - Color of the action sheet title
|
||||
$action-sheet-ios-title-color: #8f8f8f !default;
|
||||
|
||||
/// @prop - Font size of the action sheet title
|
||||
$action-sheet-ios-title-font-size: 1.3rem !default;
|
||||
|
||||
/// @prop - Font weight of the action sheet title
|
||||
$action-sheet-ios-title-font-weight: 400 !default;
|
||||
|
||||
/// @prop - Border radius of the action sheet title
|
||||
$action-sheet-ios-title-border-radius: 0 !default;
|
||||
|
||||
/// @prop - Minimum height of the action sheet button
|
||||
$action-sheet-ios-button-min-height: 5.6rem !default;
|
||||
|
||||
/// @prop - Padding of the action sheet button
|
||||
$action-sheet-ios-button-padding: 18px !default;
|
||||
|
||||
/// @prop - Text color of the action sheet button
|
||||
$action-sheet-ios-button-text-color: #007aff !default;
|
||||
|
||||
/// @prop - Fill color of the action sheet button icon
|
||||
$action-sheet-ios-button-icon-fill-color: $action-sheet-ios-button-text-color !default;
|
||||
|
||||
/// @prop - Font size of the action sheet button icon
|
||||
$action-sheet-ios-button-icon-font-size: 1.4em !default;
|
||||
|
||||
/// @prop - Padding right of the action sheet button icon
|
||||
$action-sheet-ios-button-icon-padding-right: 0.1em !default;
|
||||
|
||||
/// @prop - Height of the action sheet button icon
|
||||
$action-sheet-ios-button-icon-height: .7em !default;
|
||||
|
||||
/// @prop - Margin top of the action sheet button icon
|
||||
$action-sheet-ios-button-icon-margin-top: -10px !default;
|
||||
|
||||
/// @prop - Font size of the action sheet button
|
||||
$action-sheet-ios-button-font-size: 2rem !default;
|
||||
|
||||
/// @prop - Border width of the action sheet button
|
||||
$action-sheet-ios-button-border-width: $hairlines-width !default;
|
||||
|
||||
/// @prop - Border style of the action sheet button
|
||||
$action-sheet-ios-button-border-style: solid !default;
|
||||
|
||||
/// @prop - Border color of the action sheet button
|
||||
$action-sheet-ios-button-border-color: #d1d3d6 !default;
|
||||
|
||||
/// @prop - Background color of the action sheet button
|
||||
$action-sheet-ios-button-background: transparent !default;
|
||||
|
||||
/// @prop - Background color of the activated action sheet button
|
||||
$action-sheet-ios-button-background-activated: #ebebeb !default;
|
||||
|
||||
/// @prop - Destructive text color of the action sheet button
|
||||
$action-sheet-ios-button-destructive-text-color: #f53d3d !default;
|
||||
|
||||
/// @prop - Destructive fill color of the action sheet button icon
|
||||
$action-sheet-ios-button-destructive-icon-fill-color: $action-sheet-ios-button-destructive-text-color !default;
|
||||
|
||||
/// @prop - Background color of the action sheet cancel button
|
||||
$action-sheet-ios-button-cancel-background: #fff !default;
|
||||
|
||||
/// @prop - Font weight of the action sheet cancel button
|
||||
$action-sheet-ios-button-cancel-font-weight: 600 !default;
|
||||
|
||||
|
||||
.action-sheet-ios {
|
||||
@include text-align($action-sheet-ios-text-align);
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-container {
|
||||
@include deprecated-variable(padding, $action-sheet-ios-padding) {
|
||||
@include padding($action-sheet-ios-padding-top, $action-sheet-ios-padding-end, $action-sheet-ios-padding-bottom, $action-sheet-ios-padding-start);
|
||||
}
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-group {
|
||||
@include border-radius($action-sheet-ios-border-radius);
|
||||
@include margin(null, null, $action-sheet-ios-group-margin-bottom - 2, null);
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
background: $action-sheet-ios-background;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-group:last-child {
|
||||
@include margin(null, null, $action-sheet-ios-group-margin-bottom, null);
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-title {
|
||||
@include padding($action-sheet-ios-title-padding);
|
||||
@include text-align($action-sheet-ios-text-align);
|
||||
@include border-radius($action-sheet-ios-title-border-radius);
|
||||
|
||||
border-bottom: $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-border-color;
|
||||
font-size: $action-sheet-ios-title-font-size;
|
||||
font-weight: $action-sheet-ios-title-font-weight;
|
||||
color: $action-sheet-ios-title-color;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-button {
|
||||
@include margin(0);
|
||||
@include padding($action-sheet-ios-button-padding);
|
||||
|
||||
min-height: $action-sheet-ios-button-min-height;
|
||||
|
||||
border-bottom: $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-border-color;
|
||||
font-size: $action-sheet-ios-button-font-size;
|
||||
color: $action-sheet-ios-button-text-color;
|
||||
background: $action-sheet-ios-button-background;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-button .action-sheet-icon {
|
||||
fill: $action-sheet-ios-button-icon-fill-color;
|
||||
font-size: $action-sheet-ios-button-icon-font-size;
|
||||
padding-right: $action-sheet-ios-button-icon-padding-right;
|
||||
height: $action-sheet-ios-button-icon-height;
|
||||
margin-top: $action-sheet-ios-button-icon-margin-top;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-button:last-child {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-button.activated {
|
||||
@include margin(-$action-sheet-ios-button-border-width, null, null, null);
|
||||
|
||||
border-top: $action-sheet-ios-button-border-width $action-sheet-ios-button-border-style $action-sheet-ios-button-background-activated;
|
||||
border-bottom-color: $action-sheet-ios-button-background-activated;
|
||||
background: $action-sheet-ios-button-background-activated;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-selected {
|
||||
font-weight: bold;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-destructive {
|
||||
color: $action-sheet-ios-button-destructive-text-color;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-destructive .action-sheet-icon {
|
||||
fill: $action-sheet-ios-button-destructive-icon-fill-color;
|
||||
}
|
||||
|
||||
.action-sheet-ios .action-sheet-cancel {
|
||||
font-weight: $action-sheet-ios-button-cancel-font-weight;
|
||||
background: $action-sheet-ios-button-cancel-background;
|
||||
}
|
158
packages/core/src/components/action-sheet/action-sheet.md.scss
Normal file
158
packages/core/src/components/action-sheet/action-sheet.md.scss
Normal file
@ -0,0 +1,158 @@
|
||||
@import "../../themes/ionic.globals.md";
|
||||
@import "./action-sheet";
|
||||
|
||||
// Material Design Action Sheet
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Text align of the action sheet
|
||||
$action-sheet-md-text-align: start !default;
|
||||
|
||||
/// @prop - Background color of the action sheet
|
||||
$action-sheet-md-background: #fafafa !default;
|
||||
|
||||
/// @prop - Bottom margin of the action sheet button group
|
||||
$action-sheet-md-group-margin-bottom: 8px !default;
|
||||
|
||||
/// @prop - Color of the action sheet title
|
||||
$action-sheet-md-title-color: #757575 !default;
|
||||
|
||||
/// @prop - Font size of the action sheet title
|
||||
$action-sheet-md-title-font-size: 1.6rem !default;
|
||||
|
||||
// deprecated
|
||||
$action-sheet-md-title-padding: null !default;
|
||||
|
||||
/// @prop - Padding top of the action sheet title
|
||||
$action-sheet-md-title-padding-top: 11px !default;
|
||||
|
||||
/// @prop - Padding end of the action sheet title
|
||||
$action-sheet-md-title-padding-end: 16px !default;
|
||||
|
||||
/// @prop - Padding bottom of the action sheet title
|
||||
$action-sheet-md-title-padding-bottom: 17px !default;
|
||||
|
||||
/// @prop - Padding start of the action sheet title
|
||||
$action-sheet-md-title-padding-start: $action-sheet-md-title-padding-end !default;
|
||||
|
||||
/// @prop - Minimum height of the action sheet button
|
||||
$action-sheet-md-button-min-height: 4.8rem !default;
|
||||
|
||||
/// @prop - Text color of the action sheet button
|
||||
$action-sheet-md-button-text-color: #222 !default;
|
||||
|
||||
/// @prop - Font size of the action sheet button
|
||||
$action-sheet-md-button-font-size: 1.6rem !default;
|
||||
|
||||
// deprecated
|
||||
$action-sheet-md-button-padding: null !default;
|
||||
|
||||
/// @prop - Padding top of the action sheet button
|
||||
$action-sheet-md-button-padding-top: 0 !default;
|
||||
|
||||
/// @prop - Padding end of the action sheet button
|
||||
$action-sheet-md-button-padding-end: 16px !default;
|
||||
|
||||
/// @prop - Padding bottom of the action sheet button
|
||||
$action-sheet-md-button-padding-bottom: $action-sheet-md-button-padding-top !default;
|
||||
|
||||
/// @prop - Padding start of the action sheet button
|
||||
$action-sheet-md-button-padding-start: $action-sheet-md-button-padding-end !default;
|
||||
|
||||
/// @prop - Background color of the action sheet button
|
||||
$action-sheet-md-button-background: transparent !default;
|
||||
|
||||
/// @prop - Background color of the action sheet activated button
|
||||
$action-sheet-md-button-background-activated: #f1f1f1 !default;
|
||||
|
||||
/// @prop - Font size of the icon in the action sheet button
|
||||
$action-sheet-md-icon-font-size: 2.4rem !default;
|
||||
|
||||
/// @prop - Width of the icon in the action sheet button
|
||||
$action-sheet-md-icon-width: 2.3rem !default;
|
||||
|
||||
/// @prop - Text align of the icon in the action sheet button
|
||||
$action-sheet-md-icon-text-align: center !default;
|
||||
|
||||
/// @prop - Vertical align of the icon in the action sheet button
|
||||
$action-sheet-md-icon-vertical-align: middle !default;
|
||||
|
||||
// deprecated
|
||||
$action-sheet-md-icon-margin: null !default;
|
||||
|
||||
/// @prop - Margin top of the icon in the action sheet button
|
||||
$action-sheet-md-icon-margin-top: 0 !default;
|
||||
|
||||
/// @prop - Margin end of the icon in the action sheet button
|
||||
$action-sheet-md-icon-margin-end: 32px !default;
|
||||
|
||||
/// @prop - Margin bottom of the icon in the action sheet button
|
||||
$action-sheet-md-icon-margin-bottom: 0 !default;
|
||||
|
||||
/// @prop - Margin start of the icon in the action sheet button
|
||||
$action-sheet-md-icon-margin-start: 0 !default;
|
||||
|
||||
.action-sheet-md .action-sheet-container {
|
||||
@include padding(.8rem, 0);
|
||||
|
||||
background: $action-sheet-md-background;
|
||||
}
|
||||
|
||||
.action-sheet-md .action-sheet-title,
|
||||
.action-sheet-md .action-sheet-sub-title {
|
||||
@include text-align($action-sheet-md-text-align);
|
||||
|
||||
font-size: $action-sheet-md-title-font-size;
|
||||
color: $action-sheet-md-title-color;
|
||||
|
||||
@include deprecated-variable(padding, $action-sheet-md-title-padding) {
|
||||
@include padding($action-sheet-md-title-padding-top, $action-sheet-md-title-padding-end, $action-sheet-md-title-padding-bottom, $action-sheet-md-title-padding-start);
|
||||
}
|
||||
}
|
||||
|
||||
.action-sheet-md .action-sheet-button {
|
||||
@include text-align($action-sheet-md-text-align);
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
min-height: $action-sheet-md-button-min-height;
|
||||
|
||||
font-size: $action-sheet-md-button-font-size;
|
||||
color: $action-sheet-md-button-text-color;
|
||||
background: $action-sheet-md-button-background;
|
||||
|
||||
|
||||
@include deprecated-variable(padding, $action-sheet-md-button-padding) {
|
||||
@include padding($action-sheet-md-button-padding-top, $action-sheet-md-button-padding-end, $action-sheet-md-button-padding-bottom, $action-sheet-md-button-padding-start);
|
||||
}
|
||||
}
|
||||
|
||||
.action-sheet-md .action-sheet-button.activated {
|
||||
background: $action-sheet-md-button-background-activated;
|
||||
}
|
||||
|
||||
.action-sheet-md .action-sheet-icon {
|
||||
@include padding(0);
|
||||
@include text-align($action-sheet-md-icon-text-align);
|
||||
|
||||
width: $action-sheet-md-icon-width;
|
||||
|
||||
font-size: $action-sheet-md-icon-font-size;
|
||||
vertical-align: $action-sheet-md-icon-vertical-align;
|
||||
|
||||
@include deprecated-variable(margin, $action-sheet-md-icon-margin) {
|
||||
@include margin($action-sheet-md-icon-margin-top, $action-sheet-md-icon-margin-end, $action-sheet-md-icon-margin-bottom, $action-sheet-md-icon-margin-start);
|
||||
}
|
||||
}
|
||||
|
||||
.action-sheet-md .action-sheet-group {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-sheet-md .action-sheet-group .button-inner {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.action-sheet-md .action-sheet-selected {
|
||||
font-weight: bold;
|
||||
}
|
42
packages/core/src/components/action-sheet/action-sheet.scss
Normal file
42
packages/core/src/components/action-sheet/action-sheet.scss
Normal file
@ -0,0 +1,42 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Action Sheet
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Width of the action sheet
|
||||
$action-sheet-width: 100% !default;
|
||||
|
||||
/// @prop - Maximum width of the action sheet
|
||||
$action-sheet-max-width: 500px !default;
|
||||
|
||||
ion-action-sheet {
|
||||
@include position(0, null, null, 0);
|
||||
|
||||
position: absolute;
|
||||
z-index: $z-index-overlay;
|
||||
display: block;
|
||||
|
||||
width: $action-sheet-width;
|
||||
height: $action-sheet-width;
|
||||
}
|
||||
|
||||
.action-sheet-wrapper {
|
||||
@include position(null, 0, 0, 0);
|
||||
@include margin(auto);
|
||||
@include transform(translate3d(0, 100%, 0));
|
||||
|
||||
position: absolute;
|
||||
z-index: $z-index-overlay-wrapper;
|
||||
display: block;
|
||||
|
||||
width: $action-sheet-width;
|
||||
max-width: $action-sheet-max-width;
|
||||
}
|
||||
|
||||
.action-sheet-button {
|
||||
width: $action-sheet-width;
|
||||
}
|
||||
|
||||
ion-action-sheet-controller {
|
||||
display: none;
|
||||
}
|
254
packages/core/src/components/action-sheet/action-sheet.tsx
Normal file
254
packages/core/src/components/action-sheet/action-sheet.tsx
Normal file
@ -0,0 +1,254 @@
|
||||
import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Prop } from '@stencil/core';
|
||||
import { AnimationBuilder, Animation, AnimationController, Config } from '../../index';
|
||||
|
||||
import iOSEnterAnimation from './animations/ios.enter';
|
||||
import iOSLeaveAnimation from './animations/ios.leave';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-action-sheet',
|
||||
styleUrls: {
|
||||
ios: 'action-sheet.ios.scss',
|
||||
md: 'action-sheet.md.scss',
|
||||
wp: 'action-sheet.wp.scss'
|
||||
},
|
||||
host: {
|
||||
theme: 'action-sheet'
|
||||
}
|
||||
})
|
||||
export class ActionSheet {
|
||||
private animation: Animation;
|
||||
|
||||
@Element() private el: HTMLElement;
|
||||
|
||||
@Event() private ionActionSheetDidLoad: EventEmitter;
|
||||
@Event() private ionActionSheetDidPresent: EventEmitter;
|
||||
@Event() private ionActionSheetWillPresent: EventEmitter;
|
||||
@Event() private ionActionSheetWillDismiss: EventEmitter;
|
||||
@Event() private ionActionSheetDidDismiss: EventEmitter;
|
||||
@Event() private ionActionSheetDidUnload: EventEmitter;
|
||||
|
||||
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
|
||||
@Prop({ context: 'config' }) config: Config;
|
||||
|
||||
@Prop() cssClass: string;
|
||||
@Prop() title: string;
|
||||
@Prop() subTitle: string;
|
||||
@Prop() buttons: ActionSheetButton[];
|
||||
@Prop() enableBackdropDismiss: boolean = true;
|
||||
@Prop() showBackdrop: boolean = true;
|
||||
|
||||
@Prop() enterAnimation: AnimationBuilder;
|
||||
@Prop() exitAnimation: AnimationBuilder;
|
||||
@Prop() id: string;
|
||||
|
||||
|
||||
present() {
|
||||
return new Promise<void>(resolve => {
|
||||
this._present(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
private _present(resolve: Function) {
|
||||
if (this.animation) {
|
||||
this.animation.destroy();
|
||||
this.animation = null;
|
||||
}
|
||||
this.ionActionSheetWillPresent.emit({ actionSheet: this });
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
let animationBuilder = this.enterAnimation;
|
||||
|
||||
if (!animationBuilder) {
|
||||
// user did not provide a custom animation fn
|
||||
// decide from the config which animation to use
|
||||
animationBuilder = iOSEnterAnimation;
|
||||
}
|
||||
|
||||
// build the animation and kick it off
|
||||
this.animationCtrl.create(animationBuilder, this.el).then(animation => {
|
||||
this.animation = animation;
|
||||
|
||||
animation.onFinish((a: any) => {
|
||||
a.destroy();
|
||||
this.ionViewDidEnter();
|
||||
resolve();
|
||||
}).play();
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
if (this.animation) {
|
||||
this.animation.destroy();
|
||||
this.animation = null;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.ionActionSheetWillDismiss.emit({ actionSheet: this });
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
let animationBuilder = this.exitAnimation;
|
||||
if (!animationBuilder) {
|
||||
// user did not provide a custom animation fn
|
||||
// decide from the config which animation to use
|
||||
animationBuilder = iOSLeaveAnimation;
|
||||
}
|
||||
|
||||
// build the animation and kick it off
|
||||
this.animationCtrl.create(animationBuilder, this.el).then(animation => {
|
||||
this.animation = animation;
|
||||
|
||||
animation.onFinish((a: any) => {
|
||||
a.destroy();
|
||||
this.ionActionSheetDidDismiss.emit({ actionSheet: this });
|
||||
|
||||
Context.dom.write(() => {
|
||||
this.el.parentNode.removeChild(this.el);
|
||||
});
|
||||
|
||||
resolve();
|
||||
}).play();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected ionViewDidUnload() {
|
||||
this.ionActionSheetDidUnload.emit({ actionSheet: this });
|
||||
}
|
||||
|
||||
@Listen('ionDismiss')
|
||||
protected onDismiss(ev: UIEvent) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
this.dismiss();
|
||||
}
|
||||
|
||||
protected ionViewDidLoad() {
|
||||
this.ionActionSheetDidLoad.emit({ actionSheet: this });
|
||||
}
|
||||
|
||||
protected ionViewDidEnter() {
|
||||
this.ionActionSheetDidPresent.emit({ loading: this });
|
||||
}
|
||||
|
||||
protected backdropClick() {
|
||||
if (this.enableBackdropDismiss) {
|
||||
// const opts: NavOptions = {
|
||||
// minClickBlockDuration: 400
|
||||
// };
|
||||
this.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
protected click(button: ActionSheetButton) {
|
||||
let shouldDismiss = true;
|
||||
if (button.handler) {
|
||||
if (button.handler() === false) {
|
||||
shouldDismiss = false;
|
||||
}
|
||||
}
|
||||
if (shouldDismiss) {
|
||||
this.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
let userCssClass = 'action-sheet-content';
|
||||
if (this.cssClass) {
|
||||
userCssClass += ' ' + this.cssClass;
|
||||
}
|
||||
|
||||
let cancelButton: ActionSheetButton;
|
||||
let buttons = this.buttons
|
||||
.map(b => {
|
||||
if (typeof b === 'string') {
|
||||
b = { text: b };
|
||||
}
|
||||
if (!b.cssClass) {
|
||||
b.cssClass = '';
|
||||
}
|
||||
if (b.role === 'cancel') {
|
||||
cancelButton = b;
|
||||
return null;
|
||||
}
|
||||
return b;
|
||||
})
|
||||
.filter(b => b !== null);
|
||||
return [
|
||||
<ion-backdrop
|
||||
onClick={this.backdropClick.bind(this)}
|
||||
class="action-sheet-backdrop"
|
||||
/>,
|
||||
<div class="action-sheet-wrapper" role="dialog">
|
||||
<div class="action-sheet-container">
|
||||
<div class="action-sheet-group">
|
||||
{this.title
|
||||
? <div class="action-sheet-title">{this.title}</div>
|
||||
: null}
|
||||
{this.subTitle
|
||||
? <div class="action-sheet-sub-title">{this.subTitle}</div>
|
||||
: null}
|
||||
{buttons.map(b =>
|
||||
<button class={this.buttonClass(b)} onClick={() => this.click(b)}>
|
||||
<span class="button-inner">
|
||||
{b.icon
|
||||
? <ion-icon name={b.icon} class="action-sheet-icon" />
|
||||
: null}
|
||||
{b.text}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{cancelButton
|
||||
? <div class="action-sheet-group">
|
||||
<button
|
||||
class={this.buttonClass(cancelButton)}
|
||||
onClick={() => this.click(cancelButton)}
|
||||
>
|
||||
{cancelButton.icon
|
||||
? <ion-icon
|
||||
name={cancelButton.icon}
|
||||
class="action-sheet-icon"
|
||||
/>
|
||||
: null}
|
||||
{cancelButton.text}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
];
|
||||
}
|
||||
|
||||
buttonClass(button: ActionSheetButton): CssClassMap {
|
||||
let buttonClass: string[] = !button.role
|
||||
? ['action-sheet-button']
|
||||
: [`action-sheet-button`, `action-sheet-${button.role}`];
|
||||
return buttonClass.reduce((prevValue: any, cssClass: any) => {
|
||||
prevValue[cssClass] = true;
|
||||
return prevValue;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
export interface ActionSheetOptions {
|
||||
title?: string;
|
||||
subTitle?: string;
|
||||
cssClass?: string;
|
||||
buttons?: (ActionSheetButton | string)[];
|
||||
enableBackdropDismiss?: boolean;
|
||||
}
|
||||
|
||||
export interface ActionSheetButton {
|
||||
text?: string;
|
||||
role?: string;
|
||||
icon?: string;
|
||||
cssClass?: string;
|
||||
handler?: () => boolean | void;
|
||||
}
|
||||
|
||||
export interface ActionSheetEvent {
|
||||
detail: {
|
||||
actionSheet: ActionSheet;
|
||||
};
|
||||
}
|
168
packages/core/src/components/action-sheet/action-sheet.wp.scss
Normal file
168
packages/core/src/components/action-sheet/action-sheet.wp.scss
Normal file
@ -0,0 +1,168 @@
|
||||
@import "../../themes/ionic.globals.wp";
|
||||
@import "./action-sheet";
|
||||
|
||||
// Windows Action Sheet
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Text align of the action sheet
|
||||
$action-sheet-wp-text-align: start !default;
|
||||
|
||||
/// @prop - Background color of the action sheet
|
||||
$action-sheet-wp-background: #fff !default;
|
||||
|
||||
/// @prop - Box shadow color of the action sheet
|
||||
$action-sheet-wp-box-shadow-color: rgba(0, 0, 0, .2) !default;
|
||||
|
||||
/// @prop - Box shadow of the action sheet
|
||||
$action-sheet-wp-box-shadow: 0 -1px 0 $action-sheet-wp-box-shadow-color !default;
|
||||
|
||||
// deprecated
|
||||
$action-sheet-wp-title-padding: null !default;
|
||||
|
||||
/// @prop - Padding top of the action sheet title
|
||||
$action-sheet-wp-title-padding-top: 11px !default;
|
||||
|
||||
/// @prop - Padding end of the action sheet title
|
||||
$action-sheet-wp-title-padding-end: 16px !default;
|
||||
|
||||
/// @prop - Padding bottom of the action sheet title
|
||||
$action-sheet-wp-title-padding-bottom: 17px !default;
|
||||
|
||||
/// @prop - Padding start of the action sheet title
|
||||
$action-sheet-wp-title-padding-start: $action-sheet-wp-title-padding-end !default;
|
||||
|
||||
/// @prop - Font size of the action sheet title
|
||||
$action-sheet-wp-title-font-size: 2rem !default;
|
||||
|
||||
/// @prop - Color of the action sheet title
|
||||
$action-sheet-wp-title-color: #4d4d4d !default;
|
||||
|
||||
/// @prop - Text align of the action sheet title
|
||||
$action-sheet-wp-title-text-align: $action-sheet-wp-text-align !default;
|
||||
|
||||
/// @prop - Height of the action sheet button
|
||||
$action-sheet-wp-button-height: 4.8rem !default;
|
||||
|
||||
/// @prop - Text color of the action sheet button
|
||||
$action-sheet-wp-button-text-color: #4d4d4d !default;
|
||||
|
||||
/// @prop - Font size of the action sheet button
|
||||
$action-sheet-wp-button-font-size: 1.5rem !default;
|
||||
|
||||
// deprecated
|
||||
$action-sheet-wp-button-padding: null !default;
|
||||
|
||||
/// @prop - Padding top of the action sheet button
|
||||
$action-sheet-wp-button-padding-top: 0 !default;
|
||||
|
||||
/// @prop - Padding end of the action sheet button
|
||||
$action-sheet-wp-button-padding-end: 16px !default;
|
||||
|
||||
/// @prop - Padding bottom of the action sheet button
|
||||
$action-sheet-wp-button-padding-bottom: $action-sheet-wp-button-padding-top !default;
|
||||
|
||||
/// @prop - Padding start of the action sheet button
|
||||
$action-sheet-wp-button-padding-start: $action-sheet-wp-button-padding-end !default;
|
||||
|
||||
/// @prop - Text align of the action sheet button
|
||||
$action-sheet-wp-button-text-align: $action-sheet-wp-text-align !default;
|
||||
|
||||
/// @prop - Background color of the action sheet button
|
||||
$action-sheet-wp-button-background: transparent !default;
|
||||
|
||||
/// @prop - Background color of the action sheet activated button
|
||||
$action-sheet-wp-button-background-activated: $list-wp-activated-background-color !default;
|
||||
|
||||
/// @prop - Font size of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-font-size: 2.4rem !default;
|
||||
|
||||
/// @prop - Width of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-width: 2.3rem !default;
|
||||
|
||||
/// @prop - Text align of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-text-align: center !default;
|
||||
|
||||
/// @prop - Vertical align of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-vertical-align: middle !default;
|
||||
|
||||
// deprecated
|
||||
$action-sheet-wp-icon-margin: null !default;
|
||||
|
||||
/// @prop - Margin top of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-margin-top: 0 !default;
|
||||
|
||||
/// @prop - Margin end of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-margin-end: 20px !default;
|
||||
|
||||
/// @prop - Margin bottom of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-margin-bottom: 0 !default;
|
||||
|
||||
/// @prop - Margin start of the icon in the action sheet button
|
||||
$action-sheet-wp-icon-margin-start: 0 !default;
|
||||
|
||||
|
||||
.action-sheet-wp .action-sheet-wrapper {
|
||||
box-shadow: $action-sheet-wp-box-shadow;
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-title {
|
||||
@include text-align($action-sheet-wp-title-text-align);
|
||||
|
||||
font-size: $action-sheet-wp-title-font-size;
|
||||
color: $action-sheet-wp-title-color;
|
||||
|
||||
@include deprecated-variable(padding, $action-sheet-wp-title-padding) {
|
||||
@include padding($action-sheet-wp-title-padding-top, $action-sheet-wp-title-padding-end, $action-sheet-wp-title-padding-bottom, $action-sheet-wp-title-padding-start);
|
||||
}
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-button {
|
||||
@include text-align($action-sheet-wp-button-text-align);
|
||||
|
||||
min-height: $action-sheet-wp-button-height;
|
||||
|
||||
font-size: $action-sheet-wp-button-font-size;
|
||||
color: $action-sheet-wp-button-text-color;
|
||||
background: $action-sheet-wp-button-background;
|
||||
|
||||
@include deprecated-variable(padding, $action-sheet-wp-button-padding) {
|
||||
@include padding($action-sheet-wp-button-padding-top, $action-sheet-wp-button-padding-end, $action-sheet-wp-button-padding-bottom, $action-sheet-wp-button-padding-start);
|
||||
}
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-button.activated {
|
||||
background: $action-sheet-wp-button-background-activated;
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-icon {
|
||||
@include padding(0);
|
||||
@include text-align($action-sheet-wp-icon-text-align);
|
||||
|
||||
width: $action-sheet-wp-icon-width;
|
||||
|
||||
font-size: $action-sheet-wp-icon-font-size;
|
||||
vertical-align: $action-sheet-wp-icon-vertical-align;
|
||||
|
||||
@include deprecated-variable(margin, $action-sheet-wp-icon-margin) {
|
||||
@include margin($action-sheet-wp-icon-margin-top, $action-sheet-wp-icon-margin-end, $action-sheet-wp-icon-margin-bottom, $action-sheet-wp-icon-margin-start);
|
||||
}
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-container {
|
||||
@include padding(.8rem, 0);
|
||||
|
||||
background: $action-sheet-wp-background;
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-group .button-inner {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-sheet-wp .action-sheet-cancel {
|
||||
background: $action-sheet-wp-button-background;
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { Animation } from '../../../index';
|
||||
|
||||
/**
|
||||
* iOS Action Sheet Animation
|
||||
*/
|
||||
export default function(Animation: Animation, baseElm: HTMLElement) {
|
||||
const baseAnimation = new Animation();
|
||||
|
||||
const backdropAnimation = new Animation();
|
||||
backdropAnimation.addElement(baseElm.querySelector('.action-sheet-backdrop'));
|
||||
|
||||
const wrapperAnimation = new Animation();
|
||||
wrapperAnimation.addElement(baseElm.querySelector('.action-sheet-wrapper'));
|
||||
|
||||
backdropAnimation.fromTo('opacity', 0.01, 0.4);
|
||||
|
||||
wrapperAnimation.fromTo('translateY', '100%', '0%');
|
||||
|
||||
return baseAnimation
|
||||
.addElement(baseElm)
|
||||
.easing('cubic-bezier(.36,.66,.04,1)')
|
||||
.duration(400)
|
||||
.add(backdropAnimation)
|
||||
.add(wrapperAnimation);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { Animation } from '../../../index';
|
||||
|
||||
/**
|
||||
* iOS Action Sheet Leave Animation
|
||||
*/
|
||||
export default function(Animation: Animation, baseElm: HTMLElement) {
|
||||
const baseAnimation = new Animation();
|
||||
|
||||
const backdropAnimation = new Animation();
|
||||
backdropAnimation.addElement(baseElm.querySelector('.action-sheet-backdrop'));
|
||||
|
||||
const wrapperAnimation = new Animation();
|
||||
wrapperAnimation.addElement(baseElm.querySelector('.action-sheet-wrapper'));
|
||||
|
||||
backdropAnimation.fromTo('opacity', 0.4, 0);
|
||||
|
||||
wrapperAnimation.fromTo('translateY', '0%', '100%');
|
||||
|
||||
return baseAnimation
|
||||
.addElement(baseElm)
|
||||
.easing('cubic-bezier(.36,.66,.04,1)')
|
||||
.duration(450)
|
||||
.add(backdropAnimation)
|
||||
.add(wrapperAnimation);
|
||||
}
|
125
packages/core/src/components/action-sheet/test/basic.html
Normal file
125
packages/core/src/components/action-sheet/test/basic.html
Normal file
@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Action Sheet Basic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<script src="/dist/ionic.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Action Sheet</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content padding>
|
||||
<ion-action-sheet-controller></ion-action-sheet-controller>
|
||||
|
||||
<ion-button block onclick="presentActionSheet1()">Present Action Sheet 1</ion-button>
|
||||
<ion-button block onclick="presentActionSheet2()">Present Action Sheet 2</ion-button>
|
||||
<ion-button block onclick="presentActionSheet3()">Present Action Sheet 3</ion-button>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
|
||||
function presentActionSheet1() {
|
||||
var mode = Ionic.mode;
|
||||
|
||||
var actionSheetController = document.querySelector('ion-action-sheet-controller');
|
||||
actionSheetController.create({
|
||||
title: "Albums",
|
||||
buttons: [{
|
||||
text: 'Delete',
|
||||
role: 'destructive',
|
||||
icon: 'trash',
|
||||
handler: () => {
|
||||
console.log('Delete clicked');
|
||||
}
|
||||
}, {
|
||||
text: 'Share',
|
||||
icon: 'share',
|
||||
handler: () => {
|
||||
console.log('Share clicked');
|
||||
}
|
||||
}, {
|
||||
text: 'Play (open modal)',
|
||||
icon: 'arrow-dropright-circle',
|
||||
handler: () => {
|
||||
console.log('Play clicked');
|
||||
}
|
||||
}, {
|
||||
text: 'Favorite',
|
||||
icon: mode !== 'ios' ? 'heart' : null,
|
||||
handler: () => {
|
||||
console.log('Favorite clicked');
|
||||
}
|
||||
}, {
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
icon: mode !== 'ios' ? 'close' : null,
|
||||
handler: () => {
|
||||
console.log('Cancel clicked');
|
||||
}
|
||||
}]
|
||||
})
|
||||
.then(actionSheet => {
|
||||
actionSheet.present()
|
||||
});
|
||||
}
|
||||
|
||||
function presentActionSheet2() {
|
||||
var actionSheetController = document.querySelector('ion-action-sheet-controller');
|
||||
actionSheetController.create({
|
||||
buttons: [{
|
||||
text: 'Archive',
|
||||
handler: () => {
|
||||
console.log('Archive clicked');
|
||||
}
|
||||
}, {
|
||||
text: 'Destructive',
|
||||
role: 'destructive',
|
||||
handler: () => {
|
||||
console.log('Destructive clicked');
|
||||
}
|
||||
}, {
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
console.log('Cancel clicked');
|
||||
}
|
||||
}]
|
||||
})
|
||||
.then(actionSheet => {
|
||||
actionSheet.present()
|
||||
});
|
||||
}
|
||||
|
||||
function presentActionSheet3() {
|
||||
var actionSheetController = document.querySelector('ion-action-sheet-controller');
|
||||
actionSheetController.create({
|
||||
buttons: [{
|
||||
text: 'Open Alert',
|
||||
handler: () => {
|
||||
console.log('Open Alert clicked');
|
||||
}
|
||||
}, {
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
handler: () => {
|
||||
console.log('Cancel clicked');
|
||||
}
|
||||
}]
|
||||
})
|
||||
.then(actionSheet => {
|
||||
actionSheet.present()
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -4,7 +4,7 @@ import { Animator } from './animator';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-animation'
|
||||
tag: 'ion-animation-controller'
|
||||
})
|
||||
export class AnimationController {
|
||||
|
26
packages/core/src/components/backdrop/backdrop.scss
Normal file
26
packages/core/src/components/backdrop/backdrop.scss
Normal file
@ -0,0 +1,26 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Backdrop
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Color of the backdrop
|
||||
$backdrop-color: #000 !default;
|
||||
|
||||
ion-backdrop {
|
||||
@include position(0, null, null, 0);
|
||||
|
||||
position: absolute;
|
||||
z-index: $z-index-backdrop;
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-color: $backdrop-color;
|
||||
opacity: .01;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
ion-backdrop.backdrop-no-tappable {
|
||||
cursor: auto;
|
||||
}
|
9
packages/core/src/components/backdrop/backdrop.tsx
Normal file
9
packages/core/src/components/backdrop/backdrop.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Component } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-backdrop',
|
||||
styleUrl: 'backdrop.scss'
|
||||
})
|
||||
export class Backdrop {
|
||||
|
||||
}
|
@ -2,7 +2,7 @@ import { Component } from '@stencil/core';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-gesture-ctrl'
|
||||
tag: 'ion-gesture-controller'
|
||||
})
|
||||
export class GestureController {
|
||||
private id: number = 0;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Component, Listen, Method } from '@stencil/core';
|
||||
import { LoadingEvent, LoadingOptions, Loading } from '../../index';
|
||||
import { Loading, LoadingEvent, LoadingOptions } from '../../index';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-loading-ctrl'
|
||||
tag: 'ion-loading-controller'
|
||||
})
|
||||
export class LoadingController {
|
||||
private ids = 0;
|
||||
|
@ -32,7 +32,7 @@ export class Loading {
|
||||
@State() private showSpinner: boolean = null;
|
||||
@State() private spinner: string;
|
||||
|
||||
@Prop({ connect: 'ion-animation' }) animationCtrl: AnimationController;
|
||||
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
|
||||
@Prop({ context: 'config' }) config: Config;
|
||||
@Prop() cssClass: string;
|
||||
@Prop() content: string;
|
||||
|
@ -3,7 +3,7 @@ import { Modal, ModalEvent, ModalOptions } from '../../index';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-modal-ctrl'
|
||||
tag: 'ion-modal-controller'
|
||||
})
|
||||
export class ModalController {
|
||||
private ids = 0;
|
||||
|
@ -27,7 +27,7 @@ export class Modal {
|
||||
@Event() ionModalDidDismiss: EventEmitter;
|
||||
@Event() ionModalDidUnload: EventEmitter;
|
||||
|
||||
@Prop({ connect: 'ion-animation' }) animationCtrl: AnimationController;
|
||||
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
|
||||
@Prop() mode: string;
|
||||
@Prop() color: string;
|
||||
@Prop() component: string;
|
||||
|
@ -0,0 +1,72 @@
|
||||
import { Component, Listen, Method } from '@stencil/core';
|
||||
import { PopoverEvent, PopoverOptions, Popover } from '../../index';
|
||||
|
||||
|
||||
@Component({
|
||||
tag: 'ion-popover-controller'
|
||||
})
|
||||
export class PopoverController {
|
||||
private ids = 0;
|
||||
private popoverResolves: {[popoverId: string]: Function} = {};
|
||||
private popovers: Popover[] = [];
|
||||
|
||||
@Method()
|
||||
create(opts?: PopoverOptions) {
|
||||
// create ionic's wrapping ion-popover component
|
||||
const popover = document.createElement('ion-popover');
|
||||
const id = this.ids++;
|
||||
|
||||
// give this popover a unique id
|
||||
popover.id = `popover-${id}`;
|
||||
popover.style.zIndex = (10000 + id).toString();
|
||||
|
||||
// convert the passed in popover options into props
|
||||
// that get passed down into the new popover
|
||||
Object.assign(popover, opts);
|
||||
|
||||
// append the popover element to the document body
|
||||
const appRoot = document.querySelector('ion-app') || document.body;
|
||||
appRoot.appendChild(popover as any);
|
||||
|
||||
// store the resolve function to be called later up when the popover loads
|
||||
return new Promise<Popover>(resolve => {
|
||||
this.popoverResolves[popover.id] = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Listen('body:ionPopoverDidLoad')
|
||||
protected viewDidLoad(ev: PopoverEvent) {
|
||||
const popover = ev.detail.popover;
|
||||
const popoverResolve = this.popoverResolves[popover.id];
|
||||
if (popoverResolve) {
|
||||
popoverResolve(popover);
|
||||
delete this.popoverResolves[popover.id];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Listen('body:ionPopoverWillPresent')
|
||||
protected willPresent(ev: PopoverEvent) {
|
||||
this.popovers.push(ev.detail.popover);
|
||||
}
|
||||
|
||||
|
||||
@Listen('body:ionPopoverWillDismiss, body:ionPopoverDidUnload')
|
||||
protected willDismiss(ev: PopoverEvent) {
|
||||
const index = this.popovers.indexOf(ev.detail.popover);
|
||||
if (index > -1) {
|
||||
this.popovers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Listen('body:keyup.escape')
|
||||
protected escapeKeyUp() {
|
||||
const lastPopover = this.popovers[this.popovers.length - 1];
|
||||
if (lastPopover) {
|
||||
lastPopover.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
20
packages/core/src/components/popover/animations/ios.enter.ts
Normal file
20
packages/core/src/components/popover/animations/ios.enter.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Animation } from '../../../index';
|
||||
|
||||
|
||||
/**
|
||||
* iOS Popover Enter Animation
|
||||
*/
|
||||
export default function(Animation: Animation, baseElm: HTMLElement) {
|
||||
const baseAnimation = new Animation();
|
||||
|
||||
const backdropAnimation = new Animation();
|
||||
backdropAnimation.addElement(baseElm.querySelector('.popover-backdrop'));
|
||||
|
||||
backdropAnimation.fromTo('opacity', 0.01, 0.08);
|
||||
|
||||
return baseAnimation
|
||||
.addElement(baseElm)
|
||||
.easing('ease')
|
||||
.duration(100)
|
||||
.add(backdropAnimation);
|
||||
}
|
24
packages/core/src/components/popover/animations/ios.leave.ts
Normal file
24
packages/core/src/components/popover/animations/ios.leave.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Animation } from '../../../index';
|
||||
|
||||
/**
|
||||
* iOS Popover Leave Animation
|
||||
*/
|
||||
export default function(Animation: Animation, baseElm: HTMLElement) {
|
||||
const baseAnimation = new Animation();
|
||||
|
||||
const backdropAnimation = new Animation();
|
||||
backdropAnimation.addElement(baseElm.querySelector('.popover-backdrop'));
|
||||
|
||||
const wrapperAnimation = new Animation();
|
||||
wrapperAnimation.addElement(baseElm.querySelector('.popover-wrapper'));
|
||||
|
||||
wrapperAnimation.fromTo('opacity', 0.99, 0);
|
||||
backdropAnimation.fromTo('opacity', 0.08, 0);
|
||||
|
||||
return baseAnimation
|
||||
.addElement(baseElm)
|
||||
.easing('ease')
|
||||
.duration(500)
|
||||
.add(backdropAnimation)
|
||||
.add(wrapperAnimation);
|
||||
}
|
81
packages/core/src/components/popover/popover.ios.scss
Normal file
81
packages/core/src/components/popover/popover.ios.scss
Normal file
@ -0,0 +1,81 @@
|
||||
@import "../../themes/ionic.globals.ios";
|
||||
@import "./popover";
|
||||
|
||||
// iOS Popover
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Width of the popover content
|
||||
$popover-ios-width: 200px !default;
|
||||
|
||||
/// @prop - Min width of the popover content
|
||||
$popover-ios-min-width: 0 !default;
|
||||
|
||||
/// @prop - Minimum height of the popover content
|
||||
$popover-ios-min-height: 0 !default;
|
||||
|
||||
/// @prop - Maximum height of the popover content
|
||||
$popover-ios-max-height: 90% !default;
|
||||
|
||||
/// @prop - Border radius of the popover content
|
||||
$popover-ios-border-radius: 10px !default;
|
||||
|
||||
/// @prop - Text color of the popover content
|
||||
$popover-ios-text-color: $text-ios-color !default;
|
||||
|
||||
/// @prop - Background of the popover content
|
||||
$popover-ios-background: $background-ios-color !default;
|
||||
|
||||
/// @prop - Background of the popover arrow
|
||||
$popover-ios-arrow-background: $popover-ios-background !default;
|
||||
|
||||
|
||||
.popover-ios .popover-content {
|
||||
@include border-radius($popover-ios-border-radius);
|
||||
|
||||
width: $popover-ios-width;
|
||||
min-width: $popover-ios-min-width;
|
||||
min-height: $popover-ios-min-height;
|
||||
max-height: $popover-ios-max-height;
|
||||
|
||||
color: $popover-ios-text-color;
|
||||
background: $popover-ios-background;
|
||||
}
|
||||
|
||||
|
||||
// Popover Arrow
|
||||
// -----------------------------------------
|
||||
|
||||
.popover-ios .popover-arrow {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 20px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.popover-ios .popover-arrow::after {
|
||||
@include position(3px, null, null, 3px);
|
||||
@include border-radius(3px);
|
||||
|
||||
position: absolute;
|
||||
|
||||
z-index: $z-index-overlay-wrapper;
|
||||
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
background-color: $popover-ios-arrow-background;
|
||||
content: "";
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.popover-ios.popover-bottom .popover-arrow {
|
||||
top: auto;
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
.popover-ios.popover-bottom .popover-arrow::after {
|
||||
top: -6px;
|
||||
}
|
52
packages/core/src/components/popover/popover.md.scss
Normal file
52
packages/core/src/components/popover/popover.md.scss
Normal file
@ -0,0 +1,52 @@
|
||||
@import "../../themes/ionic.globals.md";
|
||||
@import "./popover";
|
||||
|
||||
// Material Design Popover
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Width of the popover content
|
||||
$popover-md-width: 250px !default;
|
||||
|
||||
/// @prop - Min width of the popover content
|
||||
$popover-md-min-width: 0 !default;
|
||||
|
||||
/// @prop - Minimum height of the popover content
|
||||
$popover-md-min-height: 0 !default;
|
||||
|
||||
/// @prop - Maximum height of the popover content
|
||||
$popover-md-max-height: 90% !default;
|
||||
|
||||
/// @prop - Border radius of the popover content
|
||||
$popover-md-border-radius: 2px !default;
|
||||
|
||||
/// @prop - Text color of the popover content
|
||||
$popover-md-text-color: $text-md-color !default;
|
||||
|
||||
/// @prop - Background of the popover content
|
||||
$popover-md-background: $background-md-color !default;
|
||||
|
||||
/// @prop - Box shadow color of the popover content
|
||||
$popover-md-box-shadow-color: rgba(0, 0, 0, .3) !default;
|
||||
|
||||
/// @prop - Box shadow of the popover content
|
||||
$popover-md-box-shadow: 0 3px 12px 2px $popover-md-box-shadow-color !default;
|
||||
|
||||
|
||||
.popover-md .popover-content {
|
||||
@include border-radius($popover-md-border-radius);
|
||||
@include transform-origin(start, top);
|
||||
|
||||
width: $popover-md-width;
|
||||
min-width: $popover-md-min-width;
|
||||
min-height: $popover-md-min-height;
|
||||
max-height: $popover-md-max-height;
|
||||
|
||||
color: $popover-md-text-color;
|
||||
background: $popover-md-background;
|
||||
box-shadow: $popover-md-box-shadow;
|
||||
}
|
||||
|
||||
.popover-md .popover-viewport {
|
||||
// opacity: 0;
|
||||
transition-delay: 100ms;
|
||||
}
|
74
packages/core/src/components/popover/popover.scss
Normal file
74
packages/core/src/components/popover/popover.scss
Normal file
@ -0,0 +1,74 @@
|
||||
@import "../../themes/ionic.globals";
|
||||
|
||||
// Popover
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Color of the backdrop
|
||||
$popover-backdrop-color: #000 !default;
|
||||
|
||||
|
||||
ion-popover {
|
||||
@include position(0, 0, 0, 0);
|
||||
|
||||
position: absolute;
|
||||
|
||||
z-index: $z-index-overlay;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popover-wrapper {
|
||||
z-index: $z-index-overlay-wrapper;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.popover-content {
|
||||
position: absolute;
|
||||
z-index: $z-index-overlay-wrapper;
|
||||
|
||||
display: flex;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.popover-content ion-content,
|
||||
.popover-content ion-scroll {
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.popover-content ion-scroll {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ion-popover-controller {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
// Popover Backdrop
|
||||
// --------------------------------------------------
|
||||
|
||||
.popover-backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: $z-index-backdrop;
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-color: $popover-backdrop-color;
|
||||
opacity: .01;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.popover-backdrop.backdrop-no-tappable {
|
||||
cursor: auto;
|
||||
}
|
332
packages/core/src/components/popover/popover.tsx
Normal file
332
packages/core/src/components/popover/popover.tsx
Normal file
@ -0,0 +1,332 @@
|
||||
import { Component, Element, Event, EventEmitter, Listen, Prop, State } from '@stencil/core';
|
||||
import { AnimationBuilder, Animation, AnimationController, Config } from '../../index';
|
||||
|
||||
import { createThemedClasses } from '../../utils/theme';
|
||||
|
||||
import iOSEnterAnimation from './animations/ios.enter';
|
||||
import iOSLeaveAnimation from './animations/ios.leave';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-popover',
|
||||
styleUrls: {
|
||||
ios: 'popover.ios.scss',
|
||||
md: 'popover.md.scss',
|
||||
wp: 'popover.wp.scss'
|
||||
},
|
||||
host: {
|
||||
theme: 'popover'
|
||||
}
|
||||
})
|
||||
export class Popover {
|
||||
private animation: Animation;
|
||||
|
||||
@State() positioned: boolean = false;
|
||||
|
||||
@Element() private el: HTMLElement;
|
||||
|
||||
@Event() private ionPopoverDidLoad: EventEmitter;
|
||||
@Event() private ionPopoverDidPresent: EventEmitter;
|
||||
@Event() private ionPopoverWillPresent: EventEmitter;
|
||||
@Event() private ionPopoverWillDismiss: EventEmitter;
|
||||
@Event() private ionPopoverDidDismiss: EventEmitter;
|
||||
@Event() private ionPopoverDidUnload: EventEmitter;
|
||||
|
||||
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController;
|
||||
@Prop({ context: 'config' }) config: Config;
|
||||
|
||||
@Prop() mode: string;
|
||||
@Prop() color: string;
|
||||
@Prop() component: string;
|
||||
@Prop() componentProps: any = {};
|
||||
@Prop() cssClass: string;
|
||||
@Prop() enableBackdropDismiss: boolean = true;
|
||||
@Prop() enterAnimation: AnimationBuilder;
|
||||
@Prop() exitAnimation: AnimationBuilder;
|
||||
@Prop() ev: Event;
|
||||
@Prop() id: string;
|
||||
@Prop() showBackdrop: boolean = true;
|
||||
|
||||
|
||||
present() {
|
||||
return new Promise<void>(resolve => {
|
||||
this._present(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private positionPopover() {
|
||||
const props = POPOVER_POSITION_PROPERTIES[this.mode];
|
||||
console.debug('Position popover', this.el, this.ev, props);
|
||||
|
||||
// Declare the popover elements
|
||||
let contentEl = this.el.querySelector('.popover-content') as HTMLElement;
|
||||
let arrowEl = this.el.querySelector('.popover-arrow') as HTMLElement;
|
||||
|
||||
// If no event was passed, hide the arrow
|
||||
if (!this.ev) {
|
||||
arrowEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// Set the default transform origin direction
|
||||
let origin = {
|
||||
y: 'top',
|
||||
x: 'left'
|
||||
}
|
||||
|
||||
// Popover content width and height
|
||||
const popover = {
|
||||
width: contentEl.getBoundingClientRect().width,
|
||||
height: contentEl.getBoundingClientRect().height
|
||||
}
|
||||
|
||||
// Window body width and height
|
||||
// TODO need to check if portrait/landscape?
|
||||
const body = {
|
||||
width: window.screen.width,
|
||||
height: window.screen.height
|
||||
}
|
||||
|
||||
// If ev was passed, use that for target element
|
||||
let targetDim = this.ev && this.ev.target && (this.ev.target as HTMLElement).getBoundingClientRect();
|
||||
|
||||
// The target is the object that dispatched the event that was passed
|
||||
let target = {
|
||||
top: (targetDim && 'top' in targetDim) ? targetDim.top : (body.height / 2) - (popover.height / 2),
|
||||
left: (targetDim && 'left' in targetDim) ? targetDim.left : (body.width / 2) - (popover.width / 2),
|
||||
width: targetDim && targetDim.width || 0,
|
||||
height: targetDim && targetDim.height || 0
|
||||
};
|
||||
|
||||
// If the popover should be centered to the target
|
||||
if (props.centerTarget) {
|
||||
target.left = (targetDim && 'left' in targetDim) ? targetDim.left : (body.width / 2);
|
||||
}
|
||||
|
||||
// The arrow that shows above the popover on iOS
|
||||
let arrowDim = arrowEl.getBoundingClientRect();
|
||||
|
||||
const arrow = {
|
||||
width: arrowDim.width,
|
||||
height: arrowDim.height
|
||||
}
|
||||
|
||||
let arrowCSS = {
|
||||
top: target.top + target.height,
|
||||
left: target.left + (target.width / 2) - (arrow.width / 2)
|
||||
};
|
||||
|
||||
let popoverCSS = {
|
||||
top: target.top + target.height + (arrow.height - 1),
|
||||
left: target.left
|
||||
};
|
||||
|
||||
// If the popover should be centered to the target
|
||||
if (props.centerTarget) {
|
||||
popoverCSS.left = target.left + (target.width / 2) - (popover.width / 2)
|
||||
}
|
||||
|
||||
// If the popover left is less than the padding it is off screen
|
||||
// to the left so adjust it, else if the width of the popover
|
||||
// exceeds the body width it is off screen to the right so adjust
|
||||
if (popoverCSS.left < props.padding) {
|
||||
popoverCSS.left = props.padding;
|
||||
} else if (popover.width + props.padding + popoverCSS.left > body.width) {
|
||||
popoverCSS.left = body.width - popover.width - props.padding;
|
||||
origin.x = 'right';
|
||||
}
|
||||
|
||||
// If the popover when popped down stretches past bottom of screen,
|
||||
// make it pop up if there's room above
|
||||
if (this.showFromBottom(target, popover, body)) {
|
||||
this.el.className = this.el.className + ' popover-bottom';
|
||||
origin.y = 'bottom';
|
||||
|
||||
popoverCSS.top = target.top - popover.height;
|
||||
|
||||
if (props.showArrow) {
|
||||
arrowCSS.top = target.top - (arrow.height + 1);
|
||||
popoverCSS.top = target.top - popover.height - (arrow.height - 1);
|
||||
}
|
||||
|
||||
// If the popover exceeds the viewport then cut the bottom off
|
||||
} else if (this.exceedsViewport(target, popover, body)) {
|
||||
contentEl.style.bottom = props.padding + props.unit;
|
||||
}
|
||||
|
||||
arrowEl.style.top = arrowCSS.top + 'px';
|
||||
arrowEl.style.left = arrowCSS.left + 'px';
|
||||
|
||||
contentEl.style.top = popoverCSS.top + 'px';
|
||||
contentEl.style.left = popoverCSS.left + 'px';
|
||||
|
||||
contentEl.style.transformOrigin = origin.y + ' ' + origin.x;
|
||||
|
||||
// Since the transition starts before styling is done we
|
||||
// want to wait for the styles to apply before showing the wrapper
|
||||
this.positioned = true;
|
||||
}
|
||||
|
||||
private showFromBottom(target: any, popover: any, body: any): boolean {
|
||||
return target.top + target.height + popover.height > body.height && target.top - popover.height > 0;
|
||||
}
|
||||
|
||||
private exceedsViewport(target: any, popover: any, body: any): boolean {
|
||||
return target.top + target.height + popover.height > body.height;
|
||||
}
|
||||
|
||||
private _present(resolve: Function) {
|
||||
if (this.animation) {
|
||||
this.animation.destroy();
|
||||
this.animation = null;
|
||||
}
|
||||
this.ionPopoverWillPresent.emit({ popover: this });
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
let animationBuilder = this.enterAnimation;
|
||||
|
||||
if (!animationBuilder) {
|
||||
// user did not provide a custom animation fn
|
||||
// decide from the config which animation to use
|
||||
animationBuilder = iOSEnterAnimation;
|
||||
}
|
||||
|
||||
// build the animation and kick it off
|
||||
this.animationCtrl.create(animationBuilder, this.el).then(animation => {
|
||||
this.animation = animation;
|
||||
|
||||
animation.onFinish((a: any) => {
|
||||
a.destroy();
|
||||
this.ionViewDidEnter();
|
||||
this.positionPopover();
|
||||
resolve();
|
||||
}).play();
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
if (this.animation) {
|
||||
this.animation.destroy();
|
||||
this.animation = null;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.ionPopoverWillDismiss.emit({ popover: this });
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
let animationBuilder = this.exitAnimation;
|
||||
if (!animationBuilder) {
|
||||
// user did not provide a custom animation fn
|
||||
// decide from the config which animation to use
|
||||
animationBuilder = iOSLeaveAnimation;
|
||||
}
|
||||
|
||||
// build the animation and kick it off
|
||||
this.animationCtrl.create(animationBuilder, this.el).then(animation => {
|
||||
this.animation = animation;
|
||||
|
||||
animation.onFinish((a: any) => {
|
||||
a.destroy();
|
||||
this.ionPopoverDidDismiss.emit({ popover: this });
|
||||
|
||||
Context.dom.write(() => {
|
||||
this.el.parentNode.removeChild(this.el);
|
||||
});
|
||||
|
||||
resolve();
|
||||
}).play();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected ionViewDidUnload() {
|
||||
this.ionPopoverDidUnload.emit({ popover: this });
|
||||
}
|
||||
|
||||
@Listen('ionDismiss')
|
||||
protected onDismiss(ev: UIEvent) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
this.dismiss();
|
||||
}
|
||||
|
||||
protected ionViewDidLoad() {
|
||||
this.ionPopoverDidLoad.emit({ popover: this });
|
||||
}
|
||||
|
||||
protected ionViewDidEnter() {
|
||||
this.ionPopoverDidPresent.emit({ popover: this });
|
||||
}
|
||||
|
||||
protected backdropClick() {
|
||||
if (this.enableBackdropDismiss) {
|
||||
// const opts: NavOptions = {
|
||||
// minClickBlockDuration: 400
|
||||
// };
|
||||
this.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const ThisComponent = this.component;
|
||||
|
||||
const wrapperClasses = createThemedClasses(this.mode, this.color, 'popover-wrapper');
|
||||
|
||||
const wrapperStyle = this.positioned ? { 'opacity' : '1' } : {};
|
||||
|
||||
return [
|
||||
<ion-backdrop
|
||||
onClick={this.backdropClick.bind(this)}
|
||||
class="popover-backdrop"
|
||||
/>,
|
||||
<div class={wrapperClasses} style={wrapperStyle}>
|
||||
<div class="popover-arrow"/>
|
||||
<div class="popover-content">
|
||||
<div class="popover-viewport">
|
||||
<ThisComponent
|
||||
props={this.componentProps}
|
||||
class={this.cssClass}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export interface PopoverOptions {
|
||||
component: string;
|
||||
componentProps?: any;
|
||||
showBackdrop?: boolean;
|
||||
enableBackdropDismiss?: boolean;
|
||||
enterAnimation?: AnimationBuilder;
|
||||
exitAnimation?: AnimationBuilder;
|
||||
cssClass?: string;
|
||||
ev: Event;
|
||||
}
|
||||
|
||||
export interface PopoverEvent {
|
||||
detail: {
|
||||
popover: Popover;
|
||||
};
|
||||
}
|
||||
|
||||
export const POPOVER_POSITION_PROPERTIES: any = {
|
||||
ios: {
|
||||
padding: 2,
|
||||
unit: '%',
|
||||
showArrow: true,
|
||||
centerTarget: true
|
||||
},
|
||||
md: {
|
||||
padding: 12,
|
||||
unit: 'px',
|
||||
showArrow: false,
|
||||
centerTarget: false
|
||||
},
|
||||
wp: {
|
||||
padding: 12,
|
||||
unit: 'px',
|
||||
showArrow: false,
|
||||
centerTarget: false
|
||||
}
|
||||
}
|
49
packages/core/src/components/popover/popover.wp.scss
Normal file
49
packages/core/src/components/popover/popover.wp.scss
Normal file
@ -0,0 +1,49 @@
|
||||
@import "../../themes/ionic.globals.wp";
|
||||
@import "./popover";
|
||||
|
||||
// Windows Popover
|
||||
// --------------------------------------------------
|
||||
|
||||
/// @prop - Width of the popover content
|
||||
$popover-wp-width: 200px !default;
|
||||
|
||||
/// @prop - Min width of the popover content
|
||||
$popover-wp-min-width: 0 !default;
|
||||
|
||||
/// @prop - Minimum height of the popover content
|
||||
$popover-wp-min-height: 0 !default;
|
||||
|
||||
/// @prop - Maximum height of the popover content
|
||||
$popover-wp-max-height: 90% !default;
|
||||
|
||||
/// @prop - Border of the popover content
|
||||
$popover-wp-border: 2px solid #ccc !default;
|
||||
|
||||
/// @prop - Border radius of the popover content
|
||||
$popover-wp-border-radius: 0 !default;
|
||||
|
||||
/// @prop - Text color of the popover content
|
||||
$popover-wp-text-color: $text-wp-color !default;
|
||||
|
||||
/// @prop - Background of the popover content
|
||||
$popover-wp-background: $background-wp-color !default;
|
||||
|
||||
|
||||
.popover-wp .popover-content {
|
||||
@include border-radius($popover-wp-border-radius);
|
||||
@include transform-origin(start, top);
|
||||
|
||||
width: $popover-wp-width;
|
||||
min-width: $popover-wp-min-width;
|
||||
min-height: $popover-wp-min-height;
|
||||
max-height: $popover-wp-max-height;
|
||||
|
||||
border: $popover-wp-border;
|
||||
color: $popover-wp-text-color;
|
||||
background: $popover-wp-background;
|
||||
}
|
||||
|
||||
.popover-wp .popover-viewport {
|
||||
// opacity: 0;
|
||||
transition-delay: 100ms;
|
||||
}
|
102
packages/core/src/components/popover/test/basic.html
Normal file
102
packages/core/src/components/popover/test/basic.html
Normal file
@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ionic Popover</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<script src="/dist/ionic.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-buttons slot="start">
|
||||
<ion-button onclick="presentPopover('profile-page', event)">
|
||||
<ion-icon slot="icon-only" name="person"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Popover</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content padding>
|
||||
<ion-button block onclick="presentPopover('profile-page', event)">Show Popover</ion-button>
|
||||
<ion-button block onclick="presentPopover('list-page', event)">Show Long List Popover</ion-button>
|
||||
<ion-button block onclick="presentPopover('profile-page')">No Event Popover</ion-button>
|
||||
|
||||
<ion-popover-controller></ion-popover-controller>
|
||||
</ion-content>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar color="primary">
|
||||
<ion-buttons slot="end">
|
||||
<ion-button onclick="presentPopover('profile-page', event)">
|
||||
<ion-icon slot="icon-only" name="person"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Popover</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
</ion-app>
|
||||
<script>
|
||||
function presentPopover(componentName, event) {
|
||||
var popoverController = document.querySelector('ion-popover-controller');
|
||||
popoverController.create({
|
||||
component: componentName,
|
||||
ev: event
|
||||
}).then(popover => {
|
||||
popover.present();
|
||||
});
|
||||
}
|
||||
|
||||
class ProfilePage extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = `
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-list-header>Ionic</ion-list-header>
|
||||
<ion-item><ion-label>Item 0</ion-label></ion-item>
|
||||
<ion-item><ion-label>Item 1</ion-label></ion-item>
|
||||
<ion-item><ion-label>Item 2</ion-label></ion-item>
|
||||
<ion-item><ion-label>Item 3</ion-label></ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('profile-page', ProfilePage);
|
||||
|
||||
class ListPage extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
let items = '';
|
||||
for (var i = 0; i < 30; i++) {
|
||||
items += '<ion-item><ion-label>Item ' + i + '</ion-label></ion-item>';
|
||||
}
|
||||
|
||||
this.innerHTML = `
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-list-header>Ionic</ion-list-header>
|
||||
` + items + `
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('list-page', ListPage);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
21
packages/core/src/index.d.ts
vendored
21
packages/core/src/index.d.ts
vendored
@ -1,5 +1,8 @@
|
||||
import { AnimationController } from './components/animation/animation';
|
||||
import { Animation, AnimationBuilder } from './components/animation/animation-interface';
|
||||
import { AnimationController } from './components/animation-controller/animation-controller';
|
||||
import { Animation, AnimationBuilder } from './components/animation-controller/animation-interface';
|
||||
import { ActionSheet, ActionSheetButton, ActionSheetEvent, ActionSheetOptions } from './components/action-sheet/action-sheet';
|
||||
import { ActionSheetController } from './components/action-sheet-controller/action-sheet-controller';
|
||||
import { Backdrop } from './components/backdrop/backdrop'
|
||||
import { Loading, LoadingEvent, LoadingOptions } from './components/loading/loading';
|
||||
import { LoadingController } from './components/loading-controller/loading-controller';
|
||||
import { GestureDetail, GestureCallback } from './components/gesture/gesture';
|
||||
@ -8,6 +11,10 @@ import { MenuType } from './components/menu/menu-types';
|
||||
import { MenuController } from './components/menu/menu-controller';
|
||||
import { Modal, ModalOptions, ModalEvent } from './components/modal/modal';
|
||||
import { ModalController } from './components/modal-controller/modal-controller';
|
||||
|
||||
import { Popover, PopoverEvent, PopoverOptions } from './components/popover/popover'
|
||||
import { PopoverController } from './components/popover-controller/popover-controller'
|
||||
|
||||
import { Scroll, ScrollCallback, ScrollDetail } from './components/scroll/scroll';
|
||||
import { Segment } from './components/segment/segment';
|
||||
import { SegmentButton, SegmentButtonEvent } from './components/segment-button/segment-button';
|
||||
@ -41,9 +48,15 @@ export interface BooleanInputComponent extends BaseInputComponent {
|
||||
|
||||
|
||||
export {
|
||||
ActionSheet,
|
||||
ActionSheetButton,
|
||||
ActionSheetEvent,
|
||||
ActionSheetOptions,
|
||||
ActionSheetController,
|
||||
Animation,
|
||||
AnimationBuilder,
|
||||
AnimationController,
|
||||
Backdrop,
|
||||
GestureCallback,
|
||||
GestureDetail,
|
||||
Loading,
|
||||
@ -57,6 +70,10 @@ export {
|
||||
ModalController,
|
||||
ModalOptions,
|
||||
ModalEvent,
|
||||
Popover,
|
||||
PopoverController,
|
||||
PopoverEvent,
|
||||
PopoverOptions,
|
||||
Scroll,
|
||||
ScrollCallback,
|
||||
ScrollDetail,
|
||||
|
@ -12,7 +12,7 @@
|
||||
For example:
|
||||
|
||||
$colors: (
|
||||
primary: #327eff,
|
||||
primary: #488aff,
|
||||
secondary: (base: #32db64, contrast: #000),
|
||||
);";
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
For example:
|
||||
|
||||
$colors: (
|
||||
#{$color-name}: #327eff
|
||||
#{$color-name}: #488aff
|
||||
);";
|
||||
}
|
||||
|
||||
@ -253,7 +253,7 @@
|
||||
//
|
||||
// Example values
|
||||
// --------------------------------------------------
|
||||
// primary | #327eff | #444
|
||||
// primary | #488aff | #444
|
||||
// map key | map value | hex color not in map
|
||||
// --------------------------------------------------
|
||||
//
|
||||
@ -278,7 +278,7 @@
|
||||
}
|
||||
|
||||
// If the value is a map then get the contrast
|
||||
// from the map (e.g. (base: #327eff, contrast: blue))
|
||||
// from the map (e.g. (base: #488aff, contrast: blue))
|
||||
@if (type-of($color-value) == map) {
|
||||
// If the map has the contrast key then use that
|
||||
// otherwise it is a map with just a base so get
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
$colors: (
|
||||
|
||||
primary: #327eff,
|
||||
primary: #488aff,
|
||||
secondary: #32db64,
|
||||
danger: #f53d3d,
|
||||
light: #f4f4f4,
|
||||
|
@ -4,9 +4,11 @@ exports.config = {
|
||||
publicPath: '/dist',
|
||||
generateCollection: true,
|
||||
bundles: [
|
||||
{ components: ['ion-animation'] },
|
||||
{ components: ['ion-animation-controller'] },
|
||||
{ components: ['ion-app', 'ion-content', 'ion-fixed', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-page', 'ion-title', 'ion-toolbar'] },
|
||||
{ components: ['ion-action-sheet', 'ion-action-sheet-controller'] },
|
||||
{ components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] },
|
||||
{ components: ['ion-backdrop'] },
|
||||
{ components: ['ion-button', 'ion-buttons', 'ion-icon'] },
|
||||
{ components: ['ion-card', 'ion-card-content', 'ion-card-header', 'ion-card-title'] },
|
||||
{ components: ['ion-chip', 'ion-chip-button'] },
|
||||
@ -15,9 +17,10 @@ exports.config = {
|
||||
{ components: ['ion-grid', 'ion-row', 'ion-col'] },
|
||||
{ components: ['ion-item', 'ion-item-divider', 'ion-item-sliding', 'ion-item-options', 'ion-item-option', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] },
|
||||
{ components: ['ion-input', 'ion-textarea'] },
|
||||
{ components: ['ion-loading', 'ion-loading-ctrl'] },
|
||||
{ components: ['ion-loading', 'ion-loading-controller'] },
|
||||
{ components: ['ion-menu'], priority: 'low' },
|
||||
{ components: ['ion-modal', 'ion-modal-ctrl'] },
|
||||
{ components: ['ion-modal', 'ion-modal-controller'] },
|
||||
{ components: ['ion-popover', 'ion-popover-controller'] },
|
||||
{ components: ['ion-searchbar'] },
|
||||
{ components: ['ion-segment', 'ion-segment-button'] },
|
||||
{ components: ['ion-slides', 'ion-slide'] },
|
||||
|
@ -12,7 +12,7 @@
|
||||
For example:
|
||||
|
||||
$colors: (
|
||||
primary: #327eff,
|
||||
primary: #488aff,
|
||||
secondary: (base: #32db64, contrast: #000),
|
||||
);";
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
For example:
|
||||
|
||||
$colors: (
|
||||
#{$color-name}: #327eff
|
||||
#{$color-name}: #488aff
|
||||
);";
|
||||
}
|
||||
|
||||
@ -253,7 +253,7 @@
|
||||
//
|
||||
// Example values
|
||||
// --------------------------------------------------
|
||||
// primary | #327eff | #444
|
||||
// primary | #488aff | #444
|
||||
// map key | map value | hex color not in map
|
||||
// --------------------------------------------------
|
||||
//
|
||||
@ -278,7 +278,7 @@
|
||||
}
|
||||
|
||||
// If the value is a map then get the contrast
|
||||
// from the map (e.g. (base: #327eff, contrast: blue))
|
||||
// from the map (e.g. (base: #488aff, contrast: blue))
|
||||
@if (type-of($color-value) == map) {
|
||||
// If the map has the contrast key then use that
|
||||
// otherwise it is a map with just a base so get
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
$colors: (
|
||||
|
||||
primary: #327eff,
|
||||
primary: #488aff,
|
||||
secondary: #32db64,
|
||||
danger: #f53d3d,
|
||||
light: #f4f4f4,
|
||||
|
Reference in New Issue
Block a user