172 lines
6.6 KiB
Vue
172 lines
6.6 KiB
Vue
<script setup lang="ts">
|
|
// Landing hero for @robonen/platform. Static content only — no runtime APIs are
|
|
// touched at setup top-level, so this prerenders and hydrates safely.
|
|
</script>
|
|
|
|
<template>
|
|
<div class="docs-section">
|
|
<div class="prose-docs">
|
|
<h1>@robonen/platform</h1>
|
|
<p>
|
|
Platform-dependent utilities for browser and multi-runtime JavaScript — focus management,
|
|
ARIA isolation, animation lifecycle tracking, and environment-safe globals.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="prose-docs">
|
|
<p>
|
|
Most utility libraries stop at the platform boundary: the moment you need to reach for the
|
|
DOM, shadow roots, <code>aria-hidden</code>, or <code>globalThis</code>, you are on your own.
|
|
<strong>@robonen/platform</strong> fills that gap. It packages the gritty, well-tested
|
|
primitives that overlays, dialogs, and editors depend on — focus guards, tabbable-edge
|
|
detection, sibling hiding for screen readers, and CSS animation settling — and ships them
|
|
SSR-aware and dependency-free. It is the low-level layer that powers
|
|
<NuxtLink to="/primitives">@robonen/primitives</NuxtLink> and Writekit.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
<div class="rounded-xl border border-border bg-bg-elevated p-5">
|
|
<h3 class="text-sm font-semibold text-fg">
|
|
Focus, done right
|
|
</h3>
|
|
<p class="mt-1.5 text-sm text-fg-muted">
|
|
Shadow-DOM-aware active-element lookup, scroll-free focusing, and first/last tabbable-edge
|
|
detection via a fast <code class="text-accent-text">TreeWalker</code> — the bones of any focus trap.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="rounded-xl border border-border bg-bg-elevated p-5">
|
|
<h3 class="text-sm font-semibold text-fg">
|
|
Accessible isolation
|
|
</h3>
|
|
<p class="mt-1.5 text-sm text-fg-muted">
|
|
<code class="text-accent-text">hideOthers</code> marks every sibling
|
|
<code class="text-accent-text">aria-hidden</code>, ref-counted across layers, preserving
|
|
<code class="text-accent-text">aria-live</code> regions. A dependency-free port of <code>aria-hidden</code>.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="rounded-xl border border-border bg-bg-elevated p-5">
|
|
<h3 class="text-sm font-semibold text-fg">
|
|
Animation lifecycle
|
|
</h3>
|
|
<p class="mt-1.5 text-sm text-fg-muted">
|
|
Detect running animations and transitions, then settle exit animations cleanly with
|
|
fill-mode flash prevention — so unmounts wait for the CSS to finish.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="rounded-xl border border-border bg-bg-elevated p-5">
|
|
<h3 class="text-sm font-semibold text-fg">
|
|
Multi-runtime safe
|
|
</h3>
|
|
<p class="mt-1.5 text-sm text-fg-muted">
|
|
A resolved <code class="text-accent-text">_global</code> and an
|
|
<code class="text-accent-text">isClient</code> flag that work across Node, Bun, Deno, and the
|
|
browser — guards baked in so SSR never throws.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="prose-docs">
|
|
<h2>Install</h2>
|
|
</div>
|
|
|
|
<DocsCode :code="`pnpm add @robonen/platform`" lang="bash" />
|
|
|
|
<div class="prose-docs">
|
|
<h2>Subpath exports</h2>
|
|
<p>
|
|
The package splits along the platform boundary. Browser-only helpers live under
|
|
<code>/browsers</code>; runtime-agnostic helpers live under <code>/multi</code>.
|
|
</p>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Entry</th>
|
|
<th>Scope</th>
|
|
<th>What you get</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>@robonen/platform/browsers</code></td>
|
|
<td>DOM</td>
|
|
<td>Focus, tabbable edges, <code>hideOthers</code>, animation lifecycle</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>@robonen/platform/multi</code></td>
|
|
<td>Any runtime</td>
|
|
<td><code>_global</code>, <code>isClient</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="prose-docs">
|
|
<h2>Usage</h2>
|
|
<p>
|
|
A typical overlay flow: capture the focused element, hide siblings from assistive tech, drop
|
|
focus onto the first tabbable target, and tear it all down on close.
|
|
</p>
|
|
</div>
|
|
|
|
<DocsCode lang="ts" :code="`import {
|
|
getActiveElement,
|
|
getTabbableEdges,
|
|
focus,
|
|
hideOthers,
|
|
} from '@robonen/platform/browsers';
|
|
|
|
function openDialog(dialog: HTMLElement) {
|
|
// Remember where focus was, so we can restore it on close.
|
|
const previouslyFocused = getActiveElement();
|
|
|
|
// Hide everything outside the dialog from screen readers (ref-counted).
|
|
const undoHide = hideOthers(dialog);
|
|
|
|
// Move focus to the first tabbable element inside the dialog.
|
|
const { first } = getTabbableEdges(dialog);
|
|
focus(first, { select: true });
|
|
|
|
return function close() {
|
|
undoHide();
|
|
focus(previouslyFocused);
|
|
};
|
|
}`" />
|
|
|
|
<div class="prose-docs">
|
|
<p>
|
|
On the cross-runtime side, reach for a safe global and a reliable client check without
|
|
sprinkling <code>typeof window</code> guards through your code:
|
|
</p>
|
|
</div>
|
|
|
|
<DocsCode lang="ts" :code="`import { _global, isClient } from '@robonen/platform/multi';
|
|
|
|
// Works in Node, Bun, Deno and the browser — never throws in SSR.
|
|
if (isClient) {
|
|
_global.addEventListener('resize', onResize);
|
|
}`" />
|
|
|
|
<div class="rounded-xl border border-border bg-bg-subtle p-4 text-sm text-fg-muted">
|
|
<strong class="text-fg">SSR note:</strong> browser helpers touch the DOM, so call them
|
|
inside event handlers or after mount.
|
|
<code class="text-accent-text">hideOthers</code> already no-ops when <code>document</code> is
|
|
undefined, and <code>/multi</code> is import-safe everywhere.
|
|
</div>
|
|
|
|
<div class="prose-docs">
|
|
<h2>Where to next</h2>
|
|
<p>Browse the full API reference below, or jump straight to the building blocks:</p>
|
|
<ul>
|
|
<li><NuxtLink to="/platform/focus-guard">focusGuard</NuxtLink> — add boundary guards for predictable focus wrapping</li>
|
|
<li><NuxtLink to="/platform/get-tabbable-edges">getTabbableEdges</NuxtLink> — find the first and last focusable elements in a container</li>
|
|
<li><NuxtLink to="/platform/hide-others">hideOthers</NuxtLink> — isolate a subtree for assistive technology</li>
|
|
<li><NuxtLink to="/platform/on-animation-settle">onAnimationSettle</NuxtLink> — run a callback once an animation or transition finishes</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</template>
|