@@ -46,20 +46,26 @@ type TableColumnActionItem = {
4646const { t } = useI18n ();
4747const editorRef = computed (() => props .editor );
4848const containerRef = computed (() => props .container );
49- const bubbleMenuAppendTarget = computed (() => containerRef .value ?? undefined );
5049const handleAnchorRef = useTemplateRef <HTMLElement >(" handleAnchor" );
5150const isMenuOpen = ref (false );
5251const isColorMenuOpen = ref (false );
5352const searchQuery = ref (" " );
5453const { activeCell, isColumnSelection } = useWritingStudioTableColumnMenuState (editorRef );
5554const colorPresets = useWritingStudioTableColumnColors ();
5655
56+ const TABLE_COLUMN_MENU_PLUGIN_KEY = " writing-studio-table-column-menu" ;
57+ const TABLE_COLUMN_HANDLE_WIDTH_REM = 2.5 ;
58+ const TABLE_COLUMN_HANDLE_HEIGHT_REM = 1.5 ;
59+ const TABLE_COLUMN_HANDLE_TOP_OFFSET_PX = 1 ;
60+
5761const openColorMenu = () => {
5862 isColorMenuOpen .value = true ;
63+ requestColumnMenuPositionUpdate ();
5964};
6065
6166const closeColorMenu = () => {
6267 isColorMenuOpen .value = false ;
68+ requestColumnMenuPositionUpdate ();
6369};
6470
6571const closeColumnMenu = () => {
@@ -68,6 +74,18 @@ const closeColumnMenu = () => {
6874 searchQuery .value = " " ;
6975};
7076
77+ const requestColumnMenuPositionUpdate = async () => {
78+ if (! props .editor || props .editor .isDestroyed ) {
79+ return ;
80+ }
81+
82+ await nextTick ();
83+
84+ props .editor .view .dispatch (
85+ props .editor .state .tr .setMeta (TABLE_COLUMN_MENU_PLUGIN_KEY , " updatePosition" ),
86+ );
87+ };
88+
7189const resolveFilteredColorEntries = (kind : WritingStudioTableColorKind ) => {
7290 const query = searchQuery .value .trim ().toLowerCase ();
7391 const entries = Object .entries (colorPresets [kind ]) as Array <[WritingStudioTableCellColorValue , (typeof colorPresets )[typeof kind ][WritingStudioTableCellColorValue ]]>;
@@ -152,18 +170,32 @@ const filteredTableActions = computed(() => {
152170 });
153171});
154172
155- watch (activeCell , (cell ) => {
156- if (! cell ) {
157- closeColumnMenu ();
173+ const canShowColumnMenu = computed (() => {
174+ const currentEditor = props .editor ;
175+
176+ if (! currentEditor ?.isEditable || ! isMenuOpen .value ) {
177+ return false ;
158178 }
159- });
160179
161- watch (isColumnSelection , (value ) => {
162- if (! value && isMenuOpen .value ) {
163- closeColumnMenu ();
180+ if (! activeCell .value || ! isColumnSelection .value ) {
181+ return false ;
164182 }
183+
184+ return Boolean (resolveWritingStudioActiveTableCell (currentEditor ));
165185});
166186
187+ watch (
188+ canShowColumnMenu ,
189+ (value ) => {
190+ if (! value && isMenuOpen .value ) {
191+ closeColumnMenu ();
192+ }
193+ },
194+ {
195+ flush: " post" ,
196+ },
197+ );
198+
167199watch (
168200 [editorRef , isMenuOpen ],
169201 ([editor , menuOpen ]) => {
@@ -238,24 +270,26 @@ const getColumnMenuVirtualElement = () => {
238270 return null ;
239271 }
240272
241- const handleAnchor = handleAnchorRef .value ;
273+ const currentCell = resolveWritingStudioActiveTableCell (props .editor );
274+ const rect = resolveWritingStudioTableColumnRect (props .editor , currentCell );
242275
243- if (handleAnchor ) {
244- return {
245- contextElement: handleAnchor ,
246- getBoundingClientRect : () => handleAnchor .getBoundingClientRect (),
247- };
248- }
249-
250- const rect = activeOutlineRect .value ;
251-
252- if (! rect || ! props .container || ! import .meta .client ) {
276+ if (! rect || ! import .meta .client ) {
253277 return null ;
254278 }
255279
280+ const rootFontSize = Number .parseFloat (window .getComputedStyle (document .documentElement ).fontSize || " 16" );
281+ const handleWidth = TABLE_COLUMN_HANDLE_WIDTH_REM * rootFontSize ;
282+ const handleHeight = TABLE_COLUMN_HANDLE_HEIGHT_REM * rootFontSize ;
283+ const domRect = new DOMRect (
284+ rect .left + rect .width / 2 - handleWidth / 2 ,
285+ rect .top + TABLE_COLUMN_HANDLE_TOP_OFFSET_PX - handleHeight / 2 ,
286+ handleWidth ,
287+ handleHeight ,
288+ );
289+
256290 return {
257- contextElement: props . container ,
258- getBoundingClientRect : () => new DOMRect ( rect . left + rect . width / 2 , rect . top , 0 , 0 ) ,
291+ getBoundingClientRect : () => domRect ,
292+ getClientRects : () => [ domRect ] ,
259293 };
260294};
261295
@@ -272,31 +306,19 @@ const openColumnMenu = () => {
272306
273307 isColorMenuOpen .value = false ;
274308 isMenuOpen .value = true ;
309+ requestColumnMenuPositionUpdate ();
275310};
276311
277312const shouldShowColumnMenu = (menuProps : any ) => {
278313 const currentEditor = menuProps ?.editor as Editor | undefined ;
279314
280- if (! currentEditor ?.isEditable ) {
281- return false ;
282- }
283-
284- if (! isMenuOpen .value ) {
285- return false ;
286- }
287-
288- if (! isWritingStudioColumnSelectionActive (currentEditor )) {
289- closeColumnMenu ();
290- return false ;
291- }
292-
293- const currentCell = resolveWritingStudioActiveTableCell (currentEditor );
294- if (! currentCell ) {
295- closeColumnMenu ();
296- return false ;
297- }
298-
299- return true ;
315+ return Boolean (
316+ currentEditor ?.isEditable
317+ && isMenuOpen .value
318+ && activeCell .value
319+ && isWritingStudioColumnSelectionActive (currentEditor )
320+ && resolveWritingStudioActiveTableCell (currentEditor ),
321+ );
300322};
301323
302324const runColumnAction = (actionId : TableColumnActionId ) => {
@@ -306,6 +328,7 @@ const runColumnAction = (actionId: TableColumnActionId) => {
306328
307329 if (actionId === " color" ) {
308330 openColorMenu ();
331+ requestColumnMenuPositionUpdate ();
309332 return ;
310333 }
311334
@@ -372,11 +395,10 @@ const applyColumnColor = (kind: WritingStudioTableColorKind, value: WritingStudi
372395 <div class =" ws-table-column-selection-outline" :class =" { 'ws-table-column-selection-outline--column': isMenuOpen || isColumnSelection }" />
373396 </div >
374397 <BubbleMenu
375- v-if =" editor && activeCell "
398+ v-if =" editor"
376399 plugin-key =" writing-studio-table-column-menu"
377400 class =" z-2"
378401 :editor =" editor"
379- :append-to =" bubbleMenuAppendTarget"
380402 :should-show =" shouldShowColumnMenu"
381403 :get-referenced-virtual-element =" getColumnMenuVirtualElement"
382404 :options =" { placement: 'bottom-start', strategy: 'fixed', offset: 14 }" >
@@ -409,36 +431,38 @@ const applyColumnColor = (kind: WritingStudioTableColorKind, value: WritingStudi
409431 </button >
410432 </HoverCardTrigger >
411433
412- <HoverCardContent side =" right" align =" start" :side-offset =" 12" class =" ws-table-column-color-menu" @pointerenter =" openColorMenu" >
413- <ScrollArea class =" ws-table-column-color-scroll" >
414- <div v-if =" filteredTextColorEntries.length > 0" class =" ws-table-column-color-section" >
415- <div class =" ws-table-column-color-title" >
416- {{ t("writingStudio.toolbar.table.columnMenu.colors.text.title") }}
434+ <HoverCardContent side =" right" align =" start" :side-offset =" 12" class =" ws-table-column-color-menu" >
435+ <div @pointerenter =" openColorMenu" >
436+ <ScrollArea class =" ws-table-column-color-scroll" >
437+ <div v-if =" filteredTextColorEntries.length > 0" class =" ws-table-column-color-section" >
438+ <div class =" ws-table-column-color-title" >
439+ {{ t("writingStudio.toolbar.table.columnMenu.colors.text.title") }}
440+ </div >
441+
442+ <button v-for =" [value, preset] in filteredTextColorEntries" :key =" `text-${value}`" type =" button" class =" ws-table-column-color-item" @mousedown.prevent @click =" applyColumnColor('text', value)" >
443+ <span class =" ws-table-column-color-swatch" :style =" { color: preset.textColor ?? 'oklch(var(--foreground))' }" >
444+ <Type class =" h-6 w-6" />
445+ </span >
446+ <span >{{ t(preset.labelKey) }}</span >
447+ </button >
417448 </div >
418449
419- <button v-for =" [value, preset] in filteredTextColorEntries" :key =" `text-${value}`" type =" button" class =" ws-table-column-color-item" @mousedown.prevent @click =" applyColumnColor('text', value)" >
420- <span class =" ws-table-column-color-swatch" :style =" { color: preset.textColor ?? 'oklch(var(--foreground))' }" >
421- <Type class =" h-6 w-6" />
422- </span >
423- <span >{{ t(preset.labelKey) }}</span >
424- </button >
425- </div >
450+ <Separator v-if =" filteredTextColorEntries.length > 0 && filteredBackgroundColorEntries.length > 0" class =" my-3" />
426451
427- <Separator v-if =" filteredTextColorEntries.length > 0 && filteredBackgroundColorEntries.length > 0" class =" my-3" />
452+ <div v-if =" filteredBackgroundColorEntries.length > 0" class =" ws-table-column-color-section" >
453+ <div class =" ws-table-column-color-title" >
454+ {{ t("writingStudio.toolbar.table.columnMenu.colors.background.title") }}
455+ </div >
428456
429- <div v-if =" filteredBackgroundColorEntries.length > 0" class =" ws-table-column-color-section" >
430- <div class =" ws-table-column-color-title" >
431- {{ t("writingStudio.toolbar.table.columnMenu.colors.background.title") }}
457+ <button v-for =" [value, preset] in filteredBackgroundColorEntries" :key =" `background-${value}`" type =" button" class =" ws-table-column-color-item" @mousedown.prevent @click =" applyColumnColor('background', value)" >
458+ <span class =" ws-table-column-color-swatch" :style =" { color: preset.backgroundColor ?? 'oklch(var(--muted-foreground))' }" >
459+ <Square class =" h-6 w-6 fill-current" />
460+ </span >
461+ <span >{{ t(preset.labelKey) }}</span >
462+ </button >
432463 </div >
433-
434- <button v-for =" [value, preset] in filteredBackgroundColorEntries" :key =" `background-${value}`" type =" button" class =" ws-table-column-color-item" @mousedown.prevent @click =" applyColumnColor('background', value)" >
435- <span class =" ws-table-column-color-swatch" :style =" { color: preset.backgroundColor ?? 'oklch(var(--muted-foreground))' }" >
436- <Square class =" h-6 w-6 fill-current" />
437- </span >
438- <span >{{ t(preset.labelKey) }}</span >
439- </button >
440- </div >
441- </ScrollArea >
464+ </ScrollArea >
465+ </div >
442466 </HoverCardContent >
443467 </HoverCard >
444468
0 commit comments