@ashley-shrok/viewmodel-shell 0.14.0 → 0.15.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/dist/browser.js +24 -35
- package/dist/index.d.ts +12 -24
- package/dist/tui.js +7 -26
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -674,24 +674,18 @@ export class BrowserAdapter {
|
|
|
674
674
|
box.checked = allOnPage;
|
|
675
675
|
box.indeterminate = someOnPage && !allOnPage;
|
|
676
676
|
box.addEventListener("change", () => {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
rowBox.checked = want;
|
|
690
|
-
const tr = rowBox.closest(".vms-table__row");
|
|
691
|
-
if (tr)
|
|
692
|
-
tr.classList.toggle("vms-table__row--selected", want);
|
|
693
|
-
});
|
|
694
|
-
}
|
|
677
|
+
// Toggle every row checkbox + class in this table to match the header.
|
|
678
|
+
// No dispatch — selection is purely DOM-local; bulk actions harvest the
|
|
679
|
+
// checked rows when a `selection.buttons[]` entry is clicked.
|
|
680
|
+
const want = box.checked;
|
|
681
|
+
table.querySelectorAll("tbody input.vms-table__select").forEach(rowBox => {
|
|
682
|
+
if (rowBox.disabled)
|
|
683
|
+
return;
|
|
684
|
+
rowBox.checked = want;
|
|
685
|
+
const tr = rowBox.closest(".vms-table__row");
|
|
686
|
+
if (tr)
|
|
687
|
+
tr.classList.toggle("vms-table__row--selected", want);
|
|
688
|
+
});
|
|
695
689
|
});
|
|
696
690
|
th.appendChild(box);
|
|
697
691
|
headerRow.appendChild(th);
|
|
@@ -783,23 +777,18 @@ export class BrowserAdapter {
|
|
|
783
777
|
// data-id is what selection.buttons[] harvest reads on click.
|
|
784
778
|
box.dataset.id = rowId;
|
|
785
779
|
box.addEventListener("change", () => {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
all.forEach(b => { if (b.checked)
|
|
799
|
-
checked++; });
|
|
800
|
-
headerBox.checked = all.length > 0 && checked === all.length;
|
|
801
|
-
headerBox.indeterminate = checked > 0 && checked < all.length;
|
|
802
|
-
}
|
|
780
|
+
// Flip the row class to mirror the box, then reconcile the header
|
|
781
|
+
// select-all (could now be all / some / none). No dispatch — bulk
|
|
782
|
+
// actions read the DOM via the selection.buttons[] harvest.
|
|
783
|
+
tr.classList.toggle("vms-table__row--selected", box.checked);
|
|
784
|
+
const headerBox = table.querySelector("thead input.vms-table__select--all");
|
|
785
|
+
if (headerBox) {
|
|
786
|
+
const all = table.querySelectorAll("tbody input.vms-table__select:not(:disabled)");
|
|
787
|
+
let checked = 0;
|
|
788
|
+
all.forEach(b => { if (b.checked)
|
|
789
|
+
checked++; });
|
|
790
|
+
headerBox.checked = all.length > 0 && checked === all.length;
|
|
791
|
+
headerBox.indeterminate = checked > 0 && checked < all.length;
|
|
803
792
|
}
|
|
804
793
|
});
|
|
805
794
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -213,31 +213,19 @@ export interface TableRow {
|
|
|
213
213
|
variant?: string;
|
|
214
214
|
}
|
|
215
215
|
export interface TableSelection {
|
|
216
|
-
/** Row ids that should render PRE-SELECTED on
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* server's initial pre-selection only — subsequent toggles are purely
|
|
220
|
-
* client-side DOM state and the server doesn't see them until a `buttons[]`
|
|
221
|
-
* click harvests them. */
|
|
216
|
+
/** Row ids that should render PRE-SELECTED on initial render — the server's
|
|
217
|
+
* initial pre-selection. Subsequent toggles are purely client-side DOM state;
|
|
218
|
+
* the server doesn't see them until a `buttons[]` click harvests them. */
|
|
222
219
|
selectedIds: string[];
|
|
223
|
-
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
action?: ActionEvent;
|
|
233
|
-
/** OPTIONAL (0.13.0). When present, the adapter renders these as a bulk-action
|
|
234
|
-
* toolbar ABOVE the table. On click, each button harvests the currently
|
|
235
|
-
* checked rows from the DOM and dispatches its `action` with
|
|
236
|
-
* `{ selectedIds: [...] }` merged into its `context`. Designed primarily to
|
|
237
|
-
* pair with local mode (`action` absent) — it's how the server learns the
|
|
238
|
-
* selection without a per-toggle round-trip — but works in server-truth mode
|
|
239
|
-
* too (the harvest matches `selectedIds` since the DOM reflects server-truth
|
|
240
|
-
* after each render). */
|
|
220
|
+
/** When present, the adapter renders these as a bulk-action toolbar ABOVE
|
|
221
|
+
* the table. On click, each button harvests the currently-checked rows from
|
|
222
|
+
* the DOM and dispatches its `action` with `{ selectedIds: [...] }` merged
|
|
223
|
+
* into its `context`. This is the only way to act on the selection — there
|
|
224
|
+
* is no per-toggle dispatch (0.15.0 removed the `action` mode that did,
|
|
225
|
+
* because rapid clicks were dropped by the dispatch guard and the in-flight
|
|
226
|
+
* response wiped the DOM). If a future release needs cross-page persistence
|
|
227
|
+
* or live "N selected" indicators, the way back is a redesigned wire shape
|
|
228
|
+
* (dispatch queueing + optimistic preservation), not the old `action` mode. */
|
|
241
229
|
buttons?: ButtonNode[];
|
|
242
230
|
}
|
|
243
231
|
export interface TablePagination {
|
package/dist/tui.js
CHANGED
|
@@ -914,30 +914,15 @@ function TableView({ node, ctx }) {
|
|
|
914
914
|
context: { ...(node.sortAction.context ?? {}), column: columnKey, direction },
|
|
915
915
|
});
|
|
916
916
|
};
|
|
917
|
-
// Selection — leading [x]/[ ] column.
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
//
|
|
921
|
-
//
|
|
922
|
-
// hook-less conformance walker. The browser carries the real local-mode
|
|
923
|
-
// workflow; TUI is experimental — use the browser for interactive bulk
|
|
924
|
-
// selection. The selection.buttons[] toolbar still renders below.
|
|
917
|
+
// Selection — leading [x]/[ ] column. TUI is render-only: checkboxes display
|
|
918
|
+
// selectedIds; clicks are inert (the TUI doesn't track DOM-equivalent state
|
|
919
|
+
// across the hook-less conformance walker). Bulk actions live in
|
|
920
|
+
// selection.buttons[]; the harvest reads sel.selectedIds (server's
|
|
921
|
+
// pre-selection). The browser carries the interactive surface.
|
|
925
922
|
const sel = node.selection;
|
|
926
923
|
const effectiveSet = sel ? new Set(sel.selectedIds) : null;
|
|
927
924
|
const allOnPage = sel != null && node.rows.length > 0 &&
|
|
928
925
|
node.rows.every((r) => r.id != null && effectiveSet.has(r.id));
|
|
929
|
-
const onToggleAll = sel?.action
|
|
930
|
-
? () => ctx.onAction({
|
|
931
|
-
name: sel.action.name,
|
|
932
|
-
context: { ...(sel.action.context ?? {}), all: true, checked: !allOnPage },
|
|
933
|
-
})
|
|
934
|
-
: undefined;
|
|
935
|
-
const onToggleRow = sel?.action
|
|
936
|
-
? (rowId, checked) => ctx.onAction({
|
|
937
|
-
name: sel.action.name,
|
|
938
|
-
context: { ...(sel.action.context ?? {}), id: rowId, checked },
|
|
939
|
-
})
|
|
940
|
-
: undefined;
|
|
941
926
|
return (_jsx("scrollbox", { focused: focused, focusable: isPaneFocusable, borderColor: focused ? "#88aaff" : "#555555", focusedBorderColor: "#88aaff", flexGrow: 1, flexShrink: 1, children: _jsxs("box", { flexDirection: "column", children: [sel?.buttons && sel.buttons.length > 0 ? (_jsx("box", { flexDirection: "row", gap: 1, children: sel.buttons.map((btn, i) => {
|
|
942
927
|
const harvestCtx = {
|
|
943
928
|
...ctx,
|
|
@@ -950,7 +935,7 @@ function TableView({ node, ctx }) {
|
|
|
950
935
|
},
|
|
951
936
|
};
|
|
952
937
|
return _jsx(ButtonView, { node: btn, ctx: harvestCtx }, i);
|
|
953
|
-
}) })) : null, _jsxs("box", { flexDirection: "row", gap: 2, children: [sel ? (_jsx("text", { attributes: 1 /* BOLD */,
|
|
938
|
+
}) })) : null, _jsxs("box", { flexDirection: "row", gap: 2, children: [sel ? (_jsx("text", { attributes: 1 /* BOLD */, children: allOnPage ? "[x]" : "[ ]" })) : null, node.columns.map((c) => {
|
|
954
939
|
const isSorted = node.sortColumn === c.key;
|
|
955
940
|
const caret = isSorted ? (node.sortDirection === "desc" ? " ↓" : " ↑") : "";
|
|
956
941
|
// B5 — only sortable headers respond to clicks (matches BrowserAdapter).
|
|
@@ -964,11 +949,7 @@ function TableView({ node, ctx }) {
|
|
|
964
949
|
: undefined;
|
|
965
950
|
return (_jsxs("box", { flexDirection: "row", gap: 2, ...(onRowClick ? { onMouseDown: onRowClick } : {}), children: [sel ? (() => {
|
|
966
951
|
const isSel = row.id != null && effectiveSet.has(row.id);
|
|
967
|
-
|
|
968
|
-
const onBox = rowId != null && onToggleRow
|
|
969
|
-
? () => onToggleRow(rowId, !isSel)
|
|
970
|
-
: undefined;
|
|
971
|
-
return (_jsx("text", { fg: isSel ? "#88ff88" : "#888888", ...(onBox ? { onMouseDown: onBox } : {}), children: isSel ? "[x]" : "[ ]" }));
|
|
952
|
+
return (_jsx("text", { fg: isSel ? "#88ff88" : "#888888", children: isSel ? "[x]" : "[ ]" }));
|
|
972
953
|
})() : null, node.columns.map((c) => {
|
|
973
954
|
const cell = row.cells[c.key] ?? "";
|
|
974
955
|
if (c.linkLabel != null && cell.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashley-shrok/viewmodel-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "A server-driven UI framework where the wire format is structured enough that agents can build full-stack apps without ever opening a browser and all UI tests are pure unit tests with no browser runtime. Server returns a JSON tree of typed nodes; a thin TypeScript adapter renders it to DOM. Backend-agnostic — a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|