npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

slide-confirm

v22.0.1

Published

This is an Angular Module containing Components/Services using Material

Readme

Slide Confirm

A Material-styled drag-to-confirm control, modeled on iOS's "slide to power off" gesture. A consumer drags a circular thumb across a full-width track; crossing a configurable distance threshold locks the control into a confirmed state and fires a confirmed event. A companion cancel button sits to the left of the track — clicking it resets the gesture back to idle, fires a canceled event, and disables the thumb/swipe until the consumer calls reset().

There are two variations, chosen with the confirmSubmit input:

  • Non-submit type (confirmSubmit off, the default) — completing the swipe is the whole interaction. The track immediately collapses into a 44px green circle with a checkmark, matching the cancel button's size.
  • Submit type (confirmSubmit on) — completing the swipe fires confirmed but leaves the confirmed track as a clickable submit button (submitLabel). Only clicking that button — firing submit — collapses the track into the same green checkmark circle.

Either way, the accepted end state looks identical: a small green circle with a checkmark. The two types just differ in whether there's one user action to reach it or two.

Anatomy

┌────┐  ┌──────────────────────────────────────────────────┐
│ ✕  │  │  ( thumb )        Slide to confirm                │
└────┘  └──────────────────────────────────────────────────┘
 cancel   0.5rem gap                track
 button
  • Cancel button — a 44px whitesmoke circle with a single icon (close by default), positioned left of the track with a 0.5rem gap. Clicking it resets the gesture to idle, emits canceled, and disables the thumb until reset() is called.
  • Track — a full-width pill. Shows instructionLabel before confirmation. Once confirmed, it's either the green checkmark circle (non-submit type) or a clickable submit button (submit type, until submitted).
  • Thumb — a 44px circle (Material theme-colored via color), inset 2px inside the track, draggable along the horizontal axis only. Removed from the DOM once confirmed.

Usage

import { SlideConfirmModule } from 'slide-confirm';
<app-slide-confirm
  instructionLabel="Slide to submit"
  color="warn"
  [threshold]="80"
  (confirmed)="onConfirmed()"
  (canceled)="onCanceled()"
></app-slide-confirm>

Resetting after confirm/submit

reset() is a public method, not an @Input()/CVA-driven value, so call it via a template reference or @ViewChild:

<app-slide-confirm #slide (confirmed)="onConfirmed()" (canceled)="onCanceled()"></app-slide-confirm>
<button (click)="slide.reset()">Reset</button>
@ViewChild(SlideConfirmComponent) slide!: SlideConfirmComponent;
this.slide.reset();

Note that clicking the built-in cancel button already resets the gesture for you (plus emits canceled) — but it also leaves the thumb disabled afterward, so a reset() call (from a "start over" button, or after handling canceled/submit) is what actually re-enables the swipe again.

Reactive forms

The component implements ControlValueAccessor, so formControlName/[formControl] compose for both disabled state and patching:

<app-slide-confirm formControlName="agree" (confirmed)="onConfirmed()"></app-slide-confirm>

Disabling the bound FormControl disables the whole gesture (both the thumb and the cancel button). Patching a truthy value (form.patchValue({ agree: true }), or calling writeValue(true) directly) drives the control straight into its accepted state — see Patching via writeValue below for what that looks like in each mode.

Two-step confirm-then-submit

<app-slide-confirm
  [confirmSubmit]="true"
  submitLabel="Press to submit"
  color="warn"
  (confirmed)="onConfirmed()"
  (canceled)="onCanceled()"
  (submit)="onSubmit()"
></app-slide-confirm>

Here, sliding past the threshold still fires confirmed (the gesture itself hasn't changed), but the confirmed track renders submitLabel as a real, clickable button rather than collapsing immediately. Clicking it fires submit exactly once and collapses the track into the green checkmark circle. The cancel button stays available through this "confirmed but not yet submitted" window — clicking it resets all the way back to idle (thumb and instruction text reappear, disabled) rather than just back to the submit button.

Inputs

| Input | Type | Default | Description | |---|---|---|---| | threshold | number | 80 | Percentage of the track's live rendered width the thumb must cross (and stay past, at release) to confirm. | | instructionLabel | string | 'Slide to confirm' | Text shown in the track before confirmation. | | color | ThemePalette | 'primary' | Material theme palette ('primary' \| 'accent' \| 'warn') applied to the thumb, and to the track's background while confirmed-but-not-yet-submitted (submit type only — the accepted end state is always green). | | icon | string | 'chevron_right' | Material icon shown inside the drag thumb. | | cancelIcon | string | 'close' | Material icon shown inside the cancel button. | | disabled | boolean | false | Disables the thumb (drag + keyboard) and the cancel button together. Also settable via ControlValueAccessor.setDisabledState. | | confirmSubmit | boolean | false | When true, the confirmed state renders as a clickable submit button (submitLabel) instead of collapsing to the checkmark immediately. | | submitLabel | string | 'Press to submit' | Text shown on the submit button. Only used when confirmSubmit is true. | | submittedIcon | string | 'check' | Material icon shown in the collapsed, accepted-state circle. |

Outputs

| Output | Type | Emitted when | |---|---|---| | confirmed | EventEmitter<void> | The drag crosses threshold and is released, or Enter/Space is pressed on the focused thumb. Fires exactly once per confirmation. | | canceled | EventEmitter<void> | The cancel button is clicked. The cancel button itself isn't a one-shot lock (it can be clicked again later), but each successful cancel disables the thumb — see Cancel resets the gesture and disables the thumb below. | | submit | EventEmitter<void> | The submit button is clicked (only reachable when confirmSubmit is true and the control is already confirmed). Fires exactly once. |

Public methods

| Method | Description | |---|---| | reset() | Clears the confirmed, submitted, and canceled-lock state, restoring the thumb to its interactive idle state. Does not change an externally-set disabled input — a consumer that disabled the control independently stays responsible for re-enabling it. |

Implementation notes

State machine

                    drag < threshold, released
              ┌──────────────────────────────────┐
              │                                   ▼
        ┌──────────┐   pointerdown/keydown  ┌──────────────┐
   ┌───▶│   idle   │────────────────────────▶│   dragging   │
   │    └──────────┘                         └──────────────┘
   │         ▲                                      │
   │         │ cancel clicked (resets, emits         │ drag ≥ threshold, released
   │         │ canceled — from idle this is a no-op) │      (or Enter/Space)
   │         │                                        ▼
   │                                          ┌──────────────┐
   │           confirmSubmit off: collapses    │  confirmed   │
   │           straight to the checkmark  ◀────│              │
   │                                          └──────────────┘
   │                                                  │ confirmSubmit on:
   │                                                  │ shows submit button;
   │                                                  │ cancel still resets
   │                                                  │ straight to idle from
   │                                                  ▼ here too
   │                                          ┌──────────────┐
   └───────────────── reset() ────────────────│  submitted   │
                                               │  (collapsed) │
                                               └──────────────┘

isConfirmed and isSubmitted are separate internal booleans (not a single enum) so each can independently gate its own visual treatment. Both are cleared together by reset().

Cancel is a reset, not a lock

Earlier iterations of this component treated "canceled" as its own locked, terminal state (mirroring "confirmed") that required an external reset() call to clear. That made the cancel button disable the thumb it sits next to, which reads wrong for a cancel action — clicking cancel should hand control back, not take more away. onCancel() now just calls the same internal reset logic reset() uses (clearing isConfirmed/isSubmitted/drag position) and emits canceled, so:

  • It can be clicked more than once — there's nothing left over to "unlock."
  • Right after a cancel, the thumb is immediately draggable again (isLocked no longer references anything cancel-related).
  • Canceling during the submit type's "confirmed but not yet submitted" window resets all the way back to idle (thumb and instruction text reappear), not to some intermediate disabled state.

Cancel is still blocked while disabled is set externally, and while the control is in a state cancel doesn't apply to (already confirmed, for the non-submit type; already submitted, for the submit type) — that part is unchanged, via isCancelLocked.

Three lock getters, not one

  • isLocked — disabled || isConfirmed. Gates the thumb: whether it can be dragged (cdkDragDisabled) or keyboard-confirmed (Enter/Space), and its tabindex/aria-disabled. The thumb is also removed from the DOM entirely once confirmed, so this mostly matters pre-confirmation.
  • isCancelLocked — gates the cancel button's [disabled] and the guard inside onCancel(). Always locked while disabled. Otherwise: confirmSubmit off → locked once isConfirmed; confirmSubmit on → stays unlocked through the confirmed-but-not-submitted window and only locks once isSubmitted.
  • isSubmitLocked — disabled || isSubmitted. Gates the submit button, so a second click can't re-fire submit.

Drag gesture (Angular CDK, not raw Pointer Events)

The thumb uses @angular/cdk/drag-drop's cdkDrag, axis-locked to horizontal (cdkDragLockAxis="x"), bounded to the track element (cdkDragBoundary). CDK already unifies mouse/touch/pointer input and is already a dependency used elsewhere in this workspace, so this avoids hand-rolled pointer-event math.

Threshold resolved against live width, not a fixed pixel value

The track is always full width (width: 100%; there is deliberately no compact/fullWidth mode — a short track would defeat the point of a deliberate drag-to-confirm gesture). Because its pixel width isn't fixed, calcPercent() reads trackRef.nativeElement.clientWidth and thumbRef.nativeElement.clientWidth live, at the moment they're needed (on every cdkDragMoved tick and at cdkDragEnded), so resizing the container never leaves a stale threshold calculation in place.

Live fade as you drag

onDragMoved computes dragPercent (0–100, clamped) using the same distance/width math as the release check, and the instruction label's opacity binds to 1 - dragPercent / 100 — so the text fades out smoothly as the thumb approaches the threshold. This transition is suppressed while a drag is in progress (.slide-confirm--dragging) so it tracks the pointer 1:1 instead of lagging behind a CSS transition.

Animations only play entering a state, never leaving one

The track's shape (pill → 44px circle) and background-color (gray → theme color → green) both animate going into --confirmed/--submitted, but snap back instantly on reset() — no morphing from a circle back into a pill, no color fading back to gray. This is deliberate: per the CSS transitions spec, a property change is animated only if the new matched state's transition declares that property, and transition doesn't merge across rules with different specificity — only the single highest-specificity match wins. So the base &__track rule declares no transition at all, and every --confirmed/--submitted variant declares the full property list (background-color, max-width, height, border-radius) itself. Going forward into one of those states animates (its own rule supplies the transition); going back to the untransitioned base snaps.

The confirmed-state label/checkmark is also a separate @if-rendered element (not a text swap on the same node), so it's a genuinely new DOM node when it mounts, and a slide-confirm-fade-in keyframe plays automatically. This only ever happens when isConfirmed/isSubmitted flip true — never mid-drag, never on reset.

Snap-back easing

Releasing short of threshold calls event.source.reset() (Angular CDK's built-in method for returning a free-dragged element to its origin) and the thumb's transform transitions back via a plain ease-out — deliberately not a spring/bounce curve, so the thumb decelerates straight into the start position without overshooting past it.

Accessibility

  • The thumb has tabindex="0" when interactive, -1 when locked/disabled, and aria-disabled reflects the same state.
  • Enter/Space on the focused thumb confirms immediately, as a non-drag fallback (mirroring the reference component's keyboard-focusable handle).
  • prefers-reduced-motion: reduce disables every transition/animation described above — the thumb snap-back, the instruction label's drag-fade, the track's shape/color transitions, and the confirmed-state fade-in — all become instant.

Patching via writeValue

writeValue drives the control programmatically — via formControl.patchValue(...)/setValue(...), or by calling it directly:

  • Falsy value (null, false, undefined) — equivalent to calling reset(): clears the confirmed/submitted state and returns to idle.
  • Truthy value — patches the control straight into its accepted terminal state, and that state always renders the same checkmark, regardless of confirmSubmit:
    • When confirmSubmit is true, this is the same state the control would naturally reach after a real drag-confirm followed by a real submit-click: isConfirmed and isSubmitted are both set.
    • When confirmSubmit is false, there's no submit step of its own — isSubmitted is set anyway, purely so the checkmark renders the same way a submit-type control's completed state does. confirm() itself does the same thing for the non-submit type when a real gesture completes.

Styling

  • The track and thumb size (48px track height, 44px thumb/cancel-button diameter, 2px inset) are fixed in the component's SCSS, not configurable via inputs.
  • Theme colors come from Material's own CSS custom properties (--mdc-theme-primary, --mdc-theme-secondary) with a hardcoded fallback for warn, so the component picks up an app's active Material theme automatically.
  • The cancel button is always a flat whitesmoke circle, independent of the color input — it's a secondary/neutral action by design, visually distinct from the theme-colored primary thumb.
  • The accepted end state (checkmark circle) is always green (#43a047), independent of color — color only affects the thumb, and the submit type's intermediate confirmed-but-not-submitted track color.