Calendar/Contacts/Mail: pill resize handles, day hover, locale-safe date picker
- Panel resize handles: replace the tall bar with a small centered pill/capsule (elevated fill + border, accent on hover) across Mail, Calendar and Contacts. - Calendar month/week/3-day views: brighten a day block on hover. - Calendar month grid: shade the current month's days theme-aware — a subtle dark overlay in light mode (they were only distinguished in dark mode before), white overlay in dark mode. - Calendar event form: replace the native <input type=date> (whose order follows the browser locale) with a custom field driven by the 24h/12h preference — an editable text box (typed dd/mm/yyyy for 24h) backed by a hidden ISO input, plus a popup month picker. Applies to the start date and the recurrence "Ends on date". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U72FZiF1W1pb8XxyWBTREA
This commit is contained in:
@@ -27,6 +27,8 @@ export function Resizer({ onDelta }: { onDelta: (deltaX: number) => void }) {
|
|||||||
aria-orientation="vertical"
|
aria-orientation="vertical"
|
||||||
>
|
>
|
||||||
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-transparent group-hover:bg-app-accent" />
|
<div className="absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-transparent group-hover:bg-app-accent" />
|
||||||
|
{/* Small pill grab handle in the middle for easier grabbing. */}
|
||||||
|
<div className="absolute left-1/2 top-1/2 h-7 w-[5px] -translate-x-1/2 -translate-y-1/2 rounded-full border border-app-border bg-app-elevated group-hover:border-app-accent group-hover:bg-app-accent" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ const esc = (s) => String(s == null ? "" : s).replace(/[&<>]/g, (c) => ({ "&": "
|
|||||||
let HOUR12 = false;
|
let HOUR12 = false;
|
||||||
// Date order follows the time preference: 24h → en-GB (day/month/year), 12h → en-US.
|
// Date order follows the time preference: 24h → en-GB (day/month/year), 12h → en-US.
|
||||||
const dateLocale = () => (HOUR12 ? "en-US" : "en-GB");
|
const dateLocale = () => (HOUR12 ? "en-US" : "en-GB");
|
||||||
|
// Format a native date input's ISO value (yyyy-mm-dd) for display in the preferred
|
||||||
|
// order — the native <input type=date> text is fixed to the browser locale, so the
|
||||||
|
// form overlays this label instead.
|
||||||
|
const fmtDateInput = (iso) => {
|
||||||
|
const [y, m, d] = String(iso || "").split("-").map(Number);
|
||||||
|
return y && m && d ? new Date(y, m - 1, d).toLocaleDateString(dateLocale()) : "";
|
||||||
|
};
|
||||||
const pad2 = (n) => String(n).padStart(2, "0");
|
const pad2 = (n) => String(n).padStart(2, "0");
|
||||||
// Icons copied from Mail (frontend/icons/*.svg) so the two apps match exactly.
|
// Icons copied from Mail (frontend/icons/*.svg) so the two apps match exactly.
|
||||||
const HIDE_ICON = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m4 4l16 16m-3.5-3.244C15.147 17.485 13.618 18 12 18c-3.53 0-6.634-2.452-8.413-4.221c-.47-.467-.705-.7-.854-1.159c-.107-.327-.107-.913 0-1.24c.15-.459.385-.693.855-1.16c.897-.892 2.13-1.956 3.584-2.793M19.5 14.634c.333-.293.638-.582.912-.854l.003-.003c.468-.466.703-.7.852-1.156c.107-.327.107-.914 0-1.241c-.15-.458-.384-.692-.854-1.159C18.633 8.452 15.531 6 12 6q-.507 0-1 .064m2.323 7.436a2 2 0 0 1-2.762-2.889"/></svg>`;
|
const HIDE_ICON = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="m4 4l16 16m-3.5-3.244C15.147 17.485 13.618 18 12 18c-3.53 0-6.634-2.452-8.413-4.221c-.47-.467-.705-.7-.854-1.159c-.107-.327-.107-.913 0-1.24c.15-.459.385-.693.855-1.16c.897-.892 2.13-1.956 3.584-2.793M19.5 14.634c.333-.293.638-.582.912-.854l.003-.003c.468-.466.703-.7.852-1.156c.107-.327.107-.914 0-1.241c-.15-.458-.384-.692-.854-1.159C18.633 8.452 15.531 6 12 6q-.507 0-1 .064m2.323 7.436a2 2 0 0 1-2.762-2.889"/></svg>`;
|
||||||
@@ -214,6 +221,8 @@ const CSS = `
|
|||||||
.cal-resizer{flex:0 0 auto;width:5px;margin:0 -2px;cursor:col-resize;position:relative;z-index:10}
|
.cal-resizer{flex:0 0 auto;width:5px;margin:0 -2px;cursor:col-resize;position:relative;z-index:10}
|
||||||
.cal-resizer::after{content:"";position:absolute;top:0;bottom:0;left:50%;width:1px;transform:translateX(-50%);background:transparent}
|
.cal-resizer::after{content:"";position:absolute;top:0;bottom:0;left:50%;width:1px;transform:translateX(-50%);background:transparent}
|
||||||
.cal-resizer:hover::after{background:#3b82f6}
|
.cal-resizer:hover::after{background:#3b82f6}
|
||||||
|
.cal-resizer::before{content:"";position:absolute;top:50%;left:50%;width:5px;height:28px;transform:translate(-50%,-50%);border-radius:999px;background:rgb(var(--app-elevated));border:1px solid rgb(var(--app-border));box-sizing:border-box;z-index:1}
|
||||||
|
.cal-resizer:hover::before{background:#3b82f6;border-color:#3b82f6}
|
||||||
.cal-main{flex:1;min-width:0;display:flex;flex-direction:column}
|
.cal-main{flex:1;min-width:0;display:flex;flex-direction:column}
|
||||||
.cal-right{width:320px;flex:0 0 auto;border-left:1px solid rgb(var(--app-border));overflow:auto}
|
.cal-right{width:320px;flex:0 0 auto;border-left:1px solid rgb(var(--app-border));overflow:auto}
|
||||||
/* search results panel */
|
/* search results panel */
|
||||||
@@ -286,8 +295,12 @@ const CSS = `
|
|||||||
.cal-cell .mtag{color:rgb(var(--app-muted));font-weight:400;font-size:14px}
|
.cal-cell .mtag{color:rgb(var(--app-muted));font-weight:400;font-size:14px}
|
||||||
.cal-cell.out .n{color:rgb(var(--app-faint))}
|
.cal-cell.out .n{color:rgb(var(--app-faint))}
|
||||||
.cal-cell.out .mtag{color:rgb(var(--app-faint))}
|
.cal-cell.out .mtag{color:rgb(var(--app-faint))}
|
||||||
.cal-cell:not(.out){background:rgba(255,255,255,.03)}
|
.cal-cell:not(.out){background:rgba(0,0,0,.02)}
|
||||||
|
.dark .cal-cell:not(.out){background:rgba(255,255,255,.03)}
|
||||||
.cal-cell.today{background:rgba(59,130,246,.06)}
|
.cal-cell.today{background:rgba(59,130,246,.06)}
|
||||||
|
.cal-cell:hover{background:rgba(0,0,0,.055)}
|
||||||
|
.dark .cal-cell:hover{background:rgba(255,255,255,.06)}
|
||||||
|
.cal-cell.today:hover{background:rgba(59,130,246,.11)}
|
||||||
.cal-cell.today .num{background:#3b82f6;color:#fff}
|
.cal-cell.today .num{background:#3b82f6;color:#fff}
|
||||||
.cal-ev{font-size:14px;padding:1px 4px;border-radius:4px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;border-left:3px solid #3b82f6;background:rgb(var(--app-hover))}
|
.cal-ev{font-size:14px;padding:1px 4px;border-radius:4px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;border-left:3px solid #3b82f6;background:rgb(var(--app-hover))}
|
||||||
.cal-ev.srch-hl,.cal-tev.srch-hl{outline:2px solid #3b82f6;outline-offset:-1px;background:rgba(59,130,246,.28)}
|
.cal-ev.srch-hl,.cal-tev.srch-hl{outline:2px solid #3b82f6;outline-offset:-1px;background:rgba(59,130,246,.28)}
|
||||||
@@ -305,6 +318,9 @@ const CSS = `
|
|||||||
.cal-cols{display:grid;position:relative}
|
.cal-cols{display:grid;position:relative}
|
||||||
.cal-col{border-left:1px solid rgb(var(--app-border));position:relative}
|
.cal-col{border-left:1px solid rgb(var(--app-border));position:relative}
|
||||||
.cal-col.today{background:rgba(59,130,246,.06)}
|
.cal-col.today{background:rgba(59,130,246,.06)}
|
||||||
|
.cal-col:hover{background:rgba(0,0,0,.045)}
|
||||||
|
.dark .cal-col:hover{background:rgba(255,255,255,.05)}
|
||||||
|
.cal-col.today:hover{background:rgba(59,130,246,.11)}
|
||||||
.cal-col .line{height:${HOUR_PX}px;border-top:1px solid rgb(var(--app-border))}
|
.cal-col .line{height:${HOUR_PX}px;border-top:1px solid rgb(var(--app-border))}
|
||||||
.cal-tev{position:absolute;left:2px;width:calc(100% - 4px);border-radius:5px;border:1px solid rgb(var(--app-elevated));border-left:3px solid #3b82f6;background:rgb(var(--app-hover));padding:2px 5px;font-size:14px;overflow:hidden;cursor:pointer;box-sizing:border-box}
|
.cal-tev{position:absolute;left:2px;width:calc(100% - 4px);border-radius:5px;border:1px solid rgb(var(--app-elevated));border-left:3px solid #3b82f6;background:rgb(var(--app-hover));padding:2px 5px;font-size:14px;overflow:hidden;cursor:pointer;box-sizing:border-box}
|
||||||
.cal-now{position:absolute;left:0;right:0;height:0;border-top:2px solid #ef4444;z-index:4}
|
.cal-now{position:absolute;left:0;right:0;height:0;border-top:2px solid #ef4444;z-index:4}
|
||||||
@@ -348,6 +364,22 @@ const CSS = `
|
|||||||
.cal-sw.sel{border-color:rgb(var(--app-fg))}
|
.cal-sw.sel{border-color:rgb(var(--app-fg))}
|
||||||
.cal-form input[type=text],.cal-form input[type=email],.cal-form textarea,.cal-form select,.cal-form input[type=date],.cal-form input[type=time],.cal-form input[type=number]{width:100%;padding:7px 9px;border:1px solid rgb(var(--app-border));border-radius:7px;background:rgb(var(--app-elevated));color:rgb(var(--app-fg));font:inherit;box-sizing:border-box}
|
.cal-form input[type=text],.cal-form input[type=email],.cal-form textarea,.cal-form select,.cal-form input[type=date],.cal-form input[type=time],.cal-form input[type=number]{width:100%;padding:7px 9px;border:1px solid rgb(var(--app-border));border-radius:7px;background:rgb(var(--app-elevated));color:rgb(var(--app-fg));font:inherit;box-sizing:border-box}
|
||||||
.cal-form .row{display:flex;gap:8px;align-items:center}
|
.cal-form .row{display:flex;gap:8px;align-items:center}
|
||||||
|
/* Custom date field: a formatted read-only text box + popup month picker. Native
|
||||||
|
<input type=date> renders its text in the browser locale's order, which we can't
|
||||||
|
control — this drives the display order entirely from the 24h/12h preference. */
|
||||||
|
.cal-datewrap{position:relative;display:block;width:100%;min-width:0}
|
||||||
|
.cal-datewrap input[data-datedisplay]{cursor:text}
|
||||||
|
.cal-datepop{position:absolute;left:0;top:100%;margin-top:4px;z-index:60;background:rgb(var(--app-elevated));border:1px solid rgb(var(--app-border));border-radius:10px;box-shadow:0 12px 32px rgba(0,0,0,.4);padding:8px;width:236px}
|
||||||
|
.cal-datepop-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
|
||||||
|
.cal-datepop-head span{font-size:13px;font-weight:600;color:rgb(var(--app-fg))}
|
||||||
|
.cal-datepop-head button{border:0;background:none;color:rgb(var(--app-fg));cursor:pointer;width:26px;height:26px;border-radius:6px;font-size:17px;line-height:1}
|
||||||
|
.cal-datepop-head button:hover{background:rgb(var(--app-hover))}
|
||||||
|
.cal-datepop-dow,.cal-datepop-grid{display:grid;grid-template-columns:repeat(7,1fr)}
|
||||||
|
.cal-datepop-dow span{text-align:center;font-size:11px;color:rgb(var(--app-faint));padding:2px 0}
|
||||||
|
.cal-datepop-grid button{border:0;background:none;color:rgb(var(--app-fg));cursor:pointer;height:28px;border-radius:50%;font:inherit;font-size:13px}
|
||||||
|
.cal-datepop-grid button:hover{background:rgb(var(--app-hover))}
|
||||||
|
.cal-datepop-grid button.today{outline:1px solid rgb(var(--app-border))}
|
||||||
|
.cal-datepop-grid button.sel{background:#3b82f6;color:#fff}
|
||||||
.cal-form .muted{color:rgb(var(--app-muted));font-size:13px}
|
.cal-form .muted{color:rgb(var(--app-muted));font-size:13px}
|
||||||
.cal-formbtns{display:flex;gap:8px;margin-top:16px}
|
.cal-formbtns{display:flex;gap:8px;margin-top:16px}
|
||||||
.cal-guest{display:inline-flex;align-items:center;gap:4px;background:rgb(var(--app-hover));border-radius:10px;padding:2px 8px;font-size:12px;margin:2px 4px 2px 0}
|
.cal-guest{display:inline-flex;align-items:center;gap:4px;background:rgb(var(--app-hover));border-radius:10px;padding:2px 8px;font-size:12px;margin:2px 4px 2px 0}
|
||||||
@@ -472,6 +504,115 @@ export default function mount(el, ctx, props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pad(n) { return String(n).padStart(2, "0"); }
|
function pad(n) { return String(n).padStart(2, "0"); }
|
||||||
|
// Wire every custom date field in the form: an editable text box showing the date in
|
||||||
|
// the preferred order (typing is parsed in that order), backed by a hidden ISO input,
|
||||||
|
// plus a popup month picker.
|
||||||
|
function initDatePickers() {
|
||||||
|
el.querySelectorAll("[data-datepick]").forEach(setupDatePicker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a hand-typed date honouring the preferred order (24h → d/m/y, 12h → m/d/y).
|
||||||
|
function parseTypedDate(str) {
|
||||||
|
const parts = String(str).split(/[^0-9]+/).filter(Boolean).map(Number);
|
||||||
|
if (parts.length < 3) return null;
|
||||||
|
let d, m, y;
|
||||||
|
if (HOUR12) [m, d, y] = parts; else [d, m, y] = parts;
|
||||||
|
if (y < 100) y += 2000;
|
||||||
|
if (!(y && m >= 1 && m <= 12 && d >= 1 && d <= 31)) return null;
|
||||||
|
const dt = new Date(y, m - 1, d);
|
||||||
|
if (dt.getFullYear() !== y || dt.getMonth() !== m - 1 || dt.getDate() !== d) return null;
|
||||||
|
return `${y}-${pad2(m)}-${pad2(d)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupDatePicker(wrap) {
|
||||||
|
if (wrap.dataset.wired) return; // idempotent across re-renders
|
||||||
|
wrap.dataset.wired = "1";
|
||||||
|
const hidden = wrap.querySelector("input[type=hidden]");
|
||||||
|
const display = wrap.querySelector("[data-datedisplay]");
|
||||||
|
display.setAttribute("placeholder", HOUR12 ? "mm/dd/yyyy" : "dd/mm/yyyy");
|
||||||
|
const parse = (iso) => {
|
||||||
|
const [y, m, d] = String(iso || "").split("-").map(Number);
|
||||||
|
return y && m && d ? { y, m, d } : null;
|
||||||
|
};
|
||||||
|
const refresh = () => { display.value = fmtDateInput(hidden.value); };
|
||||||
|
// Accept the typed text into the hidden ISO value, then normalise the display.
|
||||||
|
const commit = () => {
|
||||||
|
const isoTyped = parseTypedDate(display.value);
|
||||||
|
if (isoTyped) hidden.value = isoTyped;
|
||||||
|
refresh();
|
||||||
|
};
|
||||||
|
let pop = null;
|
||||||
|
let viewY = 0;
|
||||||
|
let viewM = 0; // 1-12
|
||||||
|
|
||||||
|
const onDoc = (e) => { if (pop && !wrap.contains(e.target)) { commit(); close(); } };
|
||||||
|
function close() {
|
||||||
|
if (pop) { pop.remove(); pop = null; }
|
||||||
|
document.removeEventListener("mousedown", onDoc);
|
||||||
|
}
|
||||||
|
function open() {
|
||||||
|
if (pop) return;
|
||||||
|
const cur = parseTypedDate(display.value) ? parse(parseTypedDate(display.value))
|
||||||
|
: parse(hidden.value) || (() => {
|
||||||
|
const n = new Date();
|
||||||
|
return { y: n.getFullYear(), m: n.getMonth() + 1, d: n.getDate() };
|
||||||
|
})();
|
||||||
|
viewY = cur.y; viewM = cur.m;
|
||||||
|
pop = document.createElement("div");
|
||||||
|
pop.className = "cal-datepop";
|
||||||
|
// Keep focus in the text box so clicking the picker never blurs mid-type.
|
||||||
|
pop.addEventListener("mousedown", (e) => e.preventDefault());
|
||||||
|
wrap.appendChild(pop);
|
||||||
|
renderPop();
|
||||||
|
setTimeout(() => document.addEventListener("mousedown", onDoc), 0);
|
||||||
|
}
|
||||||
|
function renderPop() {
|
||||||
|
const sel = parse(hidden.value);
|
||||||
|
const today = new Date();
|
||||||
|
const startDow = (new Date(viewY, viewM - 1, 1).getDay() + 6) % 7; // Monday-first
|
||||||
|
const daysIn = new Date(viewY, viewM, 0).getDate();
|
||||||
|
let cells = "";
|
||||||
|
for (let i = 0; i < startDow; i++) cells += "<span></span>";
|
||||||
|
for (let d = 1; d <= daysIn; d++) {
|
||||||
|
const isSel = sel && sel.y === viewY && sel.m === viewM && sel.d === d;
|
||||||
|
const isToday = today.getFullYear() === viewY && today.getMonth() + 1 === viewM && today.getDate() === d;
|
||||||
|
cells += `<button type="button" data-day="${d}" class="${isSel ? "sel" : ""}${isToday ? " today" : ""}">${d}</button>`;
|
||||||
|
}
|
||||||
|
pop.innerHTML = `
|
||||||
|
<div class="cal-datepop-head">
|
||||||
|
<button type="button" data-nav="-1">‹</button>
|
||||||
|
<span>${MONTHS[viewM - 1]} ${viewY}</span>
|
||||||
|
<button type="button" data-nav="1">›</button>
|
||||||
|
</div>
|
||||||
|
<div class="cal-datepop-dow">${DAYNAMES.map((d) => `<span>${d[0]}</span>`).join("")}</div>
|
||||||
|
<div class="cal-datepop-grid">${cells}</div>`;
|
||||||
|
pop.querySelectorAll("[data-nav]").forEach((b) => b.addEventListener("click", () => {
|
||||||
|
viewM += Number(b.getAttribute("data-nav"));
|
||||||
|
if (viewM < 1) { viewM = 12; viewY--; }
|
||||||
|
if (viewM > 12) { viewM = 1; viewY++; }
|
||||||
|
renderPop();
|
||||||
|
}));
|
||||||
|
pop.querySelectorAll("[data-day]").forEach((b) => b.addEventListener("click", () => {
|
||||||
|
hidden.value = `${viewY}-${pad2(viewM)}-${pad2(Number(b.getAttribute("data-day")))}`;
|
||||||
|
refresh();
|
||||||
|
close();
|
||||||
|
display.blur();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
display.addEventListener("focus", open);
|
||||||
|
// Live-sync the picker's shown month as a valid date is typed.
|
||||||
|
display.addEventListener("input", () => {
|
||||||
|
const isoTyped = parseTypedDate(display.value);
|
||||||
|
if (isoTyped) { hidden.value = isoTyped; const p = parse(isoTyped); viewY = p.y; viewM = p.m; if (pop) renderPop(); }
|
||||||
|
});
|
||||||
|
display.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter") { e.preventDefault(); commit(); close(); display.blur(); }
|
||||||
|
else if (e.key === "Escape") { refresh(); close(); display.blur(); }
|
||||||
|
});
|
||||||
|
display.addEventListener("blur", () => { if (!pop) commit(); });
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
function openForm(when, editEv) {
|
function openForm(when, editEv) {
|
||||||
const d = when || new Date(); // default to the current time
|
const d = when || new Date(); // default to the current time
|
||||||
state.eventPopover = null;
|
state.eventPopover = null;
|
||||||
@@ -487,6 +628,7 @@ export default function mount(el, ctx, props) {
|
|||||||
render();
|
render();
|
||||||
if (editEv) prefillForm(editEv);
|
if (editEv) prefillForm(editEv);
|
||||||
else applyDefaultReminders(state.defaultRemTimed); // new events start with the user's defaults
|
else applyDefaultReminders(state.defaultRemTimed); // new events start with the user's defaults
|
||||||
|
initDatePickers();
|
||||||
const first = el.querySelector("[data-f=summary]");
|
const first = el.querySelector("[data-f=summary]");
|
||||||
if (first) first.focus();
|
if (first) first.focus();
|
||||||
}
|
}
|
||||||
@@ -565,7 +707,7 @@ export default function mount(el, ctx, props) {
|
|||||||
} else if (r.until) {
|
} else if (r.until) {
|
||||||
q("[data-f=endmode]").value = "until";
|
q("[data-f=endmode]").value = "until";
|
||||||
q("[data-f=until]").value = r.until;
|
q("[data-f=until]").value = r.until;
|
||||||
q("[data-f=until]").style.display = "";
|
q("[data-untilwrap]").style.display = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Guests — expand the Who section and restore chips (no auto video-call).
|
// Guests — expand the Who section and restore chips (no auto video-call).
|
||||||
@@ -1100,7 +1242,7 @@ export default function mount(el, ctx, props) {
|
|||||||
</div>
|
</div>
|
||||||
<h4>When</h4>
|
<h4>When</h4>
|
||||||
<label class="row" style="margin-bottom:6px"><input type="checkbox" data-f="allday" style="width:auto"/> All day</label>
|
<label class="row" style="margin-bottom:6px"><input type="checkbox" data-f="allday" style="width:auto"/> All day</label>
|
||||||
<div class="row"><input type="date" data-f="date" value="${c.date}"/><select data-f="time">${timeOptionsHtml(c.time)}</select></div>
|
<div class="row"><span class="cal-datewrap" data-datepick><input type="text" data-datedisplay autocomplete="off"/><input type="hidden" data-f="date" value="${c.date}"/></span><select data-f="time">${timeOptionsHtml(c.time)}</select></div>
|
||||||
<div style="margin-top:8px"><select data-f="duration">
|
<div style="margin-top:8px"><select data-f="duration">
|
||||||
<option value="15">15 minutes</option><option value="30">30 minutes</option>
|
<option value="15">15 minutes</option><option value="30">30 minutes</option>
|
||||||
<option value="60" selected>1 hour</option><option value="90">1.5 hours</option><option value="120">2 hours</option>
|
<option value="60" selected>1 hour</option><option value="90">1.5 hours</option><option value="120">2 hours</option>
|
||||||
@@ -1122,7 +1264,7 @@ export default function mount(el, ctx, props) {
|
|||||||
<div class="row" style="margin-top:8px">
|
<div class="row" style="margin-top:8px">
|
||||||
<select data-f="endmode"><option value="forever">Repeats forever</option><option value="count">Ends after N</option><option value="until">Ends on date</option></select>
|
<select data-f="endmode"><option value="forever">Repeats forever</option><option value="count">Ends after N</option><option value="until">Ends on date</option></select>
|
||||||
<input type="number" data-f="count" value="10" min="1" style="width:64px;display:none"/>
|
<input type="number" data-f="count" value="10" min="1" style="width:64px;display:none"/>
|
||||||
<input type="date" data-f="until" style="display:none"/>
|
<span class="cal-datewrap" data-datepick data-untilwrap style="display:none;width:150px"><input type="text" data-datedisplay autocomplete="off"/><input type="hidden" data-f="until"/></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h4>Who</h4>
|
<h4>Who</h4>
|
||||||
@@ -1481,7 +1623,7 @@ export default function mount(el, ctx, props) {
|
|||||||
on("[data-f=endmode]", "change", (e) => {
|
on("[data-f=endmode]", "change", (e) => {
|
||||||
const v = e.currentTarget.value;
|
const v = e.currentTarget.value;
|
||||||
el.querySelector("[data-f=count]").style.display = v === "count" ? "" : "none";
|
el.querySelector("[data-f=count]").style.display = v === "count" ? "" : "none";
|
||||||
el.querySelector("[data-f=until]").style.display = v === "until" ? "" : "none";
|
el.querySelector("[data-untilwrap]").style.display = v === "until" ? "" : "none";
|
||||||
});
|
});
|
||||||
on("[data-f=allday]", "change", (e) => {
|
on("[data-f=allday]", "change", (e) => {
|
||||||
const off = e.currentTarget.checked;
|
const off = e.currentTarget.checked;
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ const CSS = `
|
|||||||
.ct-resizer{flex:0 0 auto;width:5px;margin:0 -2px;cursor:col-resize;position:relative;z-index:10}
|
.ct-resizer{flex:0 0 auto;width:5px;margin:0 -2px;cursor:col-resize;position:relative;z-index:10}
|
||||||
.ct-resizer::after{content:"";position:absolute;top:0;bottom:0;left:50%;width:1px;transform:translateX(-50%);background:transparent}
|
.ct-resizer::after{content:"";position:absolute;top:0;bottom:0;left:50%;width:1px;transform:translateX(-50%);background:transparent}
|
||||||
.ct-resizer:hover::after{background:#3b82f6}
|
.ct-resizer:hover::after{background:#3b82f6}
|
||||||
|
.ct-resizer::before{content:"";position:absolute;top:50%;left:50%;width:5px;height:28px;transform:translate(-50%,-50%);border-radius:999px;background:rgb(var(--app-elevated));border:1px solid rgb(var(--app-border));box-sizing:border-box;z-index:1}
|
||||||
|
.ct-resizer:hover::before{background:#3b82f6;border-color:#3b82f6}
|
||||||
.ct-bar{display:flex;align-items:center;gap:8px;height:49px;padding:0 14px 0 20px;border-bottom:1px solid rgb(var(--app-border));flex:0 0 auto}
|
.ct-bar{display:flex;align-items:center;gap:8px;height:49px;padding:0 14px 0 20px;border-bottom:1px solid rgb(var(--app-border));flex:0 0 auto}
|
||||||
.ct-bar .sp{flex:1}
|
.ct-bar .sp{flex:1}
|
||||||
/* Mail-style selection checkbox (bar "select all" + per-row on avatar-hover) */
|
/* Mail-style selection checkbox (bar "select all" + per-row on avatar-hover) */
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 910 B |
Reference in New Issue
Block a user