# useImeInput

Receives IME-composed text (Japanese, Chinese, Korean, etc.) for wallpapers that want to accept text input — for example a wallpaper-resident notepad, search box, or chat input.

Unlike `useKeyboard`, which streams raw pre-IME keystrokes from every window on the desktop, `useImeInput` only delivers text that the user explicitly directs at the wallpaper. Internally Fluxlay creates a tiny invisible proxy window per wallpaper (1×1 px, fully transparent, non-activating). Calling `activate()` gives that proxy keyboard focus so IME composition is routed through it. The wallpaper window itself never becomes a key window, so the "always-in-background" property of wallpapers is preserved.

## Requires the `ime-input` permission

You must declare `ime-input` in `fluxlay.yaml`:

```yaml title="fluxlay.yaml"
schemaVersion: 1
name: My Wallpaper
slug: my-wallpaper
version: 1.0.0
permissions:
  - ime-input
```

If the permission is missing the backend rejects every IME request with HTTP 403. The hook still mounts and exposes the same API, but no streams flow and `activate()` / `deactivate()` are accepted by the SDK without effect. See [`manifest`](/en/studio/developer/reference/cli/manifest.md#permissions) for the full permission list. Note that `ime-input` and `keyboard` are intentionally separate: `keyboard` exposes global keystrokes, while `ime-input` only sees text directed at the wallpaper itself.

## Import

```tsx
import { useImeInput } from "@fluxlay/react";
```

## Signature

```tsx
function useImeInput(): ImeInputApi;

interface ImeInputApi {
  composition: string | null;
  cursor: number;
  activate: (anchor?: HTMLElement | { x: number; y: number; height: number }) => void;
  deactivate: () => void;
  onCommit: (handler: (text: string) => void) => () => void;
}
```

## Usage

```tsx
import { useImeInput } from "@fluxlay/react";
import { useEffect, useRef, useState } from "react";

function NotePad() {
  const ime = useImeInput();
  const inputRef = useRef<HTMLDivElement>(null);
  const [text, setText] = useState("");

  useEffect(() => {
    return ime.onCommit(committed => setText(prev => prev + committed));
  }, [ime]);

  return (
    <div
      ref={inputRef}
      tabIndex={0}
      onClick={() => ime.activate(inputRef.current ?? undefined)}
      onBlur={ime.deactivate}
    >
      <span>{text}</span>
      {ime.composition !== null && <span style={{ opacity: 0.5 }}>{ime.composition}</span>}
    </div>
  );
}
```

## API

| Name          | Type                                                                         | Description                                                                                                                                                                                                                                                                                                                 |
| ------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `composition` | `string \| null`                                                             | The in-progress composition text (e.g. `"か"` `"かん"` `"漢"` while typing `"漢字"`). `null` when no composition is active.                                                                                                                                                                                                 |
| `cursor`      | `number`                                                                     | Caret position within `composition`, in code points.                                                                                                                                                                                                                                                                        |
| `activate`    | `(anchor?: HTMLElement \| { x: number; y: number; height: number }) => void` | Give the proxy window keyboard focus and start receiving IME input. Pass an `HTMLElement` (typically your input ref) or an explicit viewport-relative `{ x, y, height }` rect in CSS pixels to position the IME candidate window near the caret. With no argument the candidate appears at the center of the active screen. |
| `deactivate`  | `() => void`                                                                 | Release the proxy window's focus. The hook also calls this automatically on unmount.                                                                                                                                                                                                                                        |
| `onCommit`    | `(handler) => () => void`                                                    | Register a callback for finalized text (e.g. `"漢字"`). Returns an unregister function; call it from a `useEffect` cleanup.                                                                                                                                                                                                 |

Do not pass a React `MouseEvent` or other arbitrary object directly to `activate` — only an `HTMLElement` or a `{ x, y, height }` rect is interpreted correctly. If you wire up `onClick`, wrap it (`onClick={() => ime.activate(ref.current ?? undefined)}`) instead of `onClick={ime.activate}`.

## Interaction with useKeyboard

While `activate()` is in effect, `useKeyboard` events are paused on the same wallpaper to prevent IME candidate-window keys (arrows, Enter, Esc) from double-firing as raw key events. They resume normally after `deactivate()`.

## Platform notes

- **macOS**: The proxy is an `NSPanel` with `.nonactivatingPanel` style, so the Dock highlight and menu bar do not switch when activated.
- **Windows**: The proxy is a `WS_EX_NOACTIVATE` invisible HWND, so the taskbar does not flash when focus moves.

## Limitations

- Only the committed string ends up in the wallpaper. To style the in-progress text, render `composition` yourself.
- Coordinates passed to `activate` are viewport-relative CSS pixels. The backend resolves the wallpaper window's screen position itself, so do not add `window.screenX` / `window.screenY` manually — WebKit's values for those properties are unreliable in custom-scheme wallpaper contexts.
