omii-ui
Version:
使用OMII开发的WebComponents库,样式采用bootstrap。可以作为标准的esmodule使用
185 lines (180 loc) • 5.17 kB
JSX
const { h, classNames, createRef } = omii;
import { computePosition, flip, arrow, shift, offset } from "@floating-ui/dom";
import uiBase from "../uiBase";
import { getCSSStyleSheets } from "../css";
import css from "./index.scss";
import effect from "../effect";
export default class extends uiBase {
static css = [
() => getCSSStyleSheets("reboot", "utilities", "transitions", "popover"),
css,
];
static defaultProps = {
header: null,
body: null,
reference: null, //哪個對象?
strategy: "fixed", //'absolute' | 'fixed';
placement: "bottom",
// | 'top'
// | 'top-start'
// | 'top-end'
// | 'right'
// | 'right-start'
// | 'right-end'
// | 'bottom'
// | 'bottom-start'
// | 'bottom-end'
// | 'left'
// | 'left-start'
// | 'left-end';
triggerShow: "mouseenter focus", //mouseenter focus click
triggerHide: "mouseleave blur", //mouseleave blur click
offset: 6,
};
static propTypes = {
strategy: String,
placement: String,
trigger: String,
offset: Number,
};
get type() {
return "popover";
}
get $element() {
return this.$(`.${this.type}`);
}
slotRef = createRef();
get #$slot() {
return this.slotRef.current;
}
get #$reference() {
//检查slot下面有元素吗?使用第一个元素作为参考元素
let assignedElements = this.#$slot.assignedElements();
if (assignedElements.length === 0) {
let { reference } = this.$props;
if (typeof reference == "string") {
return this.getRootNode()?.querySelector(reference);
} else {
//支持使用creatRef绑定的对象
return reference?.current ?? reference;
}
} else {
return assignedElements.at(0);
}
}
setPosition() {
let { placement, offset: _offset, strategy } = this.$props;
let reference = this.#$reference;
if (reference) {
let $arrow = this.$(`.${this.type}-arrow`);
computePosition(reference, this.$element, {
strategy,
placement,
middleware: [
flip(),
shift(),
offset(_offset),
arrow({ element: $arrow }),
],
}).then(({ x, y, placement, middlewareData }) => {
Object.assign(this.$element.style, {
left: `${x}px`,
top: `${y}px`,
});
// Accessing the data
const { x: arrowX, y: arrowY } = middlewareData.arrow;
const staticSide = {
top: "bottom",
right: "left",
bottom: "top",
left: "right",
}[placement.split("-")[0]];
$arrow &&
Object.assign($arrow.style, {
left: arrowX != null ? `${arrowX}px` : "",
top: arrowY != null ? `${arrowY}px` : "",
right: "",
bottom: "",
});
let popoverDataset = this.$element.dataset;
if (staticSide == "top") {
popoverDataset.popperPlacement = "bottom";
} else if (staticSide == "bottom") {
popoverDataset.popperPlacement = "top";
} else if (staticSide == "left") {
popoverDataset.popperPlacement = "right";
} else if (staticSide == "right") {
popoverDataset.popperPlacement = "left";
}
});
}
}
#visible = false;
show() {
this.#visible = true;
Object.assign(this.$element.style, {
display: "block",
position: this.$props.strategy,
});
this.setPosition();
effect.fade.show(this.$element);
}
hide() {
this.#visible = false;
effect.fade.hide(this.$element);
Object.assign(this.$element.style, {
display: "none",
});
}
toggle() {
if (this.#visible) this.hide();
else {
this.show();
}
}
installed() {
const reference = this.#$reference;
const { triggerShow, triggerHide } = this.$props;
if (reference) {
triggerShow.split(" ").forEach((trigger) => {
if (trigger) {
reference.addEventListener(trigger, (evt) => {
evt.preventDefault();
evt.stopPropagation();
this.show();
});
}
});
triggerHide.split(" ").forEach((trigger) => {
if (trigger) {
reference.addEventListener(trigger, (evt) => {
evt.preventDefault();
evt.stopPropagation();
if (this.#visible) this.hide();
});
}
});
}
}
render(props) {
let { placement, header, body } = this.$props;
return (
<>
<slot ref={this.slotRef} tabIndex></slot>
<div
class="popover bs-popover-auto fade"
role="tooltip"
data-popper-placement={placement}
>
<div class="popover-arrow"></div>
<slot name="header" class="popover-header">
{header}
</slot>
<slot class="popover-body" name="body">
{body}
</slot>
</div>
</>
);
}
}