Skip to content

Commit 1c72e1b

Browse files
chittolinagchittolinacaio-pizzolluccas-harbourharbournick
authored
SD-2526 - feat: implement 2 extra types of bullet lists (square, circle) (#2857)
* feat: implement 2 extra types of bullet lists (square, circle) * refactor: code reuse * refactor: simplified code removing font override * chore: small code tweaks * test: add coverage for bullet style picker (SD-2526) unit: - numbering-transforms.test.ts (new): generateNewListDefinition with bulletStyle, lvlText override, rFonts strip, ordered-list ignore - list-numbering-helpers.test.js: markerTextToBulletStyle truth table + null cases - toggleList.test.js: style-aware predicate, swap-mints-numId path, no-arg fallback - create-headless-toolbar.test.ts: bullet-list direct command forwards style argument - toolbar-registry.test.ts: bullet-list state exposes raw markerText for each style behavior (playwright): - bullet-style-picker.spec.ts: AC1, AC2, AC3, AC5 picker UX + active-style reflection - bullet-style-undo-redo.spec.ts: AC9 undo of initial create - bullet-style-word-fixtures.spec.ts: round-trip from real Word disc/circle/square docs (fixtures generated via Word COM) * test(lists): verify bullet style export round-trips to DOCX Adds a behavior test that picks bullet styles via the toolbar dropdown, then exports and inspects numbering.xml to confirm the per-list w:lvlText markers match the chosen disc/square styles. * fix: footer tcs in replacement generating one per character (#2965) * fix: footer tcs in replacement generating one per character * feat: added split button for button styles * fix: generate new nsid when list style is changed * fix: changing list style changes current identation * fix: list item de-indentation * fix: lockfile * chore: removed console log --------- Co-authored-by: Gabriel Chittolina <gabrielchittolina1@gmail.com> Co-authored-by: Caio Pizzol <caio@superdoc.dev> Co-authored-by: Luccas Correa <luccas@harbourshare.com> Co-authored-by: Nick Bernal <117235294+harbournick@users.noreply.github.com>
1 parent ccd7bb9 commit 1c72e1b

30 files changed

Lines changed: 1234 additions & 67 deletions
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<script setup>
2+
import { onMounted, ref } from 'vue';
3+
import { useHighContrastMode } from '../../composables/use-high-contrast-mode';
4+
import { toolbarIcons } from './toolbarIcons.js';
5+
6+
const { isHighContrastMode } = useHighContrastMode();
7+
const emit = defineEmits(['select']);
8+
9+
const props = defineProps({
10+
selectedStyle: {
11+
type: String,
12+
default: null,
13+
},
14+
});
15+
16+
const buttonRefs = ref([]);
17+
const bulletButtons = [
18+
{ key: 'disc', icon: toolbarIcons.bulletListDisc, ariaLabel: 'Opaque circle' },
19+
{ key: 'circle', icon: toolbarIcons.bulletListCircle, ariaLabel: 'Outline circle' },
20+
{ key: 'square', icon: toolbarIcons.bulletListSquare, ariaLabel: 'Opaque square' },
21+
];
22+
23+
const select = (key) => {
24+
emit('select', key);
25+
};
26+
27+
const moveToNextButton = (index) => {
28+
if (index === buttonRefs.value.length - 1) return;
29+
const next = buttonRefs.value[index + 1];
30+
if (next) {
31+
next.setAttribute('tabindex', '0');
32+
next.focus();
33+
}
34+
};
35+
36+
const moveToPreviousButton = (index) => {
37+
if (index === 0) return;
38+
const prev = buttonRefs.value[index - 1];
39+
if (prev) {
40+
prev.setAttribute('tabindex', '0');
41+
prev.focus();
42+
}
43+
};
44+
45+
const handleKeyDown = (e, index) => {
46+
switch (e.key) {
47+
case 'ArrowLeft':
48+
moveToPreviousButton(index);
49+
break;
50+
case 'ArrowRight':
51+
moveToNextButton(index);
52+
break;
53+
case 'Enter':
54+
select(bulletButtons[index].key);
55+
break;
56+
default:
57+
break;
58+
}
59+
};
60+
61+
onMounted(() => {
62+
const first = buttonRefs.value[0];
63+
if (first) {
64+
first.setAttribute('tabindex', '0');
65+
first.focus();
66+
}
67+
});
68+
</script>
69+
70+
<template>
71+
<div class="bullet-style-buttons" :class="{ 'high-contrast': isHighContrastMode }">
72+
<div
73+
v-for="(button, index) in bulletButtons"
74+
:key="button.key"
75+
class="button-icon"
76+
:class="{ selected: props.selectedStyle === button.key }"
77+
@click="select(button.key)"
78+
v-html="button.icon"
79+
role="menuitem"
80+
:aria-label="button.ariaLabel"
81+
ref="buttonRefs"
82+
@keydown.prevent="(event) => handleKeyDown(event, index)"
83+
></div>
84+
</div>
85+
</template>
86+
87+
<style scoped>
88+
.bullet-style-buttons {
89+
display: flex;
90+
justify-content: space-between;
91+
width: 100%;
92+
padding: 8px;
93+
box-sizing: border-box;
94+
95+
.button-icon {
96+
cursor: pointer;
97+
padding: 5px;
98+
font-size: var(--sd-ui-font-size-600, 16px);
99+
color: var(--sd-ui-dropdown-text, #47484a);
100+
width: 25px;
101+
height: 25px;
102+
border-radius: var(--sd-ui-dropdown-option-radius, 3px);
103+
display: flex;
104+
justify-content: center;
105+
align-items: center;
106+
box-sizing: border-box;
107+
108+
&:hover {
109+
background-color: var(--sd-ui-dropdown-hover-bg, #d8dee5);
110+
color: var(--sd-ui-dropdown-hover-text, #47484a);
111+
}
112+
113+
:deep(svg) {
114+
width: 100%;
115+
height: 100%;
116+
display: block;
117+
fill: currentColor;
118+
}
119+
120+
&.selected {
121+
background-color: var(--sd-ui-dropdown-active-bg, #d8dee5);
122+
color: var(--sd-ui-dropdown-selected-text, #47484a);
123+
}
124+
}
125+
126+
&.high-contrast {
127+
.button-icon {
128+
&:hover,
129+
&.selected {
130+
background-color: #000;
131+
color: #fff;
132+
}
133+
}
134+
}
135+
}
136+
</style>

packages/super-editor/src/editors/v1/components/toolbar/ButtonGroup.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,21 @@ const handleToolbarButtonTextSubmit = (item, argument) => {
123123
emit('command', { item, argument });
124124
};
125125
126+
const handleSplitButtonMainClick = (item) => {
127+
if (item.disabled.value) return;
128+
129+
closeDropdowns();
130+
131+
const splitCommand = item.splitButtonCommand;
132+
const dropdownCommand = item.command;
133+
const targetCommand = splitCommand || dropdownCommand;
134+
if (!targetCommand) return;
135+
136+
const commandItem = { ...item, command: targetCommand };
137+
emit('item-clicked');
138+
emit('command', { item: commandItem, argument: null });
139+
};
140+
126141
const closeDropdowns = () => {
127142
const toolbarItems = proxy?.$toolbar?.toolbarItems || [];
128143
const overflowItems = proxy?.$toolbar?.overflowItems || [];
@@ -359,6 +374,7 @@ onBeforeUnmount(() => {
359374
:toolbar-item="item"
360375
:disabled="item.disabled.value"
361376
@textSubmit="handleToolbarButtonTextSubmit(item, $event)"
377+
@mainClick="handleSplitButtonMainClick(item)"
362378
/>
363379
</template>
364380
<div>

0 commit comments

Comments
 (0)