Skip to content

feat(picker): Visual HID/behavior picker built on upstream PR #159#4

Merged
numachang merged 6 commits into
mainfrom
feature/visual-picker
May 23, 2026
Merged

feat(picker): Visual HID/behavior picker built on upstream PR #159#4
numachang merged 6 commits into
mainfrom
feature/visual-picker

Conversation

@numachang

Copy link
Copy Markdown
Owner

Summary

Personal-fork visual rework of the binding picker, layered on top of upstream zmkfirmware/zmk-studio#159 (Andrew Kannan, @awkannan).

  • Adopts PR Grid Picker for HID Usage zmkfirmware/zmk-studio#159's category-tabbed HID grid (453980d) with attribution preserved.
  • Adds a cross-tab search bar above the HID grid and replaces the layer-id <select> with a wrap-friendly button row (1d7de0e).
  • Behavior selector: dropdown → flat chip grid grouped by tier (ZMK Standard / Firmware Extension) and category (Basic / Layer / Hold-Tap / Mouse / System / Custom / Other) (931edf0).
  • HID grid: layer ANSI 60% / TKL function+nav cluster / numpad layouts on top of the category tabs, plus locale-neutral labels for Intl1-6 / Lang1-5 (the per-region glyphs were misleading on non-JP keyboards — left for a future locale overlay) (0e226bf).
  • Keymap preview: behavior-name badge, hold-tap stack (hold faded above bold tap), paired short labels (1 ! → stacked), shrink-to-fit for 5+ glyph labels, rotation-aware bounding box (rotated thumb keys no longer clip), and a notify() toast when the firmware rejects setLayerBinding (c3b5cad).
  • Follow-up fixes from self-review: undo path no longer silently swallows firmware rejections; activeParam resets to p1 when the active tab disappears after a behavior swap (50c47a6).

Upstream stance

This is a personal fork, intentionally minimal-diff and Apache-2.0. Apache notices and @awkannan's authorship for PR zmkfirmware#159 are preserved. Individual pieces will be proposed upstream as separate PRs (rotation bbox fix, undo-rejection toast, etc. as small standalone bug fixes; the chip grid and physical layouts as opinionated UX proposals).

Test plan

Open npm run dev in real Chrome/Edge (Web Serial / BLE will not work in VSCode's embedded browser):

  • Pick &kp → HID grid auto-jumps to the Basic tab and renders the ANSI 60% layout
  • Toggle a modifier (e.g. Shft) → paired keys collapse to the shifted glyph (1 !!)
  • Pick &mt LSHFT A → keymap preview shows hold faded above bold tap, Hold/Tap tabs appear in the param picker
  • Pick a 2-param behavior, switch to the Tap tab, then swap to a 1-param behavior → no stale selectedKey warning
  • Type caps in the search bar → flat results grid filters across all tabs; clearing restores per-tab view
  • Open a layout with rotated thumb keys (Corne, Lily58) → auto zoom no longer clips the rotated keys
  • Assign a behavior the firmware can't write (e.g. mouse_move on a build without it) → error toast appears with hint text
  • Trigger the same rejection via Ctrl+Z on a successful binding restored to a now-rejected one → undo error toast appears (previously silent)

numachang and others added 6 commits May 23, 2026 10:15
…picker

Squashes upstream zmkfirmware#159 by @awkannan into a single
commit. Replaces the HID usage dropdown with a categorized tab grid
(Letters / Numbers + Punctuation / Function + Navigation / Numpad /
Apps/Media/Special / International / Other) using react-aria-components
Tabs, and keeps the implicit modifier checkboxes above the grid.

- Renames src/hid-usage-name-overrides.json to src/hid-usage-metadata.json
  and extends entries with a "category" field plus richer labels (incl.
  Japanese/Korean IME-related keys).
- Adds hid_usage_get_metadata() that falls back to the canonical name
  stripped of the "Keyboard " prefix when no override is present.
- Updates HidUsageLabel to consume the new metadata accessor.

Credit: zmkfirmware#159 (Andrew Kannan, @awkannan).
Apache-2.0 license preserved.

Co-Authored-By: Andrew Kannan <andrew.kannan@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Layered on top of upstream PR zmkfirmware#159's category-tabbed grid:

- HidUsagePicker: a global filter input above the tabs. While the field
  has content, the tab list is replaced by a flat grid of all matching
  keys (matched against canonical HID name, short/med/long override
  labels, and category). The per-tab "Other" ComboBox is kept untouched
  so the upstream layout still works when the filter is empty.

- ParameterValuePicker: the layer-id <select> is replaced with a
  wrap-friendly row of buttons styled like the keycode grid, so picking
  a target layer for `mo` / `lt` / `to` matches the visual feel of the
  rest of the picker.

- keymap-parser: re-point the override JSON import at the renamed
  hid-usage-metadata.json (PR zmkfirmware#159 deleted the old name).

Co-Authored-By: Claude <noreply@anthropic.com>
Replace the single behavior dropdown with a flat chip grid grouped by
tier ("ZMK Standard" / "Firmware Extension") and category
("Basic / Layer / Hold-Tap / Mouse / System / Custom / Other"). Chips
within a category are reordered so the most common behaviors land first
("Key Press" first, "Transparent / None" last). The block label uses a
shared uppercase-tracked style so it lines up with the keycode picker.

- BehaviorParametersPicker: show Hold/Tap tabs only for 2-param bindings
  with a HID-typed param2, fall through to the picker directly for
  1-param, and disable the field for 0-param behaviors. Defaults to the
  first metadata set so param2 is visible even when param1 = 0.
- ParameterValuePicker: drop the native <select> for layer ids in favor
  of a wrap-friendly button row styled to match the keycode grid.

Co-Authored-By: Claude <noreply@anthropic.com>
Layer ANSI 60% / TKL function+nav cluster / numpad layouts on top of
upstream PR zmkfirmware#159's tab grid, so picking a keycode reads like a real
keyboard. Modifiers and CapsLk fall back to short labels ("L Shft",
"L Ctrl") and paired short labels ("1 !", "[ {") are stacked top-small /
bottom-large to match the physical legend.

- HidUsagePicker: split into "+ MODIFIER" (eight modifier column) and
  "KEY" (search + tabs + visual grid). Tabs: Basic (ANSI 60%),
  Function + Nav (TKL cluster), Numpad, Apps/Media/Special,
  International, Other. All tabs share a fixed min/max height so the
  panel doesn't jump when switching. Auto-jumps to the tab that holds
  the current value (or Basic when value is 0).
- Search bar above the tabs filters across every category and renders a
  flat result grid while populated; clears restore the per-tab view.
- hid-usage-metadata.json: drop JIS-only sub-labels from Intl1-6 and
  Lang1-5. Locale-specific glyphs were misleading on non-JP keyboards
  (e.g. Lang1/Lang2 type Hangul per spec but trigger Henkan/Muhenkan on
  JIS layouts), so labels stay neutral until a proper i18n overlay is
  introduced.

Co-Authored-By: Claude <noreply@anthropic.com>
Make the keymap preview faithful to the bound behavior at a glance and
keep the rendering stable across zoom, rotation, and key width.

- Key + Keymap: render the behavior name in a rounded badge at the top
  of each cap (clears with pt-3.5; no pad when no badge). Hold-tap
  bindings stack the hold value small/faded above the bold tap value;
  layer-type params render the layer name directly; constants and raw
  values get type-aware text sizes. Drop the hover scale/3D translate
  so popped keys don't overlap their neighbors.
- HidUsageLabel: render paired short labels ("1 !" → top-small "!" /
  base "1") and collapse to the shifted glyph alone when an implicit
  Shift modifier is set ("LS(N2)" → "@"). Auto-shrink labels longer
  than 4 glyphs so values like "Lang1" fit in 1U keys without ellipsis.
  Add a `compact` mode used by hold-tap previews where vertical room is
  scarce.
- PhysicalLayout: bbox the layout with rotation taken into account so
  rotated thumb keys no longer clip, and switch the scale wrapper to
  observe only the parent (element observation caused a feedback loop
  on zoom-to-fit).
- Keyboard: pin the bottom panel's grid row to a fixed width with
  overflow auto so picker tab changes don't jump the layout. Surface
  setLayerBinding rejections via a toast that names the behavior and
  hints that it may not be writable from Studio in this firmware build.

Co-Authored-By: Claude <noreply@anthropic.com>
Two follow-ups from a self-review of the visual-picker branch.

- Keyboard: the undo path of setLayerBinding had an empty else, so a
  firmware-rejected Ctrl+Z would silently leave the UI out of sync with
  the device. Mirror the do-path notify() with an undo-specific message.
- BehaviorParametersPicker: when the user switches from a 2-param
  behavior to a 1-param one while the Tap tab is active, activeParam
  stays at "p2" and feeds <Tabs> a key for a tab that no longer exists.
  Snap it back to "p1" via useEffect when hasParam2 flips false.

Co-Authored-By: Claude <noreply@anthropic.com>
@numachang numachang merged commit 7f7709d into main May 23, 2026
@numachang numachang deleted the feature/visual-picker branch May 23, 2026 07:50
numachang added a commit that referenced this pull request Jun 7, 2026
- formatBinding: when param1 matches no metadata set, emit just the behavior
  reference rather than borrowing the first set's params (avoids showing a
  param2 that doesn't apply). [review #1]
- Behavior tiles are single-select, so make them `role="radio"` inside a
  `radiogroup` (consistent with the residual behavior chips) instead of
  `aria-pressed` toggles; native button + `title` keeps the visible label and
  drops the now-redundant tooltip. Search-result tiles get their own
  radiogroup. [review #2]
- Annotate the two intentional exhaustive-deps effects with
  `eslint-disable-next-line` so the warnings clear and the intent is explicit.
  [review #4]

Review #3 (declaration-order of the two tab-select effects) was info-only / no
change requested; left as-is.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant