Draggable
A box dragged anywhere in its container with the pointer or the arrow keys, kept inside it, with on_move(x, y).
Demo
Buy milk
Call Ana
Drag to move, or use the arrow keys; Shift moves further.Call Ana
Drag me by the grip.
Drag to move, or use the arrow keys; Shift moves further.The note is at 24, 24. Drag it, or Tab to it and use the arrow keys.
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/draggable.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/draggable.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/draggable.qwe
component DraggableDemoLive() { state at = [24, 24] <div.demo-stack> <div id="draggable-demo-board" style="position: relative; height: 260px; border: 1px dashed var(--ui-strong); border-radius: var(--ui-radius); background: var(--ui-subtle)"> <Draggable x={24} y={24} label="Sticky note" on_move={(x, y) => { at = [x, y] }}> <div style="width: 150px; padding: 14px; border-radius: var(--ui-radius-sm); background: var(--ui-warning-soft); border: 1px solid var(--ui-border)">Buy milk<br/>Call Ana</div> </Draggable> <Draggable x={260} y={120} handle={true} label="Panel"> <div style="width: 180px; padding: 10px 14px; background: var(--ui-surface); border: 1px solid var(--ui-border); border-top: 0; border-radius: 0 0 var(--ui-radius-sm) var(--ui-radius-sm)">Drag me by the grip.</div> </Draggable> </div> <p.demo-note>The note is at {at[0]}, {at[1]}. Drag it, or Tab to it and use the arrow keys.</p> </div>}view DraggableDemo() = <div.demo-stack> <DraggableDemoLive/> </div>Source
ui/draggable.qwe
// Draggable: a box that can be dragged anywhere inside its container, with the// pointer or with the arrow keys, and says where it ended up. For a sticker on// a canvas, a note on a board, a crop's handle, a floating panel.//// <div style="position: relative; height: 300px">// <Draggable x={40} y={20} label="Note" on_move={(x, y) => { pos = [x, y] }}><Card>Buy milk</Card></Draggable>// </div>//// The container is the nearest element around it with a position (relative),// which needs a size; the box stays inside it. x, y: where it starts, in// pixels from the container's top left corner. step: how far an arrow key// moves it (10; Shift moves ten times as far). label: what screen readers call// it, and they are told how to move it (give each on a page its own label). handle: only a grip bar along its top// starts a drag (false: anywhere on it). disabled: it stays put. on_move(x, y):// inside one of your components, called with the new place when a drag ends or// a key moves it. children: what is dragged.import ui/theme.qwe use ui_clslet draggable_css = css`.ui-drag { position: absolute; z-index: 1; touch-action: none; user-select: none; border-radius: var(--ui-radius-sm); outline: none; }.ui-drag:not(.ui-drag-handled):not(.ui-drag-off) { cursor: grab; }.ui-drag:focus-visible { box-shadow: var(--ui-focus); }.ui-drag-on { z-index: 2; cursor: grabbing !important; box-shadow: var(--ui-shadow-lg); }.ui-drag-grip { display: flex; justify-content: center; align-items: center; height: 16px; cursor: grab; color: var(--ui-muted); border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0; background: var(--ui-subtle); }.ui-drag-grip::before { content: ""; width: 24px; height: 4px; border-top: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; }.ui-drag-on .ui-drag-grip { cursor: grabbing; }`component Draggable(x: num = 0, y: num = 0, step: int = 10, label: str = "Draggable item", handle: bool = false, disabled: bool = false, on_move = null, class: str? = null, children) { style global draggable_css state px = x state py = y state grab = null // { dx, dy } from the pointer to the box's corner, while dragging state el = null let help = `ui-drag-help-${re.replace(lower(label), "[^a-z0-9]+", "-")}` let place = (nx: num, ny: num) => { let box = el.offsetParent px = round(max([0, min([nx, box.clientWidth - el.offsetWidth])])) py = round(max([0, min([ny, box.clientHeight - el.offsetHeight])])) } <div class={ui_cls(["ui-drag", grab != null && "ui-drag-on", handle && "ui-drag-handled", disabled && "ui-drag-off", class])} style={`left: ${px}px; top: ${py}px`} tabindex={disabled ? null : "0"} role="group" aria-roledescription="draggable" aria-label={label} aria-describedby={help} @mount={(me) => { el = me let d = js("document") let moving = (e) => { if grab != null { let r = el.offsetParent.getBoundingClientRect() place(e.clientX - r.left - el.offsetParent.clientLeft - grab.dx, e.clientY - r.top - el.offsetParent.clientTop - grab.dy) } } let stop = (_e = null) => { if grab != null { grab = null on_move && on_move(px, py) } } d.addEventListener("pointermove", moving) d.addEventListener("pointerup", stop) d.addEventListener("pointercancel", stop) () => { d.removeEventListener("pointermove", moving) d.removeEventListener("pointerup", stop) d.removeEventListener("pointercancel", stop) } }} @pointerdown={(e) => { let on_grip = !handle || e.target.closest(".ui-drag-grip") != null if !disabled && e.button == 0 && on_grip { e.preventDefault() el.focus() let r = el.getBoundingClientRect() grab = { dx: e.clientX - r.left, dy: e.clientY - r.top } } }} @keydown={(e) => { let by = e.shiftKey ? step * 10 : step let to = match e.key { "ArrowLeft" => [px - by, py] "ArrowRight" => [px + by, py] "ArrowUp" => [px, py - by] "ArrowDown" => [px, py + by] _ => null } if to != null && !disabled { e.preventDefault() place(to[0], to[1]) on_move && on_move(px, py) } }}> {handle && <div.ui-drag-grip aria-hidden="true"></div>} {children} <span.ui-sr id={help}>Drag to move, or use the arrow keys; Shift moves further.</span> </div>}Related
ResizableBoxA box the user resizes by dragging its corner or an edge, within limits, with the keyboard too.5 views · 5 downloadsKeyboardShortcutsBinds key combinations such as ⌘S or ? to functions on the page, leaving text boxes alone, and lists them.6 views · 8 downloadsSortableListA list put in order by dragging or with the keyboard, each move announced, the order sent with a form.5 views · 5 downloadsBrushChartA large chart of a range with an overview below, where a window dragged or moved by keys picks the range shown.6 views · 5 downloadsConfettiA burst of confetti over the page on a canvas, fired by a button, on mount, or by confetti_fire().6 views · 10 downloadsIdleTimerCalls on_idle() after a time without mouse, key, touch or scroll, with a warning dialog and a Stay button first.6 views · 7 downloads
Thread
Thread 0 posts
On Draggable: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.