Skip to content

Commit 87694e2

Browse files
committed
fix: list style change not applying for nested children inline
1 parent 517a95f commit 87694e2

5 files changed

Lines changed: 88 additions & 12 deletions

File tree

packages/super-editor/src/editors/v1/core/commands/toggleList.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,21 +230,21 @@ export const toggleList =
230230
if (!dispatch) return true;
231231

232232
if (mode === 'create') {
233-
// If we're swapping the bullet style on an already-nested item, mint the
233+
// If we're swapping the bullet/ordered style on an already-nested item, mint the
234234
// new list with the override applied at that paragraph's existing level —
235235
// otherwise the override only lands on level 0 and the nested paragraph
236236
// ends up rendering whatever marker the base template assigned to its
237237
// level. We pick the level from the first list paragraph in the
238238
// selection so style swaps stay coherent with the existing nesting.
239-
let bulletStyleLevel = 0;
240-
if (bulletStyle) {
239+
let styleOverrideLevel = 0;
240+
if (bulletStyle || orderedStyle) {
241241
const firstExistingListPara = paragraphsInSelection.find(
242242
({ node }) => getResolvedParagraphProperties(node)?.numberingProperties?.ilvl != null,
243243
);
244244
const existingIlvl = firstExistingListPara
245245
? getResolvedParagraphProperties(firstExistingListPara.node)?.numberingProperties?.ilvl
246246
: null;
247-
if (existingIlvl != null) bulletStyleLevel = existingIlvl;
247+
if (existingIlvl != null) styleOverrideLevel = existingIlvl;
248248
}
249249

250250
const numId = ListHelpers.getNewListId(editor);
@@ -253,8 +253,9 @@ export const toggleList =
253253
listType,
254254
editor,
255255
bulletStyle,
256-
bulletStyleLevel,
256+
bulletStyleLevel: styleOverrideLevel,
257257
orderedStyle,
258+
orderedStyleLevel: styleOverrideLevel,
258259
});
259260
sharedNumberingProperties = {
260261
numId: Number(numId),

packages/super-editor/src/editors/v1/core/commands/toggleList.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ describe('toggleList', () => {
214214
editor,
215215
bulletStyle: undefined,
216216
bulletStyleLevel: 0,
217+
orderedStyleLevel: 0,
217218
});
218219
const expectedNumbering = { numId: 42, ilvl: 0 };
219220
for (const [index, { node, pos }] of paragraphs.entries()) {
@@ -247,6 +248,7 @@ describe('toggleList', () => {
247248
editor,
248249
bulletStyle: undefined,
249250
bulletStyleLevel: 0,
251+
orderedStyleLevel: 0,
250252
});
251253
expect(dispatch).toHaveBeenCalledWith(tr);
252254
});
@@ -404,6 +406,7 @@ describe('toggleList', () => {
404406
bulletStyle,
405407
bulletStyleLevel: 0,
406408
orderedStyle: undefined,
409+
orderedStyleLevel: 0,
407410
});
408411
},
409412
);
@@ -433,6 +436,7 @@ describe('toggleList', () => {
433436
bulletStyle: null,
434437
bulletStyleLevel: 0,
435438
orderedStyle,
439+
orderedStyleLevel: 0,
436440
});
437441
});
438442

packages/super-editor/src/editors/v1/core/helpers/list-numbering-helpers.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export function numberingInfoToOrderedStyle(numFmt, markerText) {
7575
* @param {'disc'|'circle'|'square'|null} [param0.bulletStyle]
7676
* @param {number} [param0.bulletStyleLevel]
7777
* @param {import('../../extensions/types/paragraph-commands.js').OrderedListStyle|null} [param0.orderedStyle]
78+
* @param {number} [param0.orderedStyleLevel]
7879
* @param {import('../Editor').Editor} param0.editor
7980
* @returns {Object} The new abstract and num definitions.
8081
*/
@@ -90,6 +91,7 @@ export const generateNewListDefinition = ({
9091
bulletStyle,
9192
bulletStyleLevel,
9293
orderedStyle,
94+
orderedStyleLevel,
9395
}) => {
9496
/** @type {{ abstractDef: any, numDef: any }} */
9597
let resultDefs;
@@ -106,6 +108,7 @@ export const generateNewListDefinition = ({
106108
bulletStyle,
107109
bulletStyleLevel,
108110
orderedStyle,
111+
orderedStyleLevel,
109112
});
110113
resultDefs = { abstractDef: result.abstractDef, numDef: result.numDef };
111114
});

packages/super-editor/src/editors/v1/core/parts/adapters/numbering-transforms.style-overrides.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,44 @@ describe('generateNewListDefinition - ordered style override', () => {
118118
expect(findChild(lvl0, 'w:lvlText').attributes['w:val']).toBe('•');
119119
});
120120

121+
it('applies orderedStyle to a sublevel (ilvl=1 → "%2.") when orderedStyleLevel is set', () => {
122+
// Regression: when the user picks a list style with a non-empty selection on a
123+
// nested item, `toggleList` falls through to mode='create' and mints a new abstract
124+
// via this path. The override needs to land on the paragraph's actual level so the
125+
// marker actually changes — without this the new abstract only mutates level 0 and
126+
// the nested item keeps rendering the template's default sublevel marker.
127+
const numbering = freshModel();
128+
const result = generateNewListDefinition(numbering, {
129+
numId: 1,
130+
listType: 'orderedList',
131+
orderedStyle: 'upper-roman',
132+
orderedStyleLevel: 1,
133+
});
134+
135+
const lvl0 = findLvl0(result.abstractDef);
136+
// Level 0 is left at the template's default ("decimal" / "%1.")
137+
expect(findChild(lvl0, 'w:numFmt').attributes['w:val']).toBe('decimal');
138+
expect(findChild(lvl0, 'w:lvlText').attributes['w:val']).toBe('%1.');
139+
140+
const lvl1 = result.abstractDef.elements.find((el: any) => el.name === 'w:lvl' && el.attributes['w:ilvl'] === '1');
141+
expect(findChild(lvl1, 'w:numFmt').attributes['w:val']).toBe('upperRoman');
142+
expect(findChild(lvl1, 'w:lvlText').attributes['w:val']).toBe('%2.');
143+
});
144+
145+
it('preserves the suffix character at sublevels (ilvl=2 → "%3)")', () => {
146+
const numbering = freshModel();
147+
const result = generateNewListDefinition(numbering, {
148+
numId: 1,
149+
listType: 'orderedList',
150+
orderedStyle: 'lower-alpha-paren',
151+
orderedStyleLevel: 2,
152+
});
153+
154+
const lvl2 = result.abstractDef.elements.find((el: any) => el.name === 'w:lvl' && el.attributes['w:ilvl'] === '2');
155+
expect(findChild(lvl2, 'w:numFmt').attributes['w:val']).toBe('lowerLetter');
156+
expect(findChild(lvl2, 'w:lvlText').attributes['w:val']).toBe('%3)');
157+
});
158+
121159
it('does NOT touch the abstract when orderedStyle is unknown', () => {
122160
const numbering = freshModel();
123161
const result = generateNewListDefinition(numbering, {

packages/super-editor/src/editors/v1/core/parts/adapters/numbering-transforms.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ interface GenerateOptions {
3939
*/
4040
bulletStyleLevel?: number | null;
4141
orderedStyle?: OrderedListStyle | null;
42+
/**
43+
* Level (`w:ilvl`) at which to apply `orderedStyle`. Defaults to 0 (top-level).
44+
* Used when the user changes the ordered style for a nested list item — the
45+
* override needs to land on the paragraph's actual level, and the lvlText
46+
* counter index (`%N`) needs to match that level.
47+
*/
48+
orderedStyleLevel?: number | null;
4249
}
4350

4451
const BULLET_STYLE_CHARS: Record<string, string> = {
@@ -129,7 +136,18 @@ function refreshAbstractIdentity(abstractDef: any): void {
129136
*/
130137
export function generateNewListDefinition(numbering: NumberingModel, options: GenerateOptions): GenerateResult {
131138
let { listType } = options;
132-
const { numId, level, start, text, fmt, markerFontFamily, bulletStyle, bulletStyleLevel, orderedStyle } = options;
139+
const {
140+
numId,
141+
level,
142+
start,
143+
text,
144+
fmt,
145+
markerFontFamily,
146+
bulletStyle,
147+
bulletStyleLevel,
148+
orderedStyle,
149+
orderedStyleLevel,
150+
} = options;
133151
if (typeof listType !== 'string') listType = (listType as any).name;
134152

135153
const definition = listType === 'orderedList' ? baseOrderedListDef : baseBulletList;
@@ -176,20 +194,32 @@ export function generateNewListDefinition(numbering: NumberingModel, options: Ge
176194
}
177195
}
178196

179-
// Override the ordered list style for the new list if an ordered style is provided
197+
// Override the ordered list style for the new list if an ordered style is provided.
198+
// The override lands at `orderedStyleLevel` (default level 0). Targeting a specific
199+
// level keeps nested-item style swaps coherent with the paragraph's existing nesting
200+
// depth — without this, applying e.g. upper-roman to a level-1 item would only modify
201+
// level 0 of the new abstract and the rendered marker would not change.
180202
const shouldOverrideOrderedStyle = orderedStyle && listType === 'orderedList';
181203
if (shouldOverrideOrderedStyle) {
182204
const styleConfig = ORDERED_LIST_STYLES[orderedStyle];
183205

184206
if (styleConfig) {
185-
const lvl0 = newAbstractDef.elements.find((el: any) => el.name === 'w:lvl' && el.attributes['w:ilvl'] === '0');
207+
const targetLevel = Math.max(0, Number.isFinite(orderedStyleLevel as number) ? (orderedStyleLevel as number) : 0);
208+
const targetLevelStr = String(targetLevel);
209+
const lvl = newAbstractDef.elements.find(
210+
(el: any) => el.name === 'w:lvl' && el.attributes['w:ilvl'] === targetLevelStr,
211+
);
186212

187-
if (lvl0) {
188-
const numFmt = lvl0.elements.find((el: any) => el.name === 'w:numFmt');
213+
if (lvl) {
214+
const numFmt = lvl.elements.find((el: any) => el.name === 'w:numFmt');
189215
if (numFmt) numFmt.attributes['w:val'] = styleConfig.fmt;
190216

191-
const lvlText = lvl0.elements.find((el: any) => el.name === 'w:lvlText');
192-
if (lvlText) lvlText.attributes['w:val'] = styleConfig.text;
217+
const lvlText = lvl.elements.find((el: any) => el.name === 'w:lvlText');
218+
if (lvlText) {
219+
// OOXML `%N` references counter level N-1, so at ilvl=N the lvlText needs `%(N+1)`.
220+
// Preserve the style's suffix (e.g. ".", ")") so paren styles stay paren.
221+
lvlText.attributes['w:val'] = `%${targetLevel + 1}${styleConfig.text.replace(/^%\d+/, '')}`;
222+
}
193223
}
194224
}
195225
}

0 commit comments

Comments
 (0)