+
+
+
+
diff --git a/packages/super-editor/src/editors/v1/components/toolbar/defaultItems.js b/packages/super-editor/src/editors/v1/components/toolbar/defaultItems.js
index a16e4b00dc..f9c6206b8a 100644
--- a/packages/super-editor/src/editors/v1/components/toolbar/defaultItems.js
+++ b/packages/super-editor/src/editors/v1/components/toolbar/defaultItems.js
@@ -4,6 +4,7 @@ import { sanitizeNumber } from './helpers';
import { useToolbarItem } from './use-toolbar-item';
import AIWriter from './AIWriter.vue';
import AlignmentButtons from './AlignmentButtons.vue';
+import BulletStyleButtons from './BulletStyleButtons.vue';
import DocumentMode from './DocumentMode.vue';
import LinkedStyle from './LinkedStyle.vue';
import LinkInput from './LinkInput.vue';
@@ -630,16 +631,34 @@ export const makeDefaultItems = ({
// bullet list
const bulletedList = useToolbarItem({
- type: 'button',
+ type: 'dropdown',
name: 'list',
- command: 'toggleBulletList',
+ command: 'toggleBulletListStyle',
icon: toolbarIcons.bulletList,
- active: false,
+ hasCaret: true,
tooltip: toolbarTexts.bulletList,
restoreEditorFocus: true,
+ suppressActiveHighlight: true,
attributes: {
ariaLabel: 'Bullet list',
},
+ options: [
+ {
+ type: 'render',
+ key: 'bullet-style-buttons',
+ render: () => {
+ const handleSelect = (style) => {
+ closeDropdown(bulletedList);
+ const item = { ...bulletedList, command: 'toggleBulletListStyle' };
+ superToolbar.emitCommand({ item, argument: style });
+ };
+ return h(BulletStyleButtons, {
+ selectedStyle: bulletedList.selectedValue.value,
+ onSelect: handleSelect,
+ });
+ },
+ },
+ ],
});
// number list
diff --git a/packages/super-editor/src/editors/v1/components/toolbar/super-toolbar.js b/packages/super-editor/src/editors/v1/components/toolbar/super-toolbar.js
index c290c31ad7..28db7b3885 100644
--- a/packages/super-editor/src/editors/v1/components/toolbar/super-toolbar.js
+++ b/packages/super-editor/src/editors/v1/components/toolbar/super-toolbar.js
@@ -20,6 +20,7 @@ import { useToolbarItem } from '@components/toolbar/use-toolbar-item';
import { calculateResolvedParagraphProperties } from '@extensions/paragraph/resolvedPropertiesCache.js';
import { parseSizeUnit } from '@core/utilities';
import { findElementBySelector, getParagraphFontFamilyFromProperties } from './helpers/general.js';
+import { markerTextToBulletStyle } from '@helpers/list-numbering-helpers.js';
/**
* @typedef {function(CommandItem): void} CommandCallback
@@ -622,6 +623,15 @@ export class SuperToolbar extends EventEmitter {
if (commandState?.value != null) item.activate({ styleId: commandState.value });
else item.label.value = this.config.texts?.formatText || 'Format text';
},
+ list: () => {
+ if (commandState?.active) {
+ item.activate();
+ item.selectedValue.value = markerTextToBulletStyle(commandState.value);
+ } else {
+ item.deactivate();
+ item.selectedValue.value = null;
+ }
+ },
default: () => {
if (commandState?.active) item.activate();
else item.deactivate();
diff --git a/packages/super-editor/src/editors/v1/components/toolbar/toolbarIcons.js b/packages/super-editor/src/editors/v1/components/toolbar/toolbarIcons.js
index 70f62342f4..8b8caf0f08 100644
--- a/packages/super-editor/src/editors/v1/components/toolbar/toolbarIcons.js
+++ b/packages/super-editor/src/editors/v1/components/toolbar/toolbarIcons.js
@@ -2,6 +2,8 @@ import boldIconSvg from '@superdoc/common/icons/bold-solid.svg?raw';
import italicIconSvg from '@superdoc/common/icons/italic-solid.svg?raw';
import underlineIconSvg from '@superdoc/common/icons/underline-solid.svg?raw';
import listIconSvg from '@superdoc/common/icons/list-solid.svg?raw';
+import listCircleIconSvg from '@superdoc/common/icons/list-circle-solid.svg?raw';
+import listSquareIconSvg from '@superdoc/common/icons/list-square-solid.svg?raw';
import listOlIconSvg from '@superdoc/common/icons/list-ol-solid.svg?raw';
import imageIconSvg from '@superdoc/common/icons/image-solid.svg?raw';
import linkIconSvg from '@superdoc/common/icons/link-solid.svg?raw';
@@ -64,6 +66,9 @@ export const toolbarIcons = {
alignCenter: alignCenterIconSvg,
alignJustify: alignJustifyIconSvg,
bulletList: listIconSvg,
+ bulletListDisc: listIconSvg,
+ bulletListCircle: listCircleIconSvg,
+ bulletListSquare: listSquareIconSvg,
numberedList: listOlIconSvg,
indentLeft: outdentIconSvg,
indentRight: indentIconSvg,
diff --git a/packages/super-editor/src/editors/v1/core/commands/toggleList.js b/packages/super-editor/src/editors/v1/core/commands/toggleList.js
index 048bd03ec0..7aebd43a76 100644
--- a/packages/super-editor/src/editors/v1/core/commands/toggleList.js
+++ b/packages/super-editor/src/editors/v1/core/commands/toggleList.js
@@ -1,6 +1,6 @@
// @ts-check
import { updateNumberingProperties } from './changeListLevel.js';
-import { ListHelpers } from '@helpers/list-numbering-helpers.js';
+import { ListHelpers, markerTextToBulletStyle } from '@helpers/list-numbering-helpers.js';
import { getResolvedParagraphProperties } from '@extensions/paragraph/resolvedPropertiesCache.js';
import { isVisuallyEmptyParagraph } from './removeNumberingProperties.js';
import { Selection, TextSelection } from 'prosemirror-state';
@@ -26,10 +26,15 @@ function getParagraphListKind(node, editor) {
return numFmtIsBullet(fmt) ? 'bullet' : 'ordered';
}
-function paragraphMatchesToggleListType(node, editor, listType) {
+function paragraphMatchesToggleListType(node, editor, listType, bulletStyle) {
const kind = getParagraphListKind(node, editor);
if (!kind) return false;
- if (listType === 'bulletList') return kind === 'bullet';
+ if (listType === 'bulletList') {
+ if (kind !== 'bullet') return false;
+ if (!bulletStyle) return true;
+ const markerText = node.attrs.listRendering?.markerText;
+ return markerTextToBulletStyle(markerText) === bulletStyle;
+ }
if (listType === 'orderedList') return kind === 'ordered';
return false;
}
@@ -60,13 +65,13 @@ function getPrecedingParagraphForListReuse(doc, from, paragraphsInSelection) {
}
export const toggleList =
- (listType) =>
+ (listType, bulletStyle) =>
({ editor, state, tr, dispatch }) => {
if (listType !== 'orderedList' && listType !== 'bulletList') {
return false;
}
- const predicate = (n) => paragraphMatchesToggleListType(n, editor, listType);
+ const predicate = (n) => paragraphMatchesToggleListType(n, editor, listType, bulletStyle);
const { selection } = state;
const { from, to } = selection;
let firstListNode = null;
@@ -127,7 +132,7 @@ export const toggleList =
if (mode === 'create') {
const numId = ListHelpers.getNewListId(editor);
- ListHelpers.generateNewListDefinition({ numId: Number(numId), listType, editor });
+ ListHelpers.generateNewListDefinition({ numId: Number(numId), listType, editor, bulletStyle });
sharedNumberingProperties = {
numId: Number(numId),
ilvl: 0,
diff --git a/packages/super-editor/src/editors/v1/core/helpers/list-numbering-helpers.js b/packages/super-editor/src/editors/v1/core/helpers/list-numbering-helpers.js
index 016e536f55..3c584f2b70 100644
--- a/packages/super-editor/src/editors/v1/core/helpers/list-numbering-helpers.js
+++ b/packages/super-editor/src/editors/v1/core/helpers/list-numbering-helpers.js
@@ -29,6 +29,17 @@ import { mutateNumbering } from '@core/parts/adapters/numbering-mutation';
// Shims will be removed as callers migrate in Phases 1b–1d.
// ---------------------------------------------------------------------------
+/**
+ * Maps a bullet marker character (from `listRendering.markerText`) to its named bullet style.
+ * Returns null for unrecognized markers.
+ * @param {string|null|undefined} markerText
+ * @returns {'disc'|'circle'|'square'|null}
+ */
+export function markerTextToBulletStyle(markerText) {
+ const map = { '•': 'disc', '◦': 'circle', '▪': 'square' };
+ return map[markerText] ?? null;
+}
+
/**
* Generate a new list definition for the given list type.
* @param {Object} param0
@@ -39,10 +50,21 @@ import { mutateNumbering } from '@core/parts/adapters/numbering-mutation';
* @param {string} [param0.text]
* @param {string} [param0.fmt]
* @param {string} [param0.markerFontFamily]
+ * @param {'disc'|'circle'|'square'} [param0.bulletStyle]
* @param {import('../Editor').Editor} param0.editor
* @returns {Object} The new abstract and num definitions.
*/
-export const generateNewListDefinition = ({ numId, listType, level, start, text, fmt, editor, markerFontFamily }) => {
+export const generateNewListDefinition = ({
+ numId,
+ listType,
+ level,
+ start,
+ text,
+ fmt,
+ editor,
+ markerFontFamily,
+ bulletStyle,
+}) => {
/** @type {{ abstractDef: any, numDef: any }} */
let resultDefs;
@@ -55,6 +77,7 @@ export const generateNewListDefinition = ({ numId, listType, level, start, text,
text,
fmt,
markerFontFamily,
+ bulletStyle,
});
resultDefs = { abstractDef: result.abstractDef, numDef: result.numDef };
});
diff --git a/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-transforms.ts b/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-transforms.ts
index 84d16a318f..487a80a99e 100644
--- a/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-transforms.ts
+++ b/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-transforms.ts
@@ -30,8 +30,15 @@ interface GenerateOptions {
text?: string | null;
fmt?: string | null;
markerFontFamily?: string | null;
+ bulletStyle?: 'disc' | 'circle' | 'square' | null;
}
+const BULLET_STYLE_CHARS: Record