diff --git a/packages/core/src/components/picker-controller/picker-controller.tsx b/packages/core/src/components/picker-controller/picker-controller.tsx new file mode 100644 index 0000000000..19022073e3 --- /dev/null +++ b/packages/core/src/components/picker-controller/picker-controller.tsx @@ -0,0 +1,73 @@ +import { Component, Listen, Method } from '@stencil/core'; +import { Picker, PickerEvent, PickerOptions } from '../../index'; + + +@Component({ + tag: 'ion-picker-controller' +}) +export class PickerController { + private ids = 0; + private pickerResolves: {[pickerId: string]: Function} = {}; + private pickers: Picker[] = []; + + @Method() + create(opts?: PickerOptions): Promise { + // create ionic's wrapping ion-picker component + const picker = document.createElement('ion-picker'); + + const id = this.ids++; + + // give this picker a unique id + picker.id = `picker-${id}`; + picker.style.zIndex = (20000 + id).toString(); + + // convert the passed in picker options into props + // that get passed down into the new picker + Object.assign(picker, opts); + + // append the picker element to the document body + const appRoot = document.querySelector('ion-app') || document.body; + appRoot.appendChild(picker as any); + + // store the resolve function to be called later up when the picker loads + return new Promise(resolve => { + this.pickerResolves[picker.id] = resolve; + }); + } + + + @Listen('body:ionPickerDidLoad') + protected viewDidLoad(ev: PickerEvent) { + const picker = ev.detail.picker; + const pickerResolve = this.pickerResolves[picker.id]; + if (pickerResolve) { + pickerResolve(picker); + delete this.pickerResolves[picker.id]; + } + } + + + @Listen('body:ionPickerWillPresent') + protected willPresent(ev: PickerEvent) { + this.pickers.push(ev.detail.picker); + } + + + @Listen('body:ionPickerWillDismiss, body:ionPickerDidUnload') + protected willDismiss(ev: PickerEvent) { + const index = this.pickers.indexOf(ev.detail.picker); + if (index > -1) { + this.pickers.splice(index, 1); + } + } + + + @Listen('body:keyup.escape') + protected escapeKeyUp() { + const lastPicker = this.pickers[this.pickers.length - 1]; + if (lastPicker) { + lastPicker.dismiss(); + } + } + +} diff --git a/packages/core/src/components/picker/animations/ios.enter.ts b/packages/core/src/components/picker/animations/ios.enter.ts new file mode 100644 index 0000000000..e989b8e102 --- /dev/null +++ b/packages/core/src/components/picker/animations/ios.enter.ts @@ -0,0 +1,26 @@ +import { Animation } from '../../../index'; + + +/** + * iOS Loading Enter Animation + */ +export default function iOSEnterAnimation(Animation: Animation, baseElm: HTMLElement): Animation { + const baseAnimation = new Animation(); + + const backdropAnimation = new Animation(); + backdropAnimation.addElement(baseElm.querySelector('.picker-backdrop')); + + const wrapperAnimation = new Animation(); + wrapperAnimation.addElement(baseElm.querySelector('.picker-wrapper')); + + backdropAnimation.fromTo('opacity', 0.01, 0.26); + + wrapperAnimation.fromTo('translateY', '100%', '0%'); + + return baseAnimation + .addElement(baseElm) + .easing('ease-in-out') + .duration(200) + .add(backdropAnimation) + .add(wrapperAnimation); +} diff --git a/packages/core/src/components/picker/animations/ios.leave.ts b/packages/core/src/components/picker/animations/ios.leave.ts new file mode 100644 index 0000000000..6d2e092ba1 --- /dev/null +++ b/packages/core/src/components/picker/animations/ios.leave.ts @@ -0,0 +1,26 @@ +import { Animation } from '../../../index'; + + +/** + * iOS Loading Leave Animation + */ +export default function iOSLeaveAnimation(Animation: Animation, baseElm: HTMLElement): Animation { + const baseAnimation = new Animation(); + + const backdropAnimation = new Animation(); + backdropAnimation.addElement(baseElm.querySelector('.picker-backdrop')); + + const wrapperAnimation = new Animation(); + wrapperAnimation.addElement(baseElm.querySelector('.picker-wrapper')); + + backdropAnimation.fromTo('opacity', 0.26, 0.01); + + wrapperAnimation.fromTo('translateY', '0%', '100%'); + + return baseAnimation + .addElement(baseElm) + .easing('ease-in-out') + .duration(200) + .add(backdropAnimation) + .add(wrapperAnimation); +} diff --git a/packages/core/src/components/picker/picker-column.tsx b/packages/core/src/components/picker/picker-column.tsx new file mode 100644 index 0000000000..4cc29481ad --- /dev/null +++ b/packages/core/src/components/picker/picker-column.tsx @@ -0,0 +1,477 @@ +import { Component, Element, Prop } from '@stencil/core'; + +import { PickerColumn } from '../../index'; + +@Component({ + tag: 'ion-picker-column', + host: { + theme: 'picker-col' + } +}) +export class PickerColumnCmp { + velocity: number; + optHeight: number; + + @Element() el: HTMLElement; + + @Prop() col: PickerColumn; + + optClick(ev: Event, index: number) { + if (!this.velocity) { + ev.preventDefault(); + ev.stopPropagation(); + + this.setSelected(index, 150); + } + } + + setSelected(selectedIndex: number, duration: number) { + // if there is a selected index, then figure out it's y position + // if there isn't a selected index, then just use the top y position + let y = (selectedIndex > -1) ? ((selectedIndex * this.optHeight) * -1) : 0; + + // this._plt.cancelRaf(this.rafId); + this.velocity = 0; + + // so what y position we're at + this.update(y, duration, true, true); + } + + update(y: number, duration: number, saveY: boolean, emitChange: boolean) { + // ensure we've got a good round number :) + y = Math.round(y); + + // let i: number; + // let button: any; + // let opt: any; + // let optOffset: number; + // let visible: boolean; + // let translateX: number; + // let translateY: number; + // let translateZ: number; + // let rotateX: number; + // let transform: string; + // let selected: boolean; + + const parent = this.el.querySelector('.picker-opts'); + // const children = parent.children; + // const length = children.length; + // const selectedIndex = this.col.selectedIndex = Math.min(Math.max(Math.round(-y / this.optHeight), 0), length - 1); + + // const durationStr = (duration === 0) ? null : duration + 'ms'; + // const scaleStr = `scale(${this.scaleFactor})`; + + // for (i = 0; i < length; i++) { + // button = children[i]; + // opt = this.col.options[i]; + // optOffset = (i * this.optHeight) + y; + // visible = true; + // transform = ''; + + // if (this.rotateFactor !== 0) { + // rotateX = optOffset * this.rotateFactor; + // if (Math.abs(rotateX) > 90) { + // visible = false; + // } else { + // translateX = 0; + // translateY = 0; + // translateZ = 90; + // transform = `rotateX(${rotateX}deg) `; + // } + // } else { + // translateX = 0; + // translateZ = 0; + // translateY = optOffset; + // if (Math.abs(translateY) > 170) { + // visible = false; + // } + // } + + // selected = selectedIndex === i; + // if (visible) { + // transform += `translate3d(0px,${translateY}px,${translateZ}px) `; + // if (this.scaleFactor !== 1 && !selected) { + // transform += scaleStr; + // } + // } else { + // transform = 'translate3d(-9999px,0px,0px)'; + // } + // // Update transition duration + // if (duration !== opt._dur) { + // opt._dur = duration; + // button.style[this._plt.Css.transitionDuration] = durationStr; + // } + // // Update transform + // if (transform !== opt._trans) { + // opt._trans = transform; + // button.style[this._plt.Css.transform] = transform; + // } + // // Update selected item + // if (selected !== opt._selected) { + // opt._selected = selected; + // if (selected) { + // button.classList.add(PICKER_OPT_SELECTED); + // } else { + // button.classList.remove(PICKER_OPT_SELECTED); + // } + // } + // } + // this.col.prevSelected = selectedIndex; + + // if (saveY) { + // this.y = y; + // } + + // if (emitChange) { + // if (this.lastIndex === undefined) { + // // have not set a last index yet + // this.lastIndex = this.col.selectedIndex; + + // } else if (this.lastIndex !== this.col.selectedIndex) { + // // new selected index has changed from the last index + // // update the lastIndex and emit that it has changed + // this.lastIndex = this.col.selectedIndex; + // var ionChange = this.ionChange; + // if (ionChange.observers.length > 0) { + // this._zone.run(ionChange.emit.bind(ionChange, this.col.options[this.col.selectedIndex])); + // } + // } + // } + } + + render() { + console.log('picker column, render col', this.col); + let col = this.col; + + const pickerPrefix: any[] = []; + + if (col.prefix) { + pickerPrefix.push( +
+ {col.prefix} +
+ ); + } + + // if (this.content) { + // pickerPrefix.push( + //
+ // {this.content} + //
+ // ); + // } + + return [ + { pickerPrefix }, +
+ {col.options.map((o, index) => + + )} +
+ + ]; + + } + +} + + + +// /** +// * @hidden +// */ +// @Component({ +// selector: '.picker-col', +// template: +// '
{{col.prefix}}
' + +// '
' + +// '' + +// '
' + +// '
{{col.suffix}}
', +// host: { +// '[style.max-width]': 'col.columnWidth', +// '[class.picker-opts-left]': 'col.align=="left"', +// '[class.picker-opts-right]': 'col.align=="right"', +// } +// }) +// export class PickerColumnCmp { +// @ViewChild('colEle') colEle: ElementRef; +// @Input() col: PickerColumn; +// y: number = 0; +// colHeight: number; +// velocity: number; +// pos: number[] = []; +// startY: number = null; +// rafId: number; +// bounceFrom: number; +// minY: number; +// maxY: number; +// rotateFactor: number; +// scaleFactor: number; +// lastIndex: number; +// lastTempIndex: number; +// decelerateFunc: Function; +// debouncer: DomDebouncer; +// events: UIEventManager; + +// @Output() ionChange: EventEmitter = new EventEmitter(); + +// constructor( +// config: Config, +// private _plt: Platform, +// private elementRef: ElementRef, +// private _zone: NgZone, +// private _haptic: Haptic, +// plt: Platform, +// domCtrl: DomController, +// ) { +// this.events = new UIEventManager(plt); +// this.rotateFactor = config.getNumber('pickerRotateFactor', 0); +// this.scaleFactor = config.getNumber('pickerScaleFactor', 1); +// this.decelerateFunc = this.decelerate.bind(this); +// this.debouncer = domCtrl.debouncer(); +// } + +// ngAfterViewInit() { +// // get the scrollable element within the column +// let colEle: HTMLElement = this.colEle.nativeElement; + +// this.colHeight = colEle.clientHeight; + +// // get the height of one option +// this.optHeight = (colEle.firstElementChild ? colEle.firstElementChild.clientHeight : 0); + +// // Listening for pointer events +// this.events.pointerEvents({ +// element: this.elementRef.nativeElement, +// pointerDown: this.pointerStart.bind(this), +// pointerMove: this.pointerMove.bind(this), +// pointerUp: this.pointerEnd.bind(this), +// capture: true, +// zone: false +// }); +// } + +// ngOnDestroy() { +// this._plt.cancelRaf(this.rafId); +// this.events.destroy(); +// } + +// pointerStart(ev: UIEvent): boolean { +// console.debug('picker, pointerStart', ev.type, this.startY); +// this._haptic.gestureSelectionStart(); + +// // We have to prevent default in order to block scrolling under the picker +// // but we DO NOT have to stop propagation, since we still want +// // some "click" events to capture +// ev.preventDefault(); + +// // cancel any previous raf's that haven't fired yet +// this._plt.cancelRaf(this.rafId); + +// // remember where the pointer started from` +// this.startY = pointerCoord(ev).y; + +// // reset everything +// this.velocity = 0; +// this.pos.length = 0; +// this.pos.push(this.startY, Date.now()); + +// let options = this.col.options; +// let minY = (options.length - 1); +// let maxY = 0; +// for (var i = 0; i < options.length; i++) { +// if (!options[i].disabled) { +// minY = Math.min(minY, i); +// maxY = Math.max(maxY, i); +// } +// } + +// this.minY = (minY * this.optHeight * -1); +// this.maxY = (maxY * this.optHeight * -1); +// return true; +// } + +// pointerMove(ev: UIEvent) { +// ev.preventDefault(); +// ev.stopPropagation(); + +// let currentY = pointerCoord(ev).y; +// this.pos.push(currentY, Date.now()); + +// this.debouncer.write(() => { +// if (this.startY === null) { +// return; +// } + +// // update the scroll position relative to pointer start position +// let y = this.y + (currentY - this.startY); + +// if (y > this.minY) { +// // scrolling up higher than scroll area +// y = Math.pow(y, 0.8); +// this.bounceFrom = y; + +// } else if (y < this.maxY) { +// // scrolling down below scroll area +// y += Math.pow(this.maxY - y, 0.9); +// this.bounceFrom = y; + +// } else { +// this.bounceFrom = 0; +// } + +// this.update(y, 0, false, false); + +// let currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0); +// if (currentIndex !== this.lastTempIndex) { +// // Trigger a haptic event for physical feedback that the index has changed +// this._haptic.gestureSelectionChanged(); +// this.lastTempIndex = currentIndex; +// } +// }); +// } + +// pointerEnd(ev: UIEvent) { +// ev.preventDefault(); +// this.debouncer.cancel(); + +// if (this.startY === null) { +// return; +// } +// console.debug('picker, pointerEnd', ev.type); + +// this.velocity = 0; + +// if (this.bounceFrom > 0) { +// // bounce back up +// this.update(this.minY, 100, true, true); +// return; +// } else if (this.bounceFrom < 0) { +// // bounce back down +// this.update(this.maxY, 100, true, true); +// return; +// } + +// let endY = pointerCoord(ev).y; + +// this.pos.push(endY, Date.now()); + +// let endPos = (this.pos.length - 1); +// let startPos = endPos; +// let timeRange = (Date.now() - 100); + +// // move pointer to position measured 100ms ago +// for (var i = endPos; i > 0 && this.pos[i] > timeRange; i -= 2) { +// startPos = i; +// } + +// if (startPos !== endPos) { +// // compute relative movement between these two points +// var timeOffset = (this.pos[endPos] - this.pos[startPos]); +// var movedTop = (this.pos[startPos - 1] - this.pos[endPos - 1]); + +// // based on XXms compute the movement to apply for each render step +// var velocity = ((movedTop / timeOffset) * FRAME_MS); +// this.velocity = clamp(-MAX_PICKER_SPEED, velocity, MAX_PICKER_SPEED); +// } + +// if (Math.abs(endY - this.startY) > 3) { +// var y = this.y + (endY - this.startY); +// this.update(y, 0, true, true); +// } + +// this.startY = null; +// this.decelerate(); +// } + +// decelerate() { +// let y = 0; + +// if (isNaN(this.y) || !this.optHeight) { +// // fallback in case numbers get outta wack +// this.update(y, 0, true, true); +// this._haptic.gestureSelectionEnd(); + +// } else if (Math.abs(this.velocity) > 0) { +// // still decelerating +// this.velocity *= DECELERATION_FRICTION; + +// // do not let it go slower than a velocity of 1 +// this.velocity = (this.velocity > 0) +// ? Math.max(this.velocity, 1) +// : Math.min(this.velocity, -1); + +// y = Math.round(this.y - this.velocity); + +// if (y > this.minY) { +// // whoops, it's trying to scroll up farther than the options we have! +// y = this.minY; +// this.velocity = 0; + +// } else if (y < this.maxY) { +// // gahh, it's trying to scroll down farther than we can! +// y = this.maxY; +// this.velocity = 0; +// } + +// var notLockedIn = (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1); + +// this.update(y, 0, true, !notLockedIn); + + +// if (notLockedIn) { +// // isn't locked in yet, keep decelerating until it is +// this.rafId = this._plt.raf(this.decelerateFunc); +// } + +// } else if (this.y % this.optHeight !== 0) { +// // needs to still get locked into a position so options line up +// var currentPos = Math.abs(this.y % this.optHeight); + +// // create a velocity in the direction it needs to scroll +// this.velocity = (currentPos > (this.optHeight / 2) ? 1 : -1); +// this._haptic.gestureSelectionEnd(); + +// this.decelerate(); +// } + +// let currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0); +// if (currentIndex !== this.lastTempIndex) { +// // Trigger a haptic event for physical feedback that the index has changed +// this._haptic.gestureSelectionChanged(); +// } +// this.lastTempIndex = currentIndex; +// } + + +// refresh() { +// let min = this.col.options.length - 1; +// let max = 0; +// const options = this.col.options; +// for (var i = 0; i < options.length; i++) { +// if (!options[i].disabled) { +// min = Math.min(min, i); +// max = Math.max(max, i); +// } +// } + +// const selectedIndex = clamp(min, this.col.selectedIndex, max); +// if (this.col.prevSelected !== selectedIndex) { +// var y = (selectedIndex * this.optHeight) * -1; +// this._plt.cancelRaf(this.rafId); +// this.velocity = 0; +// this.update(y, 150, true, false); +// } +// } + +// } diff --git a/packages/core/src/components/picker/picker.ios.scss b/packages/core/src/components/picker/picker.ios.scss new file mode 100644 index 0000000000..9aa4fc3b07 --- /dev/null +++ b/packages/core/src/components/picker/picker.ios.scss @@ -0,0 +1,219 @@ +@import "../../themes/ionic.globals.ios"; +@import "./picker"; + + +// iOS Picker +// -------------------------------------------------- + +/// @prop - Height of the picker wrapper +$picker-ios-height: 260px !default; + +/// @prop - Border color of the picker wrapper +$picker-ios-border-color: $list-ios-border-color !default; + +/// @prop - Background of the picker wrapper +$picker-ios-background-color: $list-ios-background-color !default; + +/// @prop - Height of the picker toolbar +$picker-ios-toolbar-height: 44px !default; + +/// @prop - Background color of the picker toolbar +$picker-ios-toolbar-background-color: $picker-ios-background-color !default; + +/// @prop - Height of the picker button +$picker-ios-button-height: $picker-ios-toolbar-height !default; + +/// @prop - Text color of the picker button +$picker-ios-button-text-color: color($colors-ios, primary) !default; + +/// @prop - Background of the picker button +$picker-ios-button-background-color: transparent !default; + +/// @prop - Font size of the picker button +$picker-ios-button-font-size: 1.6rem !default; + +/// @prop - Padding top of the picker button +$picker-ios-button-padding-top: 0 !default; + +/// @prop - Padding end of the picker button +$picker-ios-button-padding-end: 1em !default; + +/// @prop - Padding bottom of the picker button +$picker-ios-button-padding-bottom: $picker-ios-button-padding-top !default; + +/// @prop - Padding start of the picker button +$picker-ios-button-padding-start: $picker-ios-button-padding-end !default; + +/// @prop - Font weight of the strong picker button +$picker-ios-button-strong-font-weight: 600 !default; + +/// @prop - Padding top of the picker column +$picker-ios-column-padding-top: 0 !default; + +/// @prop - Padding end of the picker column +$picker-ios-column-padding-end: 4px !default; + +/// @prop - Padding bottom of the picker column +$picker-ios-column-padding-bottom: $picker-ios-column-padding-top !default; + +/// @prop - Padding start of the picker column +$picker-ios-column-padding-start: $picker-ios-column-padding-end !default; + +/// @prop - Perspective of the picker column +$picker-ios-column-perspective: 1000px !default; + +/// @prop - Padding top of the picker option +$picker-ios-option-padding-top: 0 !default; + +/// @prop - Padding end of the picker option +$picker-ios-option-padding-end: $picker-ios-option-padding-top !default; + +/// @prop - Padding bottom of the picker option +$picker-ios-option-padding-bottom: $picker-ios-option-padding-top !default; + +/// @prop - Padding start of the picker option +$picker-ios-option-padding-start: $picker-ios-option-padding-end !default; + +/// @prop - Text color of the picker option +$picker-ios-option-text-color: $list-ios-text-color !default; + +/// @prop - Font size of the picker option +$picker-ios-option-font-size: 20px !default; + +/// @prop - Height of the picker option +$picker-ios-option-height: 42px !default; + +/// @prop - Offset y of the picker option +$picker-ios-option-offset-y: (($picker-ios-height - $picker-ios-toolbar-height) / 2) - ($picker-ios-option-height / 2) - 10 !default; + + +.picker-ios .picker-wrapper { + height: $picker-ios-height; + + border-top: 1px solid $picker-ios-border-color; + + background: $picker-ios-background-color; +} + +.picker-ios .picker-toolbar { + display: flex; + + height: $picker-ios-toolbar-height; + + border-bottom: $hairlines-width solid $picker-ios-border-color; + + background: $picker-ios-toolbar-background-color; +} + +.picker-ios .picker-toolbar-button { + @include text-align(end); + + flex: 1; +} + +.picker-ios .picker-toolbar-button:last-child .picker-button { + font-weight: $picker-ios-button-strong-font-weight; +} + +.picker-ios .picker-toolbar-cancel { + @include text-align(start); + + font-weight: normal; +} + +.picker-ios .picker-button, +.picker-ios .picker-button.activated { + @include margin(0); + @include padding($picker-ios-button-padding-top, $picker-ios-button-padding-end, $picker-ios-button-padding-bottom, $picker-ios-button-padding-start); + + height: $picker-ios-button-height; + + color: $picker-ios-button-text-color; + background: $picker-ios-button-background-color; + + font-size: $picker-ios-button-font-size; +} + +.picker-columns { + height: $picker-ios-height - $picker-ios-toolbar-height - 1; + + perspective: $picker-ios-column-perspective; +} + +.picker-ios .picker-col { + @include padding($picker-ios-column-padding-top, $picker-ios-column-padding-end, $picker-ios-column-padding-bottom, $picker-ios-column-padding-start); + + transform-style: preserve-3d; +} + +.picker-ios .picker-prefix, +.picker-ios .picker-suffix, +.picker-ios .picker-opts { + top: $picker-ios-option-offset-y; + + font-size: $picker-ios-option-font-size; + line-height: $picker-ios-option-height; + color: $picker-ios-option-text-color; + + transform-style: preserve-3d; + + pointer-events: none; +} + +.picker-ios .picker-opt { + @include margin(0); + @include transform-origin(center, center); + + height: 4.6rem; + + font-size: $picker-ios-option-font-size; + line-height: $picker-ios-option-height; + color: $picker-ios-option-text-color; + + background: transparent; + transform-style: preserve-3d; + transition-timing-function: ease-out; + + backface-visibility: hidden; + + pointer-events: auto; + + @include padding($picker-ios-option-padding-top, $picker-ios-option-padding-end, $picker-ios-option-padding-bottom, $picker-ios-option-padding-start); +} + +.picker-ios .picker-above-highlight { + @include position(0, null, null, 0); + @include transform(translate3d(0, 0, 90px)); + + position: absolute; + z-index: 10; + display: block; + + width: 100%; + height: $picker-ios-option-offset-y + 4px; + + border-bottom: 1px solid $picker-ios-border-color; + + background: linear-gradient(to bottom, + rgba($picker-ios-background-color, 1) 20%, + rgba($picker-ios-background-color, .7) 100%); +} + +.picker-ios .picker-below-highlight { + @include position($picker-ios-option-offset-y + $picker-ios-option-height - 4, null, null, 0); + @include transform(translate3d(0, 0, 90px)); + + position: absolute; + + z-index: 11; + display: block; + + width: 100%; + height: $picker-ios-option-offset-y + $picker-ios-option-height; + + border-top: 1px solid $picker-ios-border-color; + + background: linear-gradient(to top, + rgba($picker-ios-background-color, 1) 30%, + rgba($picker-ios-background-color, .7) 100%); +} \ No newline at end of file diff --git a/packages/core/src/components/picker/picker.md.scss b/packages/core/src/components/picker/picker.md.scss new file mode 100644 index 0000000000..9790b3acb6 --- /dev/null +++ b/packages/core/src/components/picker/picker.md.scss @@ -0,0 +1,195 @@ +@import "../../themes/ionic.globals.md"; +@import "./picker"; + + +// Material Design Picker +// -------------------------------------------------- + +/// @prop - Height of the picker wrapper +$picker-md-height: 260px !default; + +/// @prop - Border color of the picker wrapper +$picker-md-border-color: $list-md-border-color !default; + +/// @prop - Background of the picker wrapper +$picker-md-background-color: $list-md-background-color !default; + +/// @prop - Height of the picker toolbar +$picker-md-toolbar-height: 44px !default; + +/// @prop - Background of the picker toolbar +$picker-md-toolbar-background-color: $picker-md-background-color !default; + +/// @prop - Height of the picker button +$picker-md-button-height: $picker-md-toolbar-height !default; + +/// @prop - Text color of the picker button +$picker-md-button-text-color: color($colors-md, primary) !default; + +/// @prop - Background of the picker button +$picker-md-button-background-color: transparent !default; + +/// @prop - Font size of the picker button +$picker-md-button-font-size: 1.4rem !default; + +/// @prop - Padding top of the picker column +$picker-md-column-padding-top: 0 !default; + +/// @prop - Padding end of the picker column +$picker-md-column-padding-end: 8px !default; + +/// @prop - Padding bottom of the picker column +$picker-md-column-padding-bottom: $picker-md-column-padding-top !default; + +/// @prop - Padding start of the picker column +$picker-md-column-padding-start: $picker-md-column-padding-end !default; + +/// @prop - Padding top of the picker option +$picker-md-option-padding-top: 0 !default; + +/// @prop - Padding end of the picker option +$picker-md-option-padding-end: $picker-md-option-padding-top !default; + +/// @prop - Padding bottom of the picker option +$picker-md-option-padding-bottom: $picker-md-option-padding-top !default; + +/// @prop - Padding start of the picker option +$picker-md-option-padding-start: $picker-md-option-padding-end !default; + +/// @prop - Text color of the picker option +$picker-md-option-text-color: $list-md-text-color !default; + +/// @prop - Font size of the picker option +$picker-md-option-font-size: 22px !default; + +/// @prop - Height of the picker option +$picker-md-option-height: 42px !default; + +/// @prop - Offset y of the picker option +$picker-md-option-offset-y: (($picker-md-height - $picker-md-toolbar-height) / 2) - ($picker-md-option-height / 2) - 10 !default; + +/// @prop - Text color of the selected picker option +$picker-md-option-selected-color: color($colors-md, primary) !default; + + +.picker-md .picker-wrapper { + height: $picker-md-height; + + border-top: $hairlines-width solid $picker-md-border-color; + + background: $picker-md-background-color; +} + +.picker-md .picker-toolbar { + display: flex; + + justify-content: flex-end; + + height: $picker-md-toolbar-height; + + background: $picker-md-toolbar-background-color; +} + +.picker-md .picker-button, +.picker-md .picker-button.activated { + @include margin(0); + + height: $picker-md-button-height; + + color: $picker-md-button-text-color; + background: $picker-md-button-background-color; + + font-size: $picker-md-button-font-size; + font-weight: 500; + text-transform: uppercase; + padding: 0 1.1em; + + box-shadow: none; +} + +.picker-md .picker-columns { + height: $picker-md-height - $picker-md-toolbar-height; + + perspective: 1800px; +} + +.picker-md .picker-col { + @include padding($picker-md-column-padding-top, $picker-md-column-padding-end, $picker-md-column-padding-bottom, $picker-md-column-padding-start); + + transform-style: preserve-3d; +} + +.picker-md .picker-prefix, +.picker-md .picker-suffix, +.picker-md .picker-opts { + top: $picker-md-option-offset-y; + + font-size: $picker-md-option-font-size; + line-height: $picker-md-option-height; + color: $picker-md-option-text-color; + + transform-style: preserve-3d; + + pointer-events: none; +} + + +.picker-md .picker-opt { + @include margin(0); + @include padding($picker-md-option-padding-top, $picker-md-option-padding-end, $picker-md-option-padding-bottom, $picker-md-option-padding-start); + + height: 4.3rem; + + font-size: $picker-md-option-font-size; + line-height: $picker-md-option-height; + color: $picker-md-option-text-color; + + background: transparent; + + transition-timing-function: ease-out; + + backface-visibility: hidden; + + pointer-events: auto; +} + +.picker-md .picker-prefix, +.picker-md .picker-suffix, +.picker-md .picker-opt.picker-opt-selected { + + color: $picker-md-option-selected-color; +} + +.picker-md .picker-above-highlight { + @include position(0, null, null, 0); + @include transform(translate3d(0, 0, 90px)); + + position: absolute; + z-index: 10; + + width: 100%; + height: $picker-md-option-offset-y + 4px; + + border-bottom: 1px solid $picker-md-border-color; + + background: linear-gradient(to bottom, + rgba($picker-md-background-color, 1) 20%, + rgba($picker-md-background-color, .7) 100%); +} + +.picker-md .picker-below-highlight { + @include position($picker-md-option-offset-y + $picker-md-option-height - 4, null, null, 0); + @include transform(translate3d(0, 0, 90px)); + + position: absolute; + z-index: 11; + + width: 100%; + height: $picker-md-option-offset-y + $picker-md-option-height; + + border-top: 1px solid $picker-md-border-color; + + background: linear-gradient(to top, + rgba($picker-md-background-color, 1) 30%, + rgba($picker-md-background-color, .7) 100%); +} \ No newline at end of file diff --git a/packages/core/src/components/picker/picker.scss b/packages/core/src/components/picker/picker.scss new file mode 100644 index 0000000000..1151e2e69d --- /dev/null +++ b/packages/core/src/components/picker/picker.scss @@ -0,0 +1,169 @@ +@import "../../themes/ionic.globals"; + +// Picker +// -------------------------------------------------- + +/// @prop - Width of the picker +$picker-width: 100% !default; + +/// @prop - Max width of the picker +$picker-max-width: 500px !default; + + +ion-picker { + @include position(0, null, null, 0); + + position: absolute; + z-index: $z-index-overlay; + display: block; + + width: $picker-width; + height: $picker-width; + + contain: strict; +} + +.picker-toolbar { + z-index: 1; + + width: 100%; + + contain: strict; +} + +.picker-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: flex; + + overflow: hidden; + + flex-direction: column; + + width: $picker-width; + max-width: $picker-max-width; + + contain: strict; +} + +.picker-columns { + position: relative; + display: flex; + + overflow: hidden; + + justify-content: center; + + contain: strict; + + @include rtl() { + // Date is the same format in both directions + flex-direction: row-reverse; + } +} + +.picker-col { + position: relative; + display: flex; + + flex: 1; + justify-content: center; + + height: 100%; + + box-sizing: content-box; + + contain: content; +} + +.picker-opts { + position: relative; + + flex: 1; + + max-width: 100%; +} + +.picker-prefix { + @include text-align(end); + + position: relative; + + flex: 2; + + min-width: 45%; + max-width: 50%; + + white-space: nowrap; +} + +.picker-suffix { + @include text-align(start); + + position: relative; + + flex: 2; + + min-width: 45%; + max-width: 50%; + + white-space: nowrap; +} + +// contain property is supported by Chrome +.picker-opt { + @include position(0, null, null, 0); + @include text-align(center); + + position: absolute; + + display: block; + overflow: hidden; + + width: 100%; + + text-overflow: ellipsis; + white-space: nowrap; + + will-change: transform; + contain: strict; +} + +.picker-opt.picker-opt-disabled { + pointer-events: none; +} + +.picker-opt-disabled { + opacity: 0; +} + +.picker-opts-left { + @include ltr() { + justify-content: flex-start; + } + + @include rtl() { + justify-content: flex-end; + } +} + +.picker-opts-right { + @include ltr() { + justify-content: flex-end; + } + + @include rtl() { + justify-content: flex-start; + } +} + +.picker-above-highlight, +.picker-below-highlight { + display: none; + + pointer-events: none; +} diff --git a/packages/core/src/components/picker/picker.tsx b/packages/core/src/components/picker/picker.tsx new file mode 100644 index 0000000000..d1c658aa3a --- /dev/null +++ b/packages/core/src/components/picker/picker.tsx @@ -0,0 +1,603 @@ +import { Animation, AnimationBuilder, AnimationController, Config } from '../../index'; +import { Component, CssClassMap, Element, Event, EventEmitter, Listen, Method, Prop, State } from '@stencil/core'; + +import iOSEnterAnimation from './animations/ios.enter'; +import iOSLeaveAnimation from './animations/ios.leave'; + + +@Component({ + tag: 'ion-picker', + styleUrls: { + ios: 'picker.ios.scss', + md: 'picker.md.scss', + wp: 'picker.wp.scss' + }, + host: { + theme: 'picker' + } +}) +export class Picker { + private animation: Animation; + private durationTimeout: any; + private mode: string; + + @Element() private el: HTMLElement; + + @Event() private ionPickerDidLoad: EventEmitter; + @Event() private ionPickerDidPresent: EventEmitter; + @Event() private ionPickerWillPresent: EventEmitter; + @Event() private ionPickerWillDismiss: EventEmitter; + @Event() private ionPickerDidDismiss: EventEmitter; + @Event() private ionPickerDidUnload: EventEmitter; + + @State() private showSpinner: boolean = null; + @State() private spinner: string; + + @Prop({ connect: 'ion-animation-controller' }) animationCtrl: AnimationController; + @Prop({ context: 'config' }) config: Config; + @Prop() cssClass: string; + @Prop() content: string; + @Prop() dismissOnPageChange: boolean = false; + @Prop() duration: number; + @Prop() enterAnimation: AnimationBuilder; + @Prop() exitAnimation: AnimationBuilder; + @Prop() id: string; + @Prop() showBackdrop: boolean = true; + @Prop() enableBackdropDismiss: boolean = true; + + @Prop() buttons: PickerButton[] = []; + @Prop() columns: PickerColumn[] = []; + + present() { + return new Promise(resolve => { + this._present(resolve); + }); + } + + private _present(resolve: Function) { + if (this.animation) { + this.animation.destroy(); + this.animation = null; + } + + this.ionPickerWillPresent.emit({ picker: 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() { + clearTimeout(this.durationTimeout); + + if (this.animation) { + this.animation.destroy(); + this.animation = null; + } + + return new Promise(resolve => { + this.ionPickerWillDismiss.emit({ picker: 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.ionPickerDidDismiss.emit({ picker: this }); + + Context.dom.write(() => { + this.el.parentNode.removeChild(this.el); + }); + + resolve(); + + }).play(); + }); + }); + } + + protected ionViewDidUnload() { + this.ionPickerDidUnload.emit({ picker: this }); + } + + @Listen('ionDismiss') + protected onDismiss(ev: UIEvent) { + ev.stopPropagation(); + ev.preventDefault(); + + this.dismiss(); + } + + protected ionViewDidLoad() { + if (!this.spinner) { + let defaultSpinner = 'lines'; + + if (this.mode === 'md') { + defaultSpinner = 'crescent'; + } else if (this.mode === 'wp') { + defaultSpinner = 'circles'; + } + + this.spinner = this.config.get('pickerSpinner') || defaultSpinner; + } + + if (this.showSpinner === null || this.showSpinner === undefined) { + this.showSpinner = !!(this.spinner && this.spinner !== 'hide'); + } + this.ionPickerDidLoad.emit({ picker: this }); + } + + protected ionViewDidEnter() { + // blur the currently active element + const activeElement: any = document.activeElement; + activeElement && activeElement.blur && activeElement.blur(); + + // If there is a duration, dismiss after that amount of time + if (typeof this.duration === 'number' && this.duration > 10) { + this.durationTimeout = setTimeout(() => this.dismiss(), this.duration); + } + + this.ionPickerDidPresent.emit({ picker: this }); + } + + btnClick(button: PickerButton) { + // if (!this.enabled) { + // return; + // } + + // // keep the time of the most recent button click + // this.lastClick = Date.now(); + + let shouldDismiss = true; + + // if (button.handler) { + // // a handler has been provided, execute it + // // pass the handler the values from the inputs + // if (button.handler(this.getSelected()) === false) { + // // if the return value of the handler is false then do not dismiss + // shouldDismiss = false; + // } + // } + + if (shouldDismiss) { + this.dismiss(); + } + } + + + /** + * @param {PickerColumn} column Picker toolbar button + */ + @Method() + addColumn(column: PickerColumn) { + this.columns.push(column); + } + + @Method() + getColumn(name: string): PickerColumn { + return this.getColumns().find(column => column.name === name); + } + + @Method() + getColumns(): PickerColumn[] { + return this.columns; + } + + protected backdropClick() { + // TODO this.enabled + if (this.enableBackdropDismiss) { + let cancelBtn = this.buttons.find(b => b.role === 'cancel'); + if (cancelBtn) { + this.btnClick(cancelBtn); + } else { + this.dismiss(); + } + } + } + + protected render() { + let userCssClass = 'picker-content'; + if (this.cssClass) { + userCssClass += ' ' + this.cssClass; + } + + let buttons = this.buttons + .map(b => { + if (typeof b === 'string') { + b = { text: b }; + } + if (!b.cssClass) { + b.cssClass = ''; + } + return b; + }) + .filter(b => b !== null); + + console.log('picker render, columns', this.columns); + let columns = this.columns; + + return [ + , + + ]; + } + + buttonWrapperClass(button: PickerButton): CssClassMap { + console.log('buttonWrapperClass', button); + let buttonClass: string[] = !button.role + ? ['picker-toolbar-button'] + : [`picker-toolbar-button`, `picker-toolbar-${button.role}`]; + return buttonClass.reduce((prevValue: any, cssClass: any) => { + prevValue[cssClass] = true; + return prevValue; + }, {}); + } + + buttonClass(button: PickerButton): CssClassMap { + let buttonClass: string[] = !button.cssClass + ? ['picker-button'] + : [`picker-button`, `${button.cssClass}`]; + return buttonClass.reduce((prevValue: any, cssClass: any) => { + prevValue[cssClass] = true; + return prevValue; + }, {}); + } +} + + + + +export interface PickerButton { + text?: string; + role?: string; + cssClass?: string; + handler?: (value: any) => boolean|void; +} + +export interface PickerOptions { + buttons?: PickerButton[]; + columns?: PickerColumn[]; + cssClass?: string; + enableBackdropDismiss?: boolean; +} + +export interface PickerColumn { + name?: string; + align?: string; + selectedIndex?: number; + prevSelected?: number; + prefix?: string; + suffix?: string; + options?: PickerColumnOption[]; + cssClass?: string; + columnWidth?: string; + prefixWidth?: string; + suffixWidth?: string; + optionsWidth?: string; +} + +export interface PickerColumnOption { + text?: string; + value?: any; + disabled?: boolean; +} + +export interface PickerEvent extends Event { + detail: { + picker: Picker; + }; +} + +export const PICKER_OPT_SELECTED = 'picker-opt-selected'; +export const DECELERATION_FRICTION = 0.97; +export const FRAME_MS = (1000 / 60); +export const MAX_PICKER_SPEED = 60; + + + + + +// /** +// * @hidden +// */ +// @Component({ +// selector: 'ion-picker-cmp', +// template: ` +// +//
+//
+//
+// +// {{b.text}} +// +//
+//
+//
+//
+//
+//
+//
+//
+// `, +// host: { +// 'role': 'dialog' +// }, +// encapsulation: ViewEncapsulation.None, +// }) +// export class PickerCmp { + +// @ViewChildren(PickerColumnCmp) _cols: QueryList; +// d: PickerOptions; +// enabled: boolean; +// lastClick: number; +// id: number; +// mode: string; +// _gestureBlocker: BlockerDelegate; + +// constructor( +// private _viewCtrl: ViewController, +// private _elementRef: ElementRef, +// config: Config, +// private _plt: Platform, +// gestureCtrl: GestureController, +// params: NavParams, +// renderer: Renderer +// ) { +// this._gestureBlocker = gestureCtrl.createBlocker(BLOCK_ALL); +// this.d = params.data; +// this.mode = config.get('mode'); +// renderer.setElementClass(_elementRef.nativeElement, `picker-${this.mode}`, true); + +// if (this.d.cssClass) { +// this.d.cssClass.split(' ').forEach(cssClass => { +// renderer.setElementClass(_elementRef.nativeElement, cssClass, true); +// }); +// } + +// this.id = (++pickerIds); +// this.lastClick = 0; +// } + +// ionViewWillLoad() { +// // normalize the data +// let data = this.d; + +// data.buttons = data.buttons.map(button => { +// if (isString(button)) { +// return { text: button }; +// } +// }); + +// // clean up dat data +// data.columns = data.columns.map(column => { +// if (!isPresent(column.options)) { +// column.options = []; +// } +// column.selectedIndex = column.selectedIndex || 0; +// column.options = column.options.map(inputOpt => { +// let opt: PickerColumnOption = { +// text: '', +// value: '', +// disabled: inputOpt.disabled, +// }; + +// if (isPresent(inputOpt)) { +// if (isString(inputOpt) || isNumber(inputOpt)) { +// opt.text = inputOpt.toString(); +// opt.value = inputOpt; + +// } else { +// opt.text = isPresent(inputOpt.text) ? inputOpt.text : inputOpt.value; +// opt.value = isPresent(inputOpt.value) ? inputOpt.value : inputOpt.text; +// } +// } + +// return opt; +// }); +// return column; +// }); +// } + +// ionViewDidLoad() { +// this.refresh(); +// } + +// ionViewWillEnter() { +// this._gestureBlocker.block(); +// } + +// ionViewDidLeave() { +// this._gestureBlocker.unblock(); +// } + +// refresh() { +// this._cols.forEach(column => column.refresh()); +// } + +// _colChange(selectedOption: PickerColumnOption) { +// // one of the columns has changed its selected index +// var picker = this._viewCtrl; +// picker.ionChange.emit(this.getSelected()); +// } + +// @HostListener('body:keyup', ['$event']) +// _keyUp(ev: KeyboardEvent) { +// if (this.enabled && this._viewCtrl.isLast()) { +// if (ev.keyCode === KEY_ENTER) { +// if (this.lastClick + 1000 < Date.now()) { +// // do not fire this click if there recently was already a click +// // this can happen when the button has focus and used the enter +// // key to click the button. However, both the click handler and +// // this keyup event will fire, so only allow one of them to go. +// console.debug('picker, enter button'); +// let button = this.d.buttons[this.d.buttons.length - 1]; +// this.btnClick(button); +// } + +// } else if (ev.keyCode === KEY_ESCAPE) { +// console.debug('picker, escape button'); +// this.bdClick(); +// } +// } +// } + +// ionViewDidEnter() { +// this._plt.focusOutActiveElement(); + +// let focusableEle = this._elementRef.nativeElement.querySelector('button'); +// if (focusableEle) { +// focusableEle.focus(); +// } +// this.enabled = true; +// } + + + +// dismiss(role: string): Promise { +// return this._viewCtrl.dismiss(this.getSelected(), role); +// } + +// getSelected(): any { +// let selected: {[k: string]: any} = {}; +// this.d.columns.forEach((col, index) => { +// let selectedColumn = col.options[col.selectedIndex]; +// selected[col.name] = { +// text: selectedColumn ? selectedColumn.text : null, +// value: selectedColumn ? selectedColumn.value : null, +// columnIndex: index, +// }; +// }); +// return selected; +// } + +// ngOnDestroy() { +// assert(this._gestureBlocker.blocked === false, 'gesture blocker must be already unblocked'); +// this._gestureBlocker.destroy(); + +// } +// } + +// let pickerIds = -1; + + + + + + +// /** +// * @hidden +// */ +// export class Picker extends ViewController { +// private _app: App; + +// @Output() ionChange: EventEmitter; + +// constructor(app: App, opts: PickerOptions = {}, config: Config) { +// if (!opts) { +// opts = {}; +// } +// opts.columns = opts.columns || []; +// opts.buttons = opts.buttons || []; +// opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? Boolean(opts.enableBackdropDismiss) : true; + +// super(PickerCmp, opts, null); +// this._app = app; +// this.isOverlay = true; + +// this.ionChange = new EventEmitter(); + +// config.setTransition('picker-slide-in', PickerSlideIn); +// config.setTransition('picker-slide-out', PickerSlideOut); +// } + +// /** +// * @hidden +// */ +// getTransitionName(direction: string) { +// let key = (direction === 'back' ? 'pickerLeave' : 'pickerEnter'); +// return this._nav && this._nav.config.get(key); +// } + +// /** +// * @param {any} button Picker toolbar button +// */ +// addButton(button: any) { +// this.data.buttons.push(button); +// } + + +// refresh() { +// assert(this._cmp, 'componentRef must be valid'); +// assert(this._cmp.instance.refresh, 'instance must implement refresh()'); + +// this._cmp && this._cmp.instance.refresh && this._cmp.instance.refresh(); +// } + +// /** +// * @param {string} cssClass CSS class name to add to the picker's outer wrapper. +// */ +// setCssClass(cssClass: string) { +// this.data.cssClass = cssClass; +// } + +// /** +// * Present the picker instance. +// * +// * @param {NavOptions} [navOptions={}] Nav options to go with this transition. +// * @returns {Promise} Returns a promise which is resolved when the transition has completed. +// */ +// present(navOptions: NavOptions = {}) { +// return this._app.present(this, navOptions); +// } + +// } diff --git a/packages/core/src/components/picker/picker.wp.scss b/packages/core/src/components/picker/picker.wp.scss new file mode 100644 index 0000000000..ea9023912a --- /dev/null +++ b/packages/core/src/components/picker/picker.wp.scss @@ -0,0 +1,201 @@ +@import "../../themes/ionic.globals.wp"; +@import "./picker"; + + +// Windows Picker +// -------------------------------------------------- + +/// @prop - Height of the picker wrapper +$picker-wp-height: 260px !default; + +/// @prop - Border color of the picker wrapper +$picker-wp-border-color: $list-wp-border-color !default; + +/// @prop - Background of the picker wrapper +$picker-wp-background-color: $list-wp-background-color !default; + +/// @prop - Height of the picker toolbar +$picker-wp-toolbar-height: 44px !default; + +/// @prop - Background of the picker toolbar +$picker-wp-toolbar-background-color: $picker-wp-background-color !default; + +/// @prop - Height of the picker button +$picker-wp-button-height: $picker-wp-toolbar-height !default; + +/// @prop - Text color of the picker button +$picker-wp-button-text-color: color($colors-wp, primary) !default; + +/// @prop - Background of the picker button +$picker-wp-button-background-color: transparent !default; + +/// @prop - Padding top of the picker column +$picker-wp-column-padding-top: 0 !default; + +/// @prop - Padding end of the picker column +$picker-wp-column-padding-end: 4px !default; + +/// @prop - Padding bottom of the picker column +$picker-wp-column-padding-bottom: $picker-wp-column-padding-top !default; + +/// @prop - Padding start of the picker column +$picker-wp-column-padding-start: $picker-wp-column-padding-end !default; + +/// @prop - Padding top of the picker option +$picker-wp-option-padding-top: 0 !default; + +/// @prop - Padding end of the picker option +$picker-wp-option-padding-end: $picker-wp-option-padding-top !default; + +/// @prop - Padding bottom of the picker option +$picker-wp-option-padding-bottom: $picker-wp-option-padding-top !default; + +/// @prop - Padding start of the picker option +$picker-wp-option-padding-start: $picker-wp-option-padding-end !default; + +/// @prop - Text color of the picker option +$picker-wp-option-text-color: $list-wp-text-color !default; + +/// @prop - Font size of the picker option +$picker-wp-option-font-size: 22px !default; + +/// @prop - Height of the picker option +$picker-wp-option-height: 42px !default; + +/// @prop - Offset y of the picker option +$picker-wp-option-offset-y: (($picker-wp-height - $picker-wp-toolbar-height) / 2) - ($picker-wp-option-height / 2) - 10 !default; + +/// @prop - Text color of the selected picker option +$picker-wp-option-selected-color: color($colors-wp, primary) !default; + + +.picker-wp .picker-wrapper { + height: $picker-wp-height; + + border-top: $hairlines-width solid $picker-wp-border-color; + + background: $picker-wp-background-color; +} + +.picker-wp .picker-toolbar { + display: flex; + + justify-content: flex-end; + + height: $picker-wp-toolbar-height; + + border-width: $hairlines-width; + + background: $picker-wp-toolbar-background-color; +} + +.picker-wp .picker-toolbar-button { + @include text-align(end); + + flex: 1; +} + +.picker-wp .picker-toolbar-cancel { + @include text-align(start); + + font-weight: normal; +} + +.picker-wp .picker-button, +.picker-wp .picker-button.activated { + @include margin(0); + + height: $picker-wp-button-height; + + color: $picker-wp-button-text-color; + background: $picker-wp-button-background-color; + + box-shadow: none; +} + +.picker-wp .picker-columns { + height: $picker-wp-height - $picker-wp-toolbar-height; + + perspective: 1800px; +} + +.picker-wp .picker-col { + @include padding($picker-wp-column-padding-top, $picker-wp-column-padding-end, $picker-wp-column-padding-bottom, $picker-wp-column-padding-start); + + transform-style: preserve-3d; +} + +.picker-wp .picker-prefix, +.picker-wp .picker-suffix, +.picker-wp .picker-opts { + top: $picker-wp-option-offset-y; + + font-size: $picker-wp-option-font-size; + line-height: $picker-wp-option-height; + color: $picker-wp-option-text-color; + + transform-style: preserve-3d; + + pointer-events: none; +} + +.picker-wp .picker-opt { + @include margin(0); + @include padding($picker-wp-option-padding-top, $picker-wp-option-padding-end, $picker-wp-option-padding-bottom, $picker-wp-option-padding-start); + + height: 4.2rem; + + font-size: $picker-wp-option-font-size; + line-height: $picker-wp-option-height; + color: $picker-wp-option-text-color; + + background: transparent; + + transition-timing-function: ease-out; + + backface-visibility: hidden; + + pointer-events: auto; +} + +.picker-wp .picker-prefix, +.picker-wp .picker-suffix, +.picker-wp .picker-opt-selected { + color: $picker-wp-option-selected-color; +} + +.picker-wp .picker-above-highlight { + @include position(0, null, null, 0); + @include transform(translate3d(0, 0, 90px)); + + position: absolute; + + z-index: 10; + + width: 100%; + height: $picker-wp-option-offset-y + 4px; + + border-bottom: 1px solid $picker-wp-border-color; + + background: linear-gradient(to bottom, + rgba($picker-wp-background-color, 1) 20%, + rgba($picker-wp-background-color, .7) 100%); +} + +.picker-wp .picker-below-highlight { + @include position($picker-wp-option-offset-y + $picker-wp-option-height - 4, null, null, 0); + @include transform(translate3d(0, 0, 90px)); + + position: absolute; + + z-index: 11; + + width: 100%; + height: $picker-wp-option-offset-y + $picker-wp-option-height; + + border-top: 1px solid $picker-wp-border-color; + + background: linear-gradient(to top, + rgba($picker-wp-background-color, 1) 30%, + rgba($picker-wp-background-color, .7) 100%); +} \ No newline at end of file diff --git a/packages/core/src/index.d.ts b/packages/core/src/index.d.ts index f9f969dd4d..b66c24038b 100644 --- a/packages/core/src/index.d.ts +++ b/packages/core/src/index.d.ts @@ -14,6 +14,9 @@ import { MenuController } from './components/menu/menu-controller'; import { Modal, ModalOptions, ModalEvent } from './components/modal/modal'; import { ModalController } from './components/modal-controller/modal-controller'; +import { Picker, PickerButton, PickerColumn, PickerEvent, PickerOptions } from './components/picker/picker' +import { PickerController } from './components/picker-controller/picker-controller' + import { Popover, PopoverEvent, PopoverOptions } from './components/popover/popover' import { PopoverController } from './components/popover-controller/popover-controller' @@ -82,6 +85,12 @@ export { ModalController, ModalOptions, ModalEvent, + Picker, + PickerButton, + PickerColumn, + PickerController, + PickerEvent, + PickerOptions, Popover, PopoverController, PopoverEvent,