Feedback & status
Pull to Refresh
Elastic pull gesture with smooth interactive line charts and async lifecycle feedback
detent-pull-to-refresh
Port status
- Catalog status
- implemented
- Origin
- upstream
- Phase
- 4
- Wave
- phase-4-motion-controls
- Group
- Feedback & status
11.5 KB gzip for the class module · inlines the shared spring
Variants
Slotted content
The built-in card is replaced while the default slot has content. This panel is disabled, so it does
not pull.
Pull any enabled panel down past its threshold, or press R while it is focused. Nothing has been refreshed yet.
Recorded states
pressedrecorded state; no setterdisabledrecorded state; no setterloadingrecorded state; no setterdark themerecorded state; no setterreduced motionrecorded state; no settercompletedrecorded state; no setter
Usage
Import
import '/components/pull-to-refresh/define.js';
// or register it yourself
import { DetentPullToRefresh } from '/components/pull-to-refresh/pull-to-refresh.js';
Attributes
changecontextdisabledis-refreshingmain-valuemax-pullrefreshing-hold-offsetresistanceselected-time-rangeshow-actionsshow-default-indicatorshow-metricsthreshold
Properties
dataMaponActionClickonRefreshonTimeRangeChangepullIndicatortimeRanges
Slots
default
Events
detent-refresh— {}detent-time-range-change— {}detent-action-click— {}
Example
Runnable example: site/examples/pull-to-refresh.ts. The snippet below is generated from the catalogued attributes.
<detent-pull-to-refresh
change
context
disabled
is-refreshing
main-value
max-pull="110"
refreshing-hold-offset="48"
resistance="0.45"
selected-time-range
show-actions
show-default-indicator
show-metrics
threshold="68"
></detent-pull-to-refresh>
Property-only values
const element = document.querySelector('detent-pull-to-refresh');
if (element !== null) {
// onRefresh: () => Promise<void> | void
element.onRefresh = () => {};
}
Source
The pinned upstream source for this row:
src/components/ui/PullToRefresh.tsxsha256 9352188b212a…
Native files
The native module and its artifacts:
src/components/pull-to-refresh/pull-to-refresh.tssrc/components/pull-to-refresh/styles.csssrc/components/pull-to-refresh/define.tsdocs/components/pull-to-refresh.mdsite/examples/pull-to-refresh.tstests/browser/pull-to-refresh.spec.tsevidence/components/pull-to-refresh.md
API
Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.
Attributes
| Attribute | Property | Type | Default | Notes |
|---|---|---|---|---|
change | — | — | — | Recorded in the catalog as an attribute. |
context | — | — | — | Recorded in the catalog as an attribute. |
disabled | disabled | boolean | false | Presence semantics: present means true, absent means false. |
is-refreshing | — | — | — | Recorded in the catalog as an attribute. |
main-value | — | — | — | Recorded in the catalog as an attribute. |
max-pull | maxPull | number | 110 | Kebab-case attribute; camelCase property. |
refreshing-hold-offset | refreshingHoldOffset | number | 48 | Kebab-case attribute; camelCase property. |
resistance | resistance | number | 0.45 | Kebab-case attribute; camelCase property. |
selected-time-range | — | — | — | Recorded in the catalog as an attribute. |
show-actions | — | — | — | Recorded in the catalog as an attribute. |
show-default-indicator | showDefaultIndicator | boolean | true | Presence semantics: present means true, absent means false. |
show-metrics | — | — | — | Recorded in the catalog as an attribute. |
threshold | threshold | number | 68 | Kebab-case attribute; camelCase property. |
Properties
| Property | Type | Default | Notes |
|---|---|---|---|
dataMap | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
onActionClick | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
onRefresh | () => Promise<void> | void | undefined | Property-only: assign from script; not accepted as an attribute. |
onTimeRangeChange | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
pullIndicator | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
timeRanges | — | — | Recorded in the catalog without a top-level type; pinned from source when the row is ported. |
Events
| Event | Upstream prop | Payload type | Notes |
|---|---|---|---|
detent-refresh | onRefresh | () => Promise<void> | void | Composed, bubbles; fires only for a user action. |
detent-time-range-change | onTimeRangeChange | (range: string) => void | Composed, bubbles; fires only for a user action. |
detent-action-click | onActionClick | (action: 'deposit' | 'withdraw') => void | Composed, bubbles; fires only for a user action. |
Slots
| Slot | Notes |
|---|---|
default | Consumer content; rendered through a slot, never re-created. |
CSS customisation
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- pullIndicator is a notification hook, not a render function.
- onRefresh stays awaitable and detent-refresh carries the pinned empty detail; a 1100 ms fallback simulation covers consumers that resolve nothing.
- FinancialStatsCard is the default-slot fallback rendering, not a second tag or slug.
- The layoutId pill morph becomes a 150 ms CSS background/colour transition, and the chart has no draw animation.
- Trend line and muted labels are darkened (light) or lightened (dark) for measured contrast; the upstream up-trend stroke measured 1.07:1.
- Enter also refreshes when the region itself is focused.
- A no-op pull emits no event, and a non-matching pointerId is ignored.
- The upstream data-scrollable="true" hatch is not reproduced because the row does not record it.
- main-value, change and context are attributes while dataMap and timeRanges stay property-only.
What it does
An elastic pull-to-refresh container around consumer content, with a built-in financial statistics card (bezier chart, pointer scrubbing, 1H/4H/1D range tabs, High/Low/Volume metrics and optional deposit/transfer actions) as the default slot content.
Upstream behaviour
- Seamless Light and Dark mode theming with tuned contrast and crisp aesthetics
- Physical logarithmic spring resistance matching iOS and native touch physics
- Interactive pointer and touch chart scrubbing with realtime price and timestamp tracking
- Integrated financial statistics card with smooth bezier curve line chart, area fill, and endpoint glow
- Minimal key metrics summary row (24h High, 24h Low, Volume)
- Reactive time range switching (1H, 4H, 1D) with spring morph pill indicators
- Full async lifecycle support (idle, pulling, ready, refreshing, complete)
- Keyboard accessible refresh triggers (Press R or Enter) and screen reader support
Differences from upstream
- React `children`/`ReactNode` props become DOM slots; caller content stays light DOM.
- React callback props become typed composed CustomEvents; programmatic state assignment emits none.
- `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
- React state/hooks replaced by element-owned state with pre-upgrade property preservation.
- pullIndicator is a notification hook, not a render function.
- onRefresh stays awaitable and detent-refresh carries the pinned empty detail; a 1100 ms fallback simulation covers consumers that resolve nothing.
- FinancialStatsCard is the default-slot fallback rendering, not a second tag or slug.
- The layoutId pill morph becomes a 150 ms CSS background/colour transition, and the chart has no draw animation.
- Trend line and muted labels are darkened (light) or lightened (dark) for measured contrast; the upstream up-trend stroke measured 1.07:1.
- Enter also refreshes when the region itself is focused.
- A no-op pull emits no event, and a non-matching pointerId is ignored.
- The upstream data-scrollable="true" hatch is not reproduced because the row does not record it.
- main-value, change and context are attributes while dataMap and timeRanges stay property-only.