@ashley-shrok/viewmodel-shell 5.0.0 → 5.0.1

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 CHANGED
@@ -136,6 +136,15 @@ export declare class BrowserAdapter implements Adapter {
136
136
  /** CheckboxNode (standalone, immediate-dispatch) — bound boolean; on toggle,
137
137
  * write to state then dispatch the action name (if any). */
138
138
  private checkbox;
139
+ /** Shared button appearance + activation behavior, applied to a <button> element
140
+ * (a standalone ButtonNode's button, OR a form's submitButton). Sets the full
141
+ * className (emphasis/tone/size/width/disabled), label, and the `disabled` attr,
142
+ * and returns a guarded `activate()` that runs disabled -> confirm -> pendingLabel
143
+ * swap -> dispatch. Both the standalone button() renderer and the FormNode
144
+ * submitButton branch use this so the two can NEVER diverge — the divergence WAS
145
+ * the bug (a form-level submit button silently dropped pendingLabel/disabled/
146
+ * confirm because it re-implemented rendering without the click behavior). */
147
+ private applyButtonBehavior;
139
148
  private button;
140
149
  private text;
141
150
  private link;
package/dist/browser.js CHANGED
@@ -865,15 +865,17 @@ export class BrowserAdapter {
865
865
  const sb = n.submitButton;
866
866
  const effectiveSubmit = sb ? sb.action : n.submitAction;
867
867
  if (sb) {
868
- const submitAction = sb.action;
869
868
  const submit = document.createElement("button");
870
869
  submit.type = "submit";
871
- submit.className = `vms-button${sb.emphasis ? ` vms-button--${sb.emphasis}` : ""}${sb.tone ? ` vms-button--${sb.tone}` : ""}${sb.size ? ` vms-button--${sb.size}` : ""}${sb.width === "full" ? " vms-button--full" : ""}`;
872
- submit.textContent = sb.label;
870
+ // Same appearance + activation as a standalone button disabled/confirm/
871
+ // pendingLabel included. The form's submit event is the single dispatch
872
+ // point (keeps native Enter-to-submit for text fields working); activate()
873
+ // carries the disabled guard, confirm guard, and pendingLabel swap.
874
+ const activate = this.applyButtonBehavior(submit, sb, dispatchWithFiles);
873
875
  form.appendChild(submit);
874
876
  form.addEventListener("submit", (e) => {
875
877
  e.preventDefault();
876
- dispatchWithFiles(submitAction);
878
+ activate();
877
879
  });
878
880
  }
879
881
  else if (n.submitAction) {
@@ -1269,24 +1271,30 @@ export class BrowserAdapter {
1269
1271
  });
1270
1272
  parent.appendChild(lbl);
1271
1273
  }
1272
- button(n, parent, on) {
1273
- const btn = document.createElement("button");
1274
- btn.type = "button";
1274
+ /** Shared button appearance + activation behavior, applied to a <button> element
1275
+ * (a standalone ButtonNode's button, OR a form's submitButton). Sets the full
1276
+ * className (emphasis/tone/size/width/disabled), label, and the `disabled` attr,
1277
+ * and returns a guarded `activate()` that runs disabled -> confirm -> pendingLabel
1278
+ * swap -> dispatch. Both the standalone button() renderer and the FormNode
1279
+ * submitButton branch use this so the two can NEVER diverge — the divergence WAS
1280
+ * the bug (a form-level submit button silently dropped pendingLabel/disabled/
1281
+ * confirm because it re-implemented rendering without the click behavior). */
1282
+ applyButtonBehavior(btn, n, dispatch) {
1275
1283
  btn.className = `vms-button${n.emphasis ? ` vms-button--${n.emphasis}` : ""}${n.tone ? ` vms-button--${n.tone}` : ""}${n.size ? ` vms-button--${n.size}` : ""}${n.width === "full" ? " vms-button--full" : ""}${n.disabled ? " vms-button--disabled" : ""}`;
1276
1284
  btn.textContent = n.label;
1277
1285
  if (n.disabled)
1278
1286
  btn.disabled = true;
1279
- btn.addEventListener("click", () => {
1287
+ return () => {
1280
1288
  // Forms-completeness (3.4.0): a disabled button never dispatches. (Native
1281
- // `disabled` already suppresses the click, but guard anyway in case the
1282
- // attribute was cleared out-of-band.)
1289
+ // `disabled` already suppresses a click, but guard anyway a form submit
1290
+ // isn't a native button click, and the attribute could be cleared out-of-band.)
1283
1291
  if (n.disabled)
1284
1292
  return;
1285
1293
  // confirm: a destructive-action guard. Show the NATIVE browser confirm
1286
1294
  // BEFORE any pendingLabel swap or dispatch; Cancel suppresses everything
1287
1295
  // (no dispatch, no visual change). Native by design — zero app/framework
1288
1296
  // state, and it's a client-only human affordance (an agent never reaches
1289
- // this click handler; it dispatches the action directly over the wire).
1297
+ // this handler; it dispatches the action directly over the wire).
1290
1298
  if (n.confirm && !window.confirm(n.confirm))
1291
1299
  return;
1292
1300
  // pendingLabel: instant client-side feedback. Swap text + add
@@ -1297,8 +1305,14 @@ export class BrowserAdapter {
1297
1305
  btn.textContent = n.pendingLabel;
1298
1306
  btn.classList.add("vms-button--pending");
1299
1307
  }
1300
- on(n.action);
1301
- });
1308
+ dispatch(n.action);
1309
+ };
1310
+ }
1311
+ button(n, parent, on) {
1312
+ const btn = document.createElement("button");
1313
+ btn.type = "button";
1314
+ const activate = this.applyButtonBehavior(btn, n, on);
1315
+ btn.addEventListener("click", activate);
1302
1316
  parent.appendChild(btn);
1303
1317
  }
1304
1318
  text(n, parent) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashley-shrok/viewmodel-shell",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
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 \u2014 a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
5
5
  "type": "module",
6
6
  "license": "MIT",