@impulse-ui-native/toast 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -109,4 +109,4 @@ Toast uses `theme.components.toast` for surfaces, geometry, typography, actions
109
109
 
110
110
  ## Verification
111
111
 
112
- Run `pnpm --filter @impulse-ui-native/toast test` for Node checks of timer pause/resume and lifecycle cancellation. Native services and animation completion are mocked; use the Toast stories in the on-device Storybook host to verify rendering and touch interaction on iOS and Android.
112
+ Use the Toast stories in the on-device Storybook host to verify rendering, touch interaction, timer pause/resume, and lifecycle cancellation on iOS and Android.
@@ -8,7 +8,7 @@ import Animated, {
8
8
  withTiming
9
9
  } from "react-native-reanimated";
10
10
  import { useSafeAreaInsets } from "react-native-safe-area-context";
11
- import { useOverlayLayer } from "@impulse-ui-native/overlay";
11
+ import { OverlayOrder, useOverlayLayer } from "@impulse-ui-native/overlay";
12
12
  import { useComponentsTokens, useThemedStyles } from "@impulse-ui-native/theme";
13
13
  import { DefaultDuration, StackDuration } from "../constants/toast.constants";
14
14
  import { ToastContext } from "../contexts/toast.context";
@@ -25,7 +25,7 @@ const ToastRoot = memo(function ToastRoot2(props) {
25
25
  const insets = useSafeAreaInsets();
26
26
  const tokens = useComponentsTokens().toast;
27
27
  const lifecycle = useToastLifecycle(props, duration);
28
- const toastLayer = useOverlayLayer(id, void 0, "newest-first");
28
+ const toastLayer = useOverlayLayer(id, void 0, OverlayOrder.NewestFirst);
29
29
  const styles = useThemedStyles(themedStyles);
30
30
  const { progress } = lifecycle;
31
31
  const edgeOffset = tokens.edgeOffset;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/toast-root.tsx"],"sourcesContent":["import { memo, useEffect, useMemo } from \"react\";\nimport { StyleSheet } from \"react-native\";\nimport Animated, {\n cancelAnimation,\n useAnimatedStyle,\n useSharedValue,\n withTiming,\n} from \"react-native-reanimated\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\n\nimport type { AppTheme } from \"@impulse-ui-native/theme\";\nimport { useOverlayLayer } from \"@impulse-ui-native/overlay\";\nimport { useComponentsTokens, useThemedStyles } from \"@impulse-ui-native/theme\";\n\nimport type { ToastRootProps } from \"../types\";\nimport { DefaultDuration, StackDuration } from \"../constants/toast.constants\";\nimport { ToastContext } from \"../contexts/toast.context\";\nimport { useToastLifecycle } from \"../hooks/use-toast-lifecycle.hook\";\n\nexport const ToastRoot = memo(function ToastRoot(props: ToastRootProps) {\n const {\n id,\n children,\n placement = \"bottom\",\n tone = \"success\",\n duration = DefaultDuration,\n layer = 0,\n } = props;\n\n const insets = useSafeAreaInsets();\n\n const tokens = useComponentsTokens().toast;\n\n const lifecycle = useToastLifecycle(props, duration);\n const toastLayer = useOverlayLayer(id, undefined, \"newest-first\");\n const styles = useThemedStyles(themedStyles);\n const { progress } = lifecycle;\n const edgeOffset = tokens.edgeOffset;\n const stackDepth = Math.min(\n Math.max(0, toastLayer),\n Math.max(0, tokens.stackLimit - 1),\n );\n const animatedDepth = useSharedValue(stackDepth);\n const animatedStyle = useAnimatedStyle(() => ({\n opacity: progress.value,\n transform: [\n {\n translateY:\n (1 - progress.value) *\n (placement === \"top\" ? -edgeOffset : edgeOffset) +\n animatedDepth.value * tokens.gap,\n },\n { scale: Math.max(0, 1 - animatedDepth.value * tokens.stackScaleStep) },\n ],\n }));\n\n const context = useMemo(\n () => ({\n tone,\n interactive: lifecycle.interactive,\n close: lifecycle.close,\n }),\n [tone, lifecycle.interactive, lifecycle.close],\n );\n const containerStyle = useMemo(\n () => [\n styles.container,\n {\n left: insets.left + tokens.edgeOffset,\n right: insets.right + tokens.edgeOffset,\n [placement]:\n insets[placement] +\n (placement === \"top\" ? tokens.topOffset : tokens.edgeOffset),\n transformOrigin: \"center top\",\n zIndex: tokens.zIndexBase + layer,\n },\n animatedStyle,\n ],\n [styles.container, insets, tokens, placement, layer, animatedStyle],\n );\n\n useEffect(() => {\n animatedDepth.value = withTiming(stackDepth, { duration: StackDuration });\n\n return () => cancelAnimation(animatedDepth);\n }, [animatedDepth, stackDepth]);\n\n if (lifecycle.finished) {\n return null;\n }\n\n return (\n <ToastContext.Provider value={context}>\n <Animated.View\n pointerEvents={lifecycle.interactive ? \"auto\" : \"none\"}\n style={containerStyle}\n >\n {children}\n </Animated.View>\n </ToastContext.Provider>\n );\n});\n\nfunction themedStyles(theme: AppTheme) {\n const tokens = theme.components.toast;\n\n return StyleSheet.create({\n container: {\n position: \"absolute\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: tokens.gap,\n padding: tokens.padding,\n borderRadius: tokens.borderRadius,\n borderWidth: tokens.borderWidth,\n borderColor: tokens.borderColor,\n backgroundColor: tokens.backgroundColor,\n },\n });\n}\n"],"mappings":"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","names":["ToastRoot"]}
1
+ {"version":3,"sources":["../../src/components/toast-root.tsx"],"sourcesContent":["import { memo, useEffect, useMemo } from \"react\";\nimport { StyleSheet } from \"react-native\";\nimport Animated, {\n cancelAnimation,\n useAnimatedStyle,\n useSharedValue,\n withTiming,\n} from \"react-native-reanimated\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\n\nimport type { AppTheme } from \"@impulse-ui-native/theme\";\nimport { OverlayOrder, useOverlayLayer } from \"@impulse-ui-native/overlay\";\nimport { useComponentsTokens, useThemedStyles } from \"@impulse-ui-native/theme\";\n\nimport type { ToastRootProps } from \"../types\";\nimport { DefaultDuration, StackDuration } from \"../constants/toast.constants\";\nimport { ToastContext } from \"../contexts/toast.context\";\nimport { useToastLifecycle } from \"../hooks/use-toast-lifecycle.hook\";\n\nexport const ToastRoot = memo(function ToastRoot(props: ToastRootProps) {\n const {\n id,\n children,\n placement = \"bottom\",\n tone = \"success\",\n duration = DefaultDuration,\n layer = 0,\n } = props;\n\n const insets = useSafeAreaInsets();\n\n const tokens = useComponentsTokens().toast;\n\n const lifecycle = useToastLifecycle(props, duration);\n const toastLayer = useOverlayLayer(id, undefined, OverlayOrder.NewestFirst);\n const styles = useThemedStyles(themedStyles);\n const { progress } = lifecycle;\n const edgeOffset = tokens.edgeOffset;\n const stackDepth = Math.min(\n Math.max(0, toastLayer),\n Math.max(0, tokens.stackLimit - 1),\n );\n const animatedDepth = useSharedValue(stackDepth);\n const animatedStyle = useAnimatedStyle(() => ({\n opacity: progress.value,\n transform: [\n {\n translateY:\n (1 - progress.value) *\n (placement === \"top\" ? -edgeOffset : edgeOffset) +\n animatedDepth.value * tokens.gap,\n },\n { scale: Math.max(0, 1 - animatedDepth.value * tokens.stackScaleStep) },\n ],\n }));\n\n const context = useMemo(\n () => ({\n tone,\n interactive: lifecycle.interactive,\n close: lifecycle.close,\n }),\n [tone, lifecycle.interactive, lifecycle.close],\n );\n const containerStyle = useMemo(\n () => [\n styles.container,\n {\n left: insets.left + tokens.edgeOffset,\n right: insets.right + tokens.edgeOffset,\n [placement]:\n insets[placement] +\n (placement === \"top\" ? tokens.topOffset : tokens.edgeOffset),\n transformOrigin: \"center top\",\n zIndex: tokens.zIndexBase + layer,\n },\n animatedStyle,\n ],\n [styles.container, insets, tokens, placement, layer, animatedStyle],\n );\n\n useEffect(() => {\n animatedDepth.value = withTiming(stackDepth, { duration: StackDuration });\n\n return () => cancelAnimation(animatedDepth);\n }, [animatedDepth, stackDepth]);\n\n if (lifecycle.finished) {\n return null;\n }\n\n return (\n <ToastContext.Provider value={context}>\n <Animated.View\n pointerEvents={lifecycle.interactive ? \"auto\" : \"none\"}\n style={containerStyle}\n >\n {children}\n </Animated.View>\n </ToastContext.Provider>\n );\n});\n\nfunction themedStyles(theme: AppTheme) {\n const tokens = theme.components.toast;\n\n return StyleSheet.create({\n container: {\n position: \"absolute\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: tokens.gap,\n padding: tokens.padding,\n borderRadius: tokens.borderRadius,\n borderWidth: tokens.borderWidth,\n borderColor: tokens.borderColor,\n backgroundColor: tokens.backgroundColor,\n },\n });\n}\n"],"mappings":"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","names":["ToastRoot"]}
@@ -5,10 +5,9 @@ import {
5
5
  withTiming
6
6
  } from "react-native-reanimated";
7
7
  import { scheduleOnRN } from "react-native-worklets";
8
- import { useEventCallback } from "@impulse-ui-native/core";
8
+ import { useEventCallback, useTimer } from "@impulse-ui-native/core";
9
9
  import { useOverlayContext } from "@impulse-ui-native/overlay";
10
10
  import { EnterDuration, ExitDuration } from "../constants/toast.constants";
11
- import { useToastTimer } from "./use-toast-timer.hook";
12
11
  function useToastLifecycle(props, duration) {
13
12
  const {
14
13
  id,
@@ -51,7 +50,12 @@ function useToastLifecycle(props, duration) {
51
50
  }
52
51
  }
53
52
  );
54
- useToastTimer(entered && open && !finished, duration, close);
53
+ useTimer({
54
+ enabled: entered && open && !finished,
55
+ duration,
56
+ callback: close,
57
+ pauseOnBackground: true
58
+ });
55
59
  useEffect(() => {
56
60
  if (finished) {
57
61
  return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/use-toast-lifecycle.hook.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n cancelAnimation,\n useSharedValue,\n withTiming,\n} from \"react-native-reanimated\";\nimport { scheduleOnRN } from \"react-native-worklets\";\n\nimport { useEventCallback } from \"@impulse-ui-native/core\";\nimport { useOverlayContext } from \"@impulse-ui-native/overlay\";\n\nimport type { ToastRootProps } from \"../types\";\nimport { EnterDuration, ExitDuration } from \"../constants/toast.constants\";\nimport { useToastTimer } from \"./use-toast-timer.hook\";\n\nexport function useToastLifecycle(props: ToastRootProps, duration: number) {\n const {\n id,\n open = true,\n onOpen,\n onOpenFinished,\n onClose,\n onCloseFinished,\n } = props;\n\n const { store } = useOverlayContext();\n\n const [entered, setEntered] = useState(false);\n const [finished, setFinished] = useState(false);\n\n const opened = useRef(false);\n const closing = useRef(false);\n const settled = useRef(false);\n const transitionId = useRef(0);\n\n const progress = useSharedValue(0);\n\n const close = useEventCallback(() => store.close(id));\n\n const notifyOpen = useEventCallback(() => onOpen?.(id));\n\n const notifyEntered = useEventCallback(() => onOpenFinished?.(id));\n\n const notifyClose = useEventCallback(() => onClose?.(id));\n\n const finishClose = useEventCallback(() => {\n if (settled.current) {\n return;\n }\n\n settled.current = true;\n\n setFinished(true);\n\n onCloseFinished?.(id);\n });\n\n const finishTransition = useEventCallback(\n (completedTransitionId: number, entering: boolean) => {\n if (transitionId.current !== completedTransitionId || settled.current) {\n return;\n }\n\n if (entering) {\n setEntered(true);\n\n notifyEntered();\n } else {\n finishClose();\n }\n },\n );\n\n useToastTimer(entered && open && !finished, duration, close);\n\n useEffect(() => {\n if (finished) {\n return;\n }\n\n const currentTransitionId = ++transitionId.current;\n\n if (!open) {\n const firstClose = !closing.current;\n\n closing.current = true;\n\n if (!opened.current) {\n try {\n if (firstClose) {\n notifyClose();\n }\n } finally {\n finishClose();\n }\n\n return;\n }\n\n try {\n if (firstClose) {\n notifyClose();\n }\n } finally {\n progress.value = withTiming(\n 0,\n { duration: ExitDuration },\n (completed) => {\n if (completed) {\n scheduleOnRN(finishTransition, currentTransitionId, false);\n }\n },\n );\n }\n\n return () => {\n transitionId.current += 1;\n\n cancelAnimation(progress);\n };\n }\n\n const firstOpen = !opened.current;\n\n opened.current = true;\n\n try {\n if (firstOpen) {\n notifyOpen();\n }\n } finally {\n progress.value = withTiming(\n 1,\n { duration: EnterDuration },\n (completed) => {\n if (completed) {\n scheduleOnRN(finishTransition, currentTransitionId, true);\n }\n },\n );\n }\n\n return () => {\n transitionId.current += 1;\n\n cancelAnimation(progress);\n };\n }, [\n finishClose,\n finishTransition,\n finished,\n notifyClose,\n notifyEntered,\n notifyOpen,\n open,\n progress,\n ]);\n\n return { progress, close, finished, interactive: open && !finished };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/hooks/use-toast-lifecycle.hook.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n cancelAnimation,\n useSharedValue,\n withTiming,\n} from \"react-native-reanimated\";\nimport { scheduleOnRN } from \"react-native-worklets\";\n\nimport { useEventCallback, useTimer } from \"@impulse-ui-native/core\";\nimport { useOverlayContext } from \"@impulse-ui-native/overlay\";\n\nimport type { ToastRootProps } from \"../types\";\nimport { EnterDuration, ExitDuration } from \"../constants/toast.constants\";\n\nexport function useToastLifecycle(props: ToastRootProps, duration: number) {\n const {\n id,\n open = true,\n onOpen,\n onOpenFinished,\n onClose,\n onCloseFinished,\n } = props;\n\n const { store } = useOverlayContext();\n\n const [entered, setEntered] = useState(false);\n const [finished, setFinished] = useState(false);\n\n const opened = useRef(false);\n const closing = useRef(false);\n const settled = useRef(false);\n const transitionId = useRef(0);\n\n const progress = useSharedValue(0);\n\n const close = useEventCallback(() => store.close(id));\n\n const notifyOpen = useEventCallback(() => onOpen?.(id));\n\n const notifyEntered = useEventCallback(() => onOpenFinished?.(id));\n\n const notifyClose = useEventCallback(() => onClose?.(id));\n\n const finishClose = useEventCallback(() => {\n if (settled.current) {\n return;\n }\n\n settled.current = true;\n\n setFinished(true);\n\n onCloseFinished?.(id);\n });\n\n const finishTransition = useEventCallback(\n (completedTransitionId: number, entering: boolean) => {\n if (transitionId.current !== completedTransitionId || settled.current) {\n return;\n }\n\n if (entering) {\n setEntered(true);\n\n notifyEntered();\n } else {\n finishClose();\n }\n },\n );\n\n useTimer({\n enabled: entered && open && !finished,\n duration,\n callback: close,\n pauseOnBackground: true,\n });\n\n useEffect(() => {\n if (finished) {\n return;\n }\n\n const currentTransitionId = ++transitionId.current;\n\n if (!open) {\n const firstClose = !closing.current;\n\n closing.current = true;\n\n if (!opened.current) {\n try {\n if (firstClose) {\n notifyClose();\n }\n } finally {\n finishClose();\n }\n\n return;\n }\n\n try {\n if (firstClose) {\n notifyClose();\n }\n } finally {\n progress.value = withTiming(\n 0,\n { duration: ExitDuration },\n (completed) => {\n if (completed) {\n scheduleOnRN(finishTransition, currentTransitionId, false);\n }\n },\n );\n }\n\n return () => {\n transitionId.current += 1;\n\n cancelAnimation(progress);\n };\n }\n\n const firstOpen = !opened.current;\n\n opened.current = true;\n\n try {\n if (firstOpen) {\n notifyOpen();\n }\n } finally {\n progress.value = withTiming(\n 1,\n { duration: EnterDuration },\n (completed) => {\n if (completed) {\n scheduleOnRN(finishTransition, currentTransitionId, true);\n }\n },\n );\n }\n\n return () => {\n transitionId.current += 1;\n\n cancelAnimation(progress);\n };\n }, [\n finishClose,\n finishTransition,\n finished,\n notifyClose,\n notifyEntered,\n notifyOpen,\n open,\n progress,\n ]);\n\n return { progress, close, finished, interactive: open && !finished };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@impulse-ui-native/toast",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "description": "Compound toast notifications for ImpulseUI Native.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -27,11 +27,11 @@
27
27
  ],
28
28
  "sideEffects": false,
29
29
  "dependencies": {
30
- "@impulse-ui-native/theme": "6.1.0",
31
- "@impulse-ui-native/primitives": "6.1.0",
32
- "@impulse-ui-native/overlay": "6.1.0",
33
- "@impulse-ui-native/core": "6.1.0",
34
- "@impulse-ui-native/icon": "6.1.0"
30
+ "@impulse-ui-native/theme": "6.2.0",
31
+ "@impulse-ui-native/overlay": "6.2.0",
32
+ "@impulse-ui-native/primitives": "6.2.0",
33
+ "@impulse-ui-native/core": "6.2.0",
34
+ "@impulse-ui-native/icon": "6.2.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/react": "~19.2.14",
@@ -45,8 +45,8 @@
45
45
  "typescript": "~6.0.3",
46
46
  "react-native-svg": "15.15.4",
47
47
  "@impulse-ui-native/eslint-config": "1.0.0",
48
- "@impulse-ui-native/prettier-config": "1.0.0",
49
- "@impulse-ui-native/tsconfig": "1.0.0"
48
+ "@impulse-ui-native/tsconfig": "1.0.0",
49
+ "@impulse-ui-native/prettier-config": "1.0.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": ">=18",
@@ -64,7 +64,6 @@
64
64
  "build": "tsup --config tsup.config.ts",
65
65
  "typecheck": "tsc -p tsconfig.json --noEmit",
66
66
  "lint": "pnpm exec eslint . --fix",
67
- "format": "prettier --write \"./**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path ../../.prettierignore",
68
- "test": "node --test tests/*.test.mjs"
67
+ "format": "prettier --write \"./**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path ../../.prettierignore"
69
68
  }
70
69
  }
@@ -1,3 +0,0 @@
1
- declare function useToastTimer(active: boolean, duration: number, close: () => void): void;
2
-
3
- export { useToastTimer };
@@ -1,50 +0,0 @@
1
- import { useEffect } from "react";
2
- import { AppState } from "react-native";
3
- import { useEventCallback } from "@impulse-ui-native/core";
4
- function useToastTimer(active, duration, close) {
5
- const dismiss = useEventCallback(close);
6
- useEffect(() => {
7
- if (!active || !Number.isFinite(duration) || duration <= 0) {
8
- return;
9
- }
10
- let remaining = duration;
11
- let startedAt = 0;
12
- let timeout;
13
- const pause = () => {
14
- if (timeout === void 0) {
15
- return;
16
- }
17
- clearTimeout(timeout);
18
- timeout = void 0;
19
- remaining = Math.max(0, remaining - (Date.now() - startedAt));
20
- };
21
- const resume = () => {
22
- if (timeout !== void 0) {
23
- return;
24
- }
25
- startedAt = Date.now();
26
- timeout = setTimeout(() => {
27
- timeout = void 0;
28
- dismiss();
29
- }, remaining);
30
- };
31
- const subscription = AppState.addEventListener("change", (state) => {
32
- if (state === "active") {
33
- resume();
34
- } else {
35
- pause();
36
- }
37
- });
38
- if (AppState.currentState === "active") {
39
- resume();
40
- }
41
- return () => {
42
- pause();
43
- subscription.remove();
44
- };
45
- }, [active, dismiss, duration]);
46
- }
47
- export {
48
- useToastTimer
49
- };
50
- //# sourceMappingURL=use-toast-timer.hook.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/hooks/use-toast-timer.hook.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { AppState } from \"react-native\";\n\nimport { useEventCallback } from \"@impulse-ui-native/core\";\n\nexport function useToastTimer(\n active: boolean,\n duration: number,\n close: () => void,\n) {\n const dismiss = useEventCallback(close);\n\n useEffect(() => {\n if (!active || !Number.isFinite(duration) || duration <= 0) {\n return;\n }\n\n let remaining = duration;\n let startedAt = 0;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n\n const pause = () => {\n if (timeout === undefined) {\n return;\n }\n\n clearTimeout(timeout);\n\n timeout = undefined;\n remaining = Math.max(0, remaining - (Date.now() - startedAt));\n };\n\n const resume = () => {\n if (timeout !== undefined) {\n return;\n }\n\n startedAt = Date.now();\n timeout = setTimeout(() => {\n timeout = undefined;\n\n dismiss();\n }, remaining);\n };\n\n const subscription = AppState.addEventListener(\"change\", (state) => {\n if (state === \"active\") {\n resume();\n } else {\n pause();\n }\n });\n\n if (AppState.currentState === \"active\") {\n resume();\n }\n\n return () => {\n pause();\n subscription.remove();\n };\n }, [active, dismiss, duration]);\n}\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AAEzB,SAAS,wBAAwB;AAE1B,SAAS,cACd,QACA,UACA,OACA;AACA,QAAM,UAAU,iBAAiB,KAAK;AAEtC,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAAG;AAC1D;AAAA,IACF;AAEA,QAAI,YAAY;AAChB,QAAI,YAAY;AAChB,QAAI;AAEJ,UAAM,QAAQ,MAAM;AAClB,UAAI,YAAY,QAAW;AACzB;AAAA,MACF;AAEA,mBAAa,OAAO;AAEpB,gBAAU;AACV,kBAAY,KAAK,IAAI,GAAG,aAAa,KAAK,IAAI,IAAI,UAAU;AAAA,IAC9D;AAEA,UAAM,SAAS,MAAM;AACnB,UAAI,YAAY,QAAW;AACzB;AAAA,MACF;AAEA,kBAAY,KAAK,IAAI;AACrB,gBAAU,WAAW,MAAM;AACzB,kBAAU;AAEV,gBAAQ;AAAA,MACV,GAAG,SAAS;AAAA,IACd;AAEA,UAAM,eAAe,SAAS,iBAAiB,UAAU,CAAC,UAAU;AAClE,UAAI,UAAU,UAAU;AACtB,eAAO;AAAA,MACT,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAED,QAAI,SAAS,iBAAiB,UAAU;AACtC,aAAO;AAAA,IACT;AAEA,WAAO,MAAM;AACX,YAAM;AACN,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAChC;","names":[]}