Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@unpourtous/react-native-popup-stub

Package Overview
Dependencies
Maintainers
4
Versions
40
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@unpourtous/react-native-popup-stub - npm Package Compare versions

Comparing version 1.0.19 to 1.1.0

package-lock.json

2

package.json
{
"name": "@unpourtous/react-native-popup-stub",
"version": "1.0.19",
"version": "1.1.0",
"description": "A popup container for implements your own popups like ActionSheet, Dialog, Alert, Toast ...",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -8,7 +8,11 @@ /* global __DEV__ */

import React, { Component } from 'react'
import { View, TouchableWithoutFeedback, StyleSheet } from 'react-native'
import { Platform, View, TouchableWithoutFeedback, StyleSheet } from 'react-native'
import PropTypes from 'prop-types'
import uuidV1 from 'uuid/v1'
import * as Animatable from 'react-native-animatable'
import createPopup from './util/createPopup'
import log from './util/log'
import { reverseKeyframes } from './util/keyframes'
const IS_ANDROID = Platform.OS === 'android'
const BG_FROM = 'rgba(0,0,0,0)'

@@ -41,2 +45,29 @@ const BG_TO = 'rgba(23,26,35,0.6)'

// static method is easier to use
// these are the same api to non-static methods
static addPopup (element, option) {
if (!element || !PopupStub.stub) return
return PopupStub.stub.addPopup(element, option)
}
static removePopup (id, forceUpdate = true) {
if (!id || !PopupStub.stub) return
PopupStub.stub.removePopup(id, forceUpdate)
}
static removePopupImmediately (id) {
if (!id || !PopupStub.stub) return null
return PopupStub.stub.removePopupImmediately(id)
}
static resetPopupProperty (id, key, value) {
if (!id || !PopupStub.stub) return null
PopupStub.stub.resetPopupProperty(id, key, value)
}
constructor (props) {

@@ -46,16 +77,17 @@ super(props)

this.state = {
popups: new Map(),
// if true, show a background visual mask
hasMask: true,
// visual mask related animation
maskDelay: 0,
maskDirection: 'normal',
// background mask fades in/out
maskAnimation: {
from: { backgroundColor: BG_FROM },
to: { backgroundColor: props.maskColor }
}
popups: new Map()
}
}
_sortPopups (popups) {
if (popups.size > 1) {
// sort by zIndex
return new Map([...popups.entries()].sort((a, b) => {
return a[1].zIndex - b[1].zIndex
}))
}
return popups
}
/*

@@ -65,11 +97,3 @@ * Add a new popup

* @param {Component} element
* @param {Object} [option] ```{
id: popup global unique id.
lock: nearly same as pointerEvents, by default, 'auto' if has a mask, otherwise 'mask-only'.
mask: has a mask or not, default true.
zIndex: same as in css, the priority of popup, the bigger the higher.
position: position of element in screen, available: none, left, right, top, bottom, center(defualt).
wrapperStyle: animation wrapper style (each popup is wrapped in an Animatable.View).
...[Animatable.props](https://github.com/oblador/react-native-animatable) direction and onAnimationEnd are reserved
}```
* @param {Object} [option]
* @return {String} popup unique id

@@ -80,35 +104,12 @@ */

if (option && typeof option === 'string') {
// This warning is for original version, and will be removed in future
log('`id` parameter is deprecated, use `option` instead.', true)
option = {id: option}
}
let opt = Object.assign({id: uuidV1(), zIndex: 1, mask: true, duration: 1000}, option)
if (!opt.lock) {
opt.lock = opt.mask ? 'auto' : 'mask-only'
} else if (opt.lock === 'box-none' || opt.lock === 'box-only') {
// sorry to misuse lock with pointerEvents, this will be removed in future
log('`box-none` and `box-only` is deprecated, use `mask-only` and `all` instead.', true)
} else if (['all', 'auto', 'mask-only', 'none'].indexOf(opt.lock) < 0) {
log('lock should be all, auto, mask-only or none', true)
}
let newPopup = createPopup(element, option, uuidV1())
let popups = this.state.popups
let newPopups = this.state.popups
// close previous popup that has the same zIndex
for (let key of newPopups.keys()) {
let popup = newPopups.get(key)
if (popup.zIndex === opt.zIndex && !popup._closing) {
// TODO: enable config to close or not
for (let key of popups.keys()) {
let popup = popups.get(key)
if (popup.zIndex === newPopup.zIndex && !popup._closing) {
// new popup with same zIndex comes in with delay, visually
opt.delay = popup.duration / 2
opt.onAnimationEnd = () => {
// reset property
let popups = this.state.popups
let popup = popups.get(key)
if (popup) {
popup.delay = 0
popup.onAnimationEnd = null
this.setState({popups})
}
}
newPopup.delay = popup.duration / 2
// remove popup until it completes animation

@@ -122,19 +123,14 @@ this.removePopup(key, false)

// add this new popup to our list
opt.component = element
newPopups.set(opt.id, opt)
popups.set(newPopup.id, newPopup)
log('adding...' + opt.id)
this.setState({
// whenever a popup is added, reset background mask config
hasMask: hasMask(newPopups),
maskDelay: 0,
maskDirection: 'normal',
// We can't use position-zIndex here to identify the layer,
// cause it has compatible problem in some android devices,
// cause it has compatible problem in android devices,
// so sort by hand.
popups: sortPopups(newPopups)
popups: this._sortPopups(popups)
})
return opt.id
log('added ' + newPopup.id)
return newPopup.id
}

@@ -150,15 +146,15 @@

removePopup (id, forceUpdate = true) {
// temporarily disable playback animations
return this.removePopupImmediately(id)
if (!id || !PopupStub.stub) return
// TODO: fix playback animation in android
if (IS_ANDROID) {
return this.removePopupImmediately(id)
}
log('lazy closing...' + id)
let popups = this.state.popups
let popup = popups.get(id)
if (!popup || popup._closing) return
if (!popup || popup._closing) {
return
}
// if no animation defined, remove it directly
// TODO: There is a weird timing issue in android, so disable the animation temporarily
if (!popup.animation) {

@@ -169,8 +165,8 @@ this.removePopupImmediately(id)

log('closing...' + id)
// set close flag
popup._closing = true
// prevent double click
popup.lock = 'all'
// and try to activate background mask animation
popup.mask = false
// reset delay
popup.delay = 0

@@ -194,9 +190,3 @@ // when closing a popup, it plays back

if (forceUpdate) {
let isLast = popups.size === 1
this.setState({
popups: popups,
// if this is the last popup, prepare for fading out of background mask
maskDirection: isLast ? 'reverse' : this.state.maskDirection,
maskDelay: isLast ? Math.max(0, popup.duration - 100) : this.state.maskDelay
})
this.setState({popups})
}

@@ -212,4 +202,2 @@ }

removePopupImmediately (id) {
log('[popup]: closing ' + id)
let popups = this.state.popups

@@ -219,3 +207,3 @@ if (popups.has(id)) {

this.setState({popups})
log('closed ' + id)
return true

@@ -254,7 +242,4 @@ }

return (
<View
pointerEvents={this.state.hasMask ? 'auto' : 'box-none'}
style={[styles.full, {zIndex: 999}]}
>
{createPopupElements(this.state.popups)}
<View pointerEvents='box-none' style={[styles.full, {zIndex: 999}]}>
{createPopupElements(this.state.popups, this.props.maskColor)}
</View>

@@ -265,43 +250,3 @@ )

function log (info, isWarning) {
// Dev flag is set in project config
if (typeof __DEV__ !== 'undefined' && __DEV__) {
if (isWarning) {
console.warn('[popup]:', info)
} else {
console.log('[popup]:', info)
}
}
}
function hasMask (popups) {
for (let popup of popups.values()) {
if (popup.mask) {
return true
}
}
return false
}
function sortPopups (popups) {
if (popups.size > 1) {
// sort by zIndex
return new Map([...popups.entries()].sort((a, b) => {
return a[1].zIndex - b[1].zIndex
}))
}
return popups
}
// TODO: reverse any valid keyframes definition
function reverseKeyframes (keyframes) {
return {
from: keyframes.to,
to: keyframes.from
}
}
function createPopupElements (popups) {
function createPopupElements (popups, maskColor) {
log('render...size ' + popups.size)

@@ -311,3 +256,3 @@ let popupElements = []

// Popups are independent to each other
let pointerEvents = lockModeToPointerEvents(popup.lock)
let pointerEvents = getPointerEvents(popup)
popupElements.push(

@@ -319,7 +264,7 @@ <View

styles.full,
getPositionStyle(popup.position)
popup.position === 'center' ? styles.posCenter : null
]}
>
{popup.mask ? <TouchableWithoutFeedback onPress={() => onAutoClose(popup)}>
<View style={[styles.full, {backgroundColor: BG_TO}]} />
<View style={[styles.full, {backgroundColor: maskColor}]} />
</TouchableWithoutFeedback> : null}

@@ -345,48 +290,18 @@ <Animatable.View

function onAutoClose (popup) {
if (popup && popup.lock === 'auto' && !popup._closing) {
PopupStub.stub.removePopup(popup.id)
if (popup && popup.autoClose && !popup._closing) {
PopupStub.removePopup(popup.id)
}
}
// lock mode is not exactly same as pointerEvents,
// box-only and box-none will be removed in future.
function lockModeToPointerEvents (mode) {
switch (mode) {
case 'auto':
// if has a mask, enable autoclose
// otherwise, blank erea is not responsible
return 'auto'
case 'all':
case 'box-only':
// all touches will be blocked
return 'box-only'
case 'box-none':
case 'mask-only':
// if has a mask, it won't autoclose
// if not, blank erea may click through
return 'box-none'
case 'none':
// touches will just pass through current popup
return 'none'
default:
return 'auto'
// map popup status to pointerEvents
function getPointerEvents (popup) {
if (popup._closing) {
// all touches will be blocked, this will prevent double click
return 'box-only'
}
}
function getPositionStyle (pos) {
switch (pos) {
case 'bottom':
return styles.posBottom
case 'center':
return styles.posCenter
case 'left':
return styles.posLeft
case 'none':
return null
case 'right':
return styles.posRight
case 'top':
return styles.posTop
default:
return styles.posCenter
if (popup.mask) {
return popup.autoClose ? 'auto' : 'box-none'
} else {
// only if there is no mask, blank erea can click through it's container
return popup.enableClickThrough ? 'box-none' : 'auto'
}

@@ -403,20 +318,2 @@ }

},
posLeft: {
alignItems: 'center',
justifyContent: 'flex-start'
},
posRight: {
alignItems: 'center',
justifyContent: 'flex-end'
},
posTop: {
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'flex-start'
},
posBottom: {
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'flex-end'
},
posCenter: {

@@ -423,0 +320,0 @@ alignItems: 'center',

@@ -5,4 +5,4 @@ # react-native-popup-stub

[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
## Introduction
## Introduction
Popup global controller:

@@ -18,3 +18,3 @@

## Installation
## Installation
```

@@ -26,22 +26,30 @@ npm install @unpourtous/react-native-popup-stub --save

### Props
PopupStub properties
| param | type | description |
| --- | --- | --- |
| maskColor | String | mask color, default 'rgba(23,26,35,0.6)' |
### PopupStub.init(_ref)
Init PopupStub with PopupStub reference.
| param | type | description |
| param | type | description |
| --- | --- | --- |
| _ref | ref | should be the PopupStub component ref |
### PopupStub.stub.addPopup(component, option)
### PopupStub.addPopup(component, option)
Add popup to PopupStub, use option to controller actions for each Component/Layers.
| param | type | description |
| param | type | description |
| --- | --- | --- |
| component | Component | View component |
| option | Object | see below |
| option.id | String | popup unique id, optional |
| option.lock | Enum | popup layer lock mode, by default, 'auto' if has a mask, otherwise 'mask-only' |
| option.mask | Boolean | has a mask or not, default true |
| option.zIndex | Integer | same as in css, the priority of popup, the bigger the higher |
| option.position | String | position of element in screen, available: none, left, right, top, bottom, center(defualt) |
| option.wrapperStyle | Object | animation wrapper style (each popup is wrapped in an Animatable.View) |
| .id | String | popup unique id, optional |
| .mask | Boolean | has a visual mask or not, default true |
| .autoClose | Boolean | enable clicking mask to close or not, default true |
| .enableClickThrough | Boolean | blank erea (of container) may click through or not, default false |
| .zIndex | Integer | same as in css, the priority of popup, the bigger the higher |
| .position | String | position of element in screen, available: none, left, right, top, bottom, center(defualt) |
| .wrapperStyle | Object | animation wrapper style (each popup is wrapped in an Animatable.View) |
| Animatable.props | -- | see [Animatable.props](https://github.com/oblador/react-native-animatable), direction and onAnimationEnd are reserved |

@@ -51,6 +59,8 @@

### PopupStub.stub.removePopup(id)
**warning**: `lock` is deprecated from `v1.1.0` on, but still valid for a few versions. Use `autoClose` and `enableClickThrough` instead.
### PopupStub.removePopup(id)
Invoke popup exiting animation and remove it on animation end
| param | type | description |
| param | type | description |
| --- | --- | --- |

@@ -67,3 +77,3 @@ | id | String | popup unique id

<View style={styles.container}>
{/* Your root node */}
{/* Your root node */}
<TouchableHighlight

@@ -77,4 +87,4 @@ onPress={() => {

</TouchableHighlight>
{/* Step One: Add popup stub */}
{/* Step One: Add popup stub */}
<PopupStub ref={_ref => {

@@ -94,5 +104,5 @@ // Step Two: Init PopupStub itself

static show (msg) {
const id = PopupStub.stub.addPopup(<Toast msg={msg} />, {
lock: 'none',
const id = PopupStub.addPopup(<Toast msg={msg} />, {
mask: false,
enableClickThrough: true,
position: 'center',

@@ -107,6 +117,6 @@ zIndex: 500,

setTimeout(() => {
PopupStub.stub.removePopup(id)
PopupStub.removePopup(id)
}, 1500)
}
render () {

@@ -122,8 +132,9 @@ return (

## TODO List
## Todo
- [x] Each popup an independent mask, rather than share a visual one
- [ ] Support popup life circle callback or so
- [ ] Enable mask animation
- [ ] Enable remove animation in android
- [ ] Enable reversing any valid animations
- [ ] Support onAnimationEnd
- [ ] Support onClose callback or so
- [ ] Each popup an independent mask, rather than share a visual one

@@ -130,0 +141,0 @@ ## License

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc