IdleTimer
Calls on_idle() after a time without mouse, key, touch or scroll, with a warning dialog and a Stay button first.
Demo
Hold still for 4 seconds: a warning appears at the bottom right; 4 more and it says you went idle. Events: none yet.
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/idle_timer.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/idle_timer.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/idle_timer.qwe
component IdleTimerDemoLive() { state log = [] <div.demo-stack> <IdleTimer seconds={8} warn={4} title="Still reading?" message="This demo signs you out in {s} seconds." button="I'm here" on_warn={() => { log = push(log, "warned") }} on_active={() => { log = push(log, "back") }} on_idle={() => { log = push(log, "idle") }}/> <p.demo-note>Hold still for 4 seconds: a warning appears at the bottom right; 4 more and it says you went idle. Events: {len(log) == 0 ? "none yet" : join(log, ", ")}.</p> </div>}view IdleTimerDemo() = <div.demo-stack> <IdleTimerDemoLive/> </div>Source
ui/idle_timer.qwe
// IdleTimer: watches for a stretch with no mouse, wheel, key or touch, warns// a while before it is up, and then calls on_idle(): to sign someone out of a// bank's page, to pause a game, to stop polling. The warning is a small dialog// with the seconds left and a button to stay.//// <IdleTimer seconds={900} warn={60} on_idle={() => { navigate("/signed-out") }}/>// <IdleTimer seconds={300} warn={30} title="Still there?" message="You will be signed out in {s} seconds." button="I'm here"// on_warn={() => { ... }} on_active={() => { ... }}/>//// seconds: how long without input until it is idle (300). warn: how many// seconds before that the warning shows (60; 0 for none). title, message// ({s} is the seconds left) and button: the warning's words. on_idle(): called// once, with nothing, when the time is up; then it waits for input to start// again. on_warn(): when the warning shows. on_active(): when input comes after// a warning. Inside one of your components. Input is watched on the whole// page; the dialog is role="alertdialog", so screen readers hear it.import ui/theme.qwe use ui_clslet idle_timer_css = css`.ui-idle-host { display: contents; }.ui-idle { position: fixed; z-index: 75; right: 16px; bottom: 16px; width: min(360px, calc(100vw - 32px)); padding: 14px 16px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius); background: var(--ui-surface); color: var(--ui-fg); box-shadow: var(--ui-shadow-lg); font: 14px/1.5 var(--ui-font); animation: ui-idle-in .2s ease-out; }.ui-idle-title { margin: 0 0 4px; font-weight: 600; }.ui-idle-text { margin: 0; color: var(--ui-muted); }.ui-idle-text b { color: var(--ui-fg); font-variant-numeric: tabular-nums; }.ui-idle-bar { height: 4px; margin: 10px 0; overflow: hidden; border-radius: 999px; background: var(--ui-subtle); }.ui-idle-fill { height: 100%; background: var(--ui-warning); transition: width 1s linear; }.ui-idle-btn { height: 34px; padding: 0 14px; border: 0; border-radius: var(--ui-radius-sm); background: var(--ui-accent); color: var(--ui-accent-fg); font: 500 14px/1 var(--ui-font); cursor: pointer; }.ui-idle-btn:hover { background: color-mix(in srgb, var(--ui-accent) 88%, var(--ui-fg)); }.ui-idle-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }@keyframes ui-idle-in { from { opacity: 0; transform: translateY(8px); } }@media (prefers-reduced-motion: reduce) { .ui-idle { animation: none; } .ui-idle-fill { transition: none; } }`component IdleTimer(seconds: int = 300, warn: int = 60, title: str = "Are you still there?", message: str = "For your safety you will be signed out in {s} seconds.", button: str = "Stay signed in", on_idle = null, on_warn = null, on_active = null, class: str? = null) { style global idle_timer_css state last = now() state at = now() state fired = false state warned = false let idle = floor((at - last) / 1000) let left = max([0, seconds - idle]) let warning = !fired && warn > 0 && left <= warn if !fired { every(1000, () => { at = now() if at - last >= seconds * 1000 { fired = true warned = false on_idle && on_idle() } else if warn > 0 && !warned && (at - last) / 1000 >= seconds - warn { warned = true on_warn && on_warn() } }) } let wake = (_e = null) => { let t = now() if t - last > 500 || fired { let was = warned || fired last = t at = t fired = false warned = false if was && on_active != null { on_active() } } } <div class="ui-idle-host" @mount={(_el) => { let d = js("document") for ev in ["mousemove", "mousedown", "keydown", "touchstart", "wheel", "pointerdown"] { d.addEventListener(ev, wake, { passive: true, capture: true }) } () => { for ev in ["mousemove", "mousedown", "keydown", "touchstart", "wheel", "pointerdown"] { d.removeEventListener(ev, wake, { capture: true }) } } }}> {warning && <div class={ui_cls(["ui-idle", class])} role="alertdialog" aria-labelledby="ui-idle-title" aria-describedby="ui-idle-text"> <p.ui-idle-title id="ui-idle-title">{title}</p> <p.ui-idle-text id="ui-idle-text">{raw(escape(message) | replace("{s}", `<b>${left}</b>`))}</p> <div.ui-idle-bar aria-hidden="true"><div.ui-idle-fill style={`width: ${left * 100 / warn}%`}></div></div> <button.ui-idle-btn type="button" @click={wake}>{button}</button> </div>} </div>}Related
ReadingTime"4 min read", worked out from a text or a word count, with the wording and the reading speed yours to set.6 views · 8 downloadsConfettiA burst of confetti over the page on a canvas, fired by a button, on mount, or by confetti_fire().6 views · 10 downloadsDraggableA box dragged anywhere in its container with the pointer or the arrow keys, kept inside it, with on_move(x, y).8 views · 7 downloadsInfiniteScrollCalls on_more() when the end of its list scrolls into view, with Loading more… and That's all states and a Load more button.6 views · 10 downloadsJwtInspectorDecodes a JSON Web Token locally, reads its dates relative to now, warns on alg none and a missing exp, and says the signature was not verified.3 views · 7 downloadsKeyboardShortcutsBinds key combinations such as ⌘S or ? to functions on the page, leaving text boxes alone, and lists them.6 views · 8 downloads
Thread
Thread 0 posts
On IdleTimer: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.