Skip to content

@askable-ui/react-native

React Native bindings for askable-ui.

This initial slice focuses on explicit mobile interactions: useAskable() provides a context backed by @askable-ui/core, useAskableScreen() lets you mirror screen focus into that context, useAskableScrollView() mirrors raw ScrollView measurement into that context, useAskableVisibility() mirrors viewability updates from mobile lists, and <Askable /> turns onPress / onLongPress interactions into prompt-ready focus updates.

A runnable Expo reference app lives in examples/react-native-expo.

Install

bash
npm install @askable-ui/react-native @askable-ui/core

<Askable>

Clones a single React Native pressable child and merges focus updates into its onPress and onLongPress handlers. Nested <Askable> wrappers also contribute ancestor segments, so press-driven mobile flows can serialize the same hierarchy paths as DOM-based web flows.

tsx
import { Pressable, Text } from 'react-native';
import { Askable, useAskable } from '@askable-ui/react-native';

function RevenueCard() {
  const { ctx } = useAskable();

  return (
    <Askable ctx={ctx} meta={{ widget: 'revenue' }} text="Revenue card">
      <Pressable>
        <Text>Revenue</Text>
      </Pressable>
    </Askable>
  );
}

Props:

PropTypeDescription
ctxAskableContextContext instance that receives focus updates
metaRecord<string, unknown> | stringMetadata pushed into the context on press
scopestringOptional category stored with press-driven focus for scoped prompt/history queries
textstringOptional human-readable label stored alongside meta
childrenReactElementA single child that accepts onPress / onLongPress props
tsx
<Askable ctx={ctx} meta={{ view: 'dashboard' }} text="Dashboard" scope="analytics">
  <Askable ctx={ctx} meta={{ tab: 'finance' }} text="Finance" scope="analytics">
    <Askable ctx={ctx} meta={{ metric: 'revenue' }} text="Revenue card" scope="analytics">
      <Pressable>
        <Text>Revenue</Text>
      </Pressable>
    </Askable>
  </Askable>
</Askable>

ctx.toPromptContext();
// → "User is focused on: — view: dashboard > tab: finance > metric: revenue — value \"Revenue card\""

useAskable(options?)

Returns a React Native-friendly wrapper around an AskableContext.

ts
import { useAskable } from '@askable-ui/react-native';

const { focus, promptContext, ctx } = useAskable();

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
namestringOptional context name passed through to createAskableContext()
viewportbooleanOptional viewport-aware core context mode
eventsAskableEvent[]Forwarded to the underlying core context

Returns:

ValueTypeDescription
focusAskableFocus | nullCurrent focus created by press-driven updates
promptContextstringNatural-language context string for your LLM prompt
ctxAskableContextFull context instance for push(), clear(), toHistoryContext(), etc.

useAskableScreen(options)

Pushes screen-level context into the shared AskableContext while a screen is active.

tsx
import { useIsFocused } from '@react-navigation/native';
import { useAskable, useAskableScreen } from '@askable-ui/react-native';

function RevenueScreen() {
  const isFocused = useIsFocused();
  const { ctx } = useAskable();

  useAskableScreen({
    ctx,
    active: isFocused,
    meta: { screen: 'RevenueScreen' },
    text: 'Revenue screen',
  });

  return null;
}

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
metaRecord<string, unknown> | stringScreen metadata pushed when the screen is active
textstringOptional label stored alongside the screen metadata
activebooleanWhether the screen is currently focused. Default: true
clearOnBlurbooleanClear the context when the screen becomes inactive. Default: true
name / viewport / eventsCore optionsForwarded when the hook creates its own context

useAskableScrollView(options)

Mirrors raw ScrollView measurement into the shared AskableContext by tracking child layouts and selecting the top visible measured item.

tsx
import { Pressable, ScrollView, Text } from 'react-native';
import { Askable, useAskable, useAskableScrollView } from '@askable-ui/react-native';

const cards = [
  { id: 'rev', title: 'Revenue', meta: { widget: 'revenue' } },
  { id: 'pipe', title: 'Pipeline', meta: { widget: 'pipeline' } },
];

function DashboardFeed() {
  const { ctx } = useAskable();
  const { onScroll, createOnItemLayout } = useAskableScrollView({
    ctx,
    getMeta: (card) => ({ ...card.meta, visible: true }),
    getText: (card) => `${card.title} is leading the dashboard scroll view`,
  });

  return (
    <ScrollView onScroll={onScroll} scrollEventThrottle={16}>
      {cards.map((card) => (
        <Askable key={card.id} ctx={ctx} meta={card.meta} text={card.title}>
          <Pressable onLayout={createOnItemLayout(card.id, card)}>
            <Text>{card.title}</Text>
          </Pressable>
        </Askable>
      ))}
    </ScrollView>
  );
}

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
activebooleanWhether scroll updates should currently affect focus. Default: true
clearOnBlurbooleanClear existing scroll focus when tracking becomes inactive. Default: true
getMeta(item, measured) => Record<string, unknown> | stringMaps the visible measured item into askable metadata
getText(item, measured) => stringOptional human-readable label stored alongside the metadata
selectVisible(items) => item | nullOverride which measured item should win focus. Default: the top-most visible item
name / viewport / eventsCore optionsForwarded when the hook creates its own context

Returns:

ValueTypeDescription
ctxAskableContextContext instance used for scroll-driven focus updates
onScroll(event) => voidAttach to ScrollView.onScroll
measureItem(key, item, layout) => voidManually register/update a child layout
unmeasureItem(key) => voidRemove a child layout when it leaves the tree
clearVisibleItem() => voidClear the current scroll-driven focus
createOnItemLayout(key, item) => onLayoutHandlerConvenience helper for onLayout wiring

useAskableVisibility(options)

Mirrors FlatList / SectionList viewability callbacks into the shared AskableContext.

tsx
import { FlatList, Text, View } from 'react-native';
import { useAskable, useAskableVisibility } from '@askable-ui/react-native';

const rows = [
  { id: 'deal-1', title: 'Enterprise renewal' },
  { id: 'deal-2', title: 'Expansion pipeline' },
];

function DealsList() {
  const { ctx } = useAskable();
  const { onViewableItemsChanged } = useAskableVisibility({
    ctx,
    getMeta: (item) => ({ dealId: item.id }),
    getText: (item) => item.title,
  });

  return (
    <FlatList
      data={rows}
      keyExtractor={(item) => item.id}
      onViewableItemsChanged={onViewableItemsChanged}
      renderItem={({ item }) => (
        <View>
          <Text>{item.title}</Text>
        </View>
      )}
    />
  );
}

Options:

OptionTypeDescription
ctxAskableContextReuse an existing context instead of creating a new one
activebooleanWhether visibility updates should currently affect focus. Default: true
clearOnBlurbooleanClear existing visibility focus when tracking becomes inactive. Default: true
getMeta(item, token) => Record<string, unknown> | stringMaps the visible row into askable metadata
getText(item, token) => stringOptional human-readable label stored alongside the metadata
selectViewable(tokens) => token | nullOverride which visible token should win focus. Default: the first visible item
name / viewport / eventsCore optionsForwarded when the hook creates its own context

Notes

  • This adapter currently covers press-driven interactions, lightweight screen-awareness, raw ScrollView measurement, and list viewability callbacks.
  • useAskableScreen() is designed to pair with React Navigation's useIsFocused() or a similar focus signal.
  • useAskableScrollView() is a good fit for custom dashboard layouts built with ScrollView.
  • useAskableVisibility() is ideal for FlatList / SectionList.
  • Existing child onPress / onLongPress handlers are preserved and still run.

Released under the MIT License.