@bpmnkit/operate 0.0.5

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.
Files changed (72) hide show
  1. package/README.md +131 -0
  2. package/dist/components/badge.d.ts +2 -0
  3. package/dist/components/badge.js +2 -0
  4. package/dist/components/card.d.ts +2 -0
  5. package/dist/components/card.js +2 -0
  6. package/dist/components/chart.d.ts +6 -0
  7. package/dist/components/chart.js +161 -0
  8. package/dist/components/filter-table.d.ts +20 -0
  9. package/dist/components/filter-table.js +215 -0
  10. package/dist/components/table.d.ts +2 -0
  11. package/dist/components/table.js +2 -0
  12. package/dist/css.d.ts +2 -0
  13. package/dist/css.js +1071 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.js +2 -0
  16. package/dist/mock-data.d.ts +18 -0
  17. package/dist/mock-data.js +509 -0
  18. package/dist/operate.d.ts +3 -0
  19. package/dist/operate.js +308 -0
  20. package/dist/router.d.ts +9 -0
  21. package/dist/router.js +49 -0
  22. package/dist/stores/base.d.ts +20 -0
  23. package/dist/stores/base.js +32 -0
  24. package/dist/stores/dashboard.d.ts +6 -0
  25. package/dist/stores/dashboard.js +19 -0
  26. package/dist/stores/decisions.d.ts +9 -0
  27. package/dist/stores/decisions.js +19 -0
  28. package/dist/stores/definitions.d.ts +9 -0
  29. package/dist/stores/definitions.js +19 -0
  30. package/dist/stores/incidents.d.ts +10 -0
  31. package/dist/stores/incidents.js +27 -0
  32. package/dist/stores/instances.d.ts +15 -0
  33. package/dist/stores/instances.js +33 -0
  34. package/dist/stores/jobs.d.ts +10 -0
  35. package/dist/stores/jobs.js +19 -0
  36. package/dist/stores/tasks.d.ts +10 -0
  37. package/dist/stores/tasks.js +19 -0
  38. package/dist/stream.d.ts +12 -0
  39. package/dist/stream.js +42 -0
  40. package/dist/types.d.ts +48 -0
  41. package/dist/types.js +2 -0
  42. package/dist/views/dashboard.d.ts +6 -0
  43. package/dist/views/dashboard.js +98 -0
  44. package/dist/views/decision-detail.d.ts +15 -0
  45. package/dist/views/decision-detail.js +167 -0
  46. package/dist/views/decisions.d.ts +7 -0
  47. package/dist/views/decisions.js +78 -0
  48. package/dist/views/definition-detail.d.ts +15 -0
  49. package/dist/views/definition-detail.js +339 -0
  50. package/dist/views/definitions.d.ts +7 -0
  51. package/dist/views/definitions.js +70 -0
  52. package/dist/views/header.d.ts +9 -0
  53. package/dist/views/header.js +49 -0
  54. package/dist/views/incident-detail.d.ts +14 -0
  55. package/dist/views/incident-detail.js +540 -0
  56. package/dist/views/incidents.d.ts +7 -0
  57. package/dist/views/incidents.js +108 -0
  58. package/dist/views/instance-detail.d.ts +16 -0
  59. package/dist/views/instance-detail.js +728 -0
  60. package/dist/views/instances.d.ts +7 -0
  61. package/dist/views/instances.js +249 -0
  62. package/dist/views/jobs.d.ts +6 -0
  63. package/dist/views/jobs.js +60 -0
  64. package/dist/views/messages.d.ts +11 -0
  65. package/dist/views/messages.js +431 -0
  66. package/dist/views/nav.d.ts +10 -0
  67. package/dist/views/nav.js +60 -0
  68. package/dist/views/task-detail.d.ts +13 -0
  69. package/dist/views/task-detail.js +187 -0
  70. package/dist/views/tasks.d.ts +7 -0
  71. package/dist/views/tasks.js +72 -0
  72. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ <div align="center">
2
+ <img src="https://raw.githubusercontent.com/bpmn-sdk/monorepo/main/doc/logos/logo-2-gateway.svg" width="72" height="72" alt="BPMN Kit logo">
3
+ <h1>@bpmnkit/operate</h1>
4
+ <p>Monitoring and operations frontend for Camunda 8 clusters — real-time SSE, zero dependencies</p>
5
+
6
+ [![npm](https://img.shields.io/npm/v/@bpmnkit/operate?style=flat-square&color=6244d7)](https://www.npmjs.com/package/@bpmnkit/operate)
7
+ [![license](https://img.shields.io/npm/l/@bpmnkit/operate?style=flat-square)](https://github.com/bpmnkit/monorepo/blob/main/LICENSE)
8
+ [![typescript](https://img.shields.io/badge/TypeScript-strict-6244d7?style=flat-square&logo=typescript&logoColor=white)](https://github.com/bpmnkit/monorepo)
9
+
10
+ [Documentation](https://bpmn-sdk-docs.pages.dev) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/packages/operate/CHANGELOG.md)
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## Overview
16
+
17
+ `@bpmnkit/operate` is a zero-dependency monitoring and operations frontend for Camunda 8. Mount it into any HTML element to get a full process monitoring UI — live dashboard, instance browser, incident management, job queue, and user tasks.
18
+
19
+ It pairs with the `@bpmnkit/proxy` local server, which polls the Camunda REST API server-side and pushes updates via **Server-Sent Events**. The frontend stays clean with no polling timers.
20
+
21
+ A **mock mode** (`mock: true`) ships fixture data without any running proxy or cluster — useful for demos and local development.
22
+
23
+ ## Features
24
+
25
+ - **Dashboard** — real-time stats: active instances, open incidents, active jobs, pending tasks
26
+ - **Process Definitions** — deployed process list with name, version, and tenant
27
+ - **Process Instances** — paginated list with state filter (Active / Completed / Terminated)
28
+ - **Instance Detail** — BPMN canvas via `@bpmnkit/canvas` with live token-highlight overlay; active elements glow amber, visited elements show green tint
29
+ - **Incidents** — error type, message, process, and resolution state
30
+ - **Jobs** — job type, worker, retries, state, error message
31
+ - **User Tasks** — name, assignee, state, due date, priority
32
+ - **Profile switcher** — header dropdown populated from the proxy `/profiles` endpoint; switches reconnect all SSE streams
33
+ - **Mock/demo mode** — fully self-contained fixture data, no cluster required
34
+ - **SSE architecture** — proxy polls server-side; frontend opens one `EventSource` per view, gets pushed updates
35
+ - **Hash router** — `#/`, `#/instances`, `#/instances/:key`, `#/definitions`, etc.
36
+ - **Themeable** — light / dark / auto via `--bpmnkit-*` CSS custom properties from `@bpmnkit/ui`; theme choice persisted to `localStorage`
37
+
38
+ ## Installation
39
+
40
+ ```sh
41
+ npm install @bpmnkit/operate @bpmnkit/proxy
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ### Demo mode (no cluster needed)
47
+
48
+ ```typescript
49
+ import { createOperate } from "@bpmnkit/operate"
50
+
51
+ createOperate({
52
+ container: document.getElementById("app")!,
53
+ mock: true,
54
+ theme: "auto",
55
+ })
56
+ ```
57
+
58
+ ### Connected to a real Camunda cluster via proxy
59
+
60
+ ```typescript
61
+ import { createOperate } from "@bpmnkit/operate"
62
+
63
+ createOperate({
64
+ container: document.getElementById("app")!,
65
+ proxyUrl: "http://localhost:3033", // default
66
+ profile: "production", // optional, uses active profile if omitted
67
+ pollInterval: 15_000, // ms between server-side polls (default: 30 000)
68
+ theme: "dark",
69
+ })
70
+ ```
71
+
72
+ The proxy must be running (`pnpm proxy`) and have at least one profile configured via the `casen` CLI.
73
+
74
+ ## How it works
75
+
76
+ ```
77
+ Browser ──── EventSource ────▶ @bpmnkit/proxy ──── CamundaClient ────▶ Camunda cluster
78
+ ◀─── SSE events ────── (polls on interval, pushes results)
79
+ ```
80
+
81
+ 1. Each view opens an SSE connection to `/operate/stream?topic=<view>&interval=<ms>`.
82
+ 2. The proxy creates a `CamundaClient` using the configured profile's auth credentials.
83
+ 3. On each polling tick, the proxy fetches the relevant Camunda data and emits a `{ type: "data", payload }` SSE event.
84
+ 4. The store updates and the view re-renders.
85
+
86
+ ## API Reference
87
+
88
+ ### `createOperate(options)`
89
+
90
+ ```typescript
91
+ interface OperateOptions {
92
+ container: HTMLElement
93
+ proxyUrl?: string // default: "http://localhost:3033"
94
+ profile?: string // profile name; uses active profile if omitted
95
+ theme?: "light" | "dark" | "auto" // default: "auto"
96
+ pollInterval?: number // ms between polls; default: 30 000
97
+ mock?: boolean // use built-in fixture data; default: false
98
+ }
99
+ ```
100
+
101
+ Returns an `OperateApi`:
102
+
103
+ ```typescript
104
+ interface OperateApi {
105
+ el: HTMLElement
106
+ setProfile(name: string | null): void
107
+ setTheme(theme: "light" | "dark" | "auto"): void
108
+ navigate(path: string): void // e.g. "/instances/123456789"
109
+ destroy(): void
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Related Packages
116
+
117
+ | Package | Description |
118
+ |---------|-------------|
119
+ | [`@bpmnkit/core`](https://www.npmjs.com/package/@bpmnkit/core) | BPMN/DMN/Form parser, builder, layout engine |
120
+ | [`@bpmnkit/canvas`](https://www.npmjs.com/package/@bpmnkit/canvas) | Zero-dependency SVG BPMN viewer |
121
+ | [`@bpmnkit/editor`](https://www.npmjs.com/package/@bpmnkit/editor) | Full-featured interactive BPMN editor |
122
+ | [`@bpmnkit/engine`](https://www.npmjs.com/package/@bpmnkit/engine) | Lightweight BPMN process execution engine |
123
+ | [`@bpmnkit/feel`](https://www.npmjs.com/package/@bpmnkit/feel) | FEEL expression language parser & evaluator |
124
+ | [`@bpmnkit/plugins`](https://www.npmjs.com/package/@bpmnkit/plugins) | 22 composable canvas plugins |
125
+ | [`@bpmnkit/api`](https://www.npmjs.com/package/@bpmnkit/api) | Camunda 8 REST API TypeScript client |
126
+ | [`@bpmnkit/ascii`](https://www.npmjs.com/package/@bpmnkit/ascii) | Render BPMN diagrams as Unicode ASCII art |
127
+ | [`@bpmnkit/profiles`](https://www.npmjs.com/package/@bpmnkit/profiles) | Shared auth, profile storage, and client factories for CLI & proxy |
128
+
129
+ ## License
130
+
131
+ [MIT](https://github.com/bpmnkit/monorepo/blob/main/LICENSE) © bpmn-sdk
@@ -0,0 +1,2 @@
1
+ export { badge, cell } from "@bpmnkit/ui";
2
+ //# sourceMappingURL=badge.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { badge, cell } from "@bpmnkit/ui";
2
+ //# sourceMappingURL=badge.js.map
@@ -0,0 +1,2 @@
1
+ export { createStatsCard } from "@bpmnkit/ui";
2
+ //# sourceMappingURL=card.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { createStatsCard } from "@bpmnkit/ui";
2
+ //# sourceMappingURL=card.js.map
@@ -0,0 +1,6 @@
1
+ import type { DashboardData } from "../types.js";
2
+ export declare function createBarChart(container: HTMLElement): {
3
+ update(data: DashboardData | null): void;
4
+ destroy(): void;
5
+ };
6
+ //# sourceMappingURL=chart.d.ts.map
@@ -0,0 +1,161 @@
1
+ const METRICS = [
2
+ { key: "activeInstances", label: "Active Instances", cssVar: "--bpmnkit-accent" },
3
+ { key: "openIncidents", label: "Open Incidents", cssVar: "--op-c-amber" },
4
+ { key: "activeJobs", label: "Active Jobs", cssVar: "--op-c-green" },
5
+ { key: "pendingTasks", label: "Pending Tasks", cssVar: "--op-c-purple" },
6
+ ];
7
+ const MARGIN = { top: 12, right: 16, bottom: 36, left: 36 };
8
+ const SVG_NS = "http://www.w3.org/2000/svg";
9
+ function niceMax(raw) {
10
+ if (raw <= 0)
11
+ return 4;
12
+ const mag = 10 ** Math.floor(Math.log10(raw));
13
+ const nice = Math.ceil(raw / mag) * mag;
14
+ return Math.max(nice, 4);
15
+ }
16
+ function makeSvgEl(tag) {
17
+ return document.createElementNS(SVG_NS, tag);
18
+ }
19
+ // ── Chart factory ─────────────────────────────────────────────────────────────
20
+ export function createBarChart(container) {
21
+ const el = document.createElement("div");
22
+ el.className = "op-chart";
23
+ container.appendChild(el);
24
+ const svg = makeSvgEl("svg");
25
+ svg.setAttribute("class", "op-chart-svg");
26
+ svg.setAttribute("aria-hidden", "true");
27
+ el.appendChild(svg);
28
+ let lastData = null;
29
+ let svgWidth = 0;
30
+ let svgHeight = 0;
31
+ function clearSvg() {
32
+ while (svg.firstChild)
33
+ svg.removeChild(svg.firstChild);
34
+ }
35
+ function draw(data) {
36
+ clearSvg();
37
+ if (svgWidth <= 0 || svgHeight <= 0)
38
+ return;
39
+ if (!data) {
40
+ // Loading dots
41
+ const cx = svgWidth / 2;
42
+ const cy = svgHeight / 2;
43
+ for (let i = 0; i < 3; i++) {
44
+ const circle = makeSvgEl("circle");
45
+ circle.setAttribute("cx", String(cx + (i - 1) * 16));
46
+ circle.setAttribute("cy", String(cy));
47
+ circle.setAttribute("r", "4");
48
+ circle.setAttribute("class", "op-chart-dot-pulse");
49
+ circle.setAttribute("style", `animation-delay: ${i * 0.2}s`);
50
+ svg.appendChild(circle);
51
+ }
52
+ return;
53
+ }
54
+ const plotW = Math.max(1, svgWidth - MARGIN.left - MARGIN.right);
55
+ const plotH = Math.max(1, svgHeight - MARGIN.top - MARGIN.bottom);
56
+ // Y scale
57
+ let yMax = 0;
58
+ for (const m of METRICS) {
59
+ const v = data[m.key];
60
+ if (v > yMax)
61
+ yMax = v;
62
+ }
63
+ yMax = niceMax(yMax);
64
+ function toY(v) {
65
+ return MARGIN.top + plotH - (v / yMax) * plotH;
66
+ }
67
+ // Y grid + labels
68
+ for (let i = 0; i <= 4; i++) {
69
+ const v = Math.round((i / 4) * yMax);
70
+ const y = toY(v);
71
+ const line = makeSvgEl("line");
72
+ line.setAttribute("x1", String(MARGIN.left));
73
+ line.setAttribute("x2", String(MARGIN.left + plotW));
74
+ line.setAttribute("y1", String(y));
75
+ line.setAttribute("y2", String(y));
76
+ line.setAttribute("class", i === 0 ? "op-chart-axis" : "op-chart-grid");
77
+ svg.appendChild(line);
78
+ const lbl = makeSvgEl("text");
79
+ lbl.setAttribute("x", String(MARGIN.left - 6));
80
+ lbl.setAttribute("y", String(y + 4));
81
+ lbl.setAttribute("text-anchor", "end");
82
+ lbl.setAttribute("class", "op-chart-axis-label");
83
+ lbl.textContent = String(v);
84
+ svg.appendChild(lbl);
85
+ }
86
+ // X axis baseline
87
+ const xBase = makeSvgEl("line");
88
+ xBase.setAttribute("x1", String(MARGIN.left));
89
+ xBase.setAttribute("x2", String(MARGIN.left + plotW));
90
+ xBase.setAttribute("y1", String(MARGIN.top + plotH));
91
+ xBase.setAttribute("y2", String(MARGIN.top + plotH));
92
+ xBase.setAttribute("class", "op-chart-axis");
93
+ svg.appendChild(xBase);
94
+ // Bars — one per metric, evenly spaced
95
+ const n = METRICS.length;
96
+ const groupGap = Math.max(8, plotW * 0.06);
97
+ const barW = Math.max(1, (plotW - groupGap * (n + 1)) / n);
98
+ for (let i = 0; i < METRICS.length; i++) {
99
+ const m = METRICS[i];
100
+ if (!m)
101
+ continue;
102
+ const v = data[m.key];
103
+ const barH = (v / yMax) * plotH;
104
+ const x = MARGIN.left + groupGap * (i + 1) + barW * i;
105
+ const y = MARGIN.top + plotH - barH;
106
+ const rect = makeSvgEl("rect");
107
+ rect.setAttribute("x", String(x));
108
+ rect.setAttribute("y", String(y));
109
+ rect.setAttribute("width", String(barW));
110
+ rect.setAttribute("height", String(Math.max(1, barH)));
111
+ rect.setAttribute("fill", `var(${m.cssVar})`);
112
+ rect.setAttribute("opacity", "0.85");
113
+ rect.setAttribute("rx", "2");
114
+ svg.appendChild(rect);
115
+ // Value label above bar
116
+ if (barH > 0) {
117
+ const valLbl = makeSvgEl("text");
118
+ valLbl.setAttribute("x", String(x + barW / 2));
119
+ valLbl.setAttribute("y", String(Math.max(MARGIN.top + 10, y - 4)));
120
+ valLbl.setAttribute("text-anchor", "middle");
121
+ valLbl.setAttribute("class", "op-chart-axis-label");
122
+ valLbl.textContent = String(v);
123
+ svg.appendChild(valLbl);
124
+ }
125
+ // X label below bar
126
+ const xLbl = makeSvgEl("text");
127
+ xLbl.setAttribute("x", String(x + barW / 2));
128
+ xLbl.setAttribute("y", String(MARGIN.top + plotH + 20));
129
+ xLbl.setAttribute("text-anchor", "middle");
130
+ xLbl.setAttribute("class", "op-chart-axis-label");
131
+ // Abbreviate for space
132
+ const short = m.label.replace("Active ", "").replace("Open ", "").replace("Pending ", "");
133
+ xLbl.textContent = short;
134
+ svg.appendChild(xLbl);
135
+ }
136
+ }
137
+ function drawLast() {
138
+ draw(lastData);
139
+ }
140
+ const ro = new ResizeObserver((entries) => {
141
+ const entry = entries[0];
142
+ if (!entry)
143
+ return;
144
+ svgWidth = Math.floor(entry.contentRect.width);
145
+ svgHeight = Math.floor(entry.contentRect.height);
146
+ svg.setAttribute("viewBox", `0 0 ${svgWidth} ${svgHeight}`);
147
+ drawLast();
148
+ });
149
+ ro.observe(svg);
150
+ return {
151
+ update(data) {
152
+ lastData = data;
153
+ draw(data);
154
+ },
155
+ destroy() {
156
+ ro.disconnect();
157
+ el.remove();
158
+ },
159
+ };
160
+ }
161
+ //# sourceMappingURL=chart.js.map
@@ -0,0 +1,20 @@
1
+ export interface FilterColumn<T> {
2
+ label: string;
3
+ width?: string;
4
+ render(row: T): string | HTMLElement;
5
+ /** If provided, column header becomes clickable for sort */
6
+ sortValue?: (row: T) => string | number | null | undefined;
7
+ }
8
+ export declare function createFilterTable<T>(options: {
9
+ columns: FilterColumn<T>[];
10
+ /** Function returning searchable text for a row */
11
+ searchFn?: (row: T) => string;
12
+ onRowClick?: (row: T) => void;
13
+ emptyText?: string;
14
+ /** Initial page size (default: 10) */
15
+ pageSize?: number;
16
+ }): {
17
+ el: HTMLElement;
18
+ setRows(rows: T[]): void;
19
+ };
20
+ //# sourceMappingURL=filter-table.d.ts.map
@@ -0,0 +1,215 @@
1
+ export function createFilterTable(options) {
2
+ let allRows = [];
3
+ let searchQuery = "";
4
+ let sortState = null;
5
+ let currentPage = 0;
6
+ let pageSize = options.pageSize ?? 10;
7
+ const el = document.createElement("div");
8
+ el.className = "op-filter-table";
9
+ // Toolbar row: search input + count
10
+ const toolbar = document.createElement("div");
11
+ toolbar.className = "op-toolbar";
12
+ const searchInput = document.createElement("input");
13
+ searchInput.type = "text";
14
+ searchInput.className = "op-search";
15
+ searchInput.placeholder = "Search…";
16
+ toolbar.appendChild(searchInput);
17
+ const countEl = document.createElement("span");
18
+ countEl.className = "op-search-count";
19
+ toolbar.appendChild(countEl);
20
+ el.appendChild(toolbar);
21
+ // Table wrap
22
+ const tableWrap = document.createElement("div");
23
+ tableWrap.className = "bpmnkit-table-wrap";
24
+ el.appendChild(tableWrap);
25
+ // Header with sort support
26
+ const headerEl = document.createElement("div");
27
+ headerEl.className = "bpmnkit-table-header";
28
+ const headerCells = [];
29
+ for (let i = 0; i < options.columns.length; i++) {
30
+ const col = options.columns[i];
31
+ if (!col)
32
+ continue;
33
+ const th = document.createElement("div");
34
+ th.className = "bpmnkit-table-th";
35
+ if (col.width)
36
+ th.style.width = col.width;
37
+ if (col.sortValue) {
38
+ th.classList.add("op-th-sort");
39
+ const labelSpan = document.createElement("span");
40
+ labelSpan.textContent = col.label;
41
+ th.appendChild(labelSpan);
42
+ const icon = document.createElement("span");
43
+ icon.className = "op-sort-icon";
44
+ icon.textContent = "↕";
45
+ th.appendChild(icon);
46
+ const idx = i;
47
+ th.addEventListener("click", () => {
48
+ if (sortState?.col === idx) {
49
+ sortState = sortState.dir === "asc" ? { col: idx, dir: "desc" } : null;
50
+ }
51
+ else {
52
+ sortState = { col: idx, dir: "asc" };
53
+ }
54
+ currentPage = 0;
55
+ updateSortIcons();
56
+ renderRows();
57
+ });
58
+ }
59
+ else {
60
+ th.textContent = col.label;
61
+ }
62
+ headerCells.push(th);
63
+ headerEl.appendChild(th);
64
+ }
65
+ tableWrap.appendChild(headerEl);
66
+ const bodyEl = document.createElement("div");
67
+ bodyEl.className = "bpmnkit-table-body";
68
+ tableWrap.appendChild(bodyEl);
69
+ // Pagination bar (outside scroll area, always visible)
70
+ const paginationEl = document.createElement("div");
71
+ paginationEl.className = "op-pagination";
72
+ el.appendChild(paginationEl);
73
+ function updateSortIcons() {
74
+ for (let i = 0; i < headerCells.length; i++) {
75
+ const icon = headerCells[i]?.querySelector(".op-sort-icon");
76
+ if (!icon)
77
+ continue;
78
+ if (sortState?.col === i) {
79
+ icon.textContent = sortState.dir === "asc" ? "↑" : "↓";
80
+ icon.classList.add("op-sort-icon--active");
81
+ }
82
+ else {
83
+ icon.textContent = "↕";
84
+ icon.classList.remove("op-sort-icon--active");
85
+ }
86
+ }
87
+ }
88
+ function applyFilterSort() {
89
+ let rows = allRows;
90
+ if (searchQuery && options.searchFn) {
91
+ const q = searchQuery.toLowerCase();
92
+ rows = rows.filter((row) => options.searchFn?.(row).toLowerCase().includes(q));
93
+ }
94
+ if (sortState !== null) {
95
+ const st = sortState;
96
+ const col = options.columns[st.col];
97
+ if (col?.sortValue) {
98
+ const dir = st.dir === "asc" ? 1 : -1;
99
+ rows = [...rows].sort((a, b) => {
100
+ const av = col.sortValue?.(a) ?? "";
101
+ const bv = col.sortValue?.(b) ?? "";
102
+ if (av < bv)
103
+ return -1 * dir;
104
+ if (av > bv)
105
+ return 1 * dir;
106
+ return 0;
107
+ });
108
+ }
109
+ }
110
+ return rows;
111
+ }
112
+ function renderPagination(total, start, end, totalPages) {
113
+ paginationEl.innerHTML = "";
114
+ const sizeLabel = document.createElement("span");
115
+ sizeLabel.textContent = "Rows:";
116
+ paginationEl.appendChild(sizeLabel);
117
+ const sizeSelect = document.createElement("select");
118
+ sizeSelect.className = "op-page-size";
119
+ for (const sz of [10, 25, 50, 100]) {
120
+ const opt = document.createElement("option");
121
+ opt.value = String(sz);
122
+ opt.textContent = String(sz);
123
+ if (sz === pageSize)
124
+ opt.selected = true;
125
+ sizeSelect.appendChild(opt);
126
+ }
127
+ sizeSelect.addEventListener("change", () => {
128
+ pageSize = Number(sizeSelect.value);
129
+ currentPage = 0;
130
+ renderRows();
131
+ });
132
+ paginationEl.appendChild(sizeSelect);
133
+ const prevBtn = document.createElement("button");
134
+ prevBtn.className = "op-pagination-btn";
135
+ prevBtn.textContent = "‹";
136
+ prevBtn.disabled = currentPage === 0;
137
+ prevBtn.addEventListener("click", () => {
138
+ currentPage--;
139
+ renderRows();
140
+ });
141
+ paginationEl.appendChild(prevBtn);
142
+ const infoEl = document.createElement("span");
143
+ infoEl.className = "op-pagination-info";
144
+ infoEl.textContent = total > 0 ? `${start + 1}–${end} of ${total}` : "0 results";
145
+ paginationEl.appendChild(infoEl);
146
+ const nextBtn = document.createElement("button");
147
+ nextBtn.className = "op-pagination-btn";
148
+ nextBtn.textContent = "›";
149
+ nextBtn.disabled = currentPage >= totalPages - 1;
150
+ nextBtn.addEventListener("click", () => {
151
+ currentPage++;
152
+ renderRows();
153
+ });
154
+ paginationEl.appendChild(nextBtn);
155
+ }
156
+ function renderRows() {
157
+ const filtered = applyFilterSort();
158
+ const total = filtered.length;
159
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
160
+ if (currentPage >= totalPages)
161
+ currentPage = totalPages - 1;
162
+ const start = currentPage * pageSize;
163
+ const pageRows = filtered.slice(start, start + pageSize);
164
+ const end = start + pageRows.length;
165
+ countEl.textContent =
166
+ filtered.length !== allRows.length
167
+ ? `${filtered.length} / ${allRows.length}`
168
+ : String(allRows.length);
169
+ bodyEl.innerHTML = "";
170
+ if (total === 0) {
171
+ const empty = document.createElement("div");
172
+ empty.className = "bpmnkit-table-empty";
173
+ empty.textContent = searchQuery ? "No results" : (options.emptyText ?? "No data");
174
+ bodyEl.appendChild(empty);
175
+ renderPagination(0, 0, 0, 1);
176
+ return;
177
+ }
178
+ for (const row of pageRows) {
179
+ const tr = document.createElement("div");
180
+ tr.className = "bpmnkit-table-row";
181
+ if (options.onRowClick) {
182
+ tr.classList.add("bpmnkit-table-row--clickable");
183
+ tr.addEventListener("click", () => options.onRowClick?.(row));
184
+ }
185
+ for (const col of options.columns) {
186
+ const td = document.createElement("div");
187
+ td.className = "bpmnkit-table-td";
188
+ if (col.width)
189
+ td.style.width = col.width;
190
+ const content = col.render(row);
191
+ if (typeof content === "string")
192
+ td.textContent = content;
193
+ else
194
+ td.appendChild(content);
195
+ tr.appendChild(td);
196
+ }
197
+ bodyEl.appendChild(tr);
198
+ }
199
+ renderPagination(total, start, end, totalPages);
200
+ }
201
+ searchInput.addEventListener("input", () => {
202
+ searchQuery = searchInput.value;
203
+ currentPage = 0;
204
+ renderRows();
205
+ });
206
+ return {
207
+ el,
208
+ setRows(rows) {
209
+ allRows = rows;
210
+ currentPage = 0;
211
+ renderRows();
212
+ },
213
+ };
214
+ }
215
+ //# sourceMappingURL=filter-table.js.map
@@ -0,0 +1,2 @@
1
+ export { type Column, type TableOptions, createTable } from "@bpmnkit/ui";
2
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { createTable } from "@bpmnkit/ui";
2
+ //# sourceMappingURL=table.js.map
package/dist/css.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function injectOperateStyles(): void;
2
+ //# sourceMappingURL=css.d.ts.map