@octanejs/base-ui 0.1.14 → 0.1.16
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/package.json +5 -5
- package/src/alert-dialog.ts +2 -0
- package/src/button.ts +63 -0
- package/src/context-menu.ts +317 -0
- package/src/csp-provider.ts +37 -0
- package/src/dialog.ts +89 -22
- package/src/direction-provider.ts +32 -0
- package/src/index.ts +10 -0
- package/src/menu.ts +3387 -0
- package/src/menubar.ts +160 -0
- package/src/popover.ts +99 -25
- package/src/preview-card.ts +812 -0
- package/src/tooltip.ts +1053 -0
- package/src/unstable-use-media-query.ts +101 -0
- package/src/utils/CSPContext.ts +21 -0
- package/src/utils/ContextMenuRootContext.ts +44 -0
- package/src/utils/FloatingPortalLite.ts +29 -0
- package/src/utils/MenubarContext.ts +38 -0
- package/src/utils/RequestQueue.ts +132 -0
- package/src/utils/TimeoutManager.ts +32 -0
- package/src/utils/composite/grid-utils.ts +437 -0
- package/src/utils/composite/gridNavigation.ts +127 -0
- package/src/utils/composite/list-utils.ts +56 -28
- package/src/utils/composite/useCompositeRoot.ts +11 -13
- package/src/utils/constants.ts +22 -0
- package/src/utils/createChangeEventDetails.ts +4 -0
- package/src/utils/findRootOwnerId.ts +19 -0
- package/src/utils/floating/FloatingDelayGroup.ts +280 -0
- package/src/utils/floating/element.ts +36 -2
- package/src/utils/floating/gridNavigation.ts +54 -0
- package/src/utils/floating/safePolygon.ts +448 -0
- package/src/utils/floating/useClientPoint.ts +276 -0
- package/src/utils/floating/useFocus.ts +248 -0
- package/src/utils/floating/useHoverFloatingInteraction.ts +301 -11
- package/src/utils/floating/useHoverInteractionSharedState.ts +139 -0
- package/src/utils/floating/useHoverReferenceInteraction.ts +507 -14
- package/src/utils/floating/useHoverShared.ts +78 -0
- package/src/utils/floating/useListNavigation.ts +1018 -0
- package/src/utils/floating/useTypeahead.ts +287 -0
- package/src/utils/getCssDimensions.ts +28 -0
- package/src/utils/getPseudoElementBounds.ts +54 -0
- package/src/utils/platform.ts +10 -0
- package/src/utils/popups/index.ts +6 -0
- package/src/utils/popups/inlineRect.ts +289 -0
- package/src/utils/useMixedToggleClickHandler.ts +77 -0
- package/src/utils/useOpenInteractionType.ts +37 -1
- package/src/utils/usePopupAutoResize.ts +250 -0
- package/src/utils/usePopupViewport.ts +375 -0
- package/src/utils/usePreviousValue.ts +25 -0
package/src/menubar.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Ported from .base-ui/packages/react/src/menubar/ (v1.6.0) — Phase 3f STAGE 4.
|
|
2
|
+
//
|
|
3
|
+
// A `Menubar` is a `CompositeRoot` of `Menu.Trigger`s that share one `FloatingTree`, plus the
|
|
4
|
+
// `MenubarContext` that `Menu` has been branching on since stage 1 (`parent.type === 'menubar'`).
|
|
5
|
+
// Providing it here is what activates those already-transcribed branches: triggers render as
|
|
6
|
+
// `CompositeItem`s with `role="menuitem"`, sibling menus open on hover once one is open, the
|
|
7
|
+
// positioner flips its default side to match the bar's orientation, and the modal backdrop moves to
|
|
8
|
+
// the bar itself so the whole menubar stays clickable.
|
|
9
|
+
//
|
|
10
|
+
// `MenubarContext` itself lives in `./utils/MenubarContext` — it landed as a context-only module in
|
|
11
|
+
// stage 1 so `Menu`'s parent-union branches could be written verbatim ahead of this component.
|
|
12
|
+
//
|
|
13
|
+
// octane adaptations: `createElement`, not JSX; `forwardRef` → ref-as-prop; per-call-site `S('…')`
|
|
14
|
+
// slots with `subSlot` for every composed hook; `useFloatingNodeId()` takes an explicit
|
|
15
|
+
// (tree, slot) pair.
|
|
16
|
+
import { createElement, useState, useRef, useEffect, useMemo } from 'octane';
|
|
17
|
+
|
|
18
|
+
import { S, subSlot } from './internal';
|
|
19
|
+
import { useBaseUiId } from './utils/useBaseUiId';
|
|
20
|
+
import { CompositeRoot } from './utils/composite/CompositeRoot';
|
|
21
|
+
import {
|
|
22
|
+
FloatingNode,
|
|
23
|
+
FloatingTree,
|
|
24
|
+
useFloatingNodeId,
|
|
25
|
+
useFloatingTree,
|
|
26
|
+
} from './utils/floating/FloatingTree';
|
|
27
|
+
import { MenubarContext, useMenubarContext } from './utils/MenubarContext';
|
|
28
|
+
import type { MenubarContextValue } from './utils/MenubarContext';
|
|
29
|
+
import type { MenuOpenEventDetails, MenuRootOrientation } from './menu';
|
|
30
|
+
import type { StateAttributesMapping } from './utils/getStateAttributesProps';
|
|
31
|
+
import { REASONS } from './utils/createChangeEventDetails';
|
|
32
|
+
|
|
33
|
+
const menubarStateAttributesMapping: StateAttributesMapping<any> = {
|
|
34
|
+
hasSubmenuOpen(value: boolean) {
|
|
35
|
+
return value ? { 'data-has-submenu-open': '' } : null;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function MenubarContent(props: { children?: any }): any {
|
|
40
|
+
const slot = S('MenubarContent');
|
|
41
|
+
const nodeId = useFloatingNodeId(undefined, subSlot(slot, 'nodeId'));
|
|
42
|
+
const tree = useFloatingTree();
|
|
43
|
+
const menuEvents = (tree as any).events;
|
|
44
|
+
const rootContext = useMenubarContext();
|
|
45
|
+
|
|
46
|
+
useEffect(
|
|
47
|
+
() => {
|
|
48
|
+
function onSubmenuOpenChange(details: MenuOpenEventDetails) {
|
|
49
|
+
if (!details.nodeId || details.parentNodeId !== nodeId) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (details.open) {
|
|
54
|
+
if (!rootContext.hasSubmenuOpen) {
|
|
55
|
+
rootContext.setHasSubmenuOpen(true);
|
|
56
|
+
}
|
|
57
|
+
} else if (
|
|
58
|
+
details.reason !== REASONS.siblingOpen &&
|
|
59
|
+
details.reason !== REASONS.listNavigation
|
|
60
|
+
) {
|
|
61
|
+
rootContext.setHasSubmenuOpen(false);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
menuEvents.on('menuopenchange', onSubmenuOpenChange);
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
menuEvents.off('menuopenchange', onSubmenuOpenChange);
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
[menuEvents, nodeId, rootContext],
|
|
72
|
+
subSlot(slot, 'e'),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return createElement(FloatingNode, { id: nodeId, children: props.children });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The container for menus.
|
|
80
|
+
*
|
|
81
|
+
* Documentation: [Base UI Menubar](https://base-ui.com/react/components/menubar)
|
|
82
|
+
*/
|
|
83
|
+
export function Menubar(props: any): any {
|
|
84
|
+
const slot = S('Menubar');
|
|
85
|
+
const {
|
|
86
|
+
orientation = 'horizontal',
|
|
87
|
+
loopFocus = true,
|
|
88
|
+
render,
|
|
89
|
+
className,
|
|
90
|
+
modal = true,
|
|
91
|
+
disabled = false,
|
|
92
|
+
id: idProp,
|
|
93
|
+
style,
|
|
94
|
+
ref: forwardedRef,
|
|
95
|
+
...elementProps
|
|
96
|
+
} = props;
|
|
97
|
+
|
|
98
|
+
const [contentElement, setContentElement] = useState<HTMLElement | null>(
|
|
99
|
+
null,
|
|
100
|
+
subSlot(slot, 'content'),
|
|
101
|
+
);
|
|
102
|
+
const [hasSubmenuOpen, setHasSubmenuOpen] = useState(false, subSlot(slot, 'hasSub'));
|
|
103
|
+
|
|
104
|
+
const id = useBaseUiId(idProp, subSlot(slot, 'id'));
|
|
105
|
+
|
|
106
|
+
const state: MenubarState = {
|
|
107
|
+
orientation,
|
|
108
|
+
modal,
|
|
109
|
+
hasSubmenuOpen,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const contentRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'contentRef'));
|
|
113
|
+
const allowMouseUpTriggerRef = useRef(false, subSlot(slot, 'amut'));
|
|
114
|
+
|
|
115
|
+
const context: MenubarContextValue = useMemo(
|
|
116
|
+
() => ({
|
|
117
|
+
contentElement,
|
|
118
|
+
setContentElement,
|
|
119
|
+
setHasSubmenuOpen,
|
|
120
|
+
hasSubmenuOpen,
|
|
121
|
+
modal,
|
|
122
|
+
disabled,
|
|
123
|
+
orientation,
|
|
124
|
+
allowMouseUpTriggerRef,
|
|
125
|
+
rootId: id,
|
|
126
|
+
}),
|
|
127
|
+
[contentElement, hasSubmenuOpen, modal, disabled, orientation, id],
|
|
128
|
+
subSlot(slot, 'ctx'),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return createElement(MenubarContext.Provider, {
|
|
132
|
+
value: context,
|
|
133
|
+
children: createElement(FloatingTree, {
|
|
134
|
+
children: createElement(MenubarContent, {
|
|
135
|
+
children: createElement(CompositeRoot, {
|
|
136
|
+
render,
|
|
137
|
+
className,
|
|
138
|
+
style,
|
|
139
|
+
state,
|
|
140
|
+
stateAttributesMapping: menubarStateAttributesMapping,
|
|
141
|
+
refs: [forwardedRef, setContentElement, contentRef],
|
|
142
|
+
props: [{ role: 'menubar', id, 'aria-orientation': orientation }, elementProps],
|
|
143
|
+
orientation,
|
|
144
|
+
loopFocus,
|
|
145
|
+
enableHomeAndEndKeys: true,
|
|
146
|
+
highlightItemOnHover: hasSubmenuOpen,
|
|
147
|
+
}),
|
|
148
|
+
}),
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface MenubarState {
|
|
154
|
+
/** The orientation of the menubar. */
|
|
155
|
+
orientation: MenuRootOrientation;
|
|
156
|
+
/** Whether the menubar is modal. */
|
|
157
|
+
modal: boolean;
|
|
158
|
+
/** Whether any submenu within the menubar is open. */
|
|
159
|
+
hasSubmenuOpen: boolean;
|
|
160
|
+
}
|
package/src/popover.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// Ported from .base-ui/packages/react/src/popover/ (v1.6.0): store (PopoverStore/PopoverHandle),
|
|
2
|
-
// root (context/PopoverRoot/PopoverInteractions), trigger
|
|
3
|
-
// reusing the Store + floating
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// root (context/PopoverRoot/PopoverInteractions), trigger, and the anchored open path
|
|
3
|
+
// (Portal/Positioner/Popup/Arrow/Backdrop/Title/Description/Close), reusing the Store + floating
|
|
4
|
+
// stack from Dialog. octane adaptations: forwardRef → ref-as-prop; native events; every Store
|
|
5
|
+
// hook-method + hook threads an explicit slot; `React.createContext` → octane `createContext`;
|
|
6
|
+
// `ReactDOM.flushSync` → octane `flushSync`.
|
|
6
7
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// with `useAnchorPositioning`. Interactions are only mounted when `open || mounted`, and — as with
|
|
10
|
-
// Dialog — a stable no-DOM descriptor wraps the children (octane Provider children shape-flip bug).
|
|
8
|
+
// `openOnHover` is fully wired: the trigger side runs `useHoverReferenceInteraction` (+ safePolygon)
|
|
9
|
+
// and the popup side runs `useHoverFloatingInteraction`.
|
|
11
10
|
import {
|
|
12
11
|
createContext,
|
|
13
12
|
createElement,
|
|
13
|
+
Fragment,
|
|
14
14
|
useContext,
|
|
15
15
|
useMemo,
|
|
16
16
|
useRef,
|
|
@@ -51,13 +51,14 @@ import {
|
|
|
51
51
|
type UseAnchorPositioningSharedParameters,
|
|
52
52
|
} from './utils/useAnchorPositioning';
|
|
53
53
|
import { usePositioner } from './utils/usePositioner';
|
|
54
|
+
import { usePopupViewport } from './utils/usePopupViewport';
|
|
54
55
|
import { useAnchoredPopupScrollLock } from './utils/useAnchoredPopupScrollLock';
|
|
55
56
|
import { adaptiveOrigin } from './utils/adaptiveOriginMiddleware';
|
|
56
57
|
import { InternalBackdrop } from './utils/InternalBackdrop';
|
|
57
58
|
import { useAnimationsFinished } from './utils/useAnimationsFinished';
|
|
58
59
|
import { useOpenChangeComplete } from './utils/useOpenChangeComplete';
|
|
59
60
|
import { getDisabledMountTransitionStyles } from './utils/getDisabledMountTransitionStyles';
|
|
60
|
-
import { POPUP_COLLISION_AVOIDANCE } from './utils/constants';
|
|
61
|
+
import { POPUP_COLLISION_AVOIDANCE, PATIENT_CLICK_THRESHOLD } from './utils/constants';
|
|
61
62
|
import { ClosePartProvider, useClosePartCount, useClosePartRegistration } from './utils/closePart';
|
|
62
63
|
import { popupStateMapping } from './utils/popupStateMapping';
|
|
63
64
|
import { transitionStatusMapping } from './utils/useTransitionStatus';
|
|
@@ -97,7 +98,6 @@ import { PopupTriggerMap } from './utils/popups/popupTriggerMap';
|
|
|
97
98
|
import type { InteractionType } from './utils/useEnhancedClickHandler';
|
|
98
99
|
|
|
99
100
|
const OPEN_DELAY = 300;
|
|
100
|
-
const PATIENT_CLICK_THRESHOLD = 500;
|
|
101
101
|
const CLICK_TRIGGER_IDENTIFIER = 'data-base-ui-click-trigger';
|
|
102
102
|
|
|
103
103
|
// --- Store -------------------------------------------------------------------
|
|
@@ -415,11 +415,7 @@ function PopoverInteractions(props: any): any {
|
|
|
415
415
|
subSlot(slot, 'pip'),
|
|
416
416
|
);
|
|
417
417
|
|
|
418
|
-
return
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function PopoverChildren(props: any): any {
|
|
422
|
-
return props.children ?? null;
|
|
418
|
+
return null;
|
|
423
419
|
}
|
|
424
420
|
|
|
425
421
|
// --- Root --------------------------------------------------------------------
|
|
@@ -519,12 +515,16 @@ function PopoverRootComponent<Payload>(props: any): any {
|
|
|
519
515
|
const resolvedChildren =
|
|
520
516
|
typeof children === 'function' && !isChildrenBlock(children) ? children({ payload }) : children;
|
|
521
517
|
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
518
|
+
// `PopoverInteractions` is a headless SIBLING of the children, exactly as Base UI renders it, so
|
|
519
|
+
// toggling it never disturbs them. Rendering it as a WRAPPER instead swapped the wrapper's
|
|
520
|
+
// component type on every open, which tore down and rebuilt the whole subtree — including the
|
|
521
|
+
// trigger, whose element listeners and store registration then pointed at a detached node.
|
|
522
|
+
const content = [
|
|
523
|
+
shouldRenderInteractions
|
|
524
|
+
? createElement(PopoverInteractions, { key: 'interactions', store, modal })
|
|
525
|
+
: null,
|
|
526
|
+
createElement(Fragment, { key: 'children', children: resolvedChildren }),
|
|
527
|
+
];
|
|
528
528
|
|
|
529
529
|
return createElement(PopoverRootContext.Provider, {
|
|
530
530
|
value: contextValue as PopoverRootContextValue,
|
|
@@ -532,7 +532,12 @@ function PopoverRootComponent<Payload>(props: any): any {
|
|
|
532
532
|
});
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
-
|
|
535
|
+
interface PopoverRootProps<Payload> {
|
|
536
|
+
handle?: PopoverHandle<Payload>;
|
|
537
|
+
[key: string]: unknown;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function PopoverRoot<Payload = unknown>(props: PopoverRootProps<Payload>): any {
|
|
536
541
|
const slot = S('PopoverRootWrapper');
|
|
537
542
|
// Top-level popovers establish a FloatingTree; nested ones reuse the parent's.
|
|
538
543
|
if (usePopoverRootContext(true)) {
|
|
@@ -680,18 +685,33 @@ function PopoverTrigger(componentProps: any): any {
|
|
|
680
685
|
subSlot(slot, 're'),
|
|
681
686
|
);
|
|
682
687
|
|
|
688
|
+
// The trigger's children shape flips between a lone element (closed) and
|
|
689
|
+
// [guard, element, guard] (open, non-modal). Base UI keeps the trigger's identity across that
|
|
690
|
+
// flip by wrapping it in a fragment keyed on the trigger id in BOTH branches — without the key
|
|
691
|
+
// the button is torn down and rebuilt on every open, which detaches the element listeners
|
|
692
|
+
// (hover open/close) and makes the positioner see a spurious trigger change.
|
|
693
|
+
const keyedElement = createElement(Fragment, { key: thisTriggerId, children: element });
|
|
694
|
+
|
|
683
695
|
if (isMountedByThisTrigger && !focusManagerModal) {
|
|
696
|
+
// Base UI renders these as static JSX fragment children; octane reconciles a returned array
|
|
697
|
+
// as a list, so the guards carry stable keys too — otherwise the unkeyed siblings shift the
|
|
698
|
+
// keyed trigger and it is rebuilt anyway.
|
|
684
699
|
return [
|
|
685
|
-
createElement(FocusGuard, { ref: preFocusGuardRef, onFocus: handlePreFocusGuardFocus }),
|
|
686
|
-
element,
|
|
687
700
|
createElement(FocusGuard, {
|
|
701
|
+
key: `${thisTriggerId}-pre-guard`,
|
|
702
|
+
ref: preFocusGuardRef,
|
|
703
|
+
onFocus: handlePreFocusGuardFocus,
|
|
704
|
+
}),
|
|
705
|
+
keyedElement,
|
|
706
|
+
createElement(FocusGuard, {
|
|
707
|
+
key: `${thisTriggerId}-post-guard`,
|
|
688
708
|
ref: store.context.triggerFocusTargetRef,
|
|
689
709
|
onFocus: handleFocusTargetFocus,
|
|
690
710
|
}),
|
|
691
711
|
];
|
|
692
712
|
}
|
|
693
713
|
|
|
694
|
-
return
|
|
714
|
+
return keyedElement;
|
|
695
715
|
}
|
|
696
716
|
|
|
697
717
|
// --- Portal ------------------------------------------------------------------
|
|
@@ -1188,6 +1208,58 @@ function PopoverClose(componentProps: any): any {
|
|
|
1188
1208
|
);
|
|
1189
1209
|
}
|
|
1190
1210
|
|
|
1211
|
+
// --- Viewport ----------------------------------------------------------------
|
|
1212
|
+
|
|
1213
|
+
export const PopoverViewportCssVars = {
|
|
1214
|
+
/**
|
|
1215
|
+
* The width of the parent popup when the previous content was rendered. Placed on the
|
|
1216
|
+
* 'previous' container so the popup's dimensions can be frozen while content animates.
|
|
1217
|
+
*/
|
|
1218
|
+
popupWidth: '--popup-width',
|
|
1219
|
+
/** The height of the parent popup when the previous content was rendered. */
|
|
1220
|
+
popupHeight: '--popup-height',
|
|
1221
|
+
} as const;
|
|
1222
|
+
|
|
1223
|
+
const viewportStateAttributesMapping: StateAttributesMapping<any> = {
|
|
1224
|
+
activationDirection: (value: string | undefined) =>
|
|
1225
|
+
value ? { 'data-activation-direction': value } : null,
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
// A viewport for displaying content transitions. Only needed when one popup can be opened by
|
|
1229
|
+
// multiple triggers, its content changes per trigger, and switching between them is animated.
|
|
1230
|
+
function PopoverViewport(componentProps: any): any {
|
|
1231
|
+
const slot = S('PopoverViewport');
|
|
1232
|
+
const { render, className, style, children, ref, ...elementProps } = componentProps;
|
|
1233
|
+
|
|
1234
|
+
const { store } = usePopoverRootContext() as PopoverRootContextValue;
|
|
1235
|
+
const { side } = usePopoverPositionerContext();
|
|
1236
|
+
|
|
1237
|
+
const instantType = store.useState('instantType', subSlot(slot, 'instant'));
|
|
1238
|
+
|
|
1239
|
+
const { children: childrenToRender, state: viewportState } = usePopupViewport(
|
|
1240
|
+
{ store, side, cssVars: PopoverViewportCssVars, children },
|
|
1241
|
+
subSlot(slot, 'vp'),
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
const state = {
|
|
1245
|
+
activationDirection: viewportState.activationDirection,
|
|
1246
|
+
transitioning: viewportState.transitioning,
|
|
1247
|
+
instant: instantType,
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
return useRenderElement(
|
|
1251
|
+
'div',
|
|
1252
|
+
{ render, className, style },
|
|
1253
|
+
{
|
|
1254
|
+
state,
|
|
1255
|
+
ref,
|
|
1256
|
+
props: [elementProps, { children: childrenToRender }],
|
|
1257
|
+
stateAttributesMapping: viewportStateAttributesMapping,
|
|
1258
|
+
},
|
|
1259
|
+
subSlot(slot, 're'),
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1191
1263
|
// --- Namespace ---------------------------------------------------------------
|
|
1192
1264
|
|
|
1193
1265
|
export const Popover = {
|
|
@@ -1196,10 +1268,12 @@ export const Popover = {
|
|
|
1196
1268
|
Portal: PopoverPortal,
|
|
1197
1269
|
Positioner: PopoverPositioner,
|
|
1198
1270
|
Popup: PopoverPopup,
|
|
1271
|
+
Viewport: PopoverViewport,
|
|
1199
1272
|
Arrow: PopoverArrow,
|
|
1200
1273
|
Backdrop: PopoverBackdrop,
|
|
1201
1274
|
Title: PopoverTitle,
|
|
1202
1275
|
Description: PopoverDescription,
|
|
1203
1276
|
Close: PopoverClose,
|
|
1204
1277
|
createHandle: createPopoverHandle,
|
|
1278
|
+
Handle: PopoverHandle,
|
|
1205
1279
|
};
|