@ashley-shrok/viewmodel-shell 0.3.13 → 0.3.14

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
@@ -30,4 +30,5 @@ export declare class BrowserAdapter implements Adapter {
30
30
  private progress;
31
31
  private modal;
32
32
  private table;
33
+ private copyButton;
33
34
  }
package/dist/browser.js CHANGED
@@ -1,3 +1,19 @@
1
+ function legacyCopy(text) {
2
+ try {
3
+ const ta = document.createElement("textarea");
4
+ ta.value = text;
5
+ ta.style.position = "fixed";
6
+ ta.style.opacity = "0";
7
+ document.body.appendChild(ta);
8
+ ta.select();
9
+ const ok = document.execCommand("copy");
10
+ document.body.removeChild(ta);
11
+ return ok;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
1
17
  export class BrowserAdapter {
2
18
  container;
3
19
  fileRegistry = new Map();
@@ -142,6 +158,7 @@ export class BrowserAdapter {
142
158
  case "progress": return this.progress(n, parent);
143
159
  case "modal": return this.modal(n, parent, on);
144
160
  case "table": return this.table(n, parent, on);
161
+ case "copy-button": return this.copyButton(n, parent);
145
162
  }
146
163
  }
147
164
  kids(nodes, parent, on) {
@@ -617,4 +634,35 @@ export class BrowserAdapter {
617
634
  wrapper.appendChild(table);
618
635
  parent.appendChild(wrapper);
619
636
  }
637
+ copyButton(n, parent) {
638
+ const btn = document.createElement("button");
639
+ btn.type = "button";
640
+ btn.className = "vms-button";
641
+ btn.textContent = n.label ?? "Copy";
642
+ btn.addEventListener("click", () => {
643
+ const write = navigator.clipboard?.writeText(n.text);
644
+ if (write) {
645
+ write.then(() => {
646
+ btn.textContent = n.copiedLabel ?? "Copied!";
647
+ setTimeout(() => { btn.textContent = n.label ?? "Copy"; }, 1500);
648
+ }).catch(() => {
649
+ // primary failed — try legacy execCommand fallback
650
+ if (legacyCopy(n.text)) {
651
+ btn.textContent = n.copiedLabel ?? "Copied!";
652
+ setTimeout(() => { btn.textContent = n.label ?? "Copy"; }, 1500);
653
+ }
654
+ // both paths failed: silent, no confirmation
655
+ });
656
+ }
657
+ else {
658
+ // navigator.clipboard absent — try legacy
659
+ if (legacyCopy(n.text)) {
660
+ btn.textContent = n.copiedLabel ?? "Copied!";
661
+ setTimeout(() => { btn.textContent = n.label ?? "Copy"; }, 1500);
662
+ }
663
+ // else: silent
664
+ }
665
+ });
666
+ parent.appendChild(btn);
667
+ }
620
668
  }
package/dist/index.d.ts CHANGED
@@ -24,7 +24,7 @@ export interface Adapter {
24
24
  onUploadProgress?: (sent: number, total: number) => void;
25
25
  }): Promise<Response>;
26
26
  }
27
- export type ViewNode = PageNode | SectionNode | ListNode | ListItemNode | FormNode | FieldNode | CheckboxNode | ButtonNode | TextNode | LinkNode | StatBarNode | TabsNode | ProgressNode | ModalNode | TableNode;
27
+ export type ViewNode = PageNode | SectionNode | ListNode | ListItemNode | FormNode | FieldNode | CheckboxNode | ButtonNode | TextNode | LinkNode | StatBarNode | TabsNode | ProgressNode | ModalNode | TableNode | CopyButtonNode;
28
28
  export interface PageNode {
29
29
  type: "page";
30
30
  title?: string;
@@ -160,6 +160,15 @@ export interface TableNode {
160
160
  /** Base action. Adapter merges { column, value, filters } into context on Enter. */
161
161
  filterAction?: ActionEvent;
162
162
  }
163
+ export interface CopyButtonNode {
164
+ type: "copy-button";
165
+ /** The string to write to the clipboard on click. */
166
+ text: string;
167
+ /** Label shown on the button before copying. Adapter default: "Copy". */
168
+ label?: string;
169
+ /** Ephemeral label shown after a successful copy, reverts after ~1.5 s. Adapter default: "Copied!". */
170
+ copiedLabel?: string;
171
+ }
163
172
  export interface ShellOptions {
164
173
  endpoint: string;
165
174
  actionEndpoint: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashley-shrok/viewmodel-shell",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
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",