import DomUtil from "../Dom/Util";
import { getPageOverlayContainer } from "../Helper/PageOverlay";
import { wheneverFirstSeen } from "../Helper/Selector";
+import RepeatingTimer from "../Timer/Repeating";
+import * as UiAlignment from "../Ui/Alignment";
+
+const enum Delay {
+ Hide = 500,
+ Show = 800,
+}
class Popover {
readonly #cache = new Map<number, string>();
readonly #endpoint: URL;
#enabled = true;
readonly #identifier: string;
+ #pendingElement: HTMLElement | undefined = undefined;
+ #pendingObjectId: number | undefined = undefined;
+ #timerStart: RepeatingTimer | undefined = undefined;
+ #timerHide: RepeatingTimer | undefined = undefined;
constructor(selector: string, endpoint: string, identifier: string) {
this.#identifier = identifier;
wheneverFirstSeen(selector, (element) => {
element.addEventListener("mouseenter", () => {
- void this.#hoverStart(element);
+ this.#hoverStart(element);
});
element.addEventListener("mouseleave", () => {
this.#hoverEnd(element);
});
}
- async #hoverStart(element: HTMLElement): Promise<void> {
+ #hoverStart(element: HTMLElement): void {
const objectId = this.#getObjectId(element);
- let content = this.#cache.get(objectId);
- if (content === undefined) {
- content = await this.#fetch(objectId);
- this.#cache.set(objectId, content);
+ this.#pendingObjectId = objectId;
+ if (this.#timerStart === undefined) {
+ this.#timerStart = new RepeatingTimer((timer) => {
+ timer.stop();
+
+ const objectId = this.#pendingObjectId!;
+ void this.#getContent(objectId).then((content) => {
+ if (objectId !== this.#pendingObjectId) {
+ return;
+ }
+
+ const container = this.#getContainer();
+ DomUtil.setInnerHtml(container, content);
+
+ UiAlignment.set(container, element, { vertical: "top" });
+
+ container.setAttribute("aria-hidden", "false");
+ });
+ }, Delay.Show);
+ } else {
+ this.#timerStart.restart();
}
+ }
+
+ #hoverEnd(element: HTMLElement): void {
+ this.#timerStart?.stop();
+ this.#pendingObjectId = undefined;
- DomUtil.setInnerHtml(this.#getContainer(), content);
+ if (this.#timerHide === undefined) {
+ this.#timerHide = new RepeatingTimer(() => {
+ // do something
+ }, Delay.Hide);
+ } else {
+ this.#timerHide.restart();
+ }
}
- #hoverEnd(element: HTMLElement): void {}
+ async #getContent(objectId: number): Promise<string> {
+ let content = this.#cache.get(objectId);
+ if (content !== undefined) {
+ return content;
+ }
- async #fetch(objectId: number): Promise<string> {
this.#endpoint.searchParams.set("id", objectId.toString());
const response = await prepareRequest(this.#endpoint).get().fetchAsResponse();
- if (response?.ok) {
- return await response.text();
+ if (!response?.ok) {
+ return "";
}
- return "";
+ content = await response.text();
+ this.#cache.set(objectId, content);
+
+ return content;
}
#setEnabled(enabled: boolean): void {
#getContainer(): HTMLElement {
if (this.#container === undefined) {
this.#container = document.createElement("div");
+ this.#container.classList.add("popoverContainer");
this.#container.dataset.identifier = this.#identifier;
+ this.#container.setAttribute("aria-hidden", "true");
}
this.#container.remove();
-define(["require", "exports", "tslib", "../Ajax/Backend", "../Dom/Util", "../Helper/PageOverlay", "../Helper/Selector"], function (require, exports, tslib_1, Backend_1, Util_1, PageOverlay_1, Selector_1) {
+define(["require", "exports", "tslib", "../Ajax/Backend", "../Dom/Util", "../Helper/PageOverlay", "../Helper/Selector", "../Timer/Repeating", "../Ui/Alignment"], function (require, exports, tslib_1, Backend_1, Util_1, PageOverlay_1, Selector_1, Repeating_1, UiAlignment) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupFor = void 0;
Util_1 = tslib_1.__importDefault(Util_1);
+ Repeating_1 = tslib_1.__importDefault(Repeating_1);
+ UiAlignment = tslib_1.__importStar(UiAlignment);
class Popover {
#cache = new Map();
#currentElement = undefined;
#endpoint;
#enabled = true;
#identifier;
+ #pendingElement = undefined;
+ #pendingObjectId = undefined;
+ #timerStart = undefined;
+ #timerHide = undefined;
constructor(selector, endpoint, identifier) {
this.#identifier = identifier;
this.#endpoint = new URL(endpoint);
(0, Selector_1.wheneverFirstSeen)(selector, (element) => {
element.addEventListener("mouseenter", () => {
- void this.#hoverStart(element);
+ this.#hoverStart(element);
});
element.addEventListener("mouseleave", () => {
this.#hoverEnd(element);
this.#setEnabled(false);
});
}
- async #hoverStart(element) {
+ #hoverStart(element) {
const objectId = this.#getObjectId(element);
- let content = this.#cache.get(objectId);
- if (content === undefined) {
- content = await this.#fetch(objectId);
- this.#cache.set(objectId, content);
+ this.#pendingObjectId = objectId;
+ if (this.#timerStart === undefined) {
+ this.#timerStart = new Repeating_1.default((timer) => {
+ timer.stop();
+ const objectId = this.#pendingObjectId;
+ void this.#getContent(objectId).then((content) => {
+ if (objectId !== this.#pendingObjectId) {
+ return;
+ }
+ const container = this.#getContainer();
+ Util_1.default.setInnerHtml(container, content);
+ UiAlignment.set(container, element, { vertical: "top" });
+ container.setAttribute("aria-hidden", "false");
+ });
+ }, 800 /* Delay.Show */);
+ }
+ else {
+ this.#timerStart.restart();
}
- Util_1.default.setInnerHtml(this.#getContainer(), content);
}
- #hoverEnd(element) { }
- async #fetch(objectId) {
+ #hoverEnd(element) {
+ this.#timerStart?.stop();
+ this.#pendingObjectId = undefined;
+ if (this.#timerHide === undefined) {
+ this.#timerHide = new Repeating_1.default(() => {
+ // do something
+ }, 500 /* Delay.Hide */);
+ }
+ else {
+ this.#timerHide.restart();
+ }
+ }
+ async #getContent(objectId) {
+ let content = this.#cache.get(objectId);
+ if (content !== undefined) {
+ return content;
+ }
this.#endpoint.searchParams.set("id", objectId.toString());
const response = await (0, Backend_1.prepareRequest)(this.#endpoint).get().fetchAsResponse();
- if (response?.ok) {
- return await response.text();
+ if (!response?.ok) {
+ return "";
}
- return "";
+ content = await response.text();
+ this.#cache.set(objectId, content);
+ return content;
}
#setEnabled(enabled) {
this.#enabled = enabled;
#getContainer() {
if (this.#container === undefined) {
this.#container = document.createElement("div");
+ this.#container.classList.add("popoverContainer");
this.#container.dataset.identifier = this.#identifier;
+ this.#container.setAttribute("aria-hidden", "true");
}
this.#container.remove();
(0, PageOverlay_1.getPageOverlayContainer)().append(this.#container);
namespace wcf\action;
use Laminas\Diactoros\Response\HtmlResponse;
-use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
-use wcf\data\user\group\UserGroup;
-use wcf\data\user\UserProfile;
-use wcf\data\user\UserProfileList;
use wcf\http\Helper;
use wcf\system\cache\runtime\UserProfileRuntimeCache;
use wcf\system\WCF;
display: none;
}
}
+
+/* @since 6.1 */
+.popoverContainer {
+ --padding: 20px;
+
+ background-color: var(--wcfContentContainerBackground);
+ border: 1px solid var(--wcfContentBorderInner);
+ border-radius: var(--wcfBorderRadius);
+ box-shadow: var(--wcfBoxShadow);
+ color: var(--wcfContentText);
+ max-height: 300px;
+ max-width: 500px;
+ opacity: 0;
+ padding: var(--padding);
+ position: absolute;
+ transform: translateY(-20px);
+ transition:
+ opacity 0.12s linear,
+ transform 0.12s linear;
+
+ a {
+ color: var(--wcfContentLink);
+
+ &:hover {
+ color: var(--wcfContentLinkActive);
+ }
+ }
+
+ &[aria-hidden="false"] {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}