Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tall-doors-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clack/core': minor
---

Add `isAccessible()` and an `accessible` setting as the foundation for accessible mode. Resolution order: per-call option > `updateSettings({ accessible })` > the `ACCESSIBLE` environment variable (any non-empty value enables it, except `0` and `false`).
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ export { default as TextPrompt } from './prompts/text.js';
export type { ClackState as State } from './types.js';
export { block, getColumns, getRows, isCancel, wrapTextWithPrefix } from './utils/index.js';
export type { ClackSettings } from './utils/settings.js';
export { settings, updateSettings } from './utils/settings.js';
export { isAccessible, settings, updateSettings } from './utils/settings.js';
export type { Validate } from './utils/validation.js';
export { runValidation } from './utils/validation.js';
27 changes: 27 additions & 0 deletions packages/core/src/utils/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface InternalClackSettings {
error: string;
};
withGuide: boolean;
accessible: boolean | undefined;
date: {
monthNames: string[];
messages: {
Expand Down Expand Up @@ -54,6 +55,7 @@ export const settings: InternalClackSettings = {
error: 'Something went wrong',
},
withGuide: true,
accessible: undefined,
date: {
monthNames: [...DEFAULT_MONTH_NAMES],
messages: {
Expand Down Expand Up @@ -94,6 +96,12 @@ export interface ClackSettings {

withGuide?: boolean;

/**
* Force accessible (static, screen-reader friendly) output on or off.
* Overrides the `ACCESSIBLE` environment variable.
*/
accessible?: boolean;

/**
* Date prompt localization
*/
Expand Down Expand Up @@ -145,6 +153,10 @@ export function updateSettings(updates: ClackSettings) {
settings.withGuide = updates.withGuide !== false;
}

if (updates.accessible !== undefined) {
settings.accessible = updates.accessible === true;
}

if (updates.date !== undefined) {
const date = updates.date;
if (date.monthNames !== undefined) {
Expand All @@ -170,6 +182,21 @@ export function updateSettings(updates: ClackSettings) {
}
}

/**
* Resolves whether accessible (static, screen-reader friendly) output is enabled.
*
* Resolution order: per-call option > global `accessible` setting > `ACCESSIBLE`
* environment variable (any non-empty value enables it, except `0` and `false`).
*
* @param accessible - Per-call override; takes precedence when defined
*/
export function isAccessible(accessible?: boolean): boolean {
if (accessible !== undefined) return accessible;
if (settings.accessible !== undefined) return settings.accessible;
const value = process.env.ACCESSIBLE;
return value !== undefined && value !== '' && value !== '0' && value !== 'false';
}

/**
* Check if a key is an alias for a default action
* @param key - The raw key which might match to an action
Expand Down
63 changes: 63 additions & 0 deletions packages/core/test/utils/settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { afterEach, describe, expect, test } from 'vitest';
import { isAccessible, settings, updateSettings } from '../../src/utils/settings.js';

describe('isAccessible', () => {
const originalEnv = process.env.ACCESSIBLE;

afterEach(() => {
if (originalEnv === undefined) {
delete process.env.ACCESSIBLE;
} else {
process.env.ACCESSIBLE = originalEnv;
}
settings.accessible = undefined;
});

describe('ACCESSIBLE env var', () => {
test('disabled when unset', () => {
delete process.env.ACCESSIBLE;
expect(isAccessible()).toBe(false);
});

test.each(['', '0', 'false'])('disabled when set to %j', (value) => {
process.env.ACCESSIBLE = value;
expect(isAccessible()).toBe(false);
});

test.each(['1', 'true', 'yes'])('enabled when set to %j', (value) => {
process.env.ACCESSIBLE = value;
expect(isAccessible()).toBe(true);
});
});

describe('global setting', () => {
test('updateSettings({ accessible: true }) enables it', () => {
delete process.env.ACCESSIBLE;
updateSettings({ accessible: true });
expect(isAccessible()).toBe(true);
});

test('updateSettings({ accessible: false }) overrides the env var', () => {
process.env.ACCESSIBLE = '1';
updateSettings({ accessible: false });
expect(isAccessible()).toBe(false);
});
});

describe('per-call option', () => {
test('true overrides a false global setting', () => {
updateSettings({ accessible: false });
expect(isAccessible(true)).toBe(true);
});

test('false overrides the env var', () => {
process.env.ACCESSIBLE = '1';
expect(isAccessible(false)).toBe(false);
});

test('undefined falls through to env var', () => {
process.env.ACCESSIBLE = '1';
expect(isAccessible(undefined)).toBe(true);
});
});
});
Loading