@neovici/cosmoz-queue 2.7.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @neovici/cosmoz-queue
2
2
 
3
+ [![Changeset](https://img.shields.io/badge/Changesets----changeset-blue)](https://github.com/changesets/changesets)
4
+
3
5
  A hooks-based UI component library for building **master-detail views** with list, split, and queue navigation modes. Built on [`@pionjs/pion`](https://github.com/pionjs/pion) and [`lit-html`](https://lit.dev/docs/libraries/standalone-templates/).
4
6
 
5
7
  > **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.
@@ -26,60 +28,60 @@ import { fetchOrderDetails$ } from './api';
26
28
  import type { OrderListItem } from './types';
27
29
 
28
30
  const OrderListQueue = ({ heading }: { heading: string }) =>
29
- queue<OrderListItem>({
30
- // Title displayed above the list
31
- heading,
32
-
33
- // Unique key for persisting table column settings
34
- settingsId: 'order-list',
35
-
36
- // URL hash parameter names (enables deep-linking and back/forward)
37
- idHashParam: 'id',
38
- tabHashParam: 'qtab',
39
-
40
- // Async function that fetches full details for a list item.
41
- // Results are memoized per item identity.
42
- details: useCallback(
43
- (item: OrderListItem) => fetchOrderDetails$(item.id),
44
- [],
45
- ),
46
-
47
- // Render the list component. `thru` contains bindings that wire
48
- // the omnitable events to queue state (items, selection, clicks).
49
- // Spread them onto your list component.
50
- list: (thru, { onRef }) =>
51
- html`<order-list-core ${spread(thru)} ${ref(onRef)}></order-list-core>`,
52
-
53
- // Render the detail view. `nav` contains prev/next buttons --
54
- // place them in a slot or wherever navigation belongs in your view.
55
- view: (thru, { nav }) =>
56
- html`<order-view-core ${spread(thru)}>${nav}</order-view-core>`,
57
-
58
- // Render a loading skeleton shown while `details` resolves.
59
- loader: (thru, { nav }) =>
60
- html`<order-view-skeleton ${spread(thru)}>${nav}</order-view-skeleton>`,
61
- });
31
+ queue<OrderListItem>({
32
+ // Title displayed above the list
33
+ heading,
34
+
35
+ // Unique key for persisting table column settings
36
+ settingsId: 'order-list',
37
+
38
+ // URL hash parameter names (enables deep-linking and back/forward)
39
+ idHashParam: 'id',
40
+ tabHashParam: 'qtab',
41
+
42
+ // Async function that fetches full details for a list item.
43
+ // Results are memoized per item identity.
44
+ details: useCallback(
45
+ (item: OrderListItem) => fetchOrderDetails$(item.id),
46
+ [],
47
+ ),
48
+
49
+ // Render the list component. `thru` contains bindings that wire
50
+ // the omnitable events to queue state (items, selection, clicks).
51
+ // Spread them onto your list component.
52
+ list: (thru, { onRef }) =>
53
+ html`<order-list-core ${spread(thru)} ${ref(onRef)}></order-list-core>`,
54
+
55
+ // Render the detail view. `nav` contains prev/next buttons --
56
+ // place them in a slot or wherever navigation belongs in your view.
57
+ view: (thru, { nav }) =>
58
+ html`<order-view-core ${spread(thru)}>${nav}</order-view-core>`,
59
+
60
+ // Render a loading skeleton shown while `details` resolves.
61
+ loader: (thru, { nav }) =>
62
+ html`<order-view-skeleton ${spread(thru)}>${nav}</order-view-skeleton>`,
63
+ });
62
64
 
63
65
  customElements.define('order-list-queue', component(OrderListQueue));
64
66
  ```
65
67
 
66
68
  ### `queue()` props
67
69
 
68
- | Prop | Type | Default | Description |
69
- |---|---|---|---|
70
- | `heading` | `string` | -- | Title displayed above the tabs |
71
- | `settingsId` | `string?` | -- | Key for persisting omnitable column settings |
72
- | `idHashParam` | `string` | `'qid'` | URL hash param for the selected item ID |
73
- | `tabHashParam` | `string` | `'qtab'` | URL hash param for the active tab |
74
- | `details` | `(item: I) => PromiseLike<D>` | -- | Fetches full details for a list item |
75
- | `api` | `(id: string, item: I) => string` | -- | Alternative to `details`: returns a URL, queue fetches JSON internally |
76
- | `list` | `(thru, props) => TemplateResult` | -- | Renders the list component |
77
- | `view` | `(thru, props) => TemplateResult` | -- | Renders the detail view |
78
- | `loader` | `(thru, props) => TemplateResult` | -- | Renders the loading skeleton |
79
- | `pagination` | `Pagination?` | -- | Server-side pagination config |
80
- | `fallback` | `string?` | -- | Default tab when none is set in URL hash |
81
- | `split` | `SplitOpts?` | -- | Split.js configuration (sizes, min sizes) |
82
- | `afterHeading` | `unknown?` | -- | Extra content rendered after the heading |
70
+ | Prop | Type | Default | Description |
71
+ | -------------- | --------------------------------- | -------- | ---------------------------------------------------------------------- |
72
+ | `heading` | `string` | -- | Title displayed above the tabs |
73
+ | `settingsId` | `string?` | -- | Key for persisting omnitable column settings |
74
+ | `idHashParam` | `string` | `'qid'` | URL hash param for the selected item ID |
75
+ | `tabHashParam` | `string` | `'qtab'` | URL hash param for the active tab |
76
+ | `details` | `(item: I) => PromiseLike<D>` | -- | Fetches full details for a list item |
77
+ | `api` | `(id: string, item: I) => string` | -- | Alternative to `details`: returns a URL, queue fetches JSON internally |
78
+ | `list` | `(thru, props) => TemplateResult` | -- | Renders the list component |
79
+ | `view` | `(thru, props) => TemplateResult` | -- | Renders the detail view |
80
+ | `loader` | `(thru, props) => TemplateResult` | -- | Renders the loading skeleton |
81
+ | `pagination` | `Pagination?` | -- | Server-side pagination config |
82
+ | `fallback` | `string?` | -- | Default tab when none is set in URL hash |
83
+ | `split` | `SplitOpts?` | -- | Split.js configuration (sizes, min sizes) |
84
+ | `afterHeading` | `unknown?` | -- | Extra content rendered after the heading |
83
85
 
84
86
  Use **`details`** when you control the fetch (most common). Use **`api`** when you want the queue to handle fetching via URL.
85
87
 
@@ -88,12 +90,14 @@ Use **`details`** when you control the fetch (most common). Use **`api`** when y
88
90
  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:
89
91
 
90
92
  **For `list`:**
93
+
91
94
  - `.settingsId`, `.exposedParts` -- table configuration
92
95
  - `@visible-items-changed`, `@selected-items-changed`, `@total-available-changed` -- sync items/selection back to queue
93
96
  - `@omnitable-item-click` -- item click handler
94
97
  - `@async-simple-action` -- action completion handler
95
98
 
96
99
  **For `view`:**
100
+
97
101
  - `.item` -- the current detail item (list item or resolved detail)
98
102
  - `.hideActions` -- whether to hide actions (true when items are selected in split mode)
99
103
  - `@async-simple-action` -- action completion handler
@@ -104,11 +108,11 @@ Spread these onto your component with `${spread(thru)}` from `@open-wc/lit-helpe
104
108
 
105
109
  The queue provides three tab-based view modes:
106
110
 
107
- | Mode | Tab name | Behavior |
108
- |---|---|---|
109
- | **List** | `overview` | Full-width data table (`cosmoz-omnitable`). The default view. |
110
- | **Split** | `split` | Side-by-side: resizable list on the left, detail view on the right. Disabled on mobile. |
111
- | **Queue** | `queue` | Full-screen detail view with prev/next navigation and keyboard arrow keys. |
111
+ | Mode | Tab name | Behavior |
112
+ | --------- | ---------- | --------------------------------------------------------------------------------------- |
113
+ | **List** | `overview` | Full-width data table (`cosmoz-omnitable`). The default view. |
114
+ | **Split** | `split` | Side-by-side: resizable list on the left, detail view on the right. Disabled on mobile. |
115
+ | **Queue** | `queue` | Full-screen detail view with prev/next navigation and keyboard arrow keys. |
112
116
 
113
117
  The active tab is synced to the URL hash (e.g. `#qtab=split&id=abc-123`), enabling deep-linking and browser back/forward navigation.
114
118
 
@@ -130,111 +134,111 @@ import { t } from 'i18next';
130
134
  import type { OrderListItem } from './types';
131
135
 
132
136
  interface Props {
133
- exposedParts: string;
134
- api: (params: { pathLocator: string }) => Promise<{
135
- items: OrderListItem[];
136
- metaData: { totalAvailable: number };
137
- }>;
137
+ exposedParts: string;
138
+ api: (params: { pathLocator: string }) => Promise<{
139
+ items: OrderListItem[];
140
+ metaData: { totalAvailable: number };
141
+ }>;
138
142
  }
139
143
 
140
144
  const OrderListCore = ({ exposedParts, api }: Props) => {
141
- return listCore({
142
- // Unique key for persisting column settings
143
- settingsId: 'order-list-core',
144
-
145
- // CSS parts to expose for external styling
146
- exposedParts,
147
-
148
- // When false, omnitable applies its own local filtering
149
- noLocal: false,
150
-
151
- // Column definitions. The tuple is [factory, deps] -- same pattern
152
- // as useMemo. The factory returns an object where each key becomes
153
- // a column name.
154
- columns: [
155
- () => ({
156
- title: column({
157
- render: ({ name }) =>
158
- html`<cosmoz-omnitable-column
159
- name="${name}"
160
- title=${t('Title')}
161
- flex="2"
162
- .renderCell=${(
163
- _col: unknown,
164
- { item, index }: { item: OrderListItem; index: number },
165
- ) =>
166
- html`<a
167
- @click="${itemClick({
168
- index,
169
- activate: ['split', 'queue'],
170
- })}"
171
- >${item.title}</a
172
- >`}
173
- ></cosmoz-omnitable-column>`,
174
- }),
175
-
176
- status: column({
177
- render: ({ name }) =>
178
- html`<cosmoz-omnitable-column-autocomplete
179
- name="${name}"
180
- title=${t('Status')}
181
- value-path="status.name"
182
- ></cosmoz-omnitable-column-autocomplete>`,
183
- }),
184
-
185
- createdAt: column({
186
- render: ({ name }) =>
187
- html`<cosmoz-omnitable-column-date
188
- name="${name}"
189
- title=${t('Date created')}
190
- ></cosmoz-omnitable-column-date>`,
191
- }),
192
- }),
193
- [], // dependency array (empty = compute once)
194
- ],
195
-
196
- // Reactive query parameters. Recomputed when deps change,
197
- // which triggers a new data fetch.
198
- params: [() => ({ pathLocator: '/some/path' }), []],
199
-
200
- // Data fetcher. Receives { params, page, pageSize }.
201
- // Must return { items, total }.
202
- list$: [
203
- ({ params }) =>
204
- api(params).then((r) => ({
205
- items: r.items ?? [],
206
- total: r.metaData?.totalAvailable ?? 0,
207
- })),
208
- [api],
209
- ],
210
-
211
- // Batch actions shown when items are selected
212
- actions: [myAction()],
213
- });
145
+ return listCore({
146
+ // Unique key for persisting column settings
147
+ settingsId: 'order-list-core',
148
+
149
+ // CSS parts to expose for external styling
150
+ exposedParts,
151
+
152
+ // When false, omnitable applies its own local filtering
153
+ noLocal: false,
154
+
155
+ // Column definitions. The tuple is [factory, deps] -- same pattern
156
+ // as useMemo. The factory returns an object where each key becomes
157
+ // a column name.
158
+ columns: [
159
+ () => ({
160
+ title: column({
161
+ render: ({ name }) =>
162
+ html`<cosmoz-omnitable-column
163
+ name="${name}"
164
+ title=${t('Title')}
165
+ flex="2"
166
+ .renderCell=${(
167
+ _col: unknown,
168
+ { item, index }: { item: OrderListItem; index: number },
169
+ ) =>
170
+ html`<a
171
+ @click="${itemClick({
172
+ index,
173
+ activate: ['split', 'queue'],
174
+ })}"
175
+ >${item.title}</a
176
+ >`}
177
+ ></cosmoz-omnitable-column>`,
178
+ }),
179
+
180
+ status: column({
181
+ render: ({ name }) =>
182
+ html`<cosmoz-omnitable-column-autocomplete
183
+ name="${name}"
184
+ title=${t('Status')}
185
+ value-path="status.name"
186
+ ></cosmoz-omnitable-column-autocomplete>`,
187
+ }),
188
+
189
+ createdAt: column({
190
+ render: ({ name }) =>
191
+ html`<cosmoz-omnitable-column-date
192
+ name="${name}"
193
+ title=${t('Date created')}
194
+ ></cosmoz-omnitable-column-date>`,
195
+ }),
196
+ }),
197
+ [], // dependency array (empty = compute once)
198
+ ],
199
+
200
+ // Reactive query parameters. Recomputed when deps change,
201
+ // which triggers a new data fetch.
202
+ params: [() => ({ pathLocator: '/some/path' }), []],
203
+
204
+ // Data fetcher. Receives { params, page, pageSize }.
205
+ // Must return { items, total }.
206
+ list$: [
207
+ ({ params }) =>
208
+ api(params).then((r) => ({
209
+ items: r.items ?? [],
210
+ total: r.metaData?.totalAvailable ?? 0,
211
+ })),
212
+ [api],
213
+ ],
214
+
215
+ // Batch actions shown when items are selected
216
+ actions: [myAction()],
217
+ });
214
218
  };
215
219
 
216
220
  customElements.define(
217
- 'order-list-core',
218
- component(OrderListCore, { styleSheets: [style] }),
221
+ 'order-list-core',
222
+ component(OrderListCore, { styleSheets: [style] }),
219
223
  );
220
224
  ```
221
225
 
222
226
  ### `listCore()` props
223
227
 
224
- | Prop | Type | Description |
225
- |---|---|---|
226
- | `settingsId` | `string` | Persistence key for column settings |
227
- | `exposedParts` | `string?` | CSS `exportparts` value |
228
- | `noLocal` | `boolean` | Skip omnitable local filtering (default: `true`) |
229
- | `columns` | `[() => Columns, deps[]]` | Column definitions (memoized) |
230
- | `params` | `[(opts) => Params, deps[]]` | Query parameters (memoized). The `opts` include `{ filters, descending, sortOn, columns }` |
231
- | `list$` | `[(props) => Promise<{ items, total }>, deps[]]` | Data fetcher. Receives `{ params, page, pageSize }` |
232
- | `pageSize` | `number?` | Items per page for "load more" (default: `50`) |
233
- | `actions` | `Action[]?` | Batch actions |
234
- | `content` | `(opts) => Renderable` | Extra content inside the omnitable |
235
- | `hashParam` | `string?` | URL hash param for omnitable state |
236
- | `csvFilename` | `string?` | Filename for CSV export |
237
- | `enabledColumns` | `string[]?` | Initially visible columns |
228
+ | Prop | Type | Description |
229
+ | ---------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------ |
230
+ | `settingsId` | `string` | Persistence key for column settings |
231
+ | `exposedParts` | `string?` | CSS `exportparts` value |
232
+ | `noLocal` | `boolean` | Skip omnitable local filtering (default: `true`) |
233
+ | `columns` | `[() => Columns, deps[]]` | Column definitions (memoized) |
234
+ | `params` | `[(opts) => Params, deps[]]` | Query parameters (memoized). The `opts` include `{ filters, descending, sortOn, columns }` |
235
+ | `list$` | `[(props) => Promise<{ items, total }>, deps[]]` | Data fetcher. Receives `{ params, page, pageSize }` |
236
+ | `pageSize` | `number?` | Items per page for "load more" (default: `50`) |
237
+ | `actions` | `Action[]?` | Batch actions |
238
+ | `content` | `(opts) => Renderable` | Extra content inside the omnitable |
239
+ | `hashParam` | `string?` | URL hash param for omnitable state |
240
+ | `csvFilename` | `string?` | Filename for CSV export |
241
+ | `enabledColumns` | `string[]?` | Initially visible columns |
238
242
 
239
243
  ### `column()`
240
244
 
@@ -244,22 +248,22 @@ Type-safe column definition helper:
244
248
  import { column } from '@neovici/cosmoz-queue/list';
245
249
 
246
250
  const myColumn = column({
247
- // Optional ordering hint
248
- order: 1,
249
-
250
- // Optional sort key
251
- sort: 'name',
252
-
253
- // Optional filter transform: receives the raw filter value,
254
- // returns the value sent to the API
255
- filter: (value: string) => ({ name: value }),
256
-
257
- // Render function -- receives { name } where name is the object key
258
- render: ({ name }) =>
259
- html`<cosmoz-omnitable-column
260
- name="${name}"
261
- title="Name"
262
- ></cosmoz-omnitable-column>`,
251
+ // Optional ordering hint
252
+ order: 1,
253
+
254
+ // Optional sort key
255
+ sort: 'name',
256
+
257
+ // Optional filter transform: receives the raw filter value,
258
+ // returns the value sent to the API
259
+ filter: (value: string) => ({ name: value }),
260
+
261
+ // Render function -- receives { name } where name is the object key
262
+ render: ({ name }) =>
263
+ html`<cosmoz-omnitable-column
264
+ name="${name}"
265
+ title="Name"
266
+ ></cosmoz-omnitable-column>`,
263
267
  });
264
268
  ```
265
269
 
@@ -283,32 +287,34 @@ import { when } from 'lit-html/directives/when.js';
283
287
 
284
288
  // action() is an identity function that provides type inference
285
289
  export const approveOrder = action<OrderItem, ApproveFields>({
286
- // Label shown on the button
287
- title: () => t('Approve'),
288
-
289
- // Optional: filter which items this action applies to.
290
- // If provided, non-applicable items are excluded and the button
291
- // shows a count badge like "Approve (3/5)".
292
- applicable: (item) => item.status === 'pending',
293
-
294
- // Optional: custom button renderer. Defaults to `defaultButton()`.
295
- // Return `nothing` to hide the button conditionally.
296
- button: (opts) =>
297
- when(opts.items.length >= 1, () => defaultButton(opts)),
298
-
299
- // Dialog configuration. Opens a `cosmoz-form` dialog.
300
- // `items` contains only the applicable items.
301
- dialog: ({ items, title }) => ({
302
- heading: title,
303
- description: t('Approve the selected orders'),
304
- fields: [
305
- // cosmoz-form field definitions
306
- ],
307
- initial: {},
308
- onSave: async (values) => {
309
- await approveOrders(items.map((i) => i.id), values);
310
- },
311
- }),
290
+ // Label shown on the button
291
+ title: () => t('Approve'),
292
+
293
+ // Optional: filter which items this action applies to.
294
+ // If provided, non-applicable items are excluded and the button
295
+ // shows a count badge like "Approve (3/5)".
296
+ applicable: (item) => item.status === 'pending',
297
+
298
+ // Optional: custom button renderer. Defaults to `defaultButton()`.
299
+ // Return `nothing` to hide the button conditionally.
300
+ button: (opts) => when(opts.items.length >= 1, () => defaultButton(opts)),
301
+
302
+ // Dialog configuration. Opens a `cosmoz-form` dialog.
303
+ // `items` contains only the applicable items.
304
+ dialog: ({ items, title }) => ({
305
+ heading: title,
306
+ description: t('Approve the selected orders'),
307
+ fields: [
308
+ // cosmoz-form field definitions
309
+ ],
310
+ initial: {},
311
+ onSave: async (values) => {
312
+ await approveOrders(
313
+ items.map((i) => i.id),
314
+ values,
315
+ );
316
+ },
317
+ }),
312
318
  });
313
319
  ```
314
320
 
@@ -321,25 +327,23 @@ import { renderActions } from '@neovici/cosmoz-queue/actions';
321
327
  import { approveOrder } from './actions';
322
328
 
323
329
  // In a view component:
324
- const bottomBar = renderActions({ items: [currentItem], open })([
325
- approveOrder,
326
- ]);
330
+ const bottomBar = renderActions({ items: [currentItem], open })([approveOrder]);
327
331
  ```
328
332
 
329
333
  ### Action types
330
334
 
331
335
  ```ts
332
336
  interface Action<TItem, TDialog> {
333
- title: () => string;
334
- applicable?: (item: TItem) => boolean;
335
- button?: (opts: Action & ActionOpts) => unknown;
336
- dialog: (opts: DialogOpts) => Dialogable | Promise<Dialogable>;
337
+ title: () => string;
338
+ applicable?: (item: TItem) => boolean;
339
+ button?: (opts: Action & ActionOpts) => unknown;
340
+ dialog: (opts: DialogOpts) => Dialogable | Promise<Dialogable>;
337
341
  }
338
342
 
339
343
  interface ActionOpts<TItem> {
340
- items: TItem[];
341
- open: (dialog: Dialogable) => void;
342
- slot?: string;
344
+ items: TItem[];
345
+ open: (dialog: Dialogable) => void;
346
+ slot?: string;
343
347
  }
344
348
  ```
345
349
 
@@ -353,49 +357,56 @@ When `queue()` is too opinionated, use the individual hooks directly.
353
357
  import { useQueue, renderQueue, renderNav } from '@neovici/cosmoz-queue';
354
358
 
355
359
  const MyQueue = ({ heading }: { heading: string }) => {
356
- const {
357
- index, mobile, tabnav, items, setItems,
358
- setSelected, setTotalAvailable, totalAvailable,
359
- onItemClick, nav,
360
- } = useQueue<MyItem>({
361
- idHashParam: 'id',
362
- tabHashParam: 'tab',
363
- });
364
-
365
- return renderQueue({
366
- heading,
367
- mobile,
368
- index,
369
- items,
370
- tabnav,
371
- totalAvailable,
372
- nav,
373
- list: html`<my-list
374
- @visible-items-changed=${updateWith(setItems)}
375
- @selected-items-changed=${updateWith(setSelected)}
376
- @total-available-changed=${updateWith(setTotalAvailable)}
377
- @omnitable-item-click=${onItemClick}
378
- ></my-list>`,
379
- renderItem: ({ item, nav }) =>
380
- html`<my-view .item=${item}>${nav}</my-view>`,
381
- renderLoader: ({ item, nav }) =>
382
- html`<my-skeleton .item=${item}>${nav}</my-skeleton>`,
383
- });
360
+ const {
361
+ index,
362
+ mobile,
363
+ tabnav,
364
+ items,
365
+ setItems,
366
+ setSelected,
367
+ setTotalAvailable,
368
+ totalAvailable,
369
+ onItemClick,
370
+ nav,
371
+ } = useQueue<MyItem>({
372
+ idHashParam: 'id',
373
+ tabHashParam: 'tab',
374
+ });
375
+
376
+ return renderQueue({
377
+ heading,
378
+ mobile,
379
+ index,
380
+ items,
381
+ tabnav,
382
+ totalAvailable,
383
+ nav,
384
+ list: html`<my-list
385
+ @visible-items-changed=${updateWith(setItems)}
386
+ @selected-items-changed=${updateWith(setSelected)}
387
+ @total-available-changed=${updateWith(setTotalAvailable)}
388
+ @omnitable-item-click=${onItemClick}
389
+ ></my-list>`,
390
+ renderItem: ({ item, nav }) =>
391
+ html`<my-view .item=${item}>${nav}</my-view>`,
392
+ renderLoader: ({ item, nav }) =>
393
+ html`<my-skeleton .item=${item}>${nav}</my-skeleton>`,
394
+ });
384
395
  };
385
396
  ```
386
397
 
387
398
  ### Individual hooks
388
399
 
389
- | Hook | Import | Purpose |
390
- |---|---|---|
391
- | `useTabs({ items, hashParam, mobile, fallback })` | `@neovici/cosmoz-queue` | Manages overview/split/queue tabs. Returns `{ tabnav, activeTab }`. |
392
- | `useDataNav(items, opts)` | `@neovici/cosmoz-queue` | Item navigation with prev/next, URL hash sync. Returns `{ item, setItem, next, prev, forward, index }`. |
393
- | `useSplit({ activeTab, ...splitOpts })` | `@neovici/cosmoz-queue` | Initializes Split.js when in split mode. |
394
- | `useListState()` | `@neovici/cosmoz-queue` | Creates `items`, `selected`, `totalAvailable` state with setters. |
395
- | `useListSSE({ entity, params, list$ })` | `@neovici/cosmoz-queue` | Subscribes to Server-Sent Events (`cosmoz-${entity}-updated`) for real-time list updates. |
396
- | `useFetchActions({ pathLocator, selected, api })` | `@neovici/cosmoz-queue` | Fetches available actions for selected items from an API. Returns `{ actions, actionRows, actionsFetching }`. |
397
- | `useAsyncAction(nav)` | `@neovici/cosmoz-queue` | Handles async action completion with automatic item removal from the list. Returns `{ listRef, onAsyncSimpleAction }`. |
398
- | `usePagination()` | `@neovici/cosmoz-queue` | URL hash-based page state. Returns `{ page, onPage }`. |
400
+ | Hook | Import | Purpose |
401
+ | ------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
402
+ | `useTabs({ items, hashParam, mobile, fallback })` | `@neovici/cosmoz-queue` | Manages overview/split/queue tabs. Returns `{ tabnav, activeTab }`. |
403
+ | `useDataNav(items, opts)` | `@neovici/cosmoz-queue` | Item navigation with prev/next, URL hash sync. Returns `{ item, setItem, next, prev, forward, index }`. |
404
+ | `useSplit({ activeTab, ...splitOpts })` | `@neovici/cosmoz-queue` | Initializes Split.js when in split mode. |
405
+ | `useListState()` | `@neovici/cosmoz-queue` | Creates `items`, `selected`, `totalAvailable` state with setters. |
406
+ | `useListSSE({ entity, params, list$ })` | `@neovici/cosmoz-queue` | Subscribes to Server-Sent Events (`cosmoz-${entity}-updated`) for real-time list updates. |
407
+ | `useFetchActions({ pathLocator, selected, api })` | `@neovici/cosmoz-queue` | Fetches available actions for selected items from an API. Returns `{ actions, actionRows, actionsFetching }`. |
408
+ | `useAsyncAction(nav)` | `@neovici/cosmoz-queue` | Handles async action completion with automatic item removal from the list. Returns `{ listRef, onAsyncSimpleAction }`. |
409
+ | `usePagination()` | `@neovici/cosmoz-queue` | URL hash-based page state. Returns `{ page, onPage }`. |
399
410
 
400
411
  ## Utilities
401
412
 
@@ -405,17 +416,17 @@ Pre-configured `fetch` wrapper with CORS and credentials:
405
416
 
406
417
  ```ts
407
418
  import {
408
- fetch,
409
- setBaseInit,
410
- handleJSON,
411
- RequestError,
419
+ fetch,
420
+ setBaseInit,
421
+ handleJSON,
422
+ RequestError,
412
423
  } from '@neovici/cosmoz-queue/util/fetch';
413
424
 
414
425
  // Configure default headers (call once at app startup)
415
426
  setBaseInit({
416
- headers: { 'X-Custom-Header': 'value' },
417
- // Or use dynamic headers:
418
- getHeaders: () => ({ Authorization: `Bearer ${getToken()}` }),
427
+ headers: { 'X-Custom-Header': 'value' },
428
+ // Or use dynamic headers:
429
+ getHeaders: () => ({ Authorization: `Bearer ${getToken()}` }),
419
430
  });
420
431
 
421
432
  // fetch() includes mode: 'cors', credentials: 'include' by default
@@ -434,7 +445,7 @@ import { itemClick } from '@neovici/cosmoz-queue';
434
445
 
435
446
  // In an omnitable cell renderer:
436
447
  html`<a @click="${itemClick({ index, activate: ['split', 'queue'] })}">
437
- ${item.title}
448
+ ${item.title}
438
449
  </a>`;
439
450
  ```
440
451
 
@@ -442,10 +453,10 @@ The `activate` option specifies which tab to switch to. The queue picks the firs
442
453
 
443
454
  ### Other utilities
444
455
 
445
- | Function | Description |
446
- |---|---|
447
- | `getItems(items, selected)` | Returns `selected` if non-empty, otherwise `items` |
448
- | `touch(list, id)` | Forces an omnitable item refresh by replacing it with a shallow copy |
456
+ | Function | Description |
457
+ | --------------------------- | -------------------------------------------------------------------- |
458
+ | `getItems(items, selected)` | Returns `selected` if non-empty, otherwise `items` |
459
+ | `touch(list, id)` | Forces an omnitable item refresh by replacing it with a shallow copy |
449
460
 
450
461
  ## Architecture
451
462
 
@@ -479,14 +490,14 @@ queue() factory
479
490
 
480
491
  ## Entry points
481
492
 
482
- | Import path | Description |
483
- |---|---|
484
- | `@neovici/cosmoz-queue` | Main: `queue()`, `useQueue()`, `renderQueue()`, navigation hooks, SSE, utilities |
485
- | `@neovici/cosmoz-queue/actions` | `action()`, `renderActions()`, `defaultButton()`, `actionCount()` |
486
- | `@neovici/cosmoz-queue/list` | `listCore()`, `column()`, `useListCore()`, `useListCoreState()`, `renderListCore()` |
487
- | `@neovici/cosmoz-queue/list/more` | `useMore()` -- progressive "load more" pagination |
488
- | `@neovici/cosmoz-queue/list/more/render` | `renderLoadMore()` -- "Load more" button |
489
- | `@neovici/cosmoz-queue/util/fetch` | `fetch()`, `setBaseInit()`, `handleJSON()`, `RequestError` |
493
+ | Import path | Description |
494
+ | ---------------------------------------- | ----------------------------------------------------------------------------------- |
495
+ | `@neovici/cosmoz-queue` | Main: `queue()`, `useQueue()`, `renderQueue()`, navigation hooks, SSE, utilities |
496
+ | `@neovici/cosmoz-queue/actions` | `action()`, `renderActions()`, `defaultButton()`, `actionCount()` |
497
+ | `@neovici/cosmoz-queue/list` | `listCore()`, `column()`, `useListCore()`, `useListCoreState()`, `renderListCore()` |
498
+ | `@neovici/cosmoz-queue/list/more` | `useMore()` -- progressive "load more" pagination |
499
+ | `@neovici/cosmoz-queue/list/more/render` | `renderLoadMore()` -- "Load more" button |
500
+ | `@neovici/cosmoz-queue/util/fetch` | `fetch()`, `setBaseInit()`, `handleJSON()`, `RequestError` |
490
501
 
491
502
  ## Deprecation notices
492
503
 
@@ -497,14 +508,37 @@ removed in v2.0.0. Use `details` instead:
497
508
 
498
509
  ```ts
499
510
  // Before (deprecated)
500
- queue({ api: (id, item) => apiUrl(`items/${id}`) })
511
+ queue({ api: (id, item) => apiUrl(`items/${id}`) });
501
512
 
502
513
  // After
503
- queue({ details: (item) => fetch(apiUrl(`items/${item.id}`)).then(r => r.json()) })
514
+ queue({
515
+ details: (item) => fetch(apiUrl(`items/${item.id}`)).then((r) => r.json()),
516
+ });
504
517
  ```
505
518
 
506
519
  See [Migration guide](docs/migration-api-to-details.md) for more patterns.
507
520
 
521
+ ## Releasing
522
+
523
+ This project uses [Changesets](https://github.com/changesets/changesets) for managing releases.
524
+
525
+ ### How to release
526
+
527
+ 1. Create a changeset: `npm run changeset`
528
+ 2. Select the packages you have changed
529
+ 3. Select the type of change (patch, minor, or major)
530
+ 4. Write a summary of the change
531
+ 5. Commit the changeset file
532
+ 6. Push to GitHub
533
+
534
+ When the "Version Packages" PR is merged, the release will be published automatically.
535
+
536
+ ### Versioning
537
+
538
+ - **Patch**: Bug fixes
539
+ - **Minor**: New features (backward compatible)
540
+ - **Major**: Breaking changes
541
+
508
542
  ## License
509
543
 
510
544
  [Apache-2.0](LICENSE)
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/queue/render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/queue/render.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { lazyUntil } from '@neovici/cosmoz-utils/directives/lazy-until';
4
4
  import { t } from 'i18next';
5
5
  import { html, nothing } from 'lit-html';
6
6
  import { guard } from 'lit-html/directives/guard.js';
7
+ import { when } from 'lit-html/directives/when.js';
7
8
  import { arrow } from './icon';
8
9
  import renderStyles from './style';
9
10
  const _emptySlide = {
@@ -128,7 +129,7 @@ export const renderQueue = ({ heading, afterHeading, index, tabnav, items, total
128
129
  ${list}
129
130
  <cosmoz-slider
130
131
  id="queue"
131
- .slide=${renderSlide({ ...nav, renderLoader, renderItem, details })}
132
+ .slide=${when(activeTab !== 'overview', () => renderSlide({ ...nav, renderLoader, renderItem, details }), emptySlide)}
132
133
  ></cosmoz-slider>
133
134
  </div>
134
135
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-queue",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "description": "A reusable queue component for master-detail views with list, split, and queue modes",
5
5
  "keywords": [
6
6
  "web-components",
@@ -39,19 +39,8 @@
39
39
  "storybook:build": "storybook build",
40
40
  "storybook:deploy": "storybook-to-ghpages",
41
41
  "storybook:preview": "npm run storybook:build && http-server -d ./storybook-static/",
42
- "prepare": "husky"
43
- },
44
- "release": {
45
- "plugins": [
46
- "@semantic-release/commit-analyzer",
47
- "@semantic-release/release-notes-generator",
48
- "@semantic-release/changelog",
49
- "@semantic-release/github",
50
- "@semantic-release/npm",
51
- "@semantic-release/git"
52
- ],
53
- "branch": "master",
54
- "preset": "conventionalcommits"
42
+ "prepare": "husky",
43
+ "changeset": "changeset"
55
44
  },
56
45
  "publishConfig": {
57
46
  "access": "public"
@@ -86,13 +75,12 @@
86
75
  "split.js": "^1.6.5"
87
76
  },
88
77
  "devDependencies": {
78
+ "@changesets/cli": "^2.31.0",
89
79
  "@commitlint/cli": "^20.0.0",
90
80
  "@commitlint/config-conventional": "^20.0.0",
91
81
  "@eslint/eslintrc": "^2.0.0",
92
82
  "@neovici/cfg": "^2.11.0",
93
83
  "@neovici/testing": "^2.2.0",
94
- "@semantic-release/changelog": "^6.0.0",
95
- "@semantic-release/git": "^10.0.0",
96
84
  "@storybook/addon-links": "^10.0.0",
97
85
  "@storybook/addon-vitest": "^10.0.0",
98
86
  "@storybook/web-components": "^10.0.0",
@@ -107,12 +95,8 @@
107
95
  "husky": "^9.0.11",
108
96
  "jsdom": "^26.0.0",
109
97
  "playwright": "^1.52.0",
110
- "semantic-release": "^25.0.0",
111
98
  "storybook": "^10.0.0",
112
99
  "typescript": "^5.4.3",
113
100
  "vitest": "^4.0.0"
114
- },
115
- "overrides": {
116
- "conventional-changelog-conventionalcommits": ">= 8.0.0"
117
101
  }
118
102
  }