Skip to content

feat(prompts): add onCancel callback option to all prompts#544

Open
aqeelat wants to merge 3 commits into
bombshell-dev:mainfrom
aqeelat:on-cancel
Open

feat(prompts): add onCancel callback option to all prompts#544
aqeelat wants to merge 3 commits into
bombshell-dev:mainfrom
aqeelat:on-cancel

Conversation

@aqeelat
Copy link
Copy Markdown

@aqeelat aqeelat commented May 26, 2026

Summary

Adds an optional onCancel callback to all prompt functions via CommonOptions. When the user cancels a prompt (Ctrl+C or Escape), the callback is invoked — eliminating the need for isCancel guards at every call site. When onCancel is provided, the return type narrows from Promise<T | symbol> to Promise<T>.

Closes #83

Motivation

Every clack user repeats this pattern:

const result = await confirm({ message: "Continue?" });
if (isCancel(result)) {
  cancel("Operation cancelled.");
  process.exit(0);
}

This PR makes it:

const result = await confirm({
  message: "Continue?",
  onCancel: () => {
    cancel("Operation cancelled.");
    process.exit(0);
  },
});
// result is `boolean` — no isCancel guard needed

Changes

  • CommonOptions — added onCancel?: () => void
  • handleCancel utility — exported from @clack/prompts for custom prompt implementations
  • All 12 prompt functions — converted to function declarations with overloads for return type narrowing
  • Tests — runtime tests for cancel behavior + compile-time type narrowing tests

Type narrowing

Each prompt uses function overloads so TypeScript narrows the return type when onCancel is present:

// Without onCancel: result is boolean | symbol
const a = await confirm({ message: "Continue?" });
//    ^? boolean | symbol

// With onCancel: result is boolean (narrowed)
const b = await confirm({ message: "Continue?", onCancel: () => { process.exit(0); } });
//    ^? boolean

This works for all prompts: text, confirm, select, multiselect, groupMultiselect, password, autocomplete, autocompleteMultiselect, selectKey, date, multiline, path.

Design decisions

  • Additive, no breaking changesonCancel is optional; existing code works unchanged
  • Function overloads for narrowing — the idiomatic TypeScript pattern; converts export const arrows to export function declarations
  • handleCancel is exported — useful for custom prompt implementations

Test plan

  • All 727 existing + new tests pass
  • pnpm run build succeeds
  • pnpm run types succeeds
  • pnpm run lint succeeds
  • pnpm run format succeeds
  • Type narrowing verified via compile-time assertions (@ts-expect-error)

Add an optional onCancel callback to CommonOptions that is invoked when
the user cancels a prompt (Ctrl+C or Escape). This eliminates the need
for isCancel guards at every call site.

Also exports the handleCancel utility for custom prompt implementations.

Closes bombshell-dev#83 (partial — runtime behavior)
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 26, 2026

🦋 Changeset detected

Latest commit: 0125a25

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clack/prompts Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

aqeelat added 2 commits May 26, 2026 15:09
Convert all prompt functions to function declarations with overloads.
When onCancel is provided, the return type narrows from Promise<T | symbol>
to Promise<T>, eliminating the need for isCancel type guards.

Also removes unused WithCancel and CancelAware types from common.ts.
- Add onCancel.test.ts with runtime cancel/submit tests for all 12 prompts
- Expand type-narrowing.test.ts to cover autocomplete, autocompleteMultiselect, and path
- All 12 prompts now have full runtime and compile-time coverage
@43081j
Copy link
Copy Markdown
Collaborator

43081j commented May 26, 2026

When onCancel is provided, the return type narrows from Promise<T | symbol> to Promise.

Unsafely, yes:

const result = await confirm({
  message: "Continue?",
  onCancel: () => {
    console.log("do anything here that _doesn't_ process.exit");
  },
});

// result is still T | Symbol at runtime

i'm not sure we should go down this path yet until the issue has a resolution.

my main concern is that this is mixing two different architectures:

  1. cancellations as symbols
  2. cancellations as callbacks

we currently use the first one. introducing the second means we now have two ways of dealing with cancellations, which seems awkward to me.

ultimately you're just trying to avoid having to type-check results, but that takes about as much code as your onCancel handler. could you explain what the gain is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Request] Improve cancellation API

2 participants