react-native-airship
Advanced tools
| import { Airship } from '../types'; | ||
| /** | ||
| * Constructs an Airship component. | ||
| */ | ||
| export declare function makeAirship(): Airship; |
| var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
| return new (P || (P = Promise))(function (resolve, reject) { | ||
| function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
| function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
| step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
| }); | ||
| }; | ||
| import * as React from 'react'; | ||
| import { View } from 'react-native'; | ||
| import { makeEvent, makeEvents } from 'yavent'; | ||
| import { sidesToOffset, sidesToPadding } from '../util/sides'; | ||
| import { Barometer } from './Barometer'; | ||
| const emptyLayout = { | ||
| offset: sidesToOffset([0, 0, 0, 0]), | ||
| padding: sidesToPadding([0, 0, 0, 0]) | ||
| }; | ||
| /** | ||
| * Constructs an Airship component. | ||
| */ | ||
| export function makeAirship() { | ||
| // Static state shared by all mounted containers: | ||
| const [onClear, emitClear] = makeEvent(); | ||
| const [onGuestsChange, emitGuestsChange] = makeEvent(); | ||
| let guests = []; | ||
| let nextKey = 0; | ||
| const AirshipHost = (props) => { | ||
| const { children } = props; | ||
| // Watch the common guest list: | ||
| const [ourGuests, setGuests] = React.useState(guests); | ||
| React.useEffect(() => onGuestsChange(setGuests), []); | ||
| // Track layout changes: | ||
| const [layout, setLayout] = React.useState(emptyLayout); | ||
| return (React.createElement(React.Fragment, null, | ||
| React.createElement(Barometer, { onLayout: setLayout }), | ||
| children, | ||
| ourGuests.map(guest => (React.createElement(View, { key: guest.key, pointerEvents: "box-none", style: Object.assign(Object.assign(Object.assign({}, layout.offset), layout.padding), { flexDirection: 'row', justifyContent: 'center', position: 'absolute' }) }, guest.element))))); | ||
| }; | ||
| let clearing = false; | ||
| function clear() { | ||
| if (clearing) | ||
| return; | ||
| clearing = true; | ||
| emitClear(undefined); | ||
| clearing = false; | ||
| } | ||
| function show(render) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const key = `airship${nextKey++}`; | ||
| function remove() { | ||
| unclear(); | ||
| guests = guests.filter(guest => guest.key !== key); | ||
| emitGuestsChange(guests); | ||
| } | ||
| // Assemble the bridge: | ||
| const [on, emit] = makeEvents(); | ||
| let bridge; | ||
| const promise = new Promise((resolve, reject) => { | ||
| bridge = { | ||
| on, | ||
| onResult: callback => on('result', callback), | ||
| reject, | ||
| remove, | ||
| resolve | ||
| }; | ||
| }); | ||
| // Hook up events: | ||
| promise.then(() => emit('result', undefined), () => emit('result', undefined)); | ||
| const unclear = onClear(() => emit('clear', undefined)); | ||
| // Save the guest element in the shared state: | ||
| guests = [...guests, { key, element: render(bridge) }]; | ||
| emitGuestsChange(guests); | ||
| return promise; | ||
| }); | ||
| } | ||
| return Object.assign(AirshipHost, { clear, show }); | ||
| } |
| import * as React from 'react'; | ||
| import { ViewStyle } from 'react-native'; | ||
| import { AirshipBridge } from '../types'; | ||
| export interface AirshipDropdownProps { | ||
| bridge: AirshipBridge<undefined>; | ||
| children?: React.ReactNode; | ||
| onPress?: () => void; | ||
| autoHideMs?: number; | ||
| backgroundColor?: string; | ||
| borderRadius?: number; | ||
| flexDirection?: ViewStyle['flexDirection']; | ||
| justifyContent?: ViewStyle['justifyContent']; | ||
| margin?: number | number[]; | ||
| maxHeight?: number; | ||
| maxWidth?: number; | ||
| padding?: number | number[]; | ||
| slideInMs?: number; | ||
| slideOutMs?: number; | ||
| } | ||
| /** | ||
| * A notification that slides down from the top of the screen. | ||
| */ | ||
| export declare function AirshipDropdown(props: AirshipDropdownProps): JSX.Element; |
| import * as React from 'react'; | ||
| import { Animated, Dimensions, TouchableWithoutFeedback } from 'react-native'; | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides'; | ||
| const safeAreaGap = 64; | ||
| /** | ||
| * A notification that slides down from the top of the screen. | ||
| */ | ||
| export function AirshipDropdown(props) { | ||
| const { bridge, children, onPress = () => bridge.resolve(undefined), autoHideMs = 5000, backgroundColor = 'white', borderRadius = 4, flexDirection, justifyContent, maxHeight = defaultMaxHeight(), maxWidth = 512, slideInMs = 300, slideOutMs = 500 } = props; | ||
| const margin = sidesToMargin(fixSides(props.margin, 0)); | ||
| const padding = sidesToPadding(fixSides(props.padding, 0)); | ||
| const hiddenOffset = -(maxHeight + margin.marginBottom); | ||
| margin.marginTop = -safeAreaGap; | ||
| padding.paddingTop += safeAreaGap; | ||
| // Create the animation: | ||
| const offset = React.useRef(new Animated.Value(hiddenOffset)).current; | ||
| React.useEffect(() => { | ||
| let timeout; | ||
| // Animate in: | ||
| Animated.timing(offset, { | ||
| toValue: 0, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }).start(() => { | ||
| // Start the auto-hide timer: | ||
| if (autoHideMs > 0) { | ||
| timeout = setTimeout(() => { | ||
| timeout = undefined; | ||
| bridge.resolve(undefined); | ||
| }, autoHideMs); | ||
| } | ||
| }); | ||
| // Animate out: | ||
| bridge.on('clear', () => bridge.resolve(undefined)); | ||
| bridge.on('result', () => { | ||
| Animated.timing(offset, { | ||
| toValue: hiddenOffset, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }).start(() => bridge.remove()); | ||
| }); | ||
| return () => { | ||
| if (timeout != null) | ||
| clearTimeout(timeout); | ||
| }; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []); | ||
| const bodyStyle = Object.assign(Object.assign(Object.assign({}, margin), padding), { alignSelf: 'flex-start', backgroundColor, borderBottomLeftRadius: borderRadius, borderBottomRightRadius: borderRadius, flexDirection, flexShrink: 1, justifyContent, | ||
| maxHeight, shadowOffset: { height: 0, width: 0 }, shadowOpacity: 1, shadowRadius: 4, transform: [{ translateY: offset }], width: maxWidth // This works because flexShrink is set | ||
| }); | ||
| return (React.createElement(TouchableWithoutFeedback, { onPress: onPress }, | ||
| React.createElement(Animated.View, { style: bodyStyle }, children))); | ||
| } | ||
| function defaultMaxHeight() { | ||
| const { width, height } = Dimensions.get('screen'); | ||
| return 0.25 * Math.max(width, height); | ||
| } |
| import * as React from 'react'; | ||
| import { ViewStyle } from 'react-native'; | ||
| import { AirshipBridge } from '../types'; | ||
| export interface AirshipModalProps<T = unknown> { | ||
| bridge: AirshipBridge<T>; | ||
| children?: React.ReactNode; | ||
| onCancel: () => void; | ||
| center?: boolean; | ||
| backgroundColor?: string; | ||
| borderRadius?: number; | ||
| flexDirection?: ViewStyle['flexDirection']; | ||
| justifyContent?: ViewStyle['justifyContent']; | ||
| margin?: number | number[]; | ||
| maxHeight?: number; | ||
| maxWidth?: number; | ||
| padding?: number | number[]; | ||
| shadowOffset?: { | ||
| height: number; | ||
| width: number; | ||
| }; | ||
| shadowOpacity?: number; | ||
| shadowRadius?: number; | ||
| slideInMs?: number; | ||
| slideOutMs?: number; | ||
| underlay?: string | React.ReactElement; | ||
| } | ||
| /** | ||
| * A modal that slides a modal up from the bottom of the screen | ||
| * and dims the rest of the app. | ||
| */ | ||
| export declare function AirshipModal<T>(props: AirshipModalProps<T>): JSX.Element; |
| import * as React from 'react'; | ||
| import { Animated, BackHandler, Dimensions, TouchableWithoutFeedback } from 'react-native'; | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides'; | ||
| const safeAreaGap = 64; | ||
| /** | ||
| * A modal that slides a modal up from the bottom of the screen | ||
| * and dims the rest of the app. | ||
| */ | ||
| export function AirshipModal(props) { | ||
| const { bridge, children, onCancel, backgroundColor = 'white', borderRadius = 10, center = false, flexDirection, justifyContent, maxHeight, maxWidth = 512, shadowOffset = { height: 0, width: 0 }, shadowOpacity = 1, shadowRadius = 10, slideInMs = 300, slideOutMs = 300, underlay = 'rgba(0, 0, 0, 0.75)' } = props; | ||
| const margin = sidesToMargin(fixSides(props.margin, 0)); | ||
| const padding = sidesToPadding(fixSides(props.padding, 0)); | ||
| React.useEffect(() => bridge.on('clear', onCancel), [bridge, onCancel]); | ||
| // Create the animations: | ||
| const offset = React.useRef(new Animated.Value(Dimensions.get('window').height)).current; | ||
| const opacity = React.useRef(new Animated.Value(0)).current; | ||
| React.useEffect(() => { | ||
| // Animate in: | ||
| Animated.parallel([ | ||
| Animated.timing(opacity, { | ||
| toValue: 1, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }), | ||
| Animated.timing(offset, { | ||
| toValue: 0, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }) | ||
| ]).start(); | ||
| // Animate out: | ||
| bridge.on('result', () => { | ||
| Animated.parallel([ | ||
| Animated.timing(opacity, { | ||
| toValue: 0, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }), | ||
| Animated.timing(offset, { | ||
| toValue: Dimensions.get('window').height, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }) | ||
| ]).start(bridge.remove); | ||
| }); | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []); | ||
| // Set up the back-button handler: | ||
| React.useEffect(() => { | ||
| const backHandler = BackHandler.addEventListener('hardwareBackPress', () => { | ||
| onCancel(); | ||
| return true; | ||
| }); | ||
| return () => backHandler.remove(); | ||
| }, [onCancel]); | ||
| const underlayStyle = { | ||
| backgroundColor: typeof underlay === 'string' ? underlay : 'transparent', | ||
| bottom: 0, | ||
| left: 0, | ||
| opacity: opacity, | ||
| position: 'absolute', | ||
| right: 0, | ||
| top: 0 | ||
| }; | ||
| const bodyCommon = Object.assign(Object.assign(Object.assign({}, margin), padding), { alignSelf: center ? 'center' : 'flex-end', backgroundColor, | ||
| flexDirection, flexShrink: 1, justifyContent, | ||
| maxHeight, | ||
| shadowOffset, | ||
| shadowOpacity, | ||
| shadowRadius, transform: [{ translateY: offset }], width: maxWidth // This works because flexShrink is set | ||
| }); | ||
| const bodyStyle = center | ||
| ? Object.assign(Object.assign({}, bodyCommon), { borderRadius }) : Object.assign(Object.assign({}, bodyCommon), { borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius, marginBottom: -safeAreaGap, paddingBottom: padding.paddingBottom + safeAreaGap }); | ||
| return (React.createElement(React.Fragment, null, | ||
| React.createElement(TouchableWithoutFeedback, { onPress: () => onCancel() }, | ||
| React.createElement(Animated.View, { style: underlayStyle }, typeof underlay !== 'string' ? underlay : undefined)), | ||
| React.createElement(Animated.View, { style: bodyStyle }, children))); | ||
| } |
| import * as React from 'react'; | ||
| import { AirshipBridge } from '../types'; | ||
| export interface AirshipToastProps { | ||
| bridge: AirshipBridge<undefined>; | ||
| children?: React.ReactNode; | ||
| message?: string; | ||
| autoHideMs?: number; | ||
| backgroundColor?: string; | ||
| borderRadius?: number; | ||
| fadeInMs?: number; | ||
| fadeOutMs?: number; | ||
| margin?: number | number[]; | ||
| maxWidth?: number; | ||
| opacity?: number; | ||
| padding?: number | number[]; | ||
| textColor?: string; | ||
| textSize?: number; | ||
| } | ||
| /** | ||
| * A semi-transparent message overlay. | ||
| */ | ||
| export declare function AirshipToast(props: AirshipToastProps): JSX.Element; |
| import * as React from 'react'; | ||
| import { Animated, Text } from 'react-native'; | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides'; | ||
| /** | ||
| * A semi-transparent message overlay. | ||
| */ | ||
| export function AirshipToast(props) { | ||
| const { textSize = 14 } = props; | ||
| const { autoHideMs = 3000, backgroundColor = 'white', borderRadius = 1.5 * textSize, bridge, children, fadeInMs = 300, fadeOutMs = 1000, maxWidth = 512, opacity: finalOpacity = 0.9, message, textColor = 'black' } = props; | ||
| const margin = sidesToMargin(fixSides(props.margin, 2 * textSize)); | ||
| const padding = sidesToPadding(fixSides(props.padding, textSize)); | ||
| // Create the animation: | ||
| const opacity = React.useRef(new Animated.Value(0)).current; | ||
| React.useEffect(() => { | ||
| let timeout; | ||
| // Animate in: | ||
| Animated.timing(opacity, { | ||
| toValue: finalOpacity, | ||
| duration: fadeInMs, | ||
| useNativeDriver: true | ||
| }).start(() => { | ||
| // Start the auto-hide timer: | ||
| if (autoHideMs > 0) { | ||
| timeout = setTimeout(() => { | ||
| timeout = undefined; | ||
| bridge.resolve(undefined); | ||
| }, autoHideMs); | ||
| } | ||
| }); | ||
| // Animate out: | ||
| bridge.on('clear', () => bridge.resolve(undefined)); | ||
| bridge.on('result', () => { | ||
| Animated.timing(opacity, { | ||
| toValue: 0, | ||
| duration: fadeOutMs, | ||
| useNativeDriver: true | ||
| }).start(() => bridge.remove()); | ||
| }); | ||
| return () => { | ||
| if (timeout != null) | ||
| clearTimeout(timeout); | ||
| }; | ||
| }); | ||
| const bodyStyle = Object.assign(Object.assign(Object.assign({}, margin), padding), { alignItems: 'center', alignSelf: 'flex-end', backgroundColor, | ||
| borderRadius, flexDirection: 'row', justifyContent: 'flex-start', maxWidth, opacity: opacity }); | ||
| const textStyle = { | ||
| color: textColor, | ||
| flexShrink: 1, | ||
| fontSize: textSize, | ||
| textAlign: 'center' | ||
| }; | ||
| return (React.createElement(Animated.View, { style: bodyStyle }, | ||
| message != null ? React.createElement(Text, { style: textStyle }, message) : null, | ||
| children)); | ||
| } |
| import * as React from 'react'; | ||
| import { Offset, Padding } from '../util/sides'; | ||
| export interface BarometerLayout { | ||
| offset: Offset; | ||
| padding: Padding; | ||
| } | ||
| interface Props { | ||
| children?: React.ReactNode; | ||
| onLayout?: (layout: BarometerLayout) => void; | ||
| } | ||
| /** | ||
| * Measures various things about the Airship environment, | ||
| * so we know how to position our children. | ||
| * | ||
| * This component mounts a view with absolute positioning, | ||
| * and then measures that view relative to the window. | ||
| * If a side is inset from the window edge, we use a negative offset | ||
| * to expand it outward. If a side extends beyond the window edge, | ||
| * we use padding to push the content inward. | ||
| * | ||
| * On iOS, we also mount a child inside a SafeAreaView, to measure | ||
| * the safe area insets. We add these to the padding. | ||
| * | ||
| * Finally, we keep track of the keyboard, adding extra padding & | ||
| * scheduling animations as needed. | ||
| */ | ||
| export declare function Barometer(props: Props): JSX.Element; | ||
| export {}; |
| import * as React from 'react'; | ||
| import { Dimensions, Keyboard, Platform, SafeAreaView, StyleSheet, View } from 'react-native'; | ||
| import { addSides, mapSides, sidesToOffset, sidesToPadding, subtractSides } from '../util/sides'; | ||
| const emptySides = [0, 0, 0, 0]; | ||
| /** | ||
| * Measures various things about the Airship environment, | ||
| * so we know how to position our children. | ||
| * | ||
| * This component mounts a view with absolute positioning, | ||
| * and then measures that view relative to the window. | ||
| * If a side is inset from the window edge, we use a negative offset | ||
| * to expand it outward. If a side extends beyond the window edge, | ||
| * we use padding to push the content inward. | ||
| * | ||
| * On iOS, we also mount a child inside a SafeAreaView, to measure | ||
| * the safe area insets. We add these to the padding. | ||
| * | ||
| * Finally, we keep track of the keyboard, adding extra padding & | ||
| * scheduling animations as needed. | ||
| */ | ||
| export function Barometer(props) { | ||
| const { children, onLayout = () => { } } = props; | ||
| // Mutable state: | ||
| const keyboardHeight = React.useRef(0); | ||
| const lastLayoutJson = React.useRef(''); | ||
| const view = React.useRef(null); | ||
| const childView = React.useRef(null); | ||
| // Handle layout changes: | ||
| const handleLayout = React.useCallback(() => { | ||
| // Measure the view in the window: | ||
| const viewPromise = new Promise(resolve => { | ||
| if (view.current == null) | ||
| return resolve(emptySides); | ||
| view.current.measureInWindow((x, y, width, height) => { | ||
| const window = Dimensions.get('window'); | ||
| resolve([y, window.width - width - x, window.height - height - y, x]); | ||
| }); | ||
| }); | ||
| // Measure the child view in the window: | ||
| const childPromise = new Promise(resolve => { | ||
| if (childView.current == null) | ||
| return resolve(viewPromise); | ||
| childView.current.measureInWindow((x, y, width, height) => { | ||
| const window = Dimensions.get('window'); | ||
| resolve([y, window.width - width - x, window.height - height - y, x]); | ||
| }); | ||
| }); | ||
| // Measure the gap between the bottom of the screen and the view: | ||
| const bottomPromise = new Promise(resolve => { | ||
| if (view.current == null) | ||
| return 0; | ||
| view.current.measure((x, y, width, height, screenX, screenY) => { | ||
| const screen = Dimensions.get('screen'); | ||
| resolve(screen.height - height - screenY); | ||
| }); | ||
| }); | ||
| // Combine the results, then call the callback: | ||
| Promise.all([viewPromise, childPromise, bottomPromise]) | ||
| .then(([viewOffset, childOffset, bottomGap]) => { | ||
| // Cancel out any offset, so we cover the full window: | ||
| const offset = mapSides(viewOffset, side => -Math.max(side, 0)); | ||
| // If the offset is negative, issue positive padding, | ||
| // plus any safe area: | ||
| const safePadding = subtractSides(childOffset, viewOffset); | ||
| const padding = addSides(safePadding, mapSides(viewOffset, side => Math.abs(side))); | ||
| // Use the keyboard padding, if needed: | ||
| const keyboardPadding = Math.max(keyboardHeight.current - bottomGap - offset[2], 0); | ||
| padding[2] = Math.max(padding[2], keyboardPadding); | ||
| // Send an update if we have changes: | ||
| const string = JSON.stringify([offset, padding]); | ||
| if (string !== lastLayoutJson.current) { | ||
| lastLayoutJson.current = string; | ||
| onLayout({ | ||
| offset: sidesToOffset(offset), | ||
| padding: sidesToPadding(padding) | ||
| }); | ||
| } | ||
| }) | ||
| .catch(() => { }); | ||
| }, [onLayout]); | ||
| // Subscribe to keyboard changes: | ||
| React.useEffect(() => { | ||
| const handleKeyboard = event => { | ||
| const screen = Dimensions.get('screen'); | ||
| keyboardHeight.current = Math.min( | ||
| // These two give different results sometimes, so pick the smaller one: | ||
| screen.height - event.endCoordinates.screenY, event.endCoordinates.height); | ||
| if (event.duration > 0) { | ||
| Keyboard.scheduleLayoutAnimation(event); | ||
| } | ||
| handleLayout(); | ||
| }; | ||
| if (Platform.OS === 'android') { | ||
| Keyboard.addListener('keyboardDidShow', handleKeyboard); | ||
| Keyboard.addListener('keyboardDidHide', handleKeyboard); | ||
| } | ||
| else { | ||
| Keyboard.addListener('keyboardWillChangeFrame', handleKeyboard); | ||
| } | ||
| return () => { | ||
| if (Platform.OS === 'android') { | ||
| Keyboard.removeListener('keyboardDidShow', handleKeyboard); | ||
| Keyboard.removeListener('keyboardDidHide', handleKeyboard); | ||
| } | ||
| else { | ||
| Keyboard.removeListener('keyboardWillChangeFrame', handleKeyboard); | ||
| } | ||
| }; | ||
| }, [handleLayout]); | ||
| if (Platform.OS === 'android') { | ||
| return (React.createElement(View, { ref: view, onLayout: handleLayout, pointerEvents: "none", style: StyleSheet.absoluteFill, testID: "AirshipBarometer" }, children)); | ||
| } | ||
| return (React.createElement(SafeAreaView, { ref: view, onLayout: handleLayout, pointerEvents: "none", style: StyleSheet.absoluteFill, testID: "AirshipBarometer" }, | ||
| React.createElement(View, { ref: childView, style: { flex: 1 }, testID: "AirshipBarometerChild" }, children))); | ||
| } |
| export { Unsubscribe } from 'yavent'; | ||
| export { makeAirship } from './components/Airship'; | ||
| export { AirshipDropdown, AirshipDropdownProps } from './components/AirshipDropdown'; | ||
| export { AirshipModal, AirshipModalProps } from './components/AirshipModal'; | ||
| export { AirshipToast, AirshipToastProps } from './components/AirshipToast'; | ||
| export { Airship, AirshipBridge } from './types'; |
| export { makeAirship } from './components/Airship'; | ||
| export { AirshipDropdown } from './components/AirshipDropdown'; | ||
| export { AirshipModal } from './components/AirshipModal'; | ||
| export { AirshipToast } from './components/AirshipToast'; |
Sorry, the diff of this file is not supported yet
| import * as React from 'react'; | ||
| import { OnEvents } from 'yavent'; | ||
| export interface AirshipEvents { | ||
| result: undefined; | ||
| clear: undefined; | ||
| } | ||
| /** | ||
| * Control panel for managing a component inside an airship. | ||
| */ | ||
| export interface AirshipBridge<T> { | ||
| resolve: (value: T | PromiseLike<T>) => void; | ||
| reject: (error: Error) => void; | ||
| remove: () => void; | ||
| on: OnEvents<AirshipEvents>; | ||
| onResult: (callback: () => unknown) => void; | ||
| } | ||
| /** | ||
| * Renders a component to place inside the airship. | ||
| */ | ||
| export declare type AirshipRender<T> = (bridge: AirshipBridge<T>) => React.ReactNode; | ||
| /** | ||
| * Props the Airship container component accepts. | ||
| */ | ||
| export interface AirshipProps { | ||
| children?: React.ReactNode; | ||
| } | ||
| /** | ||
| * The Airship itself is a component you should mount after your main | ||
| * scene or router. | ||
| * | ||
| * It has a static method anyone can call to display components. | ||
| * The method returns a promise, which the component can use to pass values | ||
| * to the outside world. | ||
| */ | ||
| export interface Airship extends React.FunctionComponent<AirshipProps> { | ||
| clear: () => void; | ||
| show: <T>(render: AirshipRender<T>) => Promise<T>; | ||
| } |
| export {}; |
| /** | ||
| * The four sides (top, right, bottom, left) as a tuple. | ||
| */ | ||
| export declare type SideList = [number, number, number, number]; | ||
| export interface Margin { | ||
| marginBottom: number; | ||
| marginLeft: number; | ||
| marginRight: number; | ||
| marginTop: number; | ||
| } | ||
| export interface Offset { | ||
| bottom: number; | ||
| left: number; | ||
| right: number; | ||
| top: number; | ||
| } | ||
| export interface Padding { | ||
| paddingBottom: number; | ||
| paddingLeft: number; | ||
| paddingRight: number; | ||
| paddingTop: number; | ||
| } | ||
| /** | ||
| * Interprets an array of 0-4 numbers as a web CSS sides shorthand | ||
| * (top, right, bottom, left). | ||
| */ | ||
| export declare function fixSides(sides: number[] | number | undefined, fallback: number): SideList; | ||
| export declare function addSides(a: SideList, b: SideList): SideList; | ||
| export declare function subtractSides(a: SideList, b: SideList): SideList; | ||
| export declare function mapSides(sides: SideList, f: (side: number) => number): SideList; | ||
| /** | ||
| * Turns a list of sides into CSS margin properties. | ||
| */ | ||
| export declare function sidesToMargin(sides: SideList): Margin; | ||
| /** | ||
| * Turns a list of sides into CSS positioning properties. | ||
| */ | ||
| export declare function sidesToOffset(sides: SideList): Offset; | ||
| /** | ||
| * Turns a list of sides into CSS padding properties. | ||
| */ | ||
| export declare function sidesToPadding(sides: SideList): Padding; |
| /** | ||
| * Interprets an array of 0-4 numbers as a web CSS sides shorthand | ||
| * (top, right, bottom, left). | ||
| */ | ||
| export function fixSides(sides, fallback) { | ||
| var _a, _b, _c, _d; | ||
| if (sides == null) { | ||
| return [fallback, fallback, fallback, fallback]; | ||
| } | ||
| if (typeof sides === 'number') { | ||
| return [sides, sides, sides, sides]; | ||
| } | ||
| const top = (_a = sides[0]) !== null && _a !== void 0 ? _a : fallback; | ||
| const right = (_b = sides[1]) !== null && _b !== void 0 ? _b : top; | ||
| const bottom = (_c = sides[2]) !== null && _c !== void 0 ? _c : top; | ||
| const left = (_d = sides[3]) !== null && _d !== void 0 ? _d : right; | ||
| return [top, right, bottom, left]; | ||
| } | ||
| export function addSides(a, b) { | ||
| return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]]; | ||
| } | ||
| export function subtractSides(a, b) { | ||
| return [a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]]; | ||
| } | ||
| export function mapSides(sides, f) { | ||
| return [f(sides[0]), f(sides[1]), f(sides[2]), f(sides[3])]; | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS margin properties. | ||
| */ | ||
| export function sidesToMargin(sides) { | ||
| return { | ||
| marginTop: sides[0], | ||
| marginRight: sides[1], | ||
| marginBottom: sides[2], | ||
| marginLeft: sides[3] | ||
| }; | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS positioning properties. | ||
| */ | ||
| export function sidesToOffset(sides) { | ||
| return { | ||
| top: sides[0], | ||
| right: sides[1], | ||
| bottom: sides[2], | ||
| left: sides[3] | ||
| }; | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS padding properties. | ||
| */ | ||
| export function sidesToPadding(sides) { | ||
| return { | ||
| paddingTop: sides[0], | ||
| paddingRight: sides[1], | ||
| paddingBottom: sides[2], | ||
| paddingLeft: sides[3] | ||
| }; | ||
| } |
+15
-8
| # react-native-airship | ||
| ## 0.2.5 | ||
| ## 0.2.6 (2021-07-15) | ||
| - fix: Make the Flow `Airship` type work like the Typescript version. | ||
| - feature: Add optional shadow properties to the `AirshipModal`: | ||
| - `shadowOffset` | ||
| - `shadowOpacity` | ||
| - `shadowRadius` | ||
| ## 0.2.4 | ||
| ## 0.2.5 (2021-04-15) | ||
| - fix: Make the `Airship` Flow type work like the Typescript version. | ||
| ## 0.2.4 (2021-03-25) | ||
| - fix: Do not crash when calling `Airship.clear` recursively. | ||
| ## 0.2.3 | ||
| ## 0.2.3 (2021-01-23) | ||
@@ -17,15 +24,15 @@ - fix: Measure the screen by mounting a test component and seeing where it lands, avoiding the need for various properties to control the layout. If the status bar is translucent, or if they keyboard is `adjustPan` mode on Android, we can automatically determine that now and do the right thing. | ||
| ## 0.2.2 | ||
| ## 0.2.2 (2020-09-03) | ||
| - feature: Add an `Airship.clear` method, which calls any callbacks registered with `bridge.on('clear')`. | ||
| - feature: Add a `bridge.on('result')` method to replace `bridge.onResult`. | ||
| - fix: Add some missing Flow type defitions. | ||
| - fix: Add some missing Flow type definitions. | ||
| - fix: Make the Typescript definitions work better with strict mode. | ||
| - deprecated: `bridge.onResult` | ||
| ## 0.2.1 | ||
| ## 0.2.1 (2020-08-11) | ||
| - Fix various Flow & documentation issues from the previous release. | ||
| ## 0.2.0 | ||
| ## 0.2.0(2020-08-07) | ||
@@ -32,0 +39,0 @@ With this version, the demo components become an official part of the library. The old `react-native-airship/demos` entry point has gone away, so you can import `AirshipDropdown`, `AirshipModal`, and `AirshipToast` directly from `react-native-airship` now. |
+9
-14
| { | ||
| "name": "react-native-airship", | ||
| "version": "0.2.5", | ||
| "version": "0.2.6", | ||
| "private": false, | ||
@@ -15,4 +15,4 @@ "description": "Flexible toolkit for building modals & alerts", | ||
| "author": "William Swanson <swansontec@gmail.com>", | ||
| "main": "lib/src/index.js", | ||
| "types": "lib/src/index.d.ts", | ||
| "main": "lib/index.js", | ||
| "types": "lib/index.d.ts", | ||
| "files": [ | ||
@@ -22,18 +22,13 @@ "/CHANGELOG.md", | ||
| "/package.json", | ||
| "/README.md", | ||
| "/src/*" | ||
| "/README.md" | ||
| ], | ||
| "scripts": { | ||
| "build.flow": "flow", | ||
| "build.lib": "tsc && cp src/index.flow.js lib/src/index.js.flow && rimraf lib/AirshipDemo", | ||
| "build.lib": "tsc && cp src/index.flow.js lib/index.js.flow", | ||
| "clean": "rimraf lib", | ||
| "fix": "npm run lint -- --fix", | ||
| "lint": "eslint .", | ||
| "prepare": "npm-run-all clean -p build.*" | ||
| "precommit": "lint-staged && run-p build.* && (cd AirshipDemo; tsc)", | ||
| "prepare": "husky install && npm-run-all clean -p build.*" | ||
| }, | ||
| "husky": { | ||
| "hooks": { | ||
| "pre-commit": "lint-staged && run-p build.*" | ||
| } | ||
| }, | ||
| "lint-staged": { | ||
@@ -46,3 +41,3 @@ "*.{js,jsx,ts,tsx}": "eslint" | ||
| "devDependencies": { | ||
| "@types/react": "^16.9.43", | ||
| "@types/react": "^17.0.14", | ||
| "@types/react-native": "^0.63.2", | ||
@@ -62,3 +57,3 @@ "@typescript-eslint/eslint-plugin": "^4.8.2", | ||
| "flow-bin": "^0.132.0", | ||
| "husky": "^4.3.0", | ||
| "husky": "^7.0.0", | ||
| "lint-staged": "^10.5.3", | ||
@@ -65,0 +60,0 @@ "npm-run-all": "^4.1.5", |
| import { Airship } from '../types'; | ||
| /** | ||
| * Constructs an Airship component. | ||
| */ | ||
| export declare function makeAirship(): Airship; |
| var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
| function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
| return new (P || (P = Promise))(function (resolve, reject) { | ||
| function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
| function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
| function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
| step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
| }); | ||
| }; | ||
| import * as React from 'react'; | ||
| import { View } from 'react-native'; | ||
| import { makeEvent, makeEvents } from 'yavent'; | ||
| import { sidesToOffset, sidesToPadding } from '../util/sides'; | ||
| import { Barometer } from './Barometer'; | ||
| const emptyLayout = { | ||
| offset: sidesToOffset([0, 0, 0, 0]), | ||
| padding: sidesToPadding([0, 0, 0, 0]) | ||
| }; | ||
| /** | ||
| * Constructs an Airship component. | ||
| */ | ||
| export function makeAirship() { | ||
| // Static state shared by all mounted containers: | ||
| const [onClear, emitClear] = makeEvent(); | ||
| const [onGuestsChange, emitGuestsChange] = makeEvent(); | ||
| let guests = []; | ||
| let nextKey = 0; | ||
| const AirshipHost = (props) => { | ||
| const { children } = props; | ||
| // Watch the common guest list: | ||
| const [ourGuests, setGuests] = React.useState(guests); | ||
| React.useEffect(() => onGuestsChange(setGuests), []); | ||
| // Track layout changes: | ||
| const [layout, setLayout] = React.useState(emptyLayout); | ||
| return (React.createElement(React.Fragment, null, | ||
| React.createElement(Barometer, { onLayout: setLayout }), | ||
| children, | ||
| ourGuests.map(guest => (React.createElement(View, { key: guest.key, pointerEvents: "box-none", style: Object.assign(Object.assign(Object.assign({}, layout.offset), layout.padding), { flexDirection: 'row', justifyContent: 'center', position: 'absolute' }) }, guest.element))))); | ||
| }; | ||
| let clearing = false; | ||
| function clear() { | ||
| if (clearing) | ||
| return; | ||
| clearing = true; | ||
| emitClear(undefined); | ||
| clearing = false; | ||
| } | ||
| function show(render) { | ||
| return __awaiter(this, void 0, void 0, function* () { | ||
| const key = `airship${nextKey++}`; | ||
| function remove() { | ||
| unclear(); | ||
| guests = guests.filter(guest => guest.key !== key); | ||
| emitGuestsChange(guests); | ||
| } | ||
| // Assemble the bridge: | ||
| const [on, emit] = makeEvents(); | ||
| let bridge; | ||
| const promise = new Promise((resolve, reject) => { | ||
| bridge = { | ||
| on, | ||
| onResult: callback => on('result', callback), | ||
| reject, | ||
| remove, | ||
| resolve | ||
| }; | ||
| }); | ||
| // Hook up events: | ||
| promise.then(() => emit('result', undefined), () => emit('result', undefined)); | ||
| const unclear = onClear(() => emit('clear', undefined)); | ||
| // Save the guest element in the shared state: | ||
| guests = [...guests, { key, element: render(bridge) }]; | ||
| emitGuestsChange(guests); | ||
| return promise; | ||
| }); | ||
| } | ||
| return Object.assign(AirshipHost, { clear, show }); | ||
| } | ||
| //# sourceMappingURL=Airship.js.map |
| {"version":3,"file":"Airship.js","sourceRoot":"","sources":["../../../src/components/Airship.tsx"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,OAAO,EAAiB,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAS7D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAA;AAOxD,MAAM,WAAW,GAAoB;IACnC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;CACtC,CAAA;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,iDAAiD;IACjD,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,GAAgB,SAAS,EAAE,CAAA;IACrD,MAAM,CAAC,cAAc,EAAE,gBAAgB,CAAC,GAAmB,SAAS,EAAE,CAAA;IACtE,IAAI,MAAM,GAAY,EAAE,CAAA;IACxB,IAAI,OAAO,GAAW,CAAC,CAAA;IAEvB,MAAM,WAAW,GAAG,CAAC,KAAmB,EAAe,EAAE;QACvD,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAA;QAE1B,+BAA+B;QAC/B,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACrD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAA;QAEpD,wBAAwB;QACxB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;QAEvD,OAAO,CACL;YACE,oBAAC,SAAS,IAAC,QAAQ,EAAE,SAAS,GAAI;YACjC,QAAQ;YACR,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CACtB,oBAAC,IAAI,IACH,GAAG,EAAE,KAAK,CAAC,GAAG,EACd,aAAa,EAAC,UAAU,EACxB,KAAK,gDACA,MAAM,CAAC,MAAM,GACb,MAAM,CAAC,OAAO,KACjB,aAAa,EAAE,KAAK,EACpB,cAAc,EAAE,QAAQ,EACxB,QAAQ,EAAE,UAAU,OAGrB,KAAK,CAAC,OAAO,CACT,CACR,CAAC,CACD,CACJ,CAAA;IACH,CAAC,CAAA;IAED,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,SAAS,KAAK;QACZ,IAAI,QAAQ;YAAE,OAAM;QACpB,QAAQ,GAAG,IAAI,CAAA;QACf,SAAS,CAAC,SAAS,CAAC,CAAA;QACpB,QAAQ,GAAG,KAAK,CAAA;IAClB,CAAC;IAED,SAAe,IAAI,CAAI,MAAwB;;YAC7C,MAAM,GAAG,GAAG,UAAU,OAAO,EAAE,EAAE,CAAA;YAEjC,SAAS,MAAM;gBACb,OAAO,EAAE,CAAA;gBACT,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC,CAAA;gBAClD,gBAAgB,CAAC,MAAM,CAAC,CAAA;YAC1B,CAAC;YAED,uBAAuB;YACvB,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,GAA0B,UAAU,EAAE,CAAA;YACtD,IAAI,MAAyB,CAAA;YAC7B,MAAM,OAAO,GAAe,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1D,MAAM,GAAG;oBACP,EAAE;oBACF,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;oBAC5C,MAAM;oBACN,MAAM;oBACN,OAAO;iBACR,CAAA;YACH,CAAC,CAAC,CAAA;YAEF,kBAAkB;YAClB,OAAO,CAAC,IAAI,CACV,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,EAC/B,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAChC,CAAA;YACD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAA;YAEvD,8CAA8C;YAC9C,MAAM,GAAG,CAAC,GAAG,MAAM,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YACtD,gBAAgB,CAAC,MAAM,CAAC,CAAA;YACxB,OAAO,OAAO,CAAA;QAChB,CAAC;KAAA;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACpD,CAAC"} |
| import * as React from 'react'; | ||
| import { ViewStyle } from 'react-native'; | ||
| import { AirshipBridge } from '../types'; | ||
| export interface AirshipDropdownProps { | ||
| bridge: AirshipBridge<undefined>; | ||
| children?: React.ReactNode; | ||
| onPress?: () => void; | ||
| autoHideMs?: number; | ||
| backgroundColor?: string; | ||
| borderRadius?: number; | ||
| flexDirection?: ViewStyle['flexDirection']; | ||
| justifyContent?: ViewStyle['justifyContent']; | ||
| margin?: number | number[]; | ||
| maxHeight?: number; | ||
| maxWidth?: number; | ||
| padding?: number | number[]; | ||
| slideInMs?: number; | ||
| slideOutMs?: number; | ||
| } | ||
| /** | ||
| * A notification that slides down from the top of the screen. | ||
| */ | ||
| export declare function AirshipDropdown(props: AirshipDropdownProps): JSX.Element; |
| import * as React from 'react'; | ||
| import { Animated, Dimensions, TouchableWithoutFeedback } from 'react-native'; | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides'; | ||
| const safeAreaGap = 64; | ||
| /** | ||
| * A notification that slides down from the top of the screen. | ||
| */ | ||
| export function AirshipDropdown(props) { | ||
| const { bridge, children, onPress = () => bridge.resolve(undefined), autoHideMs = 5000, backgroundColor = 'white', borderRadius = 4, flexDirection, justifyContent, maxHeight = defaultMaxHeight(), maxWidth = 512, slideInMs = 300, slideOutMs = 500 } = props; | ||
| const margin = sidesToMargin(fixSides(props.margin, 0)); | ||
| const padding = sidesToPadding(fixSides(props.padding, 0)); | ||
| const hiddenOffset = -(maxHeight + margin.marginBottom); | ||
| margin.marginTop = -safeAreaGap; | ||
| padding.paddingTop += safeAreaGap; | ||
| // Create the animation: | ||
| const offset = React.useRef(new Animated.Value(hiddenOffset)).current; | ||
| React.useEffect(() => { | ||
| let timeout; | ||
| // Animate in: | ||
| Animated.timing(offset, { | ||
| toValue: 0, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }).start(() => { | ||
| // Start the auto-hide timer: | ||
| if (autoHideMs > 0) { | ||
| timeout = setTimeout(() => { | ||
| timeout = undefined; | ||
| bridge.resolve(undefined); | ||
| }, autoHideMs); | ||
| } | ||
| }); | ||
| // Animate out: | ||
| bridge.on('clear', () => bridge.resolve(undefined)); | ||
| bridge.on('result', () => { | ||
| Animated.timing(offset, { | ||
| toValue: hiddenOffset, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }).start(() => bridge.remove()); | ||
| }); | ||
| return () => { | ||
| if (timeout != null) | ||
| clearTimeout(timeout); | ||
| }; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []); | ||
| const bodyStyle = Object.assign(Object.assign(Object.assign({}, margin), padding), { alignSelf: 'flex-start', backgroundColor, borderBottomLeftRadius: borderRadius, borderBottomRightRadius: borderRadius, flexDirection, flexShrink: 1, justifyContent, | ||
| maxHeight, shadowOffset: { height: 0, width: 0 }, shadowOpacity: 1, shadowRadius: 4, transform: [{ translateY: offset }], width: maxWidth // This works because flexShrink is set | ||
| }); | ||
| return (React.createElement(TouchableWithoutFeedback, { onPress: onPress }, | ||
| React.createElement(Animated.View, { style: bodyStyle }, children))); | ||
| } | ||
| function defaultMaxHeight() { | ||
| const { width, height } = Dimensions.get('screen'); | ||
| return 0.25 * Math.max(width, height); | ||
| } | ||
| //# sourceMappingURL=AirshipDropdown.js.map |
| {"version":3,"file":"AirshipDropdown.js","sourceRoot":"","sources":["../../../src/components/AirshipDropdown.tsx"],"names":[],"mappings":"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"} |
| import * as React from 'react'; | ||
| import { ViewStyle } from 'react-native'; | ||
| import { AirshipBridge } from '../types'; | ||
| export interface AirshipModalProps<T = unknown> { | ||
| bridge: AirshipBridge<T>; | ||
| children?: React.ReactNode; | ||
| onCancel: () => void; | ||
| center?: boolean; | ||
| backgroundColor?: string; | ||
| borderRadius?: number; | ||
| flexDirection?: ViewStyle['flexDirection']; | ||
| justifyContent?: ViewStyle['justifyContent']; | ||
| margin?: number | number[]; | ||
| maxHeight?: number; | ||
| maxWidth?: number; | ||
| padding?: number | number[]; | ||
| slideInMs?: number; | ||
| slideOutMs?: number; | ||
| underlay?: string | React.ReactElement; | ||
| } | ||
| /** | ||
| * A modal that slides a modal up from the bottom of the screen | ||
| * and dims the rest of the app. | ||
| */ | ||
| export declare function AirshipModal<T>(props: AirshipModalProps<T>): JSX.Element; |
| import * as React from 'react'; | ||
| import { Animated, BackHandler, Dimensions, TouchableWithoutFeedback } from 'react-native'; | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides'; | ||
| const safeAreaGap = 64; | ||
| /** | ||
| * A modal that slides a modal up from the bottom of the screen | ||
| * and dims the rest of the app. | ||
| */ | ||
| export function AirshipModal(props) { | ||
| const { bridge, children, onCancel, backgroundColor = 'white', borderRadius = 10, center = false, flexDirection, justifyContent, maxHeight, maxWidth = 512, slideInMs = 300, slideOutMs = 300, underlay = 'rgba(0, 0, 0, 0.75)' } = props; | ||
| const margin = sidesToMargin(fixSides(props.margin, 0)); | ||
| const padding = sidesToPadding(fixSides(props.padding, 0)); | ||
| React.useEffect(() => bridge.on('clear', onCancel), [bridge, onCancel]); | ||
| // Create the animations: | ||
| const offset = React.useRef(new Animated.Value(Dimensions.get('window').height)).current; | ||
| const opacity = React.useRef(new Animated.Value(0)).current; | ||
| React.useEffect(() => { | ||
| // Animate in: | ||
| Animated.parallel([ | ||
| Animated.timing(opacity, { | ||
| toValue: 1, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }), | ||
| Animated.timing(offset, { | ||
| toValue: 0, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }) | ||
| ]).start(); | ||
| // Animate out: | ||
| bridge.on('result', () => { | ||
| Animated.parallel([ | ||
| Animated.timing(opacity, { | ||
| toValue: 0, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }), | ||
| Animated.timing(offset, { | ||
| toValue: Dimensions.get('window').height, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }) | ||
| ]).start(bridge.remove); | ||
| }); | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []); | ||
| // Set up the back-button handler: | ||
| React.useEffect(() => { | ||
| const backHandler = BackHandler.addEventListener('hardwareBackPress', () => { | ||
| onCancel(); | ||
| return true; | ||
| }); | ||
| return () => backHandler.remove(); | ||
| }, [onCancel]); | ||
| const underlayStyle = { | ||
| backgroundColor: typeof underlay === 'string' ? underlay : 'transparent', | ||
| bottom: 0, | ||
| left: 0, | ||
| opacity: opacity, | ||
| position: 'absolute', | ||
| right: 0, | ||
| top: 0 | ||
| }; | ||
| const bodyCommon = Object.assign(Object.assign(Object.assign({}, margin), padding), { alignSelf: center ? 'center' : 'flex-end', backgroundColor, | ||
| flexDirection, flexShrink: 1, justifyContent, | ||
| maxHeight, shadowOffset: { height: 0, width: 0 }, shadowOpacity: 1, shadowRadius: 10, transform: [{ translateY: offset }], width: maxWidth // This works because flexShrink is set | ||
| }); | ||
| const bodyStyle = center | ||
| ? Object.assign(Object.assign({}, bodyCommon), { borderRadius }) : Object.assign(Object.assign({}, bodyCommon), { borderTopLeftRadius: borderRadius, borderTopRightRadius: borderRadius, marginBottom: -safeAreaGap, paddingBottom: padding.paddingBottom + safeAreaGap }); | ||
| return (React.createElement(React.Fragment, null, | ||
| React.createElement(TouchableWithoutFeedback, { onPress: () => onCancel() }, | ||
| React.createElement(Animated.View, { style: underlayStyle }, typeof underlay !== 'string' ? underlay : undefined)), | ||
| React.createElement(Animated.View, { style: bodyStyle }, children))); | ||
| } | ||
| //# sourceMappingURL=AirshipModal.js.map |
| {"version":3,"file":"AirshipModal.js","sourceRoot":"","sources":["../../../src/components/AirshipModal.tsx"],"names":[],"mappings":"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"} |
| import * as React from 'react'; | ||
| import { AirshipBridge } from '../types'; | ||
| export interface AirshipToastProps { | ||
| bridge: AirshipBridge<undefined>; | ||
| children?: React.ReactNode; | ||
| message?: string; | ||
| autoHideMs?: number; | ||
| backgroundColor?: string; | ||
| borderRadius?: number; | ||
| fadeInMs?: number; | ||
| fadeOutMs?: number; | ||
| margin?: number | number[]; | ||
| maxWidth?: number; | ||
| opacity?: number; | ||
| padding?: number | number[]; | ||
| textColor?: string; | ||
| textSize?: number; | ||
| } | ||
| /** | ||
| * A semi-transparent message overlay. | ||
| */ | ||
| export declare function AirshipToast(props: AirshipToastProps): JSX.Element; |
| import * as React from 'react'; | ||
| import { Animated, Text } from 'react-native'; | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides'; | ||
| /** | ||
| * A semi-transparent message overlay. | ||
| */ | ||
| export function AirshipToast(props) { | ||
| const { textSize = 14 } = props; | ||
| const { autoHideMs = 3000, backgroundColor = 'white', borderRadius = 1.5 * textSize, bridge, children, fadeInMs = 300, fadeOutMs = 1000, maxWidth = 512, opacity: finalOpacity = 0.9, message, textColor = 'black' } = props; | ||
| const margin = sidesToMargin(fixSides(props.margin, 2 * textSize)); | ||
| const padding = sidesToPadding(fixSides(props.padding, textSize)); | ||
| // Create the animation: | ||
| const opacity = React.useRef(new Animated.Value(0)).current; | ||
| React.useEffect(() => { | ||
| let timeout; | ||
| // Animate in: | ||
| Animated.timing(opacity, { | ||
| toValue: finalOpacity, | ||
| duration: fadeInMs, | ||
| useNativeDriver: true | ||
| }).start(() => { | ||
| // Start the auto-hide timer: | ||
| if (autoHideMs > 0) { | ||
| timeout = setTimeout(() => { | ||
| timeout = undefined; | ||
| bridge.resolve(undefined); | ||
| }, autoHideMs); | ||
| } | ||
| }); | ||
| // Animate out: | ||
| bridge.on('clear', () => bridge.resolve(undefined)); | ||
| bridge.on('result', () => { | ||
| Animated.timing(opacity, { | ||
| toValue: 0, | ||
| duration: fadeOutMs, | ||
| useNativeDriver: true | ||
| }).start(() => bridge.remove()); | ||
| }); | ||
| return () => { | ||
| if (timeout != null) | ||
| clearTimeout(timeout); | ||
| }; | ||
| }); | ||
| const bodyStyle = Object.assign(Object.assign(Object.assign({}, margin), padding), { alignItems: 'center', alignSelf: 'flex-end', backgroundColor, | ||
| borderRadius, flexDirection: 'row', justifyContent: 'flex-start', maxWidth, opacity: opacity }); | ||
| const textStyle = { | ||
| color: textColor, | ||
| flexShrink: 1, | ||
| fontSize: textSize, | ||
| textAlign: 'center' | ||
| }; | ||
| return (React.createElement(Animated.View, { style: bodyStyle }, | ||
| message != null ? React.createElement(Text, { style: textStyle }, message) : null, | ||
| children)); | ||
| } | ||
| //# sourceMappingURL=AirshipToast.js.map |
| {"version":3,"file":"AirshipToast.js","sourceRoot":"","sources":["../../../src/components/AirshipToast.tsx"],"names":[],"mappings":"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"} |
| import * as React from 'react'; | ||
| import { Offset, Padding } from '../util/sides'; | ||
| export interface BarometerLayout { | ||
| offset: Offset; | ||
| padding: Padding; | ||
| } | ||
| interface Props { | ||
| children?: React.ReactNode; | ||
| onLayout?: (layout: BarometerLayout) => void; | ||
| } | ||
| /** | ||
| * Measures various things about the Airship environment, | ||
| * so we know how to position our children. | ||
| * | ||
| * This component mounts a view with absolute positioning, | ||
| * and then measures that view relative to the window. | ||
| * If a side is inset from the window edge, we use a negative offset | ||
| * to expand it outward. If a side extends beyond the window edge, | ||
| * we use padding to push the content inward. | ||
| * | ||
| * On iOS, we also mount a child inside a SafeAreaView, to measure | ||
| * the safe area insets. We add these to the padding. | ||
| * | ||
| * Finally, we keep track of the keyboard, adding extra padding & | ||
| * scheduling animations as needed. | ||
| */ | ||
| export declare function Barometer(props: Props): JSX.Element; | ||
| export {}; |
| import * as React from 'react'; | ||
| import { Dimensions, Keyboard, Platform, SafeAreaView, StyleSheet, View } from 'react-native'; | ||
| import { addSides, mapSides, sidesToOffset, sidesToPadding, subtractSides } from '../util/sides'; | ||
| const emptySides = [0, 0, 0, 0]; | ||
| /** | ||
| * Measures various things about the Airship environment, | ||
| * so we know how to position our children. | ||
| * | ||
| * This component mounts a view with absolute positioning, | ||
| * and then measures that view relative to the window. | ||
| * If a side is inset from the window edge, we use a negative offset | ||
| * to expand it outward. If a side extends beyond the window edge, | ||
| * we use padding to push the content inward. | ||
| * | ||
| * On iOS, we also mount a child inside a SafeAreaView, to measure | ||
| * the safe area insets. We add these to the padding. | ||
| * | ||
| * Finally, we keep track of the keyboard, adding extra padding & | ||
| * scheduling animations as needed. | ||
| */ | ||
| export function Barometer(props) { | ||
| const { children, onLayout = () => { } } = props; | ||
| // Mutable state: | ||
| const keyboardHeight = React.useRef(0); | ||
| const lastLayoutJson = React.useRef(''); | ||
| const view = React.useRef(null); | ||
| const childView = React.useRef(null); | ||
| // Handle layout changes: | ||
| const handleLayout = React.useCallback(() => { | ||
| // Measure the view in the window: | ||
| const viewPromise = new Promise(resolve => { | ||
| if (view.current == null) | ||
| return resolve(emptySides); | ||
| view.current.measureInWindow((x, y, width, height) => { | ||
| const window = Dimensions.get('window'); | ||
| resolve([y, window.width - width - x, window.height - height - y, x]); | ||
| }); | ||
| }); | ||
| // Measure the child view in the window: | ||
| const childPromise = new Promise(resolve => { | ||
| if (childView.current == null) | ||
| return resolve(viewPromise); | ||
| childView.current.measureInWindow((x, y, width, height) => { | ||
| const window = Dimensions.get('window'); | ||
| resolve([y, window.width - width - x, window.height - height - y, x]); | ||
| }); | ||
| }); | ||
| // Measure the gap between the bottom of the screen and the view: | ||
| const bottomPromise = new Promise(resolve => { | ||
| if (view.current == null) | ||
| return 0; | ||
| view.current.measure((x, y, width, height, screenX, screenY) => { | ||
| const screen = Dimensions.get('screen'); | ||
| resolve(screen.height - height - screenY); | ||
| }); | ||
| }); | ||
| // Combine the results, then call the callback: | ||
| Promise.all([viewPromise, childPromise, bottomPromise]) | ||
| .then(([viewOffset, childOffset, bottomGap]) => { | ||
| // Cancel out any offset, so we cover the full window: | ||
| const offset = mapSides(viewOffset, side => -Math.max(side, 0)); | ||
| // If the offset is negative, issue positive padding, | ||
| // plus any safe area: | ||
| const safePadding = subtractSides(childOffset, viewOffset); | ||
| const padding = addSides(safePadding, mapSides(viewOffset, side => Math.abs(side))); | ||
| // Use the keyboard padding, if needed: | ||
| const keyboardPadding = Math.max(keyboardHeight.current - bottomGap - offset[2], 0); | ||
| padding[2] = Math.max(padding[2], keyboardPadding); | ||
| // Send an update if we have changes: | ||
| const string = JSON.stringify([offset, padding]); | ||
| if (string !== lastLayoutJson.current) { | ||
| lastLayoutJson.current = string; | ||
| onLayout({ | ||
| offset: sidesToOffset(offset), | ||
| padding: sidesToPadding(padding) | ||
| }); | ||
| } | ||
| }) | ||
| .catch(() => { }); | ||
| }, [onLayout]); | ||
| // Subscribe to keyboard changes: | ||
| React.useEffect(() => { | ||
| const handleKeyboard = event => { | ||
| const screen = Dimensions.get('screen'); | ||
| keyboardHeight.current = Math.min( | ||
| // These two give different results sometimes, so pick the smaller one: | ||
| screen.height - event.endCoordinates.screenY, event.endCoordinates.height); | ||
| if (event.duration > 0) { | ||
| // @ts-expect-error | ||
| Keyboard.scheduleLayoutAnimation(event); | ||
| } | ||
| handleLayout(); | ||
| }; | ||
| if (Platform.OS === 'android') { | ||
| Keyboard.addListener('keyboardDidShow', handleKeyboard); | ||
| Keyboard.addListener('keyboardDidHide', handleKeyboard); | ||
| } | ||
| else { | ||
| Keyboard.addListener('keyboardWillChangeFrame', handleKeyboard); | ||
| } | ||
| return () => { | ||
| if (Platform.OS === 'android') { | ||
| Keyboard.removeListener('keyboardDidShow', handleKeyboard); | ||
| Keyboard.removeListener('keyboardDidHide', handleKeyboard); | ||
| } | ||
| else { | ||
| Keyboard.removeListener('keyboardWillChangeFrame', handleKeyboard); | ||
| } | ||
| }; | ||
| }, [handleLayout]); | ||
| if (Platform.OS === 'android') { | ||
| return (React.createElement(View, { ref: view, onLayout: handleLayout, pointerEvents: "none", style: StyleSheet.absoluteFill, testID: "AirshipBarometer" }, children)); | ||
| } | ||
| return (React.createElement(SafeAreaView, { ref: view, onLayout: handleLayout, pointerEvents: "none", style: StyleSheet.absoluteFill, testID: "AirshipBarometer" }, | ||
| React.createElement(View, { ref: childView, style: { flex: 1 }, testID: "AirshipBarometerChild" }, children))); | ||
| } | ||
| //# sourceMappingURL=Barometer.js.map |
| {"version":3,"file":"Barometer.js","sourceRoot":"","sources":["../../../src/components/Barometer.tsx"],"names":[],"mappings":"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"} |
| export { Unsubscribe } from 'yavent'; | ||
| export { makeAirship } from './components/Airship'; | ||
| export { AirshipDropdown, AirshipDropdownProps } from './components/AirshipDropdown'; | ||
| export { AirshipModal, AirshipModalProps } from './components/AirshipModal'; | ||
| export { AirshipToast, AirshipToastProps } from './components/AirshipToast'; | ||
| export { Airship, AirshipBridge } from './types'; |
| export { makeAirship } from './components/Airship'; | ||
| export { AirshipDropdown } from './components/AirshipDropdown'; | ||
| export { AirshipModal } from './components/AirshipModal'; | ||
| export { AirshipToast } from './components/AirshipToast'; | ||
| //# sourceMappingURL=index.js.map |
Sorry, the diff of this file is not supported yet
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EACL,eAAe,EAEhB,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,YAAY,EAAqB,MAAM,2BAA2B,CAAA;AAC3E,OAAO,EAAE,YAAY,EAAqB,MAAM,2BAA2B,CAAA"} |
| import * as React from 'react'; | ||
| import { OnEvents } from 'yavent'; | ||
| export interface AirshipEvents { | ||
| result: undefined; | ||
| clear: undefined; | ||
| } | ||
| /** | ||
| * Control panel for managing a component inside an airship. | ||
| */ | ||
| export interface AirshipBridge<T> { | ||
| resolve: (value: T | PromiseLike<T>) => void; | ||
| reject: (error: Error) => void; | ||
| remove: () => void; | ||
| on: OnEvents<AirshipEvents>; | ||
| onResult: (callback: () => unknown) => void; | ||
| } | ||
| /** | ||
| * Renders a component to place inside the airship. | ||
| */ | ||
| export declare type AirshipRender<T> = (bridge: AirshipBridge<T>) => React.ReactNode; | ||
| /** | ||
| * Props the Airship container component accepts. | ||
| */ | ||
| export interface AirshipProps { | ||
| children?: React.ReactNode; | ||
| } | ||
| /** | ||
| * The Airship itself is a component you should mount after your main | ||
| * scene or router. | ||
| * | ||
| * It has a static method anyone can call to display components. | ||
| * The method returns a promise, which the component can use to pass values | ||
| * to the outside world. | ||
| */ | ||
| export interface Airship extends React.FunctionComponent<AirshipProps> { | ||
| clear: () => void; | ||
| show: <T>(render: AirshipRender<T>) => Promise<T>; | ||
| } |
| export {}; | ||
| //# sourceMappingURL=types.js.map |
| {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""} |
| /** | ||
| * The four sides (top, right, bottom, left) as a tuple. | ||
| */ | ||
| export declare type SideList = [number, number, number, number]; | ||
| export interface Margin { | ||
| marginBottom: number; | ||
| marginLeft: number; | ||
| marginRight: number; | ||
| marginTop: number; | ||
| } | ||
| export interface Offset { | ||
| bottom: number; | ||
| left: number; | ||
| right: number; | ||
| top: number; | ||
| } | ||
| export interface Padding { | ||
| paddingBottom: number; | ||
| paddingLeft: number; | ||
| paddingRight: number; | ||
| paddingTop: number; | ||
| } | ||
| /** | ||
| * Interprets an array of 0-4 numbers as a web CSS sides shorthand | ||
| * (top, right, bottom, left). | ||
| */ | ||
| export declare function fixSides(sides: number[] | number | undefined, fallback: number): SideList; | ||
| export declare function addSides(a: SideList, b: SideList): SideList; | ||
| export declare function subtractSides(a: SideList, b: SideList): SideList; | ||
| export declare function mapSides(sides: SideList, f: (side: number) => number): SideList; | ||
| /** | ||
| * Turns a list of sides into CSS margin properties. | ||
| */ | ||
| export declare function sidesToMargin(sides: SideList): Margin; | ||
| /** | ||
| * Turns a list of sides into CSS positioning properties. | ||
| */ | ||
| export declare function sidesToOffset(sides: SideList): Offset; | ||
| /** | ||
| * Turns a list of sides into CSS padding properties. | ||
| */ | ||
| export declare function sidesToPadding(sides: SideList): Padding; |
| /** | ||
| * Interprets an array of 0-4 numbers as a web CSS sides shorthand | ||
| * (top, right, bottom, left). | ||
| */ | ||
| export function fixSides(sides, fallback) { | ||
| var _a, _b, _c, _d; | ||
| if (sides == null) { | ||
| return [fallback, fallback, fallback, fallback]; | ||
| } | ||
| if (typeof sides === 'number') { | ||
| return [sides, sides, sides, sides]; | ||
| } | ||
| const top = (_a = sides[0]) !== null && _a !== void 0 ? _a : fallback; | ||
| const right = (_b = sides[1]) !== null && _b !== void 0 ? _b : top; | ||
| const bottom = (_c = sides[2]) !== null && _c !== void 0 ? _c : top; | ||
| const left = (_d = sides[3]) !== null && _d !== void 0 ? _d : right; | ||
| return [top, right, bottom, left]; | ||
| } | ||
| export function addSides(a, b) { | ||
| return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]]; | ||
| } | ||
| export function subtractSides(a, b) { | ||
| return [a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]]; | ||
| } | ||
| export function mapSides(sides, f) { | ||
| return [f(sides[0]), f(sides[1]), f(sides[2]), f(sides[3])]; | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS margin properties. | ||
| */ | ||
| export function sidesToMargin(sides) { | ||
| return { | ||
| marginTop: sides[0], | ||
| marginRight: sides[1], | ||
| marginBottom: sides[2], | ||
| marginLeft: sides[3] | ||
| }; | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS positioning properties. | ||
| */ | ||
| export function sidesToOffset(sides) { | ||
| return { | ||
| top: sides[0], | ||
| right: sides[1], | ||
| bottom: sides[2], | ||
| left: sides[3] | ||
| }; | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS padding properties. | ||
| */ | ||
| export function sidesToPadding(sides) { | ||
| return { | ||
| paddingTop: sides[0], | ||
| paddingRight: sides[1], | ||
| paddingBottom: sides[2], | ||
| paddingLeft: sides[3] | ||
| }; | ||
| } | ||
| //# sourceMappingURL=sides.js.map |
| {"version":3,"file":"sides.js","sourceRoot":"","sources":["../../../src/util/sides.ts"],"names":[],"mappings":"AA0BA;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,KAAoC,EACpC,QAAgB;;IAEhB,IAAI,KAAK,IAAI,IAAI,EAAE;QACjB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAA;KAChD;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QAC7B,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;KACpC;IAED,MAAM,GAAG,SAAG,KAAK,CAAC,CAAC,CAAC,mCAAI,QAAQ,CAAA;IAChC,MAAM,KAAK,SAAG,KAAK,CAAC,CAAC,CAAC,mCAAI,GAAG,CAAA;IAC7B,MAAM,MAAM,SAAG,KAAK,CAAC,CAAC,CAAC,mCAAI,GAAG,CAAA;IAC9B,MAAM,IAAI,SAAG,KAAK,CAAC,CAAC,CAAC,mCAAI,KAAK,CAAA;IAC9B,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAA;AACnC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,CAAW,EAAE,CAAW;IAC/C,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,CAAW,EAAE,CAAW;IACpD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,KAAe,EACf,CAA2B;IAE3B,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAe;IAC3C,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;QACnB,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;QACrB,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;QACtB,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;KACrB,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,KAAe;IAC3C,OAAO;QACL,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QACb,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACf,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;QAChB,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;KACf,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAe;IAC5C,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;QACpB,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;QACtB,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;QACvB,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;KACtB,CAAA;AACH,CAAC"} |
| import * as React from 'react' | ||
| import { View } from 'react-native' | ||
| import { Event, Events, makeEvent, makeEvents } from 'yavent' | ||
| import { | ||
| Airship, | ||
| AirshipBridge, | ||
| AirshipEvents, | ||
| AirshipProps, | ||
| AirshipRender | ||
| } from '../types' | ||
| import { sidesToOffset, sidesToPadding } from '../util/sides' | ||
| import { Barometer, BarometerLayout } from './Barometer' | ||
| interface Guest { | ||
| key: string | ||
| element: React.ReactNode | ||
| } | ||
| const emptyLayout: BarometerLayout = { | ||
| offset: sidesToOffset([0, 0, 0, 0]), | ||
| padding: sidesToPadding([0, 0, 0, 0]) | ||
| } | ||
| /** | ||
| * Constructs an Airship component. | ||
| */ | ||
| export function makeAirship(): Airship { | ||
| // Static state shared by all mounted containers: | ||
| const [onClear, emitClear]: Event<void> = makeEvent() | ||
| const [onGuestsChange, emitGuestsChange]: Event<Guest[]> = makeEvent() | ||
| let guests: Guest[] = [] | ||
| let nextKey: number = 0 | ||
| const AirshipHost = (props: AirshipProps): JSX.Element => { | ||
| const { children } = props | ||
| // Watch the common guest list: | ||
| const [ourGuests, setGuests] = React.useState(guests) | ||
| React.useEffect(() => onGuestsChange(setGuests), []) | ||
| // Track layout changes: | ||
| const [layout, setLayout] = React.useState(emptyLayout) | ||
| return ( | ||
| <> | ||
| <Barometer onLayout={setLayout} /> | ||
| {children} | ||
| {ourGuests.map(guest => ( | ||
| <View | ||
| key={guest.key} | ||
| pointerEvents="box-none" | ||
| style={{ | ||
| ...layout.offset, | ||
| ...layout.padding, | ||
| flexDirection: 'row', | ||
| justifyContent: 'center', | ||
| position: 'absolute' | ||
| }} | ||
| > | ||
| {guest.element} | ||
| </View> | ||
| ))} | ||
| </> | ||
| ) | ||
| } | ||
| let clearing = false | ||
| function clear(): void { | ||
| if (clearing) return | ||
| clearing = true | ||
| emitClear(undefined) | ||
| clearing = false | ||
| } | ||
| async function show<T>(render: AirshipRender<T>): Promise<T> { | ||
| const key = `airship${nextKey++}` | ||
| function remove(): void { | ||
| unclear() | ||
| guests = guests.filter(guest => guest.key !== key) | ||
| emitGuestsChange(guests) | ||
| } | ||
| // Assemble the bridge: | ||
| const [on, emit]: Events<AirshipEvents> = makeEvents() | ||
| let bridge!: AirshipBridge<T> | ||
| const promise: Promise<T> = new Promise((resolve, reject) => { | ||
| bridge = { | ||
| on, | ||
| onResult: callback => on('result', callback), | ||
| reject, | ||
| remove, | ||
| resolve | ||
| } | ||
| }) | ||
| // Hook up events: | ||
| promise.then( | ||
| () => emit('result', undefined), | ||
| () => emit('result', undefined) | ||
| ) | ||
| const unclear = onClear(() => emit('clear', undefined)) | ||
| // Save the guest element in the shared state: | ||
| guests = [...guests, { key, element: render(bridge) }] | ||
| emitGuestsChange(guests) | ||
| return promise | ||
| } | ||
| return Object.assign(AirshipHost, { clear, show }) | ||
| } |
| import * as React from 'react' | ||
| import { | ||
| Animated, | ||
| Dimensions, | ||
| TouchableWithoutFeedback, | ||
| ViewStyle | ||
| } from 'react-native' | ||
| import { AirshipBridge } from '../types' | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides' | ||
| export interface AirshipDropdownProps { | ||
| bridge: AirshipBridge<undefined> | ||
| children?: React.ReactNode | ||
| // Called when the user taps anywhere in the dropdown. | ||
| // Defaults to hiding the dropdown. | ||
| onPress?: () => void | ||
| // Determines how long the dropdown remains visible, | ||
| // or 0 to disable auto-hide. Defaults to 5000ms. | ||
| autoHideMs?: number | ||
| // The component color. Defaults to white. | ||
| backgroundColor?: string | ||
| // The radius to use on the bottom corners. Defaults to 4. | ||
| borderRadius?: number | ||
| // The flex direction for the contents. | ||
| flexDirection?: ViewStyle['flexDirection'] | ||
| // How to justify the contents along the flex direction. | ||
| justifyContent?: ViewStyle['justifyContent'] | ||
| // The minimum gap between the component and the screen edges. | ||
| // Takes 0-4 numbers (top, right, bottom, left), | ||
| // using the same logic as the web `margin` property. Defaults to 0. | ||
| margin?: number | number[] | ||
| // The maximum height the component will be. | ||
| // Defaults to 25% of the longest screen dimension. | ||
| maxHeight?: number | ||
| // The maximum width the component will be. | ||
| // Defaults to 512. | ||
| maxWidth?: number | ||
| // Internal padding to place inside the component. | ||
| // Takes 0-4 numbers (top, right, bottom, left), | ||
| // using the same logic as the web `padding` property. Defaults to 0. | ||
| padding?: number | number[] | ||
| // How long the entry animation should be. Defaults to 300ms. | ||
| slideInMs?: number | ||
| // How long the exit animation should be. Defaults to 500ms. | ||
| slideOutMs?: number | ||
| } | ||
| const safeAreaGap = 64 | ||
| /** | ||
| * A notification that slides down from the top of the screen. | ||
| */ | ||
| export function AirshipDropdown(props: AirshipDropdownProps): JSX.Element { | ||
| const { | ||
| bridge, | ||
| children, | ||
| onPress = () => bridge.resolve(undefined), | ||
| autoHideMs = 5000, | ||
| backgroundColor = 'white', | ||
| borderRadius = 4, | ||
| flexDirection, | ||
| justifyContent, | ||
| maxHeight = defaultMaxHeight(), | ||
| maxWidth = 512, | ||
| slideInMs = 300, | ||
| slideOutMs = 500 | ||
| } = props | ||
| const margin = sidesToMargin(fixSides(props.margin, 0)) | ||
| const padding = sidesToPadding(fixSides(props.padding, 0)) | ||
| const hiddenOffset = -(maxHeight + margin.marginBottom) | ||
| margin.marginTop = -safeAreaGap | ||
| padding.paddingTop += safeAreaGap | ||
| // Create the animation: | ||
| const offset = React.useRef(new Animated.Value(hiddenOffset)).current | ||
| React.useEffect(() => { | ||
| let timeout: ReturnType<typeof setTimeout> | undefined | ||
| // Animate in: | ||
| Animated.timing(offset, { | ||
| toValue: 0, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }).start(() => { | ||
| // Start the auto-hide timer: | ||
| if (autoHideMs > 0) { | ||
| timeout = setTimeout(() => { | ||
| timeout = undefined | ||
| bridge.resolve(undefined) | ||
| }, autoHideMs) | ||
| } | ||
| }) | ||
| // Animate out: | ||
| bridge.on('clear', () => bridge.resolve(undefined)) | ||
| bridge.on('result', () => { | ||
| Animated.timing(offset, { | ||
| toValue: hiddenOffset, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }).start(() => bridge.remove()) | ||
| }) | ||
| return () => { | ||
| if (timeout != null) clearTimeout(timeout) | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []) | ||
| const bodyStyle: ViewStyle = { | ||
| ...margin, | ||
| ...padding, | ||
| alignSelf: 'flex-start', | ||
| backgroundColor, | ||
| borderBottomLeftRadius: borderRadius, | ||
| borderBottomRightRadius: borderRadius, | ||
| flexDirection, | ||
| flexShrink: 1, | ||
| justifyContent, | ||
| maxHeight, | ||
| shadowOffset: { height: 0, width: 0 }, | ||
| shadowOpacity: 1, | ||
| shadowRadius: 4, | ||
| transform: [{ translateY: offset as any }], | ||
| width: maxWidth // This works because flexShrink is set | ||
| } | ||
| return ( | ||
| <TouchableWithoutFeedback onPress={onPress}> | ||
| <Animated.View style={bodyStyle}>{children}</Animated.View> | ||
| </TouchableWithoutFeedback> | ||
| ) | ||
| } | ||
| function defaultMaxHeight(): number { | ||
| const { width, height } = Dimensions.get('screen') | ||
| return 0.25 * Math.max(width, height) | ||
| } |
| import * as React from 'react' | ||
| import { | ||
| Animated, | ||
| BackHandler, | ||
| Dimensions, | ||
| TouchableWithoutFeedback, | ||
| ViewStyle | ||
| } from 'react-native' | ||
| import { AirshipBridge } from '../types' | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides' | ||
| export interface AirshipModalProps<T = unknown> { | ||
| bridge: AirshipBridge<T> | ||
| children?: React.ReactNode | ||
| // Called when the user taps outside the modal or clicks the back button: | ||
| onCancel: () => void | ||
| // True to have the modal float in the center of the screen, | ||
| // or false for a bottom modal. Defaults to false. | ||
| center?: boolean | ||
| // The component color. Defaults to white. | ||
| backgroundColor?: string | ||
| // The radius to use on the corners. Defaults to 10. | ||
| borderRadius?: number | ||
| // The flex direction for the contents. | ||
| flexDirection?: ViewStyle['flexDirection'] | ||
| // How to justify the contents along the flex direction. | ||
| justifyContent?: ViewStyle['justifyContent'] | ||
| // The minimum gap between the component and the screen edges. | ||
| // Takes 0-4 numbers (top, right, bottom, left), | ||
| // using the same logic as the web `margin` property. Defaults to 0. | ||
| margin?: number | number[] | ||
| // The maximum height the component will be. | ||
| // Defaults to no limit. | ||
| maxHeight?: number | ||
| // The maximum width the component will be. | ||
| // Defaults to 512. | ||
| maxWidth?: number | ||
| // Internal padding to place inside the component. | ||
| // Takes 0-4 numbers (top, right, bottom, left), | ||
| // using the same logic as the web `padding` property. Defaults to 0. | ||
| padding?: number | number[] | ||
| // How long the entry animation should be. Defaults to 300ms. | ||
| slideInMs?: number | ||
| // How long the exit animation should be. Defaults to 300ms. | ||
| slideOutMs?: number | ||
| // The color of the window underlay, | ||
| // or a React element for a custom background. | ||
| // Defaults to rgba(0, 0, 0, 0.75). | ||
| underlay?: string | React.ReactElement | ||
| } | ||
| const safeAreaGap = 64 | ||
| /** | ||
| * A modal that slides a modal up from the bottom of the screen | ||
| * and dims the rest of the app. | ||
| */ | ||
| export function AirshipModal<T>(props: AirshipModalProps<T>): JSX.Element { | ||
| const { | ||
| bridge, | ||
| children, | ||
| onCancel, | ||
| backgroundColor = 'white', | ||
| borderRadius = 10, | ||
| center = false, | ||
| flexDirection, | ||
| justifyContent, | ||
| maxHeight, | ||
| maxWidth = 512, | ||
| slideInMs = 300, | ||
| slideOutMs = 300, | ||
| underlay = 'rgba(0, 0, 0, 0.75)' | ||
| } = props | ||
| const margin = sidesToMargin(fixSides(props.margin, 0)) | ||
| const padding = sidesToPadding(fixSides(props.padding, 0)) | ||
| React.useEffect(() => bridge.on('clear', onCancel), [bridge, onCancel]) | ||
| // Create the animations: | ||
| const offset = React.useRef( | ||
| new Animated.Value(Dimensions.get('window').height) | ||
| ).current | ||
| const opacity = React.useRef(new Animated.Value(0)).current | ||
| React.useEffect(() => { | ||
| // Animate in: | ||
| Animated.parallel([ | ||
| Animated.timing(opacity, { | ||
| toValue: 1, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }), | ||
| Animated.timing(offset, { | ||
| toValue: 0, | ||
| duration: slideInMs, | ||
| useNativeDriver: true | ||
| }) | ||
| ]).start() | ||
| // Animate out: | ||
| bridge.on('result', () => { | ||
| Animated.parallel([ | ||
| Animated.timing(opacity, { | ||
| toValue: 0, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }), | ||
| Animated.timing(offset, { | ||
| toValue: Dimensions.get('window').height, | ||
| duration: slideOutMs, | ||
| useNativeDriver: true | ||
| }) | ||
| ]).start(bridge.remove) | ||
| }) | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []) | ||
| // Set up the back-button handler: | ||
| React.useEffect(() => { | ||
| const backHandler = BackHandler.addEventListener( | ||
| 'hardwareBackPress', | ||
| () => { | ||
| onCancel() | ||
| return true | ||
| } | ||
| ) | ||
| return () => backHandler.remove() | ||
| }, [onCancel]) | ||
| const underlayStyle: ViewStyle = { | ||
| backgroundColor: typeof underlay === 'string' ? underlay : 'transparent', | ||
| bottom: 0, | ||
| left: 0, | ||
| opacity: opacity as any, | ||
| position: 'absolute', | ||
| right: 0, | ||
| top: 0 | ||
| } | ||
| const bodyCommon: ViewStyle = { | ||
| ...margin, | ||
| ...padding, | ||
| alignSelf: center ? 'center' : 'flex-end', | ||
| backgroundColor, | ||
| flexDirection, | ||
| flexShrink: 1, | ||
| justifyContent, | ||
| maxHeight, | ||
| shadowOffset: { height: 0, width: 0 }, | ||
| shadowOpacity: 1, | ||
| shadowRadius: 10, | ||
| transform: [{ translateY: offset as any }], | ||
| width: maxWidth // This works because flexShrink is set | ||
| } | ||
| const bodyStyle = center | ||
| ? { | ||
| ...bodyCommon, | ||
| borderRadius | ||
| } | ||
| : { | ||
| ...bodyCommon, | ||
| borderTopLeftRadius: borderRadius, | ||
| borderTopRightRadius: borderRadius, | ||
| marginBottom: -safeAreaGap, | ||
| paddingBottom: padding.paddingBottom + safeAreaGap | ||
| } | ||
| return ( | ||
| <> | ||
| <TouchableWithoutFeedback onPress={() => onCancel()}> | ||
| <Animated.View style={underlayStyle}> | ||
| {typeof underlay !== 'string' ? underlay : undefined} | ||
| </Animated.View> | ||
| </TouchableWithoutFeedback> | ||
| <Animated.View style={bodyStyle}>{children}</Animated.View> | ||
| </> | ||
| ) | ||
| } |
| import * as React from 'react' | ||
| import { Animated, Text, TextStyle, ViewStyle } from 'react-native' | ||
| import { AirshipBridge } from '../types' | ||
| import { fixSides, sidesToMargin, sidesToPadding } from '../util/sides' | ||
| export interface AirshipToastProps { | ||
| bridge: AirshipBridge<undefined> | ||
| children?: React.ReactNode | ||
| // A message to show inside the toast. | ||
| // This will come before any other children. | ||
| message?: string | ||
| // Determines how long the dropdown remains visible, | ||
| // or 0 to disable auto-hide. Defaults to 3000ms. | ||
| autoHideMs?: number | ||
| // The component color. Defaults to grey. | ||
| backgroundColor?: string | ||
| // The radius to use on the corners. | ||
| borderRadius?: number | ||
| // How long the entry animation should be. Defaults to 300ms. | ||
| fadeInMs?: number | ||
| // How long the exit animation should be. Defaults to 500ms. | ||
| fadeOutMs?: number | ||
| // The minimum gap between the component and the screen edges. | ||
| // Takes 0-4 numbers (top, right, bottom, left), | ||
| // using the same logic as the web `margin` property. | ||
| margin?: number | number[] | ||
| // The maximum width the component will be. | ||
| maxWidth?: number | ||
| // The opacity the component should fade to. Defaults to 0.9. | ||
| opacity?: number | ||
| // Internal padding to place inside the component. | ||
| // Takes 0-4 numbers (top, right, bottom, left), | ||
| // using the same logic as the web `padding` property. | ||
| padding?: number | number[] | ||
| // The color to use for the text. Defaults to black. | ||
| textColor?: string | ||
| // The size of the text. | ||
| textSize?: number | ||
| } | ||
| /** | ||
| * A semi-transparent message overlay. | ||
| */ | ||
| export function AirshipToast(props: AirshipToastProps): JSX.Element { | ||
| const { textSize = 14 } = props | ||
| const { | ||
| autoHideMs = 3000, | ||
| backgroundColor = 'white', | ||
| borderRadius = 1.5 * textSize, | ||
| bridge, | ||
| children, | ||
| fadeInMs = 300, | ||
| fadeOutMs = 1000, | ||
| maxWidth = 512, | ||
| opacity: finalOpacity = 0.9, | ||
| message, | ||
| textColor = 'black' | ||
| } = props | ||
| const margin = sidesToMargin(fixSides(props.margin, 2 * textSize)) | ||
| const padding = sidesToPadding(fixSides(props.padding, textSize)) | ||
| // Create the animation: | ||
| const opacity = React.useRef(new Animated.Value(0)).current | ||
| React.useEffect(() => { | ||
| let timeout: ReturnType<typeof setTimeout> | undefined | ||
| // Animate in: | ||
| Animated.timing(opacity, { | ||
| toValue: finalOpacity, | ||
| duration: fadeInMs, | ||
| useNativeDriver: true | ||
| }).start(() => { | ||
| // Start the auto-hide timer: | ||
| if (autoHideMs > 0) { | ||
| timeout = setTimeout(() => { | ||
| timeout = undefined | ||
| bridge.resolve(undefined) | ||
| }, autoHideMs) | ||
| } | ||
| }) | ||
| // Animate out: | ||
| bridge.on('clear', () => bridge.resolve(undefined)) | ||
| bridge.on('result', () => { | ||
| Animated.timing(opacity, { | ||
| toValue: 0, | ||
| duration: fadeOutMs, | ||
| useNativeDriver: true | ||
| }).start(() => bridge.remove()) | ||
| }) | ||
| return () => { | ||
| if (timeout != null) clearTimeout(timeout) | ||
| } | ||
| }) | ||
| const bodyStyle: ViewStyle = { | ||
| ...margin, | ||
| ...padding, | ||
| alignItems: 'center', | ||
| alignSelf: 'flex-end', | ||
| backgroundColor, | ||
| borderRadius, | ||
| flexDirection: 'row', | ||
| justifyContent: 'flex-start', | ||
| maxWidth, | ||
| opacity: opacity as any | ||
| } | ||
| const textStyle: TextStyle = { | ||
| color: textColor, | ||
| flexShrink: 1, | ||
| fontSize: textSize, | ||
| textAlign: 'center' | ||
| } | ||
| return ( | ||
| <Animated.View style={bodyStyle}> | ||
| {message != null ? <Text style={textStyle}>{message}</Text> : null} | ||
| {children} | ||
| </Animated.View> | ||
| ) | ||
| } |
| import * as React from 'react' | ||
| import { | ||
| Dimensions, | ||
| Keyboard, | ||
| KeyboardEventListener, | ||
| Platform, | ||
| SafeAreaView, | ||
| StyleSheet, | ||
| View | ||
| } from 'react-native' | ||
| import { | ||
| addSides, | ||
| mapSides, | ||
| Offset, | ||
| Padding, | ||
| SideList, | ||
| sidesToOffset, | ||
| sidesToPadding, | ||
| subtractSides | ||
| } from '../util/sides' | ||
| export interface BarometerLayout { | ||
| offset: Offset | ||
| padding: Padding | ||
| } | ||
| interface Props { | ||
| children?: React.ReactNode | ||
| onLayout?: (layout: BarometerLayout) => void | ||
| } | ||
| const emptySides: SideList = [0, 0, 0, 0] | ||
| /** | ||
| * Measures various things about the Airship environment, | ||
| * so we know how to position our children. | ||
| * | ||
| * This component mounts a view with absolute positioning, | ||
| * and then measures that view relative to the window. | ||
| * If a side is inset from the window edge, we use a negative offset | ||
| * to expand it outward. If a side extends beyond the window edge, | ||
| * we use padding to push the content inward. | ||
| * | ||
| * On iOS, we also mount a child inside a SafeAreaView, to measure | ||
| * the safe area insets. We add these to the padding. | ||
| * | ||
| * Finally, we keep track of the keyboard, adding extra padding & | ||
| * scheduling animations as needed. | ||
| */ | ||
| export function Barometer(props: Props): JSX.Element { | ||
| const { children, onLayout = () => {} } = props | ||
| // Mutable state: | ||
| const keyboardHeight: React.MutableRefObject<number> = React.useRef(0) | ||
| const lastLayoutJson: React.MutableRefObject<string> = React.useRef('') | ||
| const view: React.RefObject<SafeAreaView | View> = React.useRef(null) | ||
| const childView: React.RefObject<View> = React.useRef(null) | ||
| // Handle layout changes: | ||
| const handleLayout = React.useCallback((): void => { | ||
| // Measure the view in the window: | ||
| const viewPromise: Promise<SideList> = new Promise(resolve => { | ||
| if (view.current == null) return resolve(emptySides) | ||
| view.current.measureInWindow((x, y, width, height) => { | ||
| const window = Dimensions.get('window') | ||
| resolve([y, window.width - width - x, window.height - height - y, x]) | ||
| }) | ||
| }) | ||
| // Measure the child view in the window: | ||
| const childPromise: Promise<SideList> = new Promise(resolve => { | ||
| if (childView.current == null) return resolve(viewPromise) | ||
| childView.current.measureInWindow((x, y, width, height) => { | ||
| const window = Dimensions.get('window') | ||
| resolve([y, window.width - width - x, window.height - height - y, x]) | ||
| }) | ||
| }) | ||
| // Measure the gap between the bottom of the screen and the view: | ||
| const bottomPromise: Promise<number> = new Promise(resolve => { | ||
| if (view.current == null) return 0 | ||
| view.current.measure((x, y, width, height, screenX, screenY) => { | ||
| const screen = Dimensions.get('screen') | ||
| resolve(screen.height - height - screenY) | ||
| }) | ||
| }) | ||
| // Combine the results, then call the callback: | ||
| Promise.all([viewPromise, childPromise, bottomPromise]) | ||
| .then(([viewOffset, childOffset, bottomGap]) => { | ||
| // Cancel out any offset, so we cover the full window: | ||
| const offset = mapSides(viewOffset, side => -Math.max(side, 0)) | ||
| // If the offset is negative, issue positive padding, | ||
| // plus any safe area: | ||
| const safePadding = subtractSides(childOffset, viewOffset) | ||
| const padding = addSides( | ||
| safePadding, | ||
| mapSides(viewOffset, side => Math.abs(side)) | ||
| ) | ||
| // Use the keyboard padding, if needed: | ||
| const keyboardPadding = Math.max( | ||
| keyboardHeight.current - bottomGap - offset[2], | ||
| 0 | ||
| ) | ||
| padding[2] = Math.max(padding[2], keyboardPadding) | ||
| // Send an update if we have changes: | ||
| const string = JSON.stringify([offset, padding]) | ||
| if (string !== lastLayoutJson.current) { | ||
| lastLayoutJson.current = string | ||
| onLayout({ | ||
| offset: sidesToOffset(offset), | ||
| padding: sidesToPadding(padding) | ||
| }) | ||
| } | ||
| }) | ||
| .catch(() => {}) | ||
| }, [onLayout]) | ||
| // Subscribe to keyboard changes: | ||
| React.useEffect(() => { | ||
| const handleKeyboard: KeyboardEventListener = event => { | ||
| const screen = Dimensions.get('screen') | ||
| keyboardHeight.current = Math.min( | ||
| // These two give different results sometimes, so pick the smaller one: | ||
| screen.height - event.endCoordinates.screenY, | ||
| event.endCoordinates.height | ||
| ) | ||
| if (event.duration > 0) { | ||
| // @ts-expect-error | ||
| Keyboard.scheduleLayoutAnimation(event) | ||
| } | ||
| handleLayout() | ||
| } | ||
| if (Platform.OS === 'android') { | ||
| Keyboard.addListener('keyboardDidShow', handleKeyboard) | ||
| Keyboard.addListener('keyboardDidHide', handleKeyboard) | ||
| } else { | ||
| Keyboard.addListener('keyboardWillChangeFrame', handleKeyboard) | ||
| } | ||
| return () => { | ||
| if (Platform.OS === 'android') { | ||
| Keyboard.removeListener('keyboardDidShow', handleKeyboard) | ||
| Keyboard.removeListener('keyboardDidHide', handleKeyboard) | ||
| } else { | ||
| Keyboard.removeListener('keyboardWillChangeFrame', handleKeyboard) | ||
| } | ||
| } | ||
| }, [handleLayout]) | ||
| if (Platform.OS === 'android') { | ||
| return ( | ||
| <View | ||
| ref={view} | ||
| onLayout={handleLayout} | ||
| pointerEvents="none" | ||
| style={StyleSheet.absoluteFill} | ||
| testID="AirshipBarometer" | ||
| > | ||
| {children} | ||
| </View> | ||
| ) | ||
| } | ||
| return ( | ||
| <SafeAreaView | ||
| ref={view} | ||
| onLayout={handleLayout} | ||
| pointerEvents="none" | ||
| style={StyleSheet.absoluteFill} | ||
| testID="AirshipBarometer" | ||
| > | ||
| <View ref={childView} style={{ flex: 1 }} testID="AirshipBarometerChild"> | ||
| {children} | ||
| </View> | ||
| </SafeAreaView> | ||
| ) | ||
| } |
| // @flow | ||
| import * as React from 'react' | ||
| import { type OnEvents, type Unsubscribe } from 'yavent' | ||
| export type { Unsubscribe } | ||
| type AirshipEvents = { | ||
| result: void, | ||
| clear: void | ||
| } | ||
| /** | ||
| * Control panel for managing a component inside an airship. | ||
| */ | ||
| export type AirshipBridge<T> = { | ||
| // Use these to pass values to the outside world: | ||
| +resolve: (value: T | Promise<T>) => void, | ||
| +reject: (error: Error) => void, | ||
| // Unmounts the component: | ||
| +remove: () => void, | ||
| // Subscribes to events. | ||
| // Use `on('result', callback)` to subscribe to | ||
| // the promise being resolved or rejected. | ||
| // Use `on('clear', callback)` to subscribe to | ||
| // the `Airship.clear` method being called. | ||
| +on: OnEvents<AirshipEvents>, | ||
| // Runs a callback when the result promise settles. | ||
| // Deprecated in favor of `on('result')`. | ||
| +onResult: (callback: () => mixed) => void | ||
| } | ||
| /** | ||
| * Renders a component to place inside the airship. | ||
| */ | ||
| type AirshipRender<T> = (bridge: AirshipBridge<T>) => React.Node | ||
| /** | ||
| * Props the Airship container component accepts. | ||
| */ | ||
| export interface AirshipProps { | ||
| children?: React.Node; | ||
| } | ||
| /** | ||
| * The airship itself is a component you should mount after your main | ||
| * scene or router. | ||
| * | ||
| * It has a static method anyone can call to display components. | ||
| * The method returns a promise, which the component can use to pass values | ||
| * to the outside world. | ||
| */ | ||
| declare class AirshipClass extends React.Component<AirshipProps> { | ||
| static clear(): void; | ||
| static show<T>(render: AirshipRender<T>): Promise<T>; | ||
| } | ||
| export type Airship = typeof AirshipClass | ||
| /** | ||
| * Constructs an Airship component. | ||
| */ | ||
| declare export function makeAirship(): Airship | ||
| type FlexDirection = 'column-reverse' | 'column' | 'row-reverse' | 'row' | ||
| type JustifyContent = | ||
| | 'center' | ||
| | 'flex-end' | ||
| | 'flex-start' | ||
| | 'space-around' | ||
| | 'space-between' | ||
| | 'space-evenly' | ||
| /** | ||
| * A drop-down alert. | ||
| */ | ||
| export type AirshipDropdownProps = { | ||
| bridge: AirshipBridge<void>, | ||
| children?: React.Node, | ||
| onPress?: () => void, | ||
| autoHideMs?: number, | ||
| backgroundColor?: string, | ||
| borderRadius?: number, | ||
| flexDirection?: FlexDirection, | ||
| justifyContent?: JustifyContent, | ||
| margin?: number | number[], | ||
| maxHeight?: number, | ||
| maxWidth?: number, | ||
| padding?: number | number[], | ||
| slideInMs?: number, | ||
| slideOutMs?: number | ||
| } | ||
| declare export class AirshipDropdown | ||
| extends React.Component<AirshipDropdownProps> {} | ||
| /** | ||
| * A slide-up modal which dims the rest of the screen. | ||
| */ | ||
| export type AirshipModalProps<T> = { | ||
| bridge: AirshipBridge<T>, | ||
| children?: React.Node, | ||
| onCancel: () => void, | ||
| center?: boolean, | ||
| backgroundColor?: string, | ||
| borderRadius?: number, | ||
| flexDirection?: FlexDirection, | ||
| justifyContent?: JustifyContent, | ||
| margin?: number | number[], | ||
| maxHeight?: number, | ||
| maxWidth?: number, | ||
| padding?: number | number[], | ||
| slideInMs?: number, | ||
| slideOutMs?: number, | ||
| underlay?: string | React.Element<any> | ||
| } | ||
| declare export class AirshipModal<T> | ||
| extends React.Component<AirshipModalProps<T>> {} | ||
| /** | ||
| * Emulates the Android Toast component in a cross-platform way. | ||
| */ | ||
| export type AirshipToastProps = { | ||
| bridge: AirshipBridge<void>, | ||
| children?: React.Node, | ||
| message?: string, | ||
| autoHideMs?: number, | ||
| backgroundColor?: string, | ||
| borderRadius?: number, | ||
| fadeInMs?: number, | ||
| fadeOutMs?: number, | ||
| margin?: number | number[], | ||
| maxWidth?: number, | ||
| opacity?: number, | ||
| padding?: number | number[], | ||
| textColor?: string, | ||
| textSize?: number | ||
| } | ||
| declare export class AirshipToast extends React.Component<AirshipToastProps> {} |
-10
| export { Unsubscribe } from 'yavent' | ||
| export { makeAirship } from './components/Airship' | ||
| export { | ||
| AirshipDropdown, | ||
| AirshipDropdownProps | ||
| } from './components/AirshipDropdown' | ||
| export { AirshipModal, AirshipModalProps } from './components/AirshipModal' | ||
| export { AirshipToast, AirshipToastProps } from './components/AirshipToast' | ||
| export { Airship, AirshipBridge } from './types' |
-55
| import * as React from 'react' | ||
| import { OnEvents } from 'yavent' | ||
| export interface AirshipEvents { | ||
| result: undefined | ||
| clear: undefined | ||
| } | ||
| /** | ||
| * Control panel for managing a component inside an airship. | ||
| */ | ||
| export interface AirshipBridge<T> { | ||
| // Use these to pass values to the outside world: | ||
| resolve: (value: T | PromiseLike<T>) => void | ||
| reject: (error: Error) => void | ||
| // Unmounts the component: | ||
| remove: () => void | ||
| // Subscribes to events. | ||
| // Use `on('result', callback)` to subscribe to | ||
| // the promise being resolved or rejected. | ||
| // Use `on('clear', callback)` to subscribe to | ||
| // the `Airship.clear` method being called. | ||
| on: OnEvents<AirshipEvents> | ||
| // Runs a callback when the result promise settles. | ||
| // Deprecated in favor of `on('result')`. | ||
| onResult: (callback: () => unknown) => void | ||
| } | ||
| /** | ||
| * Renders a component to place inside the airship. | ||
| */ | ||
| export type AirshipRender<T> = (bridge: AirshipBridge<T>) => React.ReactNode | ||
| /** | ||
| * Props the Airship container component accepts. | ||
| */ | ||
| export interface AirshipProps { | ||
| children?: React.ReactNode | ||
| } | ||
| /** | ||
| * The Airship itself is a component you should mount after your main | ||
| * scene or router. | ||
| * | ||
| * It has a static method anyone can call to display components. | ||
| * The method returns a promise, which the component can use to pass values | ||
| * to the outside world. | ||
| */ | ||
| export interface Airship extends React.FunctionComponent<AirshipProps> { | ||
| clear: () => void | ||
| show: <T>(render: AirshipRender<T>) => Promise<T> | ||
| } |
| /** | ||
| * The four sides (top, right, bottom, left) as a tuple. | ||
| */ | ||
| export type SideList = [number, number, number, number] | ||
| export interface Margin { | ||
| marginBottom: number | ||
| marginLeft: number | ||
| marginRight: number | ||
| marginTop: number | ||
| } | ||
| export interface Offset { | ||
| bottom: number | ||
| left: number | ||
| right: number | ||
| top: number | ||
| } | ||
| export interface Padding { | ||
| paddingBottom: number | ||
| paddingLeft: number | ||
| paddingRight: number | ||
| paddingTop: number | ||
| } | ||
| /** | ||
| * Interprets an array of 0-4 numbers as a web CSS sides shorthand | ||
| * (top, right, bottom, left). | ||
| */ | ||
| export function fixSides( | ||
| sides: number[] | number | undefined, | ||
| fallback: number | ||
| ): SideList { | ||
| if (sides == null) { | ||
| return [fallback, fallback, fallback, fallback] | ||
| } | ||
| if (typeof sides === 'number') { | ||
| return [sides, sides, sides, sides] | ||
| } | ||
| const top = sides[0] ?? fallback | ||
| const right = sides[1] ?? top | ||
| const bottom = sides[2] ?? top | ||
| const left = sides[3] ?? right | ||
| return [top, right, bottom, left] | ||
| } | ||
| export function addSides(a: SideList, b: SideList): SideList { | ||
| return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]] | ||
| } | ||
| export function subtractSides(a: SideList, b: SideList): SideList { | ||
| return [a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]] | ||
| } | ||
| export function mapSides( | ||
| sides: SideList, | ||
| f: (side: number) => number | ||
| ): SideList { | ||
| return [f(sides[0]), f(sides[1]), f(sides[2]), f(sides[3])] | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS margin properties. | ||
| */ | ||
| export function sidesToMargin(sides: SideList): Margin { | ||
| return { | ||
| marginTop: sides[0], | ||
| marginRight: sides[1], | ||
| marginBottom: sides[2], | ||
| marginLeft: sides[3] | ||
| } | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS positioning properties. | ||
| */ | ||
| export function sidesToOffset(sides: SideList): Offset { | ||
| return { | ||
| top: sides[0], | ||
| right: sides[1], | ||
| bottom: sides[2], | ||
| left: sides[3] | ||
| } | ||
| } | ||
| /** | ||
| * Turns a list of sides into CSS padding properties. | ||
| */ | ||
| export function sidesToPadding(sides: SideList): Padding { | ||
| return { | ||
| paddingTop: sides[0], | ||
| paddingRight: sides[1], | ||
| paddingBottom: sides[2], | ||
| paddingLeft: sides[3] | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
35988
-56.44%20
-45.95%644
-59.09%1
Infinity%