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

@neovici/cosmoz-queue

v2.4.1

Published

A reusable queue component for master-detail views with list, split, and queue modes

Readme

@neovici/cosmoz-queue

A hooks-based UI component library for building master-detail views with list, split, and queue navigation modes. Built on @pionjs/pion and lit-html.

What is a "queue"? Not a data structure -- a UI pattern. Users work through a list of items one-by-one, like processing a queue of tasks. The component provides three view modes: a data table, a side-by-side split, and a full-screen sequential navigator.

Installation

npm install @neovici/cosmoz-queue

Peer dependencies: @pionjs/pion, lit-html, i18next.

Quick start

The queue() factory is the recommended high-level API. It composes all the internal hooks and renders the complete queue UI -- tabs, navigation, list, and detail view.

import { queue } from '@neovici/cosmoz-queue';
import { spread } from '@open-wc/lit-helpers';
import { component, useCallback } from '@pionjs/pion';
import { html } from 'lit-html';
import { ref } from 'lit-html/directives/ref.js';
import { fetchOrderDetails$ } from './api';
import type { OrderListItem } from './types';

const OrderListQueue = ({ heading }: { heading: string }) =>
  queue<OrderListItem>({
    // Title displayed above the list
    heading,

    // Unique key for persisting table column settings
    settingsId: 'order-list',

    // URL hash parameter names (enables deep-linking and back/forward)
    idHashParam: 'id',
    tabHashParam: 'qtab',

    // Async function that fetches full details for a list item.
    // Results are memoized per item identity.
    details: useCallback(
      (item: OrderListItem) => fetchOrderDetails$(item.id),
      [],
    ),

    // Render the list component. `thru` contains bindings that wire
    // the omnitable events to queue state (items, selection, clicks).
    // Spread them onto your list component.
    list: (thru, { onRef }) =>
      html`<order-list-core ${spread(thru)} ${ref(onRef)}></order-list-core>`,

    // Render the detail view. `nav` contains prev/next buttons --
    // place them in a slot or wherever navigation belongs in your view.
    view: (thru, { nav }) =>
      html`<order-view-core ${spread(thru)}>${nav}</order-view-core>`,

    // Render a loading skeleton shown while `details` resolves.
    loader: (thru, { nav }) =>
      html`<order-view-skeleton ${spread(thru)}>${nav}</order-view-skeleton>`,
  });

customElements.define('order-list-queue', component(OrderListQueue));

queue() props

| Prop | Type | Default | Description | |---|---|---|---| | heading | string | -- | Title displayed above the tabs | | settingsId | string? | -- | Key for persisting omnitable column settings | | idHashParam | string | 'qid' | URL hash param for the selected item ID | | tabHashParam | string | 'qtab' | URL hash param for the active tab | | details | (item: I) => PromiseLike<D> | -- | Fetches full details for a list item | | api | (id: string, item: I) => string | -- | Alternative to details: returns a URL, queue fetches JSON internally | | list | (thru, props) => TemplateResult | -- | Renders the list component | | view | (thru, props) => TemplateResult | -- | Renders the detail view | | loader | (thru, props) => TemplateResult | -- | Renders the loading skeleton | | pagination | Pagination? | -- | Server-side pagination config | | fallback | string? | -- | Default tab when none is set in URL hash | | split | SplitOpts? | -- | Split.js configuration (sizes, min sizes) | | afterHeading | unknown? | -- | Extra content rendered after the heading |

Use details when you control the fetch (most common). Use api when you want the queue to handle fetching via URL.

The thru bindings

The list, view, and loader render functions receive a thru object as their first argument. This object contains property and event bindings that wire the child component to queue state:

For list:

  • .settingsId, .exposedParts -- table configuration
  • @visible-items-changed, @selected-items-changed, @total-available-changed -- sync items/selection back to queue
  • @omnitable-item-click -- item click handler
  • @async-simple-action -- action completion handler

For view:

  • .item -- the current detail item (list item or resolved detail)
  • .hideActions -- whether to hide actions (true when items are selected in split mode)
  • @async-simple-action -- action completion handler

Spread these onto your component with ${spread(thru)} from @open-wc/lit-helpers.

View modes

The queue provides three tab-based view modes:

| Mode | Tab name | Behavior | |---|---|---| | List | overview | Full-width data table (cosmoz-omnitable). The default view. | | Split | split | Side-by-side: resizable list on the left, detail view on the right. Disabled on mobile. | | Queue | queue | Full-screen detail view with prev/next navigation and keyboard arrow keys. |

The active tab is synced to the URL hash (e.g. #qtab=split&id=abc-123), enabling deep-linking and browser back/forward navigation.

On mobile viewports, the split tab is disabled and falls back to queue mode automatically.

List module

The @neovici/cosmoz-queue/list entry point provides a managed cosmoz-omnitable with built-in state management, data fetching, pagination, and action rendering.

listCore()

The high-level factory that combines useListCore() + renderListCore():

import { itemClick } from '@neovici/cosmoz-queue';
import { column, listCore, style } from '@neovici/cosmoz-queue/list';
import { component, html } from '@pionjs/pion';
import { t } from 'i18next';
import type { OrderListItem } from './types';

interface Props {
  exposedParts: string;
  api: (params: { pathLocator: string }) => Promise<{
    items: OrderListItem[];
    metaData: { totalAvailable: number };
  }>;
}

const OrderListCore = ({ exposedParts, api }: Props) => {
  return listCore({
    // Unique key for persisting column settings
    settingsId: 'order-list-core',

    // CSS parts to expose for external styling
    exposedParts,

    // When false, omnitable applies its own local filtering
    noLocal: false,

    // Column definitions. The tuple is [factory, deps] -- same pattern
    // as useMemo. The factory returns an object where each key becomes
    // a column name.
    columns: [
      () => ({
        title: column({
          render: ({ name }) =>
            html`<cosmoz-omnitable-column
              name="${name}"
              title=${t('Title')}
              flex="2"
              .renderCell=${(
                _col: unknown,
                { item, index }: { item: OrderListItem; index: number },
              ) =>
                html`<a
                  @click="${itemClick({
                    index,
                    activate: ['split', 'queue'],
                  })}"
                  >${item.title}</a
                >`}
            ></cosmoz-omnitable-column>`,
        }),

        status: column({
          render: ({ name }) =>
            html`<cosmoz-omnitable-column-autocomplete
              name="${name}"
              title=${t('Status')}
              value-path="status.name"
            ></cosmoz-omnitable-column-autocomplete>`,
        }),

        createdAt: column({
          render: ({ name }) =>
            html`<cosmoz-omnitable-column-date
              name="${name}"
              title=${t('Date created')}
            ></cosmoz-omnitable-column-date>`,
        }),
      }),
      [], // dependency array (empty = compute once)
    ],

    // Reactive query parameters. Recomputed when deps change,
    // which triggers a new data fetch.
    params: [() => ({ pathLocator: '/some/path' }), []],

    // Data fetcher. Receives { params, page, pageSize }.
    // Must return { items, total }.
    list$: [
      ({ params }) =>
        api(params).then((r) => ({
          items: r.items ?? [],
          total: r.metaData?.totalAvailable ?? 0,
        })),
      [api],
    ],

    // Batch actions shown when items are selected
    actions: [myAction()],
  });
};

customElements.define(
  'order-list-core',
  component(OrderListCore, { styleSheets: [style] }),
);

listCore() props

| Prop | Type | Description | |---|---|---| | settingsId | string | Persistence key for column settings | | exposedParts | string? | CSS exportparts value | | noLocal | boolean | Skip omnitable local filtering (default: true) | | columns | [() => Columns, deps[]] | Column definitions (memoized) | | params | [(opts) => Params, deps[]] | Query parameters (memoized). The opts include { filters, descending, sortOn, columns } | | list$ | [(props) => Promise<{ items, total }>, deps[]] | Data fetcher. Receives { params, page, pageSize } | | pageSize | number? | Items per page for "load more" (default: 50) | | actions | Action[]? | Batch actions | | content | (opts) => Renderable | Extra content inside the omnitable | | hashParam | string? | URL hash param for omnitable state | | csvFilename | string? | Filename for CSV export | | enabledColumns | string[]? | Initially visible columns |

column()

Type-safe column definition helper:

import { column } from '@neovici/cosmoz-queue/list';

const myColumn = column({
  // Optional ordering hint
  order: 1,

  // Optional sort key
  sort: 'name',

  // Optional filter transform: receives the raw filter value,
  // returns the value sent to the API
  filter: (value: string) => ({ name: value }),

  // Render function -- receives { name } where name is the object key
  render: ({ name }) =>
    html`<cosmoz-omnitable-column
      name="${name}"
      title="Name"
    ></cosmoz-omnitable-column>`,
});

useListCore() / useListCoreState()

For cases where you need more control over the list without renderListCore():

  • useListCore(props) -- manages columns, params, data fetching, pagination, and form dialog state. Returns { data$, columns, loadMore, dialog, open, ...state }.
  • useListCoreState(defaults?) -- lower-level state: filters, sortOn, descending, groupOn, selectedItems, plus their setters and setTotalAvailable.

Actions module

The @neovici/cosmoz-queue/actions entry point provides a declarative system for defining user actions that open form dialogs.

Defining an action

import { action, Action, defaultButton } from '@neovici/cosmoz-queue/actions';
import { t } from 'i18next';
import { when } from 'lit-html/directives/when.js';

// action() is an identity function that provides type inference
export const approveOrder = action<OrderItem, ApproveFields>({
  // Label shown on the button
  title: () => t('Approve'),

  // Optional: filter which items this action applies to.
  // If provided, non-applicable items are excluded and the button
  // shows a count badge like "Approve (3/5)".
  applicable: (item) => item.status === 'pending',

  // Optional: custom button renderer. Defaults to `defaultButton()`.
  // Return `nothing` to hide the button conditionally.
  button: (opts) =>
    when(opts.items.length >= 1, () => defaultButton(opts)),

  // Dialog configuration. Opens a `cosmoz-form` dialog.
  // `items` contains only the applicable items.
  dialog: ({ items, title }) => ({
    heading: title,
    description: t('Approve the selected orders'),
    fields: [
      // cosmoz-form field definitions
    ],
    initial: {},
    onSave: async (values) => {
      await approveOrders(items.map((i) => i.id), values);
    },
  }),
});

Rendering actions

Actions are rendered automatically by listCore() when passed as the actions prop. For manual rendering (e.g. in a detail view's bottom bar):

import { renderActions } from '@neovici/cosmoz-queue/actions';
import { approveOrder } from './actions';

// In a view component:
const bottomBar = renderActions({ items: [currentItem], open })([
  approveOrder,
]);

Action types

interface Action<TItem, TDialog> {
  title: () => string;
  applicable?: (item: TItem) => boolean;
  button?: (opts: Action & ActionOpts) => unknown;
  dialog: (opts: DialogOpts) => Dialogable | Promise<Dialogable>;
}

interface ActionOpts<TItem> {
  items: TItem[];
  open: (dialog: Dialogable) => void;
  slot?: string;
}

Advanced: composing with hooks

When queue() is too opinionated, use the individual hooks directly.

useQueue() + renderQueue()

import { useQueue, renderQueue, renderNav } from '@neovici/cosmoz-queue';

const MyQueue = ({ heading }: { heading: string }) => {
  const {
    index, mobile, tabnav, items, setItems,
    setSelected, setTotalAvailable, totalAvailable,
    onItemClick, nav,
  } = useQueue<MyItem>({
    idHashParam: 'id',
    tabHashParam: 'tab',
  });

  return renderQueue({
    heading,
    mobile,
    index,
    items,
    tabnav,
    totalAvailable,
    nav,
    list: html`<my-list
      @visible-items-changed=${updateWith(setItems)}
      @selected-items-changed=${updateWith(setSelected)}
      @total-available-changed=${updateWith(setTotalAvailable)}
      @omnitable-item-click=${onItemClick}
    ></my-list>`,
    renderItem: ({ item, nav }) =>
      html`<my-view .item=${item}>${nav}</my-view>`,
    renderLoader: ({ item, nav }) =>
      html`<my-skeleton .item=${item}>${nav}</my-skeleton>`,
  });
};

Individual hooks

| Hook | Import | Purpose | |---|---|---| | useTabs({ items, hashParam, mobile, fallback }) | @neovici/cosmoz-queue | Manages overview/split/queue tabs. Returns { tabnav, activeTab }. | | useDataNav(items, opts) | @neovici/cosmoz-queue | Item navigation with prev/next, URL hash sync. Returns { item, setItem, next, prev, forward, index }. | | useSplit({ activeTab, ...splitOpts }) | @neovici/cosmoz-queue | Initializes Split.js when in split mode. | | useListState() | @neovici/cosmoz-queue | Creates items, selected, totalAvailable state with setters. | | useListSSE({ entity, params, list$ }) | @neovici/cosmoz-queue | Subscribes to Server-Sent Events (cosmoz-${entity}-updated) for real-time list updates. | | useFetchActions({ pathLocator, selected, api }) | @neovici/cosmoz-queue | Fetches available actions for selected items from an API. Returns { actions, actionRows, actionsFetching }. | | useAsyncAction(nav) | @neovici/cosmoz-queue | Handles async action completion with automatic item removal from the list. Returns { listRef, onAsyncSimpleAction }. | | usePagination() | @neovici/cosmoz-queue | URL hash-based page state. Returns { page, onPage }. |

Utilities

Fetch helpers (@neovici/cosmoz-queue/util/fetch)

Pre-configured fetch wrapper with CORS and credentials:

import {
  fetch,
  setBaseInit,
  handleJSON,
  RequestError,
} from '@neovici/cosmoz-queue/util/fetch';

// Configure default headers (call once at app startup)
setBaseInit({
  headers: { 'X-Custom-Header': 'value' },
  // Or use dynamic headers:
  getHeaders: () => ({ Authorization: `Bearer ${getToken()}` }),
});

// fetch() includes mode: 'cors', credentials: 'include' by default
const response = await fetch('/api/orders');
const data = await handleJSON(response);

RequestError extends Error with .response and .data properties for structured error handling.

itemClick()

Makes list cells clickable, dispatching omnitable-item-click events that the queue listens for:

import { itemClick } from '@neovici/cosmoz-queue';

// In an omnitable cell renderer:
html`<a @click="${itemClick({ index, activate: ['split', 'queue'] })}">
  ${item.title}
</a>`;

The activate option specifies which tab to switch to. The queue picks the first non-disabled tab from the array.

Other utilities

| Function | Description | |---|---| | getItems(items, selected) | Returns selected if non-empty, otherwise items | | touch(list, id) | Forces an omnitable item refresh by replacing it with a shallow copy |

Architecture

queue() factory
  |
  +---> useQueue()                     State orchestration
  |       |
  |       +---> useListState()         items, selected, totalAvailable
  |       +---> useTabs()              overview | split | queue
  |       +---> useDataNav()           current item, prev/next, URL hash
  |       +---> useKeyNav()            arrow key navigation
  |       +---> useSplit()             Split.js initialization
  |       +---> useUpdates()           list-item-remove events
  |
  +---> useAsyncAction()               Post-action item removal
  |
  +---> renderQueue()                  Template composition
          |
          +---> cosmoz-tabs-next       Tab bar (List / Split / Queue)
          +---> renderStats()          "3-5 of 120"
          +---> renderPagination()     Page prev/next
          +---> <div.split>
                 +---> list            cosmoz-omnitable (user-provided)
                 +---> cosmoz-slider   Animated detail view
                        +---> renderSlide() --> renderView()
                                                  |
                                                  +---> details()   Async fetch
                                                  +---> renderItem  or renderLoader

Entry points

| Import path | Description | |---|---| | @neovici/cosmoz-queue | Main: queue(), useQueue(), renderQueue(), navigation hooks, SSE, utilities | | @neovici/cosmoz-queue/actions | action(), renderActions(), defaultButton(), actionCount() | | @neovici/cosmoz-queue/list | listCore(), column(), useListCore(), useListCoreState(), renderListCore() | | @neovici/cosmoz-queue/list/more | useMore() -- progressive "load more" pagination | | @neovici/cosmoz-queue/list/more/render | renderLoadMore() -- "Load more" button | | @neovici/cosmoz-queue/util/fetch | fetch(), setBaseInit(), handleJSON(), RequestError |

Deprecation notices

api property → details

The api property on useQueue() / queue() is deprecated and will be removed in v2.0.0. Use details instead:

// Before (deprecated)
queue({ api: (id, item) => apiUrl(`items/${id}`) })

// After
queue({ details: (item) => fetch(apiUrl(`items/${item.id}`)).then(r => r.json()) })

See Migration guide for more patterns.

License

Apache-2.0