@popperjs/core#createPopper TypeScript Examples
The following examples show how to use
@popperjs/core#createPopper.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: popper.directive.ts From TypeFast with MIT License | 6 votes |
ngOnInit(): void {
let tooltipEl = this.el.nativeElement.getElementsByClassName(
'tooltip'
)[0] as HTMLElement;
if (this.text) {
tooltipEl = document.createElement('div');
tooltipEl.className = 'tooltip';
tooltipEl.innerText = this.text;
document.body.appendChild(tooltipEl);
}
this.popper = createPopper(this.el.nativeElement, tooltipEl, {
placement: 'right' as Placement,
});
}
Example #2
Source File: suggest.ts From Templater with GNU Affero General Public License v3.0 | 6 votes |
open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.pushScope(this.scope);
container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "sameWidth",
enabled: true,
fn: ({ state, instance }) => {
// Note: positioning needs to be calculated twice -
// first pass - positioning it according to the width of the popper
// second pass - position it with the width bound to the reference element
// we need to early exit to avoid an infinite loop
const targetWidth = `${state.rects.reference.width}px`;
if (state.styles.popper.width === targetWidth) {
return;
}
state.styles.popper.width = targetWidth;
instance.update();
},
phase: "beforeWrite",
requires: ["computeStyles"],
},
],
});
}
Example #3
Source File: DatePicker.tsx From symphony-ui-toolkit with Apache License 2.0 | 6 votes |
mountDayPickerInstance() {
const { placement, menuShouldBlockScroll } = this.props;
const { popperElement, referenceElement, refContainer } = this.state;
this.dayPickerInstance = createPopper(referenceElement, popperElement, {
placement: `${placement}-start` as
| 'bottom-start'
| 'top-start'
| 'right-start'
| 'left-start',
modifiers: [
{
name: 'flip',
options: {
fallbackPlacements: ['top-start', 'right-start', 'left-start'],
},
},
{
name: 'offset',
options: {
offset: [0, 4],
},
},
],
});
if (menuShouldBlockScroll) {
const scrollContainer = getScrollParent(refContainer);
if (scrollContainer) {
const wheelEvent = EventListener.onwheel in document.createElement('div') ? EventListener.wheel : EventListener.mousewheel;
scrollContainer.addEventListener(EventListener.DOMMouseScroll, this.handleScrollParent); // older Firefox
scrollContainer.addEventListener(EventListener.touchmove, this.handleScrollParent); // mobile
scrollContainer.addEventListener(wheelEvent, this.handleScrollParent); // modern desktop
scrollContainer.addEventListener(EventListener.keydown, this.handleKeydownScrollParent);
}
}
}
Example #4
Source File: suggest.ts From quickadd with MIT License | 6 votes |
open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.pushScope(this.scope);
container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "sameWidth",
enabled: true,
fn: ({ state, instance }) => {
// Note: positioning needs to be calculated twice -
// first pass - positioning it according to the width of the popper
// second pass - position it with the width bound to the reference element
// we need to early exit to avoid an infinite loop
const targetWidth = `${state.rects.reference.width}px`;
if (state.styles.popper.width === targetWidth) {
return;
}
state.styles.popper.width = targetWidth;
instance.update();
},
phase: "beforeWrite",
requires: ["computeStyles"],
},
],
});
}
Example #5
Source File: suggester.ts From obsidian-initiative-tracker with GNU General Public License v3.0 | 6 votes |
open(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.pushScope(this.scope);
document.body.appendChild(this.suggestEl);
this.popper = createPopper(this.inputEl, this.suggestEl, {
placement: "auto-start",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10]
}
},
{
name: "flip",
options: {
allowedAutoPlacements: ["top-start", "bottom-start"]
}
}
]
});
}
Example #6
Source File: suggester.ts From obsidian-fantasy-calendar with MIT License | 6 votes |
open(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
this.app.keymap.pushScope(this.scope);
document.body.appendChild(this.suggestEl);
this.popper = createPopper(this.inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10]
}
},
{
name: "flip",
options: {
fallbackPlacements: ["top"]
}
}
]
});
}
Example #7
Source File: index.ts From obsidian-admonition with MIT License | 6 votes |
open(): void {
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<any>this.app).keymap.pushScope(this.scope);
document.body.appendChild(this.suggestEl);
this.popper = createPopper(this.inputEl, this.suggestEl, {
placement: "bottom-start",
modifiers: [
{
name: "offset",
options: {
offset: [0, 10]
}
},
{
name: "flip",
options: {
fallbackPlacements: ["top"]
}
}
]
});
}
Example #8
Source File: useOverlayPosition.ts From kodiak-ui with MIT License | 6 votes |
export function useOverlayPosition(
{
isVisible,
placement = 'bottom-start',
offset = [0, 8],
}: UseOverlayPositionProps,
triggerRef,
overlayRef,
) {
useLayoutEffect(() => {
if (!isVisible || !triggerRef || !overlayRef) {
return
}
const popperInstance = createPopper(
triggerRef?.current,
overlayRef?.current,
{
placement,
modifiers: [{ name: 'offset', options: { offset } }],
},
)
return () => {
popperInstance.destroy()
}
}, [isVisible, offset, overlayRef, placement, triggerRef])
return null
}
Example #9
Source File: tooltip.ts From rabbit-ui with MIT License | 6 votes |
export function _newCreatePopper(
reference: Element,
popper: HTMLElement,
placement: string | any,
offset: number
): any {
return createPopper(reference, popper, {
placement: placement, // 设置位置
modifiers: [
{
name: 'computeStyles',
options: {
gpuAcceleration: false // 使用top/left属性。否则会和弹出器动画冲突
}
},
{
name: 'computeStyles',
options: {
adaptive: false // 避免重新计算弹出器位置从而造成位置牛头不对马嘴
}
},
{
name: 'offset',
options: {
offset: [offset] // 自定义弹出器出现位置的偏移量
}
}
]
});
}
Example #10
Source File: livePreview.tsx From roam-toolkit with MIT License | 6 votes |
private makePopper(target: HTMLElement) {
this.popper = createPopper(target, this.iframe, {
placement: 'right',
modifiers: [
{
name: 'preventOverflow',
options: {
padding: {top: 48},
},
},
{
name: 'flip',
options: {
boundary: document.querySelector('#app'),
},
},
],
})
}
Example #11
Source File: Popup.tsx From aria-devtools with MIT License | 5 votes |
Popup = ({
onMouseOver,
onMouseLeave,
children,
targetElementRef
}) => {
const popupContainer = React.useContext(PopupContext);
const popupWrapper = React.useRef(null);
React.useEffect(() => {
const instance = createPopper(
targetElementRef.current,
popupWrapper.current,
{
placement: "auto",
modifiers: [
{
name: "arrow",
options: {
padding: 30
}
}
]
}
);
return () => {
instance.destroy();
};
}, [targetElementRef.current, popupContainer.current]);
if (!popupContainer.current) return null;
return ReactDOM.createPortal(
<PopupDiv
onMouseOver={onMouseOver}
onMouseLeave={onMouseLeave}
ref={popupWrapper}
>
<div data-popper-arrow={true} />
<div data-popper-content={true}>{children}</div>
</PopupDiv>,
popupContainer.current
);
}
Example #12
Source File: dropdown.directive.ts From sba-angular with MIT License | 5 votes |
show(usePopper = false) {
if (!this.dropdownToggle || !this.dropdownMenu || !this.dropdown) {
return;
}
if (/*TODO element.disabled || */
this.dropdownToggle.classList.contains(gClassName.DISABLED) ||
this.dropdownMenu.classList.contains(gClassName.SHOW)) {
return;
}
const parent = this.dropdown;
// Disable totally Popper.js for Dropdown in Navbar
if (!this.inNavbar || usePopper) {
let referenceElement = this.dropdownToggle;
if (gConfig.reference === 'parent') {
referenceElement = parent;
}
// If boundary is not `scrollParent`, then set position to `static`
// to allow the menu to "escape" the scroll parent's boundaries
// https://github.com/twbs/bootstrap/issues/24251
if (gConfig.boundary !== 'clippingParents') {
parent.classList.add(gClassName.POSITION_STATIC);
}
this.popper = createPopper(referenceElement, this.dropdownMenu, this.getPopperConfig());
}
// If this is a touch-enabled device we add extra
// empty mouseover listeners to the body's immediate children;
// only needed because of broken event delegation on iOS
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
if ('ontouchstart' in document.documentElement &&
!parent.closest(gSelector.NAVBAR_NAV)) {
Array.from(document.body.children).forEach((element) => element.addEventListener('mouseover', noop));
}
this.dropdownToggle.focus();
this.dropdownToggle.setAttribute('aria-expanded', "true");
this.dropdownMenu.classList.toggle(gClassName.SHOW);
parent.classList.toggle(gClassName.SHOW);
}
Example #13
Source File: insertionLine.ts From starboard-notebook with Mozilla Public License 2.0 | 5 votes |
firstUpdated() {
this.insertPosition = this.classList.contains("insertion-line-top") ? "before" : "after";
if (!globalCellTypePicker) {
// TODO: Flow runtime into this some nicer way.
globalCellTypePicker = new CellTypePicker((window as any).runtime);
}
this.classList.add("line-grid");
let unpop: () => void;
let lastActive: number;
let popoverIsActive = false;
// TODO: refactor into separate function (and maybe find a way to detect "out of bounds" click in a nicer way)
if (this.buttonElement !== undefined) {
const btn = this.buttonElement;
this.buttonElement.addEventListener("click", (_: MouseEvent) => {
if (popoverIsActive) return;
this.appendChild(globalCellTypePicker);
lastActive = Date.now();
const listener = (evt: MouseEvent) => {
const isClickInside = globalCellTypePicker.contains(evt.target as any);
if (!isClickInside) {
unpop();
}
};
unpop = () => {
// Clean up the overlay
if (Date.now() - lastActive < 100) {
return;
}
popoverIsActive = false;
pop.destroy();
globalCellTypePicker.remove();
document.removeEventListener("click", listener);
};
document.addEventListener("click", listener);
const pop = createPopper(btn, globalCellTypePicker, {
placement: "right-start",
strategy: "fixed",
});
const parent = this.parentElement;
if (parent && parent instanceof CellElement) {
globalCellTypePicker.setHighlightedCellType(parent.cell.cellType);
}
globalCellTypePicker.onInsert = (cellData: Partial<Cell>) => {
// Right now we assume the insertion line has a cell as parent
if (parent && parent instanceof CellElement) {
this.runtime.controls.insertCell({
adjacentCellId: parent.cell.id,
position: this.insertPosition,
data: cellData,
});
unpop();
}
};
popoverIsActive = true;
});
}
}
Example #14
Source File: tooltip.tsx From nota with MIT License | 4 votes |
Tooltip = observer(({ children: Inner, Popup }: TooltipProps) => {
const [referenceElement, setReferenceElement] = useState<HTMLElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLElement | null>(null);
const [arrowElement, setArrowElement] = useState<HTMLElement | null>(null);
const [instance, setInstance] = useState<Instance | null>(null);
let ctx = usePlugin(TooltipPlugin);
let [id] = useState(_.uniqueId());
let [stage, setStage] = useState("start");
let [show, setShow] = useState(false);
let trigger: React.MouseEventHandler = e => {
e.preventDefault();
if (show) {
setShow(false);
} else {
if (stage == "start") {
setStage("mount");
}
ctx.queueUpdate(id);
}
};
useEffect(() => {
if (stage == "mount" && referenceElement && popperElement) {
setStage("done");
// TODO: I noticed that when embedding a document within another,
// the nested-document tooltips on math elements would be misaligned.
// Unclear why, but a fix was to use the popperjs "virtual element"
// feature that manually calls getBoundingClientRect(). Probably an issue
// with whatever their getBoundingClientRect alternative is?
let popperRefEl = {
getBoundingClientRect: () => referenceElement.getBoundingClientRect(),
};
let instance = createPopper(popperRefEl, popperElement, {
placement: "top",
modifiers: [
// Push tooltip farther away from content
{ name: "offset", options: { offset: [0, 10] } },
// Add arrow
{ name: "arrow", options: { element: arrowElement } },
],
});
setInstance(instance);
ctx.elts[id] = {
popperElement,
referenceElement: popperRefEl,
instance,
setShow,
};
ctx.checkQueue();
}
}, [stage, referenceElement, popperElement]);
let inner = isConstructor(Inner) ? (
<Inner ref={setReferenceElement} onClick={trigger} />
) : (
<Container ref={setReferenceElement} onClick={trigger}>
{Inner}
</Container>
);
return (
<>
{inner}
{stage != "start" ? (
<ToplevelElem>
<div
className="tooltip"
ref={setPopperElement}
style={
{
...(stage == "done" ? instance!.state.styles.popper : {}),
// Have to use visibility instead of display so tooltips can
// correctly compute position for stacking
visibility: show ? "visible" : "hidden",
} as any
}
{...(stage == "done" ? instance!.state.attributes.popper : {})}
>
<div
className="arrow"
ref={setArrowElement}
style={{
// Can't use visibility here b/c it messes with the special CSS for arrows
display: show ? "block" : "none",
}}
/>
{getOrRender(Popup, {})}
</div>
</ToplevelElem>
) : null}
</>
);
})
Example #15
Source File: useMenu.ts From kodiak-ui with MIT License | 4 votes |
export function useMenu({
placement = 'bottom-start',
offset = [0, 8],
}: UseMenuProps = {}): UseMenuReturnValue {
const buttonRef = React.useRef<HTMLButtonElement | null>(null)
const menuRef = React.useRef<HTMLUListElement>()
const itemsRef = React.useRef<{ [key: string]: HTMLLIElement | Element }>({})
const itemHandlersRef = React.useRef<{
[key: string]: ((event: any) => void) | undefined
}>({})
const elementIds = React.useRef<ElementIds>(generateElementIds())
const popperInstanceRef = React.useRef<any | null>(null)
const [activeItem, setActiveItem] = React.useState('')
const {
isOpen: isExpanded,
handleOpenPortal,
handleClosePortal,
Portal,
} = usePortal()
useIsomorphicLayoutEffect(
function initializePopper() {
if (!isExpanded && (!buttonRef.current || !menuRef.current)) {
return
}
const popperInstance = createPopper(
buttonRef.current as Element | VirtualElement,
menuRef.current as HTMLElement,
{
placement,
modifiers: [{ name: 'offset', options: { offset } }],
},
)
popperInstanceRef.current = popperInstance
return () => {
popperInstance.destroy()
popperInstanceRef.current = null
}
},
[isExpanded, placement, offset],
)
function getItemNodeFromIndex(index: number): HTMLLIElement | Element | null {
return itemsRef && itemsRef.current && itemsRef.current[index]
}
function focusMenuRef() {
menuRef && menuRef.current && menuRef.current.focus()
}
function focusButtonRef() {
buttonRef && buttonRef.current && buttonRef.current.focus()
}
useKey({
key: 'Escape',
target: menuRef.current,
handler: () => {
if (isExpanded) {
handleClosePortal({})
}
focusButtonRef()
},
})
useKey({
key: 'ArrowDown',
target: menuRef.current,
handler: () => {
const nextItem = getNextItem({
moveAmount: 1,
baseIndex: Object.keys(itemsRef.current).indexOf(activeItem),
items: itemsRef.current,
getItemNodeFromIndex,
})
setActiveItem(nextItem)
setAttributes(itemsRef.current[nextItem], {
'aria-selected': 'true',
})
},
})
useKey({
key: 'ArrowUp',
target: menuRef.current,
handler: () => {
const nextItem = getNextItem({
moveAmount: -1,
baseIndex: Object.keys(itemsRef.current).indexOf(activeItem),
items: itemsRef.current,
getItemNodeFromIndex,
})
setActiveItem(nextItem)
setAttributes(itemsRef.current[nextItem], {
'aria-selected': 'true',
})
},
})
useKey({
key: 'Enter',
target: menuRef.current,
handler: () => {
const handler = itemHandlersRef.current[activeItem] as () => void
handler?.()
},
})
useOnClickOutside({
ref: menuRef as React.MutableRefObject<Element>,
refException: buttonRef as React.MutableRefObject<Element>,
handler: () => {
handleClosePortal({})
},
})
React.useEffect(() => {
if (isExpanded) {
focusMenuRef()
}
}, [isExpanded])
function registerButtonElement({
ref,
options,
}: RefAndOptions<Element>): RefAndOptions<Element> {
if (ref && ref.tagName === MenuElementTagNames.Button) {
buttonRef.current = ref as HTMLButtonElement
setAttributes(buttonRef.current, {
id: elementIds.current.buttonId,
'aria-haspopup': 'true',
'aria-controls': 'IDREF',
})
}
return { ref, options }
}
function registerMenuElement({
ref,
options,
}: RefAndOptions<Element>): RefAndOptions<Element> {
if (ref && ref.tagName === MenuElementTagNames.Ul) {
menuRef.current = ref as HTMLUListElement
setAttributes(menuRef.current, {
id: elementIds.current.menuId,
role: 'menu',
tabIndex: '-1',
'aria-labelledby':
buttonRef && buttonRef.current ? `${buttonRef.current.id}` : '',
})
}
return { ref, options }
}
function registerMenuItemElement({
ref,
options,
}: RefAndOptions<Element>): RefAndOptions<Element> {
if (ref && ref.tagName === MenuElementTagNames.Li) {
const name = options && options.name
const handler = options && options.handler
const items = itemsRef.current
const itemHandlers = itemHandlersRef.current
items[name as string] = ref
itemHandlers[name as string] = handler
const item = items[name as string]
setAttributes(item, {
id: elementIds.current.getItemId(name as string),
role: 'option',
'aria-selected': 'false',
})
}
return { ref, options }
}
const registerElementRefs = React.useCallback(function registerElementRefs(
ref: Element | null,
options?: RegisterOptions,
): RefAndOptions<Element> {
return registerMenuItemElement(
registerMenuElement(registerButtonElement({ ref, options })),
)
},
[])
/**
* Register the menu elements
*
* Allows the ability to add the appropriate HTML attributes
* to an HTML element.
*/
const register = React.useCallback(
function register(
ref: (HTMLButtonElement | HTMLUListElement | HTMLLIElement) | null,
options?: RegisterOptions,
): RefAndOptions<Element> | null {
return ref && registerElementRefs(ref, options)
},
[registerElementRefs],
)
const handleToggleMenu = React.useCallback(
function handleToggleMenu(event) {
setAttributes(buttonRef && (buttonRef.current as Element | null), {
'aria-expanded': `${!isExpanded}`,
})
isExpanded ? handleClosePortal(event) : handleOpenPortal(event)
},
[isExpanded, handleOpenPortal, handleClosePortal],
)
const handleCloseMenu = React.useCallback(
function handleCloseMenu() {
setAttributes(buttonRef && (buttonRef.current as Element | null), {
'aria-expanded': 'false',
})
handleClosePortal({})
},
[handleClosePortal],
)
function getItemProps(name: string) {
return {
onClick: (event: React.MouseEvent<any, MouseEvent>) => {
// TS doesn't like this itemHandlersRef.current[name] && itemHandlersRef.current[name](event)
// this will work when the bundler supports conditional chaining itemHandlersRef.current[name]?.(event)
const itemHandler = itemHandlersRef.current[name]
itemHandler && itemHandler(event)
},
onMouseEnter: () => setActiveItem(name),
}
}
return {
register,
isExpanded,
activeItem,
handleToggleMenu,
handleCloseMenu,
getItemProps,
Menu: Portal,
}
}
Example #16
Source File: useTooltip.ts From kodiak-ui with MIT License | 4 votes |
export function useTooltip({
placement = 'top',
offset = [0, 10],
closeTimeout = 0,
}: UseTooltipProps = {}): UseTooltipReturn {
const triggerRef = React.useRef<HTMLElement | null>(null)
const tooltipRef = React.useRef<HTMLElement | null>(null)
const arrowRef = React.useRef<HTMLElement | null>(null)
const popperInstanceRef = React.useRef<any>(null)
const timeoutIdRef = React.useRef<any>(null)
const id = useId()
const {
isOpen: isVisible,
handleOpenPortal,
handleClosePortal: instantlyClosePortal,
Portal,
portalRef,
} = usePortal()
const delayedClosePortal = React.useCallback(
function delayedClosePortal(event) {
clearTimeout(timeoutIdRef.current)
if (closeTimeout === 0) {
instantlyClosePortal(event)
} else {
timeoutIdRef.current = setTimeout(() => {
return instantlyClosePortal(event)
}, closeTimeout)
}
},
[instantlyClosePortal, closeTimeout],
)
React.useLayoutEffect(
function initializePopper() {
if (!isVisible && (!triggerRef.current || !tooltipRef.current)) {
return
}
const popperInstance = createPopper(
triggerRef.current as Element | VirtualElement,
tooltipRef.current as HTMLElement,
{
placement,
modifiers: [
offset ? { name: 'offset', options: { offset } } : {},
arrowRef && arrowRef.current
? {
name: 'arrow',
options: { element: arrowRef && arrowRef.current },
}
: {},
],
},
)
popperInstanceRef.current = popperInstance
return () => {
popperInstance.destroy()
popperInstanceRef.current = null
}
},
[isVisible, offset, placement],
)
useOnClickOutside({
ref: portalRef as React.MutableRefObject<Element>,
refException: triggerRef as React.MutableRefObject<Element>,
handler: () => {
instantlyClosePortal({})
},
})
useKey({
key: 'Escape',
target: triggerRef.current,
handler: () => {
if (isVisible) {
instantlyClosePortal({})
}
},
})
function registerTriggerElement({
ref,
options,
}: RefAndOptions): RefAndOptions {
if (ref && options && options.trigger) {
triggerRef.current = ref
setAttributes(triggerRef.current, {
'aria-describedby': `kodiak-ui-tooltip-${id}`,
})
}
return { ref, options }
}
function registerTooltipElement({
ref,
options,
}: RefAndOptions): RefAndOptions {
if ((ref && !options) || (options && !options.trigger && !options.arrow)) {
tooltipRef.current = ref
setAttributes(tooltipRef.current, {
id: `kodiak-ui-tooltip-${id}`,
role: 'tooltip',
})
}
return { ref, options }
}
function registerArrowElement({
ref,
options,
}: RefAndOptions): RefAndOptions {
if (ref && options && options.arrow) {
arrowRef.current = ref
}
return { ref, options }
}
function register(
ref: TooltipRef,
options?: RegisterOptions,
): {
ref: TooltipRef
options?: RegisterOptions
} {
return registerArrowElement(
registerTriggerElement(registerTooltipElement({ ref, options })),
)
}
const getTriggerProps = React.useCallback(
function getTriggerProps() {
return {
onFocus: handleOpenPortal,
onBlur: delayedClosePortal,
onMouseEnter: handleOpenPortal,
onMouseLeave: delayedClosePortal,
}
},
[handleOpenPortal, delayedClosePortal],
)
return {
isVisible,
register,
getTriggerProps,
Portal,
}
}
Example #17
Source File: popper.ts From ExpressiveAnimator with Apache License 2.0 | 4 votes |
export function popperAction(trigger: HTMLElement, {content, options, closeOnClickOutside, update}) {
let instance = null;
let open: boolean = false;
const clickOutside = e => {
if (instance && open && !trigger.contains(e.target)) {
hide();
}
};
const toggle = () => {
if (!open) {
show();
} else {
hide();
}
}
const show = () => {
if (instance) {
instance.setOptions(options);
instance.update();
} else {
instance = createPopper(trigger, content(), options);
}
if (closeOnClickOutside) {
outsideEventHandler(clickOutside, true);
}
open = true;
update(true);
}
const hide = () => {
if (instance) {
instance.setOptions(hideOptions);
}
if (closeOnClickOutside) {
outsideEventHandler(clickOutside, false);
}
open = false;
update(false);
}
trigger.addEventListener('popper-show', show);
trigger.addEventListener('popper-hide', hide);
trigger.addEventListener('popper-toggle', toggle);
return {
update(params) {
if (!params) {
return;
}
let force = false;
if (params?.options) {
options = params.options;
force = true;
}
if (params?.closeOnClickOutside !== closeOnClickOutside) {
closeOnClickOutside = !!params?.closeOnClickOutside;
outsideEventHandler(clickOutside, closeOnClickOutside);
}
if (params?.update) {
update = params?.update;
}
if (params?.content !== content) {
content = params.content;
if (instance) {
instance.destroy();
if (open) {
instance = createPopper(trigger, content(), options);
force = false;
}
}
}
if (force && open && instance) {
instance.setOptions(options);
instance.update();
}
},
destroy() {
if (instance) {
instance.destroy();
instance = null;
}
trigger.removeEventListener('popper-show', show);
trigger.removeEventListener('popper-hide', hide);
trigger.removeEventListener('popper-toggle', toggle);
if (closeOnClickOutside) {
outsideEventHandler(clickOutside, false);
}
}
};
}
Example #18
Source File: Root.tsx From houston with MIT License | 4 votes |
PopoverRoot: React.FC<IPopoverProps> = ({ children }) => {
const [state, setState] = React.useState<IState>({
opened: false,
target: null,
content: null,
closedTarget: null,
timestamp: 0,
placement: 'auto'
});
React.useEffect(() => {
if (!state.opened) {
state.content?.classList?.remove('--opened');
return undefined;
}
const instance = createPopper(state.target, state.content, {
placement: state.placement ?? 'auto',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8]
}
}
]
});
state.content?.classList?.add('--opened');
return () => {
state.content?.classList?.remove('--opened');
setTimeout(() => instance.destroy(), 100);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.opened]);
useOnClickOutside(
state.content,
() => {
const justOpened = (Date.now() - state.timestamp ?? 0) < 100;
if (!state.opened || justOpened) return;
setState(currentState => ({ ...currentState, opened: false, closedTarget: currentState.target }));
setTimeout(() => {
setState(currentState => ({ ...currentState, closedTarget: null }));
}, 300);
},
[state.opened]
);
const contextSetValue = React.useCallback<IPopoverContext['setState']>(newState => {
setTimeout(() => {
const resolveNewState = (currentState: IState) => ({
...currentState,
...newState,
timestamp: Date.now(),
opened: currentState.closedTarget === newState.target ? false : true
});
setState(currentState => {
const waitPreviousclose = !!currentState.closedTarget;
if (waitPreviousclose) {
setTimeout(() => setState(resolveNewState), 100);
return currentState;
}
return resolveNewState(currentState);
});
}, 0);
return () => {
setState(currentState => {
if (currentState.target !== newState.target) return currentState;
return { ...currentState, opened: false };
});
};
}, []);
const contextValue = React.useMemo<IPopoverContext>(
() => ({ setState: contextSetValue, openedTarget: state.opened ? state.target : null }),
[contextSetValue, state.opened, state.target]
);
return <PopoverContext.Provider value={contextValue}>{children}</PopoverContext.Provider>;
}
Example #19
Source File: index.ts From elenext with MIT License | 4 votes |
usePopper = (props: UsePopperOptions) => {
const popperId = uniqueId('el-popper')
const { referenceRef, popperRef } = props
const timers: {
showTimer: any
hideTimer: any
} = { showTimer: undefined, hideTimer: undefined }
const state = reactive<usePopperState>({
instance: null,
popperId,
attrs: {
styles: {
popper: {
position: 'absolute',
left: '0',
top: '0',
},
arrow: {
position: 'absolute',
},
},
attributes: {},
},
})
const popperOptions = computed<PopperOptions>(() => {
return {
placement: props.placement || 'bottom-start',
strategy: 'absolute',
modifiers: [
{
name: 'updateState',
enabled: true,
phase: 'write',
fn: ({ state: popperState }: any) => {
const elements = Object.keys(popperState.elements)
state.attrs = {
styles: fromEntries(elements.map(element => [element, popperState.styles[element] || {}])),
attributes: fromEntries(elements.map(element => [element, popperState.attributes[element] || {}])),
}
},
requires: ['computeStyles'],
},
{ name: 'applyStyles', enabled: false },
{ name: 'offset', options: { offset: [0, props.offset || 0] } },
],
}
})
const clearScheduled = () => {
clearTimeout(timers.hideTimer)
clearTimeout(timers.showTimer)
}
let clickEvent: any = null
const togglePopper = (event: MouseEvent) => {
clickEvent = event
props.onTrigger(popperId)
}
const showPopper = () => {
clearScheduled()
timers.showTimer = setTimeout(() => {
props.onTrigger(popperId, true)
}, 0)
}
const hidePopper = () => {
clearScheduled()
timers.hideTimer = setTimeout(() => {
props.onTrigger(popperId, false)
}, props.hideDaly || 200)
}
const outSideClickHandler = (event: MouseEvent) => {
// outSideClick 和 togglePopper 冲突
if (event === clickEvent) {
return
}
if (popperRef.value && !popperRef.value.contains(event.target as Node)) {
if (
['hover', 'focus'].indexOf(props.trigger) !== -1 &&
referenceRef.value &&
referenceRef.value.contains(event.target as Node)
) {
return
} else {
hidePopper()
}
}
}
const eventRegOrUnReg = isReg => {
const referenceEl = referenceRef.value
const popperEl = popperRef.value
const event = isReg ? 'addEventListener' : 'removeEventListener'
if (referenceEl && popperEl) {
if (props.trigger === 'hover') {
referenceEl[event]('mouseenter', showPopper)
referenceEl[event]('mouseleave', hidePopper)
popperEl[event]('mouseenter', showPopper)
popperEl[event]('mouseleave', hidePopper)
}
if (props.trigger === 'click') {
referenceEl[event]('click', togglePopper)
// popperEl[event]('mouseenter', showPopper)
// popperEl[event]('mouseleave', hidePopper)
}
if (props.trigger === 'focus') {
referenceEl[event]('focus', showPopper)
referenceEl[event]('blur', hidePopper)
}
if (props.trigger !== 'manual') {
document[event]('click', outSideClickHandler)
}
}
}
watchEffect(() => {
if (state.instance) {
state.instance.setOptions(popperOptions.value)
}
})
watch([referenceRef, popperRef], () => {
const referenceEl = referenceRef.value
const popperEl = popperRef.value
if (referenceEl && popperEl) {
if (state.instance) {
state.instance.destroy()
}
state.instance = createPopper(referenceEl, popperEl as HTMLElement, popperOptions.value)
}
})
watchEffect(onInvalidate => {
const referenceEl = referenceRef.value
const popperEl = popperRef.value
onInvalidate(() => {
eventRegOrUnReg(false)
})
if (referenceEl && popperEl) {
eventRegOrUnReg(true)
}
})
onUnmounted(() => {
if (state.instance) {
state.instance.destroy()
}
})
return state
}