react-native-copilot
Version:
Make an interactive step by step tour guide for you react-native app
1,145 lines (1,126 loc) • 38.6 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __restKey = (key) => typeof key === "symbol" ? key : key + "";
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/components/style.ts
var import_react_native, STEP_NUMBER_RADIUS, STEP_NUMBER_DIAMETER, ZINDEX, MARGIN, OFFSET_WIDTH, ARROW_SIZE, styles;
var init_style = __esm({
"src/components/style.ts"() {
"use strict";
import_react_native = require("react-native");
STEP_NUMBER_RADIUS = 14;
STEP_NUMBER_DIAMETER = STEP_NUMBER_RADIUS * 2;
ZINDEX = 100;
MARGIN = 13;
OFFSET_WIDTH = 4;
ARROW_SIZE = 6;
styles = import_react_native.StyleSheet.create({
container: {
position: "absolute",
left: 0,
top: 0,
right: 0,
bottom: 0,
zIndex: ZINDEX
},
arrow: {
position: "absolute",
borderWidth: ARROW_SIZE
},
tooltip: {
position: "absolute",
paddingTop: 15,
paddingHorizontal: 15,
backgroundColor: "#fff",
borderRadius: 3,
overflow: "hidden"
},
tooltipText: {},
tooltipContainer: {
flex: 1
},
stepNumberContainer: {
position: "absolute",
width: STEP_NUMBER_DIAMETER,
height: STEP_NUMBER_DIAMETER,
overflow: "hidden",
zIndex: ZINDEX + 1
},
stepNumber: {
flex: 1,
alignItems: "center",
justifyContent: "center",
borderWidth: 2,
borderRadius: STEP_NUMBER_RADIUS,
borderColor: "#FFFFFF",
backgroundColor: "#27ae60"
},
stepNumberText: {
fontSize: 10,
backgroundColor: "transparent",
color: "#FFFFFF"
},
button: {
padding: 10
},
buttonText: {
color: "#27ae60"
},
bottomBar: {
marginTop: 10,
flexDirection: "row",
justifyContent: "flex-end"
},
overlayRectangle: {
position: "absolute",
backgroundColor: "rgba(0,0,0,0.2)",
left: 0,
top: 0,
bottom: 0,
right: 0
},
overlayContainer: {
position: "absolute",
left: 0,
top: 0,
bottom: 0,
right: 0
}
});
}
});
// src/components/SvgMask.tsx
var SvgMask_exports = {};
__export(SvgMask_exports, {
SvgMask: () => SvgMask
});
var import_react3, import_react_native4, import_react_native_svg, AnimatedSvgPath, windowDimensions, defaultSvgPath, SvgMask;
var init_SvgMask = __esm({
"src/components/SvgMask.tsx"() {
"use strict";
import_react3 = __toESM(require("react"));
import_react_native4 = require("react-native");
import_react_native_svg = __toESM(require("react-native-svg"));
AnimatedSvgPath = import_react_native4.Animated.createAnimatedComponent(import_react_native_svg.Path);
windowDimensions = import_react_native4.Dimensions.get("window");
defaultSvgPath = ({
size,
position,
canvasSize
}) => {
const positionX = position.x._value;
const positionY = position.y._value;
const sizeX = size.x._value;
const sizeY = size.y._value;
return `M0,0H${canvasSize.x}V${canvasSize.y}H0V0ZM${positionX},${positionY}H${positionX + sizeX}V${positionY + sizeY}H${positionX}V${positionY}Z`;
};
SvgMask = ({
size,
position,
style,
easing = import_react_native4.Easing.linear,
animationDuration = 300,
animated,
backdropColor,
svgMaskPath = defaultSvgPath,
onClick,
currentStep
}) => {
const [canvasSize, setCanvasSize] = (0, import_react3.useState)({
x: windowDimensions.width,
y: windowDimensions.height
});
const sizeValue = (0, import_react3.useRef)(
new import_react_native4.Animated.ValueXY(size)
).current;
const positionValue = (0, import_react3.useRef)(
new import_react_native4.Animated.ValueXY(position)
).current;
const maskRef = (0, import_react3.useRef)(null);
const animationListener = (0, import_react3.useCallback)(() => {
const d = svgMaskPath({
size: sizeValue,
position: positionValue,
canvasSize,
step: currentStep
});
if (maskRef.current) {
maskRef.current.setNativeProps({ d });
}
}, [canvasSize, currentStep, svgMaskPath, positionValue, sizeValue]);
const animate = (0, import_react3.useCallback)(
(toSize = size, toPosition = position) => {
if (animated) {
import_react_native4.Animated.parallel([
import_react_native4.Animated.timing(sizeValue, {
toValue: toSize,
duration: animationDuration,
easing,
useNativeDriver: false
}),
import_react_native4.Animated.timing(positionValue, {
toValue: toPosition,
duration: animationDuration,
easing,
useNativeDriver: false
})
]).start();
} else {
sizeValue.setValue(toSize);
positionValue.setValue(toPosition);
}
},
[
animated,
animationDuration,
easing,
positionValue,
position,
size,
sizeValue
]
);
(0, import_react3.useEffect)(() => {
const id = positionValue.addListener(animationListener);
return () => {
positionValue.removeListener(id);
};
}, [animationListener, positionValue]);
(0, import_react3.useEffect)(() => {
if (size && position) {
animate(size, position);
}
}, [animate, position, size]);
const handleLayout = ({
nativeEvent: {
layout: { width, height }
}
}) => {
setCanvasSize({
x: width,
y: height
});
};
return /* @__PURE__ */ import_react3.default.createElement(
import_react_native4.View,
{
style,
onLayout: handleLayout,
onStartShouldSetResponder: onClick
},
canvasSize ? /* @__PURE__ */ import_react3.default.createElement(import_react_native_svg.default, { pointerEvents: "none", width: canvasSize.x, height: canvasSize.y }, /* @__PURE__ */ import_react3.default.createElement(
AnimatedSvgPath,
{
ref: maskRef,
fill: backdropColor,
fillRule: "evenodd",
strokeWidth: 1,
d: svgMaskPath({
size: sizeValue,
position: positionValue,
canvasSize,
step: currentStep
})
}
)) : null
);
};
}
});
// src/components/ViewMask.tsx
var ViewMask_exports = {};
__export(ViewMask_exports, {
ViewMask: () => ViewMask
});
var import_react4, import_react_native5, ViewMask;
var init_ViewMask = __esm({
"src/components/ViewMask.tsx"() {
"use strict";
import_react4 = __toESM(require("react"));
import_react_native5 = require("react-native");
init_style();
ViewMask = (props) => {
const sizeValue = (0, import_react4.useRef)(
new import_react_native5.Animated.ValueXY(props.size)
).current;
const positionValue = (0, import_react4.useRef)(
new import_react_native5.Animated.ValueXY(props.position)
).current;
const [animated, setAnimated] = (0, import_react4.useState)(false);
const animate = (0, import_react4.useCallback)(
(size = props.size, position = props.position) => {
if (animated) {
import_react_native5.Animated.parallel([
import_react_native5.Animated.timing(sizeValue, {
toValue: size,
duration: props.animationDuration,
easing: props.easing,
useNativeDriver: false
}),
import_react_native5.Animated.timing(positionValue, {
toValue: position,
duration: props.animationDuration,
easing: props.easing,
useNativeDriver: false
})
]).start();
} else {
sizeValue.setValue(size);
positionValue.setValue(position);
setAnimated(props.animated);
}
},
[
animated,
positionValue,
props.animated,
props.animationDuration,
props.easing,
props.position,
props.size,
sizeValue
]
);
(0, import_react4.useEffect)(() => {
if (props.position || props.size) {
animate(props.size, props.position);
}
}, [animate, props.position, props.size]);
const width = props.layout ? props.layout.width : 500;
const height = props.layout ? props.layout.height : 500;
const leftOverlayRight = import_react_native5.Animated.add(
width,
import_react_native5.Animated.multiply(positionValue.x, -1)
);
const rightOverlayLeft = import_react_native5.Animated.add(sizeValue.x, positionValue.x);
const bottomOverlayTopBoundary = import_react_native5.Animated.add(sizeValue.y, positionValue.y);
const topOverlayBottomBoundary = import_react_native5.Animated.add(
height,
import_react_native5.Animated.multiply(-1, positionValue.y)
);
const verticalOverlayLeftBoundary = positionValue.x;
const verticalOverlayRightBoundary = import_react_native5.Animated.add(
width,
import_react_native5.Animated.multiply(-1, rightOverlayLeft)
);
return /* @__PURE__ */ import_react4.default.createElement(import_react_native5.View, { style: props.style, onStartShouldSetResponder: props.onClick }, [
{
right: leftOverlayRight,
backgroundColor: props.backdropColor
},
{
left: rightOverlayLeft,
backgroundColor: props.backdropColor
},
{
top: bottomOverlayTopBoundary,
left: verticalOverlayLeftBoundary,
right: verticalOverlayRightBoundary,
backgroundColor: props.backdropColor
},
{
bottom: topOverlayBottomBoundary,
left: verticalOverlayLeftBoundary,
right: verticalOverlayRightBoundary,
backgroundColor: props.backdropColor
}
].map((style, index) => /* @__PURE__ */ import_react4.default.createElement(import_react_native5.Animated.View, { key: index, style: [styles.overlayRectangle, style] })));
};
}
});
// src/index.ts
var src_exports = {};
__export(src_exports, {
CopilotProvider: () => CopilotProvider,
CopilotStep: () => CopilotStep,
DefaultUI: () => DefaultUI,
useCopilot: () => useCopilot,
walkthroughable: () => walkthroughable
});
module.exports = __toCommonJS(src_exports);
// src/components/default-ui/StepNumber.tsx
var import_react9 = __toESM(require("react"));
var import_react_native8 = require("react-native");
// src/contexts/CopilotProvider.tsx
var import_mitt = __toESM(require("mitt"));
var import_react8 = __toESM(require("react"));
var import_react_native7 = require("react-native");
// src/components/CopilotModal.tsx
var import_react5 = __toESM(require("react"));
var import_react_native6 = require("react-native");
// src/components/default-ui/Tooltip.tsx
var import_react2 = __toESM(require("react"));
var import_react_native3 = require("react-native");
// src/components/default-ui/Button.tsx
var import_react = __toESM(require("react"));
var import_react_native2 = require("react-native");
init_style();
var Button = (_a) => {
var _b = _a, { wrapperStyle, style } = _b, rest = __objRest(_b, ["wrapperStyle", "style"]);
return /* @__PURE__ */ import_react.default.createElement(import_react_native2.View, { style: [styles.button, wrapperStyle] }, /* @__PURE__ */ import_react.default.createElement(import_react_native2.Text, __spreadValues({ style: [styles.buttonText, style] }, rest)));
};
// src/components/default-ui/Tooltip.tsx
init_style();
var Tooltip = ({ labels }) => {
const { goToNext, goToPrev, stop, currentStep, isFirstStep, isLastStep } = useCopilot();
const handleStop = () => {
void stop();
};
const handleNext = () => {
void goToNext();
};
const handlePrev = () => {
void goToPrev();
};
return /* @__PURE__ */ import_react2.default.createElement(import_react_native3.View, null, /* @__PURE__ */ import_react2.default.createElement(import_react_native3.View, { style: styles.tooltipContainer }, /* @__PURE__ */ import_react2.default.createElement(import_react_native3.Text, { testID: "stepDescription", style: styles.tooltipText }, currentStep == null ? void 0 : currentStep.text)), /* @__PURE__ */ import_react2.default.createElement(import_react_native3.View, { style: [styles.bottomBar] }, !isLastStep ? /* @__PURE__ */ import_react2.default.createElement(import_react_native3.TouchableOpacity, { onPress: handleStop }, /* @__PURE__ */ import_react2.default.createElement(Button, null, labels.skip)) : null, !isFirstStep ? /* @__PURE__ */ import_react2.default.createElement(import_react_native3.TouchableOpacity, { onPress: handlePrev }, /* @__PURE__ */ import_react2.default.createElement(Button, null, labels.previous)) : null, !isLastStep ? /* @__PURE__ */ import_react2.default.createElement(import_react_native3.TouchableOpacity, { onPress: handleNext }, /* @__PURE__ */ import_react2.default.createElement(Button, null, labels.next)) : /* @__PURE__ */ import_react2.default.createElement(import_react_native3.TouchableOpacity, { onPress: handleStop }, /* @__PURE__ */ import_react2.default.createElement(Button, null, labels.finish))));
};
// src/components/CopilotModal.tsx
init_style();
var noop = () => {
};
var makeDefaultLayout = () => ({
x: 0,
y: 0,
width: 0,
height: 0
});
var CopilotModal = (0, import_react5.forwardRef)(
function CopilotModal2({
easing = import_react_native6.Easing.elastic(0.7),
animationDuration = 400,
tooltipComponent: TooltipComponent = Tooltip,
tooltipStyle = {},
stepNumberComponent: StepNumberComponent = StepNumber,
overlay = typeof import_react_native6.NativeModules.RNSVGSvgViewManager !== "undefined" ? "svg" : "view",
animated = typeof import_react_native6.NativeModules.RNSVGSvgViewManager !== "undefined",
androidStatusBarVisible = false,
backdropColor = "rgba(0, 0, 0, 0.4)",
labels = {
finish: "Finish",
next: "Next",
previous: "Previous",
skip: "Skip"
},
svgMaskPath,
stopOnOutsideClick = false,
arrowColor = "#fff",
arrowSize = ARROW_SIZE,
margin = MARGIN
}, ref) {
const { stop, currentStep, visible } = useCopilot();
const [tooltipStyles, setTooltipStyles] = (0, import_react5.useState)({});
const [arrowStyles, setArrowStyles] = (0, import_react5.useState)({});
const [animatedValues] = (0, import_react5.useState)({
top: new import_react_native6.Animated.Value(0),
stepNumberLeft: new import_react_native6.Animated.Value(0)
});
const layoutRef = (0, import_react5.useRef)(makeDefaultLayout());
const [layout, setLayout] = (0, import_react5.useState)(
void 0
);
const [maskRect, setMaskRect] = (0, import_react5.useState)();
const [isAnimated, setIsAnimated] = (0, import_react5.useState)(false);
const [containerVisible, setContainerVisible] = (0, import_react5.useState)(false);
(0, import_react5.useEffect)(() => {
if (visible) {
setContainerVisible(true);
}
}, [visible]);
(0, import_react5.useEffect)(() => {
if (!visible) {
reset();
}
}, [visible]);
const handleLayoutChange = ({
nativeEvent: { layout: newLayout }
}) => {
layoutRef.current = newLayout;
};
const measure = () => __async(this, null, function* () {
return yield new Promise((resolve) => {
const updateLayout = () => {
if (layoutRef.current.width !== 0) {
resolve(layoutRef.current);
} else {
requestAnimationFrame(updateLayout);
}
};
updateLayout();
});
});
const _animateMove = (0, import_react5.useCallback)(
(rect) => __async(this, null, function* () {
var _a;
const newMeasuredLayout = yield measure();
if (!androidStatusBarVisible && import_react_native6.Platform.OS === "android") {
rect.y -= (_a = import_react_native6.StatusBar.currentHeight) != null ? _a : 0;
}
let stepNumberLeft = rect.x - STEP_NUMBER_RADIUS;
if (stepNumberLeft < 0) {
stepNumberLeft = rect.x + rect.width - STEP_NUMBER_RADIUS;
if (stepNumberLeft > newMeasuredLayout.width - STEP_NUMBER_DIAMETER) {
stepNumberLeft = newMeasuredLayout.width - STEP_NUMBER_DIAMETER;
}
}
const center = {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2
};
const relativeToLeft = center.x;
const relativeToTop = center.y;
const relativeToBottom = Math.abs(center.y - newMeasuredLayout.height);
const relativeToRight = Math.abs(center.x - newMeasuredLayout.width);
const verticalPosition = relativeToBottom > relativeToTop ? "bottom" : "top";
const horizontalPosition = relativeToLeft > relativeToRight ? "left" : "right";
const tooltip = {};
const arrow = {};
arrow.position = "absolute";
if (verticalPosition === "bottom") {
tooltip.top = rect.y + rect.height + margin;
arrow.borderBottomColor = arrowColor;
arrow.borderTopColor = "transparent";
arrow.borderLeftColor = "transparent";
arrow.borderRightColor = "transparent";
arrow.top = tooltip.top - arrowSize * 2;
} else {
tooltip.bottom = newMeasuredLayout.height - (rect.y - margin);
arrow.borderTopColor = arrowColor;
arrow.borderLeftColor = "transparent";
arrow.borderRightColor = "transparent";
arrow.borderBottomColor = "transparent";
arrow.bottom = tooltip.bottom - arrowSize * 2;
}
if (horizontalPosition === "left") {
tooltip.right = Math.max(
newMeasuredLayout.width - (rect.x + rect.width),
0
);
tooltip.right = tooltip.right === 0 ? tooltip.right + margin : tooltip.right;
tooltip.maxWidth = newMeasuredLayout.width - tooltip.right - margin;
arrow.right = tooltip.right + margin;
} else {
tooltip.left = Math.max(rect.x, 0);
tooltip.left = tooltip.left === 0 ? tooltip.left + margin : tooltip.left;
tooltip.maxWidth = newMeasuredLayout.width - tooltip.left - margin;
arrow.left = tooltip.left + margin;
}
sanitize(arrow);
sanitize(tooltip);
sanitize(rect);
const animate = [
["top", rect.y],
["stepNumberLeft", stepNumberLeft]
];
if (isAnimated) {
import_react_native6.Animated.parallel(
animate.map(([key, value]) => {
return import_react_native6.Animated.timing(animatedValues[key], {
toValue: value,
duration: animationDuration,
easing,
useNativeDriver: false
});
})
).start();
} else {
animate.forEach(([key, value]) => {
animatedValues[key].setValue(value);
});
}
setTooltipStyles(tooltip);
setArrowStyles(arrow);
setLayout(newMeasuredLayout);
setMaskRect({
width: rect.width,
height: rect.height,
x: Math.floor(Math.max(rect.x, 0)),
y: Math.floor(Math.max(rect.y, 0))
});
}),
[
androidStatusBarVisible,
animatedValues,
animationDuration,
arrowColor,
easing,
isAnimated,
arrowSize,
margin
]
);
const animateMove = (0, import_react5.useCallback)(
(rect) => __async(this, null, function* () {
yield new Promise((resolve) => {
const frame = () => __async(this, null, function* () {
yield _animateMove(rect);
resolve();
});
setContainerVisible(true);
requestAnimationFrame(() => {
void frame();
});
});
}),
[_animateMove]
);
const reset = () => {
setIsAnimated(false);
setContainerVisible(false);
setLayout(void 0);
};
const handleStop = () => {
reset();
void stop();
};
const handleMaskClick = () => {
if (stopOnOutsideClick) {
handleStop();
}
};
(0, import_react5.useImperativeHandle)(
ref,
() => {
return {
animateMove
};
},
[animateMove]
);
const modalVisible = containerVisible || visible;
const contentVisible = layout != null && containerVisible;
if (!modalVisible) {
return null;
}
return /* @__PURE__ */ import_react5.default.createElement(
import_react_native6.Modal,
{
animationType: "none",
visible: true,
onRequestClose: noop,
transparent: true,
supportedOrientations: ["portrait", "landscape"]
},
/* @__PURE__ */ import_react5.default.createElement(import_react_native6.View, { style: styles.container, onLayout: handleLayoutChange }, contentVisible && renderMask(), contentVisible && renderTooltip())
);
function renderMask() {
const MaskComponent = overlay === "svg" ? (
// eslint-disable-next-line @typescript-eslint/no-var-requires
(init_SvgMask(), __toCommonJS(SvgMask_exports)).SvgMask
) : (
// eslint-disable-next-line @typescript-eslint/no-var-requires
(init_ViewMask(), __toCommonJS(ViewMask_exports)).ViewMask
);
const size = maskRect && {
x: maskRect.width,
y: maskRect.height
};
const position = maskRect;
return /* @__PURE__ */ import_react5.default.createElement(
MaskComponent,
{
animated,
layout,
style: styles.overlayContainer,
size,
position,
easing,
animationDuration,
backdropColor,
svgMaskPath,
onClick: handleMaskClick,
currentStep
}
);
}
function renderTooltip() {
if (!currentStep) {
return null;
}
return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(
import_react_native6.Animated.View,
{
key: "stepNumber",
style: [
styles.stepNumberContainer,
{
left: animatedValues.stepNumberLeft,
top: import_react_native6.Animated.add(animatedValues.top, -STEP_NUMBER_RADIUS)
}
]
},
/* @__PURE__ */ import_react5.default.createElement(StepNumberComponent, null)
), !!arrowSize && /* @__PURE__ */ import_react5.default.createElement(import_react_native6.Animated.View, { key: "arrow", style: [styles.arrow, arrowStyles] }), /* @__PURE__ */ import_react5.default.createElement(
import_react_native6.Animated.View,
{
key: "tooltip",
style: [styles.tooltip, tooltipStyles, tooltipStyle]
},
/* @__PURE__ */ import_react5.default.createElement(TooltipComponent, { labels })
));
}
}
);
var floorify = (obj) => {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === "number") {
obj[key] = Math.floor(obj[key]);
}
});
};
var removeNan = (obj) => {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === "number" && isNaN(obj[key])) {
delete obj[key];
}
});
};
var sanitize = (obj) => {
floorify(obj);
removeNan(obj);
};
// src/contexts/CopilotProvider.tsx
init_style();
// src/hooks/useStateWithAwait.ts
var import_react6 = require("react");
var useStateWithAwait = (initialState) => {
const endPending = (0, import_react6.useRef)(() => {
});
const newDesiredValue = (0, import_react6.useRef)(initialState);
const [state, setState] = (0, import_react6.useState)(initialState);
const setStateWithAwait = (newState) => __async(void 0, null, function* () {
const pending = new Promise((resolve) => {
endPending.current = resolve;
});
newDesiredValue.current = newState;
setState(newState);
yield pending;
});
(0, import_react6.useEffect)(() => {
if (state === newDesiredValue.current) {
endPending.current();
}
}, [state]);
return [state, setStateWithAwait];
};
// src/hooks/useStepsMap.ts
var import_react7 = require("react");
var useStepsMap = () => {
const [currentStep, setCurrentStepState] = (0, import_react7.useState)(
void 0
);
const [steps, dispatch] = (0, import_react7.useReducer)((state, action) => {
var _a;
switch (action.type) {
case "register":
return __spreadProps(__spreadValues({}, state), {
[action.step.name]: action.step
});
case "unregister": {
const _b = state, { [_a = action.stepName]: _ } = _b, rest = __objRest(_b, [__restKey(_a)]);
return rest;
}
default:
return state;
}
}, {});
const orderedSteps = (0, import_react7.useMemo)(
() => Object.values(steps).sort((a, b) => a.order - b.order),
[steps]
);
const stepIndex = (0, import_react7.useCallback)(
(step = currentStep) => step ? orderedSteps.findIndex(
(stepCandidate) => stepCandidate.order === step.order
) : -1,
[currentStep, orderedSteps]
);
const currentStepNumber = (0, import_react7.useMemo)(
(step = currentStep) => stepIndex(step) + 1,
[currentStep, stepIndex]
);
const totalStepsNumber = (0, import_react7.useMemo)(() => orderedSteps.length, [orderedSteps]);
const getFirstStep = (0, import_react7.useCallback)(() => orderedSteps[0], [orderedSteps]);
const getLastStep = (0, import_react7.useCallback)(
() => orderedSteps[orderedSteps.length - 1],
[orderedSteps]
);
const getPrevStep = (0, import_react7.useCallback)(
(step = currentStep) => step && orderedSteps[stepIndex(step) - 1],
[currentStep, stepIndex, orderedSteps]
);
const getNextStep = (0, import_react7.useCallback)(
(step = currentStep) => step && orderedSteps[stepIndex(step) + 1],
[currentStep, stepIndex, orderedSteps]
);
const getNthStep = (0, import_react7.useCallback)(
(n) => orderedSteps[n - 1],
[orderedSteps]
);
const isFirstStep = (0, import_react7.useMemo)(
() => currentStep === getFirstStep(),
[currentStep, getFirstStep]
);
const isLastStep = (0, import_react7.useMemo)(
() => currentStep === getLastStep(),
[currentStep, getLastStep]
);
const registerStep = (0, import_react7.useCallback)((step) => {
dispatch({ type: "register", step });
}, []);
const unregisterStep = (0, import_react7.useCallback)((stepName) => {
dispatch({ type: "unregister", stepName });
}, []);
return {
currentStepNumber,
totalStepsNumber,
getFirstStep,
getLastStep,
getPrevStep,
getNextStep,
getNthStep,
isFirstStep,
isLastStep,
currentStep,
setCurrentStepState,
steps,
registerStep,
unregisterStep
};
};
// src/contexts/CopilotProvider.tsx
var MAX_START_TRIES = 120;
var CopilotContext = (0, import_react8.createContext)(void 0);
var CopilotProvider = (_a) => {
var _b = _a, {
verticalOffset = 0,
children
} = _b, rest = __objRest(_b, [
"verticalOffset",
"children"
]);
const startTries = (0, import_react8.useRef)(0);
const copilotEvents = (0, import_react8.useRef)((0, import_mitt.default)()).current;
const modal = (0, import_react8.useRef)(null);
const [visible, setVisibility] = useStateWithAwait(false);
const [scrollView, setScrollView] = (0, import_react8.useState)(null);
const {
currentStep,
currentStepNumber,
totalStepsNumber,
getFirstStep,
getPrevStep,
getNextStep,
getNthStep,
isFirstStep,
isLastStep,
setCurrentStepState,
steps,
registerStep,
unregisterStep
} = useStepsMap();
const moveModalToStep = (0, import_react8.useCallback)(
(step) => __async(void 0, null, function* () {
var _a2;
const size = yield step == null ? void 0 : step.measure();
if (!size) {
return;
}
yield (_a2 = modal.current) == null ? void 0 : _a2.animateMove({
width: size.width + OFFSET_WIDTH,
height: size.height + OFFSET_WIDTH,
x: size.x - OFFSET_WIDTH / 2,
y: size.y - OFFSET_WIDTH / 2 + verticalOffset
});
}),
[verticalOffset]
);
const setCurrentStep = (0, import_react8.useCallback)(
(step, move = true) => __async(void 0, null, function* () {
var _a2;
setCurrentStepState(step);
copilotEvents.emit("stepChange", step);
if (scrollView != null) {
const nodeHandle = (0, import_react_native7.findNodeHandle)(scrollView);
if (nodeHandle) {
(_a2 = step == null ? void 0 : step.wrapperRef.current) == null ? void 0 : _a2.measureLayout(
nodeHandle,
(_x, y, _w, h) => {
const yOffset = y > 0 ? y - h / 2 : 0;
scrollView.scrollTo({ y: yOffset, animated: false });
}
);
}
}
setTimeout(
() => {
if (move && step) {
void moveModalToStep(step);
}
},
scrollView != null ? 100 : 0
);
}),
[copilotEvents, moveModalToStep, scrollView, setCurrentStepState]
);
const start = (0, import_react8.useCallback)(
(fromStep, suppliedScrollView = null) => __async(void 0, null, function* () {
if (scrollView == null) {
setScrollView(suppliedScrollView);
}
const currentStep2 = fromStep ? steps[fromStep] : getFirstStep();
if (startTries.current > MAX_START_TRIES) {
startTries.current = 0;
return;
}
if (currentStep2 == null) {
startTries.current += 1;
requestAnimationFrame(() => {
void start(fromStep);
});
} else {
copilotEvents.emit("start");
yield setCurrentStep(currentStep2);
yield moveModalToStep(currentStep2);
yield setVisibility(true);
startTries.current = 0;
}
}),
[
copilotEvents,
getFirstStep,
moveModalToStep,
scrollView,
setCurrentStep,
setVisibility,
steps
]
);
const stop = (0, import_react8.useCallback)(() => __async(void 0, null, function* () {
yield setVisibility(false);
copilotEvents.emit("stop");
}), [copilotEvents, setVisibility]);
const next = (0, import_react8.useCallback)(() => __async(void 0, null, function* () {
yield setCurrentStep(getNextStep());
}), [getNextStep, setCurrentStep]);
const nth = (0, import_react8.useCallback)(
(n) => __async(void 0, null, function* () {
yield setCurrentStep(getNthStep(n));
}),
[getNthStep, setCurrentStep]
);
const prev = (0, import_react8.useCallback)(() => __async(void 0, null, function* () {
yield setCurrentStep(getPrevStep());
}), [getPrevStep, setCurrentStep]);
const value = (0, import_react8.useMemo)(
() => ({
registerStep,
unregisterStep,
currentStep,
start,
stop,
visible,
copilotEvents,
goToNext: next,
goToNth: nth,
goToPrev: prev,
isFirstStep,
isLastStep,
currentStepNumber,
totalStepsNumber
}),
[
registerStep,
unregisterStep,
currentStep,
start,
stop,
visible,
copilotEvents,
next,
nth,
prev,
isFirstStep,
isLastStep,
currentStepNumber,
totalStepsNumber
]
);
return /* @__PURE__ */ import_react8.default.createElement(CopilotContext.Provider, { value }, /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, /* @__PURE__ */ import_react8.default.createElement(
CopilotModal,
__spreadValues({
ref: modal
}, rest)
), children));
};
var useCopilot = () => {
const value = (0, import_react8.useContext)(CopilotContext);
if (value == null) {
throw new Error("You must wrap your app inside CopilotProvider");
}
return value;
};
// src/components/default-ui/StepNumber.tsx
init_style();
var StepNumber = () => {
const { currentStepNumber } = useCopilot();
return /* @__PURE__ */ import_react9.default.createElement(import_react_native8.View, { style: styles.stepNumber }, /* @__PURE__ */ import_react9.default.createElement(import_react_native8.Text, { style: styles.stepNumberText }, currentStepNumber));
};
// src/hocs/walkthroughable.tsx
var import_react10 = __toESM(require("react"));
function walkthroughable(WrappedComponent) {
const Component = (props) => {
const _a = props, { copilot } = _a, rest = __objRest(_a, ["copilot"]);
return /* @__PURE__ */ import_react10.default.createElement(WrappedComponent, __spreadValues(__spreadValues({}, copilot), rest));
};
Component.displayName = "Walkthroughable";
return Component;
}
// src/components/CopilotStep.tsx
var import_react11 = __toESM(require("react"));
var CopilotStep = ({
name,
order,
text,
children,
active = true
}) => {
const registeredName = (0, import_react11.useRef)(null);
const { registerStep, unregisterStep } = useCopilot();
const wrapperRef = import_react11.default.useRef(null);
const measure = () => __async(void 0, null, function* () {
return yield new Promise((resolve) => {
const measure2 = () => {
if (wrapperRef.current != null && "measure" in wrapperRef.current) {
wrapperRef.current.measure((_ox, _oy, width, height, x, y) => {
resolve({
x,
y,
width,
height
});
});
} else {
requestAnimationFrame(measure2);
}
};
measure2();
});
});
(0, import_react11.useEffect)(() => {
if (active) {
if (registeredName.current && registeredName.current !== name) {
unregisterStep(registeredName.current);
}
registerStep({
name,
text,
order,
measure,
wrapperRef,
visible: true
});
registeredName.current = name;
}
}, [name, order, text, registerStep, unregisterStep, active]);
(0, import_react11.useEffect)(() => {
if (active) {
return () => {
if (registeredName.current) {
unregisterStep(registeredName.current);
}
};
}
}, [name, unregisterStep, active]);
const copilotProps = (0, import_react11.useMemo)(
() => ({
ref: wrapperRef,
onLayout: () => {
}
// Android hack
}),
[]
);
return import_react11.default.cloneElement(children, { copilot: copilotProps });
};
// src/index.ts
var DefaultUI = {
StepNumber,
Tooltip
};
//# sourceMappingURL=index.js.map