function createTableSorting({ environment }) { const memory = new Map(); const storageKey = id => `opportunities-extension-table-sort-v1-${id}`; function read(id, columns) { let saved = memory.get(id); if (!saved) { try { saved = JSON.parse(environment.localStorage?.getItem(storageKey(id)) || "null"); } catch { /* Storage is optional. */ } } return saved && columns.some(column => column.key === saved.key && column.sortable !== false) && ["ascending", "descending"].includes(saved.direction) ? { key: saved.key, direction: saved.direction } : { key: "", direction: "ascending" }; } function save(id, state) { memory.set(id, { ...state }); try { environment.localStorage?.setItem(storageKey(id), JSON.stringify(state)); } catch { /* Retain the page-session preference. */ } } function value(raw, type) { if (raw == null || String(raw).trim() === "") return null; if (type === "number") return Number.isFinite(Number(raw)) ? Number(raw) : null; if (type === "date") { const timestamp = Date.parse(raw); return Number.isFinite(timestamp) ? timestamp : null; } return String(raw); } function create(id, columns) { let state; const headers = new Map(); const get = () => state || (state = read(id, columns)); function toggle(key) { if (!columns.some(column => column.key === key && column.sortable !== false)) return; const previous = get(); state = { key, direction: previous.key === key && previous.direction === "ascending" ? "descending" : "ascending" }; save(id, state); headers.forEach(update); } function select(items) { const selected = get(), column = columns.find(column => column.key === selected.key); if (!column) return [...items]; const direction = selected.direction === "descending" ? -1 : 1; return items.map((item, index) => ({ item, index, value: value(column.value ? column.value(item) : item[column.key], column.type) })) .sort((a, b) => { if (a.value === null || b.value === null) return a.value === b.value ? a.index - b.index : a.value === null ? 1 : -1; const result = typeof a.value === "number" ? a.value - b.value : a.value.localeCompare(b.value, undefined, { numeric: true, sensitivity: "base" }); return result * direction || a.index - b.index; }).map(record => record.item); } function update({ th, button, key, label }) { const selected = get(); const direction = selected.key === key ? selected.direction : "none"; th.setAttribute("aria-sort", direction); button.setAttribute("data-sort-direction", direction); button.setAttribute("aria-label", `Sort by ${label}${direction === "none" ? "" : `, ${direction}`}`); } function header(th, key, label, onChange, disabled = false) { const button = environment.document.createElement("button"); button.type = "button"; button.className = "opportunities-extension-sort-button"; button.disabled = disabled; button.title = `Sort by ${label}`; button.setAttribute("data-sort-key", key); const text = environment.document.createElement("span"); text.className = "opportunities-extension-sort-label"; text.textContent = label; button.append(text); const record = { th, button, key, label }; headers.set(key, record); update(record); th.append(button); button.addEventListener("click", () => { toggle(key); onChange?.(key); }); return button; } return { get, toggle, select, header }; } return { read, save, create }; }