Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,20 @@ find the full documentation for it [in our repository](https://github.com/change

We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

## Releasing `commandmenu`

Releases take two merges. That's how Changesets works, not a fault:

1. A PR that should ship carries a changeset: run `pnpm changeset`, pick the bump, describe the change, and commit the `.changeset/*.md` file it writes.
2. When that PR lands on `master`, the Release workflow doesn't publish yet. It opens (or updates) a **"Version Packages"** PR from `changeset-release/master`. That PR bumps `packages/commandmenu/package.json` and writes `CHANGELOG.md`.
3. Merging the "Version Packages" PR runs the Release workflow again. With no changesets left, it builds, lints, typechecks and tests the package, then publishes to npm through trusted publishing and pushes the `commandmenu@x.y.z` tag and GitHub release.

Pushes without a changeset publish only if `package.json` holds a version npm doesn't have yet, which is how the 0.5.x releases went out.

The workflow needs these to be in place:

- Settings → Actions → General → Workflow permissions: "Allow GitHub Actions to create and approve pull requests" is on, or step 2 fails.
- npm → `commandmenu` → Settings → Trusted publishing points at `wojtekolek/commandmenu`, workflow `release.yml`, environment `Production`.

The "Version Packages" PR is opened with the workflow's own token, so CI doesn't run on it. Don't make CI checks required on `master` without also giving the Release workflow a token that can trigger them.
61 changes: 61 additions & 0 deletions .changeset/stable-order-aria.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
"commandmenu": minor
---

Rework the hook around a single ordered item list and add ARIA wiring.

Breaking:

- Items now expose `itemProps` (`id`, `role`, `aria-disabled`, `onClick`, `onMouseDown`, `onPointerMove`) instead of top-level `onClick`/`onPointerMove`. Spread it onto the item element.
- `selection` is replaced by `selectedId`. Scrolling needs no ref wiring from you: `listProps` now carries a `ref`, which the hook uses.
- `Group<T>` now accepts either an item type or a config array type.
- Without `groups`, async results are appended to the flat list instead of replacing it with a single group.
- `PreparedItem` keeps `disabled` so you can style it.
- Arrow keys and Enter are handled only when pressed in the search input (or on the menu element itself). Other controls inside the menu keep their own keys.
- Cmd/Ctrl and Alt combinations that match no shortcut are left to the browser. For example, Cmd+ArrowDown no longer moves the highlight.
- Shortcuts match the character the key types first, then the physical key.
- Item ids must be unique. An id repeated across groups or async results now appears only once, at its first occurrence.
- `menuProps.onKeyUp` is always a function and calls your latest `onKeyUp`.
- `isAsyncLoading` is removed, and `AsyncResultsGroup` no longer takes `isLoading`. Both only handed back the value you passed in; keep the loading state in your own component.

Fixes:

- Keyboard order now follows the rendered (grouped) order. Items missing from every group are no longer navigable and their shortcuts don't fire.
- Enter and shortcuts no longer trigger disabled items. Arrow keys skip disabled items. A disabled item no longer hides a later enabled item that has the same shortcut.
- ArrowUp works right after the list shrinks.
- The highlight follows its item by id, so async results arriving above it no longer move it to a different item.
- Pressing an item no longer moves focus out of the search input, so the keyboard keeps working after a click.
- Scrolling happens after keyboard moves and query resets, never on hover. It scrolls only the nearest scrollable container, never the page, and works inside shadow roots and iframes. It honours `scroll-margin` and `scroll-padding`.
- IME:
- Keys pressed during composition are ignored.
- So is the Enter that commits a composition, which Safari reports only as keyCode 229.
- Held-down Enter and shortcuts no longer select repeatedly.
- Shortcuts work with digits, with non-QWERTY and non-Latin layouts, and case-insensitively. Alt combinations don't trigger them.
- `onSearchChange("")` fires when the query is reset after a selection.
- Consumer `onKeyDown` runs before shortcut matching, so it can intercept.
- Inline `onKeyDown`, `onKeyUp` and `onSearchChange` no longer rebuild the list. `menuProps` and `listProps` keep their identity across renders.
- `list` no longer rebuilds when `asyncResultsGroup` is recreated around the same `items` array, or around a fresh empty one.
- Option DOM ids can't collide with the listbox id, and stay valid for item ids that contain spaces.
- `isGroupList` is no longer fooled by items that carry their own `items` field.
- Types:
- `PreparedItem` distributes over unions, so discriminated item types keep each variant's fields.
- `as const` configs keep the optional `Config` fields readable.
- Async results get their own type parameter, so they work together with an `as const` config and typed `groups`.
- The published package now includes the README and the LICENSE.

Performance:

- Each item is prepared once and keeps its identity while the query changes. Typing no longer re-renders memoized rows that stay in the list; widening the query renders only the rows that come back.
- Async results arriving render only the new rows. Groups whose rows didn't change keep their identity.
- Labels are lowercased once per config instead of on every keystroke, and the display order is computed once per `config`/`groups` rather than per keystroke.
- These interactions no longer render at all:
- hovering the highlighted item, which before cost an extra render after a change
- pressing an arrow key at the end of the list
- selecting while the menu is already reset
- The first keystroke renders once instead of twice.

Added:

- `useCommandMenu<T>` is generic over the item type. Extra fields on items are preserved in `PreparedItem<T>`.
- `searchProps` carries combobox ARIA attributes, `listProps` carries `role="listbox"`, its `id` and a `ref`.
- `sideEffects: false` and per-format type entries in `exports`.
20 changes: 11 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5

- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version-file: package.json
cache: pnpm
Expand All @@ -32,11 +32,11 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5

- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version-file: package.json
cache: pnpm
Expand All @@ -49,15 +49,17 @@ jobs:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5

- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version-file: package.json
cache: pnpm

- run: pnpm install --frozen-lockfile

- run: pnpm typecheck

- run: pnpm test
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ jobs:
id-token: write
environment: Production
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5

- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version-file: package.json
cache: pnpm
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ yarn-error.log*

# turbo
.turbo

# copied from the repo root at pack time (see packages/commandmenu "prepack")
packages/commandmenu/README.md
packages/commandmenu/LICENSE
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022-present Wojtek Olek

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading
Loading