@deck.gl-community/widgets 9.2.5 → 9.3.0-beta.1
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/README.md +11 -1
- package/dist/graph-widgets/_deprecate/long-press-button.d.ts.map +1 -0
- package/dist/graph-widgets/_deprecate/long-press-button.js.map +1 -0
- package/dist/graph-widgets/_deprecate/view-control-widget.d.ts.map +1 -0
- package/dist/graph-widgets/_deprecate/view-control-widget.js.map +1 -0
- package/dist/{widgets → graph-widgets}/long-press-button.d.ts +1 -0
- package/dist/graph-widgets/long-press-button.d.ts.map +1 -0
- package/dist/{widgets → graph-widgets}/long-press-button.js +1 -0
- package/dist/graph-widgets/long-press-button.js.map +1 -0
- package/dist/graph-widgets/long-press-controller.d.ts.map +1 -0
- package/dist/graph-widgets/long-press-controller.js.map +1 -0
- package/dist/{widgets → graph-widgets}/pan-widget.d.ts +3 -2
- package/dist/graph-widgets/pan-widget.d.ts.map +1 -0
- package/dist/{widgets → graph-widgets}/pan-widget.js +19 -15
- package/dist/graph-widgets/pan-widget.js.map +1 -0
- package/dist/{widgets → graph-widgets}/zoom-range-widget.d.ts +3 -3
- package/dist/graph-widgets/zoom-range-widget.d.ts.map +1 -0
- package/dist/{widgets → graph-widgets}/zoom-range-widget.js +24 -23
- package/dist/graph-widgets/zoom-range-widget.js.map +1 -0
- package/dist/html-overlay-widgets/html-cluster-widget.d.ts.map +1 -0
- package/dist/html-overlay-widgets/html-cluster-widget.js.map +1 -0
- package/dist/{widgets → html-overlay-widgets}/html-overlay-item.d.ts +1 -0
- package/dist/html-overlay-widgets/html-overlay-item.d.ts.map +1 -0
- package/dist/html-overlay-widgets/html-overlay-item.js.map +1 -0
- package/dist/{widgets → html-overlay-widgets}/html-overlay-widget.d.ts +8 -2
- package/dist/html-overlay-widgets/html-overlay-widget.d.ts.map +1 -0
- package/dist/{widgets → html-overlay-widgets}/html-overlay-widget.js +21 -7
- package/dist/html-overlay-widgets/html-overlay-widget.js.map +1 -0
- package/dist/{widgets → html-overlay-widgets}/html-tooltip-widget.d.ts +1 -0
- package/dist/html-overlay-widgets/html-tooltip-widget.d.ts.map +1 -0
- package/dist/html-overlay-widgets/html-tooltip-widget.js.map +1 -0
- package/dist/index.cjs +5119 -87
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +33 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -6
- package/dist/index.js.map +1 -1
- package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.d.ts +18 -0
- package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.d.ts.map +1 -0
- package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.js +47 -0
- package/dist/keyboard-shortcuts/keyboard-shortcuts-manager.js.map +1 -0
- package/dist/keyboard-shortcuts/keyboard-shortcuts.d.ts +22 -0
- package/dist/keyboard-shortcuts/keyboard-shortcuts.d.ts.map +1 -0
- package/dist/keyboard-shortcuts/keyboard-shortcuts.js +83 -0
- package/dist/keyboard-shortcuts/keyboard-shortcuts.js.map +1 -0
- package/dist/lib/settings/settings.d.ts +17 -0
- package/dist/lib/settings/settings.d.ts.map +1 -0
- package/dist/lib/settings/settings.js +140 -0
- package/dist/lib/settings/settings.js.map +1 -0
- package/dist/ready-to-upstream-widgets/reset-view-widget.d.ts +21 -0
- package/dist/ready-to-upstream-widgets/reset-view-widget.d.ts.map +1 -0
- package/dist/ready-to-upstream-widgets/reset-view-widget.js +29 -0
- package/dist/ready-to-upstream-widgets/reset-view-widget.js.map +1 -0
- package/dist/widget-components/icon-button.d.ts +12 -0
- package/dist/widget-components/icon-button.d.ts.map +1 -0
- package/dist/widget-components/icon-button.js +12 -0
- package/dist/widget-components/icon-button.js.map +1 -0
- package/dist/widget-components/select-widget-component.d.ts +15 -0
- package/dist/widget-components/select-widget-component.d.ts.map +1 -0
- package/dist/widget-components/select-widget-component.js +229 -0
- package/dist/widget-components/select-widget-component.js.map +1 -0
- package/dist/widget-panels/box-widget.d.ts +43 -0
- package/dist/widget-panels/box-widget.d.ts.map +1 -0
- package/dist/widget-panels/box-widget.js +191 -0
- package/dist/widget-panels/box-widget.js.map +1 -0
- package/dist/widget-panels/box-widget.test.d.ts +2 -0
- package/dist/widget-panels/box-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/box-widget.test.js +41 -0
- package/dist/widget-panels/box-widget.test.js.map +1 -0
- package/dist/widget-panels/full-screen-panel-widget.d.ts +33 -0
- package/dist/widget-panels/full-screen-panel-widget.d.ts.map +1 -0
- package/dist/widget-panels/full-screen-panel-widget.js +153 -0
- package/dist/widget-panels/full-screen-panel-widget.js.map +1 -0
- package/dist/widget-panels/full-screen-panel-widget.test.d.ts +2 -0
- package/dist/widget-panels/full-screen-panel-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/full-screen-panel-widget.test.js +40 -0
- package/dist/widget-panels/full-screen-panel-widget.test.js.map +1 -0
- package/dist/widget-panels/heap-memory-widget.d.ts +26 -0
- package/dist/widget-panels/heap-memory-widget.d.ts.map +1 -0
- package/dist/widget-panels/heap-memory-widget.js +156 -0
- package/dist/widget-panels/heap-memory-widget.js.map +1 -0
- package/dist/widget-panels/keyboard-shortcuts-widget.d.ts +46 -0
- package/dist/widget-panels/keyboard-shortcuts-widget.d.ts.map +1 -0
- package/dist/widget-panels/keyboard-shortcuts-widget.js +301 -0
- package/dist/widget-panels/keyboard-shortcuts-widget.js.map +1 -0
- package/dist/widget-panels/modal-widget.d.ts +62 -0
- package/dist/widget-panels/modal-widget.d.ts.map +1 -0
- package/dist/widget-panels/modal-widget.js +309 -0
- package/dist/widget-panels/modal-widget.js.map +1 -0
- package/dist/widget-panels/modal-widget.test.d.ts +2 -0
- package/dist/widget-panels/modal-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/modal-widget.test.js +103 -0
- package/dist/widget-panels/modal-widget.test.js.map +1 -0
- package/dist/widget-panels/omni-box-widget.d.ts +59 -0
- package/dist/widget-panels/omni-box-widget.d.ts.map +1 -0
- package/dist/widget-panels/omni-box-widget.js +562 -0
- package/dist/widget-panels/omni-box-widget.js.map +1 -0
- package/dist/widget-panels/omni-box-widget.test.d.ts +2 -0
- package/dist/widget-panels/omni-box-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/omni-box-widget.test.js +49 -0
- package/dist/widget-panels/omni-box-widget.test.js.map +1 -0
- package/dist/widget-panels/reset-view-widget.d.ts +20 -0
- package/dist/widget-panels/reset-view-widget.d.ts.map +1 -0
- package/dist/widget-panels/reset-view-widget.js +28 -0
- package/dist/widget-panels/reset-view-widget.js.map +1 -0
- package/dist/widget-panels/settings-panel.d.ts +49 -0
- package/dist/widget-panels/settings-panel.d.ts.map +1 -0
- package/dist/widget-panels/settings-panel.js +263 -0
- package/dist/widget-panels/settings-panel.js.map +1 -0
- package/dist/widget-panels/settings-panel.test.d.ts +2 -0
- package/dist/widget-panels/settings-panel.test.d.ts.map +1 -0
- package/dist/widget-panels/settings-panel.test.js +217 -0
- package/dist/widget-panels/settings-panel.test.js.map +1 -0
- package/dist/widget-panels/sidebar-widget.d.ts +65 -0
- package/dist/widget-panels/sidebar-widget.d.ts.map +1 -0
- package/dist/widget-panels/sidebar-widget.js +339 -0
- package/dist/widget-panels/sidebar-widget.js.map +1 -0
- package/dist/widget-panels/sidebar-widget.test.d.ts +2 -0
- package/dist/widget-panels/sidebar-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/sidebar-widget.test.js +175 -0
- package/dist/widget-panels/sidebar-widget.test.js.map +1 -0
- package/dist/widget-panels/stats-panel.d.ts +34 -0
- package/dist/widget-panels/stats-panel.d.ts.map +1 -0
- package/dist/widget-panels/stats-panel.js +61 -0
- package/dist/widget-panels/stats-panel.js.map +1 -0
- package/dist/widget-panels/stats-panel.test.d.ts +2 -0
- package/dist/widget-panels/stats-panel.test.d.ts.map +1 -0
- package/dist/widget-panels/stats-panel.test.js +36 -0
- package/dist/widget-panels/stats-panel.test.js.map +1 -0
- package/dist/widget-panels/text-editor-panel-monaco-runtime.d.ts +17 -0
- package/dist/widget-panels/text-editor-panel-monaco-runtime.d.ts.map +1 -0
- package/dist/widget-panels/text-editor-panel-monaco-runtime.js +69 -0
- package/dist/widget-panels/text-editor-panel-monaco-runtime.js.map +1 -0
- package/dist/widget-panels/text-editor-panel.d.ts +42 -0
- package/dist/widget-panels/text-editor-panel.d.ts.map +1 -0
- package/dist/widget-panels/text-editor-panel.js +249 -0
- package/dist/widget-panels/text-editor-panel.js.map +1 -0
- package/dist/widget-panels/text-editor-panel.test.d.ts +2 -0
- package/dist/widget-panels/text-editor-panel.test.d.ts.map +1 -0
- package/dist/widget-panels/text-editor-panel.test.js +393 -0
- package/dist/widget-panels/text-editor-panel.test.js.map +1 -0
- package/dist/widget-panels/time-measure-widget.d.ts +49 -0
- package/dist/widget-panels/time-measure-widget.d.ts.map +1 -0
- package/dist/widget-panels/time-measure-widget.js +351 -0
- package/dist/widget-panels/time-measure-widget.js.map +1 -0
- package/dist/widget-panels/toast-manager.d.ts +24 -0
- package/dist/widget-panels/toast-manager.d.ts.map +1 -0
- package/dist/widget-panels/toast-manager.js +96 -0
- package/dist/widget-panels/toast-manager.js.map +1 -0
- package/dist/widget-panels/toast-manager.test.d.ts +2 -0
- package/dist/widget-panels/toast-manager.test.d.ts.map +1 -0
- package/dist/widget-panels/toast-manager.test.js +75 -0
- package/dist/widget-panels/toast-manager.test.js.map +1 -0
- package/dist/widget-panels/toast-widget.d.ts +20 -0
- package/dist/widget-panels/toast-widget.d.ts.map +1 -0
- package/dist/widget-panels/toast-widget.js +207 -0
- package/dist/widget-panels/toast-widget.js.map +1 -0
- package/dist/widget-panels/toast-widget.test.d.ts +2 -0
- package/dist/widget-panels/toast-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/toast-widget.test.js +81 -0
- package/dist/widget-panels/toast-widget.test.js.map +1 -0
- package/dist/widget-panels/toggle-widget.d.ts +34 -0
- package/dist/widget-panels/toggle-widget.d.ts.map +1 -0
- package/dist/widget-panels/toggle-widget.js +46 -0
- package/dist/widget-panels/toggle-widget.js.map +1 -0
- package/dist/widget-panels/toolbar-widget.d.ts +53 -0
- package/dist/widget-panels/toolbar-widget.d.ts.map +1 -0
- package/dist/widget-panels/toolbar-widget.js +160 -0
- package/dist/widget-panels/toolbar-widget.js.map +1 -0
- package/dist/widget-panels/toolbar-widget.test.d.ts +2 -0
- package/dist/widget-panels/toolbar-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/toolbar-widget.test.js +105 -0
- package/dist/widget-panels/toolbar-widget.test.js.map +1 -0
- package/dist/widget-panels/widget-containers.d.ts +275 -0
- package/dist/widget-panels/widget-containers.d.ts.map +1 -0
- package/dist/widget-panels/widget-containers.js +761 -0
- package/dist/widget-panels/widget-containers.js.map +1 -0
- package/dist/widget-panels/widget-containers.test.d.ts +2 -0
- package/dist/widget-panels/widget-containers.test.d.ts.map +1 -0
- package/dist/widget-panels/widget-containers.test.js +337 -0
- package/dist/widget-panels/widget-containers.test.js.map +1 -0
- package/dist/widget-panels/y-zoom-widget.d.ts +66 -0
- package/dist/widget-panels/y-zoom-widget.d.ts.map +1 -0
- package/dist/widget-panels/y-zoom-widget.js +264 -0
- package/dist/widget-panels/y-zoom-widget.js.map +1 -0
- package/dist/widget-panels/y-zoom-widget.test.d.ts +2 -0
- package/dist/widget-panels/y-zoom-widget.test.d.ts.map +1 -0
- package/dist/widget-panels/y-zoom-widget.test.js +71 -0
- package/dist/widget-panels/y-zoom-widget.test.js.map +1 -0
- package/dist/widgets/heap-memory-widget.d.ts +26 -0
- package/dist/widgets/heap-memory-widget.d.ts.map +1 -0
- package/dist/widgets/heap-memory-widget.js +158 -0
- package/dist/widgets/heap-memory-widget.js.map +1 -0
- package/dist/widgets/keyboard-shortcuts-widget.d.ts +28 -0
- package/dist/widgets/keyboard-shortcuts-widget.d.ts.map +1 -0
- package/dist/widgets/keyboard-shortcuts-widget.js +125 -0
- package/dist/widgets/keyboard-shortcuts-widget.js.map +1 -0
- package/dist/widgets/omni-box-widget.d.ts +59 -0
- package/dist/widgets/omni-box-widget.d.ts.map +1 -0
- package/dist/widgets/omni-box-widget.js +493 -0
- package/dist/widgets/omni-box-widget.js.map +1 -0
- package/dist/widgets/settings-widget.d.ts +64 -0
- package/dist/widgets/settings-widget.d.ts.map +1 -0
- package/dist/widgets/settings-widget.js +148 -0
- package/dist/widgets/settings-widget.js.map +1 -0
- package/dist/widgets/view-manager-utils.d.ts +1 -1
- package/dist/widgets/view-manager-utils.d.ts.map +1 -1
- package/dist/widgets/view-manager-utils.js.map +1 -1
- package/package.json +4 -3
- package/src/{widgets → graph-widgets}/long-press-button.tsx +1 -0
- package/src/{widgets → graph-widgets}/pan-widget.tsx +30 -23
- package/src/{widgets → graph-widgets}/zoom-range-widget.tsx +36 -34
- package/src/{widgets → html-overlay-widgets}/html-overlay-item.tsx +1 -0
- package/src/{widgets → html-overlay-widgets}/html-overlay-widget.tsx +32 -9
- package/src/{widgets → html-overlay-widgets}/html-tooltip-widget.tsx +1 -0
- package/src/index.ts +109 -12
- package/src/keyboard-shortcuts/keyboard-shortcuts-manager.ts +58 -0
- package/src/keyboard-shortcuts/keyboard-shortcuts.ts +113 -0
- package/src/keyboard-shortcuts/keyboard-shortcuts.ts.disabled +107 -0
- package/src/lib/settings/settings.ts +203 -0
- package/src/ready-to-upstream-widgets/reset-view-widget.tsx +57 -0
- package/src/widget-components/icon-button.tsx +38 -0
- package/src/widget-components/select-widget-component.tsx +354 -0
- package/src/widget-panels/box-widget.test.tsx +50 -0
- package/src/widget-panels/box-widget.tsx +284 -0
- package/src/widget-panels/full-screen-panel-widget.test.tsx +49 -0
- package/src/widget-panels/full-screen-panel-widget.tsx +223 -0
- package/src/widget-panels/heap-memory-widget.tsx +221 -0
- package/src/widget-panels/keyboard-shortcuts-widget.tsx +511 -0
- package/src/widget-panels/modal-widget.test.tsx +124 -0
- package/src/widget-panels/modal-widget.tsx +464 -0
- package/src/widget-panels/omni-box-widget.test.tsx +59 -0
- package/src/widget-panels/omni-box-widget.tsx +849 -0
- package/src/widget-panels/reset-view-widget.tsx +56 -0
- package/src/widget-panels/settings-panel.test.tsx +286 -0
- package/src/widget-panels/settings-panel.tsx +619 -0
- package/src/widget-panels/sidebar-widget.test.tsx +215 -0
- package/src/widget-panels/sidebar-widget.tsx +525 -0
- package/src/widget-panels/stats-panel.test.tsx +41 -0
- package/src/widget-panels/stats-panel.tsx +108 -0
- package/src/widget-panels/text-editor-panel-monaco-runtime.ts +97 -0
- package/src/widget-panels/text-editor-panel.test.tsx +618 -0
- package/src/widget-panels/text-editor-panel.tsx +375 -0
- package/src/widget-panels/time-measure-widget.tsx +445 -0
- package/src/widget-panels/toast-manager.test.ts +98 -0
- package/src/widget-panels/toast-manager.ts +134 -0
- package/src/widget-panels/toast-widget.test.tsx +105 -0
- package/src/widget-panels/toast-widget.tsx +293 -0
- package/src/widget-panels/toggle-widget.tsx +93 -0
- package/src/widget-panels/toolbar-widget.test.ts +129 -0
- package/src/widget-panels/toolbar-widget.tsx +293 -0
- package/src/widget-panels/widget-containers.test.tsx +453 -0
- package/src/widget-panels/widget-containers.tsx +1330 -0
- package/src/widget-panels/worker-modules.d.ts +7 -0
- package/src/widget-panels/y-zoom-widget.test.tsx +101 -0
- package/src/widget-panels/y-zoom-widget.tsx +376 -0
- package/src/widgets/heap-memory-widget.tsx +223 -0
- package/src/widgets/keyboard-shortcuts-widget.tsx +245 -0
- package/src/widgets/omni-box-widget.tsx +768 -0
- package/src/widgets/settings-widget.tsx +277 -0
- package/src/widgets/view-manager-utils.ts +1 -1
- package/dist/_deprecate/long-press-button.d.ts.map +0 -1
- package/dist/_deprecate/long-press-button.js.map +0 -1
- package/dist/_deprecate/view-control-widget.d.ts.map +0 -1
- package/dist/_deprecate/view-control-widget.js.map +0 -1
- package/dist/widgets/html-cluster-widget.d.ts.map +0 -1
- package/dist/widgets/html-cluster-widget.js.map +0 -1
- package/dist/widgets/html-overlay-item.d.ts.map +0 -1
- package/dist/widgets/html-overlay-item.js.map +0 -1
- package/dist/widgets/html-overlay-widget.d.ts.map +0 -1
- package/dist/widgets/html-overlay-widget.js.map +0 -1
- package/dist/widgets/html-tooltip-widget.d.ts.map +0 -1
- package/dist/widgets/html-tooltip-widget.js.map +0 -1
- package/dist/widgets/long-press-button.d.ts.map +0 -1
- package/dist/widgets/long-press-button.js.map +0 -1
- package/dist/widgets/long-press-controller.d.ts.map +0 -1
- package/dist/widgets/long-press-controller.js.map +0 -1
- package/dist/widgets/pan-widget.d.ts.map +0 -1
- package/dist/widgets/pan-widget.js.map +0 -1
- package/dist/widgets/zoom-range-widget.d.ts.map +0 -1
- package/dist/widgets/zoom-range-widget.js.map +0 -1
- /package/dist/{_deprecate → graph-widgets/_deprecate}/long-press-button.d.ts +0 -0
- /package/dist/{_deprecate → graph-widgets/_deprecate}/long-press-button.js +0 -0
- /package/dist/{_deprecate → graph-widgets/_deprecate}/view-control-widget.d.ts +0 -0
- /package/dist/{_deprecate → graph-widgets/_deprecate}/view-control-widget.js +0 -0
- /package/dist/{widgets → graph-widgets}/long-press-controller.d.ts +0 -0
- /package/dist/{widgets → graph-widgets}/long-press-controller.js +0 -0
- /package/dist/{widgets → html-overlay-widgets}/html-cluster-widget.d.ts +0 -0
- /package/dist/{widgets → html-overlay-widgets}/html-cluster-widget.js +0 -0
- /package/dist/{widgets → html-overlay-widgets}/html-overlay-item.js +0 -0
- /package/dist/{widgets → html-overlay-widgets}/html-tooltip-widget.js +0 -0
- /package/src/{_deprecate → graph-widgets/_deprecate}/long-press-button.tsx +0 -0
- /package/src/{_deprecate → graph-widgets/_deprecate}/view-control-widget.tsx +0 -0
- /package/src/{widgets → graph-widgets}/long-press-controller.ts +0 -0
- /package/src/{widgets → html-overlay-widgets}/html-cluster-widget.ts +0 -0
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
/** @jsxImportSource preact */
|
|
2
|
+
import {createContext} from 'preact';
|
|
3
|
+
import {
|
|
4
|
+
useCallback,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useLayoutEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState
|
|
11
|
+
} from 'preact/hooks';
|
|
12
|
+
|
|
13
|
+
import {DarkTheme, LightTheme} from '@deck.gl/widgets';
|
|
14
|
+
|
|
15
|
+
import type {ComponentChildren, JSX} from 'preact';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Internal panel identifier used by both accordion and tab containers.
|
|
19
|
+
*/
|
|
20
|
+
type WidgetPanelId = string;
|
|
21
|
+
|
|
22
|
+
/** Light/dark theme modes used for panel-scoped overrides. */
|
|
23
|
+
export type WidgetPanelThemeMode = 'light' | 'dark';
|
|
24
|
+
|
|
25
|
+
/** Public theme override options for widget panels. */
|
|
26
|
+
export type WidgetPanelTheme = 'inherit' | 'light' | 'dark' | 'invert';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Describes one entry in an accordion or tabbed container.
|
|
30
|
+
*/
|
|
31
|
+
export type WidgetPanel = {
|
|
32
|
+
/** Stable id used for expansion/selection bookkeeping. */
|
|
33
|
+
id: WidgetPanelId;
|
|
34
|
+
/** Visible heading text for the panel. */
|
|
35
|
+
title: string;
|
|
36
|
+
/** Renderable panel body. */
|
|
37
|
+
content: JSX.Element;
|
|
38
|
+
/** Optional theme override applied to this panel subtree. */
|
|
39
|
+
theme?: WidgetPanelTheme;
|
|
40
|
+
/**
|
|
41
|
+
* If true, the panel can not be interacted with and will not switch/expand.
|
|
42
|
+
*/
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* If true, keep the panel mounted when collapsed (for preserving internal state).
|
|
46
|
+
*/
|
|
47
|
+
keepMounted?: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type WidgetPanelRecord = Record<string, WidgetPanel>;
|
|
51
|
+
|
|
52
|
+
export type AccordeonPanelProps = {
|
|
53
|
+
/**
|
|
54
|
+
* Map of panel IDs to panel definitions.
|
|
55
|
+
*/
|
|
56
|
+
panels: WidgetPanelRecord;
|
|
57
|
+
/** Optional identifier for the wrapper panel when embedded in another container. */
|
|
58
|
+
id?: string;
|
|
59
|
+
/** Optional heading used by outer overlays when this is rendered as a direct child panel. */
|
|
60
|
+
title?: string;
|
|
61
|
+
/** Optional theme override applied to this panel subtree. */
|
|
62
|
+
theme?: WidgetPanelTheme;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type TabbedPanelProps = {
|
|
66
|
+
/**
|
|
67
|
+
* Map of panel IDs to panel definitions.
|
|
68
|
+
*/
|
|
69
|
+
panels: WidgetPanelRecord;
|
|
70
|
+
/** Optional identifier for the wrapper panel when embedded in another container. */
|
|
71
|
+
id?: string;
|
|
72
|
+
/** Optional heading used by outer overlays when this is rendered as a direct child panel. */
|
|
73
|
+
title?: string;
|
|
74
|
+
/** Controls whether the tab list wraps onto multiple rows or scrolls horizontally. */
|
|
75
|
+
tabListLayout?: 'wrap' | 'scroll';
|
|
76
|
+
/** Optional theme override applied to this panel subtree. */
|
|
77
|
+
theme?: WidgetPanelTheme;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type ColumnPanelProps = {
|
|
81
|
+
/**
|
|
82
|
+
* Map of panel IDs to panel definitions.
|
|
83
|
+
*/
|
|
84
|
+
panels: WidgetPanelRecord;
|
|
85
|
+
/** Optional identifier for the wrapper panel when embedded in another container. */
|
|
86
|
+
id?: string;
|
|
87
|
+
/** Optional heading used by outer overlays when this is rendered as a direct child panel. */
|
|
88
|
+
title?: string;
|
|
89
|
+
/** Optional theme override applied to this panel subtree. */
|
|
90
|
+
theme?: WidgetPanelTheme;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type CustomPanelProps = {
|
|
94
|
+
/** Stable id used for expansion/selection bookkeeping. */
|
|
95
|
+
id: string;
|
|
96
|
+
/** Visible heading text for the panel. */
|
|
97
|
+
title: string;
|
|
98
|
+
/**
|
|
99
|
+
* Called after the host element mounts.
|
|
100
|
+
* Return a cleanup callback to dispose any manual DOM work on unmount.
|
|
101
|
+
*/
|
|
102
|
+
onRenderHTML: (rootElement: HTMLElement) => void | (() => void);
|
|
103
|
+
/**
|
|
104
|
+
* If true, the panel can not be interacted with and will not switch/expand.
|
|
105
|
+
*/
|
|
106
|
+
disabled?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* If true, keep the panel mounted when collapsed (for preserving internal state).
|
|
109
|
+
*/
|
|
110
|
+
keepMounted?: boolean;
|
|
111
|
+
/** Optional class name applied to the host element. */
|
|
112
|
+
className?: string;
|
|
113
|
+
/** Optional theme override applied to this panel subtree. */
|
|
114
|
+
theme?: WidgetPanelTheme;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type MarkdownPanelProps = {
|
|
118
|
+
/** Stable id used for expansion/selection bookkeeping. */
|
|
119
|
+
id: string;
|
|
120
|
+
/** Visible heading text for the panel. */
|
|
121
|
+
title: string;
|
|
122
|
+
/** Markdown source rendered into a small built-in safe subset. */
|
|
123
|
+
markdown: string;
|
|
124
|
+
/**
|
|
125
|
+
* If true, the panel can not be interacted with and will not switch/expand.
|
|
126
|
+
*/
|
|
127
|
+
disabled?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* If true, keep the panel mounted when collapsed (for preserving internal state).
|
|
130
|
+
*/
|
|
131
|
+
keepMounted?: boolean;
|
|
132
|
+
/** Optional class name applied to the markdown content host. */
|
|
133
|
+
className?: string;
|
|
134
|
+
/** Optional theme override applied to this panel subtree. */
|
|
135
|
+
theme?: WidgetPanelTheme;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Normalizes an object map of panels into an array in insertion order.
|
|
140
|
+
*/
|
|
141
|
+
function normalizePanelRecordPanels(panels: WidgetPanelRecord): WidgetPanel[] {
|
|
142
|
+
return Object.keys(panels).map((panelId) => {
|
|
143
|
+
const panel = panels[panelId];
|
|
144
|
+
return {
|
|
145
|
+
...panel,
|
|
146
|
+
id: panelId
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Mounts an imperative HTML host for custom widget panels.
|
|
153
|
+
*/
|
|
154
|
+
function CustomPanelContent({
|
|
155
|
+
className,
|
|
156
|
+
onRenderHTML
|
|
157
|
+
}: Pick<CustomPanelProps, 'className' | 'onRenderHTML'>) {
|
|
158
|
+
const rootElementRef = useRef<HTMLDivElement | null>(null);
|
|
159
|
+
|
|
160
|
+
useLayoutEffect(() => {
|
|
161
|
+
const rootElement = rootElementRef.current;
|
|
162
|
+
if (!rootElement) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const cleanup = onRenderHTML(rootElement);
|
|
167
|
+
return () => {
|
|
168
|
+
if (typeof cleanup === 'function') {
|
|
169
|
+
cleanup();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}, [onRenderHTML]);
|
|
173
|
+
|
|
174
|
+
return <div ref={rootElementRef} className={className} />;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* A wrapper panel that renders child panels in an accordion layout.
|
|
179
|
+
*/
|
|
180
|
+
export class AccordeonPanel implements WidgetPanel {
|
|
181
|
+
id: string;
|
|
182
|
+
title: string;
|
|
183
|
+
content: JSX.Element;
|
|
184
|
+
theme?: WidgetPanelTheme;
|
|
185
|
+
|
|
186
|
+
constructor({
|
|
187
|
+
panels,
|
|
188
|
+
id = 'accordeon-widgets',
|
|
189
|
+
title = 'Panels',
|
|
190
|
+
theme = 'inherit'
|
|
191
|
+
}: AccordeonPanelProps) {
|
|
192
|
+
this.id = id;
|
|
193
|
+
this.title = title;
|
|
194
|
+
this.theme = theme;
|
|
195
|
+
this.content = <AccordeonWidgetContainer panels={normalizePanelRecordPanels(panels)} />;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A wrapper panel that renders child panels in a tabbed layout.
|
|
201
|
+
*/
|
|
202
|
+
export class TabbedPanel implements WidgetPanel {
|
|
203
|
+
id: string;
|
|
204
|
+
title: string;
|
|
205
|
+
content: JSX.Element;
|
|
206
|
+
theme?: WidgetPanelTheme;
|
|
207
|
+
|
|
208
|
+
constructor({
|
|
209
|
+
panels,
|
|
210
|
+
id = 'tabbed-widgets',
|
|
211
|
+
title = 'Panels',
|
|
212
|
+
tabListLayout = 'wrap',
|
|
213
|
+
theme = 'inherit'
|
|
214
|
+
}: TabbedPanelProps) {
|
|
215
|
+
this.id = id;
|
|
216
|
+
this.title = title;
|
|
217
|
+
this.theme = theme;
|
|
218
|
+
this.content = (
|
|
219
|
+
<TabbedWidgetContainer
|
|
220
|
+
panels={normalizePanelRecordPanels(panels)}
|
|
221
|
+
tabListLayout={tabListLayout}
|
|
222
|
+
/>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A wrapper panel that renders child panels in a vertical column.
|
|
229
|
+
*/
|
|
230
|
+
export class ColumnPanel implements WidgetPanel {
|
|
231
|
+
id: string;
|
|
232
|
+
title: string;
|
|
233
|
+
content: JSX.Element;
|
|
234
|
+
theme?: WidgetPanelTheme;
|
|
235
|
+
|
|
236
|
+
constructor({
|
|
237
|
+
panels,
|
|
238
|
+
id = 'column-widgets',
|
|
239
|
+
title = 'Panels',
|
|
240
|
+
theme = 'inherit'
|
|
241
|
+
}: ColumnPanelProps) {
|
|
242
|
+
this.id = id;
|
|
243
|
+
this.title = title;
|
|
244
|
+
this.theme = theme;
|
|
245
|
+
this.content = <ColumnWidgetContainer panels={normalizePanelRecordPanels(panels)} />;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* A wrapper panel that renders imperative HTML content into a managed host element.
|
|
251
|
+
*/
|
|
252
|
+
export class CustomPanel implements WidgetPanel {
|
|
253
|
+
id: string;
|
|
254
|
+
title: string;
|
|
255
|
+
content: JSX.Element;
|
|
256
|
+
theme?: WidgetPanelTheme;
|
|
257
|
+
disabled?: boolean;
|
|
258
|
+
keepMounted?: boolean;
|
|
259
|
+
|
|
260
|
+
constructor({
|
|
261
|
+
id,
|
|
262
|
+
title,
|
|
263
|
+
onRenderHTML,
|
|
264
|
+
disabled,
|
|
265
|
+
keepMounted,
|
|
266
|
+
className,
|
|
267
|
+
theme = 'inherit'
|
|
268
|
+
}: CustomPanelProps) {
|
|
269
|
+
this.id = id;
|
|
270
|
+
this.title = title;
|
|
271
|
+
this.theme = theme;
|
|
272
|
+
this.disabled = disabled;
|
|
273
|
+
this.keepMounted = keepMounted;
|
|
274
|
+
this.content = <CustomPanelContent className={className} onRenderHTML={onRenderHTML} />;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* A wrapper panel that renders a minimal built-in Markdown subset without external parsers.
|
|
280
|
+
*/
|
|
281
|
+
export class MarkdownPanel implements WidgetPanel {
|
|
282
|
+
id: string;
|
|
283
|
+
title: string;
|
|
284
|
+
content: JSX.Element;
|
|
285
|
+
theme?: WidgetPanelTheme;
|
|
286
|
+
disabled?: boolean;
|
|
287
|
+
keepMounted?: boolean;
|
|
288
|
+
|
|
289
|
+
constructor({
|
|
290
|
+
id,
|
|
291
|
+
title,
|
|
292
|
+
markdown,
|
|
293
|
+
disabled,
|
|
294
|
+
keepMounted,
|
|
295
|
+
className,
|
|
296
|
+
theme = 'inherit'
|
|
297
|
+
}: MarkdownPanelProps) {
|
|
298
|
+
this.id = id;
|
|
299
|
+
this.title = title;
|
|
300
|
+
this.theme = theme;
|
|
301
|
+
this.disabled = disabled;
|
|
302
|
+
this.keepMounted = keepMounted;
|
|
303
|
+
this.content = (
|
|
304
|
+
<div className={className} style={MARKDOWN_PANEL_STYLE}>
|
|
305
|
+
{renderMarkdownBlocks(markdown)}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Shared props for both container implementations.
|
|
313
|
+
*/
|
|
314
|
+
export type WidgetContainerPanelBase = {
|
|
315
|
+
/** Optional class name applied to the outer container. */
|
|
316
|
+
className?: string;
|
|
317
|
+
/** Optional set of panels to render. */
|
|
318
|
+
panels: ReadonlyArray<WidgetPanel>;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Single-panel container props for direct modal/sidebar content rendering.
|
|
323
|
+
*/
|
|
324
|
+
export type WidgetPanelContainerProps = {
|
|
325
|
+
/** Optional class name applied to the outer container. */
|
|
326
|
+
className?: string;
|
|
327
|
+
/** The panel to render as raw content. */
|
|
328
|
+
panel: WidgetPanel;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Accordion container properties.
|
|
333
|
+
*/
|
|
334
|
+
export type AccordeonWidgetContainerProps = WidgetContainerPanelBase & {
|
|
335
|
+
/** Optional uncontrolled default expanded panel ids. */
|
|
336
|
+
defaultExpandedPanelIds?: ReadonlyArray<WidgetPanelId>;
|
|
337
|
+
/**
|
|
338
|
+
* Controlled expanded panel ids. If supplied, callers manage expand/collapse state.
|
|
339
|
+
*/
|
|
340
|
+
expandedPanelIds?: ReadonlyArray<WidgetPanelId>;
|
|
341
|
+
/**
|
|
342
|
+
* Called when user intent changes expanded panel ids.
|
|
343
|
+
*/
|
|
344
|
+
onExpandedPanelIdsChange?: (expandedPanelIds: ReadonlyArray<WidgetPanelId>) => void;
|
|
345
|
+
/**
|
|
346
|
+
* If false, opening one panel closes all others. Defaults to true.
|
|
347
|
+
*/
|
|
348
|
+
allowMultipleExpanded?: boolean;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Tabs container properties.
|
|
353
|
+
*/
|
|
354
|
+
export type TabbedWidgetContainerProps = WidgetContainerPanelBase & {
|
|
355
|
+
/** Optional uncontrolled default active tab id. */
|
|
356
|
+
defaultActivePanelId?: WidgetPanelId;
|
|
357
|
+
/**
|
|
358
|
+
* Controlled active tab id. If supplied, callers manage active tab state.
|
|
359
|
+
*/
|
|
360
|
+
activePanelId?: WidgetPanelId;
|
|
361
|
+
/**
|
|
362
|
+
* Called when user intent changes active tab id.
|
|
363
|
+
*/
|
|
364
|
+
onActivePanelIdChange?: (activePanelId: WidgetPanelId | undefined) => void;
|
|
365
|
+
/** Controls whether the tab list wraps onto multiple rows or scrolls horizontally. */
|
|
366
|
+
tabListLayout?: 'wrap' | 'scroll';
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Column container properties.
|
|
371
|
+
*/
|
|
372
|
+
export type ColumnWidgetContainerProps = WidgetContainerPanelBase;
|
|
373
|
+
|
|
374
|
+
/** A serialized form describing an accordion widget container. */
|
|
375
|
+
export type WidgetAccordeonContainer = {
|
|
376
|
+
/** Container variant discriminator. */
|
|
377
|
+
kind: 'accordeon';
|
|
378
|
+
/** Accordion container props. */
|
|
379
|
+
props: AccordeonWidgetContainerProps;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
/** A serialized form describing a tabbed widget container. */
|
|
383
|
+
export type WidgetTabbedContainer = {
|
|
384
|
+
/** Container variant discriminator. */
|
|
385
|
+
kind: 'tabs';
|
|
386
|
+
/** Tabs container props. */
|
|
387
|
+
props: TabbedWidgetContainerProps;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/** A serialized form describing a single panel widget container. */
|
|
391
|
+
export type WidgetPanelContainer = {
|
|
392
|
+
/** Container variant discriminator. */
|
|
393
|
+
kind: 'panel';
|
|
394
|
+
/** Single-panel props. */
|
|
395
|
+
props: WidgetPanelContainerProps;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
/** A serialized widget-container description consumed by modal and sidebar widgets. */
|
|
399
|
+
export type WidgetContainer =
|
|
400
|
+
| WidgetAccordeonContainer
|
|
401
|
+
| WidgetTabbedContainer
|
|
402
|
+
| WidgetPanelContainer;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Builds a direct-content container for modal-style and sidebar panel shorthands.
|
|
406
|
+
*/
|
|
407
|
+
export function asPanelContainer(panel: WidgetPanel): WidgetPanelContainer {
|
|
408
|
+
return {
|
|
409
|
+
kind: 'panel',
|
|
410
|
+
props: {
|
|
411
|
+
panel
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Renders an accordion-style widget panel stack.
|
|
418
|
+
*/
|
|
419
|
+
export function AccordeonWidgetContainer({
|
|
420
|
+
panels,
|
|
421
|
+
className,
|
|
422
|
+
defaultExpandedPanelIds = [],
|
|
423
|
+
expandedPanelIds,
|
|
424
|
+
onExpandedPanelIdsChange,
|
|
425
|
+
allowMultipleExpanded = true
|
|
426
|
+
}: AccordeonWidgetContainerProps) {
|
|
427
|
+
const [currentExpandedPanelIds, setCurrentExpandedPanelIds] = useControlledStringListState(
|
|
428
|
+
expandedPanelIds,
|
|
429
|
+
defaultExpandedPanelIds
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const expandedPanelIdSet = useMemo(
|
|
433
|
+
() => new Set(currentExpandedPanelIds),
|
|
434
|
+
[currentExpandedPanelIds]
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const effectivePanels = useMemo(() => [...panels], [panels]);
|
|
438
|
+
|
|
439
|
+
const handleTogglePanel = useCallback(
|
|
440
|
+
(panelId: WidgetPanelId) => {
|
|
441
|
+
const nextPanelIds = new Set(allowMultipleExpanded ? currentExpandedPanelIds : []);
|
|
442
|
+
if (nextPanelIds.has(panelId)) {
|
|
443
|
+
nextPanelIds.delete(panelId);
|
|
444
|
+
} else {
|
|
445
|
+
if (!allowMultipleExpanded) {
|
|
446
|
+
nextPanelIds.clear();
|
|
447
|
+
}
|
|
448
|
+
nextPanelIds.add(panelId);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const next = [...nextPanelIds];
|
|
452
|
+
setCurrentExpandedPanelIds(next);
|
|
453
|
+
onExpandedPanelIdsChange?.(next);
|
|
454
|
+
},
|
|
455
|
+
[allowMultipleExpanded, currentExpandedPanelIds, onExpandedPanelIdsChange]
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
return (
|
|
459
|
+
<div className={className} style={ACCORDEON_CONTAINER_STYLE}>
|
|
460
|
+
{effectivePanels.map((panel) => {
|
|
461
|
+
const isExpanded = expandedPanelIdSet.has(panel.id);
|
|
462
|
+
const shouldRenderContent = panel.keepMounted || isExpanded;
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<section key={panel.id} style={ACCORDION_PANEL_STYLE}>
|
|
466
|
+
<button
|
|
467
|
+
type="button"
|
|
468
|
+
style={{
|
|
469
|
+
...ACCORDION_HEADING_STYLE,
|
|
470
|
+
cursor: panel.disabled ? 'not-allowed' : 'pointer',
|
|
471
|
+
opacity: panel.disabled ? 0.55 : 1
|
|
472
|
+
}}
|
|
473
|
+
disabled={panel.disabled}
|
|
474
|
+
onPointerDown={(event) => {
|
|
475
|
+
if (panel.disabled) {
|
|
476
|
+
event.stopPropagation();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
event.preventDefault();
|
|
480
|
+
handleTogglePanel(panel.id);
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
<span>{panel.title}</span>
|
|
484
|
+
<span
|
|
485
|
+
style={{
|
|
486
|
+
transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
|
|
487
|
+
transition: 'transform 160ms ease'
|
|
488
|
+
}}
|
|
489
|
+
>
|
|
490
|
+
▸
|
|
491
|
+
</span>
|
|
492
|
+
</button>
|
|
493
|
+
<div
|
|
494
|
+
style={{
|
|
495
|
+
...ACCORDION_CONTENT_STYLE,
|
|
496
|
+
display: isExpanded ? 'block' : 'none'
|
|
497
|
+
}}
|
|
498
|
+
>
|
|
499
|
+
{shouldRenderContent ? (
|
|
500
|
+
<WidgetPanelThemeScope panel={panel}>{panel.content}</WidgetPanelThemeScope>
|
|
501
|
+
) : null}
|
|
502
|
+
</div>
|
|
503
|
+
</section>
|
|
504
|
+
);
|
|
505
|
+
})}
|
|
506
|
+
</div>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Renders a tabbed widget panel switcher.
|
|
512
|
+
*/
|
|
513
|
+
export function TabbedWidgetContainer({
|
|
514
|
+
panels,
|
|
515
|
+
className,
|
|
516
|
+
defaultActivePanelId,
|
|
517
|
+
activePanelId,
|
|
518
|
+
onActivePanelIdChange,
|
|
519
|
+
tabListLayout = 'wrap'
|
|
520
|
+
}: TabbedWidgetContainerProps) {
|
|
521
|
+
const [currentActivePanelId, setCurrentActivePanelId] = useControlledStringState(
|
|
522
|
+
activePanelId,
|
|
523
|
+
defaultActivePanelId
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const enabledPanels = useMemo(() => panels.filter((panel) => !panel.disabled), [panels]);
|
|
527
|
+
const initialActivePanelId = useMemo(() => {
|
|
528
|
+
const isActivePanelEnabled = currentActivePanelId
|
|
529
|
+
? enabledPanels.some((panel) => panel.id === currentActivePanelId)
|
|
530
|
+
: false;
|
|
531
|
+
return isActivePanelEnabled ? currentActivePanelId : enabledPanels[0]?.id;
|
|
532
|
+
}, [currentActivePanelId, enabledPanels]);
|
|
533
|
+
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
const shouldSyncActivePanel = currentActivePanelId !== initialActivePanelId;
|
|
536
|
+
if (!shouldSyncActivePanel || !initialActivePanelId) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
setCurrentActivePanelId(initialActivePanelId);
|
|
541
|
+
onActivePanelIdChange?.(initialActivePanelId);
|
|
542
|
+
}, [currentActivePanelId, initialActivePanelId, onActivePanelIdChange, setCurrentActivePanelId]);
|
|
543
|
+
return (
|
|
544
|
+
<div className={className} style={TABBED_CONTAINER_STYLE}>
|
|
545
|
+
<div data-widget-tabs="" style={getTabListStyle(tabListLayout)}>
|
|
546
|
+
{panels.map((panel) => {
|
|
547
|
+
const isActive = panel.id === initialActivePanelId;
|
|
548
|
+
return (
|
|
549
|
+
<button
|
|
550
|
+
key={panel.id}
|
|
551
|
+
type="button"
|
|
552
|
+
style={{
|
|
553
|
+
...TAB_BUTTON_STYLE,
|
|
554
|
+
opacity: panel.disabled ? 0.55 : 1,
|
|
555
|
+
backgroundColor: isActive
|
|
556
|
+
? 'var(--button-background, rgba(255, 255, 255, 0.96))'
|
|
557
|
+
: 'transparent',
|
|
558
|
+
borderColor: isActive
|
|
559
|
+
? 'var(--menu-border, rgba(148, 163, 184, 0.35))'
|
|
560
|
+
: 'transparent',
|
|
561
|
+
color: isActive
|
|
562
|
+
? 'var(--button-text, rgb(24, 24, 26))'
|
|
563
|
+
: 'var(--button-icon-idle, rgb(71, 85, 105))',
|
|
564
|
+
boxShadow: isActive ? '0 1px 2px rgba(15, 23, 42, 0.08)' : 'none'
|
|
565
|
+
}}
|
|
566
|
+
disabled={panel.disabled}
|
|
567
|
+
onPointerDown={(event) => {
|
|
568
|
+
if (panel.disabled) {
|
|
569
|
+
event.stopPropagation();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
event.preventDefault();
|
|
573
|
+
setCurrentActivePanelId(panel.id);
|
|
574
|
+
onActivePanelIdChange?.(panel.id);
|
|
575
|
+
}}
|
|
576
|
+
>
|
|
577
|
+
{panel.title}
|
|
578
|
+
</button>
|
|
579
|
+
);
|
|
580
|
+
})}
|
|
581
|
+
</div>
|
|
582
|
+
<div style={TAB_PANEL_STYLE}>
|
|
583
|
+
{panels.map((panel) => {
|
|
584
|
+
const isActive = panel.id === initialActivePanelId;
|
|
585
|
+
|
|
586
|
+
return (
|
|
587
|
+
<div
|
|
588
|
+
key={panel.id}
|
|
589
|
+
aria-hidden={!isActive}
|
|
590
|
+
style={{
|
|
591
|
+
...TAB_PANEL_CONTENT_STYLE,
|
|
592
|
+
visibility: isActive ? 'visible' : 'hidden',
|
|
593
|
+
pointerEvents: isActive ? 'auto' : 'none'
|
|
594
|
+
}}
|
|
595
|
+
>
|
|
596
|
+
<WidgetPanelThemeScope panel={panel}>{panel.content}</WidgetPanelThemeScope>
|
|
597
|
+
</div>
|
|
598
|
+
);
|
|
599
|
+
})}
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Renders child panels in a simple vertical column.
|
|
607
|
+
*/
|
|
608
|
+
export function ColumnWidgetContainer({panels, className}: ColumnWidgetContainerProps) {
|
|
609
|
+
const effectivePanels = useMemo(() => [...panels], [panels]);
|
|
610
|
+
|
|
611
|
+
return (
|
|
612
|
+
<div className={className} style={COLUMN_CONTAINER_STYLE}>
|
|
613
|
+
{effectivePanels.map((panel, panelIndex) => (
|
|
614
|
+
<section
|
|
615
|
+
key={panel.id}
|
|
616
|
+
style={{
|
|
617
|
+
...COLUMN_PANEL_STYLE,
|
|
618
|
+
borderTop:
|
|
619
|
+
panelIndex > 0 ? '1px solid var(--menu-border, rgba(148, 163, 184, 0.25))' : 'none',
|
|
620
|
+
opacity: panel.disabled ? 0.55 : 1
|
|
621
|
+
}}
|
|
622
|
+
>
|
|
623
|
+
{panel.title ? <header style={COLUMN_PANEL_HEADER_STYLE}>{panel.title}</header> : null}
|
|
624
|
+
<div style={COLUMN_PANEL_CONTENT_STYLE}>
|
|
625
|
+
<WidgetPanelThemeScope panel={panel}>{panel.content}</WidgetPanelThemeScope>
|
|
626
|
+
</div>
|
|
627
|
+
</section>
|
|
628
|
+
))}
|
|
629
|
+
</div>
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Renders the requested container based on descriptor kind.
|
|
635
|
+
*/
|
|
636
|
+
export function WidgetContainerRenderer({container}: {container: WidgetContainer}) {
|
|
637
|
+
if (container.kind === 'accordeon') {
|
|
638
|
+
return <AccordeonWidgetContainer {...container.props} />;
|
|
639
|
+
}
|
|
640
|
+
if (container.kind === 'panel') {
|
|
641
|
+
return (
|
|
642
|
+
<div className={container.props.className}>
|
|
643
|
+
<WidgetPanelThemeScope panel={container.props.panel}>
|
|
644
|
+
{container.props.panel.content}
|
|
645
|
+
</WidgetPanelThemeScope>
|
|
646
|
+
</div>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
return <TabbedWidgetContainer {...container.props} />;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const PanelThemeModeContext = createContext<WidgetPanelThemeMode | undefined>(undefined);
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Returns the effective light/dark theme mode for the current panel subtree.
|
|
656
|
+
*/
|
|
657
|
+
export function useEffectiveWidgetPanelThemeMode(): WidgetPanelThemeMode {
|
|
658
|
+
return useContext(PanelThemeModeContext) ?? 'light';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Applies a panel-level theme override and exposes the resolved mode to descendants.
|
|
663
|
+
*/
|
|
664
|
+
function WidgetPanelThemeScope({
|
|
665
|
+
panel,
|
|
666
|
+
children
|
|
667
|
+
}: {
|
|
668
|
+
panel: WidgetPanel;
|
|
669
|
+
children: ComponentChildren;
|
|
670
|
+
}) {
|
|
671
|
+
const inheritedMode = useContext(PanelThemeModeContext);
|
|
672
|
+
const hostElementRef = useRef<HTMLDivElement | null>(null);
|
|
673
|
+
const [rootMode, setRootMode] = useState<WidgetPanelThemeMode>('light');
|
|
674
|
+
const parentMode = inheritedMode ?? rootMode;
|
|
675
|
+
const resolvedMode = resolveWidgetPanelThemeMode(parentMode, panel.theme);
|
|
676
|
+
|
|
677
|
+
useLayoutEffect(() => {
|
|
678
|
+
if (!inheritedMode) {
|
|
679
|
+
const hostElement = hostElementRef.current;
|
|
680
|
+
if (!hostElement) {
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
const parentHostElement =
|
|
684
|
+
hostElement.parentElement instanceof HTMLElement ? hostElement.parentElement : hostElement;
|
|
685
|
+
|
|
686
|
+
const updateRootMode = () => {
|
|
687
|
+
const inferredMode = inferWidgetPanelThemeMode(parentHostElement);
|
|
688
|
+
setRootMode((previousMode) =>
|
|
689
|
+
previousMode === inferredMode ? previousMode : inferredMode
|
|
690
|
+
);
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
updateRootMode();
|
|
694
|
+
|
|
695
|
+
const themedContainer = parentHostElement.closest('.deck-widget-container');
|
|
696
|
+
const mutationObserver = new MutationObserver(() => {
|
|
697
|
+
updateRootMode();
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
mutationObserver.observe(parentHostElement, {
|
|
701
|
+
attributes: true,
|
|
702
|
+
attributeFilter: ['style', 'class']
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
if (themedContainer && themedContainer !== parentHostElement) {
|
|
706
|
+
mutationObserver.observe(themedContainer, {
|
|
707
|
+
attributes: true,
|
|
708
|
+
attributeFilter: ['style', 'class']
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return () => {
|
|
713
|
+
mutationObserver.disconnect();
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return undefined;
|
|
718
|
+
}, [inheritedMode]);
|
|
719
|
+
|
|
720
|
+
return (
|
|
721
|
+
<PanelThemeModeContext.Provider value={resolvedMode}>
|
|
722
|
+
<div
|
|
723
|
+
ref={hostElementRef}
|
|
724
|
+
data-panel-theme-mode={resolvedMode}
|
|
725
|
+
style={getWidgetPanelThemeScopeStyle(resolvedMode)}
|
|
726
|
+
>
|
|
727
|
+
{children}
|
|
728
|
+
</div>
|
|
729
|
+
</PanelThemeModeContext.Provider>
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Resolves one local panel theme override against its parent effective mode.
|
|
735
|
+
*/
|
|
736
|
+
function resolveWidgetPanelThemeMode(
|
|
737
|
+
parentMode: WidgetPanelThemeMode,
|
|
738
|
+
theme: WidgetPanelTheme | undefined
|
|
739
|
+
): WidgetPanelThemeMode {
|
|
740
|
+
if (theme === 'dark') {
|
|
741
|
+
return 'dark';
|
|
742
|
+
}
|
|
743
|
+
if (theme === 'light') {
|
|
744
|
+
return 'light';
|
|
745
|
+
}
|
|
746
|
+
if (theme === 'invert') {
|
|
747
|
+
return parentMode === 'dark' ? 'light' : 'dark';
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return parentMode;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Builds the inline CSS variable scope for one resolved panel theme mode.
|
|
755
|
+
*/
|
|
756
|
+
function getWidgetPanelThemeScopeStyle(mode: WidgetPanelThemeMode): JSX.CSSProperties {
|
|
757
|
+
const themeVariables = mode === 'dark' ? DarkTheme : LightTheme;
|
|
758
|
+
return {...themeVariables} as JSX.CSSProperties;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Infers the surrounding widget theme mode from resolved CSS variables.
|
|
763
|
+
*/
|
|
764
|
+
function inferWidgetPanelThemeMode(hostElement: HTMLElement): WidgetPanelThemeMode {
|
|
765
|
+
const ownerWindow = hostElement.ownerDocument.defaultView;
|
|
766
|
+
if (!ownerWindow) {
|
|
767
|
+
return 'light';
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const computedStyle = ownerWindow.getComputedStyle(hostElement);
|
|
771
|
+
const menuBackground = computedStyle.getPropertyValue('--menu-background').trim();
|
|
772
|
+
const parsedColor = parseThemeColor(menuBackground);
|
|
773
|
+
if (!parsedColor) {
|
|
774
|
+
return 'light';
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return getRelativeLuminance(parsedColor) < 0.5 ? 'dark' : 'light';
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Parses a CSS rgb/rgba/hex color string into numeric channels.
|
|
782
|
+
*/
|
|
783
|
+
function parseThemeColor(value: string): [number, number, number] | null {
|
|
784
|
+
if (!value) {
|
|
785
|
+
return null;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (value.startsWith('#')) {
|
|
789
|
+
const hex = value.slice(1);
|
|
790
|
+
if (hex.length === 3) {
|
|
791
|
+
return [
|
|
792
|
+
parseInt(hex[0] + hex[0], 16),
|
|
793
|
+
parseInt(hex[1] + hex[1], 16),
|
|
794
|
+
parseInt(hex[2] + hex[2], 16)
|
|
795
|
+
];
|
|
796
|
+
}
|
|
797
|
+
if (hex.length >= 6) {
|
|
798
|
+
return [
|
|
799
|
+
parseInt(hex.slice(0, 2), 16),
|
|
800
|
+
parseInt(hex.slice(2, 4), 16),
|
|
801
|
+
parseInt(hex.slice(4, 6), 16)
|
|
802
|
+
];
|
|
803
|
+
}
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const channelMatches = value.match(/[\d.]+/g);
|
|
808
|
+
if (!channelMatches || channelMatches.length < 3) {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return [Number(channelMatches[0]), Number(channelMatches[1]), Number(channelMatches[2])];
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Computes relative luminance for an RGB color.
|
|
817
|
+
*/
|
|
818
|
+
function getRelativeLuminance([red, green, blue]: [number, number, number]): number {
|
|
819
|
+
return (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Keeps local string-state in sync with controlled/uncontrolled props.
|
|
824
|
+
*/
|
|
825
|
+
function useControlledStringState(
|
|
826
|
+
controlledValue?: WidgetPanelId,
|
|
827
|
+
defaultValue?: WidgetPanelId
|
|
828
|
+
): [WidgetPanelId | undefined, (next: WidgetPanelId | undefined) => void] {
|
|
829
|
+
const [internalValue, setInternalValue] = useState<WidgetPanelId | undefined>(defaultValue);
|
|
830
|
+
const isControlled = controlledValue !== undefined;
|
|
831
|
+
const resolvedValue = isControlled ? controlledValue : internalValue;
|
|
832
|
+
|
|
833
|
+
useEffect(() => {
|
|
834
|
+
if (!isControlled) {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
setInternalValue(controlledValue);
|
|
838
|
+
}, [isControlled, controlledValue]);
|
|
839
|
+
|
|
840
|
+
const setValue = useCallback(
|
|
841
|
+
(next: WidgetPanelId | undefined) => {
|
|
842
|
+
if (!isControlled) {
|
|
843
|
+
setInternalValue(next);
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
[isControlled]
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
return [resolvedValue, setValue];
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Renders a Markdown document into a safe built-in subset of block elements.
|
|
854
|
+
*/
|
|
855
|
+
/* eslint-disable max-statements, no-continue */
|
|
856
|
+
function renderMarkdownBlocks(markdown: string): JSX.Element[] {
|
|
857
|
+
const normalizedMarkdown = markdown.replace(/\r\n?/g, '\n');
|
|
858
|
+
const lines = normalizedMarkdown.split('\n');
|
|
859
|
+
const blocks: JSX.Element[] = [];
|
|
860
|
+
const paragraphLines: string[] = [];
|
|
861
|
+
const unorderedListItems: string[] = [];
|
|
862
|
+
const orderedListItems: string[] = [];
|
|
863
|
+
let fencedCodeLines: string[] | undefined;
|
|
864
|
+
let blockIndex = 0;
|
|
865
|
+
|
|
866
|
+
const flushParagraph = () => {
|
|
867
|
+
if (paragraphLines.length === 0) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const paragraphText = paragraphLines.join(' ');
|
|
872
|
+
const currentBlockKey = `paragraph-${blockIndex++}`;
|
|
873
|
+
blocks.push(
|
|
874
|
+
<p key={currentBlockKey} style={MARKDOWN_PARAGRAPH_STYLE}>
|
|
875
|
+
{renderInlineMarkdown(paragraphText, currentBlockKey)}
|
|
876
|
+
</p>
|
|
877
|
+
);
|
|
878
|
+
paragraphLines.length = 0;
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
const flushUnorderedList = () => {
|
|
882
|
+
if (unorderedListItems.length === 0) {
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const currentBlockIndex = blockIndex++;
|
|
887
|
+
blocks.push(
|
|
888
|
+
<ul key={`unordered-list-${currentBlockIndex}`} style={MARKDOWN_LIST_STYLE}>
|
|
889
|
+
{unorderedListItems.map((item, itemIndex) => (
|
|
890
|
+
<li
|
|
891
|
+
key={`unordered-item-${currentBlockIndex}-${itemIndex}`}
|
|
892
|
+
style={MARKDOWN_LIST_ITEM_STYLE}
|
|
893
|
+
>
|
|
894
|
+
{renderInlineMarkdown(item, `unordered-item-${currentBlockIndex}-${itemIndex}`)}
|
|
895
|
+
</li>
|
|
896
|
+
))}
|
|
897
|
+
</ul>
|
|
898
|
+
);
|
|
899
|
+
unorderedListItems.length = 0;
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
const flushOrderedList = () => {
|
|
903
|
+
if (orderedListItems.length === 0) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const currentBlockIndex = blockIndex++;
|
|
908
|
+
blocks.push(
|
|
909
|
+
<ol key={`ordered-list-${currentBlockIndex}`} style={MARKDOWN_LIST_STYLE}>
|
|
910
|
+
{orderedListItems.map((item, itemIndex) => (
|
|
911
|
+
<li
|
|
912
|
+
key={`ordered-item-${currentBlockIndex}-${itemIndex}`}
|
|
913
|
+
style={MARKDOWN_LIST_ITEM_STYLE}
|
|
914
|
+
>
|
|
915
|
+
{renderInlineMarkdown(item, `ordered-item-${currentBlockIndex}-${itemIndex}`)}
|
|
916
|
+
</li>
|
|
917
|
+
))}
|
|
918
|
+
</ol>
|
|
919
|
+
);
|
|
920
|
+
orderedListItems.length = 0;
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
const flushFencedCodeBlock = () => {
|
|
924
|
+
if (fencedCodeLines === undefined) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
blocks.push(
|
|
929
|
+
<pre key={`code-${blockIndex++}`} style={MARKDOWN_CODE_BLOCK_STYLE}>
|
|
930
|
+
<code>{fencedCodeLines.join('\n')}</code>
|
|
931
|
+
</pre>
|
|
932
|
+
);
|
|
933
|
+
fencedCodeLines = undefined;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
for (const line of lines) {
|
|
937
|
+
const trimmedLine = line.trim();
|
|
938
|
+
|
|
939
|
+
if (fencedCodeLines !== undefined) {
|
|
940
|
+
if (trimmedLine.startsWith('```')) {
|
|
941
|
+
flushFencedCodeBlock();
|
|
942
|
+
} else {
|
|
943
|
+
fencedCodeLines.push(line);
|
|
944
|
+
}
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (trimmedLine.startsWith('```')) {
|
|
949
|
+
flushParagraph();
|
|
950
|
+
flushUnorderedList();
|
|
951
|
+
flushOrderedList();
|
|
952
|
+
fencedCodeLines = [];
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
if (trimmedLine.length === 0) {
|
|
957
|
+
flushParagraph();
|
|
958
|
+
flushUnorderedList();
|
|
959
|
+
flushOrderedList();
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const headingMatch = trimmedLine.match(/^(#{1,6})\s+(.+)$/);
|
|
964
|
+
if (headingMatch) {
|
|
965
|
+
flushParagraph();
|
|
966
|
+
flushUnorderedList();
|
|
967
|
+
flushOrderedList();
|
|
968
|
+
|
|
969
|
+
const headingLevel = headingMatch[1].length;
|
|
970
|
+
const headingText = headingMatch[2];
|
|
971
|
+
const HeadingTag = `h${headingLevel}` as keyof JSX.IntrinsicElements;
|
|
972
|
+
const currentBlockKey = `heading-${blockIndex++}`;
|
|
973
|
+
blocks.push(
|
|
974
|
+
<HeadingTag key={currentBlockKey} style={getMarkdownHeadingStyle(headingLevel)}>
|
|
975
|
+
{renderInlineMarkdown(headingText, currentBlockKey)}
|
|
976
|
+
</HeadingTag>
|
|
977
|
+
);
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const unorderedListMatch = trimmedLine.match(/^[-*]\s+(.+)$/);
|
|
982
|
+
if (unorderedListMatch) {
|
|
983
|
+
flushParagraph();
|
|
984
|
+
flushOrderedList();
|
|
985
|
+
unorderedListItems.push(unorderedListMatch[1]);
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const orderedListMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
|
|
990
|
+
if (orderedListMatch) {
|
|
991
|
+
flushParagraph();
|
|
992
|
+
flushUnorderedList();
|
|
993
|
+
orderedListItems.push(orderedListMatch[1]);
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
paragraphLines.push(trimmedLine);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
flushParagraph();
|
|
1001
|
+
flushUnorderedList();
|
|
1002
|
+
flushOrderedList();
|
|
1003
|
+
flushFencedCodeBlock();
|
|
1004
|
+
|
|
1005
|
+
return blocks;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Renders a minimal inline Markdown subset into safe text and inline elements.
|
|
1010
|
+
*/
|
|
1011
|
+
// eslint-disable-next-line complexity
|
|
1012
|
+
function renderInlineMarkdown(source: string, keyPrefix: string): ComponentChildren[] {
|
|
1013
|
+
const inlineTokenPattern =
|
|
1014
|
+
/(\[([^\]]+)\]\(([^)\s]+)\)|`([^`]+)`|\*\*([^*]+)\*\*|__([^_]+)__|\*([^*]+)\*|_([^_]+)_)/g;
|
|
1015
|
+
const children: ComponentChildren[] = [];
|
|
1016
|
+
let match: RegExpExecArray | null;
|
|
1017
|
+
let previousIndex = 0;
|
|
1018
|
+
let tokenIndex = 0;
|
|
1019
|
+
|
|
1020
|
+
for (match = inlineTokenPattern.exec(source); match; match = inlineTokenPattern.exec(source)) {
|
|
1021
|
+
if (match.index > previousIndex) {
|
|
1022
|
+
children.push(source.slice(previousIndex, match.index));
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (match[2] !== undefined && match[3] !== undefined) {
|
|
1026
|
+
children.push(
|
|
1027
|
+
<a
|
|
1028
|
+
key={`${keyPrefix}-link-${tokenIndex}`}
|
|
1029
|
+
href={match[3]}
|
|
1030
|
+
target="_blank"
|
|
1031
|
+
rel="noreferrer"
|
|
1032
|
+
style={MARKDOWN_LINK_STYLE}
|
|
1033
|
+
>
|
|
1034
|
+
{match[2]}
|
|
1035
|
+
</a>
|
|
1036
|
+
);
|
|
1037
|
+
} else if (match[4] !== undefined) {
|
|
1038
|
+
children.push(
|
|
1039
|
+
<code key={`${keyPrefix}-code-${tokenIndex}`} style={MARKDOWN_INLINE_CODE_STYLE}>
|
|
1040
|
+
{match[4]}
|
|
1041
|
+
</code>
|
|
1042
|
+
);
|
|
1043
|
+
} else if (match[5] !== undefined || match[6] !== undefined) {
|
|
1044
|
+
children.push(
|
|
1045
|
+
<strong key={`${keyPrefix}-strong-${tokenIndex}`}>{match[5] ?? match[6]}</strong>
|
|
1046
|
+
);
|
|
1047
|
+
} else if (match[7] !== undefined || match[8] !== undefined) {
|
|
1048
|
+
children.push(<em key={`${keyPrefix}-em-${tokenIndex}`}>{match[7] ?? match[8]}</em>);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
previousIndex = match.index + match[0].length;
|
|
1052
|
+
tokenIndex += 1;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (previousIndex < source.length) {
|
|
1056
|
+
children.push(source.slice(previousIndex));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (children.length === 0) {
|
|
1060
|
+
return [source];
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return children;
|
|
1064
|
+
}
|
|
1065
|
+
/* eslint-enable max-statements, no-continue */
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Returns the heading style for one Markdown heading level.
|
|
1069
|
+
*/
|
|
1070
|
+
function getMarkdownHeadingStyle(level: number): JSX.CSSProperties {
|
|
1071
|
+
if (level === 1) {
|
|
1072
|
+
return MARKDOWN_HEADING_1_STYLE;
|
|
1073
|
+
}
|
|
1074
|
+
if (level === 2) {
|
|
1075
|
+
return MARKDOWN_HEADING_2_STYLE;
|
|
1076
|
+
}
|
|
1077
|
+
if (level === 3) {
|
|
1078
|
+
return MARKDOWN_HEADING_3_STYLE;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
return MARKDOWN_HEADING_4_TO_6_STYLE;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Normalizes panel id collections for deterministic membership checks and stable state.
|
|
1086
|
+
*/
|
|
1087
|
+
function normalizePanelIds(
|
|
1088
|
+
values: ReadonlyArray<WidgetPanelId> | undefined
|
|
1089
|
+
): ReadonlyArray<WidgetPanelId> {
|
|
1090
|
+
if (!values || values.length === 0) {
|
|
1091
|
+
return [];
|
|
1092
|
+
}
|
|
1093
|
+
const deduped: WidgetPanelId[] = [];
|
|
1094
|
+
const seen = new Set<string>();
|
|
1095
|
+
for (const value of values) {
|
|
1096
|
+
const trimmed = String(value);
|
|
1097
|
+
if (!seen.has(trimmed)) {
|
|
1098
|
+
seen.add(trimmed);
|
|
1099
|
+
deduped.push(trimmed);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
return deduped;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Keeps local list state in sync with controlled/uncontrolled props.
|
|
1107
|
+
*/
|
|
1108
|
+
function useControlledStringListState(
|
|
1109
|
+
controlledValue?: ReadonlyArray<WidgetPanelId>,
|
|
1110
|
+
defaultValue?: ReadonlyArray<WidgetPanelId>
|
|
1111
|
+
): [ReadonlyArray<WidgetPanelId>, (next: ReadonlyArray<WidgetPanelId>) => void] {
|
|
1112
|
+
const [internalValue, setInternalValue] = useState<ReadonlyArray<WidgetPanelId>>(() =>
|
|
1113
|
+
normalizePanelIds(controlledValue ?? defaultValue)
|
|
1114
|
+
);
|
|
1115
|
+
const isControlled = controlledValue !== undefined;
|
|
1116
|
+
const resolvedValue = isControlled ? normalizePanelIds(controlledValue) : internalValue;
|
|
1117
|
+
|
|
1118
|
+
useEffect(() => {
|
|
1119
|
+
if (!isControlled) {
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
setInternalValue(normalizePanelIds(controlledValue));
|
|
1123
|
+
}, [isControlled, controlledValue]);
|
|
1124
|
+
|
|
1125
|
+
const setValue = useCallback(
|
|
1126
|
+
(next: ReadonlyArray<WidgetPanelId>) => {
|
|
1127
|
+
if (!isControlled) {
|
|
1128
|
+
setInternalValue(normalizePanelIds(next));
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
[isControlled]
|
|
1132
|
+
);
|
|
1133
|
+
|
|
1134
|
+
return [resolvedValue, setValue];
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Shared accordion/tab layout styles.
|
|
1139
|
+
*/
|
|
1140
|
+
const ACCORDEON_CONTAINER_STYLE: JSX.CSSProperties = {
|
|
1141
|
+
display: 'flex',
|
|
1142
|
+
flexDirection: 'column',
|
|
1143
|
+
gap: '8px'
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
const ACCORDION_PANEL_STYLE: JSX.CSSProperties = {
|
|
1147
|
+
border: 'var(--menu-border, 1px solid rgba(148, 163, 184, 0.35))',
|
|
1148
|
+
borderRadius: 'var(--button-corner-radius, 8px)',
|
|
1149
|
+
overflow: 'hidden'
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
const ACCORDION_HEADING_STYLE: JSX.CSSProperties = {
|
|
1153
|
+
width: '100%',
|
|
1154
|
+
border: '0',
|
|
1155
|
+
margin: '0',
|
|
1156
|
+
padding: '10px 12px',
|
|
1157
|
+
display: 'flex',
|
|
1158
|
+
alignItems: 'center',
|
|
1159
|
+
justifyContent: 'space-between',
|
|
1160
|
+
gap: '8px',
|
|
1161
|
+
backgroundColor: 'var(--menu-background, #fff)',
|
|
1162
|
+
color: 'var(--menu-text, rgb(24, 24, 26))',
|
|
1163
|
+
fontSize: '12px',
|
|
1164
|
+
lineHeight: 1.2,
|
|
1165
|
+
fontWeight: 700
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
const ACCORDION_CONTENT_STYLE: JSX.CSSProperties = {
|
|
1169
|
+
padding: '8px 10px 10px 12px',
|
|
1170
|
+
borderTop: 'var(--menu-divider, var(--menu-border, 1px solid rgba(148, 163, 184, 0.25)))',
|
|
1171
|
+
backgroundColor: 'var(--menu-background, #fff)',
|
|
1172
|
+
color: 'var(--menu-text, rgb(24, 24, 26))'
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
const TABBED_CONTAINER_STYLE: JSX.CSSProperties = {
|
|
1176
|
+
display: 'flex',
|
|
1177
|
+
flexDirection: 'column',
|
|
1178
|
+
minWidth: '220px',
|
|
1179
|
+
border: 'var(--menu-border, 1px solid rgba(148, 163, 184, 0.35))',
|
|
1180
|
+
borderRadius: 'var(--button-corner-radius, 8px)',
|
|
1181
|
+
overflow: 'hidden',
|
|
1182
|
+
backgroundColor: 'var(--menu-background, #fff)',
|
|
1183
|
+
color: 'var(--menu-text, rgb(24, 24, 26))'
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
function getTabListStyle(tabListLayout: 'wrap' | 'scroll'): JSX.CSSProperties {
|
|
1187
|
+
return {
|
|
1188
|
+
display: 'flex',
|
|
1189
|
+
alignItems: 'flex-start',
|
|
1190
|
+
flexWrap: tabListLayout === 'wrap' ? 'wrap' : 'nowrap',
|
|
1191
|
+
gap: '4px',
|
|
1192
|
+
overflowX: tabListLayout === 'scroll' ? 'auto' : 'hidden',
|
|
1193
|
+
overflowY: 'hidden',
|
|
1194
|
+
background:
|
|
1195
|
+
'var(--menu-weak-background, var(--button-background, var(--menu-background, #fff)))',
|
|
1196
|
+
padding: '4px 6px',
|
|
1197
|
+
borderBottom: 'var(--menu-divider, var(--menu-border, 1px solid rgba(148, 163, 184, 0.2)))',
|
|
1198
|
+
position: 'sticky',
|
|
1199
|
+
top: 0,
|
|
1200
|
+
zIndex: 1
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const TAB_BUTTON_STYLE: JSX.CSSProperties = {
|
|
1205
|
+
flex: '0 0 auto',
|
|
1206
|
+
border: '1px solid transparent',
|
|
1207
|
+
borderRadius: '4px',
|
|
1208
|
+
margin: 0,
|
|
1209
|
+
padding: '4px 8px',
|
|
1210
|
+
fontSize: '12px',
|
|
1211
|
+
fontWeight: 600,
|
|
1212
|
+
lineHeight: 1.25,
|
|
1213
|
+
cursor: 'pointer',
|
|
1214
|
+
color: 'var(--menu-text, rgb(24, 24, 26))',
|
|
1215
|
+
backgroundColor: 'rgba(255, 255, 255, 0.35)',
|
|
1216
|
+
whiteSpace: 'nowrap',
|
|
1217
|
+
transition: 'background-color 120ms ease, border-color 120ms ease, color 120ms ease'
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
const TAB_PANEL_STYLE: JSX.CSSProperties = {
|
|
1221
|
+
display: 'grid',
|
|
1222
|
+
alignItems: 'start',
|
|
1223
|
+
padding: '10px',
|
|
1224
|
+
overflow: 'auto'
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
const TAB_PANEL_CONTENT_STYLE: JSX.CSSProperties = {
|
|
1228
|
+
gridArea: '1 / 1',
|
|
1229
|
+
minWidth: 0
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
const COLUMN_CONTAINER_STYLE: JSX.CSSProperties = {
|
|
1233
|
+
display: 'grid',
|
|
1234
|
+
gap: '0'
|
|
1235
|
+
};
|
|
1236
|
+
|
|
1237
|
+
const COLUMN_PANEL_STYLE: JSX.CSSProperties = {
|
|
1238
|
+
display: 'grid',
|
|
1239
|
+
gap: '10px',
|
|
1240
|
+
padding: '12px 0'
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
const COLUMN_PANEL_HEADER_STYLE: JSX.CSSProperties = {
|
|
1244
|
+
fontSize: '12px',
|
|
1245
|
+
fontWeight: 700,
|
|
1246
|
+
color: 'var(--button-text, currentColor)',
|
|
1247
|
+
padding: '0 2px'
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
const COLUMN_PANEL_CONTENT_STYLE: JSX.CSSProperties = {
|
|
1251
|
+
minWidth: 0
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
const MARKDOWN_PANEL_STYLE: JSX.CSSProperties = {
|
|
1255
|
+
display: 'flex',
|
|
1256
|
+
flexDirection: 'column',
|
|
1257
|
+
gap: '12px',
|
|
1258
|
+
color: 'var(--menu-text, rgb(24, 24, 26))',
|
|
1259
|
+
fontSize: '13px',
|
|
1260
|
+
lineHeight: '1.5'
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
const MARKDOWN_PARAGRAPH_STYLE: JSX.CSSProperties = {
|
|
1264
|
+
margin: '0'
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
const MARKDOWN_LIST_STYLE: JSX.CSSProperties = {
|
|
1268
|
+
margin: '0',
|
|
1269
|
+
paddingLeft: '18px',
|
|
1270
|
+
display: 'flex',
|
|
1271
|
+
flexDirection: 'column',
|
|
1272
|
+
gap: '6px'
|
|
1273
|
+
};
|
|
1274
|
+
|
|
1275
|
+
const MARKDOWN_LIST_ITEM_STYLE: JSX.CSSProperties = {
|
|
1276
|
+
margin: '0'
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
const MARKDOWN_CODE_BLOCK_STYLE: JSX.CSSProperties = {
|
|
1280
|
+
margin: '0',
|
|
1281
|
+
padding: '10px 12px',
|
|
1282
|
+
borderRadius: '8px',
|
|
1283
|
+
overflowX: 'auto',
|
|
1284
|
+
background: 'var(--menu-weak-background, var(--button-background, var(--menu-background, #fff)))',
|
|
1285
|
+
border: '1px solid var(--menu-border, rgba(148, 163, 184, 0.2))',
|
|
1286
|
+
color: 'var(--menu-text, rgb(24, 24, 26))',
|
|
1287
|
+
fontSize: '12px',
|
|
1288
|
+
lineHeight: '1.45'
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
const MARKDOWN_INLINE_CODE_STYLE: JSX.CSSProperties = {
|
|
1292
|
+
padding: '1px 5px',
|
|
1293
|
+
borderRadius: '4px',
|
|
1294
|
+
background: 'var(--menu-weak-background, var(--button-background, var(--menu-background, #fff)))',
|
|
1295
|
+
border: '1px solid var(--menu-border, rgba(148, 163, 184, 0.2))',
|
|
1296
|
+
color: 'var(--menu-text, rgb(24, 24, 26))',
|
|
1297
|
+
fontSize: '12px'
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
const MARKDOWN_LINK_STYLE: JSX.CSSProperties = {
|
|
1301
|
+
color: 'var(--button-text, rgb(29, 78, 216))'
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1304
|
+
const MARKDOWN_HEADING_1_STYLE: JSX.CSSProperties = {
|
|
1305
|
+
margin: '0',
|
|
1306
|
+
fontSize: '20px',
|
|
1307
|
+
fontWeight: 700,
|
|
1308
|
+
lineHeight: '1.25'
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
const MARKDOWN_HEADING_2_STYLE: JSX.CSSProperties = {
|
|
1312
|
+
margin: '0',
|
|
1313
|
+
fontSize: '17px',
|
|
1314
|
+
fontWeight: 700,
|
|
1315
|
+
lineHeight: '1.3'
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
const MARKDOWN_HEADING_3_STYLE: JSX.CSSProperties = {
|
|
1319
|
+
margin: '0',
|
|
1320
|
+
fontSize: '15px',
|
|
1321
|
+
fontWeight: 700,
|
|
1322
|
+
lineHeight: '1.35'
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
const MARKDOWN_HEADING_4_TO_6_STYLE: JSX.CSSProperties = {
|
|
1326
|
+
margin: '0',
|
|
1327
|
+
fontSize: '13px',
|
|
1328
|
+
fontWeight: 700,
|
|
1329
|
+
lineHeight: '1.4'
|
|
1330
|
+
};
|