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

pict-section-picker

v1.6.0

Published

Pict-native themeable searchable select / combobox — single & multi select, server pagination, categorized groups and creatable entries, driven by a host-agnostic async DataProvider (with a built-in Meadow EntityProvider adapter). A jQuery/select2-free re

Readme

pict-section-picker

A Pict-native, themeable searchable select / combobox — a jQuery/select2-free replacement for entity pickers and tag inputs in Pict applications.

  • Single & multi select — scalar value, or an array of values rendered as removable chips.
  • Search with keyboard navigation (↑/↓ + Enter), click-outside to close.
  • Server pagination — drive options from an async DataProvider(searchTerm, page); "Load more" / infinite scroll.
  • Categorized options — group rows under headers.
  • Creatable — let users mint new entries from the search term via OnCreate.
  • Built-in Meadow adapter — point it at a Meadow entity and it builds the server DataProvider (FoxHound LIKE search + paging) and the pre-bound-value resolver for you.
  • Themeable via --theme-color-* tokens. No jQuery, no select2, no addEventListener — pure Pict conventions.

Install

npm install pict-section-picker

Quick start

Register the provider, then create pickers through it. Each picker renders into a host DOM element and reads/writes its selection from an AppData address.

const libPictSectionPicker = require('pict-section-picker');

// In your application's onAfterInitializeAsync:
this.pict.addProvider('Pict-Section-Picker', libPictSectionPicker.default_configuration, libPictSectionPicker);
const tmpPicker = this.pict.providers['Pict-Section-Picker'];

// A simple static single-select.
tmpPicker.createPicker('CountryPicker',
{
    DestinationAddress: '#CountryPicker',     // where to render
    ValueAddress: 'AppData.Form.Country',     // selection is read from / written to here
    Placeholder: 'Select a country…',
    Options: [ { Value: 'us', Text: 'United States' }, { Value: 'ca', Text: 'Canada' } ],
    OnChange: (pValue, pRecord) => { /* … */ },
});
this.pict.views['CountryPicker'].render();

The control renders into #CountryPicker; AppData.Form.Country holds the selected value.

Picker modes

Single (default)

Mode: 'single'ValueAddress holds the scalar value. Selecting closes the dropdown.

Clearable: set AllowClear: true to give a single-select a way back to empty — a pinned "Any" row at the top of the dropdown (checked while nothing is selected) and an inline × on the control while a value is selected. Either empties the selection and fires OnChange(null, null); clearing while already empty just closes. ClearLabel renames the row (default "Any"). The natural fit is filters, where empty means "no constraint" — the recordset quick filters enable it automatically. Multi mode ignores the option (chips already clear individually).

Multi

Mode: 'multi'ValueAddress holds an array of values, rendered as chips with × buttons. Selecting toggles membership and keeps the dropdown open for rapid multi-pick. Two optional mirror bindings (the EntitySelectorMultiple contract):

| Option | Holds | |---|---| | ValueAddress | the array of values, e.g. [2, 10, 141] | | StringArrayValueAddress | a csv string, e.g. "2,10,141" | | SelectedValuesAddress | the full record list, e.g. [{Value, Text}, …] |

tmpPicker.createPicker('TagsPicker',
{
    Mode: 'multi',
    DestinationAddress: '#TagsPicker',
    ValueAddress: 'AppData.Form.Tags',
    Placeholder: 'Add tags…',
    Options: [ { Value: 'urgent', Text: 'urgent' }, { Value: 'review', Text: 'review' } ],
});

Async data (server search + pagination)

Pass a DataProvider instead of (or in addition to) static Options. It is called with the current search term and a zero-based page index, and resolves a page of results plus whether more remain:

DataProvider: (pSearchTerm, pPage) => Promise.resolve(
{
    results: [ { Value: 1, Text: 'First' }, /* … up to PageSize … */ ],
    hasMore: true,   // show a "Load more" button
})

Searches are debounced; "Load more" appends the next page. For a value that is already bound when the picker mounts (e.g. an ID with no text yet), supply ResolveValue(value) => Promise<{Value, Text}> so the control can show its label.

Meadow entity pickers

For the common case — picking a Meadow entity over the REST API — use createEntityPicker. It builds the server DataProvider (FoxHound LIKE search across your fields, offset/limit paging) and the ResolveValue resolver from pict.EntityProvider automatically.

tmpPicker.createEntityPicker('AuthorPicker',
{
    Entity: 'Author',                 // the Meadow entity
    SearchFields: [ 'Name' ],         // fields to LIKE-search (default ['Name'])
    ValueField: 'IDAuthor',           // option Value (default 'ID<Entity>')
    TextField: 'Name',                // option Text  (default 'Name')
    PageSize: 20,
    DestinationAddress: '#AuthorPicker',
    ValueAddress: 'AppData.Form.IDAuthor',
    Placeholder: 'Search authors…',
    // Works in multi mode too — add Mode: 'multi'.
});
this.pict.views['AuthorPicker'].render();

Entity-source configuration:

| Option | Default | Purpose | |---|---|---| | Entity | — (required) | The Meadow entity name. | | SearchFields | ['Name'] | Fields OR'd together in the LIKE search. | | ValueField | ID<Entity> | Record field used as the option Value. | | TextField | 'Name' | Record field used as the option Text. | | PageSize | 20 | Records per page. | | Sort | — | Field to sort ascending (FSF~<field>~ASC~0). | | BaseFilter | — | An always-applied FoxHound filter (AND), e.g. FBV~IDCustomer~EQ~1. | | MapRecord | — | (record) => {Value, Text} mapper, overriding Value/TextField. |

The lower-level builders are also exposed: createEntityDataProvider(cfg) and createEntityResolveValue(cfg) return the raw functions if you want to wire them yourself.

Joined display (parent-entity context)

Sometimes a searched entity is ambiguous on its own — a LineItem only makes sense next to its Project, a Review next to its Book. Set JoinEntity and the picker renders a compound label by joining each searched row to a parent entity through a foreign key the row carries:

tmpPicker.createEntityPicker('ReviewPicker',
{
    Entity: 'Review',
    SearchFields: [ 'Summary' ],
    JoinEntity: 'Book',                 // the parent entity to join
    JoinField: 'IDBook',                // the FK on the Review row -> Book
    JoinEntityDisplayField: 'Title',    // the Book field to show
    DestinationAddress: '#ReviewPicker',
    ValueAddress: 'AppData.Form.IDReview',
});
// options render as "Neuromancer - Loved it"; the Value is still IDReview.

Meadow can't join in a single read, so this is fetch-then-merge: after each search page the picker collects the rows' unique FK ids and issues one FBL~ID{JoinEntity}~INN~<ids> request, then stitches the joined display onto every row (also exposed as Record.JoinName / Record.JoinRecord for MapRecord / templates). The same join resolves a pre-bound value's label on first render.

| Option | Default | Purpose | |---|---|---| | JoinEntity | — | Parent entity to join for the compound display. Setting it enables the feature. | | JoinField | ID<JoinEntity> | The FK column on the searched row pointing at JoinEntity. | | JoinEntityValueField | ID<JoinEntity> | The PK column on JoinEntity to match (the INN column). | | JoinEntityDisplayField | 'Name' | The JoinEntity field shown in the compound label. | | JoinEntityFirst | true | trueParent - Row; falseRow - Parent. | | JoinSeparator | ' - ' | Separator between the two parts. |

The same options ride through the form-input adapter (PictForm.JoinEntity, …) and the pict-section-recordset entity filters (set JoinEntity on the clause) — so an entity filter can show parent context for its options with no host code, layered on top of either the 1:1 (direct-FK / InternalJoin) or 1:many (junction / ExternalJoin) filter relationship.

Tag badge (EntityTag)

Show a small code/number badge next to each option — the select2 EntitySelector "tag" parity. For entity pickers, EntityTag names the record field to badge; the picker renders it as a pill beside the label:

tmpPicker.createEntityPicker('PayItemPicker',
{
    Entity: 'PayItem',
    SearchFields: [ 'Name' ],
    EntityTag: 'ItemCode',        // each option shows its ItemCode as a badge
    DestinationAddress: '#PayItemPicker',
    ValueAddress: 'AppData.Form.IDPayItem',
});
// options render as badge + label, e.g.  [201-1] Excavation

Static options can carry a Tag directly ({ Value, Text, Tag }). The badge rides on the dropdown options, the selected single value, and multi-select chips alike. It composes with JoinEntity — the join folds into the label, the tag stays a separate pill.

Multiple badges (EntityTags). For several disambiguation chips per option (e.g. a book's ISBN and publication year), pass EntityTags — an array of field specs:

tmpPicker.createEntityPicker('BookPicker',
{
    Entity: 'Book',
    SearchFields: [ 'Title', 'ISBN' ],
    EntityTags: [ 'ISBN', { Field: 'PublicationYear', Label: 'Year' } ],  // -> [1416524797] [Year: 2000]
    TagLast: true,                // chips after the title:  The Da Vinci Code  1416524797  Year: 2000
    DestinationAddress: '#BookPicker',
    ValueAddress: 'AppData.Form.IDBook',
});

Each spec is a field name (raw value) or { Field, Label?, Template? }Label prefixes the value ("Year: 2000"); Template renders against the whole record. Options carry the results as a Tags array (static options can set Tags directly); it composes with the single Tag. The recordset association UIs drive this from a per-side ChipFields config.

| Option | Default | Purpose | |---|---|---| | EntityTag | — | (entity pickers) record field whose value becomes each option's single Tag badge. | | EntityTags | — | (entity pickers) array of string \| {Field, Label?, Template?} specs → multiple chips (Tags). | | TagLast | false | false → badge(s) before the label; true → after. |

Through the form-input adapter + the recordset entity filters these are PictForm.EntityTag / PictForm.EntityTagLast.

Categories

Give option rows an optional Group field and the list renders headered sections (preserving order; rows without a Group fall into a leading unlabeled section):

Options:
[
    { Value: 'us', Text: 'United States', Group: 'Americas' },
    { Value: 'gb', Text: 'United Kingdom', Group: 'Europe' },
    { Value: 'jp', Text: 'Japan',          Group: 'Asia' },
]

Async sources can return Group on each result row too.

Creatable

Set OnCreate(searchTerm) => {Value, Text} | Promise<{Value, Text}>. When the search term is non-empty and doesn't exactly match an existing row, a "Create …" row appears (also triggerable with Enter). The returned record is selected (single) or added as a chip (multi):

OnCreate: (pTerm) =>
{
    // A real app would POST the new entity and return the saved row (sync or async).
    return { Value: slugify(pTerm), Text: pTerm };
}

Configuration reference

| Option | Default | Purpose | |---|---|---| | DestinationAddress | #<hash> | CSS selector to render the control into. | | ValueAddress | — | AppData address the selection is read from / written to. | | Mode | 'single' | 'single' (scalar) or 'multi' (array + chips). | | Placeholder | 'Select…' | Text shown when nothing is selected. | | Searchable | true | Show the search box. | | Options | [] | Static option list ({Value, Text, Group?}). | | DataProvider | — | Async source (term, page) => Promise<{results, hasMore}>. | | PageSize | 20 | Page size for async sources. | | ResolveValue | — | (value) => Promise<{Value, Text}> to label a pre-bound value. | | StringArrayValueAddress | — | (multi) mirror the value array as a csv string. | | SelectedValuesAddress | — | (multi) mirror the selection as a record array. | | OnCreate | — | (term) => {Value, Text} to enable creatable entries. | | OnChange | — | Called after a selection: single → (value, record), multi → (values, records). |

View methods

Call these on the picker view instance — this.pict.views['<hash>']:

| Method | Description | |--------|-------------| | render() | Paint (or repaint) the control into its destination. | | getValue() | The current selection — a scalar in single mode, an array of values in multi mode. | | setValue(pValue) | Set the selection programmatically — the supported counterpart to getValue(). Single mode takes a scalar; multi mode takes an array (or a csv string). Writes through to the bound address(es), resolves the display label of any unknown value (from the loaded options, else via ResolveValue in async mode), and repaints. Does not fire OnChange — it is a programmatic set (e.g. a host marshaling a form value into the control), not a user pick. Returns the view for chaining. | | getSelectedRecords() | (multi) The full {Value, Text} record list for the current selection. |

const tmpPicker = this.pict.views['AuthorPicker'];
tmpPicker.setValue(141);            // single: select author 141 (label resolves via ResolveValue if async)
tmpPicker.setValue([ 2, 10, 141 ]); // multi: select these values (array or "2,10,141" csv both accepted)
const tmpSelected = tmpPicker.getValue();

Theming

The widget paints from --theme-color-* tokens with sensible hex fallbacks, so it inherits the host app's theme. Relevant tokens: --theme-color-brand-primary, --theme-color-text-primary, --theme-color-text-muted, --theme-color-border-default, --theme-color-border-light, --theme-color-border-strong, --theme-color-background-primary, --theme-color-background-panel, --theme-color-background-tertiary.

Example application

example_applications/picker_demo exercises every mode (static / async / entity, single / multi, categorized, creatable). Build it with npm run build in that folder and open dist/index.html. The entity pickers in the demo talk to a live Meadow harness.

License

MIT