@moldable-ai/ui 0.2.1 → 0.2.3

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 (64) hide show
  1. package/dist/components/chat/chat-message.d.ts +4 -0
  2. package/dist/components/chat/chat-message.d.ts.map +1 -1
  3. package/dist/components/chat/chat-message.js +64 -10
  4. package/dist/components/chat/chat-panel.d.ts +20 -1
  5. package/dist/components/chat/chat-panel.d.ts.map +1 -1
  6. package/dist/components/chat/chat-panel.js +20 -5
  7. package/dist/components/chat/index.d.ts +4 -1
  8. package/dist/components/chat/index.d.ts.map +1 -1
  9. package/dist/components/chat/index.js +4 -1
  10. package/dist/components/chat/tool-approval-context.d.ts +21 -0
  11. package/dist/components/chat/tool-approval-context.d.ts.map +1 -0
  12. package/dist/components/chat/tool-approval-context.js +19 -0
  13. package/dist/components/chat/tool-approval.d.ts +85 -0
  14. package/dist/components/chat/tool-approval.d.ts.map +1 -0
  15. package/dist/components/chat/tool-approval.js +80 -0
  16. package/dist/components/chat/tool-handlers.d.ts +21 -0
  17. package/dist/components/chat/tool-handlers.d.ts.map +1 -1
  18. package/dist/components/chat/tool-handlers.js +147 -35
  19. package/dist/components/chat/tool-progress-context.d.ts +27 -0
  20. package/dist/components/chat/tool-progress-context.d.ts.map +1 -0
  21. package/dist/components/chat/tool-progress-context.js +26 -0
  22. package/dist/components/ui/button.d.ts +3 -2
  23. package/dist/components/ui/button.d.ts.map +1 -1
  24. package/dist/components/ui/button.js +6 -5
  25. package/dist/components/ui/card.d.ts +7 -8
  26. package/dist/components/ui/card.d.ts.map +1 -1
  27. package/dist/components/ui/card.js +14 -23
  28. package/dist/components/ui/checkbox.js +1 -1
  29. package/dist/components/ui/collapsible.d.ts +3 -3
  30. package/dist/components/ui/collapsible.d.ts.map +1 -1
  31. package/dist/components/ui/collapsible.js +3 -11
  32. package/dist/components/ui/command.d.ts +15 -15
  33. package/dist/components/ui/command.d.ts.map +1 -1
  34. package/dist/components/ui/command.js +24 -29
  35. package/dist/components/ui/dialog.d.ts +17 -13
  36. package/dist/components/ui/dialog.d.ts.map +1 -1
  37. package/dist/components/ui/dialog.js +19 -32
  38. package/dist/components/ui/dropdown-menu.js +6 -6
  39. package/dist/components/ui/input.d.ts +2 -1
  40. package/dist/components/ui/input.d.ts.map +1 -1
  41. package/dist/components/ui/input.js +5 -4
  42. package/dist/components/ui/label.d.ts +1 -1
  43. package/dist/components/ui/label.d.ts.map +1 -1
  44. package/dist/components/ui/label.js +3 -3
  45. package/dist/components/ui/scroll-area.d.ts +2 -2
  46. package/dist/components/ui/scroll-area.d.ts.map +1 -1
  47. package/dist/components/ui/scroll-area.js +7 -9
  48. package/dist/components/ui/select.d.ts +11 -13
  49. package/dist/components/ui/select.d.ts.map +1 -1
  50. package/dist/components/ui/select.js +23 -35
  51. package/dist/components/ui/switch.d.ts +2 -2
  52. package/dist/components/ui/switch.d.ts.map +1 -1
  53. package/dist/components/ui/switch.js +4 -4
  54. package/dist/components/ui/textarea.d.ts +2 -1
  55. package/dist/components/ui/textarea.d.ts.map +1 -1
  56. package/dist/components/ui/textarea.js +5 -4
  57. package/dist/components/ui/tooltip.js +1 -1
  58. package/dist/index.d.ts +1 -1
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -1
  61. package/dist/lib/commands.d.ts +37 -0
  62. package/dist/lib/commands.d.ts.map +1 -1
  63. package/dist/lib/commands.js +85 -0
  64. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import * as SwitchPrimitive from '@radix-ui/react-switch';
3
+ import * as SwitchPrimitives from '@radix-ui/react-switch';
4
+ import * as React from 'react';
4
5
  import { cn } from '../../lib/utils';
5
- function Switch({ className, ...props }) {
6
- return (_jsx(SwitchPrimitive.Root, { "data-slot": "switch", className: cn('data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 shadow-xs peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent outline-none transition-all focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', className), ...props, children: _jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: cn('bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0') }) }));
7
- }
6
+ const Switch = React.forwardRef(({ className, ...props }, ref) => (_jsx(SwitchPrimitives.Root, { className: cn('focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', className), ...props, ref: ref, children: _jsx(SwitchPrimitives.Thumb, { className: cn('bg-background pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0') }) })));
7
+ Switch.displayName = SwitchPrimitives.Root.displayName;
8
8
  export { Switch };
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
- declare function Textarea({ className, ...props }: React.ComponentProps<'textarea'>): import("react/jsx-runtime").JSX.Element;
2
+ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
3
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
3
4
  export { Textarea };
4
5
  //# sourceMappingURL=textarea.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/components/ui/textarea.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,iBAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,2CAW1E;AAED,OAAO,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/components/ui/textarea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,MAAM,MAAM,aAAa,GAAG,KAAK,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,CAAA;AAE7E,QAAA,MAAM,QAAQ,2FAab,CAAA;AAGD,OAAO,EAAE,QAAQ,EAAE,CAAA"}
@@ -1,7 +1,8 @@
1
- 'use client';
2
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
3
  import { cn } from '../../lib/utils';
4
- function Textarea({ className, ...props }) {
5
- return (_jsx("textarea", { "data-slot": "textarea", className: cn('border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 field-sizing-content shadow-xs flex min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm', className), ...props }));
6
- }
4
+ const Textarea = React.forwardRef(({ className, ...props }, ref) => {
5
+ return (_jsx("textarea", { className: cn('border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', className), ref: ref, ...props }));
6
+ });
7
+ Textarea.displayName = 'Textarea';
7
8
  export { Textarea };
@@ -12,6 +12,6 @@ function TooltipTrigger({ ...props }) {
12
12
  return _jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
13
13
  }
14
14
  function TooltipContent({ className, sideOffset = 0, children, ...props }) {
15
- return (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn('bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin) z-50 w-fit text-balance rounded-md px-3 py-1.5 text-xs', className), ...props, children: [children, _jsx(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" })] }) }));
15
+ return (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn('origin-(--radix-tooltip-content-transform-origin) animate-in bg-foreground text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 z-50 w-fit text-balance rounded-md px-3 py-1.5 text-xs', className), ...props, children: [children, _jsx(TooltipPrimitive.Arrow, { className: "bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px]" })] }) }));
16
16
  }
17
17
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { cn } from './lib/utils';
2
2
  export { ThemeProvider, useTheme, themeScript, type Theme } from './lib/theme';
3
3
  export { WorkspaceProvider, useWorkspace, WORKSPACE_HEADER, } from './lib/workspace';
4
- export { useMoldableCommands, useMoldableCommand, isInMoldable, sendToMoldable, type AppCommand, type CommandAction, type CommandsResponse, type CommandMessage, } from './lib/commands';
4
+ export { useMoldableCommands, useMoldableCommand, isInMoldable, sendToMoldable, downloadFile, type AppCommand, type CommandAction, type CommandsResponse, type CommandMessage, type DownloadFileOptions, } from './lib/commands';
5
5
  export * from './components/ui';
6
6
  export { useIsMobile } from './hooks/use-mobile';
7
7
  export { Markdown } from './components/markdown';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAGhC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAA;AAG9E,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAA;AAGvB,cAAc,iBAAiB,CAAA;AAG/B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAGhD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAGzD,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAGhC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,aAAa,CAAA;AAG9E,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAA;AAGvB,cAAc,iBAAiB,CAAA;AAG/B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGhD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAGhD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAGzD,cAAc,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export { ThemeProvider, useTheme, themeScript } from './lib/theme';
5
5
  // Export workspace
6
6
  export { WorkspaceProvider, useWorkspace, WORKSPACE_HEADER, } from './lib/workspace';
7
7
  // Export commands
8
- export { useMoldableCommands, useMoldableCommand, isInMoldable, sendToMoldable, } from './lib/commands';
8
+ export { useMoldableCommands, useMoldableCommand, isInMoldable, sendToMoldable, downloadFile, } from './lib/commands';
9
9
  // Export UI components
10
10
  export * from './components/ui';
11
11
  // Export hooks
@@ -71,4 +71,41 @@ export declare function sendToMoldable(message: {
71
71
  type: string;
72
72
  [key: string]: unknown;
73
73
  }): void;
74
+ /**
75
+ * Options for downloading a file
76
+ */
77
+ export interface DownloadFileOptions {
78
+ /** Suggested filename for the save dialog */
79
+ filename: string;
80
+ /** File content - either a string or base64-encoded data */
81
+ data: string;
82
+ /** MIME type of the file (e.g., 'text/csv', 'application/json') */
83
+ mimeType: string;
84
+ /** If true, data is base64-encoded binary; if false, data is plain text */
85
+ isBase64?: boolean;
86
+ }
87
+ /**
88
+ * Trigger a file download via Moldable's native save dialog.
89
+ * Works inside Moldable's iframe environment where browser downloads don't work.
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * // Export CSV
94
+ * downloadFile({
95
+ * filename: 'data.csv',
96
+ * data: 'name,value\nfoo,1\nbar,2',
97
+ * mimeType: 'text/csv',
98
+ * })
99
+ *
100
+ * // Export JSON
101
+ * downloadFile({
102
+ * filename: 'data.json',
103
+ * data: JSON.stringify({ items: [...] }, null, 2),
104
+ * mimeType: 'application/json',
105
+ * })
106
+ * ```
107
+ *
108
+ * @returns Promise that resolves when the save dialog completes (or rejects on error)
109
+ */
110
+ export declare function downloadFile(options: DownloadFileOptions): Promise<void>;
74
111
  //# sourceMappingURL=commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,MAAM,EAAE,aAAa,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,UAAU,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC,QAwBtD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,QAgBrC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB,QAMA"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAA;IACb,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gDAAgD;IAChD,MAAM,EAAE,aAAa,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,UAAU,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC,QAwBtD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,IAAI,QAgBrC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB,QAMA;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAA;IACZ,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAA;IAChB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DxE"}
@@ -71,3 +71,88 @@ export function sendToMoldable(message) {
71
71
  }
72
72
  window.parent.postMessage(message, '*');
73
73
  }
74
+ /**
75
+ * Trigger a file download via Moldable's native save dialog.
76
+ * Works inside Moldable's iframe environment where browser downloads don't work.
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * // Export CSV
81
+ * downloadFile({
82
+ * filename: 'data.csv',
83
+ * data: 'name,value\nfoo,1\nbar,2',
84
+ * mimeType: 'text/csv',
85
+ * })
86
+ *
87
+ * // Export JSON
88
+ * downloadFile({
89
+ * filename: 'data.json',
90
+ * data: JSON.stringify({ items: [...] }, null, 2),
91
+ * mimeType: 'application/json',
92
+ * })
93
+ * ```
94
+ *
95
+ * @returns Promise that resolves when the save dialog completes (or rejects on error)
96
+ */
97
+ export function downloadFile(options) {
98
+ return new Promise((resolve, reject) => {
99
+ if (!isInMoldable()) {
100
+ // Fallback for browser: use traditional blob download
101
+ try {
102
+ const blob = options.isBase64
103
+ ? new Blob([Uint8Array.from(atob(options.data), (c) => c.charCodeAt(0))], {
104
+ type: options.mimeType,
105
+ })
106
+ : new Blob([options.data], { type: options.mimeType });
107
+ const url = URL.createObjectURL(blob);
108
+ const a = document.createElement('a');
109
+ a.href = url;
110
+ a.download = options.filename;
111
+ document.body.appendChild(a);
112
+ a.click();
113
+ document.body.removeChild(a);
114
+ URL.revokeObjectURL(url);
115
+ resolve();
116
+ }
117
+ catch (err) {
118
+ reject(err);
119
+ }
120
+ return;
121
+ }
122
+ // Generate a unique ID for this download request
123
+ const requestId = `download-${Date.now()}-${Math.random().toString(36).slice(2)}`;
124
+ // Listen for the response
125
+ const handleResponse = (event) => {
126
+ if (event.data?.type !== 'moldable:save-file-result')
127
+ return;
128
+ if (event.data?.requestId !== requestId)
129
+ return;
130
+ window.removeEventListener('message', handleResponse);
131
+ if (event.data.success) {
132
+ resolve();
133
+ }
134
+ else if (event.data.cancelled) {
135
+ // User cancelled - not an error, just resolve
136
+ resolve();
137
+ }
138
+ else {
139
+ reject(new Error(event.data.error || 'Download failed'));
140
+ }
141
+ };
142
+ window.addEventListener('message', handleResponse);
143
+ // Send the download request to Moldable
144
+ sendToMoldable({
145
+ type: 'moldable:save-file',
146
+ requestId,
147
+ filename: options.filename,
148
+ data: options.data,
149
+ mimeType: options.mimeType,
150
+ isBase64: options.isBase64 ?? false,
151
+ });
152
+ // Timeout after 5 minutes (user might take time in save dialog)
153
+ setTimeout(() => {
154
+ window.removeEventListener('message', handleResponse);
155
+ reject(new Error('Download timed out'));
156
+ }, 5 * 60 * 1000);
157
+ });
158
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moldable-ai/ui",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Shared UI components for Moldable applications",
5
5
  "author": "Desiderata LLC",
6
6
  "license": "Elastic-2.0",