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
2 changes: 1 addition & 1 deletion packages/superdoc/src/core/SuperDoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,7 @@ export class SuperDoc extends EventEmitter {

/**
* Export editors to DOCX format.
* @param {{ commentsType?: string, isFinalDoc?: boolean, fieldsHighlightColor?: string }} [options]
* @param {{ commentsType?: string, isFinalDoc?: boolean, fieldsHighlightColor?: string | null }} [options]
* @returns {Promise<Array<Blob>>}
*/
async exportEditorsToDOCX({ commentsType, isFinalDoc, fieldsHighlightColor } = {}) {
Expand Down
10 changes: 8 additions & 2 deletions packages/superdoc/src/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1160,8 +1160,14 @@ export interface ExportParams {
isFinalDoc?: boolean;
/** Auto-download or return blob. */
triggerDownload?: boolean;
/** Color for field highlights. */
fieldsHighlightColor?: string;
/**
* Color for field highlights. The runtime defaults to `null` when no
* value is supplied (and forwards `null` through to the underlying
* editor export, which accepts `string | null`); the typedef accepts
* `null` explicitly so consumers can pass an explicit "no highlight"
* value without a typecheck failure.
*/
fieldsHighlightColor?: string | null;
}

/** Surface where the edit originated. */
Expand Down
34 changes: 34 additions & 0 deletions tests/consumer-typecheck/src/export-params-fields-highlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Consumer typecheck: `ExportParams.fieldsHighlightColor` accepts
* `string | null | undefined` (SD-2828).
*
* The runtime defaults the field to `null` when the consumer omits it,
* forwards that `null` through to `Editor.exportDocx` (which accepts
* `string | null`), and then on through the converter. Before this
* widening the public typedef declared `string` only, so consumers
* passing the runtime-equivalent `null` got a strict-mode typecheck
* failure on a value the runtime accepts.
*
* If a future change re-narrows this field to `string`, the assignments
* below stop compiling and CI fails.
*/
import type { ExportParams } from 'superdoc';

// Each of the three valid shapes a consumer can pass: explicit color,
// explicit "no highlight" (null), and omitted (undefined). The current
// runtime treats omitted and explicit-null the same.
const withColor: ExportParams = { fieldsHighlightColor: '#ff0000' };
const withNull: ExportParams = { fieldsHighlightColor: null };
const omitted: ExportParams = {};

// Strict type-equality assertion: a re-narrowing to `string` (or to
// `string | undefined` without `null`) would still leave the explicit
// color and omitted forms compiling, so plain assignments alone do not
// catch a regression. Pin the exact field type here.
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
type AssertEqual<A, B> = Equal<A, B> extends true ? true : never;

const _fieldsHighlightColorTypeIsExact: AssertEqual<ExportParams['fieldsHighlightColor'], string | null | undefined> =
true;

void [withColor, withNull, omitted, _fieldsHighlightColorTypeIsExact];
26 changes: 26 additions & 0 deletions tests/consumer-typecheck/typecheck-matrix.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,32 @@ const scenarios = [
files: ['src/search-match.ts'],
mustPass: true,
},

// SD-2828: `ExportParams.fieldsHighlightColor` accepts `string | null
// | undefined`. The runtime defaults the field to `null` when omitted
// and forwards that `null` straight through to `Editor.exportDocx`
// (which already types it as `string | null`). The previous public
// typedef narrowed to `string`, so consumers passing the
// runtime-equivalent `null` failed strict-mode typechecks. Pinned
// here so a future re-narrowing surfaces as a typecheck failure.
{
name: 'bundler / fieldsHighlightColor accepts null (SD-2828)',
module: 'ESNext',
moduleResolution: 'bundler',
skipLibCheck: true,
strict: true,
files: ['src/export-params-fields-highlight.ts'],
mustPass: true,
},
{
name: 'node16 / fieldsHighlightColor accepts null (SD-2828)',
module: 'Node16',
moduleResolution: 'node16',
skipLibCheck: true,
strict: true,
files: ['src/export-params-fields-highlight.ts'],
mustPass: true,
},
];

const tscPath = join(__dirname, 'node_modules', '.bin', 'tsc');
Expand Down
Loading