noteloom

Mobile & voice typing

Two ways to get content into noteloom without a physical keyboard and mouse.

Touch-first editing

Typing "/"/"@" still works on a phone keyboard, but it's not a reliable or discoverable primary path there (autocorrect, awkward key access, nothing to discover it by). On a coarse (touch) pointer, mount MobileActionBar alongside your other trigger hooks and it takes over as the touch-first equivalent, pinned above the on-screen keyboard:

jsx
import { MobileActionBar } from 'noteloom';

// Next to your other trigger hooks/components, same containerRef:
<MobileActionBar containerRef={containerRef} />

It renders nothing on a mouse/trackpad, and nothing until focus is actually inside the editor. Its contents swap based on context:

ContextShows
Caret/selection inside any blockDuplicate / Move up / Move down / Hide-Show / Delete — the mobile home for the desktop gutter's grip-handle menu.
Text selectedFormatting actions (bold/italic/underline/link) — the same surface the desktop FloatingToolbar would show, which disables itself on touch.
Collapsed caret, table cellInsert row/column.
Collapsed caret, code blockLanguage picker.
Collapsed caret, calloutColor picker.
Collapsed caret, everywhere else"+" (opens a tap-friendly block picker sheet — the same commands "/" offers), Undo/Redo, dismiss-keyboard.

The desktop block gutter is hidden entirely on touch input — there's no hover state to reveal it by, and its position sits in a page margin that doesn't exist on a narrow viewport — so both of its actions ("+" and the options menu) live in this bar instead.

How touch is detected

Deliberately not a static matchMedia('(pointer: coarse)') check (see useCoarsePointer, also exported) — a touchscreen laptop reports its trackpad as the "primary" pointer even though the touchscreen sitting right there can be used at any moment, so a pure media-query check would never show touch UI on that class of device.

Instead, the media query only supplies the initial guess (correct pre-interaction, SSR-safe); every real pointerdown afterward overrides it with that event's own pointerType, so a 2-in-1 laptop shows desktop UI while the trackpad is in use and mobile UI the instant the screen is tapped — live, no reload needed. The same signal is mirrored onto <html class="be-touch-input">, so plain CSS (the gutter-hiding rule) reacts to it too, not just MobileActionBar itself.

Trigger-menu and Select popovers reposition above the caret instead of below it when there isn't room before the keyboard, via useVirtualKeyboardInset() (also exported, in case you're positioning your own UI against the keyboard).

Not included: a touch equivalent for dragging in the block gutter to select a range of blocks — Notion, TipTap, and Editor.js all keep that gesture desktop/mouse-only too.

Voice typing

useVoiceTyping() wraps the browser's native Web Speech API (SpeechRecognition) for continuous dictation mixed with spoken structural commands — say "heading one", "new paragraph", "bulleted list", "quote", "undo", etc. while dictating, and the current block converts (or a new one is inserted) instead of those words being typed as text.

jsx
import { useVoiceTyping } from 'noteloom';

function MicButton() {
  const voice = useVoiceTyping();
  if (!voice.isSupported) return null; // e.g. Firefox — no bundled fallback, degrades to nothing
  return (
    <button onClick={() => (voice.isListening ? voice.stop() : voice.start())}>
      {voice.isListening ? 'Stop dictation' : 'Start dictation'}
    </button>
  );
}

Returns { isSupported, isListening, status, permissionState, error, start, stop }. No speech-to-text SDK is bundled — real speech recognition needs either a network round-trip to a vendor's servers or a genuinely heavy on-device model, either of which conflicts with staying zero-runtime-dependency — so this is built entirely on SpeechRecognition/webkitSpeechRecognition, and isSupported is false wherever that API doesn't exist (Firefox, most notably).

A command is only recognized when an entire finalized spoken utterance (a natural pause before/after, as reported by the Speech API itself) matches a known phrase exactly — so a command word merely mentioned mid-sentence while dictating prose is never misread as a command.

Voice typing only acts on finalized speech results, not interim/in-progress ones, and there's no explicit "command mode" trigger (push-to-command, wake phrase) yet — just pause-based auto-detection.
See Accessibility & RTL for keyboard, screen-reader, and right-to-left support.