diff --git a/.changeset/dirty-forks-study.md b/.changeset/dirty-forks-study.md new file mode 100644 index 00000000..d1a4f42c --- /dev/null +++ b/.changeset/dirty-forks-study.md @@ -0,0 +1,5 @@ +--- +'@clack/core': minor +--- + +Add an `accessible` option to the shared `PromptOptions` and a resolved `accessible` getter on the base `Prompt`, so any prompt can opt into accessible mode individually. Resolution order: per-prompt option > `updateSettings({ accessible })` > the `ACCESSIBLE` environment variable. diff --git a/packages/core/src/prompts/prompt.ts b/packages/core/src/prompts/prompt.ts index 2291544b..16830864 100644 --- a/packages/core/src/prompts/prompt.ts +++ b/packages/core/src/prompts/prompt.ts @@ -9,6 +9,7 @@ import { CANCEL_SYMBOL, diffLines, getRows, + isAccessible, isActionKey, setRawMode, settings, @@ -18,6 +19,11 @@ import { runValidation } from '../utils/validation.js'; export interface PromptOptions> { render(this: Omit): string | undefined; + /** + * Whether to render this prompt in accessible (static, screen-reader friendly) mode. + * Takes precedence over the global `accessible` setting and the `ACCESSIBLE` env var. + */ + accessible?: boolean; initialValue?: any; initialUserInput?: string; @@ -50,6 +56,15 @@ export default class Prompt { public value: TValue | undefined; public userInput = ''; + /** + * Whether accessible (static, screen-reader friendly) output is enabled for + * this prompt, resolved from the `accessible` option, the global setting, + * and the `ACCESSIBLE` env var. + */ + public get accessible(): boolean { + return isAccessible(this.opts.accessible); + } + constructor(options: PromptOptions>, trackValue = true) { const { input = stdin, output = stdout, render, signal, ...opts } = options; diff --git a/packages/core/test/prompts/prompt.test.ts b/packages/core/test/prompts/prompt.test.ts index 89c1e37f..02dc4570 100644 --- a/packages/core/test/prompts/prompt.test.ts +++ b/packages/core/test/prompts/prompt.test.ts @@ -2,7 +2,7 @@ import { type } from 'arktype'; import { cursor } from 'sisteransi'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { default as Prompt } from '../../src/prompts/prompt.js'; -import { isCancel } from '../../src/utils/index.js'; +import { isCancel, settings } from '../../src/utils/index.js'; import { MockReadable } from '../mock-readable.js'; import { MockWritable } from '../mock-writable.js'; @@ -315,6 +315,55 @@ describe('Prompt', () => { }); }); + describe('accessible', () => { + const originalEnv = process.env.ACCESSIBLE; + + afterEach(() => { + if (originalEnv === undefined) { + delete process.env.ACCESSIBLE; + } else { + process.env.ACCESSIBLE = originalEnv; + } + settings.accessible = undefined; + }); + + test('accessible: true option enables it', () => { + const instance = new Prompt({ + input, + output, + render: () => 'foo', + accessible: true, + }); + + expect(instance.accessible).to.equal(true); + }); + + test('accessible: false option overrides the env var', () => { + process.env.ACCESSIBLE = '1'; + const instance = new Prompt({ + input, + output, + render: () => 'foo', + accessible: false, + }); + + expect(instance.accessible).to.equal(false); + }); + + test('falls through to the env var when option is unset', () => { + delete process.env.ACCESSIBLE; + const instance = new Prompt({ + input, + output, + render: () => 'foo', + }); + + expect(instance.accessible).to.equal(false); + process.env.ACCESSIBLE = '1'; + expect(instance.accessible).to.equal(true); + }); + }); + describe('standard schema', () => { test('accepts invalid initial value', () => { const instance = new Prompt({