Skip to content
CodeAtlas UI

Navigation

Actions

Svelte actions from the @codeatlasdev/ui/actions subpath for outside clicks, focus trapping, autofocus, clipboard and long press.

<script>
  import { clickOutside, focusTrap, autofocus, clipboard, longpress, scrollFocusable, keyboardAware, primeKeyboard, focusTextField } from "@codeatlasdev/ui/actions";
</script>

<div use:clickOutside={() => (open = false)}>...</div>

Actions attach behavior to a plain element with use:. They are exported from @codeatlasdev/ui/actions, which keeps them out of the main bundle. Dialog, Sheet, Drawer and Popover already handle focus, Escape and outside presses through the layer engine, so reach for these actions on your own elements.

clickOutside

Calls the handler for every click that lands outside the element. It listens on the capture phase of `document`.

Click inside this area, then click outside.

Outside clicks: 0

Code
svelte
<script lang="ts">
  import { clickOutside } from '@codeatlasdev/ui/actions';

  let count = $state(0);
</script>

<div use:clickOutside={() => count++} class="box">
  <p>Click inside this area, then click outside.</p>
</div>
<p class="hint">Outside clicks: {count}</p>

<style>
  .box {
    padding: 24px;
    border-radius: var(--ca-radius-md);
    box-shadow: inset 0 0 0 1px var(--ca-outline-subtle);
    display: inline-block;
  }
  .box p, .hint { margin: 0; font-size: var(--ca-font-size-body2); }
  .hint { color: var(--ca-text-secondary); align-self: center; }
</style>

focusTrap

Keeps Tab and Shift+Tab inside the element while enabled.

Code
svelte
<script lang="ts">
  import { Button } from '@codeatlasdev/ui';
  import { focusTrap } from '@codeatlasdev/ui/actions';

  let open = $state(false);
</script>

<Button onclick={() => (open = true)}>Open trap</Button>
{#if open}
  <div use:focusTrap={open} class="trap">
    <span>Tab stays inside this box:</span>
    <Button variant="outlined">Button A</Button>
    <Button variant="outlined">Button B</Button>
    <Button variant="outlined" onclick={() => (open = false)}>Close</Button>
  </div>
{/if}

<style>
  .trap {
    flex-basis: 100%;
    padding: 20px;
    border-radius: var(--ca-radius-md);
    box-shadow: inset 0 0 0 1px var(--ca-brand-main);
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    align-items: center;
    font-size: var(--ca-font-size-body2);
  }
</style>

autofocus

Focuses the element when it mounts.

Code
svelte
<script lang="ts">
  import { Button } from '@codeatlasdev/ui';
  import { autofocus } from '@codeatlasdev/ui/actions';

  let key = $state(0);
</script>

<Button onclick={() => key++}>Remount input</Button>
{#key key}
  <input use:autofocus placeholder="Focused on mount" class="input" aria-label="Autofocused input" />
{/key}

<style>
  .input {
    min-height: var(--ca-control-height);
    flex-basis: 100%;
    padding: 8px 12px;
    border-radius: var(--ca-radius-md);
    border: 1px solid var(--ca-outline-subtle);
    background: transparent;
    color: var(--ca-text-primary);
    font: inherit;
    font-size: var(--ca-font-size-body2);
  }
</style>

clipboard

Copies the given text on click and dispatches a copy event (listen with `oncopy`) once the copy succeeded.

Code
svelte
<script lang="ts">
  import { clipboard } from '@codeatlasdev/ui/actions';

  let copied = $state(false);
  function done() {
    copied = true;
    setTimeout(() => (copied = false), 2000);
  }
</script>

<button use:clipboard={'bun add @codeatlasdev/ui'} oncopy={done} class="btn">
  {copied ? 'Copied!' : 'Copy install command'}
</button>

<style>
  .btn {
    min-height: var(--ca-control-height);
    padding: 8px 20px;
    border-radius: var(--ca-radius-md);
    border: 1px solid var(--ca-outline-subtle);
    background: var(--ca-action-hover);
    cursor: pointer;
    font: inherit;
    font-size: var(--ca-font-size-body2);
    color: var(--ca-text-primary);
  }
</style>

longpress

Dispatches a longpress event (listen with `onlongpress`) after the pointer is held for the given duration.

Code
svelte
<script lang="ts">
  import { longpress } from '@codeatlasdev/ui/actions';

  let fired = $state(false);
  function done() {
    fired = true;
    setTimeout(() => (fired = false), 1500);
  }
</script>

<button use:longpress={800} onlongpress={done} class="btn">
  {fired ? 'Long press!' : 'Press and hold (800ms)'}
</button>

<style>
  .btn {
    min-height: var(--ca-control-height);
    padding: 12px 24px;
    border-radius: var(--ca-radius-md);
    border: 1px solid var(--ca-outline-subtle);
    background: var(--ca-action-hover);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    touch-action: manipulation;
    font: inherit;
    font-size: var(--ca-font-size-body2);
    color: var(--ca-text-primary);
  }
</style>

Actions

PropTypeDefaultDescription
clickOutsideAction<HTMLElement, () => void>–Calls the handler when a click lands outside the element.
focusTrapAction<HTMLElement, boolean>–Traps Tab focus inside the element while the parameter is true.
autofocusAction<HTMLElement, { touch?: boolean } | undefined>–Focuses the element on mount without scrolling. On touch devices it only focuses when primeKeyboard() ran in the opening gesture; pass { touch: true } to focus anyway.
clipboardAction<HTMLElement, string>–Copies the text to the clipboard on click and, once the copy succeeded, dispatches a copy CustomEvent with the text as detail. Nothing is dispatched when the Clipboard API is unavailable, for example on a non-HTTPS page.
longpressAction<HTMLElement, number>–Dispatches a longpress CustomEvent after the pointer is held for the given milliseconds (default 500). Releasing or leaving the element cancels it.
scrollFocusableAction<HTMLElement>–Makes a scroll container keyboard-reachable only while it overflows: sets tabindex="0" when the content is wider or taller than the box and removes it otherwise. SimpleTable and ScrollArea use it.
keyboardAwareAction<HTMLElement>–Pads the element's bottom by --ca-kb-inset and scrolls the focused field into view when the on-screen keyboard opens. Use on composers and sticky action bars.
primeKeyboard() => void–Helper, not an action. Call synchronously in the tap handler that opens a view with an autofocused field so iOS and Android open the on-screen keyboard. No-op with a fine pointer.
focusTextField(el: HTMLElement | null | undefined, force?: boolean) => boolean–Helper used by autofocus. Focuses the element with a fine pointer, or on touch only after primeKeyboard(). Returns whether it focused.