JavaScript List

@lemonadejs/list · ✓ 26 contract checks · framework-agnostic · zero dependencies

<List /> — full behavioral parity with the v5 plugin (@lemonadejs/list).

The v5 model, ported faithfully:

  • data: an array of records rendered one element per item; the item template comes from the caller (v5: children/template string → v6: the render prop, which can return an html“ view — any block can live inside an item)
  • built-in search across EVERY property of every record (v5: the bound input over Pagination.find); onbeforesearch fires before the filter, onsearch after it; searching resets to page zero
  • pagination: N items per page, numbered pager, onchangepage(page)
  • remote mode (total > 0): the component never filters or slices — data IS the current page, total drives the pager, and the events (onsearch / onchangepage) are the caller’s cue to fetch; data assignments keep the page (local assignments reset it, as v5)
  • message: the empty state (a real .lm-list-message element — v5 used :empty::before, which cannot see v6’s slot markers)
  • data BY REFERENCE: mutate records + touch() re-renders

v5 → v6 mapping: children template → render(item, index); self.input → api.setSearch(query); self.setPage → api.setPage(page); the «/» ten-page strip → the house prev/next + ellipsis pager (shared with <Datagrid />); page resets stay silent (v5 dispatched onchangepage even on load); the search box is opt-in (search), as in <Datagrid />.

Plus the richer list affordances, where they cost nothing:

  • a default item renderer over { title, secondary, icon, avatar }: avatar/icon slot + primary/secondary text (primitive items render as plain text rows)
  • dense and divider variants
  • onitemclick(item, index, event) makes rows interactive (hover + cursor through data-clickable; keyboard via an inner role=button wrapper — tabindex=0, Enter/Space activate)
  • virtual scrolling (height + rowheight, no pagination): the datagrid window pattern — 100k-item feeds keep ~a viewport of DOM alive

Example

live
import { html } from 'lemonadejs';
import List from '@lemonadejs/list';

const FIRST = ['Ana', 'Bruno', 'Carla', 'Daniel', 'Eva', 'Felix', 'Gina', 'Hugo', 'Iris', 'Jonas'];
const LAST = ['Silva', 'Mendes', 'Rocha', 'Keller', 'Tanaka', 'Moreau', 'Costa', 'Weber', 'Olsen', 'Russo'];
const ROLES = ['Engineering', 'Design', 'Sales', 'Support', 'Finance'];

const people = Array.from({ length: 32 }, (_, i) => ({
    id: i + 1,
    title: FIRST[i % 10] + ' ' + LAST[(i * 7) % 10],
    secondary: ROLES[i % 5],
    icon: FIRST[i % 10][0],
}));

const App = (props, { state }) => {
    const picked = state('none yet');

    return html`<div>
        <${List} data="${people}" search pagination="5" divider
            onitemclick="${(item) => (picked.value = item.title + ' — ' + item.secondary)}" />
        <p style="font-size:13px">Clicked: <b>${picked}</b></p>
    </div>`;
};

Installation

npm install @lemonadejs/list
import List from '@lemonadejs/list';
import '@lemonadejs/list/style.css';

Three deployment forms, one component:

html`<${List} />`                       // by value (no registration)
setComponents({ List });               // then <List /> by name anywhere
createWebComponent(List);              // <lm-list> in plain HTML/any framework

Props

Every declared prop arrives as a live state — pass a value for a snapshot or a state for a two-way live wire. Attribute strings are coerced to the declared type.

PropTypeDefaultDescription
dataarrayrecords BY REFERENCE (mutate + touch())
renderfunction(item, index) => string
searchbooleanfalsebuilt-in search box
paginationnumber0items per page; 0 = no pager
totalnumber0> 0 = remote mode: data is the current page, total drives the pager
messagestring"No records found"empty state text
densebooleanfalsetighter rows (dense variant)
dividerbooleanfalsehairline between rows
heightnumber0px viewport; with no pagination enables virtual scroll
rowheightnumber40fixed row height (virtual mode)

Events

All event names are lowercase (the platform convention — LJS-305 warns otherwise).

  • onbeforesearch — (query) before the local filter — the remote hook
  • onsearch — (query) after the filter
  • onchangepage — (page) on user/api page changes
  • onitemclick — (item, index, event)

API

import { ref } from 'lemonadejs';
const list = ref();
html`<${List} ref="${list}" />`;
// list.current.setPage(...)  ·  list.current.getPage(...)  ·  list.current.setSearch(...)  ·  list.current.refresh(...)
  • setPage()
  • getPage()
  • setSearch()
  • refresh()

Styling

All classes follow the lm-list-* convention; visual variants are data-* attributes on the root. Override freely — there is no styling engine to fight.

Contract

The machine-readable schema ships with the package:

import contract from '@lemonadejs/list/contract.json';

verify.json carries the conformance proof produced by verify(List).

Looking for the v5 plugin? See the archived v5 documentation.