npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

phonetic-board

v1.0.0

Published

English IPA (International Phonetic Alphabet) keyboard for H5 and PC. Framework-agnostic, zero dependencies, TypeScript.

Readme

phonetic-board

英语国际音标(IPA)键盘,原生 TypeScript 实现,零框架依赖(不依赖 Vue / React / jQuery 等)。

键盘只负责“展示 + 把按下的按键交给你的回调”,不关心输入框、不改动 DOM、不自动插入文本 —— 插入逻辑完全由使用方决定。

  • H5 页面:从底部弹出的键盘,宽度占满浏览器,高度固定。
  • PC 页面:固定尺寸的悬浮键盘,自动出现在锚点元素的上方或下方。

工具不会自动探测终端类型,由你根据运行环境自行选择调用方式:传入元素 = PC 悬浮模式,不传元素 = H5 底部弹出模式。


特性

  • 🎹 按设计稿还原的 5 行音标布局:10 / 9 / 9 / 7 / 7 键,含收起 / 关闭键、/、:、空格、主重音 ˈ、次重音 ˌ、退格
  • 📱 H5 底部弹出:整屏宽度、固定高度、适配 iPhone 底部安全区
  • 🖥 PC 悬浮:自动判断上下方位(下方空间够 → 下方;下方不够而上方够 → 上方;上下都不够 → 仍然在下方并贴边夹紧),页面滚动 / 窗口尺寸变化时自动跟随
  • 🔔 单实例:全局同一时刻只存在一个键盘,重复打开会复用实例并更新配置
  • 🎨 支持 light / dark 主题,可通过 CSS 变量与 ::part() 深度定制
  • 🧩 纯 TS + Shadow DOM,样式与宿主页面完全隔离
  • ♿ 键盘可聚焦、支持 Esc 关闭、prefers-reduced-motion 下自动关闭动画
  • 🧪 开箱即用的单测(Vitest + jsdom)与本地调试页(Vite)

键盘布局

| 行 | 按键 | | --- | ------------------------------------------- | | 1 | ɑ ɔ ɜ i u ʌ ɒ ə ɪ ʊ | | 2 | e æ a p b t d k g | | 3 | f v s z θ ð ʃ ʒ h | | 4 | r l m n ŋ w j(行高更紧凑) | | 5 | ⌄ / ×(收起 / 关闭) / : ␣(空格) ˈ ˌ ⌫ |

使用环境

| 项 | 要求 | | -------- | -------------------------------------------------------------------------- | | 运行时 | 浏览器(含移动端 H5);需要 Shadow DOM、Element.prototype.closest | | 语言 | TypeScript ≥ 4.5 / ES2019 目标 | | 浏览器 | Chrome / Edge ≥ 79、Safari ≥ 15.4、Firefox ≥ 72、iOS Safari ≥ 15.4(Shadow DOM + ::part()) | | 构建工具 | 任意(Vite / webpack / Rollup / esbuild…);同时提供 ESM / CJS / IIFE 产物 | | 服务端 | ❌ 不支持 SSR 渲染(需在浏览器环境中调用 openKeyboard) |

安装

npm install phonetic-board
# 或
pnpm add phonetic-board
yarn add phonetic-board

CDN 直接引入(全局变量 PhoneticBoard):

<script src="https://unpkg.com/phonetic-board/dist/index.global.js"></script>
<script>
  PhoneticBoard.openKeyboard({ onKey: (p) => console.log(p) });
</script>

快速开始

H5:底部弹出

import { openKeyboard, closeKeyboard } from 'phonetic-board';

const input = document.querySelector<HTMLInputElement>('#field')!;

input.addEventListener('focus', () => {
  openKeyboard({
    onKey: ({ type, key }) => {
      // 插入逻辑由使用方决定
      input.value = type === 'delete' ? input.value.slice(0, -1) : input.value + key;
    },
  });
});

document.querySelector('#done')!.addEventListener('click', () => {
  closeKeyboard();
});

PC:悬浮在输入框上方 / 下方

import { openKeyboard, closeKeyboard } from 'phonetic-board';

document.querySelectorAll<HTMLInputElement>('.ipa-field').forEach((field) => {
  field.addEventListener('click', () => {
    openKeyboard({
      anchor: field, // ← 传入元素即 PC 悬浮模式
      onKey: ({ type, key }) => {
        field.value = type === 'delete' ? field.value.slice(0, -1) : field.value + key;
        field.focus();
      },
      // 键盘已打开时再次调用上面的代码,会自动移动到新元素旁边
    });
  });
});

也支持简洁的位置参数写法:

openKeyboard(anchorElement, ({ type, key }) => {
  /* ... */
});

⚠️ 注意:anchor 需要是已经挂载到文档中的元素(能拿到 getBoundingClientRect())。锚点元素被移除时键盘会自动关闭。

API

openKeyboard(options?: PhoneticBoardOptions): PhoneticBoardHandle

openKeyboard(anchor?: Element | null, onKey?: PhoneticKeyHandler): PhoneticBoardHandle

打开音标键盘。传入元素 → PC 悬浮模式;不传 → H5 底部弹出模式。

全局只有一个实例,重复调用会复用同一个键盘并用新配置覆盖(可用来切换主题、锚点、方位等)。

返回的句柄(PhoneticBoardHandle)方法与下列全局方法等价:

| 方法 | 说明 | | --------------------------------- | ----------------------------------------------------------- | | openKeyboard(options?) | 打开键盘(可带锚点元素 + 回调 + 配置) | | closeKeyboard() | 关闭键盘 | | isKeyboardOpen() | 是否处于打开状态 | | isKeyboardCollapsed() | 是否处于收起(胶囊)状态 | | setKeyboardCollapsed(collapsed) | 设置收起状态(始终是收起为胶囊,不受 collapseBehavior 影响) | | toggleKeyboardCollapsed() | 切换收起状态 | | updateKeyboardPosition() | 手动重算位置(PC 模式下滚动 / 尺寸变化会自动调用,无需手动) | | getKeyboard() | 获取当前实例句柄(未创建时为 null) | | destroyKeyboard() | 关闭并销毁实例(组件卸载、SPA 路由切换、测试清理时使用) |

句柄实例还提供:open()、close()、isOpen()、isCollapsed()、setCollapsed()、update(),以及只读属性 mode('h5' | 'pc')与 element(宿主元素,未打开时 null)。

配置项 PhoneticBoardOptions

| 选项 | 类型 | 默认值 | 说明 | | ------------------------------- | --------------------------------------- | ------------------------------- | ------------------------------------------------------------------------ | | anchor | Element \| null | null | 锚点元素。传入 → PC 悬浮;不传 → H5 底部弹出 | | onKey | (payload) => void | — | 点击按键的回调 | | placement | 'auto' \| 'top' \| 'bottom' | 'auto' | PC 方位策略;auto 按可用空间自动判断,强制方位同样会被夹紧在视口内 | | offset | number | 8 | 键盘与锚点(或视口边缘)的距离,单位 px | | margin | number | 8 | 键盘与视口边缘的最小留白,单位 px | | mask | boolean | false | H5 模式下是否显示半透明遮罩(点击遮罩会关闭键盘) | | dismissOnOutsideClick | boolean | PC true / H5 false | 点击键盘外部是否关闭 | | shouldDismissOnOutsideClick | (event: Event) => boolean | — | 外部点击判定前的过滤:返回 false 表示“这次点击不算外部点击”,不会关闭键盘 | | closeOnEscape | boolean | true | 按 Esc 是否关闭 | | collapseBehavior | 'pill' \| 'close' | H5 'pill' / PC 'close' | 第 5 行左侧那颗键的行为与图标:'pill' 显示 ⌄、收起为可再次点开的胶囊;'close' 显示 ×、直接关闭 | | theme | 'light' \| 'dark' | 'light' | 主题 | | zIndex | number | 9999 | 宿主元素层级 | | container | HTMLElement \| null | document.body | 挂载容器 | | className | string | — | 追加到宿主元素上的类名,便于外部定制 | | safeArea | boolean | true | H5 模式是否适配底部安全区(刘海屏) | | animation | boolean | true | 是否启用入场 / 收起动画 | | onClose | () => void | — | 键盘关闭后的回调 | | onCollapseChange | (collapsed: boolean) => void | — | 收起状态变化的回调 |

shouldDismissOnOutsideClick 使用示例(把页面上的工具栏排除在“外部”之外):

openKeyboard({
  theme: 'dark',
  anchor: field,
  onKey,
  shouldDismissOnOutsideClick: (event) =>
    !(event.target as Element)?.closest('#board-toolbar'),
});

回调载荷 PhoneticKeyPayload

interface PhoneticKeyPayload {
  type: 'insert' | 'delete'; // insert = 插入字符;delete = 退格
  key: string;               // 要插入的字符;delete 时为空字符串 ''
  id: string;                // 按键标识
}

| 按键 | type | key | id | | ------------------------------------ | -------- | ------ | ---------------------------------------------------- | | 音标 / 字母键 | insert | 字符 | 与字符相同,如 'ɑ'、'θ'、'ŋ' | | / : | insert | / :| 'slash'、'colon' | | 空格 | insert | ' ' | 'space' | | 主重音 / 次重音 | insert | ˈ ˌ| 'stress-primary'、'stress-secondary' | | 退格 | delete | '' | 'backspace' | | 收起 / 关闭键 | — | — | 不触发 onKey(内部处理收起 / 关闭) |

行为说明

  • 单实例:无论调用多少次 openKeyboard,页面中始终只有一个键盘;H5 与 PC 模式之间来回切换也会复用同一实例。
  • PC 方位判定:以锚点元素的视口矩形为准 —— 下方剩余空间 ≥ 键盘高度 + margin → 显示在下方;否则上方够就显示在上方;上下都不够 → 仍然显示在下方并夹紧在视口内。页面滚动、窗口 resize、visualViewport 变化时自动重算。
  • H5 布局:position: fixed 贴底,宽度占满视口,按键尺寸按视口宽度等比缩放(上限 720px),因此不同屏宽下比例与设计稿一致。
  • 收起 / 关闭键:第 5 行左侧那颗功能键的行为与图标由 collapseBehavior 决定 —— PC 默认 'close'(显示 ×,点击即关闭,PC 悬浮面板没有可收起的去处);H5 默认 'pill'(显示 ⌄,收起为一个可点击的小胶囊,点它即可展开)。两种模式都可用该选项覆盖。
  • setKeyboardCollapsed() 与它无关:这个方法始终是“收起为胶囊”,方便你自己做收起 / 展开按钮。
  • 可访问性:按键为原生 <button>,支持 Tab / Enter / Space;键盘容器 role="group",Esc 关闭。

样式定制

键盘运行在 Shadow DOM 中,宿主页面样式不会影响它。可用 CSS 变量调整视觉:

/* 主题变量既可写在宿主元素上(通过 className) */
.phonetic-board-host {
  --pb-kw: 52px;          /* 基准键宽,其他尺寸按比例推导 */
  --pb-bg: #e9ecf2;       /* 面板背景 */
  --pb-key-bg: #ffffff;   /* 普通键背景 */
  --pb-key-fn-bg: #b2b7c4;/* 功能键背景 */
  --pb-text: #131519;     /* 键位文字颜色 */
  --pb-panel-radius: 12px;
  --pb-shadow: 0 12px 30px rgba(17, 24, 51, 0.22);
}

全部可用变量:--pb-kw、--pb-kh、--pb-gap-x、--pb-gap-y、--pb-pad、--pb-font-size、--pb-key-radius、--pb-panel-radius、--pb-pill-size、--pb-bg、--pb-key-bg、--pb-key-bg-active、--pb-key-fn-bg、--pb-key-fn-bg-active、--pb-text、--pb-key-fn-text、--pb-focus、--pb-backdrop、--pb-shadow。

需要更细粒度时使用 ::part():

.phonetic-board-host::part(panel) { /* 面板 */ }
.phonetic-board-host::part(row) { /* 行 */ }
.phonetic-board-host::part(key) { /* 音标键 */ }
.phonetic-board-host::part(function-key) { /* 功能键 */ }
.phonetic-board-host::part(pill) { /* 收起后的胶囊 */ }
.phonetic-board-host::part(backdrop) { /* H5 遮罩 */ }

提示:className 传入的类名加在宿主元素上,用它写变量最方便;::part() 只能挂在宿主元素上。

框架集成

包本身不依赖任何框架,下面是常见写法(仅示意集成方式):

Vue 3

<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue';
import { openKeyboard, destroyKeyboard, type PhoneticKeyPayload } from 'phonetic-board';

const field = ref<HTMLInputElement | null>(null);

function onFocus() {
  if (!field.value) return;
  openKeyboard({
    anchor: field.value,
    onKey: ({ type, key }: PhoneticKeyPayload) => {
      field.value!.value = type === 'delete' ? field.value!.value.slice(0, -1) : field.value!.value + key;
    },
  });
}

onBeforeUnmount(() => destroyKeyboard());
</script>

<template>
  <input ref="field" @focus="onFocus" />
</template>

React

import { useEffect, useRef } from 'react';
import { openKeyboard, destroyKeyboard } from 'phonetic-board';

export function IpaInput() {
  const ref = useRef<HTMLInputElement>(null);

  useEffect(() => destroyKeyboard, []);

  return (
    <input
      ref={ref}
      onFocus={() => {
        const el = ref.current;
        if (!el) return;
        openKeyboard({
          anchor: el,
          onKey: ({ type, key }) => {
            el.value = type === 'delete' ? el.value.slice(0, -1) : el.value + key;
          },
        });
      }}
    />
  );
}

本地开发与调试

npm install
npm run dev        # 启动 Vite 调试服务器 http://localhost:5173

内置三个调试页面:

| 页面 | 用途 | | ------------------------------------- | ----------------------------------------------------------------------------- | | http://localhost:5173/ | 总览:直接体验 H5 / PC 两种模式 | | http://localhost:5173/h5.html | H5 底部弹出调试:手机模拟器、主题、遮罩、安全区、插件开关;加 ?auto 自动打开 | | http://localhost:5173/pc.html | PC 悬浮调试:锚点方位、滚动翻转、间距、主题、× 关闭、折叠键行为、外部点击关闭 |

其他脚本:

npm run typecheck  # tsc --noEmit
npm run test       # Vitest 单测(只跑一次)
npm run test:watch # 监听模式
npm run build      # tsup 打包 JS + tsc 生成类型声明,产物在 dist/
npm run build:demo # 打包调试页,产物在 dist-demo/
npm run prepublishOnly 会依次执行 typecheck + test + build

构建产物

| 文件 | 格式 | 用途 | | ---------------------- | ---------------- | --------------------------------------- | | dist/index.js | ESM | import(bundler / "type": "module") | | dist/index.cjs | CommonJS | require / Node 工具链 | | dist/index.global.js | IIFE(全局变量) | 浏览器 <script>、unpkg / jsDelivr | | dist/index.d.ts | 类型声明 | TypeScript 提示(入口,配套 dist/*.d.ts 为各模块声明) |

常见问题

为什么键盘没有出现在输入框旁边? anchor 必须是已挂载、可见(非 display: none)的元素;若元素不可见则拿不到尺寸,键盘会退化为贴视口显示。

为什么 H5 上键盘挡住了输入框? 本包不会滚动页面,建议在打开键盘后自行 anchor.scrollIntoView({ block: 'center' }),或改用 PC 悬浮模式。

点了键盘,输入框光标消失了? 键盘在 pointerdown 时调用了 preventDefault() 以保持焦点,请在回调末尾重新 focus() 目标输入框(见示例)。

可以同时打开两个键盘吗? 不可以,这是刻意设计:全局单实例,重复调用只会更新配置。


English Summary

phonetic-board is a framework-agnostic, TypeScript-only English IPA (International Phonetic Alphabet) on-screen keyboard for H5 and desktop pages.

  • Pass an anchor element → PC mode: a fixed-size floating panel that is automatically placed below the anchor when there is enough room, otherwise above, and clamped to the viewport when neither side fits.
  • Pass no anchor → H5 mode: a fixed-height bottom sheet spanning the full viewport width.
  • The library never edits your inputs: every key press is delivered to your onKey({ type, key, id }) callback (type is insert or delete).
  • Global API: openKeyboard, closeKeyboard, isKeyboardOpen, isKeyboardCollapsed, setKeyboardCollapsed, toggleKeyboardCollapsed, updateKeyboardPosition, getKeyboard, destroyKeyboard. Only one keyboard can exist at a time.
  • Built with Shadow DOM (style-isolated), ships ESM / CJS / IIFE bundles plus type declarations, and can be themed through CSS custom properties and ::part().
npm install phonetic-board
npm run dev   # http://localhost:5173 debug pages for H5 and PC

License

MIT