Fully implemented the Manage Time functionality

This commit is contained in:
2026-10-19 10:30:09 -03:00
parent d392e5ddd7
commit 9eef03e5f4
17 changed files with 1429 additions and 139 deletions

View File

@@ -6,7 +6,7 @@ function createSearchableSelect({ environment }) {
if (text !== undefined) node.textContent = text;
return node;
}
function searchableSelect(select, field, onChange, { floating = false, tooltipContent } = {}) {
function searchableSelect(select, field, onChange, { floating = false, tooltipContent, listWidth = 0, listMaxHeight = 180 } = {}) {
const box = element("div", "opportunities-extension-action-combobox");
const input = element("input");
input.type = "text";
@@ -79,11 +79,11 @@ function createSearchableSelect({ environment }) {
input.value = options().find(item => item.value === select.value)?.textContent || "";
input.title = tooltipContent ? "" : input.value;
}
function highlight() {
function highlight(withTooltip = true) {
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]);
if (withTooltip && matches[index]) showTooltip(matches[index].value, list.children[index]);
}
function choose(i) {
if (!matches[i]) return;
@@ -93,18 +93,22 @@ function createSearchableSelect({ environment }) {
if (changed) onChange?.();
}
function positionList() {
const bounds = input.closest?.(".opportunities-extension-action-form-dialog")?.getBoundingClientRect();
let bounds = input.closest?.(".opportunities-extension-action-form-dialog")?.getBoundingClientRect();
const anchor = input.getBoundingClientRect?.();
if (!bounds || !anchor) return;
if (floating && listWidth && document.documentElement?.clientWidth) {
bounds = { top: 0, left: 0, right: document.documentElement.clientWidth, bottom: document.documentElement.clientHeight };
}
const below = Math.max(0, bounds.bottom - anchor.bottom - 8);
const above = Math.max(0, anchor.top - bounds.top - 8);
const opensAbove = below < 180 && above > below;
const opensAbove = below < listMaxHeight && above > below;
list.setAttribute("data-placement", opensAbove ? "above" : "below");
list.style.maxHeight = `${Math.min(180, opensAbove ? above : below)}px`;
list.style.maxHeight = `${Math.min(listMaxHeight, opensAbove ? above : below)}px`;
if (floating) {
const width = listWidth ? Math.min(Math.max(anchor.width, listWidth), bounds.right - bounds.left - 16) : anchor.width;
list.style.position = "fixed";
list.style.left = `${anchor.left}px`;
list.style.width = `${anchor.width}px`;
list.style.left = `${listWidth ? Math.max(bounds.left + 8, Math.min(anchor.left, bounds.right - width - 8)) : anchor.left}px`;
list.style.width = `${width}px`;
list.style.right = "auto";
list.style.bottom = "auto";
list.style.top = `${opensAbove ? anchor.top - 4 : anchor.bottom + 4}px`;
@@ -115,7 +119,7 @@ function createSearchableSelect({ environment }) {
if (input.disabled) return;
hideTooltip();
matches = options().filter(item => normalize(`${item.textContent} ${item.getAttribute?.("data-description") || ""} ${item.value}`).includes(normalize(query)));
index = -1;
index = matches.findIndex(item => item.value === select.value);
list.textContent = "";
matches.forEach((item, i) => {
const row = element("div", "opportunities-extension-action-option", item.textContent);
@@ -140,7 +144,7 @@ function createSearchableSelect({ environment }) {
positionList();
list.hidden = false;
input.setAttribute("aria-expanded", "true");
input.setAttribute("aria-activedescendant", "");
highlight(false);
}
input.addEventListener("click", () => { render(); input.select?.(); });
input.addEventListener("input", () => render(input.value));