mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-18 18:41:52 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import {
|
||||
arrow,
|
||||
type ComputePositionReturn,
|
||||
flip,
|
||||
offset,
|
||||
type Placement,
|
||||
} from '@floating-ui/dom';
|
||||
import type { CSSResult } from 'lit';
|
||||
import { css, html, LitElement, unsafeCSS } from 'lit';
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { HoverController, type HoverOptions } from '../hover/index.js';
|
||||
|
||||
const styles = css`
|
||||
.affine-tooltip {
|
||||
box-sizing: border-box;
|
||||
max-width: 280px;
|
||||
min-height: 32px;
|
||||
font-family: var(--affine-font-family);
|
||||
font-size: var(--affine-font-sm);
|
||||
border-radius: 4px;
|
||||
padding: 6px 12px;
|
||||
color: var(--affine-white);
|
||||
background: var(--affine-tooltip);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
position: absolute;
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
// See http://apps.eky.hk/css-triangle-generator/
|
||||
const TRIANGLE_HEIGHT = 6;
|
||||
const triangleMap = {
|
||||
top: {
|
||||
bottom: '-6px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '6px 5px 0 5px',
|
||||
borderColor: 'var(--affine-tooltip) transparent transparent transparent',
|
||||
},
|
||||
right: {
|
||||
left: '-6px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '5px 6px 5px 0',
|
||||
borderColor: 'transparent var(--affine-tooltip) transparent transparent',
|
||||
},
|
||||
bottom: {
|
||||
top: '-6px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '0 5px 6px 5px',
|
||||
borderColor: 'transparent transparent var(--affine-tooltip) transparent',
|
||||
},
|
||||
left: {
|
||||
right: '-6px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '5px 0 5px 6px',
|
||||
borderColor: 'transparent transparent transparent var(--affine-tooltip)',
|
||||
},
|
||||
};
|
||||
|
||||
// Ported from https://floating-ui.com/docs/tutorial#arrow-middleware
|
||||
const updateArrowStyles = ({
|
||||
placement,
|
||||
middlewareData,
|
||||
}: ComputePositionReturn): StyleInfo => {
|
||||
const arrowX = middlewareData.arrow?.x;
|
||||
const arrowY = middlewareData.arrow?.y;
|
||||
|
||||
const triangleStyles =
|
||||
triangleMap[placement.split('-')[0] as keyof typeof triangleMap];
|
||||
|
||||
return {
|
||||
left: arrowX != null ? `${arrowX}px` : '',
|
||||
top: arrowY != null ? `${arrowY}px` : '',
|
||||
...triangleStyles,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* // Simple usage
|
||||
* html`
|
||||
* <affine-tooltip>Content</affine-tooltip>
|
||||
* `
|
||||
* // With placement
|
||||
* html`
|
||||
* <affine-tooltip tip-position="top">
|
||||
* Content
|
||||
* </affine-tooltip>
|
||||
* `
|
||||
*
|
||||
* // With custom properties
|
||||
* html`
|
||||
* <affine-tooltip
|
||||
* .zIndex=${0}
|
||||
* .offset=${4}
|
||||
* .autoFlip=${true}
|
||||
* .arrow=${true}
|
||||
* .tooltipStyle=${css`:host { z-index: 0; --affine-tooltip: #fff; }`}
|
||||
* .allowInteractive=${false}
|
||||
* >
|
||||
* Content
|
||||
* </affine-tooltip>
|
||||
* `
|
||||
* ```
|
||||
*/
|
||||
export class Tooltip extends LitElement {
|
||||
static override styles = css`
|
||||
:host {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
private _hoverController!: HoverController;
|
||||
|
||||
private _setUpHoverController = () => {
|
||||
this._hoverController = new HoverController(
|
||||
this,
|
||||
() => {
|
||||
// const parentElement = this.parentElement;
|
||||
// if (
|
||||
// parentElement &&
|
||||
// 'disabled' in parentElement &&
|
||||
// parentElement.disabled
|
||||
// )
|
||||
// return null;
|
||||
if (this.hidden) return null;
|
||||
let arrowStyles: StyleInfo = {};
|
||||
return {
|
||||
template: ({ positionSlot, updatePortal }) => {
|
||||
positionSlot.on(data => {
|
||||
// The tooltip placement may change,
|
||||
// so we need to update the arrow position
|
||||
if (this.arrow) {
|
||||
arrowStyles = updateArrowStyles(data);
|
||||
} else {
|
||||
arrowStyles = {};
|
||||
}
|
||||
updatePortal();
|
||||
});
|
||||
|
||||
const children = Array.from(this.childNodes).map(node =>
|
||||
node.cloneNode(true)
|
||||
);
|
||||
|
||||
return html`
|
||||
<style>
|
||||
${this._getStyles()}
|
||||
</style>
|
||||
<div class="affine-tooltip" role="tooltip">${children}</div>
|
||||
<div class="arrow" style=${styleMap(arrowStyles)}></div>
|
||||
`;
|
||||
},
|
||||
computePosition: portalRoot => ({
|
||||
referenceElement: this.parentElement!,
|
||||
placement: this.placement,
|
||||
middleware: [
|
||||
this.autoFlip && flip({ padding: 12 }),
|
||||
offset((this.arrow ? TRIANGLE_HEIGHT : 0) + this.offset),
|
||||
arrow({
|
||||
element: portalRoot.shadowRoot!.querySelector('.arrow')!,
|
||||
}),
|
||||
],
|
||||
autoUpdate: true,
|
||||
}),
|
||||
};
|
||||
},
|
||||
{
|
||||
leaveDelay: 0,
|
||||
// The tooltip is not interactive by default
|
||||
safeBridge: false,
|
||||
allowMultiple: true,
|
||||
...this.hoverOptions,
|
||||
}
|
||||
);
|
||||
|
||||
const parent = this.parentElement;
|
||||
assertExists(parent, 'Tooltip must have a parent element');
|
||||
|
||||
// Wait for render
|
||||
setTimeout(() => {
|
||||
this._hoverController.setReference(parent);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
private _getStyles() {
|
||||
return css`
|
||||
${styles}
|
||||
:host {
|
||||
z-index: ${unsafeCSS(this.zIndex)};
|
||||
opacity: 0;
|
||||
${
|
||||
// All the styles are applied to the portal element
|
||||
unsafeCSS(this.style.cssText)
|
||||
}
|
||||
}
|
||||
|
||||
${this.allowInteractive
|
||||
? css``
|
||||
: css`
|
||||
:host {
|
||||
pointer-events: none;
|
||||
}
|
||||
`}
|
||||
|
||||
${this.tooltipStyle}
|
||||
`;
|
||||
}
|
||||
|
||||
override connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this._setUpHoverController();
|
||||
}
|
||||
|
||||
getPortal() {
|
||||
return this._hoverController.portal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the tooltip to be interactive.
|
||||
* eg. allow the user to select text in the tooltip.
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
accessor allowInteractive = false;
|
||||
|
||||
/**
|
||||
* Show a triangle arrow pointing to the reference element.
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
accessor arrow = true;
|
||||
|
||||
/**
|
||||
* changes the placement of the floating element in order to keep it in view,
|
||||
* with the ability to flip to any placement.
|
||||
*
|
||||
* See https://floating-ui.com/docs/flip
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
accessor autoFlip = true;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor hoverOptions: Partial<HoverOptions> = {};
|
||||
|
||||
/**
|
||||
* Default is `4px`
|
||||
*
|
||||
* See https://floating-ui.com/docs/offset
|
||||
*/
|
||||
@property({ attribute: false })
|
||||
accessor offset = 4;
|
||||
|
||||
@property({ attribute: 'tip-position' })
|
||||
accessor placement: Placement = 'top';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor tooltipStyle: CSSResult = css``;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor zIndex: number | string = 'var(--affine-z-index-popover)';
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'affine-tooltip': Tooltip;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user