@blueprintjs/core
Version:
Core styles & components
107 lines (96 loc) • 3.94 kB
text/typescript
/*
* Copyright 2015 Palantir Technologies, Inc. All rights reserved.
* Licensed under the BSD-3 License as modified (the “License”); you may obtain a copy
* of the license at https://github.com/palantir/blueprint/blob/master/LICENSE
* and https://github.com/palantir/blueprint/blob/master/PATENTS
*/
// TODO: shim for new option added in Tether 1.4.0
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/13142
declare module "tether" {
interface ITetherOptions {
bodyElement?: HTMLElement;
}
}
import * as Tether from "tether";
import { Position } from "./position";
// per https://github.com/HubSpot/tether/pull/204, Tether now exposes a `bodyElement` option that,
// when present, gets the tethered element injected into *it* instead of into the document body.
// but both approaches still cause React to freak out, because it loses its handle on the DOM
// element. thus, we pass a fake HTML bodyElement to Tether, with a no-op `appendChild` function
// (the only function the library uses from bodyElement).
const fakeHtmlElement = ({
appendChild : () => { /* No-op */ },
} as any) as HTMLElement;
export interface ITetherConstraint {
attachment?: string;
outOfBoundsClass?: string;
pin?: boolean | string[];
pinnedClass?: string;
to?: string | HTMLElement | number[];
}
/** @internal */
export function createTetherOptions(
element: Element,
target: Node,
position: Position,
tetherOptions: Partial<Tether.ITetherOptions> = {},
): Tether.ITetherOptions {
return {
...tetherOptions,
attachment: getPopoverAttachment(position),
bodyElement: fakeHtmlElement,
classPrefix: "pt-tether",
element,
target,
targetAttachment: getTargetAttachment(position),
};
}
/** @internal */
export function getTargetAttachment(position: Position) {
const attachments: {[p: number]: string} = {
[ ]: "top left",
[ ]: "top center",
[ ]: "top right",
[ ]: "top right",
[ ]: "middle right",
[ ]: "bottom right",
[ ]: "bottom right",
[ ]: "bottom center",
[ ]: "bottom left",
[ ]: "bottom left",
[ ]: "middle left",
[ ]: "top left",
};
return attachments[position];
}
/** @internal */
export function getPopoverAttachment(position: Position) {
const attachments: {[p: number]: string} = {
[ ]: "bottom left",
[ ]: "bottom center",
[ ]: "bottom right",
[ ]: "top left",
[ ]: "middle left",
[ ]: "bottom left",
[ ]: "top right",
[ ]: "top center",
[ ]: "top left",
[ ]: "bottom right",
[ ]: "middle right",
[ ]: "top right",
};
return attachments[position];
}
/** @internal */
export function getAttachmentClasses(position: Position) {
// this essentially reimplements the Tether logic for attachment classes so the same styles
// can be reused outside of Tether-based popovers.
return [
...expandAttachmentClasses(getPopoverAttachment(position), "pt-tether-element-attached"),
...expandAttachmentClasses(getTargetAttachment(position), "pt-tether-target-attached"),
];
}
function expandAttachmentClasses(attachments: string, prefix: string) {
const [verticalAlign, horizontalAlign] = attachments.split(" ");
return [`${prefix}-${verticalAlign}`, `${prefix}-${horizontalAlign}`];
}