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, keyboardAware, primeKeyboard } 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.

clickOutside

Calls the handler for every click that lands outside the element.

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 {
    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.

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 {
    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 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 {
    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 dispatches a copy CustomEvent with the text as detail.
longpressAction<HTMLElement, number>–Dispatches a longpress CustomEvent after the pointer is held for the given milliseconds (default 500).
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.