@@ -95,7 +95,7 @@ export type EditorToolbarSlots<
9595 </script >
9696
9797<script setup lang="ts" generic =" T extends ArrayOrNested <EditorToolbarItem >" >
98- import { computed , inject , shallowRef , watch } from ' vue'
98+ import { computed , inject , onBeforeUnmount , shallowRef , watch } from ' vue'
9999import type { ShallowRef } from ' vue'
100100import { Primitive , Separator , useForwardProps } from ' reka-ui'
101101import { defu } from ' defu'
@@ -391,6 +391,8 @@ function buildRenderGroups(): ToolbarRenderEntry[][] {
391391}
392392
393393const renderGroups = shallowRef <ToolbarRenderEntry [][]>(buildRenderGroups ())
394+ let pendingFrameId: number | null = null
395+ let pendingForceRefresh = false
394396
395397function refreshState(force = false ) {
396398 for (const group of renderGroups .value ) {
@@ -404,20 +406,55 @@ function refreshState(force = false) {
404406
405407 if (' items' in entry .item && entry .item .items ?.length ) {
406408 const previousDropdownState = entry .dropdownState .value
409+ const dropdownChanged = ! sameDropdownButtonProps (previousDropdownState , resolved .dropdown )
407410
408- // Always update dropdown items since child active/disabled states may change
409- entry .dropdownState .value = resolved .dropdown
410- entry .dropdownItems .value = resolved .dropdown ?.items || []
411+ if (force || stateChanged || dropdownChanged ) {
412+ entry .dropdownState .value = resolved .dropdown
413+ entry .dropdownItems .value = resolved .dropdown ?.items || []
414+ }
411415
412416 // Only rebuild button props when activeChild icon/label changes
413- if (stateChanged || force || ! sameDropdownButtonProps ( previousDropdownState , resolved . dropdown ) ) {
417+ if (stateChanged || force || dropdownChanged ) {
414418 entry .buttonProps .value = buildButtonProps (entry .item , resolved .dropdown )
415419 }
416420 }
417421 }
418422 }
419423}
420424
425+ function flushRefresh() {
426+ const force = pendingForceRefresh
427+ pendingForceRefresh = false
428+ pendingFrameId = null
429+ refreshState (force )
430+ }
431+
432+ function scheduleRefresh(force = false ) {
433+ pendingForceRefresh || = force
434+
435+ if (pendingFrameId !== null ) {
436+ return
437+ }
438+
439+ if (typeof requestAnimationFrame === ' function' ) {
440+ pendingFrameId = requestAnimationFrame (() => {
441+ flushRefresh ()
442+ })
443+ return
444+ }
445+
446+ flushRefresh ()
447+ }
448+
449+ function cancelScheduledRefresh() {
450+ if (pendingFrameId !== null && typeof cancelAnimationFrame === ' function' ) {
451+ cancelAnimationFrame (pendingFrameId )
452+ }
453+
454+ pendingFrameId = null
455+ pendingForceRefresh = false
456+ }
457+
421458watch (() => props .items , () => {
422459 renderGroups .value = buildRenderGroups ()
423460}, { deep: true })
@@ -427,27 +464,41 @@ watch(() => [props.color, props.variant, props.activeColor, props.activeVariant,
427464})
428465
429466watch (() => handlers .value , () => {
430- refreshState (true )
467+ scheduleRefresh (true )
431468}, { deep: true })
432469
433470watch (() => props .editor , (editor , _ , onCleanup ) => {
434- refreshState (true )
471+ scheduleRefresh (true )
435472
436473 if (typeof (editor as any )?.on !== ' function' || typeof (editor as any )?.off !== ' function' ) {
437474 return
438475 }
439476
477+ const onSelectionUpdate = () => {
478+ scheduleRefresh ()
479+ }
480+
440481 const onTransaction = () => {
441- refreshState ()
482+ scheduleRefresh ()
442483 }
443484
485+ editor .on (' selectionUpdate' , onSelectionUpdate )
486+ editor .on (' focus' , onSelectionUpdate )
487+ editor .on (' blur' , onSelectionUpdate )
444488 editor .on (' transaction' , onTransaction )
445489
446490 onCleanup (() => {
491+ editor .off (' selectionUpdate' , onSelectionUpdate )
492+ editor .off (' focus' , onSelectionUpdate )
493+ editor .off (' blur' , onSelectionUpdate )
447494 editor .off (' transaction' , onTransaction )
448495 })
449496}, { immediate: true })
450497
498+ onBeforeUnmount (() => {
499+ cancelScheduledRefresh ()
500+ })
501+
451502function getRenderEntry(key : string ) {
452503 return renderEntryMap .value .get (key )
453504}
0 commit comments