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

@graphql-workbench/query-list

v0.1.0

Published

A web component that lists GraphQL queries with status and row actions

Readme

@graphql-workbench/query-list

<gwb-query-list> is a framework-agnostic web component that lists GraphQL queries. Each row shows the query name, an optional status icon, and consumer-defined actions.

Installation

npm install @graphql-workbench/query-list

@preact/signals-core is a peer dependency.

Usage

<gwb-query-list id="queries" label="Saved queries"></gwb-query-list>
<script type="module">
  import '@graphql-workbench/query-list';

  const list = document.getElementById('queries');
  list.queries = [
    { id: 'get-user', name: 'GetUser', status: 'complete' },
    { id: 'list-repos', name: 'ListRepositories', status: 'running' },
    { id: 'create-issue', name: 'CreateIssue', status: 'error' },
    { id: 'update-profile', name: 'UpdateProfile' },
  ];
  list.actions = [
    { id: 'run', label: 'Run', icon: 'M8 5v14l11-7z' },
    { id: 'delete', label: 'Delete' },
  ];
  list.selectedId = 'get-user';
  list.addEventListener('gwb-query-select', ({ detail }) => {
    console.log('open', detail.id);
  });
  list.addEventListener('gwb-query-action', ({ detail }) => {
    console.log(detail.actionId, detail.queryId);
  });
</script>

The package also publishes a standalone UMD bundle, dist/query-list.umd.js, which registers the element and exposes a QueryList browser global.

Properties

| Property | Attribute | Description | | ------------ | ------------- | --------------------------------------------------------------- | | queries | none | The queries to list. Assign a new array to update them. | | actions | none | The row actions, in toolbar order. | | selectedId | selected-id | The selected query id, or null. Reflected. | | theme | theme | 'auto' (default, follows the system), 'light', or 'dark'. | | label | label | Accessible name of the list. Defaults to "Queries". | | store | none | An optional shared store; see Store. |

Each query needs a unique string id and a name. Its optional status is 'running', 'error', or 'complete'; omit it or use null for no icon. Statuses are announced to screen readers as text as well as shown as icons.

Assigning selectedId selects a row without emitting an event. It may name a query that hasn't loaded yet; that row is selected once it appears.

Row actions

Each action has an id, a label, and an optional icon given as SVG path data in a 24×24 viewBox, such as a path from Material Symbols. Actions with an icon show it in currentColor; actions without one show their label. Every button is named and titled " ", for example "Run GetUser".

By default every row offers every action. Give a query an actions array of action ids to limit its row, or an empty array for none:

list.queries = [
  { id: 'draft', name: 'Draft' },
  { id: 'shared', name: 'SharedQuery', actions: ['run'] },
];

The actions appear in a toolbar over the hovered row, or over the selected row while the list has keyboard focus. Activating one emits gwb-query-action; it does not change the selection. The component never performs the action itself. If the action removes the row, for example a delete, focus returns to the list.

Events

| Event | Detail | Emitted when | | ------------------ | ----------------------- | --------------------------------------------------------- | | gwb-query-select | { id } | The user selects a row with the pointer or keyboard. | | gwb-query-action | { queryId, actionId } | The user activates a row action with the pointer or keys. |

Both events bubble and cross shadow DOM boundaries. Programmatic changes never emit them.

Keyboard

| Key | In the list | In the actions toolbar | | ------------- | -------------------------------------------------- | ------------------------------------------- | | Up / Down | Select the previous or next query, wrapping around | | | Home / End | Select the first or last query | | | Tab | Move to the selected query's first action, if any | Next action; leaves the list after the last | | Shift+Tab | Leave the list | Previous action, or back to the list | | Escape | | Back to the list | | Enter / Space | | Activate the focused action |

Selection follows the keyboard, so each move emits gwb-query-select.

Theming

Set CSS custom properties on the element:

gwb-query-list {
  --gwb-ql-height: 320px;
  --gwb-ql-font-size: 14px;
  --gwb-ql-selected-bg: #0058d0;
}

The list grows with its content until --gwb-ql-height bounds it, and then it scrolls.

  • Layout and typography: --gwb-ql-height, --gwb-ql-font-family, --gwb-ql-font-size
  • Colors: --gwb-ql-bg, --gwb-ql-text-primary, --gwb-ql-text-secondary, --gwb-ql-border, --gwb-ql-selected-bg, --gwb-ql-selected-text, --gwb-ql-hover-bg, --gwb-ql-focus-ring
  • Status colors: --gwb-ql-status-running, --gwb-ql-status-error, --gwb-ql-status-complete

Parts: list, row, row-selected, status, status-running, status-error, status-complete, name, empty, actions, action, and action-icon.

Store

createQueryListStore() holds the list state in Preact signals. Assign one to the store property to share state between several lists. Import it from @graphql-workbench/query-list/store to use it without loading the web component, for example in Node or in a coordinator.

import { createQueryListStore } from '@graphql-workbench/query-list/store';

const store = createQueryListStore();
store.actions.value = [
  { id: 'run', label: 'Run', icon: 'M8 5v14l11-7z' },
  { id: 'delete', label: 'Delete' },
];
store.queries.value = [
  { id: 'get-user', name: 'GetUser', status: 'complete' },
  { id: 'list-repos', name: 'ListRepositories', status: 'running', actions: ['delete'] },
];

store.events.subscribe('selection-change', ({ id }) => {
  console.log('selected', id);
});
store.events.subscribe('action-requested', ({ queryId, actionId }) => {
  console.log(actionId, queryId);
});

store.select('get-user');
store.requestAction('get-user', 'run');
  • queries is consumer-owned. Items are never mutated; assign a new array to update them. Each item needs a unique string id and a name. When ids repeat, the first item wins. status is 'running', 'error', 'complete', or omitted for no icon.
  • actions lists every row action. An item's optional actions array of action ids limits which actions its row offers. icon is SVG path data in a 24×24 viewBox.
  • rows and selectedQuery are read-only signals derived from the inputs.
  • select(id) selects a loaded query, or clears the selection with null. It returns whether the selection changed. Pass { emitEvents: false } when applying a change you have already observed.
  • requestAction(queryId, actionId) emits action-requested when the action is available on that row, and returns false otherwise.
  • Assigning a signal directly, such as store.selectedId.value = 'get-user', never emits an event. A selectedId for a query that hasn't loaded yet is kept until it arrives.
  • Pass { theme } with a shared signal to coordinate the theme with other stores. Call dispose() when the owner is finished.

An assigned store is authoritative: the element's properties read and write it, and every list bound to it renders the same state. It stays owned by you; removing it from the element or disconnecting the element never disposes it. Store actions you call directly update every bound list without emitting DOM events. A user interaction emits its store event once and its DOM event once, from the list that handled it. Without an assigned store, each element creates its own.

Snapshots

store.snapshot() returns detached, deeply frozen JSON state: the queries (id, name, and per-row actions), selectedId, and theme. Runtime status and the list-level action definitions are not included. parseQueryListStoreSnapshot(value) validates a snapshot without changing anything, and store.restore(value) validates and then applies it in one batch without emitting events.

Showing execution status

The list does not depend on @graphql-workbench/execute-query, but its statuses map onto that component's execution states. Subscribe to the execution store's status signal and update the query you are executing:

import type { ExecuteQueryStore, ExecutionStatus } from '@graphql-workbench/execute-query';
import type { QueryList, QueryListStatus } from '@graphql-workbench/query-list';

function listStatus(status: ExecutionStatus): QueryListStatus | null {
  if (status === 'waiting' || status === 'streaming') return 'running';
  if (status === 'complete' || status === 'error') return status;
  return null; // idle and cancelled show no icon
}

function trackExecution(list: QueryList, execution: ExecuteQueryStore, queryId: string) {
  return execution.status.subscribe((status) => {
    list.queries = list.queries.map((query) =>
      query.id === queryId ? { ...query, status: listStatus(status) } : query,
    );
  });
}

trackExecution() returns the unsubscribe function; call it before tracking another query.

License

MIT