Skip to content

Commit a9ab6f1

Browse files
authored
fix(content-controls): default newly-created controls to richText, not unknown (SD-3139) (#3281)
Follow-up to SD-3131 (#3275). After that PR, imported typeless w:sdtPr correctly resolves to controlType: 'richText' per ECMA-376 §17.5.2.26, but newly created controls via create.contentControl (no controlType) and contentControls.wrap still defaulted to 'unknown'. Customers filtering contentControls.list() by type saw different results before vs after save/reopen — the same typeless OOXML round-tripped from 'unknown' to 'richText'. - createWrapper: collapse three `?? 'unknown'` into a single `?? 'richText'` local. - wrapWrapper: explicitly seed controlType, type, and a default sdtPr with <w:richText/> on the wrapper attrs (previously controlType was unset entirely, leaving resolveControlType to fall back to 'unknown'). Newly created controls now emit explicit <w:richText/> in sdtPr for unambiguous engine state and exported markup. Imported typeless Word-authored SDTs preserve their original raw sdtPr (import path unchanged). Per the SD-3131 design, 'unknown' keeps its meaning of "unsupported or unrecognized type" — it's no longer the default for new controls. Tests: 3 new (default-create → richText, explicit-richText-create seeds <w:richText/>, wrap → richText + seeds <w:richText/>). 42 wrappers + 1189 conformance + 1398 document-api pass.
1 parent c68d5d4 commit a9ab6f1

2 files changed

Lines changed: 63 additions & 4 deletions

File tree

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,52 @@ describe('create.contentControl default sdtPr seeding', () => {
10951095
expect(dateEl?.elements?.some((el) => el.name === 'w:storeMappedDataAs')).toBe(true);
10961096
expect(dateEl?.elements?.some((el) => el.name === 'w:calendar')).toBe(true);
10971097
});
1098+
1099+
it('defaults newly-created controls without controlType to richText', () => {
1100+
const editor = makeSdtEditor();
1101+
const adapter = createContentControlsAdapter(editor);
1102+
1103+
const result = adapter.create({ kind: 'inline' }, { changeMode: 'direct' });
1104+
expect(result.success).toBe(true);
1105+
1106+
const insertInline = editor.commands!.insertStructuredContentInline as ReturnType<typeof vi.fn>;
1107+
const attrs = insertInline.mock.calls[0][0].attrs as Record<string, unknown>;
1108+
expect(attrs.controlType).toBe('richText');
1109+
expect(attrs.type).toBe('richText');
1110+
const sdtPr = attrs.sdtPr as { elements?: Array<{ name: string }> };
1111+
expect(sdtPr.elements?.some((el) => el.name === 'w:richText')).toBe(true);
1112+
});
1113+
1114+
it('seeds explicit richText controls with w:richText in sdtPr', () => {
1115+
const editor = makeSdtEditor();
1116+
const adapter = createContentControlsAdapter(editor);
1117+
1118+
const result = adapter.create({ kind: 'inline', controlType: 'richText' }, { changeMode: 'direct' });
1119+
expect(result.success).toBe(true);
1120+
1121+
const insertInline = editor.commands!.insertStructuredContentInline as ReturnType<typeof vi.fn>;
1122+
const attrs = insertInline.mock.calls[0][0].attrs as Record<string, unknown>;
1123+
expect(attrs.controlType).toBe('richText');
1124+
const sdtPr = attrs.sdtPr as { elements?: Array<{ name: string }> };
1125+
expect(sdtPr.elements?.some((el) => el.name === 'w:richText')).toBe(true);
1126+
});
1127+
});
1128+
1129+
describe('contentControls.wrap default classification', () => {
1130+
it('sets wrapper controlType to richText and seeds w:richText in sdtPr', () => {
1131+
const editor = makeSdtEditor();
1132+
const adapter = createContentControlsAdapter(editor);
1133+
1134+
adapter.wrap({ target: SDT_TARGET, kind: 'block' }, { changeMode: 'direct' });
1135+
1136+
const createFn = editor.schema.nodes.structuredContentBlock.create as ReturnType<typeof vi.fn>;
1137+
expect(createFn).toHaveBeenCalledTimes(1);
1138+
const [attrs] = createFn.mock.calls[0];
1139+
expect(attrs.controlType).toBe('richText');
1140+
expect(attrs.type).toBe('richText');
1141+
const sdtPr = attrs.sdtPr as { elements?: Array<{ name: string }> };
1142+
expect(sdtPr.elements?.some((el) => el.name === 'w:richText')).toBe(true);
1143+
});
10981144
});
10991145

11001146
describe('contentControls.setType default sdtPr seeding', () => {

packages/super-editor/src/editors/v1/document-api-adapters/plan-engine/content-controls-wrappers.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,18 @@ function wrapWrapper(
493493
const nodeType = editor.schema.nodes[nodeTypeName];
494494
if (!nodeType) return false;
495495

496+
// ECMA-376 §17.5.2.26: typeless sdtPr resolves to richText. Default here so
497+
// the wrapper classifies the same in-session and after reimport.
496498
const wrapperNode = nodeType.create(
497-
{ id, tag: input.tag, alias: input.alias, lockMode: input.lockMode ?? 'unlocked' },
499+
{
500+
id,
501+
tag: input.tag,
502+
alias: input.alias,
503+
lockMode: input.lockMode ?? 'unlocked',
504+
controlType: 'richText',
505+
type: 'richText',
506+
sdtPr: buildDefaultSdtPr('richText'),
507+
},
498508
resolved.node,
499509
);
500510
const { tr } = editor.state;
@@ -1809,15 +1819,18 @@ function createWrapper(
18091819
}
18101820

18111821
return executeSdtMutation(editor, target, options, () => {
1822+
// ECMA-376 §17.5.2.26: typeless sdtPr resolves to richText. 'unknown' is for
1823+
// unsupported/unrecognized type children, not a default for new controls.
1824+
const controlType = input.controlType ?? 'richText';
18121825
const attrs: Record<string, unknown> = {
18131826
id,
18141827
tag: input.tag,
18151828
alias: input.alias,
18161829
lockMode: input.lockMode ?? 'unlocked',
1817-
controlType: input.controlType ?? 'unknown',
1818-
type: input.controlType ?? 'unknown',
1830+
controlType,
1831+
type: controlType,
18191832
};
1820-
const defaultSdtPr = buildDefaultSdtPr(input.controlType ?? 'unknown');
1833+
const defaultSdtPr = buildDefaultSdtPr(controlType);
18211834
const isDateCreate = input.controlType === 'date' && input.content == null;
18221835
const dateDefaults = isDateCreate ? buildDateControlDefaults() : null;
18231836
const sdtPrWithDateDefaults = dateDefaults ? applyDateDefaultsToSdtPr(defaultSdtPr, dateDefaults) : defaultSdtPr;

0 commit comments

Comments
 (0)