@ashley-shrok/viewmodel-shell 1.0.1 → 1.1.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.d.ts +8 -2
- package/dist/browser.js +53 -5
- package/dist/index.d.ts +16 -4
- package/package.json +1 -1
- package/styles/default.css +4 -0
package/dist/browser.d.ts
CHANGED
|
@@ -49,8 +49,14 @@ export declare class BrowserAdapter implements Adapter {
|
|
|
49
49
|
* sortActions[col.key]; filter inputs are bound to filterBinds[col.key],
|
|
50
50
|
* every keystroke writes, Enter dispatches filterAction; pagination
|
|
51
51
|
* prev/next write the target page to paginationBind then dispatch
|
|
52
|
-
* prevAction/nextAction. Per-row
|
|
53
|
-
*
|
|
52
|
+
* prevAction/nextAction. Per-row controls (row.actions[]) are a mix of
|
|
53
|
+
* ButtonNode and CheckboxNode dispatched by entry.type. When row.action
|
|
54
|
+
* is set, the entire <tr> becomes clickable + keyboard-activatable
|
|
55
|
+
* (Enter / Space — Space preventDefaults page scroll) and exposes
|
|
56
|
+
* role="button", tabindex=0, and an aria-label derived from cell text;
|
|
57
|
+
* clicks on per-row controls or cell linkLabel anchors stopPropagation
|
|
58
|
+
* so they don't also fire row.action. Selection is no longer a framework
|
|
59
|
+
* concept. */
|
|
54
60
|
private table;
|
|
55
61
|
private copyButton;
|
|
56
62
|
}
|
package/dist/browser.js
CHANGED
|
@@ -626,8 +626,14 @@ export class BrowserAdapter {
|
|
|
626
626
|
* sortActions[col.key]; filter inputs are bound to filterBinds[col.key],
|
|
627
627
|
* every keystroke writes, Enter dispatches filterAction; pagination
|
|
628
628
|
* prev/next write the target page to paginationBind then dispatch
|
|
629
|
-
* prevAction/nextAction. Per-row
|
|
630
|
-
*
|
|
629
|
+
* prevAction/nextAction. Per-row controls (row.actions[]) are a mix of
|
|
630
|
+
* ButtonNode and CheckboxNode dispatched by entry.type. When row.action
|
|
631
|
+
* is set, the entire <tr> becomes clickable + keyboard-activatable
|
|
632
|
+
* (Enter / Space — Space preventDefaults page scroll) and exposes
|
|
633
|
+
* role="button", tabindex=0, and an aria-label derived from cell text;
|
|
634
|
+
* clicks on per-row controls or cell linkLabel anchors stopPropagation
|
|
635
|
+
* so they don't also fire row.action. Selection is no longer a framework
|
|
636
|
+
* concept. */
|
|
631
637
|
table(n, parent, on) {
|
|
632
638
|
const wrapper = document.createElement("div");
|
|
633
639
|
wrapper.className = "vms-table-wrapper";
|
|
@@ -708,9 +714,36 @@ export class BrowserAdapter {
|
|
|
708
714
|
let rowClass = "vms-table__row";
|
|
709
715
|
if (row.variant)
|
|
710
716
|
rowClass += ` vms-table__row--${row.variant}`;
|
|
717
|
+
if (row.action)
|
|
718
|
+
rowClass += " vms-table__row--clickable";
|
|
711
719
|
tr.className = rowClass;
|
|
712
720
|
if (row.id)
|
|
713
721
|
tr.dataset.id = row.id;
|
|
722
|
+
// row.action — click-anywhere + keyboard + ARIA. Per-row controls and
|
|
723
|
+
// cell linkLabel anchors stopPropagation below so they don't double-fire.
|
|
724
|
+
if (row.action) {
|
|
725
|
+
const rowActionName = row.action.name;
|
|
726
|
+
tr.tabIndex = 0;
|
|
727
|
+
tr.setAttribute("role", "button");
|
|
728
|
+
const labelParts = Object.values(row.cells)
|
|
729
|
+
.filter(v => v && v.trim())
|
|
730
|
+
.map(v => v.trim());
|
|
731
|
+
const ariaLabel = labelParts.length > 0
|
|
732
|
+
? labelParts.join(" · ")
|
|
733
|
+
: (row.id ? `Row ${row.id}` : "");
|
|
734
|
+
if (ariaLabel)
|
|
735
|
+
tr.setAttribute("aria-label", ariaLabel);
|
|
736
|
+
tr.addEventListener("click", () => { on({ name: rowActionName }); });
|
|
737
|
+
tr.addEventListener("keydown", (e) => {
|
|
738
|
+
if (e.key === "Enter") {
|
|
739
|
+
on({ name: rowActionName });
|
|
740
|
+
}
|
|
741
|
+
else if (e.key === " " || e.key === "Spacebar") {
|
|
742
|
+
e.preventDefault(); // suppress page scroll
|
|
743
|
+
on({ name: rowActionName });
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
714
747
|
n.columns.forEach(col => {
|
|
715
748
|
const td = document.createElement("td");
|
|
716
749
|
td.className = "vms-table__td";
|
|
@@ -724,6 +757,9 @@ export class BrowserAdapter {
|
|
|
724
757
|
a.target = "_blank";
|
|
725
758
|
a.rel = "noopener noreferrer";
|
|
726
759
|
}
|
|
760
|
+
if (row.action) {
|
|
761
|
+
a.addEventListener("click", (e) => { e.stopPropagation(); });
|
|
762
|
+
}
|
|
727
763
|
td.appendChild(a);
|
|
728
764
|
}
|
|
729
765
|
else {
|
|
@@ -731,12 +767,24 @@ export class BrowserAdapter {
|
|
|
731
767
|
}
|
|
732
768
|
tr.appendChild(td);
|
|
733
769
|
});
|
|
734
|
-
// Per-row
|
|
770
|
+
// Per-row interactive controls — dispatch by entry.type so a CheckboxNode
|
|
771
|
+
// renders as a real <input type="checkbox"> rather than silently as an
|
|
772
|
+
// empty button. When row.action is set, swallow clicks on the actions td
|
|
773
|
+
// so toggling a per-row control doesn't ALSO fire the row action.
|
|
735
774
|
if (row.actions && row.actions.length > 0) {
|
|
736
775
|
const td = document.createElement("td");
|
|
737
776
|
td.className = "vms-table__td vms-table__td--actions";
|
|
738
|
-
for (const
|
|
739
|
-
|
|
777
|
+
for (const entry of row.actions) {
|
|
778
|
+
if (entry.type === "button")
|
|
779
|
+
this.button(entry, td, on);
|
|
780
|
+
else if (entry.type === "checkbox")
|
|
781
|
+
this.checkbox(entry, td, on);
|
|
782
|
+
// forward-compatible: unknown entry types no-op (the closed TS union
|
|
783
|
+
// narrows; the runtime guard is for late-arriving wire shapes).
|
|
784
|
+
}
|
|
785
|
+
if (row.action) {
|
|
786
|
+
td.addEventListener("click", (e) => { e.stopPropagation(); });
|
|
787
|
+
}
|
|
740
788
|
tr.appendChild(td);
|
|
741
789
|
}
|
|
742
790
|
tbody.appendChild(tr);
|
package/dist/index.d.ts
CHANGED
|
@@ -237,10 +237,22 @@ export interface TableColumn {
|
|
|
237
237
|
export interface TableRow {
|
|
238
238
|
id?: string;
|
|
239
239
|
cells: Record<string, string>;
|
|
240
|
-
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
|
|
240
|
+
/** Click-anywhere row dispatch primitive. When set, the renderer makes the
|
|
241
|
+
* entire row clickable AND keyboard-activatable (Enter / Space — Space
|
|
242
|
+
* preventDefaults page scroll) AND exposes accessibility (role="button",
|
|
243
|
+
* tabindex=0, aria-label derived from cell text). Per-row identity is
|
|
244
|
+
* encoded in the action name (e.g. `select-ticket-42`) — no context field,
|
|
245
|
+
* consistent with the Phase 6 wire. Coexists with `actions[]`: clicking a
|
|
246
|
+
* per-row button, checkbox, or cell linkLabel anchor does NOT also fire
|
|
247
|
+
* `row.action` (the renderer stops propagation on those targets). */
|
|
248
|
+
action?: ActionEvent;
|
|
249
|
+
/** Per-row interactive controls rendered in a trailing actions cell. Each
|
|
250
|
+
* entry is either a ButtonNode (its own unique action name encodes per-row
|
|
251
|
+
* identity, e.g. `delete-row-42`) or a CheckboxNode (its own `bind` path
|
|
252
|
+
* per row). The renderer dispatches by `entry.type` so both types render
|
|
253
|
+
* correctly — a previous version typed this as `ButtonNode[]` and called
|
|
254
|
+
* the button renderer blindly, silently dropping non-button entries. */
|
|
255
|
+
actions?: (ButtonNode | CheckboxNode)[];
|
|
244
256
|
variant?: string;
|
|
245
257
|
}
|
|
246
258
|
export interface TablePagination {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ashley-shrok/viewmodel-shell",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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",
|
package/styles/default.css
CHANGED
|
@@ -605,6 +605,10 @@ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl);
|
|
|
605
605
|
.vms-table__row:not(:last-child) .vms-table__td { border-bottom: 1px solid var(--vms-border); }
|
|
606
606
|
.vms-table__row--clickable { cursor: pointer; }
|
|
607
607
|
.vms-table__row--clickable:hover { background: var(--vms-surface); }
|
|
608
|
+
.vms-table__row--clickable:focus-visible {
|
|
609
|
+
outline: 2px solid var(--vms-accent);
|
|
610
|
+
outline-offset: -2px;
|
|
611
|
+
}
|
|
608
612
|
/* Row status variants — tints derive from theme vars (color-mix), so a
|
|
609
613
|
custom :root theme recolors them automatically; no app CSS, no literals. */
|
|
610
614
|
.vms-table__row--done { opacity: 0.6; }
|