@agimon-ai/po-native-components 0.0.0-alpha.45 → 0.0.0-alpha.47

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.
@@ -229,7 +229,7 @@ function AnnotationSurface({ children, marks, selectedId, onPlace, onDraw, onSel
229
229
  },
230
230
  onPress: place,
231
231
  children: /* @__PURE__ */ jsxs(View, {
232
- pointerEvents: zoomable ? "none" : "auto",
232
+ pointerEvents: zoomable && viewport.scale > 1 ? "none" : "auto",
233
233
  style: zoomable ? { transform: [
234
234
  { translateX: viewport.x },
235
235
  { translateY: viewport.y },
@@ -1 +1 @@
1
- {"version":3,"file":"AnnotationSurface.mjs","names":["ReactNode","useEffect","useRef","useState","GestureResponderEvent","StyleProp","ViewStyle","StyleSheet","fontOf","selectionTick","AnnotationMark","id","x","y","label","description","points","AnnotationSurfaceProps","children","marks","selectedId","onPlace","point","onDraw","onSelectMark","accessibilityLabel","zoomable","onViewportChange","viewport","width","height","zoom","originX","originY","style","clamp","value","Math","min","max","MAX_POINTS","Point","Stroke","bounded","slice","map","index","previous","every","Number","isFinite","dx","dy","length","hypot","styles","segment","left","top","transform","rotate","atan2","AnnotationSurface","size","setSize","setViewport","scale","pinch","distance","offsetX","offsetY","suppressTap","reportViewport","next","dimensions","resetViewport","current","sourcePoint","moveViewport","event","first","second","nativeEvent","touches","pageX","pageY","start","maxX","maxY","live","setLive","stroke","draw","drawing","undefined","cancel","append","locationX","locationY","last","push","place","known","frame","overflow","layout","overlay","name","actionName","translateX","translateY","mark","some","selected","hit","pin","pinSelected","labelSelected","HIT","PIN","create","theme","position","alignSelf","right","bottom","borderRadius","backgroundColor","colors","magenta","uni__dependencies","marginLeft","marginTop","alignItems","justifyContent","borderWidth","borderColor","bg","color","fontFamily","sans","fontSize","sm","fontWeight","semibold"],"sources":["../../src/components/AnnotationSurface.tsx"],"sourcesContent":["import { type ReactNode, useEffect, useRef, useState } from 'react';\nimport { type GestureResponderEvent, Pressable, type StyleProp, Text, View, type ViewStyle } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { fontOf } from '../lib/fonts';\nimport { selectionTick } from '../lib/haptics';\n\n/** One note pinned to the content, with an optional stroke, in fractions (0 to 1) of the surface's width and height. */\nexport interface AnnotationMark {\n id: string;\n x: number;\n y: number;\n /** The short text drawn on the pin, such as \"1\". */\n label: string;\n /** What a screen reader announces for the pin, such as the note text. */\n description?: string;\n /** Optional normalized freehand stroke. At most 128 finite points are rendered. */\n points?: readonly { x: number; y: number }[];\n}\n\nexport interface AnnotationSurfaceProps {\n /** The content being marked up: an image, a page, a block of text. */\n children: ReactNode;\n marks: readonly AnnotationMark[];\n selectedId?: string;\n /** A tap places a note there. Without onPlace or onDraw the surface is read-only. */\n onPlace?: (point: { x: number; y: number }) => void;\n /** Enables drawing instead of tap placement. Receives 2 to 128 finite [0,1] points only on a completed stroke. */\n onDraw?: (points: { x: number; y: number }[]) => void;\n onSelectMark?: (id: string) => void;\n /** What the content is, such as \"hero.png\". */\n accessibilityLabel: string;\n /** Image surfaces can pinch and move with two fingers on both native platforms. */\n zoomable?: boolean;\n /** Viewport dimensions and source-layout-to-viewport transform, all in RN layout units, not raster pixels. */\n onViewportChange?: (viewport: {\n width: number;\n height: number;\n zoom: number;\n originX: number;\n originY: number;\n }) => void;\n style?: StyleProp<ViewStyle>;\n}\n\nconst clamp = (value: number) => Math.min(1, Math.max(0, value));\nconst MAX_POINTS = 128;\ntype Point = { x: number; y: number };\n\nfunction Stroke({ points, width, height }: { points: readonly Point[]; width: number; height: number }) {\n // Slice before processing so even externally supplied strokes have bounded work.\n const bounded = points.slice(0, MAX_POINTS);\n return bounded.map((point, index) => {\n const previous = bounded[index - 1];\n if (!previous || ![point.x, point.y, previous.x, previous.y].every(Number.isFinite)) return null;\n const x = clamp(previous.x) * width;\n const y = clamp(previous.y) * height;\n const dx = clamp(point.x) * width - x;\n const dy = clamp(point.y) * height - y;\n const length = Math.hypot(dx, dy);\n return (\n <View\n key={index}\n style={[\n styles.segment,\n {\n left: x + dx / 2 - length / 2,\n top: y + dy / 2 - 1.5,\n width: length,\n transform: [{ rotate: `${Math.atan2(dy, dx)}rad` }],\n },\n ]}\n />\n );\n });\n}\n\n/**\n * Tap to pin a note on whatever it wraps. Each pin is its own 44pt button, so a\n * screen reader can reach every note; the surface's own element sits behind the\n * content, so text and controls inside stay readable, and activating it places a\n * note at its centre.\n */\nexport function AnnotationSurface({\n children,\n marks,\n selectedId,\n onPlace,\n onDraw,\n onSelectMark,\n accessibilityLabel,\n zoomable = false,\n onViewportChange,\n style,\n}: AnnotationSurfaceProps) {\n const [size, setSize] = useState({ width: 0, height: 0 });\n const [viewport, setViewport] = useState({ scale: 1, x: 0, y: 0 });\n const transform = useRef(viewport);\n const pinch = useRef<{\n distance: number;\n x: number;\n y: number;\n scale: number;\n offsetX: number;\n offsetY: number;\n } | null>(null);\n const suppressTap = useRef(false);\n const reportViewport = (next: typeof viewport, dimensions = size) => {\n onViewportChange?.({\n ...dimensions,\n zoom: next.scale,\n originX: (dimensions.width * (1 - next.scale)) / 2 + next.x,\n originY: (dimensions.height * (1 - next.scale)) / 2 + next.y,\n });\n };\n const resetViewport = () => {\n pinch.current = null;\n transform.current = { scale: 1, x: 0, y: 0 };\n setViewport(transform.current);\n reportViewport(transform.current);\n };\n useEffect(() => {\n resetViewport();\n }, [zoomable]);\n useEffect(() => {\n reportViewport(viewport);\n }, [viewport, size, onViewportChange]);\n const sourcePoint = (x: number, y: number) => {\n const current = transform.current;\n return {\n x: clamp((x - size.width / 2 - current.x) / current.scale / size.width + 0.5),\n y: clamp((y - size.height / 2 - current.y) / current.scale / size.height + 0.5),\n };\n };\n const moveViewport = (event: GestureResponderEvent) => {\n const [first, second] = event.nativeEvent.touches;\n if (!zoomable || !first || !second || size.width <= 0 || size.height <= 0) {\n pinch.current = null;\n return;\n }\n const x = (first.pageX + second.pageX) / 2;\n const y = (first.pageY + second.pageY) / 2;\n const distance = Math.hypot(first.pageX - second.pageX, first.pageY - second.pageY);\n if (![x, y, distance].every(Number.isFinite) || distance <= 0) {\n pinch.current = null;\n return;\n }\n if (!pinch.current) {\n pinch.current = {\n x,\n y,\n distance,\n scale: transform.current.scale,\n offsetX: transform.current.x,\n offsetY: transform.current.y,\n };\n return;\n }\n const start = pinch.current;\n const scale = Math.max(1, Math.min(4, (start.scale * distance) / start.distance));\n const maxX = (size.width * (scale - 1)) / 2;\n const maxY = (size.height * (scale - 1)) / 2;\n transform.current = {\n scale,\n x: Math.max(-maxX, Math.min(maxX, start.offsetX + x - start.x)),\n y: Math.max(-maxY, Math.min(maxY, start.offsetY + y - start.y)),\n };\n // Publish synchronously so capture can reject a gesture even before React renders it.\n reportViewport(transform.current);\n setViewport(transform.current);\n };\n const [live, setLive] = useState<readonly Point[]>([]);\n const stroke = useRef<Point[] | null>(null);\n // Callers pass inline callbacks, so a new identity is not a new tool: only switching drawing off or on cancels.\n const draw = useRef(onDraw);\n draw.current = onDraw;\n const drawing = onDraw !== undefined;\n const cancel = () => {\n stroke.current = null;\n setLive([]);\n };\n useEffect(() => {\n stroke.current = null;\n setLive([]);\n return () => {\n stroke.current = null;\n };\n }, [drawing]);\n const append = (event: GestureResponderEvent) => {\n if (!stroke.current || !draw.current) return cancel();\n const { locationX, locationY, touches } = event.nativeEvent;\n if (\n touches.length > 1 ||\n ![locationX, locationY, size.width, size.height].every(Number.isFinite) ||\n size.width <= 0 ||\n size.height <= 0\n )\n return cancel();\n const point = sourcePoint(locationX, locationY);\n const last = stroke.current[stroke.current.length - 1];\n if (last?.x === point.x && last.y === point.y) return;\n // Keep the start and a bounded prefix; update the last point once full.\n if (stroke.current.length < MAX_POINTS) stroke.current.push(point);\n else stroke.current[MAX_POINTS - 1] = point;\n setLive([...stroke.current]);\n };\n const place = (event: GestureResponderEvent) => {\n if (!onPlace || onDraw || suppressTap.current) return;\n const { locationX, locationY } = event.nativeEvent;\n const known = size.width > 0 && size.height > 0 && Number.isFinite(locationX) && Number.isFinite(locationY);\n selectionTick();\n onPlace(known ? sourcePoint(locationX, locationY) : { x: 0.5, y: 0.5 });\n };\n return (\n <View\n style={[styles.frame, zoomable && { overflow: 'hidden' }, style]}\n onStartShouldSetResponderCapture={(event) => zoomable && event.nativeEvent.touches.length > 1}\n onMoveShouldSetResponderCapture={(event) => zoomable && event.nativeEvent.touches.length > 1}\n onResponderGrant={\n zoomable\n ? (event) => {\n cancel();\n suppressTap.current = true;\n moveViewport(event);\n }\n : undefined\n }\n onResponderMove={zoomable ? moveViewport : undefined}\n onResponderRelease={\n zoomable\n ? () => {\n pinch.current = null;\n }\n : undefined\n }\n onResponderTerminationRequest={() => true}\n onResponderTerminate={\n zoomable\n ? () => {\n pinch.current = null;\n cancel();\n }\n : undefined\n }\n onLayout={(event) => {\n cancel();\n resetViewport();\n const dimensions = { width: event.nativeEvent.layout.width, height: event.nativeEvent.layout.height };\n reportViewport(transform.current, dimensions);\n setSize(dimensions);\n }}\n >\n <View\n pointerEvents=\"none\"\n style={styles.overlay}\n accessible\n accessibilityRole={onPlace && !onDraw ? 'button' : 'image'}\n accessibilityLabel={accessibilityLabel}\n accessibilityHint={\n onPlace && !onDraw\n ? 'Double tap to add a note here. Use the Note on whole image action to mark the whole image.'\n : undefined\n }\n accessibilityActions={\n onPlace\n ? [...(onDraw ? [] : [{ name: 'activate' }]), { name: 'noteWholeImage', label: 'Note on whole image' }]\n : undefined\n }\n onAccessibilityAction={(event) => {\n const { actionName } = event.nativeEvent;\n if (actionName === 'noteWholeImage' || (actionName === 'activate' && !onDraw)) onPlace?.({ x: 0.5, y: 0.5 });\n }}\n />\n <Pressable\n accessible={false}\n disabled={!onPlace || !!onDraw}\n onPressIn={() => {\n if (!pinch.current) suppressTap.current = false;\n }}\n onPress={place}\n >\n <View\n pointerEvents={zoomable ? 'none' : 'auto'}\n style={\n zoomable\n ? { transform: [{ translateX: viewport.x }, { translateY: viewport.y }, { scale: viewport.scale }] }\n : undefined\n }\n >\n {children}\n <View pointerEvents=\"none\" accessible={false} style={styles.overlay}>\n {marks.map(\n (mark) =>\n mark.points && <Stroke key={mark.id} points={mark.points} width={size.width} height={size.height} />,\n )}\n {onDraw && <Stroke points={live} width={size.width} height={size.height} />}\n </View>\n </View>\n </Pressable>\n {onDraw && (\n <View\n accessible={false}\n // Only JS responder props, which Fabric may flatten out of the native tree; keep it hit-testable.\n collapsable={false}\n style={styles.overlay}\n onStartShouldSetResponder={() => true}\n onResponderGrant={(event) => {\n stroke.current = [];\n append(event);\n }}\n onResponderStart={(event) => {\n if (event.nativeEvent.touches.length > 1) cancel();\n }}\n onResponderMove={append}\n onResponderRelease={(event) => {\n append(event);\n const points = stroke.current;\n cancel();\n if (\n points &&\n points.length >= 2 &&\n points.some((point) => point.x !== points[0].x || point.y !== points[0].y)\n ) {\n draw.current?.(points);\n }\n }}\n onResponderTerminationRequest={() => true}\n onResponderTerminate={cancel}\n />\n )}\n {marks.map((mark) => {\n const selected = mark.id === selectedId;\n return (\n <Pressable\n key={mark.id}\n accessibilityRole=\"button\"\n accessibilityLabel={`Note ${mark.label}${mark.description ? `: ${mark.description}` : ''}`}\n accessibilityState={{ selected }}\n onPress={() => onSelectMark?.(mark.id)}\n style={[\n styles.hit,\n zoomable\n ? {\n left: size.width / 2 + (clamp(mark.x) - 0.5) * size.width * viewport.scale + viewport.x,\n top: size.height / 2 + (clamp(mark.y) - 0.5) * size.height * viewport.scale + viewport.y,\n }\n : { left: `${clamp(mark.x) * 100}%`, top: `${clamp(mark.y) * 100}%` },\n ]}\n >\n <View style={[styles.pin, selected && styles.pinSelected]}>\n <Text style={[styles.label, selected && styles.labelSelected]}>{mark.label}</Text>\n </View>\n </Pressable>\n );\n })}\n </View>\n );\n}\n\nconst HIT = 44;\nconst PIN = 28;\n\nconst styles = StyleSheet.create((theme) => ({\n frame: { position: 'relative', alignSelf: 'stretch' },\n overlay: { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 },\n segment: { position: 'absolute', height: 3, borderRadius: 1.5, backgroundColor: theme.colors.magenta },\n hit: {\n position: 'absolute',\n width: HIT,\n height: HIT,\n marginLeft: -HIT / 2,\n marginTop: -HIT / 2,\n alignItems: 'center',\n justifyContent: 'center',\n },\n pin: {\n width: PIN,\n height: PIN,\n borderRadius: PIN / 2,\n alignItems: 'center',\n justifyContent: 'center',\n borderWidth: 2,\n borderColor: theme.colors.magenta,\n backgroundColor: theme.colors.bg,\n },\n pinSelected: { backgroundColor: theme.colors.magenta },\n label: {\n color: theme.colors.magenta,\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.sm,\n fontWeight: theme.fontWeight.semibold,\n },\n labelSelected: { color: theme.colors.bg },\n}));\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnnotationSurface.mjs","names":["ReactNode","useEffect","useRef","useState","GestureResponderEvent","StyleProp","ViewStyle","StyleSheet","fontOf","selectionTick","AnnotationMark","id","x","y","label","description","points","AnnotationSurfaceProps","children","marks","selectedId","onPlace","point","onDraw","onSelectMark","accessibilityLabel","zoomable","onViewportChange","viewport","width","height","zoom","originX","originY","style","clamp","value","Math","min","max","MAX_POINTS","Point","Stroke","bounded","slice","map","index","previous","every","Number","isFinite","dx","dy","length","hypot","styles","segment","left","top","transform","rotate","atan2","AnnotationSurface","size","setSize","setViewport","scale","pinch","distance","offsetX","offsetY","suppressTap","reportViewport","next","dimensions","resetViewport","current","sourcePoint","moveViewport","event","first","second","nativeEvent","touches","pageX","pageY","start","maxX","maxY","live","setLive","stroke","draw","drawing","undefined","cancel","append","locationX","locationY","last","push","place","known","frame","overflow","layout","overlay","name","actionName","translateX","translateY","mark","some","selected","hit","pin","pinSelected","labelSelected","HIT","PIN","create","theme","position","alignSelf","right","bottom","borderRadius","backgroundColor","colors","magenta","uni__dependencies","marginLeft","marginTop","alignItems","justifyContent","borderWidth","borderColor","bg","color","fontFamily","sans","fontSize","sm","fontWeight","semibold"],"sources":["../../src/components/AnnotationSurface.tsx"],"sourcesContent":["import { type ReactNode, useEffect, useRef, useState } from 'react';\nimport { type GestureResponderEvent, Pressable, type StyleProp, Text, View, type ViewStyle } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { fontOf } from '../lib/fonts';\nimport { selectionTick } from '../lib/haptics';\n\n/** One note pinned to the content, with an optional stroke, in fractions (0 to 1) of the surface's width and height. */\nexport interface AnnotationMark {\n id: string;\n x: number;\n y: number;\n /** The short text drawn on the pin, such as \"1\". */\n label: string;\n /** What a screen reader announces for the pin, such as the note text. */\n description?: string;\n /** Optional normalized freehand stroke. At most 128 finite points are rendered. */\n points?: readonly { x: number; y: number }[];\n}\n\nexport interface AnnotationSurfaceProps {\n /** The content being marked up: an image, a page, a block of text. */\n children: ReactNode;\n marks: readonly AnnotationMark[];\n selectedId?: string;\n /** A tap places a note there. Without onPlace or onDraw the surface is read-only. */\n onPlace?: (point: { x: number; y: number }) => void;\n /** Enables drawing instead of tap placement. Receives 2 to 128 finite [0,1] points only on a completed stroke. */\n onDraw?: (points: { x: number; y: number }[]) => void;\n onSelectMark?: (id: string) => void;\n /** What the content is, such as \"hero.png\". */\n accessibilityLabel: string;\n /** Image surfaces can pinch and move with two fingers on both native platforms. */\n zoomable?: boolean;\n /** Viewport dimensions and source-layout-to-viewport transform, all in RN layout units, not raster pixels. */\n onViewportChange?: (viewport: {\n width: number;\n height: number;\n zoom: number;\n originX: number;\n originY: number;\n }) => void;\n style?: StyleProp<ViewStyle>;\n}\n\nconst clamp = (value: number) => Math.min(1, Math.max(0, value));\nconst MAX_POINTS = 128;\ntype Point = { x: number; y: number };\n\nfunction Stroke({ points, width, height }: { points: readonly Point[]; width: number; height: number }) {\n // Slice before processing so even externally supplied strokes have bounded work.\n const bounded = points.slice(0, MAX_POINTS);\n return bounded.map((point, index) => {\n const previous = bounded[index - 1];\n if (!previous || ![point.x, point.y, previous.x, previous.y].every(Number.isFinite)) return null;\n const x = clamp(previous.x) * width;\n const y = clamp(previous.y) * height;\n const dx = clamp(point.x) * width - x;\n const dy = clamp(point.y) * height - y;\n const length = Math.hypot(dx, dy);\n return (\n <View\n key={index}\n style={[\n styles.segment,\n {\n left: x + dx / 2 - length / 2,\n top: y + dy / 2 - 1.5,\n width: length,\n transform: [{ rotate: `${Math.atan2(dy, dx)}rad` }],\n },\n ]}\n />\n );\n });\n}\n\n/**\n * Tap to pin a note on whatever it wraps. Each pin is its own 44pt button, so a\n * screen reader can reach every note; the surface's own element sits behind the\n * content, so text and controls inside stay readable, and activating it places a\n * note at its centre.\n */\nexport function AnnotationSurface({\n children,\n marks,\n selectedId,\n onPlace,\n onDraw,\n onSelectMark,\n accessibilityLabel,\n zoomable = false,\n onViewportChange,\n style,\n}: AnnotationSurfaceProps) {\n const [size, setSize] = useState({ width: 0, height: 0 });\n const [viewport, setViewport] = useState({ scale: 1, x: 0, y: 0 });\n const transform = useRef(viewport);\n const pinch = useRef<{\n distance: number;\n x: number;\n y: number;\n scale: number;\n offsetX: number;\n offsetY: number;\n } | null>(null);\n const suppressTap = useRef(false);\n const reportViewport = (next: typeof viewport, dimensions = size) => {\n onViewportChange?.({\n ...dimensions,\n zoom: next.scale,\n originX: (dimensions.width * (1 - next.scale)) / 2 + next.x,\n originY: (dimensions.height * (1 - next.scale)) / 2 + next.y,\n });\n };\n const resetViewport = () => {\n pinch.current = null;\n transform.current = { scale: 1, x: 0, y: 0 };\n setViewport(transform.current);\n reportViewport(transform.current);\n };\n useEffect(() => {\n resetViewport();\n }, [zoomable]);\n useEffect(() => {\n reportViewport(viewport);\n }, [viewport, size, onViewportChange]);\n const sourcePoint = (x: number, y: number) => {\n const current = transform.current;\n return {\n x: clamp((x - size.width / 2 - current.x) / current.scale / size.width + 0.5),\n y: clamp((y - size.height / 2 - current.y) / current.scale / size.height + 0.5),\n };\n };\n const moveViewport = (event: GestureResponderEvent) => {\n const [first, second] = event.nativeEvent.touches;\n if (!zoomable || !first || !second || size.width <= 0 || size.height <= 0) {\n pinch.current = null;\n return;\n }\n const x = (first.pageX + second.pageX) / 2;\n const y = (first.pageY + second.pageY) / 2;\n const distance = Math.hypot(first.pageX - second.pageX, first.pageY - second.pageY);\n if (![x, y, distance].every(Number.isFinite) || distance <= 0) {\n pinch.current = null;\n return;\n }\n if (!pinch.current) {\n pinch.current = {\n x,\n y,\n distance,\n scale: transform.current.scale,\n offsetX: transform.current.x,\n offsetY: transform.current.y,\n };\n return;\n }\n const start = pinch.current;\n const scale = Math.max(1, Math.min(4, (start.scale * distance) / start.distance));\n const maxX = (size.width * (scale - 1)) / 2;\n const maxY = (size.height * (scale - 1)) / 2;\n transform.current = {\n scale,\n x: Math.max(-maxX, Math.min(maxX, start.offsetX + x - start.x)),\n y: Math.max(-maxY, Math.min(maxY, start.offsetY + y - start.y)),\n };\n // Publish synchronously so capture can reject a gesture even before React renders it.\n reportViewport(transform.current);\n setViewport(transform.current);\n };\n const [live, setLive] = useState<readonly Point[]>([]);\n const stroke = useRef<Point[] | null>(null);\n // Callers pass inline callbacks, so a new identity is not a new tool: only switching drawing off or on cancels.\n const draw = useRef(onDraw);\n draw.current = onDraw;\n const drawing = onDraw !== undefined;\n const cancel = () => {\n stroke.current = null;\n setLive([]);\n };\n useEffect(() => {\n stroke.current = null;\n setLive([]);\n return () => {\n stroke.current = null;\n };\n }, [drawing]);\n const append = (event: GestureResponderEvent) => {\n if (!stroke.current || !draw.current) return cancel();\n const { locationX, locationY, touches } = event.nativeEvent;\n if (\n touches.length > 1 ||\n ![locationX, locationY, size.width, size.height].every(Number.isFinite) ||\n size.width <= 0 ||\n size.height <= 0\n )\n return cancel();\n const point = sourcePoint(locationX, locationY);\n const last = stroke.current[stroke.current.length - 1];\n if (last?.x === point.x && last.y === point.y) return;\n // Keep the start and a bounded prefix; update the last point once full.\n if (stroke.current.length < MAX_POINTS) stroke.current.push(point);\n else stroke.current[MAX_POINTS - 1] = point;\n setLive([...stroke.current]);\n };\n const place = (event: GestureResponderEvent) => {\n if (!onPlace || onDraw || suppressTap.current) return;\n const { locationX, locationY } = event.nativeEvent;\n const known = size.width > 0 && size.height > 0 && Number.isFinite(locationX) && Number.isFinite(locationY);\n selectionTick();\n onPlace(known ? sourcePoint(locationX, locationY) : { x: 0.5, y: 0.5 });\n };\n return (\n <View\n style={[styles.frame, zoomable && { overflow: 'hidden' }, style]}\n onStartShouldSetResponderCapture={(event) => zoomable && event.nativeEvent.touches.length > 1}\n onMoveShouldSetResponderCapture={(event) => zoomable && event.nativeEvent.touches.length > 1}\n onResponderGrant={\n zoomable\n ? (event) => {\n cancel();\n suppressTap.current = true;\n moveViewport(event);\n }\n : undefined\n }\n onResponderMove={zoomable ? moveViewport : undefined}\n onResponderRelease={\n zoomable\n ? () => {\n pinch.current = null;\n }\n : undefined\n }\n onResponderTerminationRequest={() => true}\n onResponderTerminate={\n zoomable\n ? () => {\n pinch.current = null;\n cancel();\n }\n : undefined\n }\n onLayout={(event) => {\n cancel();\n resetViewport();\n const dimensions = { width: event.nativeEvent.layout.width, height: event.nativeEvent.layout.height };\n reportViewport(transform.current, dimensions);\n setSize(dimensions);\n }}\n >\n <View\n pointerEvents=\"none\"\n style={styles.overlay}\n accessible\n accessibilityRole={onPlace && !onDraw ? 'button' : 'image'}\n accessibilityLabel={accessibilityLabel}\n accessibilityHint={\n onPlace && !onDraw\n ? 'Double tap to add a note here. Use the Note on whole image action to mark the whole image.'\n : undefined\n }\n accessibilityActions={\n onPlace\n ? [...(onDraw ? [] : [{ name: 'activate' }]), { name: 'noteWholeImage', label: 'Note on whole image' }]\n : undefined\n }\n onAccessibilityAction={(event) => {\n const { actionName } = event.nativeEvent;\n if (actionName === 'noteWholeImage' || (actionName === 'activate' && !onDraw)) onPlace?.({ x: 0.5, y: 0.5 });\n }}\n />\n <Pressable\n accessible={false}\n disabled={!onPlace || !!onDraw}\n onPressIn={() => {\n if (!pinch.current) suppressTap.current = false;\n }}\n onPress={place}\n >\n {/* Zoomed content is only a picture, so every touch maps through the frame. At 1x it keeps its own controls, such as PDF page buttons. */}\n <View\n pointerEvents={zoomable && viewport.scale > 1 ? 'none' : 'auto'}\n style={\n zoomable\n ? { transform: [{ translateX: viewport.x }, { translateY: viewport.y }, { scale: viewport.scale }] }\n : undefined\n }\n >\n {children}\n <View pointerEvents=\"none\" accessible={false} style={styles.overlay}>\n {marks.map(\n (mark) =>\n mark.points && <Stroke key={mark.id} points={mark.points} width={size.width} height={size.height} />,\n )}\n {onDraw && <Stroke points={live} width={size.width} height={size.height} />}\n </View>\n </View>\n </Pressable>\n {onDraw && (\n <View\n accessible={false}\n // Only JS responder props, which Fabric may flatten out of the native tree; keep it hit-testable.\n collapsable={false}\n style={styles.overlay}\n onStartShouldSetResponder={() => true}\n onResponderGrant={(event) => {\n stroke.current = [];\n append(event);\n }}\n onResponderStart={(event) => {\n if (event.nativeEvent.touches.length > 1) cancel();\n }}\n onResponderMove={append}\n onResponderRelease={(event) => {\n append(event);\n const points = stroke.current;\n cancel();\n if (\n points &&\n points.length >= 2 &&\n points.some((point) => point.x !== points[0].x || point.y !== points[0].y)\n ) {\n draw.current?.(points);\n }\n }}\n onResponderTerminationRequest={() => true}\n onResponderTerminate={cancel}\n />\n )}\n {marks.map((mark) => {\n const selected = mark.id === selectedId;\n return (\n <Pressable\n key={mark.id}\n accessibilityRole=\"button\"\n accessibilityLabel={`Note ${mark.label}${mark.description ? `: ${mark.description}` : ''}`}\n accessibilityState={{ selected }}\n onPress={() => onSelectMark?.(mark.id)}\n style={[\n styles.hit,\n zoomable\n ? {\n left: size.width / 2 + (clamp(mark.x) - 0.5) * size.width * viewport.scale + viewport.x,\n top: size.height / 2 + (clamp(mark.y) - 0.5) * size.height * viewport.scale + viewport.y,\n }\n : { left: `${clamp(mark.x) * 100}%`, top: `${clamp(mark.y) * 100}%` },\n ]}\n >\n <View style={[styles.pin, selected && styles.pinSelected]}>\n <Text style={[styles.label, selected && styles.labelSelected]}>{mark.label}</Text>\n </View>\n </Pressable>\n );\n })}\n </View>\n );\n}\n\nconst HIT = 44;\nconst PIN = 28;\n\nconst styles = StyleSheet.create((theme) => ({\n frame: { position: 'relative', alignSelf: 'stretch' },\n overlay: { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 },\n segment: { position: 'absolute', height: 3, borderRadius: 1.5, backgroundColor: theme.colors.magenta },\n hit: {\n position: 'absolute',\n width: HIT,\n height: HIT,\n marginLeft: -HIT / 2,\n marginTop: -HIT / 2,\n alignItems: 'center',\n justifyContent: 'center',\n },\n pin: {\n width: PIN,\n height: PIN,\n borderRadius: PIN / 2,\n alignItems: 'center',\n justifyContent: 'center',\n borderWidth: 2,\n borderColor: theme.colors.magenta,\n backgroundColor: theme.colors.bg,\n },\n pinSelected: { backgroundColor: theme.colors.magenta },\n label: {\n color: theme.colors.magenta,\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.sm,\n fontWeight: theme.fontWeight.semibold,\n },\n labelSelected: { color: theme.colors.bg },\n}));\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"List.d.mts","names":[],"sources":["../../src/components/List.tsx"],"mappings":";;;iBAoBiB,UAAU;EACzB,eAAe;EACf,aAAa,MAAM,GAAG,kBAAkB;;EAExC,eAAe,MAAM,GAAG;;;;;EAKxB;;EAEA;;EAEA,QAAQ;EACR,SAAS;EACT,SAAS;EACT;EACA;;EAEA,QAAQ,UAAU;;EAElB;;wBAOc,KAAK,KACnB,MACA,YACA,cACA,eACA,YACA,OACA,QACA,QACA,cACA,oBACA,OACA,kBACC,UAAU,qBAAE,IAAA;iBA6BE;EACf,OAAO;;EAEP,cAAc;;EAEd,UAAU;;EAEV,WAAW;;EAEX;EACA;EACA;;EAEA;EACA;EACA,QAAQ,UAAU;;;wBAIJ,UACd,OACA,aACA,SACA,UACA,UACA,UACA,SACA,YACA,oBACA,SACC,+BAAY,IAAA"}
1
+ {"version":3,"file":"List.d.mts","names":[],"sources":["../../src/components/List.tsx"],"mappings":";;;iBA+BiB,UAAU;EACzB,eAAe;EACf,aAAa,MAAM,GAAG,kBAAkB;;EAExC,eAAe,MAAM,GAAG;;;;;EAKxB;;EAEA;;EAEA,QAAQ;EACR,SAAS;EACT,SAAS;EACT;EACA;;EAEA,QAAQ,UAAU;;EAElB;;wBAOc,KAAK,KACnB,MACA,YACA,cACA,eACA,YACA,OACA,QACA,QACA,cACA,oBACA,OACA,kBACC,UAAU,qBAAE,IAAA;iBAkDE;EACf,OAAO;;EAEP,cAAc;;EAEd,UAAU;;EAEV,WAAW;;EAEX;EACA;EACA;;EAEA;EACA;EACA,QAAQ,UAAU;;;wBAIJ,UACd,OACA,aACA,SACA,UACA,UACA,UACA,SACA,YACA,oBACA,SACC,+BAAY,IAAA"}
@@ -1,10 +1,11 @@
1
1
  import { fontOf } from "../lib/fonts.mjs";
2
- import { ChevronRightIcon } from "../icons/icons.mjs";
2
+ import { Button } from "./Button.mjs";
3
+ import { ChevronDownIcon, ChevronRightIcon } from "../icons/icons.mjs";
3
4
  import { View } from "react-native-unistyles/components/native/View";
4
5
  import { Text } from "react-native-unistyles/components/native/Text";
5
6
  import { StyleSheet } from "react-native-unistyles";
6
7
  import { Platform } from "react-native";
7
- import { useCallback } from "react";
8
+ import { useCallback, useRef, useState } from "react";
8
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
10
  import { Pressable } from "react-native-unistyles/components/native/Pressable";
10
11
  import { FlashList } from "@shopify/flash-list";
@@ -14,15 +15,27 @@ const MIN_ROW_HEIGHT = 44;
14
15
  const DISABLED_OPACITY = .4;
15
16
  /** How close to the bottom a reader must be for a new row to keep the list pinned, as a fraction of the visible height. */
16
17
  const PIN_THRESHOLD = .1;
18
+ /** How far above the bottom, in visible heights, a chat reader must scroll before the jump-to-latest button shows. */
19
+ const JUMP_THRESHOLD = 1;
17
20
  function Separator() {
18
21
  return /* @__PURE__ */ jsx(View, { style: styles.separator });
19
22
  }
20
23
  function List({ data, renderItem, keyExtractor, startAtBottom = false, separators = true, empty, header, footer, onEndReached, accessibilityLabel, style, contentPadding }) {
21
24
  const renderRow = useCallback(({ item, index }) => renderItem(item, index), [renderItem]);
22
- return /* @__PURE__ */ jsx(View, {
25
+ const listRef = useRef(null);
26
+ const [awayFromBottom, setAwayFromBottom] = useState(false);
27
+ const onScroll = useCallback(({ nativeEvent }) => {
28
+ const { contentOffset, contentSize, layoutMeasurement } = nativeEvent;
29
+ const distance = contentSize.height - layoutMeasurement.height - contentOffset.y;
30
+ setAwayFromBottom(distance > layoutMeasurement.height * JUMP_THRESHOLD);
31
+ }, []);
32
+ return /* @__PURE__ */ jsxs(View, {
23
33
  style: [styles.frame, style],
24
34
  accessibilityLabel,
25
- children: /* @__PURE__ */ jsx(FlashList, {
35
+ children: [/* @__PURE__ */ jsx(FlashList, {
36
+ ref: listRef,
37
+ onScroll: startAtBottom ? onScroll : void 0,
38
+ scrollEventThrottle: startAtBottom ? 100 : void 0,
26
39
  data,
27
40
  renderItem: renderRow,
28
41
  keyExtractor,
@@ -39,7 +52,14 @@ function List({ data, renderItem, keyExtractor, startAtBottom = false, separator
39
52
  startRenderingFromBottom: true,
40
53
  autoscrollToBottomThreshold: PIN_THRESHOLD
41
54
  } : void 0
42
- })
55
+ }), startAtBottom && awayFromBottom ? /* @__PURE__ */ jsx(Button, {
56
+ variant: "subtle",
57
+ size: "icon-md",
58
+ accessibilityLabel: "Jump to latest",
59
+ onPress: () => listRef.current?.scrollToEnd({ animated: true }),
60
+ style: styles.jump,
61
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 18 })
62
+ }) : null]
43
63
  });
44
64
  }
45
65
  /** One list row: 44pt tall, optional leading and trailing slots, pressable when it navigates. */
@@ -103,6 +123,14 @@ const styles = StyleSheet.create((theme) => ({
103
123
  flex: 1,
104
124
  minHeight: 0
105
125
  },
126
+ jump: {
127
+ position: "absolute",
128
+ bottom: theme.spacing(3),
129
+ alignSelf: "center",
130
+ borderRadius: theme.borderRadius.full,
131
+ borderColor: theme.colors.border,
132
+ uni__dependencies: [0]
133
+ },
106
134
  separator: {
107
135
  height: StyleSheet.hairlineWidth,
108
136
  marginLeft: theme.spacing(4),
@@ -1 +1 @@
1
- {"version":3,"file":"List.mjs","names":["FlashList","ReactElement","ReactNode","useCallback","Platform","StyleProp","ViewStyle","StyleSheet","ChevronRightIcon","fontOf","MIN_ROW_HEIGHT","DISABLED_OPACITY","PIN_THRESHOLD","ListProps","data","T","renderItem","item","index","keyExtractor","startAtBottom","separators","empty","header","footer","onEndReached","accessibilityLabel","style","contentPadding","Separator","styles","separator","List","renderRow","frame","undefined","OS","padding","startRenderingFromBottom","autoscrollToBottomThreshold","ListRowProps","title","description","leading","trailing","selected","disabled","onPress","disclosure","ListRow","chevron","content","text","titleSelected","row","pressed","create","theme","flex","minHeight","height","hairlineWidth","marginLeft","spacing","backgroundColor","colors","uni__dependencies","flexDirection","alignItems","gap","paddingHorizontal","paddingVertical","panel","opacity","minWidth","color","hi","fontFamily","sans","fontSize","base","lineHeight","fontWeight","medium","green","semibold","faint","sm"],"sources":["../../src/components/List.tsx"],"sourcesContent":["import { FlashList } from '@shopify/flash-list';\nimport { type ReactElement, type ReactNode, useCallback } from 'react';\nimport { Platform, Pressable, type StyleProp, Text, View, type ViewStyle } from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { ChevronRightIcon } from '../icons/icons';\nimport { fontOf } from '../lib/fonts';\n\n/*\n * A virtualised list on FlashList: sessions, devices, a transcript. Rows\n * recycle, separators are hairlines, and a chat list can start at the bottom\n * and stay pinned there as rows arrive. The public props are this package's\n * own, so FlashList's types never reach a caller.\n */\n\nconst MIN_ROW_HEIGHT = 44;\nconst DISABLED_OPACITY = 0.4;\n/** How close to the bottom a reader must be for a new row to keep the list pinned, as a fraction of the visible height. */\nconst PIN_THRESHOLD = 0.1;\n\nexport interface ListProps<T> {\n data: readonly T[];\n renderItem: (item: T, index: number) => ReactElement | null;\n /** A stable key per row; FlashList recycles views by it. */\n keyExtractor: (item: T, index: number) => string;\n /**\n * A chat list: render from the bottom and keep the newest row in view as\n * rows arrive, while the reader is near the bottom.\n */\n startAtBottom?: boolean;\n /** Hairline separators between rows. On by default. */\n separators?: boolean;\n /** Shown when `data` is empty. */\n empty?: ReactNode;\n header?: ReactNode;\n footer?: ReactNode;\n onEndReached?: () => void;\n accessibilityLabel?: string;\n /** The list's frame; give it a height or a flex parent, since a virtualised list needs bounds. */\n style?: StyleProp<ViewStyle>;\n /** Padding around the rows. */\n contentPadding?: number;\n}\n\nfunction Separator() {\n return <View style={styles.separator} />;\n}\n\nexport function List<T>({\n data,\n renderItem,\n keyExtractor,\n startAtBottom = false,\n separators = true,\n empty,\n header,\n footer,\n onEndReached,\n accessibilityLabel,\n style,\n contentPadding,\n}: ListProps<T>) {\n const renderRow = useCallback(({ item, index }: { item: T; index: number }) => renderItem(item, index), [renderItem]);\n return (\n <View style={[styles.frame, style]} accessibilityLabel={accessibilityLabel}>\n <FlashList\n data={data}\n renderItem={renderRow}\n keyExtractor={keyExtractor}\n ItemSeparatorComponent={separators ? Separator : null}\n ListEmptyComponent={empty === undefined ? null : <>{empty}</>}\n ListHeaderComponent={header === undefined ? null : <>{header}</>}\n ListFooterComponent={footer === undefined ? null : <>{footer}</>}\n onEndReached={onEndReached}\n // Dragging the rows pulls the keyboard down with the finger, as in Messages.\n keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}\n // A chat's rows are mostly pressable (long-press bubbles, tool groups), so any tap there hides the keyboard\n // first, as in Messages; other lists let a tap on a row land.\n keyboardShouldPersistTaps={startAtBottom ? 'never' : 'handled'}\n // Lets UIKit inset the rows under a translucent or large-title header instead of drawing beneath it.\n contentInsetAdjustmentBehavior=\"automatic\"\n contentContainerStyle={contentPadding === undefined ? undefined : { padding: contentPadding }}\n maintainVisibleContentPosition={\n startAtBottom ? { startRenderingFromBottom: true, autoscrollToBottomThreshold: PIN_THRESHOLD } : undefined\n }\n />\n </View>\n );\n}\n\nexport interface ListRowProps {\n title: ReactNode;\n /** A second, quieter line under the title. */\n description?: ReactNode;\n /** Before the text: a Dot, an icon. */\n leading?: ReactNode;\n /** After the text: a Badge, a ghost button, a count. */\n trailing?: ReactNode;\n /** Selected rows sit on a green tint. */\n selected?: boolean;\n disabled?: boolean;\n onPress?: () => void;\n /** The iOS disclosure chevron. Defaults to on for a pressable row on iOS, where rows that navigate wear one. */\n disclosure?: boolean;\n accessibilityLabel?: string;\n style?: StyleProp<ViewStyle>;\n}\n\n/** One list row: 44pt tall, optional leading and trailing slots, pressable when it navigates. */\nexport function ListRow({\n title,\n description,\n leading,\n trailing,\n selected = false,\n disabled = false,\n onPress,\n disclosure,\n accessibilityLabel,\n style,\n}: ListRowProps) {\n const chevron = disclosure ?? (onPress !== undefined && Platform.OS === 'ios');\n const content = (\n <>\n {leading}\n <View style={styles.text}>\n {typeof title === 'string' ? (\n <Text numberOfLines={1} style={[styles.title, selected && styles.titleSelected]}>\n {title}\n </Text>\n ) : (\n title\n )}\n {typeof description === 'string' ? (\n <Text numberOfLines={2} style={styles.description}>\n {description}\n </Text>\n ) : (\n description\n )}\n </View>\n {trailing}\n {chevron ? <ChevronRightIcon size={14} color=\"faint\" /> : null}\n </>\n );\n if (onPress === undefined) {\n return (\n <View\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{ selected, disabled }}\n style={[styles.row, selected && styles.selected, disabled && styles.disabled, style]}\n >\n {content}\n </View>\n );\n }\n return (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{ selected, disabled }}\n disabled={disabled}\n onPress={onPress}\n style={({ pressed }) => [\n styles.row,\n selected && styles.selected,\n pressed && styles.pressed,\n disabled && styles.disabled,\n style,\n ]}\n >\n {content}\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n frame: { flex: 1, minHeight: 0 },\n separator: {\n height: StyleSheet.hairlineWidth,\n marginLeft: theme.spacing(4),\n backgroundColor: theme.colors['border-soft'],\n },\n row: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.spacing(3),\n minHeight: MIN_ROW_HEIGHT,\n paddingHorizontal: theme.spacing(4),\n paddingVertical: theme.spacing(2),\n },\n selected: { backgroundColor: theme.colors['tint-green'] },\n pressed: { backgroundColor: theme.colors.panel },\n disabled: { opacity: DISABLED_OPACITY },\n text: { flex: 1, minWidth: 0, gap: theme.spacing(0.5) },\n title: {\n color: theme.colors.hi,\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.base,\n lineHeight: theme.lineHeight.base,\n fontWeight: theme.fontWeight.medium,\n },\n titleSelected: { color: theme.colors.green, fontWeight: theme.fontWeight.semibold },\n description: {\n color: theme.colors.faint,\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.sm,\n lineHeight: theme.lineHeight.sm,\n },\n}));\n"],"mappings":";;;;;;;;;;;;AAeA,MAAMU,iBAAiB;AACvB,MAAMC,mBAAmB;;AAEzB,MAAMC,gBAAgB;AA0BtB,SAASiB,YAAY;CACnB,OAAO,oBAAC,MAAD,EAAM,OAAOC,OAAOC,UAAU;AACvC;AAEA,SAAgBC,KAAQ,EACtBlB,MACAE,YACAG,cACAC,gBAAgB,OAChBC,aAAa,MACbC,OACAC,QACAC,QACAC,cACAC,oBACAC,OACAC,kBACe;CACf,MAAMK,YAAY9B,aAAa,EAAEc,MAAMC,YAAwCF,WAAWC,MAAMC,KAAK,GAAG,CAACF,UAAU,CAAC;CACpH,OACE,oBAAC,MAAD;EAAM,OAAO,CAACc,OAAOI,OAAOP,KAAK;EAAuBD;YACtD,oBAAC,WAAD;GACQZ;GACN,YAAYmB;GACEd;GACd,wBAAwBE,aAAaQ,YAAY;GACjD,oBAAoBP,UAAUa,SAAY,OAAO,0CAAGb,MAAM;GAC1D,qBAAqBC,WAAWY,SAAY,OAAO,0CAAGZ,OAAO;GAC7D,qBAAqBC,WAAWW,SAAY,OAAO,0CAAGX,OAAO;GAC/CC;GAEd,qBAAqBrB,SAASgC,OAAO,QAAQ,gBAAgB;GAG7D,2BAA2BhB,gBAAgB,UAAU;GAErD,gCAA+B;GAC/B,uBAAuBQ,mBAAmBO,SAAYA,SAAY,EAAEE,SAAST,eAAe;GAC5F,gCACER,gBAAgB;IAAEkB,0BAA0B;IAAMC,6BAA6B3B;GAAc,IAAIuB;EAClG;CAEC;AAEV;;AAqBA,SAAgBc,QAAQ,EACtBR,OACAC,aACAC,SACAC,UACAC,WAAW,OACXC,WAAW,OACXC,SACAC,YACAtB,oBACAC,SACe;CACf,MAAMuB,UAAUF,eAAeD,YAAYZ,UAAa/B,SAASgC,OAAO;CACxE,MAAMe,UACJ;EACGR;EACD,qBAAC,MAAD;GAAM,OAAOb,OAAOsB;aAApB,CACG,OAAOX,UAAU,WAChB,oBAAC,MAAD;IAAM,eAAe;IAAG,OAAO,CAACX,OAAOW,OAAOI,YAAYf,OAAOuB,aAAa;cAC3EZ;GACG,KAENA,OAED,OAAOC,gBAAgB,WACtB,oBAAC,MAAD;IAAM,eAAe;IAAG,OAAOZ,OAAOY;cACnCA;GACG,KAENA,WAEE;;EACLE;EACAM,UAAU,oBAAC,kBAAD;GAAkB,MAAM;GAAI,OAAM;EAAO,KAAM;CAC5D;CAEF,IAAIH,YAAYZ,QACd,OACE,oBAAC,MAAD;EACsBT;EACpB,oBAAoB;GAAEmB;GAAUC;EAAS;EACzC,OAAO;GAAChB,OAAOwB;GAAKT,YAAYf,OAAOe;GAAUC,YAAYhB,OAAOgB;GAAUnB;EAAK;YAElFwB;CACG;CAGV,OACE,oBAAC,WAAD;EACE,mBAAkB;EACEzB;EACpB,oBAAoB;GAAEmB;GAAUC;EAAS;EAC/BA;EACDC;EACT,QAAQ,EAAEQ,cAAc;GACtBzB,OAAOwB;GACPT,YAAYf,OAAOe;GACnBU,WAAWzB,OAAOyB;GAClBT,YAAYhB,OAAOgB;GACnBnB;EAAK;YAGNwB;CACQ;AAEf;AAEA,MAAMrB,SAASvB,WAAWiD,QAAQC,WAAW;CAC3CvB,OAAO;EAAEwB,MAAM;EAAGC,WAAW;CAAE;CAC/B5B,WAAW;EACT6B,QAAQrD,WAAWsD;EACnBC,YAAYL,MAAMM,QAAQ,CAAC;EAC3BC,iBAAiBP,MAAMQ,OAAO;EAAcC,mBAAA,CAAA,CAAA;CAC9C;CACAZ,KAAK;EACHa,eAAe;EACfC,YAAY;EACZC,KAAKZ,MAAMM,QAAQ,CAAC;EACpBJ,WAAWjD;EACX4D,mBAAmBb,MAAMM,QAAQ,CAAC;EAClCQ,iBAAiBd,MAAMM,QAAQ,CAAC;EAACG,mBAAA,CAAA,CAAA;CACnC;CACArB,UAAU;EAAEmB,iBAAiBP,MAAMQ,OAAO;EAAaC,mBAAA,CAAA,CAAA;CAAC;CACxDX,SAAS;EAAES,iBAAiBP,MAAMQ,OAAOO;EAAKN,mBAAA,CAAA,CAAA;CAAC;CAC/CpB,UAAU,EAAE2B,SAAS9D,iBAAiB;CACtCyC,MAAM;EAAEM,MAAM;EAAGgB,UAAU;EAAGL,KAAKZ,MAAMM,QAAQ,EAAG;EAACG,mBAAA,CAAA,CAAA;CAAC;CACtDzB,OAAO;EACLkC,OAAOlB,MAAMQ,OAAOW;EACpBC,YAAYpE,OAAOgD,MAAMoB,WAAWC,IAAI;EACxCC,UAAUtB,MAAMsB,SAASC;EACzBC,YAAYxB,MAAMwB,WAAWD;EAC7BE,YAAYzB,MAAMyB,WAAWC;EAAMjB,mBAAA,CAAA,CAAA;CACrC;CACAb,eAAe;EAAEsB,OAAOlB,MAAMQ,OAAOmB;EAAOF,YAAYzB,MAAMyB,WAAWG;EAAQnB,mBAAA,CAAA,CAAA;CAAC;CAClFxB,aAAa;EACXiC,OAAOlB,MAAMQ,OAAOqB;EACpBT,YAAYpE,OAAOgD,MAAMoB,WAAWC,IAAI;EACxCC,UAAUtB,MAAMsB,SAASQ;EACzBN,YAAYxB,MAAMwB,WAAWM;EAAErB,mBAAA,CAAA,CAAA;CACjC;AACF,EAAE"}
1
+ {"version":3,"file":"List.mjs","names":["FlashList","FlashListRef","ReactElement","ReactNode","useCallback","useRef","useState","NativeScrollEvent","NativeSyntheticEvent","Platform","StyleProp","ViewStyle","StyleSheet","ChevronDownIcon","ChevronRightIcon","fontOf","Button","MIN_ROW_HEIGHT","DISABLED_OPACITY","PIN_THRESHOLD","JUMP_THRESHOLD","ListProps","data","T","renderItem","item","index","keyExtractor","startAtBottom","separators","empty","header","footer","onEndReached","accessibilityLabel","style","contentPadding","Separator","styles","separator","List","renderRow","listRef","awayFromBottom","setAwayFromBottom","onScroll","nativeEvent","contentOffset","contentSize","layoutMeasurement","distance","height","y","frame","undefined","OS","padding","startRenderingFromBottom","autoscrollToBottomThreshold","current","scrollToEnd","animated","jump","ListRowProps","title","description","leading","trailing","selected","disabled","onPress","disclosure","ListRow","chevron","content","text","titleSelected","row","pressed","create","theme","flex","minHeight","position","bottom","spacing","alignSelf","borderRadius","full","borderColor","colors","border","uni__dependencies","hairlineWidth","marginLeft","backgroundColor","flexDirection","alignItems","gap","paddingHorizontal","paddingVertical","panel","opacity","minWidth","color","hi","fontFamily","sans","fontSize","base","lineHeight","fontWeight","medium","green","semibold","faint","sm"],"sources":["../../src/components/List.tsx"],"sourcesContent":["import { FlashList, type FlashListRef } from '@shopify/flash-list';\nimport { type ReactElement, type ReactNode, useCallback, useRef, useState } from 'react';\nimport {\n type NativeScrollEvent,\n type NativeSyntheticEvent,\n Platform,\n Pressable,\n type StyleProp,\n Text,\n View,\n type ViewStyle,\n} from 'react-native';\nimport { StyleSheet } from 'react-native-unistyles';\n\nimport { ChevronDownIcon, ChevronRightIcon } from '../icons/icons';\nimport { fontOf } from '../lib/fonts';\nimport { Button } from './Button';\n/*\n * A virtualised list on FlashList: sessions, devices, a transcript. Rows\n * recycle, separators are hairlines, and a chat list can start at the bottom\n * and stay pinned there as rows arrive. The public props are this package's\n * own, so FlashList's types never reach a caller.\n */\n\nconst MIN_ROW_HEIGHT = 44;\nconst DISABLED_OPACITY = 0.4;\n/** How close to the bottom a reader must be for a new row to keep the list pinned, as a fraction of the visible height. */\nconst PIN_THRESHOLD = 0.1;\n/** How far above the bottom, in visible heights, a chat reader must scroll before the jump-to-latest button shows. */\nconst JUMP_THRESHOLD = 1;\n\nexport interface ListProps<T> {\n data: readonly T[];\n renderItem: (item: T, index: number) => ReactElement | null;\n /** A stable key per row; FlashList recycles views by it. */\n keyExtractor: (item: T, index: number) => string;\n /**\n * A chat list: render from the bottom and keep the newest row in view as\n * rows arrive, while the reader is near the bottom.\n */\n startAtBottom?: boolean;\n /** Hairline separators between rows. On by default. */\n separators?: boolean;\n /** Shown when `data` is empty. */\n empty?: ReactNode;\n header?: ReactNode;\n footer?: ReactNode;\n onEndReached?: () => void;\n accessibilityLabel?: string;\n /** The list's frame; give it a height or a flex parent, since a virtualised list needs bounds. */\n style?: StyleProp<ViewStyle>;\n /** Padding around the rows. */\n contentPadding?: number;\n}\n\nfunction Separator() {\n return <View style={styles.separator} />;\n}\n\nexport function List<T>({\n data,\n renderItem,\n keyExtractor,\n startAtBottom = false,\n separators = true,\n empty,\n header,\n footer,\n onEndReached,\n accessibilityLabel,\n style,\n contentPadding,\n}: ListProps<T>) {\n const renderRow = useCallback(({ item, index }: { item: T; index: number }) => renderItem(item, index), [renderItem]);\n const listRef = useRef<FlashListRef<T>>(null);\n const [awayFromBottom, setAwayFromBottom] = useState(false);\n const onScroll = useCallback(({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {\n const { contentOffset, contentSize, layoutMeasurement } = nativeEvent;\n const distance = contentSize.height - layoutMeasurement.height - contentOffset.y;\n setAwayFromBottom(distance > layoutMeasurement.height * JUMP_THRESHOLD);\n }, []);\n return (\n <View style={[styles.frame, style]} accessibilityLabel={accessibilityLabel}>\n <FlashList\n ref={listRef}\n onScroll={startAtBottom ? onScroll : undefined}\n scrollEventThrottle={startAtBottom ? 100 : undefined}\n data={data}\n renderItem={renderRow}\n keyExtractor={keyExtractor}\n ItemSeparatorComponent={separators ? Separator : null}\n ListEmptyComponent={empty === undefined ? null : <>{empty}</>}\n ListHeaderComponent={header === undefined ? null : <>{header}</>}\n ListFooterComponent={footer === undefined ? null : <>{footer}</>}\n onEndReached={onEndReached}\n // Dragging the rows pulls the keyboard down with the finger, as in Messages.\n keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}\n // A chat's rows are mostly pressable (long-press bubbles, tool groups), so any tap there hides the keyboard\n // first, as in Messages; other lists let a tap on a row land.\n keyboardShouldPersistTaps={startAtBottom ? 'never' : 'handled'}\n // Lets UIKit inset the rows under a translucent or large-title header instead of drawing beneath it.\n contentInsetAdjustmentBehavior=\"automatic\"\n contentContainerStyle={contentPadding === undefined ? undefined : { padding: contentPadding }}\n maintainVisibleContentPosition={\n startAtBottom ? { startRenderingFromBottom: true, autoscrollToBottomThreshold: PIN_THRESHOLD } : undefined\n }\n />\n {startAtBottom && awayFromBottom ? (\n <Button\n variant=\"subtle\"\n size=\"icon-md\"\n accessibilityLabel=\"Jump to latest\"\n onPress={() => listRef.current?.scrollToEnd({ animated: true })}\n style={styles.jump}\n >\n <ChevronDownIcon size={18} />\n </Button>\n ) : null}\n </View>\n );\n}\n\nexport interface ListRowProps {\n title: ReactNode;\n /** A second, quieter line under the title. */\n description?: ReactNode;\n /** Before the text: a Dot, an icon. */\n leading?: ReactNode;\n /** After the text: a Badge, a ghost button, a count. */\n trailing?: ReactNode;\n /** Selected rows sit on a green tint. */\n selected?: boolean;\n disabled?: boolean;\n onPress?: () => void;\n /** The iOS disclosure chevron. Defaults to on for a pressable row on iOS, where rows that navigate wear one. */\n disclosure?: boolean;\n accessibilityLabel?: string;\n style?: StyleProp<ViewStyle>;\n}\n\n/** One list row: 44pt tall, optional leading and trailing slots, pressable when it navigates. */\nexport function ListRow({\n title,\n description,\n leading,\n trailing,\n selected = false,\n disabled = false,\n onPress,\n disclosure,\n accessibilityLabel,\n style,\n}: ListRowProps) {\n const chevron = disclosure ?? (onPress !== undefined && Platform.OS === 'ios');\n const content = (\n <>\n {leading}\n <View style={styles.text}>\n {typeof title === 'string' ? (\n <Text numberOfLines={1} style={[styles.title, selected && styles.titleSelected]}>\n {title}\n </Text>\n ) : (\n title\n )}\n {typeof description === 'string' ? (\n <Text numberOfLines={2} style={styles.description}>\n {description}\n </Text>\n ) : (\n description\n )}\n </View>\n {trailing}\n {chevron ? <ChevronRightIcon size={14} color=\"faint\" /> : null}\n </>\n );\n if (onPress === undefined) {\n return (\n <View\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{ selected, disabled }}\n style={[styles.row, selected && styles.selected, disabled && styles.disabled, style]}\n >\n {content}\n </View>\n );\n }\n return (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={accessibilityLabel}\n accessibilityState={{ selected, disabled }}\n disabled={disabled}\n onPress={onPress}\n style={({ pressed }) => [\n styles.row,\n selected && styles.selected,\n pressed && styles.pressed,\n disabled && styles.disabled,\n style,\n ]}\n >\n {content}\n </Pressable>\n );\n}\n\nconst styles = StyleSheet.create((theme) => ({\n frame: { flex: 1, minHeight: 0 },\n jump: {\n position: 'absolute',\n bottom: theme.spacing(3),\n alignSelf: 'center',\n borderRadius: theme.borderRadius.full,\n borderColor: theme.colors.border,\n },\n separator: {\n height: StyleSheet.hairlineWidth,\n marginLeft: theme.spacing(4),\n backgroundColor: theme.colors['border-soft'],\n },\n row: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.spacing(3),\n minHeight: MIN_ROW_HEIGHT,\n paddingHorizontal: theme.spacing(4),\n paddingVertical: theme.spacing(2),\n },\n selected: { backgroundColor: theme.colors['tint-green'] },\n pressed: { backgroundColor: theme.colors.panel },\n disabled: { opacity: DISABLED_OPACITY },\n text: { flex: 1, minWidth: 0, gap: theme.spacing(0.5) },\n title: {\n color: theme.colors.hi,\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.base,\n lineHeight: theme.lineHeight.base,\n fontWeight: theme.fontWeight.medium,\n },\n titleSelected: { color: theme.colors.green, fontWeight: theme.fontWeight.semibold },\n description: {\n color: theme.colors.faint,\n fontFamily: fontOf(theme.fontFamily.sans),\n fontSize: theme.fontSize.sm,\n lineHeight: theme.lineHeight.sm,\n },\n}));\n"],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { StyleProp, TextStyle } from "react-native";
2
+ //#region src/components/LiveDuration.d.ts
3
+ /** What a custom clock shows at `now`, and how long until it changes; no `nextMs` freezes it. */
4
+ export interface LiveDurationReading {
5
+ text: string;
6
+ nextMs?: number;
7
+ }
8
+ export type LiveDurationProps = {
9
+ style?: StyleProp<TextStyle>;
10
+ } & ({
11
+ /** The copy before the elapsed time, such as "running". */
12
+ label: string;
13
+ /** Epoch milliseconds the clock counts from. */
14
+ since: number;
15
+ clock?: undefined;
16
+ } | {
17
+ /** A custom format; keep it stable (useCallback), since a new function restarts the clock. */
18
+ clock: (now: number) => LiveDurationReading;
19
+ label?: undefined;
20
+ since?: undefined;
21
+ });
22
+ /**
23
+ * "running · 12m" that ages by itself, or any `clock` format. Each change writes a shared value the UI thread
24
+ * draws, so a list of running work re-renders nothing to keep its clocks current. It sleeps while the app is in
25
+ * the background and catches up on return. Read-only and not focusable: it sits inside a row whose own label
26
+ * speaks for it.
27
+ */
28
+ export declare function LiveDuration({ label, since, clock, style }: LiveDurationProps): import("react").JSX.Element;
29
+ //#endregion
30
+ //# sourceMappingURL=LiveDuration.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveDuration.d.mts","names":[],"sources":["../../src/components/LiveDuration.tsx"],"mappings":";;;iBASiB;EACf;EACA;;YAGU;EACV,QAAQ,UAAU;;;EAId;;EAEA;EACA;;;EAIA,QAAQ,gBAAgB;EACxB;EACA;;;;;;;;wBAUU,eAAe,OAAO,OAAO,OAAO,SAAS,oCAAiB,IAAA"}
@@ -0,0 +1,67 @@
1
+ import { clockRefreshMs, clockText } from "@agimon-ai/po-web-components/text";
2
+ import { StyleSheet } from "react-native-unistyles";
3
+ import { AppState } from "react-native";
4
+ import { useEffect, useState } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ import { TextInput } from "react-native-unistyles/components/native/TextInput";
7
+ import Animated, { useAnimatedProps, useSharedValue } from "react-native-reanimated";
8
+
9
+ //#region src/components/LiveDuration.tsx
10
+ const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
11
+ /**
12
+ * "running · 12m" that ages by itself, or any `clock` format. Each change writes a shared value the UI thread
13
+ * draws, so a list of running work re-renders nothing to keep its clocks current. It sleeps while the app is in
14
+ * the background and catches up on return. Read-only and not focusable: it sits inside a row whose own label
15
+ * speaks for it.
16
+ */
17
+ function LiveDuration({ label, since, clock, style }) {
18
+ const read = (now) => clock ? clock(now) : {
19
+ text: clockText({
20
+ label,
21
+ since
22
+ }, now),
23
+ nextMs: clockRefreshMs(since, now)
24
+ };
25
+ const [initial] = useState(() => read(Date.now()).text);
26
+ const text = useSharedValue(initial);
27
+ useEffect(() => {
28
+ let timer;
29
+ const tick = () => {
30
+ clearTimeout(timer);
31
+ timer = void 0;
32
+ if (AppState.currentState !== "active") return;
33
+ const reading = read(Date.now());
34
+ text.value = reading.text;
35
+ if (reading.nextMs !== void 0) timer = setTimeout(tick, Math.max(1, reading.nextMs));
36
+ };
37
+ const subscription = AppState.addEventListener("change", tick);
38
+ tick();
39
+ return () => {
40
+ clearTimeout(timer);
41
+ subscription.remove();
42
+ };
43
+ }, [
44
+ label,
45
+ since,
46
+ clock,
47
+ text
48
+ ]);
49
+ const animatedProps = useAnimatedProps(() => ({ text: text.value }));
50
+ return /* @__PURE__ */ jsx(AnimatedTextInput, {
51
+ editable: false,
52
+ focusable: false,
53
+ pointerEvents: "none",
54
+ underlineColorAndroid: "transparent",
55
+ defaultValue: initial,
56
+ animatedProps,
57
+ style: [styles.text, style]
58
+ });
59
+ }
60
+ const styles = StyleSheet.create({ text: {
61
+ padding: 0,
62
+ margin: 0
63
+ } });
64
+
65
+ //#endregion
66
+ export { LiveDuration };
67
+ //# sourceMappingURL=LiveDuration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveDuration.mjs","names":["clockRefreshMs","clockText","useEffect","useState","AppState","StyleProp","TextInputProps","TextStyle","Animated","useAnimatedProps","useSharedValue","StyleSheet","AnimatedTextInput","createAnimatedComponent","TextInput","LiveDurationReading","text","nextMs","LiveDurationProps","style","label","since","clock","now","LiveDuration","read","initial","Date","timer","ReturnType","setTimeout","tick","clearTimeout","undefined","currentState","reading","value","Math","max","subscription","addEventListener","remove","animatedProps","styles","create","padding","margin"],"sources":["../../src/components/LiveDuration.tsx"],"sourcesContent":["import { clockRefreshMs, clockText } from '@agimon-ai/po-web-components/text';\nimport { useEffect, useState } from 'react';\nimport { AppState, type StyleProp, TextInput, type TextInputProps, type TextStyle } from 'react-native';\nimport Animated, { useAnimatedProps, useSharedValue } from 'react-native-reanimated';\nimport { StyleSheet } from 'react-native-unistyles';\n\nconst AnimatedTextInput = Animated.createAnimatedComponent(TextInput);\n\n/** What a custom clock shows at `now`, and how long until it changes; no `nextMs` freezes it. */\nexport interface LiveDurationReading {\n text: string;\n nextMs?: number;\n}\n\nexport type LiveDurationProps = {\n style?: StyleProp<TextStyle>;\n} & (\n | {\n /** The copy before the elapsed time, such as \"running\". */\n label: string;\n /** Epoch milliseconds the clock counts from. */\n since: number;\n clock?: undefined;\n }\n | {\n /** A custom format; keep it stable (useCallback), since a new function restarts the clock. */\n clock: (now: number) => LiveDurationReading;\n label?: undefined;\n since?: undefined;\n }\n);\n\n/**\n * \"running · 12m\" that ages by itself, or any `clock` format. Each change writes a shared value the UI thread\n * draws, so a list of running work re-renders nothing to keep its clocks current. It sleeps while the app is in\n * the background and catches up on return. Read-only and not focusable: it sits inside a row whose own label\n * speaks for it.\n */\nexport function LiveDuration({ label, since, clock, style }: LiveDurationProps) {\n const read = (now: number): LiveDurationReading =>\n clock ? clock(now) : { text: clockText({ label, since }, now), nextMs: clockRefreshMs(since, now) };\n // The first paint's text; the effect below keeps it current from then on.\n const [initial] = useState(() => read(Date.now()).text);\n const text = useSharedValue(initial);\n useEffect(() => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const tick = (): void => {\n clearTimeout(timer);\n timer = undefined;\n if (AppState.currentState !== 'active') return;\n const reading = read(Date.now());\n text.value = reading.text;\n if (reading.nextMs !== undefined) timer = setTimeout(tick, Math.max(1, reading.nextMs));\n };\n const subscription = AppState.addEventListener('change', tick);\n tick();\n return () => {\n clearTimeout(timer);\n subscription.remove();\n };\n // read is rebuilt each render from exactly these inputs.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [label, since, clock, text]);\n const animatedProps = useAnimatedProps(() => ({ text: text.value }) as TextInputProps);\n return (\n <AnimatedTextInput\n editable={false}\n focusable={false}\n pointerEvents=\"none\"\n underlineColorAndroid=\"transparent\"\n defaultValue={initial}\n animatedProps={animatedProps}\n style={[styles.text, style]}\n />\n );\n}\n\nconst styles = StyleSheet.create({\n // A TextInput pads itself on Android; a status line sits flush like the Text beside it.\n text: { padding: 0, margin: 0 },\n});\n"],"mappings":";;;;;;;;;AAMA,MAAMY,oBAAoBJ,SAASK,wBAAwBC,SAAS;;;;;;;AAgCpE,SAAgBU,aAAa,EAAEJ,OAAOC,OAAOC,OAAOH,SAA4B;CAC9E,MAAMM,QAAQF,QACZD,QAAQA,MAAMC,GAAG,IAAI;EAAEP,MAAMf,UAAU;GAAEmB;GAAOC;EAAM,GAAGE,GAAG;EAAGN,QAAQjB,eAAeqB,OAAOE,GAAG;CAAE;CAEpG,MAAM,CAACG,WAAWvB,eAAesB,KAAKE,KAAKJ,IAAI,CAAC,CAAC,CAACP,IAAI;CACtD,MAAMA,OAAON,eAAegB,OAAO;CACnCxB,gBAAgB;EACd,IAAI0B;EACJ,MAAMG,aAAmB;GACvBC,aAAaJ,KAAK;GAClBA,QAAQK;GACR,IAAI7B,SAAS8B,iBAAiB,UAAU;GACxC,MAAMC,UAAUV,KAAKE,KAAKJ,IAAI,CAAC;GAC/BP,KAAKoB,QAAQD,QAAQnB;GACrB,IAAImB,QAAQlB,WAAWgB,QAAWL,QAAQE,WAAWC,MAAMM,KAAKC,IAAI,GAAGH,QAAQlB,MAAM,CAAC;EACxF;EACA,MAAMsB,eAAenC,SAASoC,iBAAiB,UAAUT,IAAI;EAC7DA,KAAK;EACL,aAAa;GACXC,aAAaJ,KAAK;GAClBW,aAAaE,OAAO;EACtB;CAGF,GAAG;EAACrB;EAAOC;EAAOC;EAAON;CAAI,CAAC;CAC9B,MAAM0B,gBAAgBjC,wBAAwB,EAAEO,MAAMA,KAAKoB,MAAM,EAAoB;CACrF,OACE,oBAAC,mBAAD;EACE,UAAU;EACV,WAAW;EACX,eAAc;EACd,uBAAsB;EACtB,cAAcV;EACCgB;EACf,OAAO,CAACC,OAAO3B,MAAMG,KAAK;CAAE;AAGlC;AAEA,MAAMwB,SAAShC,WAAWiC,OAAO,EAE/B5B,MAAM;CAAE6B,SAAS;CAAGC,QAAQ;AAAE,EAChC,CAAC"}
package/dist/index.d.mts CHANGED
@@ -12,6 +12,7 @@ import { HashlineLines, HashlineLinesProps } from "./components/HashlineLines.mj
12
12
  import { FieldSize, FieldVariant, Input, InputProps } from "./components/Input.mjs";
13
13
  import { Label, LabelProps } from "./components/Label.mjs";
14
14
  import { List, ListProps, ListRow, ListRowProps } from "./components/List.mjs";
15
+ import { LiveDuration, LiveDurationProps, LiveDurationReading } from "./components/LiveDuration.mjs";
15
16
  import { FileLinkHandler, Markdown, MarkdownImageLoader, MarkdownProps } from "./components/Markdown.mjs";
16
17
  import { STATUS_EDGE, StatusBadge, StatusBadgeProps, StatusBadgeSize, StatusTone } from "./components/StatusBadge.mjs";
17
18
  import { MessageItem, MessageItemBody, MessageItemGroup, MessageItemGroupProps, MessageItemHeader, MessageItemHeaderProps, MessageItemPartProps, MessageItemProps, MessageItemState, MessageItemStatus, MessageItemStatusProps, STATUS_GLYPH, STATUS_LABEL, toolTone, useMessageItem } from "./components/MessageItem.mjs";
@@ -33,4 +34,4 @@ import { fontOf } from "./lib/fonts.mjs";
33
34
  import { IconColorContext } from "./icons/IconColorContext.mjs";
34
35
  import { impactLight, selectionTick } from "./lib/haptics.mjs";
35
36
  import { AnsiSpan, PresentedLine, ReviewComment, ansiSpans, buildReviewPrompt, collapseLines, hashlineBody, resultTextLines } from "@agimon-ai/po-web-components/text";
36
- export { ActivityIcon, AgentAvatar, type AgentState, AlertIcon, type AnnotationMark, AnnotationSurface, type AnnotationSurfaceProps, type AnsiSpan, ArrowRightIcon, Badge, type BadgeProps, type BadgeSize, type BadgeTone, BranchIcon, Button, type ButtonProps, type ButtonSize, type ButtonVariant, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, CloseIcon, CodeBlock, type CodeBlockProps, CopyButton, type CopyButtonProps, CopyIcon, Dialog, DialogBody, DialogClose, DialogContent, type DialogContentProps, DialogDescription, DialogFooter, type DialogFooterProps, type DialogFooterVariant, DialogHeader, type DialogHeaderProps, type DialogPartProps, type DialogProps, DialogTitle, DialogTrigger, type DialogWidth, Dot, type DotProps, type DotSize, type DotTone, DownloadIcon, DropdownMenu, DropdownMenuCheckboxItem, type DropdownMenuCheckboxItemProps, DropdownMenuContent, type DropdownMenuContentProps, DropdownMenuGroup, DropdownMenuItem, type DropdownMenuItemProps, type DropdownMenuItemVariant, DropdownMenuLabel, type DropdownMenuPartProps, type DropdownMenuProps, DropdownMenuRadioGroup, type DropdownMenuRadioGroupProps, DropdownMenuRadioItem, type DropdownMenuRadioItemProps, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, EditIcon, EmptyState, type EmptyStateProps, type FieldSize, type FieldVariant, FileIcon, type FileLinkHandler, FolderIcon, ForkIcon, GearIcon, GlobeIcon, HashlineLines, type HashlineLinesProps, HomeIcon, IconColorContext, type IconName, type IconProps, ImageIcon, Input, type InputProps, KebabIcon, Label, type LabelProps, LightbulbIcon, List, type ListProps, ListRow, type ListRowProps, LockIcon, Markdown, type MarkdownImageLoader, type MarkdownProps, MessageIcon, MessageItem, MessageItemBody, MessageItemGroup, type MessageItemGroupProps, MessageItemHeader, type MessageItemHeaderProps, type MessageItemPartProps, type MessageItemProps, type MessageItemState, MessageItemStatus, type MessageItemStatusProps, MicIcon, PaletteIcon, Panel, PanelBody, PanelHeader, type PanelPartProps, PlayCircleIcon, PlusIcon, PoNativeProvider, type PoNativeProviderProps, type PresentedLine, type ReviewComment, STATUS_EDGE, STATUS_GLYPH, STATUS_LABEL, SearchIcon, SectionLabel, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, SendIcon, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, type SheetHeaderProps, SheetList, type SheetListProps, type SheetPartProps, type SheetProps, SheetTitle, SheetTrigger, ShowMore, type ShowMoreProps, SlidersIcon, SparklesIcon, Spinner, type SpinnerProps, StatusBadge, type StatusBadgeProps, type StatusBadgeSize, type StatusTone, StopIcon, SyntaxLine, type SyntaxLineProps, TabBadge, type TabBadgeProps, Tabs, TabsContent, type TabsContentProps, TabsList, type TabsListProps, type TabsProps, TabsTrigger, type TabsTriggerProps, TaskIcon, Textarea, type TextareaProps, ToolPathLink, type ToolPathLinkProps, TrashIcon, UsersIcon, WorkflowIcon, WrenchIcon, agentInitials, ansiSpans, buildReviewPrompt, collapseLines, fontOf, hashlineBody, impactLight, resultTextLines, selectionTick, toolTone, useMessageItem, useSyntaxLines };
37
+ export { ActivityIcon, AgentAvatar, type AgentState, AlertIcon, type AnnotationMark, AnnotationSurface, type AnnotationSurfaceProps, type AnsiSpan, ArrowRightIcon, Badge, type BadgeProps, type BadgeSize, type BadgeTone, BranchIcon, Button, type ButtonProps, type ButtonSize, type ButtonVariant, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, CloseIcon, CodeBlock, type CodeBlockProps, CopyButton, type CopyButtonProps, CopyIcon, Dialog, DialogBody, DialogClose, DialogContent, type DialogContentProps, DialogDescription, DialogFooter, type DialogFooterProps, type DialogFooterVariant, DialogHeader, type DialogHeaderProps, type DialogPartProps, type DialogProps, DialogTitle, DialogTrigger, type DialogWidth, Dot, type DotProps, type DotSize, type DotTone, DownloadIcon, DropdownMenu, DropdownMenuCheckboxItem, type DropdownMenuCheckboxItemProps, DropdownMenuContent, type DropdownMenuContentProps, DropdownMenuGroup, DropdownMenuItem, type DropdownMenuItemProps, type DropdownMenuItemVariant, DropdownMenuLabel, type DropdownMenuPartProps, type DropdownMenuProps, DropdownMenuRadioGroup, type DropdownMenuRadioGroupProps, DropdownMenuRadioItem, type DropdownMenuRadioItemProps, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, EditIcon, EmptyState, type EmptyStateProps, type FieldSize, type FieldVariant, FileIcon, type FileLinkHandler, FolderIcon, ForkIcon, GearIcon, GlobeIcon, HashlineLines, type HashlineLinesProps, HomeIcon, IconColorContext, type IconName, type IconProps, ImageIcon, Input, type InputProps, KebabIcon, Label, type LabelProps, LightbulbIcon, List, type ListProps, ListRow, type ListRowProps, LiveDuration, type LiveDurationProps, type LiveDurationReading, LockIcon, Markdown, type MarkdownImageLoader, type MarkdownProps, MessageIcon, MessageItem, MessageItemBody, MessageItemGroup, type MessageItemGroupProps, MessageItemHeader, type MessageItemHeaderProps, type MessageItemPartProps, type MessageItemProps, type MessageItemState, MessageItemStatus, type MessageItemStatusProps, MicIcon, PaletteIcon, Panel, PanelBody, PanelHeader, type PanelPartProps, PlayCircleIcon, PlusIcon, PoNativeProvider, type PoNativeProviderProps, type PresentedLine, type ReviewComment, STATUS_EDGE, STATUS_GLYPH, STATUS_LABEL, SearchIcon, SectionLabel, SegmentedControl, type SegmentedControlOption, type SegmentedControlProps, SendIcon, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, type SheetHeaderProps, SheetList, type SheetListProps, type SheetPartProps, type SheetProps, SheetTitle, SheetTrigger, ShowMore, type ShowMoreProps, SlidersIcon, SparklesIcon, Spinner, type SpinnerProps, StatusBadge, type StatusBadgeProps, type StatusBadgeSize, type StatusTone, StopIcon, SyntaxLine, type SyntaxLineProps, TabBadge, type TabBadgeProps, Tabs, TabsContent, type TabsContentProps, TabsList, type TabsListProps, type TabsProps, TabsTrigger, type TabsTriggerProps, TaskIcon, Textarea, type TextareaProps, ToolPathLink, type ToolPathLinkProps, TrashIcon, UsersIcon, WorkflowIcon, WrenchIcon, agentInitials, ansiSpans, buildReviewPrompt, collapseLines, fontOf, hashlineBody, impactLight, resultTextLines, selectionTick, toolTone, useMessageItem, useSyntaxLines };
package/dist/index.mjs CHANGED
@@ -19,6 +19,7 @@ import { HashlineLines } from "./components/HashlineLines.mjs";
19
19
  import { Input } from "./components/Input.mjs";
20
20
  import { Label } from "./components/Label.mjs";
21
21
  import { List, ListRow } from "./components/List.mjs";
22
+ import { LiveDuration } from "./components/LiveDuration.mjs";
22
23
  import { Markdown } from "./components/Markdown.mjs";
23
24
  import { STATUS_EDGE, StatusBadge } from "./components/StatusBadge.mjs";
24
25
  import { MessageItem, MessageItemBody, MessageItemGroup, MessageItemHeader, MessageItemStatus, STATUS_GLYPH, STATUS_LABEL, toolTone, useMessageItem } from "./components/MessageItem.mjs";
@@ -33,4 +34,4 @@ import { Textarea } from "./components/Textarea.mjs";
33
34
  import { ToolPathLink } from "./components/ToolPathLink.mjs";
34
35
  import { ansiSpans, buildReviewPrompt, collapseLines, hashlineBody, resultTextLines } from "@agimon-ai/po-web-components/text";
35
36
 
36
- export { ActivityIcon, AgentAvatar, AlertIcon, AnnotationSurface, ArrowRightIcon, Badge, BranchIcon, Button, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, CloseIcon, CodeBlock, CopyButton, CopyIcon, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Dot, DownloadIcon, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, EditIcon, EmptyState, FileIcon, FolderIcon, ForkIcon, GearIcon, GlobeIcon, HashlineLines, HomeIcon, IconColorContext, ImageIcon, Input, KebabIcon, Label, LightbulbIcon, List, ListRow, LockIcon, Markdown, MessageIcon, MessageItem, MessageItemBody, MessageItemGroup, MessageItemHeader, MessageItemStatus, MicIcon, PaletteIcon, Panel, PanelBody, PanelHeader, PlayCircleIcon, PlusIcon, PoNativeProvider, STATUS_EDGE, STATUS_GLYPH, STATUS_LABEL, SearchIcon, SectionLabel, SegmentedControl, SendIcon, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetList, SheetTitle, SheetTrigger, ShowMore, SlidersIcon, SparklesIcon, Spinner, StatusBadge, StopIcon, SyntaxLine, TabBadge, Tabs, TabsContent, TabsList, TabsTrigger, TaskIcon, Textarea, ToolPathLink, TrashIcon, UsersIcon, WorkflowIcon, WrenchIcon, agentInitials, ansiSpans, buildReviewPrompt, collapseLines, fontOf, hashlineBody, impactLight, resultTextLines, selectionTick, toolTone, useMessageItem, useSyntaxLines };
37
+ export { ActivityIcon, AgentAvatar, AlertIcon, AnnotationSurface, ArrowRightIcon, Badge, BranchIcon, Button, CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, CloseIcon, CodeBlock, CopyButton, CopyIcon, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Dot, DownloadIcon, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, EditIcon, EmptyState, FileIcon, FolderIcon, ForkIcon, GearIcon, GlobeIcon, HashlineLines, HomeIcon, IconColorContext, ImageIcon, Input, KebabIcon, Label, LightbulbIcon, List, ListRow, LiveDuration, LockIcon, Markdown, MessageIcon, MessageItem, MessageItemBody, MessageItemGroup, MessageItemHeader, MessageItemStatus, MicIcon, PaletteIcon, Panel, PanelBody, PanelHeader, PlayCircleIcon, PlusIcon, PoNativeProvider, STATUS_EDGE, STATUS_GLYPH, STATUS_LABEL, SearchIcon, SectionLabel, SegmentedControl, SendIcon, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetList, SheetTitle, SheetTrigger, ShowMore, SlidersIcon, SparklesIcon, Spinner, StatusBadge, StopIcon, SyntaxLine, TabBadge, Tabs, TabsContent, TabsList, TabsTrigger, TaskIcon, Textarea, ToolPathLink, TrashIcon, UsersIcon, WorkflowIcon, WrenchIcon, agentInitials, ansiSpans, buildReviewPrompt, collapseLines, fontOf, hashlineBody, impactLight, resultTextLines, selectionTick, toolTone, useMessageItem, useSyntaxLines };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-native-components",
3
- "version": "0.0.0-alpha.45",
3
+ "version": "0.0.0-alpha.47",
4
4
  "description": "Shared React Native components for Po and its native plugins: the web component library's look and API on native parts, tuned to the Po palette.",
5
5
  "keywords": [
6
6
  "ai",
@@ -45,11 +45,11 @@
45
45
  "access": "public"
46
46
  },
47
47
  "dependencies": {
48
- "@agimon-ai/po-web-components": "0.0.0-alpha.45"
48
+ "@agimon-ai/po-web-components": "0.0.0-alpha.47"
49
49
  },
50
50
  "devDependencies": {
51
- "@agimon-ai/po-build": "0.0.0-alpha.45",
52
- "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.45",
51
+ "@agimon-ai/po-build": "0.0.0-alpha.47",
52
+ "@agimon-ai/vibe-lint-plugin-po-web": "0.0.0-alpha.47",
53
53
  "@babel/core": "7.29.7",
54
54
  "@expo/vector-icons": "15.1.1",
55
55
  "@fontsource-variable/ibm-plex-sans": "5.3.0",