A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: roving navigation

Brooke c01b8a8f 50348128

+31 -6
+1
packages/panel/package.json
··· 43 43 "parse-sse": "^0.1.0", 44 44 "prettier": "^3.4.2", 45 45 "prettier-plugin-svelte": "^3.3.3", 46 + "roving-ux": "^1.0.5", 46 47 "runed": "^0.37.1", 47 48 "sass": "^1.89.0", 48 49 "svelte": "^5.0.0",
+3
packages/panel/src/ambient.d.ts
··· 1 + declare module "roving-ux" { 2 + export declare function rovingIndex({ element, target }: { element: HTMLElement; target: string }): void; 3 + }
+2
packages/panel/src/lib/component/Tabs.svelte
··· 15 15 import { Accordion } from "melt/builders"; 16 16 import type { Snippet } from "svelte"; 17 17 import { page } from "$app/state"; 18 + import { roving } from "$lib"; 18 19 import Fa from "svelte-fa"; 19 20 20 21 let { tabs }: { tabs: { label: string; icon: IconDefinition; content: Snippet<[]> }[] } = $props(); ··· 44 45 class:flexc={isMobile()} 45 46 class="flexr gap-10" 46 47 style:width={isMobile() ? "100%" : "fit-content"} 48 + {@attach roving("button")} 47 49 > 48 50 {#each tabs as tab (tab.label)} 49 51 {@const item = accordion.getItem({ id: tab.label })}
+10
packages/panel/src/lib/index.ts
··· 1 1 import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; 2 2 import { faCircleXmark } from "@fortawesome/free-regular-svg-icons"; 3 + import type { Attachment } from "svelte/attachments"; 3 4 import { addToast } from "../routes/Toaster.svelte"; 5 + import { rovingIndex } from "roving-ux"; 4 6 5 7 export { openDialog, closeDialog } from "../routes/Dialog.svelte"; 6 8 7 9 export * as api from "./api"; 8 10 9 11 export { isMobile } from "../routes/+layout.svelte"; 12 + 13 + /// An attachment that applies roving behavior to the attached element. Allowing its children to be navigated with arrow keys. 14 + export const roving: (target?: string) => Attachment<HTMLElement> = (target) => (element) => { 15 + rovingIndex({ 16 + target: target ?? "& > a, button", 17 + element, 18 + }); 19 + }; 10 20 11 21 /** 12 22 * Trims {@paramstr} to {@param maxLength}, adding ellipses if it was too long.
+2 -2
packages/panel/src/routes/(authenticated)/Navbar.svelte
··· 9 9 import Crane from "$lib/component/Crane.svelte"; 10 10 import { onNavigate } from "$app/navigation"; 11 11 import { slide } from "svelte/transition"; 12 + import { isMobile, roving } from "$lib"; 12 13 import type { Snippet } from "svelte"; 13 14 import { page } from "$app/state"; 14 - import { isMobile } from "$lib"; 15 15 import Fa from "svelte-fa"; 16 16 import { 17 17 faMagnifyingGlass, ··· 105 105 {:else} 106 106 <div style:min-width="{navbarWidth}px"></div> 107 107 108 - <nav class:expanded bind:clientWidth={navbarWidth}> 108 + <nav class:expanded bind:clientWidth={navbarWidth} {@attach roving()}> 109 109 {@render links()} 110 110 111 111 <button class="a entry" onclick={toggleExpanded} aria-label="{expanded ? 'collapse' : 'expand'} sidebar">
+2 -1
packages/panel/src/routes/(authenticated)/projects/+page.svelte
··· 7 7 import { Debounced } from "runed"; 8 8 import hotkeys from "hotkeys-js"; 9 9 import Fa from "svelte-fa"; 10 + import { roving } from "$lib"; 10 11 11 12 const ORDER = ["healthy", "running", "exited", "paused", "down", "paused"] as LuminaryStatus[]; 12 13 ··· 69 70 <hr /> 70 71 </button> 71 72 {#if item.isExpanded} 72 - <div class="grid" {...item.content}> 73 + <div class="grid" {...item.content} {@attach roving("a.project")}> 73 74 {#each projects as project (project.name)} 74 75 <a href="/projects/{project.name}" class="project"> 75 76 <h2>
+3 -3
packages/panel/src/routes/(authenticated)/projects/[project]/ProjectStatus.svelte
··· 1 1 <script lang="ts"> 2 2 import PromiseButton from "$lib/component/PromiseButton.svelte"; 3 + import { api, closeDialog, openDialog, roving } from "$lib"; 3 4 import StatusIcon from "$lib/component/StatusIcon.svelte"; 4 5 import Tooltip from "$lib/component/Tooltip.svelte"; 5 6 import { goto } from "$app/navigation"; 6 - import { api, closeDialog, openDialog } from "$lib"; 7 7 import Fa from "svelte-fa"; 8 8 import { 9 9 faArrowsRotate, ··· 89 89 {#if project.invalid} 90 90 <div>You must fix the <a href="#compose">compose file</a> to trigger actions.</div> 91 91 {:else} 92 - <div class="flexr gap-5 wrap"> 92 + <div class="flexr gap-5 wrap" {@attach roving()}> 93 93 <PromiseButton 94 94 fit 95 95 style="outline" ··· 197 197 </div> 198 198 199 199 {#if !service.orphan} 200 - <div class="flexr center gap-5"> 200 + <div class="flexr center gap-5" {@attach roving()}> 201 201 <Tooltip placement="left" content="Start Service"> 202 202 <PromiseButton 203 203 style="a"
+8
pnpm-lock.yaml
··· 100 100 prettier-plugin-svelte: 101 101 specifier: ^3.3.3 102 102 version: 3.5.1(prettier@3.8.1)(svelte@5.53.7) 103 + roving-ux: 104 + specifier: ^1.0.5 105 + version: 1.0.5 103 106 runed: 104 107 specifier: ^0.37.1 105 108 version: 0.37.1(@sveltejs/kit@2.53.4(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.53.7)(vite@6.4.1(@types/node@25.3.5)(sass@1.97.3)(yaml@2.8.2)))(svelte@5.53.7)(typescript@5.9.3)(vite@6.4.1(@types/node@25.3.5)(sass@1.97.3)(yaml@2.8.2)))(svelte@5.53.7) ··· 1191 1194 resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} 1192 1195 engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1193 1196 hasBin: true 1197 + 1198 + roving-ux@1.0.5: 1199 + resolution: {integrity: sha512-JCvxfcLOhhw8lGP/vrfZzQdsZ0tmM2Z04TPzxPANMOjg3p80cbRyOEhUZdIOwn76bGSJA1S/TfYk+hcNA3jhLA==} 1194 1200 1195 1201 runed@0.23.4: 1196 1202 resolution: {integrity: sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==} ··· 2411 2417 '@rollup/rollup-win32-x64-gnu': 4.59.0 2412 2418 '@rollup/rollup-win32-x64-msvc': 4.59.0 2413 2419 fsevents: 2.3.3 2420 + 2421 + roving-ux@1.0.5: {} 2414 2422 2415 2423 runed@0.23.4(svelte@5.53.7): 2416 2424 dependencies: