Detent web components
By Shane Logsdon

Data & tables

Recovery Ledger

Implemented

Version history timeline & state recovery ledger

detent-recovery-ledger

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-data
Group
Data & tables

7.9 KB gzip for the class module · inlines the shared spring

Variants

The ledger in the main preview uses the default timeline variant: a rail, timeline pips and a tinted Current Head card. The one above uses variant="compact", which hides the rail and the pips. Use the disclosure button on any snapshot to open its diff and metadata drawer; the revert button emits detent-restore and enters the documented loading state until endRestore() is called. Sample snapshots are local data: nothing is fetched, and the simulated revert only re-marks the current entry.

No snapshot has been expanded yet.

Recorded states

  • loading recorded state; no setter
  • active recorded state; no setter

Usage

Import

import '/components/recovery-ledger/define.js';

// or register it yourself
import { DetentRecoveryLedger } from '/components/recovery-ledger/recovery-ledger.js';

Attributes

  • current-entry-id
  • variant

Properties

  • entries
  • onRestore
  • onSelect

Slots

No slots are recorded for this component.

Events

  • detent-restore — {}
  • detent-select — {}

Example

Runnable example: site/examples/recovery-ledger.ts. The snippet below is generated from the catalogued attributes.

<detent-recovery-ledger
  current-entry-id
  variant="timeline"
></detent-recovery-ledger>

Property-only values

const element = document.querySelector('detent-recovery-ledger');
if (element !== null) {
  // entries: LedgerEntry[]
  element.entries = [];
  // onRestore: (id: string) => Promise<void> | void
  element.onRestore = () => {};
  // onSelect: (id: string) => void
  element.onSelect = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/RecoveryLedger.tsx sha256 866a7fb1af2c…

Native files

The native module and its artifacts:

  • src/components/recovery-ledger/recovery-ledger.ts
  • src/components/recovery-ledger/styles.css
  • src/components/recovery-ledger/define.ts
  • docs/components/recovery-ledger.md
  • site/examples/recovery-ledger.ts
  • tests/browser/recovery-ledger.spec.ts
  • evidence/components/recovery-ledger.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
current-entry-idcurrentEntryIdstringundefinedKebab-case attribute; camelCase property.
variantvariant'timeline' | 'compact''timeline'Kebab-case attribute; camelCase property.

Properties

Properties
PropertyTypeDefaultNotes
entriesLedgerEntry[]—Property-only: assign from script; not accepted as an attribute.
onRestore(id: string) => Promise<void> | voidundefinedProperty-only: assign from script; not accepted as an attribute.
onSelect(id: string) => voidundefinedProperty-only: assign from script; not accepted as an attribute.

Events

Events
EventUpstream propPayload typeNotes
detent-restoreonRestore(id: string) => Promise<void> | voidComposed, bubbles; fires only for a user action.
detent-selectonSelect(id: string) => voidComposed, bubbles; fires only for a user action.

Slots

No slots are recorded for this component.

CSS customisation

  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `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.
  • React children/ReactNode props become DOM slots; this row has none, so no content slot exists. className is replaced by the documented parts and custom properties (no Tailwind in consumers).
  • entries defaults to []; upstream declares it required with no default.
  • onSelect / onRestore become the composed events detent-select { id } and detent-restore { id }; programmatic writes emit none (PC-6.5).
  • onRestore's Promise return cannot be awaited across dispatchEvent, so the loading state ends through the documented endRestore() method, or implicitly when the consumer replaces entries/currentEntryId.
  • Upstream shows the revert button only when an onRestore prop exists. A custom element cannot observe listeners reliably, so the button is rendered for every restorable, non-current entry and the consumer decides whether to act.
  • The revert label is Revert to version <id> (contains the visible Revert); upstream used Restore version <id> with the visible word Revert, a WCAG 2.5.3 failure.
  • The drawer's springSmooth (200/28/0.85) height/opacity presence runs on the shared spring helper: a closing drawer is inert at once and hidden when the spring settles. Newly mounted entries rise in idx x 50 ms apart on framer-motion 13's default transition (y spring 500/25/1 on the shared helper, which settles on its own threshold rather than Framer's restSpeed 10; opacity a 0.3 s cubic-bezier(0.25, 0.1, 0.35, 1) tween via WAAPI). The chevron turns 180 degrees on springSnappy through the shared spring helper. Motion fidelity remains a manual gate; reduced motion jumps the drawer and removes the remaining transitions.
  • Upstream's dark-only palette (bg-[#0E0E0E]) is replaced by theme tokens so both colour schemes meet AA.
  • lucide-react icons (RotateCcw, ChevronDown, User, Clock, Loader2) become local text glyphs marked aria-hidden.

What it does

A chronological version-history ledger: a vertical timeline rail, a current-head marker, an expandable diff/metadata drawer per snapshot and a one-click revert action that reports a loading state.

Upstream behaviour
  • Chronological version ledger with vertical timeline rail and active head marker
  • One-click state rollback action with loading animation feedback
  • Expandable code diff viewer and snapshot metadata inspection drawer
  • Full keyboard tab access and screen reader timestamp announcements
Differences from upstream
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `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.
  • React children/ReactNode props become DOM slots; this row has none, so no content slot exists. className is replaced by the documented parts and custom properties (no Tailwind in consumers).
  • entries defaults to []; upstream declares it required with no default.
  • onSelect / onRestore become the composed events detent-select { id } and detent-restore { id }; programmatic writes emit none (PC-6.5).
  • onRestore's Promise return cannot be awaited across dispatchEvent, so the loading state ends through the documented endRestore() method, or implicitly when the consumer replaces entries/currentEntryId.
  • Upstream shows the revert button only when an onRestore prop exists. A custom element cannot observe listeners reliably, so the button is rendered for every restorable, non-current entry and the consumer decides whether to act.
  • The revert label is Revert to version <id> (contains the visible Revert); upstream used Restore version <id> with the visible word Revert, a WCAG 2.5.3 failure.
  • The drawer's springSmooth (200/28/0.85) height/opacity presence runs on the shared spring helper: a closing drawer is inert at once and hidden when the spring settles. Newly mounted entries rise in idx x 50 ms apart on framer-motion 13's default transition (y spring 500/25/1 on the shared helper, which settles on its own threshold rather than Framer's restSpeed 10; opacity a 0.3 s cubic-bezier(0.25, 0.1, 0.35, 1) tween via WAAPI). The chevron turns 180 degrees on springSnappy through the shared spring helper. Motion fidelity remains a manual gate; reduced motion jumps the drawer and removes the remaining transitions.
  • Upstream's dark-only palette (bg-[#0E0E0E]) is replaced by theme tokens so both colour schemes meet AA.
  • lucide-react icons (RotateCcw, ChevronDown, User, Clock, Loader2) become local text glyphs marked aria-hidden.
Previous: Rainbow Button Next: Reveal Card

Docs

Components

Project