Skip to content

Commit f56b509

Browse files
committed
fix(ui): preserve embed dropdown ownership
1 parent a7ab8a1 commit f56b509

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

packages/ui/src/views/components/ribbon/TooltipButtonWrapper.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@ import {
3939
import { combineLatest, map, merge, of, scan, startWith } from 'rxjs';
4040
import { IMenuManagerService } from '../../../services/menu/menu-manager.service';
4141
import { useDependency, useObservable } from '../../../utils/di';
42-
import { keepInteractionInsideSameEmbedBoundary } from '../../../utils/embed-boundary';
42+
import {
43+
EMBED_CHILD_UNIT_ID_ATTRIBUTE,
44+
EMBED_INTERACTION_BOUNDARY_OWNER_ATTRIBUTE,
45+
getEmbedBoundaryOwner,
46+
getEmbedChildUnitId,
47+
keepInteractionInsideSameEmbedBoundary,
48+
} from '../../../utils/embed-boundary';
4349
import { CustomLabel } from '../../custom-label/CustomLabel';
4450

4551
const TooltipWrapperContext = createContext({
@@ -155,6 +161,10 @@ export function DropdownWrapper(props: Omit<Partial<IDropdownProps>, 'overlay'>
155161
const { dropdownVisible, setDropdownVisible } = useContext(TooltipWrapperContext);
156162
const triggerRef = useRef<HTMLDivElement>(null);
157163
const overlayRef = useRef<HTMLDivElement>(null);
164+
const [embedBoundary, setEmbedBoundary] = useState<{
165+
childUnitId?: string;
166+
owner?: string;
167+
}>({});
158168

159169
useEffect(() => {
160170
if (disabled) {
@@ -178,9 +188,19 @@ export function DropdownWrapper(props: Omit<Partial<IDropdownProps>, 'overlay'>
178188
}, [dropdownVisible, setDropdownVisible]);
179189

180190
function handleVisibleChange(visible: boolean) {
191+
if (visible) {
192+
setEmbedBoundary({
193+
childUnitId: getEmbedChildUnitId(triggerRef.current),
194+
owner: getEmbedBoundaryOwner(triggerRef.current),
195+
});
196+
}
181197
setDropdownVisible(visible);
182198
}
183199

200+
function handleEmbedBoundaryFocusOutside(event: { currentTarget: EventTarget | null; target: EventTarget | null; preventDefault: () => void }) {
201+
keepInteractionInsideSameEmbedBoundary(event);
202+
}
203+
184204
return (
185205
<Dropdown
186206
align={align ?? (direction === 'rtl' ? 'end' : 'start')}
@@ -192,6 +212,12 @@ export function DropdownWrapper(props: Omit<Partial<IDropdownProps>, 'overlay'>
192212
disabled={disabled}
193213
open={dropdownVisible}
194214
onOpenChange={handleVisibleChange}
215+
onFocusOutside={handleEmbedBoundaryFocusOutside}
216+
onInteractOutside={handleEmbedBoundaryFocusOutside}
217+
{...{
218+
[EMBED_CHILD_UNIT_ID_ATTRIBUTE]: embedBoundary.childUnitId,
219+
[EMBED_INTERACTION_BOUNDARY_OWNER_ATTRIBUTE]: embedBoundary.owner,
220+
}}
195221
>
196222
<div ref={triggerRef} className="univer-h-full" onClick={(e) => e.stopPropagation()}>
197223
{children}

packages/ui/src/views/components/ribbon/__tests__/TooltipButtonWrapper.spec.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { IconManager } from '../../../../common/icon-manager';
3030
import { MenuItemType } from '../../../../services/menu/menu';
3131
import { IMenuManagerService } from '../../../../services/menu/menu-manager.service';
3232
import { connectInjector } from '../../../../utils/di';
33+
import { EMBED_CHILD_UNIT_ID_ATTRIBUTE, EMBED_INTERACTION_BOUNDARY_OWNER_ATTRIBUTE } from '../../../../utils/embed-boundary';
3334
import {
3435
DropdownMenuLabel,
3536
DropdownMenuWrapper,
@@ -171,6 +172,31 @@ describe('DropdownWrapper', () => {
171172

172173
expect(queryByText('Dropdown content')).toBeNull();
173174
});
175+
176+
it('copies the embed boundary to the portal before rendering its content', async () => {
177+
const { findByText, getByRole } = render(
178+
<div
179+
{...{
180+
[EMBED_CHILD_UNIT_ID_ATTRIBUTE]: 'child-doc',
181+
[EMBED_INTERACTION_BOUNDARY_OWNER_ATTRIBUTE]: 'embed-doc',
182+
}}
183+
>
184+
<ToolbarDropdownProvider>
185+
<TooltipWrapper dropdownKey="test-embed-dropdown">
186+
<DropdownWrapper overlay={<div>Embed dropdown content</div>}>
187+
<button type="button">Open embed dropdown</button>
188+
</DropdownWrapper>
189+
</TooltipWrapper>
190+
</ToolbarDropdownProvider>
191+
</div>
192+
);
193+
194+
fireEvent.click(getByRole('button', { name: 'Open embed dropdown' }));
195+
const content = (await findByText('Embed dropdown content')).closest('[data-slot="popover-content"]');
196+
197+
expect(content?.getAttribute(EMBED_INTERACTION_BOUNDARY_OWNER_ATTRIBUTE)).toBe('embed-doc');
198+
expect(content?.getAttribute(EMBED_CHILD_UNIT_ID_ATTRIBUTE)).toBe('child-doc');
199+
});
174200
});
175201

176202
describe('DropdownMenuWrapper', () => {

0 commit comments

Comments
 (0)