@marimo-team/islands 0.24.1-dev115 → 0.24.1-dev116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-3vyrwCHu.js → ConnectedDataExplorerComponent-BabDOEkd.js} +10 -10
- package/dist/{ErrorBoundary-QF0xsxMw.js → ErrorBoundary-DXp6EXOC.js} +2 -2
- package/dist/{any-language-editor-B2e3EkTe.js → any-language-editor-CpTnK43-.js} +3 -3
- package/dist/{chat-ui-BgGHMO2G.js → chat-ui-B2KTPWqm.js} +15 -15
- package/dist/{check-C_-c2CXl.js → check-CHig47QV.js} +1 -1
- package/dist/{common-Cdx64lyR.js → common-mAFq987V.js} +7588 -7544
- package/dist/{copy-D4GHy7GA.js → copy-CD7S58CY.js} +1 -1
- package/dist/{dist-mymH-A_x.js → dist-3_o3T78I.js} +14 -14
- package/dist/{dropdown-menu-Div6gwlw.js → dropdown-menu-D1s8H8Ow.js} +123 -123
- package/dist/{error-banner-DmgOcFS3.js → error-banner-WDhUzJyg.js} +202 -202
- package/dist/{esm-CmFEQJjP.js → esm-CesmlDrs.js} +1 -1
- package/dist/{extends-CGTKDWHv.js → extends-BPB6_lTK.js} +3 -3
- package/dist/{formats-DuRRWVwb.js → formats-DrWM-ouO.js} +1 -1
- package/dist/{glide-data-editor-ekJ5GwaZ.js → glide-data-editor-B-3cUJQd.js} +7 -7
- package/dist/{glide-utils-Dwc8Eh8G.js → glide-utils-NShAPPtn.js} +2 -2
- package/dist/{html-to-image-u8Ik6EnK.js → html-to-image-DqqHcuV2.js} +3378 -3378
- package/dist/{label-DdGS_CZZ.js → label-lwzzBHUW.js} +1 -1
- package/dist/main.js +792 -792
- package/dist/{mermaid-Bt45Zzl4.js → mermaid-DWMOBSe8.js} +3 -3
- package/dist/{process-output-DQiqt-_U.js → process-output-D5dqSIGW.js} +3 -3
- package/dist/{reveal-component-tQ-rgxcQ.js → reveal-component-DU6Wrhzv.js} +8 -8
- package/dist/{schemas-BjeOpZLk.js → schemas-DzK6U94N.js} +1379 -1363
- package/dist/{select-DotAQ4jp.js → select-3qHpFKF9.js} +286 -286
- package/dist/{spec-BesaYkGC.js → spec-DzTo7SI8.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{toDate-CrAIOASL.js → toDate-Cq6SCTVJ.js} +2 -2
- package/dist/{tooltip-COXZxyl7.js → tooltip-Dt77sBeT.js} +87 -87
- package/dist/{useAsyncData-x0_tWDWI.js → useAsyncData-DqlulK9M.js} +1 -1
- package/dist/{useDateFormatter-B3V__WqX.js → useDateFormatter-Cp9Jdxls.js} +2 -2
- package/dist/{useDeepCompareMemoize-KaRHJEEU.js → useDeepCompareMemoize-Ceu-LMdC.js} +1 -1
- package/dist/{useLifecycle-x0OriHo-.js → useLifecycle-DmYfrWCI.js} +2 -2
- package/dist/{useTheme-Bm9_FyqO.js → useTheme-CWrCsZgw.js} +1 -1
- package/dist/{vega-component-nD2oP6WN.js → vega-component-Dzo_03Dh.js} +7 -7
- package/package.json +1 -1
- package/src/components/editor/Output.tsx +40 -0
- package/src/components/editor/__tests__/Output.test.tsx +85 -2
- package/src/components/ui/__tests__/fullscreen.test.tsx +88 -0
- package/src/components/ui/fullscreen.tsx +43 -1
- package/src/hooks/useEventListener.ts +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { act, render } from "@testing-library/react";
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { useIsFullScreen } from "../fullscreen";
|
|
6
|
+
|
|
7
|
+
const FullScreenFlag = ({ label }: { label: string }) => {
|
|
8
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
9
|
+
const isFullScreen = useIsFullScreen(ref);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div ref={ref} data-testid={label}>
|
|
13
|
+
{String(isFullScreen)}
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe("useIsFullScreen", () => {
|
|
19
|
+
let fullscreenElement: Element | null = null;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
fullscreenElement = null;
|
|
23
|
+
Object.defineProperty(document, "fullscreenElement", {
|
|
24
|
+
configurable: true,
|
|
25
|
+
get: () => fullscreenElement,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
Reflect.deleteProperty(document, "fullscreenElement");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const countFullScreenListeners = (calls: unknown[][]) =>
|
|
34
|
+
calls.filter((call) => call[0] === "fullscreenchange").length;
|
|
35
|
+
|
|
36
|
+
it("adds one document listener for many subscribers", () => {
|
|
37
|
+
const addEventListener = vi.spyOn(document, "addEventListener");
|
|
38
|
+
|
|
39
|
+
render(
|
|
40
|
+
<>
|
|
41
|
+
<FullScreenFlag label="first" />
|
|
42
|
+
<FullScreenFlag label="second" />
|
|
43
|
+
<FullScreenFlag label="third" />
|
|
44
|
+
</>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(countFullScreenListeners(addEventListener.mock.calls)).toBe(1);
|
|
48
|
+
addEventListener.mockRestore();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("removes the listener when the last subscriber unmounts", () => {
|
|
52
|
+
const removeEventListener = vi.spyOn(document, "removeEventListener");
|
|
53
|
+
|
|
54
|
+
const { unmount } = render(
|
|
55
|
+
<>
|
|
56
|
+
<FullScreenFlag label="first" />
|
|
57
|
+
<FullScreenFlag label="second" />
|
|
58
|
+
</>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(countFullScreenListeners(removeEventListener.mock.calls)).toBe(0);
|
|
62
|
+
|
|
63
|
+
unmount();
|
|
64
|
+
|
|
65
|
+
expect(countFullScreenListeners(removeEventListener.mock.calls)).toBe(1);
|
|
66
|
+
removeEventListener.mockRestore();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("reports true for the full screen element only", () => {
|
|
70
|
+
const { getByTestId } = render(
|
|
71
|
+
<>
|
|
72
|
+
<FullScreenFlag label="first" />
|
|
73
|
+
<FullScreenFlag label="second" />
|
|
74
|
+
</>,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(getByTestId("first")).toHaveTextContent("false");
|
|
78
|
+
expect(getByTestId("second")).toHaveTextContent("false");
|
|
79
|
+
|
|
80
|
+
fullscreenElement = getByTestId("second");
|
|
81
|
+
act(() => {
|
|
82
|
+
document.dispatchEvent(new Event("fullscreenchange"));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(getByTestId("first")).toHaveTextContent("false");
|
|
86
|
+
expect(getByTestId("second")).toHaveTextContent("true");
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -4,7 +4,7 @@ import type { Popover } from "radix-ui";
|
|
|
4
4
|
|
|
5
5
|
type PopoverContentProps = Popover.PopoverContentProps;
|
|
6
6
|
|
|
7
|
-
import React, { useState } from "react";
|
|
7
|
+
import React, { type RefObject, useState, useSyncExternalStore } from "react";
|
|
8
8
|
import { isInVscodeExtension } from "@/core/vscode/is-in-vscode";
|
|
9
9
|
import { useEventListener } from "@/hooks/useEventListener";
|
|
10
10
|
|
|
@@ -27,6 +27,48 @@ export function useFullScreenElement() {
|
|
|
27
27
|
return fullScreenElement;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
const fullScreenSubscribers = new Set<() => void>();
|
|
31
|
+
|
|
32
|
+
function notifyFullScreenSubscribers() {
|
|
33
|
+
for (const subscriber of fullScreenSubscribers) {
|
|
34
|
+
subscriber();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One document listener serves every subscriber, so the cost of a full screen
|
|
40
|
+
* transition does not grow with the number of mounted components.
|
|
41
|
+
*/
|
|
42
|
+
function subscribeToFullScreen(onStoreChange: () => void) {
|
|
43
|
+
if (fullScreenSubscribers.size === 0) {
|
|
44
|
+
document.addEventListener("fullscreenchange", notifyFullScreenSubscribers);
|
|
45
|
+
}
|
|
46
|
+
fullScreenSubscribers.add(onStoreChange);
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
fullScreenSubscribers.delete(onStoreChange);
|
|
50
|
+
if (fullScreenSubscribers.size === 0) {
|
|
51
|
+
document.removeEventListener(
|
|
52
|
+
"fullscreenchange",
|
|
53
|
+
notifyFullScreenSubscribers,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether the given element is the full screen element.
|
|
61
|
+
*
|
|
62
|
+
* The hook returns a boolean, so a component re-renders only when its own
|
|
63
|
+
* element enters or leaves full screen.
|
|
64
|
+
*/
|
|
65
|
+
export function useIsFullScreen(ref: RefObject<Element | null>): boolean {
|
|
66
|
+
return useSyncExternalStore(subscribeToFullScreen, () => {
|
|
67
|
+
const fullScreenElement = document.fullscreenElement;
|
|
68
|
+
return fullScreenElement !== null && fullScreenElement === ref.current;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
30
72
|
/**
|
|
31
73
|
* HOC wrapping a Portal component to use the
|
|
32
74
|
* full screen element as the container if we are in full screen mode
|
|
@@ -17,7 +17,7 @@ export function isRefObject<T>(target: unknown): target is RefObject<T | null> {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function useEventListener<K extends keyof DocumentEventMap>(
|
|
20
|
-
targetValue: Document,
|
|
20
|
+
targetValue: Document | null,
|
|
21
21
|
type: K,
|
|
22
22
|
listener: (ev: DocumentEventMap[K]) => unknown,
|
|
23
23
|
options?: boolean | AddEventListenerOptions,
|