@fast-link/core

中文文档
0.0.2发布于 2026/11/03MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 1万

DOM controller for same-origin mouse navigation on pointerdown

原始使用文档
文档目录12 节
  1. Requirements
  2. Install the package
  3. Start document navigation on pointerdown
  4. Initialize inside each iframe
  5. Know which interactions Fast Link accepts
  6. API reference
  7. createFastLink
  8. FastLinkIntent
  9. Understand event ordering
  10. Share navigation decisions in an adapter
  11. Understand controller ownership
  12. Related packages

▲ @fast-link/core

@fast-link/core starts eligible same-origin link navigation on primary mouse pointerdown. Use it with plain DOM elements or as the interaction layer for a framework adapter.

For Next.js App Router applications, use @fast-link/next instead.

For press timing and opt-outs, see when to use Fast Link.

Sites requiring WCAG Level A or AA conformance should leave Fast Link disabled. See the accessibility limitation.

Requirements

Supports Node.js 22 or later. Navigation requires a browser DOM.

Install the package

npm install @fast-link/core

The Fast Link agent skill is included at skills/fast-link/SKILL.md.

Start document navigation on pointerdown

Navigation starts on press, before a click handler can cancel it. Keep links that need confirmation, release-time checks, or expected URL dragging native by adding data-no-fast-link to the anchor.

Attach one controller to the narrowest element that contains links sharing the same navigation behavior:

import { createFastLink } from '@fast-link/core';

const root = document.querySelector<HTMLElement>('nav');
if (!root) throw new Error('Navigation root not found');

const fastLink = createFastLink(root);

Primary mouse navigation now starts on pointerdown. By default, core navigates with window.location.assign(). Call fastLink.destroy() when the root leaves the document.

Pass a caller-owned navigate function when a client router owns the destination:

import { createFastLink } from '@fast-link/core';

export function attachRouterNavigation(
  root: HTMLElement,
  navigate: (destination: URL) => void,
) {
  return createFastLink(root, {
    navigate: ({ url }) => navigate(url),
  });
}

Core validates the link before it calls the router. The adapter remains responsible for history, scroll, route state, and transitions.

Initialize inside each iframe

  • Load Fast Link and call createFastLink inside each iframe it controls. Use elements from that iframe's document.
  • For example, run the document navigation quickstart above from the iframe's own script.
  • A parent-document controller does not receive child-document events.
  • Loading Fast Link in the parent and attaching its controllers to iframe elements is unsupported.

Core accepts only a primary mouse pointerdown on an eligible same-origin link.

Accepted anchor roles:

  • No role attribute, an empty value, or only ASCII whitespace.
  • A single link token, case-insensitive, with optional surrounding ASCII whitespace.

ASCII whitespace means tab, line feed, form feed, carriage return, or space.

It leaves these interactions to the browser:

  • Touch and pen input
  • Middle, right, and modified clicks
  • Links with a target other than _self
  • Downloads, external URLs, and hash-only links
  • Anchors with other roles or role lists
  • Presses that start on button, input, label, select, summary, textarea, or an element with a button role token
  • An anchor or descendant with data-no-fast-link
  • Keyboard activation

An accepted native event is claimed so nested roots, including separately bundled copies of core, navigate once.

API reference

ExportKindPurpose
createFastLinkFunctionAttach navigation behavior to an anchor or region
runFastNavigationFunctionApply a router or document navigation decision
FastLinkControllerTypeUpdate, suppress clicks, or destroy a controller
FastLinkOptionsTypeConfigure controller eligibility and callbacks
FastLinkIntentTypeDescribe an accepted anchor and destination
FastNavigationDecisionTypeSelect router or document navigation
FastRouterDecisionTypeUse the adapter route with an optional scroll override
FastDocumentDecisionTypeLoad the validated URL with assign or replace
function createFastLink(
  root: HTMLElement,
  options?: FastLinkOptions,
): FastLinkController;

The root can be one anchor or a region containing anchors.

OptionTypeDefaultBehavior
enabledbooleantrueControls whether new pointerdowns can start navigation
navigate(intent) => voidwindow.location.assign(intent.url.href)Performs navigation after onFastNavigate; its return value is ignored
onFastNavigate(intent) => voidNoneRuns synchronously before navigate, for observation such as analytics

createFastLink copies its options. Mutating the original object has no effect.

Controller methodBehavior
update(options)Replaces every option without clearing a pending follow-up click
suppressClick(event)Applies pending suppression from an earlier framework capture handler
destroy()Removes both listeners permanently; calling it more than once is safe

Disable early navigation for new presses while preserving suppression of the pending follow-up click. Destroy the controller when the region is no longer used:

import { createFastLink } from '@fast-link/core';

export function attachNavigation(
  root: HTMLElement,
  navigate: (destination: URL) => void,
) {
  const options = { navigate: ({ url }: { url: URL }) => navigate(url) };
  const controller = createFastLink(root, options);

  return {
    setEnabled(enabled: boolean) {
      controller.update({ ...options, enabled });
    },
    destroy() {
      controller.destroy();
    },
  };
}

Pass the callbacks on each update; omitted options reset to their defaults.

Framework adapters can call suppressClick(event) before descendant capture handlers run. Pass the framework event itself, with target, detail, and preventDefault, so its prevention flag is updated. A containing controller can suppress a managed descendant's pending click, even when the containing controller has a pending click for another anchor. Only targets inside the accepted anchor and its controller root consume that pending click. Unrelated clicks and detail-zero clicks remain native, and calls after destroy do nothing. Use @fast-link/react to supply this capture binding in React rather than implementing it in an app.

FastLinkIntent

FieldTypeValue
anchorHTMLAnchorElementThe source link
hrefstringThe original, unnormalized href attribute
urlURLThe same-origin destination resolved against the document base URI

Core resolves and validates url against the document origin. A router with a base path may need to derive its route from href or application metadata on anchor.

Understand event ordering

Core checks eligibility and claims an accepted primary mouse press before calling navigation callbacks. It remembers the anchor so it can prevent the later pointer click's default navigation:

Ready
  |
  | Accept press; claim event
  | Remember anchor
  v
Pending click
  | Call onFastNavigate, then navigate
  | Options update: stay here
  |
  | Matching pointer click:
  | clear pending; prevent default
  v
Ready
  • A matching click's target must be inside both the accepted anchor and the controller root.
  • Core prevents the click's default action without stopping propagation. Click handlers can still run if their elements remain mounted, but cannot cancel navigation that has already started.
  • A follow-up click may never arrive. Each new pointerdown clears stale pending state before eligibility checks.
  • Destroying the controller clears pending state and removes its listeners.

Core's onFastNavigate observes the accepted intent. It receives no cancellation event, and its return value is ignored. If it throws, navigate still runs, then the error surfaces. If navigation also throws, the navigation error takes precedence. The follow-up click remains suppressed.

Next's onNavigate receives a cancellable event. The navigation runner's observe callback receives a frozen decision after policy selection. Navigation is still attempted if observe throws.

Clicks with detail: 0, including keyboard activation, remain native even after a pointer interaction whose click was lost. If the follow-up never arrives, a later scripted positive-detail click on the same anchor can still be suppressed; its event fields do not distinguish it from the missing follow-up.

Share navigation decisions in an adapter

Use runFastNavigation(intent, options) when an adapter must select between its router and document navigation:

import {
  createFastLink,
  runFastNavigation,
  type FastLinkIntent,
  type FastNavigationDecision,
  type FastRouterDecision,
} from '@fast-link/core';

type Adapter = {
  owns(intent: FastLinkIntent): boolean;
  observe?(decision: FastNavigationDecision): void;
  route(intent: FastLinkIntent, decision: FastRouterDecision): void;
};

export function attachAdapter(root: HTMLElement, adapter: Adapter) {
  return createFastLink(root, {
    navigate: (intent) =>
      runFastNavigation(intent, {
        defaultRouterDecision: { kind: 'router' },
        policy: () =>
          adapter.owns(intent)
            ? { kind: 'router' }
            : { kind: 'document', method: 'assign' },
        observe: (decision) => adapter.observe?.(decision),
        route: (decision) => adapter.route(intent, decision),
      }),
  });
}

The caller supplies the router implementation. Core imports no framework and makes no router-ownership assumptions.

DecisionAction
{ kind: 'router' }Use the adapter route and its proposed scroll behavior
{ kind: 'router', scroll: false }Use the adapter route and preserve scroll position
{ kind: 'document', method: 'assign' }Load the validated URL with a new history entry
{ kind: 'document', method: 'replace' }Load the validated URL and replace the current history entry

runFastNavigation uses defaultRouterDecision only when policy is absent. A policy runs once and synchronously. Extra decision properties are ignored.

The runner captures the URL before policy evaluation, validates the decision, calls observe with a frozen snapshot, and attempts one selected action. An observer error does not prevent navigation. A router error is surfaced without document fallback. A throwing policy or invalid decision attempts document assign before surfacing the policy error. If navigation also throws after a policy or observer error, the navigation error takes precedence.

Understand controller ownership

A live controller attached directly to an anchor owns its press before any containing controller, even when disabled. Destroying the anchor controller returns ownership to the containing controller. Multiple controllers and separately bundled copies share ownership registration, and cleanup removes only the destroyed controller. Nested regions retain outer-first capture ordering.

版本与发布记录

@fast-link/core 共发布 4 个版本,首次发布于 2026/10/07;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.0.2latest2026/11/0311 天前
0.0.12026/10/2717 天前
0.0.02026/10/071 个月前
0.0.0-stage2026/10/071 个月前

关于 @fast-link/core 的常见问题

@fast-link/core 是什么?

DOM controller for same-origin mouse navigation on pointerdown

如何安装 @fast-link/core?

在项目目录运行 npm install @fast-link/core,或使用 pnpm add / yarn add / bun add。

@fast-link/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fast-link/core 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@fast-link/core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@fast-link/core 可以商用吗?

@fast-link/core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fast-link/core 的下载量有多少?

根据 npm 官方下载统计,@fast-link/core 在 2026/10/15 – 2026/11/13 这 30 天内被下载 10,104 次,日均约 337 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fast-link/core 还在维护吗?

最新版本 0.0.2 发布于 2026/11/03(11 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/10/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。