Detent web components
By Shane Logsdon

Feedback & status

Pull to Refresh

Implemented

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

  • pressed recorded state; no setter
  • disabled recorded state; no setter
  • loading recorded state; no setter
  • dark theme recorded state; no setter
  • reduced motion recorded state; no setter
  • completed recorded 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

  • change
  • context
  • disabled
  • is-refreshing
  • main-value
  • max-pull
  • refreshing-hold-offset
  • resistance
  • selected-time-range
  • show-actions
  • show-default-indicator
  • show-metrics
  • threshold

Properties

  • dataMap
  • onActionClick
  • onRefresh
  • onTimeRangeChange
  • pullIndicator
  • timeRanges

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.tsx sha256 9352188b212a…

Native files

The native module and its artifacts:

  • src/components/pull-to-refresh/pull-to-refresh.ts
  • src/components/pull-to-refresh/styles.css
  • src/components/pull-to-refresh/define.ts
  • docs/components/pull-to-refresh.md
  • site/examples/pull-to-refresh.ts
  • tests/browser/pull-to-refresh.spec.ts
  • evidence/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

Attributes
AttributePropertyTypeDefaultNotes
change———Recorded in the catalog as an attribute.
context———Recorded in the catalog as an attribute.
disableddisabledbooleanfalsePresence 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-pullmaxPullnumber110Kebab-case attribute; camelCase property.
refreshing-hold-offsetrefreshingHoldOffsetnumber48Kebab-case attribute; camelCase property.
resistanceresistancenumber0.45Kebab-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-indicatorshowDefaultIndicatorbooleantruePresence semantics: present means true, absent means false.
show-metrics———Recorded in the catalog as an attribute.
thresholdthresholdnumber68Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
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> | voidundefinedProperty-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

Events
EventUpstream propPayload typeNotes
detent-refreshonRefresh() => Promise<void> | voidComposed, bubbles; fires only for a user action.
detent-time-range-changeonTimeRangeChange(range: string) => voidComposed, bubbles; fires only for a user action.
detent-action-clickonActionClick(action: 'deposit' | 'withdraw') => voidComposed, bubbles; fires only for a user action.

Slots

Slots
SlotNotes
defaultConsumer 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.
Previous: Profile Card Next: Rainbow Button

Docs

Components

Project