Partially implemented the Manage Time functionality
This commit is contained in:
@@ -6,7 +6,7 @@ function createSearchableSelect({ environment }) {
|
||||
if (text !== undefined) node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
function searchableSelect(select, field, onChange) {
|
||||
function searchableSelect(select, field, onChange, { floating = false, tooltipContent } = {}) {
|
||||
const box = element("div", "opportunities-extension-action-combobox");
|
||||
const input = element("input");
|
||||
input.type = "text";
|
||||
@@ -27,10 +27,47 @@ function createSearchableSelect({ environment }) {
|
||||
select.tabIndex = -1;
|
||||
box.append(input, list);
|
||||
field.append(box);
|
||||
const tooltip = tooltipContent ? element("div", "opportunities-extension-select-tooltip") : null;
|
||||
if (tooltip) {
|
||||
tooltip.id = `${select.id}-tooltip`;
|
||||
tooltip.setAttribute("role", "tooltip");
|
||||
tooltip.hidden = true;
|
||||
box.append(tooltip);
|
||||
}
|
||||
let tooltipAnchor = null;
|
||||
function hideTooltip() {
|
||||
if (!tooltip) return;
|
||||
tooltip.hidden = true;
|
||||
tooltipAnchor?.removeAttribute?.("aria-describedby");
|
||||
tooltipAnchor = null;
|
||||
}
|
||||
function showTooltip(value, anchor) {
|
||||
hideTooltip();
|
||||
if (!tooltip || !value) return;
|
||||
const content = tooltipContent(value);
|
||||
if (!content) return;
|
||||
tooltip.replaceChildren(content);
|
||||
tooltip.hidden = false;
|
||||
tooltipAnchor = anchor;
|
||||
anchor.setAttribute("aria-describedby", tooltip.id);
|
||||
const rect = anchor.getBoundingClientRect?.();
|
||||
const bounds = input.closest?.(".opportunities-extension-action-form-dialog")?.getBoundingClientRect();
|
||||
if (!rect || !bounds) return;
|
||||
const size = tooltip.getBoundingClientRect();
|
||||
const left = rect.right + 8 + size.width <= bounds.right ? rect.right + 8 : Math.max(bounds.left + 8, rect.left - size.width - 8);
|
||||
tooltip.style.left = `${left}px`;
|
||||
tooltip.style.top = `${Math.max(bounds.top + 8, Math.min(rect.top, bounds.bottom - size.height - 8))}px`;
|
||||
}
|
||||
function leaveTooltip(event) {
|
||||
if (!tooltip?.contains?.(event.relatedTarget)) hideTooltip();
|
||||
}
|
||||
tooltip?.addEventListener("mouseleave", hideTooltip);
|
||||
list.addEventListener("scroll", hideTooltip);
|
||||
let matches = [], index = -1;
|
||||
const options = () => Array.from(select.children).filter(item => item.value);
|
||||
const normalize = value => String(value).normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
||||
function closeList() {
|
||||
hideTooltip();
|
||||
list.hidden = true;
|
||||
input.setAttribute("aria-expanded", "false");
|
||||
input.setAttribute("aria-activedescendant", "");
|
||||
@@ -40,12 +77,13 @@ function createSearchableSelect({ environment }) {
|
||||
input.disabled = !!select.disabled;
|
||||
input.placeholder = select.children[0]?.textContent || "Select";
|
||||
input.value = options().find(item => item.value === select.value)?.textContent || "";
|
||||
input.title = input.value;
|
||||
input.title = tooltipContent ? "" : input.value;
|
||||
}
|
||||
function highlight() {
|
||||
Array.from(list.children).forEach((node, i) => node.setAttribute("aria-selected", String(i === index)));
|
||||
input.setAttribute("aria-activedescendant", matches[index] ? `${list.id}-${index}` : "");
|
||||
list.children[index]?.scrollIntoView?.({ block: "nearest" });
|
||||
if (matches[index]) showTooltip(matches[index].value, list.children[index]);
|
||||
}
|
||||
function choose(i) {
|
||||
if (!matches[i]) return;
|
||||
@@ -63,9 +101,19 @@ function createSearchableSelect({ environment }) {
|
||||
const opensAbove = below < 180 && above > below;
|
||||
list.setAttribute("data-placement", opensAbove ? "above" : "below");
|
||||
list.style.maxHeight = `${Math.min(180, opensAbove ? above : below)}px`;
|
||||
if (floating) {
|
||||
list.style.position = "fixed";
|
||||
list.style.left = `${anchor.left}px`;
|
||||
list.style.width = `${anchor.width}px`;
|
||||
list.style.right = "auto";
|
||||
list.style.bottom = "auto";
|
||||
list.style.top = `${opensAbove ? anchor.top - 4 : anchor.bottom + 4}px`;
|
||||
list.style.transform = opensAbove ? "translateY(-100%)" : "none";
|
||||
}
|
||||
}
|
||||
function render(query = "") {
|
||||
if (input.disabled) return;
|
||||
hideTooltip();
|
||||
matches = options().filter(item => normalize(`${item.textContent} ${item.getAttribute?.("data-description") || ""} ${item.value}`).includes(normalize(query)));
|
||||
index = -1;
|
||||
list.textContent = "";
|
||||
@@ -78,6 +126,10 @@ function createSearchableSelect({ environment }) {
|
||||
row.setAttribute("aria-selected", "false");
|
||||
row.addEventListener("mousedown", event => event.preventDefault());
|
||||
row.addEventListener("click", event => { event.preventDefault(); choose(i); });
|
||||
if (tooltip) {
|
||||
row.addEventListener("mouseenter", () => showTooltip(item.value, row));
|
||||
row.addEventListener("mouseleave", leaveTooltip);
|
||||
}
|
||||
list.append(row);
|
||||
});
|
||||
if (!matches.length) {
|
||||
@@ -93,6 +145,10 @@ function createSearchableSelect({ environment }) {
|
||||
input.addEventListener("click", () => { render(); input.select?.(); });
|
||||
input.addEventListener("input", () => render(input.value));
|
||||
input.addEventListener("blur", sync);
|
||||
if (tooltip) {
|
||||
input.addEventListener("mouseenter", () => { if (list.hidden) showTooltip(select.value, input); });
|
||||
input.addEventListener("mouseleave", leaveTooltip);
|
||||
}
|
||||
input.addEventListener("keydown", event => {
|
||||
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
@@ -102,7 +158,7 @@ function createSearchableSelect({ environment }) {
|
||||
} else if (event.key === "Enter" && !list.hidden) {
|
||||
event.preventDefault();
|
||||
choose(index < 0 && matches.length === 1 ? 0 : index);
|
||||
} else if (event.key === "Escape" && !list.hidden) {
|
||||
} else if (event.key === "Escape" && (!list.hidden || (tooltip && !tooltip.hidden))) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
sync();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
function createSrSessionPreparation({ ensureCurrentUser, allowsCrmActivities, sendRequest, reportProgress, appendLog }) {
|
||||
function createSrSessionPreparation({ ensureCurrentUser, allowsCrmActivities, sendRequest, reportProgress, appendLog, onReady = async () => {} }) {
|
||||
let pending = null;
|
||||
function ensure() {
|
||||
if (pending) return pending;
|
||||
@@ -11,6 +11,7 @@ function createSrSessionPreparation({ ensureCurrentUser, allowsCrmActivities, se
|
||||
appendLog(result?.debugRequests || []);
|
||||
if (!result?.ok) throw new Error(result?.error || "Session initialization failed.");
|
||||
reportProgress("complete");
|
||||
await onReady();
|
||||
} catch {
|
||||
reportProgress("error");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user