@june-ai/june-ui 0.2.9 → 0.2.11

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.
@@ -141,7 +141,7 @@ function OpenUIDevtoolsGate() {
141
141
 
142
142
  // src/engine/renderer/june-renderer.tsx
143
143
  import { Renderer } from "@openuidev/react-lang";
144
- import { Fragment, useCallback as useCallback2, useMemo as useMemo3, useRef as useRef2 } from "react";
144
+ import { Fragment, useCallback as useCallback2, useMemo as useMemo4, useRef as useRef2 } from "react";
145
145
 
146
146
  // src/core/context.ts
147
147
  import { createContext as createContext2, useContext as useContext2 } from "react";
@@ -224,9 +224,16 @@ function createActionHandler(options) {
224
224
  };
225
225
  }
226
226
 
227
+ // src/features/document-editor/document-bridge.tsx
228
+ import { useMemo as useMemo2 } from "react";
229
+
227
230
  // src/features/document-editor/document-context.ts
228
231
  import { createContext as createContext3, useContext as useContext3, useSyncExternalStore } from "react";
229
232
  var JuneDocumentContext = createContext3({});
233
+ var DocumentEditorConfigContext = createContext3({});
234
+ function useDocumentEditorConfig() {
235
+ return useContext3(DocumentEditorConfigContext);
236
+ }
230
237
  function useDocumentOptions() {
231
238
  return useContext3(JuneDocumentContext);
232
239
  }
@@ -608,13 +615,20 @@ function useDocumentBridge(options) {
608
615
 
609
616
  // src/features/document-editor/document-bridge.tsx
610
617
  import { jsx as jsx2 } from "react/jsx-runtime";
611
- function DocumentBridge({ children, ...options }) {
618
+ function DocumentBridge({
619
+ children,
620
+ documentEditor,
621
+ ...options
622
+ }) {
612
623
  const value = useDocumentBridge(options);
613
- return /* @__PURE__ */ jsx2(JuneDocumentContext.Provider, { value, children });
624
+ const presentation = documentEditor?.presentation;
625
+ const width = documentEditor?.width;
626
+ const config = useMemo2(() => ({ presentation, width }), [presentation, width]);
627
+ return /* @__PURE__ */ jsx2(JuneDocumentContext.Provider, { value, children: /* @__PURE__ */ jsx2(DocumentEditorConfigContext.Provider, { value: config, children }) });
614
628
  }
615
629
 
616
630
  // src/themes/theme-provider.tsx
617
- import { useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
631
+ import { useMemo as useMemo3, useSyncExternalStore as useSyncExternalStore2 } from "react";
618
632
 
619
633
  // src/themes/dark.ts
620
634
  var darkTheme = {
@@ -675,7 +689,7 @@ function useResolvedTheme(mode) {
675
689
  function JuneThemeProvider({ mode, vars, className, style, children }) {
676
690
  const explicit = mode !== void 0;
677
691
  const resolved = useResolvedTheme(mode ?? "system");
678
- const mergedStyle = useMemo2(() => {
692
+ const mergedStyle = useMemo3(() => {
679
693
  const own = resolveThemeVars(vars, resolved);
680
694
  if (!explicit && !own && !style) return void 0;
681
695
  const base = explicit ? resolved === "dark" ? darkTheme : lightTheme : void 0;
@@ -686,7 +700,7 @@ function JuneThemeProvider({ mode, vars, className, style, children }) {
686
700
  ...style
687
701
  };
688
702
  }, [explicit, resolved, vars, style]);
689
- const context = useMemo2(() => ({ mode: mode ?? "system", theme: resolved }), [mode, resolved]);
703
+ const context = useMemo3(() => ({ mode: mode ?? "system", theme: resolved }), [mode, resolved]);
690
704
  return /* @__PURE__ */ jsx3(JuneContext.Provider, { value: context, children: /* @__PURE__ */ jsx3(
691
705
  "div",
692
706
  {
@@ -1056,7 +1070,7 @@ var STATEMENT_START = /^\$?[A-Za-z_]\w*\s*=/;
1056
1070
  var PENDING_IDENTIFIER = /^\s*\$?[A-Za-z_]\w*\s*$/;
1057
1071
  var FENCE = /^(`{3,})\s*([\w-]*)/;
1058
1072
  var OPENUI_FENCE_LANGS = /* @__PURE__ */ new Set(["", "openui", "openui-lang", "openuilang"]);
1059
- function scan(line, state) {
1073
+ function scan(line, state, preserveQuote = false) {
1060
1074
  for (let i = 0; i < line.length; i++) {
1061
1075
  const ch = line[i];
1062
1076
  if (state.quote) {
@@ -1070,7 +1084,95 @@ function scan(line, state) {
1070
1084
  state.depth = Math.max(0, state.depth - 1);
1071
1085
  }
1072
1086
  }
1073
- state.quote = null;
1087
+ if (!preserveQuote) state.quote = null;
1088
+ }
1089
+ function quoteClosesLater(lines, from, state) {
1090
+ const probe = { depth: state.depth, quote: state.quote };
1091
+ for (let i = from; i < lines.length; i++) {
1092
+ const line = lines[i];
1093
+ if (probe.depth === 0 && STATEMENT_START.test(line.trim())) return false;
1094
+ scan(line, probe, true);
1095
+ if (!probe.quote) return true;
1096
+ }
1097
+ return false;
1098
+ }
1099
+ function decodeLangString(raw) {
1100
+ let out = "";
1101
+ for (let i = 0; i < raw.length; i++) {
1102
+ const ch = raw[i];
1103
+ if (ch === "\\" && i + 1 < raw.length) {
1104
+ const next = raw[i + 1];
1105
+ out += next === "n" ? "\n" : next === "t" ? " " : next === "r" ? "\r" : next === "b" ? "\b" : next === "f" ? "\f" : next;
1106
+ i++;
1107
+ continue;
1108
+ }
1109
+ out += ch;
1110
+ }
1111
+ return out.replace(/\r/g, "");
1112
+ }
1113
+ function foldMultilineStrings(source) {
1114
+ let out = "";
1115
+ let i = 0;
1116
+ while (i < source.length) {
1117
+ if (source.startsWith('"""', i)) {
1118
+ const end = source.indexOf('"""', i + 3);
1119
+ if (end < 0) {
1120
+ const nl = source.indexOf("\n", i);
1121
+ if (nl < 0) {
1122
+ out += source.slice(i);
1123
+ break;
1124
+ }
1125
+ out += source.slice(i, nl + 1);
1126
+ i = nl + 1;
1127
+ continue;
1128
+ }
1129
+ out += JSON.stringify(source.slice(i + 3, end).replace(/\r/g, ""));
1130
+ i = end + 3;
1131
+ continue;
1132
+ }
1133
+ if (source[i] !== '"') {
1134
+ out += source[i];
1135
+ i++;
1136
+ continue;
1137
+ }
1138
+ let j = i + 1;
1139
+ let closed = false;
1140
+ let hasNewline = false;
1141
+ while (j < source.length) {
1142
+ const ch = source[j];
1143
+ if (ch === "\\") {
1144
+ j += 2;
1145
+ continue;
1146
+ }
1147
+ if (ch === "\n") {
1148
+ if (STATEMENT_START.test(source.slice(j + 1))) break;
1149
+ hasNewline = true;
1150
+ }
1151
+ if (ch === '"') {
1152
+ closed = true;
1153
+ break;
1154
+ }
1155
+ j++;
1156
+ }
1157
+ if (!closed) {
1158
+ const nl = source.indexOf("\n", i);
1159
+ if (nl < 0) {
1160
+ out += source.slice(i);
1161
+ break;
1162
+ }
1163
+ out += source.slice(i, nl + 1);
1164
+ i = nl + 1;
1165
+ continue;
1166
+ }
1167
+ if (!hasNewline) {
1168
+ out += source.slice(i, j + 1);
1169
+ i = j + 1;
1170
+ continue;
1171
+ }
1172
+ out += JSON.stringify(decodeLangString(source.slice(i + 1, j)));
1173
+ i = j + 1;
1174
+ }
1175
+ return out;
1074
1176
  }
1075
1177
  function splitResponse(source, options = {}) {
1076
1178
  const markdown = options.markdown === true;
@@ -1092,8 +1194,15 @@ function splitResponse(source, options = {}) {
1092
1194
  text = [];
1093
1195
  if (joined) (seenUi ? after : before).push(joined);
1094
1196
  };
1095
- for (const line of input.split(/\r?\n/)) {
1197
+ const lines = input.split(/\r?\n/);
1198
+ for (let index = 0; index < lines.length; index++) {
1199
+ const line = lines[index];
1096
1200
  const trimmed = line.trim();
1201
+ if (state.quote) {
1202
+ ui.push(line);
1203
+ scan(line, state, true);
1204
+ continue;
1205
+ }
1097
1206
  if (codeFence !== null) {
1098
1207
  text.push(line);
1099
1208
  if (trimmed.startsWith(codeFence)) {
@@ -1122,7 +1231,8 @@ function splitResponse(source, options = {}) {
1122
1231
  flushText();
1123
1232
  seenUi = true;
1124
1233
  ui.push(line);
1125
- scan(line, state);
1234
+ scan(line, state, true);
1235
+ if (state.quote && !quoteClosesLater(lines, index + 1, state)) state.quote = null;
1126
1236
  continue;
1127
1237
  }
1128
1238
  if (trimmed === "") {
@@ -1134,7 +1244,7 @@ function splitResponse(source, options = {}) {
1134
1244
  flushText();
1135
1245
  return {
1136
1246
  before,
1137
- ui: ui.join("\n"),
1247
+ ui: foldMultilineStrings(ui.join("\n")),
1138
1248
  after,
1139
1249
  pure: !sawFence && before.length === 0 && after.length === 0
1140
1250
  };
@@ -1144,7 +1254,7 @@ function splitResponse(source, options = {}) {
1144
1254
  function resolveMixedContent(response, options = {}) {
1145
1255
  const { mixedContent = true, isStreaming = false, repair = true, markdown = false } = options;
1146
1256
  const parts = mixedContent && response ? splitResponse(response, { streaming: isStreaming, markdown }) : null;
1147
- const raw = parts && !parts.pure ? parts.ui : response;
1257
+ const raw = parts && !parts.pure ? parts.ui : typeof response === "string" ? foldMultilineStrings(response) : response;
1148
1258
  const ui = !repair || !mixedContent || isStreaming || typeof raw !== "string" || raw.length === 0 ? raw : normalizeProgram(raw);
1149
1259
  const before = parts?.before ?? [];
1150
1260
  const after = parts?.after ?? [];
@@ -1224,6 +1334,7 @@ function JuneRenderer(props) {
1224
1334
  onError,
1225
1335
  onJuneError,
1226
1336
  documents,
1337
+ documentEditor,
1227
1338
  onResponseChange,
1228
1339
  responseChangeDelay,
1229
1340
  loadingFallback = null,
@@ -1267,8 +1378,8 @@ function JuneRenderer(props) {
1267
1378
  );
1268
1379
  }, []);
1269
1380
  const handleDocumentError = useCallback2((error) => latest.current.onJuneError?.(error), []);
1270
- const runtime = useMemo3(() => ({ isStreaming }), [isStreaming]);
1271
- const content = useMemo3(
1381
+ const runtime = useMemo4(() => ({ isStreaming }), [isStreaming]);
1382
+ const content = useMemo4(
1272
1383
  () => resolveMixedContent(response, { mixedContent, isStreaming, repair, markdown: textFormat === "markdown" }),
1273
1384
  [response, mixedContent, isStreaming, repair, textFormat]
1274
1385
  );
@@ -1280,6 +1391,7 @@ function JuneRenderer(props) {
1280
1391
  DocumentBridge,
1281
1392
  {
1282
1393
  documents,
1394
+ documentEditor,
1283
1395
  response,
1284
1396
  onResponseChange,
1285
1397
  responseChangeDelay,
@@ -1643,7 +1755,7 @@ function defineJuneAction(definition) {
1643
1755
  }
1644
1756
 
1645
1757
  // src/features/streaming/use-june-stream.ts
1646
- import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState } from "react";
1758
+ import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo5, useRef as useRef3, useState } from "react";
1647
1759
 
1648
1760
  // src/features/streaming/stream-handler.ts
1649
1761
  function isReadableStream(source) {
@@ -1906,7 +2018,7 @@ function useJuneStream(options = {}) {
1906
2018
  },
1907
2019
  [clearTimer]
1908
2020
  );
1909
- return useMemo4(
2021
+ return useMemo5(
1910
2022
  () => ({ ...state, start, startFetch, abort, reset }),
1911
2023
  [state, start, startFetch, abort, reset]
1912
2024
  );
@@ -1935,7 +2047,7 @@ function DocumentDraft({ title, words, state }) {
1935
2047
 
1936
2048
  // src/features/document-editor/document-editor.tsx
1937
2049
  import { useEditor } from "@tiptap/react";
1938
- import { useCallback as useCallback4, useEffect as useEffect7, useId, useMemo as useMemo5, useRef as useRef8, useState as useState6 } from "react";
2050
+ import { useCallback as useCallback4, useEffect as useEffect7, useId, useMemo as useMemo6, useRef as useRef8, useState as useState6 } from "react";
1939
2051
 
1940
2052
  // src/features/document-editor/document-bubble.tsx
1941
2053
  import { useEditorState as useEditorState2 } from "@tiptap/react";
@@ -2115,6 +2227,10 @@ var PATHS = {
2115
2227
  /* @__PURE__ */ jsx8("path", { d: "M18 6 6 18" }),
2116
2228
  /* @__PURE__ */ jsx8("path", { d: "m6 6 12 12" })
2117
2229
  ] }),
2230
+ pencil: /* @__PURE__ */ jsxs3(Fragment2, { children: [
2231
+ /* @__PURE__ */ jsx8("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" }),
2232
+ /* @__PURE__ */ jsx8("path", { d: "m15 5 4 4" })
2233
+ ] }),
2118
2234
  maximize: /* @__PURE__ */ jsxs3(Fragment2, { children: [
2119
2235
  /* @__PURE__ */ jsx8("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
2120
2236
  /* @__PURE__ */ jsx8("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
@@ -3603,15 +3719,15 @@ function DocumentEditorImpl(props) {
3603
3719
  className
3604
3720
  } = props;
3605
3721
  const ctxOptions = useDocumentOptions();
3606
- const options = useMemo5(() => ({ ...ctxOptions, ...props.options }), [ctxOptions, props.options]);
3722
+ const options = useMemo6(() => ({ ...ctxOptions, ...props.options }), [ctxOptions, props.options]);
3607
3723
  const optionsRef = useRef8(options);
3608
3724
  optionsRef.current = options;
3609
- const store = useMemo5(() => options.store ?? getDefaultDocumentStore(), [options.store]);
3725
+ const store = useMemo6(() => options.store ?? getDefaultDocumentStore(), [options.store]);
3610
3726
  const instanceId = useId();
3611
3727
  const { theme: contextTheme } = useJuneTheme();
3612
3728
  const resolvedTheme = theme === "auto" ? contextTheme : theme;
3613
3729
  const contentKey = typeof content === "string" ? content : content ? JSON.stringify(content) : "";
3614
- const parsed = useMemo5(() => {
3730
+ const parsed = useMemo6(() => {
3615
3731
  if (!contentKey) return { json: null, error: null };
3616
3732
  try {
3617
3733
  return { json: parseDocumentContent(contentKey), error: null };
@@ -3663,7 +3779,7 @@ function DocumentEditorImpl(props) {
3663
3779
  );
3664
3780
  const saveRef = useRef8(async () => {
3665
3781
  });
3666
- const autosave = useMemo5(() => {
3782
+ const autosave = useMemo6(() => {
3667
3783
  const delay = options.autosaveDelay ?? 1500;
3668
3784
  return delay > 0 ? debounce(() => void saveRef.current(), delay) : null;
3669
3785
  }, [options.autosaveDelay]);
@@ -3715,7 +3831,7 @@ function DocumentEditorImpl(props) {
3715
3831
  return () => clearTimeout(t);
3716
3832
  }, [saveState, options.onSave]);
3717
3833
  const [initialJson] = useState6(() => store.get(documentId)?.json ?? parsed.json ?? emptyDoc());
3718
- const extensions = useMemo5(() => createDocumentExtensions(), []);
3834
+ const extensions = useMemo6(() => createDocumentExtensions(), []);
3719
3835
  const onUpdateRef = useRef8(() => {
3720
3836
  });
3721
3837
  onUpdateRef.current = (json) => {
@@ -3940,9 +4056,186 @@ function DocumentEditorImpl(props) {
3940
4056
  );
3941
4057
  }
3942
4058
 
4059
+ // src/features/document-editor/document-preview.tsx
4060
+ import { useEffect as useEffect8, useMemo as useMemo7, useState as useState7 } from "react";
4061
+ import { createPortal } from "react-dom";
4062
+
4063
+ // src/features/document-editor/document-presentation.ts
4064
+ function resolveDocumentPresentation(documentPresentation, configured) {
4065
+ if (documentPresentation === "inline" || documentPresentation === "side-panel") return documentPresentation;
4066
+ if (configured === "inline" || configured === "side-panel") return configured;
4067
+ return "inline";
4068
+ }
4069
+ function documentPanelWidth(width) {
4070
+ if (typeof width === "number" && Number.isFinite(width)) return `${width}px`;
4071
+ if (typeof width === "string" && width.trim()) return width.trim();
4072
+ return void 0;
4073
+ }
4074
+
4075
+ // src/features/document-editor/document-preview.tsx
4076
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
4077
+ var FORMAT_LABEL = {
4078
+ docx: "Word document",
4079
+ pdf: "PDF",
4080
+ html: "Web page",
4081
+ markdown: "Markdown",
4082
+ txt: "Plain text"
4083
+ };
4084
+ function DocumentPreview(props) {
4085
+ const { documentId, title = "Untitled document", content, editable = true, exportFormats } = props;
4086
+ const { theme: contextTheme } = useJuneTheme();
4087
+ const resolvedTheme = props.theme && props.theme !== "auto" ? props.theme : contextTheme;
4088
+ const ctxOptions = useDocumentOptions();
4089
+ const editorConfig = useDocumentEditorConfig();
4090
+ const presentation = resolveDocumentPresentation(props.presentation, editorConfig.presentation);
4091
+ const panelWidth = documentPanelWidth(editorConfig.width);
4092
+ const store = useMemo7(() => props.options?.store ?? ctxOptions.store ?? getDefaultDocumentStore(), [props.options?.store, ctxOptions.store]);
4093
+ const storedJson = useDocumentValue(store, documentId, (record) => record?.json);
4094
+ const [open, setOpen] = useState7(false);
4095
+ const [preferStore, setPreferStore] = useState7(false);
4096
+ const [notice, setNotice] = useState7(null);
4097
+ const contentKey = typeof content === "string" ? content : content ? JSON.stringify(content) : "";
4098
+ const options = props.options ?? ctxOptions;
4099
+ useEffect8(() => {
4100
+ setPreferStore(false);
4101
+ }, [contentKey]);
4102
+ const doc = useMemo7(() => {
4103
+ try {
4104
+ const fromContent = contentKey ? parseDocumentContent(contentKey) : null;
4105
+ return preferStore && storedJson ? storedJson : fromContent;
4106
+ } catch {
4107
+ return null;
4108
+ }
4109
+ }, [contentKey, preferStore, storedJson]);
4110
+ const html = useMemo7(() => doc ? docToHtml(doc) : "", [doc]);
4111
+ const formats = (exportFormats ?? [...ALL_EXPORT_FORMATS]).filter((format2) => listDocumentExporters().includes(format2));
4112
+ useEffect8(() => {
4113
+ if (!notice) return;
4114
+ const timer = setTimeout(() => setNotice(null), 2e3);
4115
+ return () => clearTimeout(timer);
4116
+ }, [notice]);
4117
+ useEffect8(() => {
4118
+ if (!open || presentation !== "side-panel" || typeof document === "undefined") return;
4119
+ const prev = document.body.style.overflow;
4120
+ document.body.style.overflow = "hidden";
4121
+ return () => {
4122
+ document.body.style.overflow = prev;
4123
+ };
4124
+ }, [open, presentation]);
4125
+ const close = () => {
4126
+ setPreferStore(true);
4127
+ setOpen(false);
4128
+ };
4129
+ const handleExport = async (format2) => {
4130
+ if (!doc) return;
4131
+ try {
4132
+ const result = await exportDocument(format2, { documentId, title, json: doc });
4133
+ if (result.blob) downloadResult(result);
4134
+ options.onExport?.({ documentId, format: format2, ...result });
4135
+ } catch (error) {
4136
+ options.onError?.(toJuneError(error, "EXPORT_ERROR"));
4137
+ setNotice("Export failed");
4138
+ }
4139
+ };
4140
+ const handleCopy = async () => {
4141
+ if (!doc) return;
4142
+ setNotice(await copyDocumentToClipboard({ title, json: doc }) ? "Copied" : "Copy failed");
4143
+ };
4144
+ const editor = /* @__PURE__ */ jsx17(
4145
+ DocumentEditorImpl,
4146
+ {
4147
+ documentId: props.documentId,
4148
+ title: props.title,
4149
+ content: props.content,
4150
+ documentType: props.documentType,
4151
+ status: props.status,
4152
+ editable: props.editable,
4153
+ exportFormats: props.exportFormats,
4154
+ theme: props.theme,
4155
+ generating: props.generating,
4156
+ options: props.options
4157
+ }
4158
+ );
4159
+ const closeBar = /* @__PURE__ */ jsx17("div", { className: "june-doc-editor__bar", children: /* @__PURE__ */ jsxs11("button", { type: "button", className: "june-doc__btn june-doc__btn--ghost", "aria-label": "Close document", onClick: close, children: [
4160
+ /* @__PURE__ */ jsx17(Icon, { name: "close", size: 16 }),
4161
+ /* @__PURE__ */ jsx17("span", { children: "Close" })
4162
+ ] }) });
4163
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
4164
+ !open ? /* @__PURE__ */ jsxs11("section", { className: "june-doc june-doc-card", "data-theme": resolvedTheme, "aria-label": title, children: [
4165
+ /* @__PURE__ */ jsxs11("div", { className: "june-doc-card__bar", children: [
4166
+ /* @__PURE__ */ jsx17("span", { className: "june-doc-card__title", children: title }),
4167
+ /* @__PURE__ */ jsxs11("div", { className: "june-doc-card__actions", children: [
4168
+ notice ? /* @__PURE__ */ jsx17("span", { className: "june-doc-card__notice", children: notice }) : null,
4169
+ /* @__PURE__ */ jsx17(
4170
+ Dropdown,
4171
+ {
4172
+ align: "end",
4173
+ className: "june-doc__menu",
4174
+ trigger: ({ open: menuOpen, toggle }) => /* @__PURE__ */ jsxs11("button", { type: "button", className: "june-doc__btn june-doc__btn--ghost", "aria-label": "Download", "aria-haspopup": "menu", "aria-expanded": menuOpen, onClick: toggle, children: [
4175
+ /* @__PURE__ */ jsx17(Icon, { name: "download", size: 16 }),
4176
+ /* @__PURE__ */ jsx17("span", { children: "Download" })
4177
+ ] }),
4178
+ children: (dismiss) => /* @__PURE__ */ jsx17("div", { role: "menu", "aria-label": "Download options", children: formats.map((format2) => /* @__PURE__ */ jsx17(
4179
+ "button",
4180
+ {
4181
+ type: "button",
4182
+ role: "menuitem",
4183
+ className: "june-doc__menu-item",
4184
+ onClick: () => {
4185
+ dismiss();
4186
+ void handleExport(format2);
4187
+ },
4188
+ children: /* @__PURE__ */ jsx17("span", { className: "june-doc__menu-label", children: FORMAT_LABEL[format2] ?? format2.toUpperCase() })
4189
+ },
4190
+ format2
4191
+ )) })
4192
+ }
4193
+ ),
4194
+ /* @__PURE__ */ jsx17("button", { type: "button", className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost", "aria-label": "Copy document", title: "Copy", onClick: () => void handleCopy(), children: /* @__PURE__ */ jsx17(Icon, { name: "copy", size: 16 }) }),
4195
+ editable ? /* @__PURE__ */ jsx17("button", { type: "button", className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost", "aria-label": "Edit document", title: "Edit", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx17(Icon, { name: "pencil", size: 16 }) }) : null
4196
+ ] })
4197
+ ] }),
4198
+ /* @__PURE__ */ jsx17("div", { className: "june-doc-card__canvas", children: /* @__PURE__ */ jsx17("div", { className: "june-doc-card__sheet", children: /* @__PURE__ */ jsx17("div", { className: "june-doc__prose", dangerouslySetInnerHTML: { __html: html } }) }) })
4199
+ ] }) : null,
4200
+ open && presentation === "inline" ? /* @__PURE__ */ jsxs11("section", { className: "june-doc june-doc-inline", "data-theme": resolvedTheme, "aria-label": title, onKeyDown: (event) => {
4201
+ if (event.key !== "Escape" || event.defaultPrevented) return;
4202
+ if (event.currentTarget.querySelector(".june-doc--fullscreen")) return;
4203
+ close();
4204
+ }, children: [
4205
+ closeBar,
4206
+ editor
4207
+ ] }) : null,
4208
+ open && presentation === "side-panel" && typeof document !== "undefined" ? createPortal(
4209
+ /* @__PURE__ */ jsx17("div", { className: "june-doc-modal", "data-theme": resolvedTheme, onMouseDown: close, children: /* @__PURE__ */ jsxs11(
4210
+ "div",
4211
+ {
4212
+ className: "june-doc june-doc-modal__panel",
4213
+ role: "dialog",
4214
+ "aria-modal": "true",
4215
+ "aria-label": title,
4216
+ tabIndex: -1,
4217
+ "data-theme": resolvedTheme,
4218
+ style: panelWidth ? { width: panelWidth, maxWidth: "100vw" } : void 0,
4219
+ onMouseDown: (event) => event.stopPropagation(),
4220
+ onKeyDown: (event) => {
4221
+ if (event.key !== "Escape" || event.defaultPrevented) return;
4222
+ if (event.currentTarget.querySelector(".june-doc--fullscreen")) return;
4223
+ close();
4224
+ },
4225
+ children: [
4226
+ closeBar,
4227
+ /* @__PURE__ */ jsx17("div", { className: "june-doc-modal__body", children: editor })
4228
+ ]
4229
+ }
4230
+ ) }),
4231
+ document.body
4232
+ ) : null
4233
+ ] });
4234
+ }
4235
+
3943
4236
  // src/features/document-editor/document-component.tsx
3944
- import { jsx as jsx17 } from "react/jsx-runtime";
3945
- var DOCUMENT_COMPONENT_DESCRIPTION = 'Custom component. An editable document. DocumentEditor(documentId, title, content, documentType?, status?, editable?, exportFormats?). Place it in the root container (Card or Stack). "content" is the full document body as one Markdown string. Reuse documentId when revising the same document. Use it only when the user asks to create or revise a document.';
4237
+ import { jsx as jsx18 } from "react/jsx-runtime";
4238
+ var DOCUMENT_COMPONENT_DESCRIPTION = 'Custom component. An editable document. DocumentEditor(documentId, title, content, documentType?, status?, editable?, exportFormats?, presentation?). Place it in the root container (Card or Stack). "content" is the full document body as one Markdown string. Reuse documentId when revising the same document. Use it only when the user asks to create or revise a document. presentation is "inline" or "side-panel"; omit it to use the app default.';
3946
4239
  var documentProps = z.object({
3947
4240
  documentId: z.string().describe("Stable id. Reuse it when revising the same document."),
3948
4241
  title: z.string().describe("Document title. Do not repeat it as a heading in content."),
@@ -3950,28 +4243,30 @@ var documentProps = z.object({
3950
4243
  documentType: z.string().optional().describe("Kind of document, such as proposal, contract or invoice."),
3951
4244
  status: z.string().optional().describe('Use "draft" for generated documents.'),
3952
4245
  editable: z.boolean().optional().describe("Whether the user can edit. Defaults to true."),
3953
- exportFormats: z.array(z.string()).optional().describe("Export formats to offer, such as html, markdown, docx.")
4246
+ exportFormats: z.array(z.string()).optional().describe("Export formats to offer, such as html, markdown, docx."),
4247
+ presentation: z.enum(["inline", "side-panel"]).optional().describe('Where Edit opens the editor: "inline" in the chat, or "side-panel". Omit to use the app default.')
3954
4248
  });
3955
4249
  function DocumentEditor(props) {
3956
4250
  const { isStreaming } = useJuneRuntime();
3957
4251
  if (props.generating || isStreaming) {
3958
- return /* @__PURE__ */ jsx17(DocumentDraft, { title: props.title, state: "drafting" });
4252
+ return /* @__PURE__ */ jsx18(DocumentDraft, { title: props.title, state: "drafting" });
3959
4253
  }
3960
- return /* @__PURE__ */ jsx17(DocumentEditorImpl, { ...props });
4254
+ return /* @__PURE__ */ jsx18(DocumentEditorImpl, { ...props });
3961
4255
  }
3962
4256
  function OpenUIDocument({ props }) {
3963
- return /* @__PURE__ */ jsx17(
3964
- DocumentEditor,
3965
- {
3966
- documentId: props.documentId,
3967
- title: props.title,
3968
- content: props.content,
3969
- documentType: props.documentType,
3970
- status: props.status,
3971
- editable: props.editable,
3972
- exportFormats: props.exportFormats
3973
- }
3974
- );
4257
+ const { isStreaming } = useJuneRuntime();
4258
+ const shared = {
4259
+ documentId: props.documentId,
4260
+ title: props.title,
4261
+ content: props.content,
4262
+ documentType: props.documentType,
4263
+ status: props.status,
4264
+ editable: props.editable,
4265
+ exportFormats: props.exportFormats,
4266
+ presentation: props.presentation
4267
+ };
4268
+ if (isStreaming) return /* @__PURE__ */ jsx18(DocumentDraft, { title: props.title, state: "drafting" });
4269
+ return /* @__PURE__ */ jsx18(DocumentPreview, { ...shared });
3975
4270
  }
3976
4271
  var JuneDocumentEditorComponent = defineComponent3({
3977
4272
  name: "DocumentEditor",
@@ -4009,7 +4304,7 @@ function getDocumentEditorRules(options = {}) {
4009
4304
  return [
4010
4305
  `WHEN TO USE ${name}: use it only when the user asks you to create, draft, write, prepare, generate or revise an editable document (${types}, or similar). Put the finished document inside ${name}.`,
4011
4306
  `WHEN NOT TO USE ${name}: for explanations ("explain what an NDA is"), factual answers, comparisons, brainstorming and general questions, answer with normal text and the other components. Never open ${name} for those.`,
4012
- `${name}(documentId, title, content, documentType?, status?, editable?, exportFormats?): "content" is the whole document body as Markdown inside ONE double-quoted string. Write line breaks as \\n and escape double quotes as \\". Supported Markdown: headings (#, ##, ###), paragraphs, **bold**, *italic*, lists, > quotes, [links](https://...), and tables.`,
4307
+ `${name}(documentId, title, content, documentType?, status?, editable?, exportFormats?, presentation?): "content" is the whole document body as Markdown inside ONE double-quoted string. Write line breaks as \\n and escape double quotes as \\". Supported Markdown: headings (#, ##, ###), paragraphs, **bold**, *italic*, lists, > quotes, [links](https://...), and tables. presentation is optional: "inline" or "side-panel". Omit it to use the app default.`,
4013
4308
  `Do not repeat the title as a heading inside "content"; the title is shown separately. Structure the document with headings, short paragraphs and tables where useful. Keep a professional tone.`,
4014
4309
  `EDITING: when the user asks to change an existing document (add a section, rewrite a part, change the title), write ${name} again with the SAME documentId and the COMPLETE updated content, starting from the current document text you were given. Copy every unchanged section exactly as it was. Change only what was requested. The app merges your version with the user's own edits.`,
4015
4310
  `If the user asks for a document but leaves out details, write the document anyway and use clear placeholders such as [CLIENT NAME], [DATE], [AMOUNT]. Do not stop to ask unless the request is impossible to interpret.`,
@@ -4044,8 +4339,8 @@ ${docToMarkdown2(r.json)}
4044
4339
  }
4045
4340
 
4046
4341
  // src/features/tool-approval/tool-approval.tsx
4047
- import { useId as useId2, useState as useState7 } from "react";
4048
- import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
4342
+ import { useId as useId2, useState as useState8 } from "react";
4343
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
4049
4344
  var defaultToolApprovalLabels = {
4050
4345
  approve: "Approve",
4051
4346
  reject: "Reject",
@@ -4083,8 +4378,8 @@ function ToolApproval({
4083
4378
  }) {
4084
4379
  const labels = { ...defaultToolApprovalLabels, ...labelOverrides };
4085
4380
  const options = request.options?.length ? request.options : ["approve", "reject"];
4086
- const [draft, setDraft] = useState7(null);
4087
- const [draftError, setDraftError] = useState7(null);
4381
+ const [draft, setDraft] = useState8(null);
4382
+ const [draftError, setDraftError] = useState8(null);
4088
4383
  const titleId = useId2();
4089
4384
  const pending = status === "pending";
4090
4385
  const decide = (decision) => {
@@ -4102,7 +4397,7 @@ function ToolApproval({
4102
4397
  setDraft(null);
4103
4398
  decide({ choice: "edit", editedArguments });
4104
4399
  };
4105
- return /* @__PURE__ */ jsxs11(
4400
+ return /* @__PURE__ */ jsxs12(
4106
4401
  "section",
4107
4402
  {
4108
4403
  className: className ? `june-approval ${className}` : "june-approval",
@@ -4110,14 +4405,14 @@ function ToolApproval({
4110
4405
  "data-status": status,
4111
4406
  "data-risk": request.risk,
4112
4407
  children: [
4113
- /* @__PURE__ */ jsxs11("header", { className: "june-approval__header", children: [
4114
- /* @__PURE__ */ jsx18("strong", { id: titleId, className: "june-approval__tool", children: request.tool }),
4115
- request.risk && /* @__PURE__ */ jsx18("span", { className: "june-approval__badge", "data-risk": request.risk, children: labels.risk(request.risk) }),
4116
- request.sideEffect && /* @__PURE__ */ jsx18("span", { className: "june-approval__badge", children: labels.sideEffect[request.sideEffect] })
4408
+ /* @__PURE__ */ jsxs12("header", { className: "june-approval__header", children: [
4409
+ /* @__PURE__ */ jsx19("strong", { id: titleId, className: "june-approval__tool", children: request.tool }),
4410
+ request.risk && /* @__PURE__ */ jsx19("span", { className: "june-approval__badge", "data-risk": request.risk, children: labels.risk(request.risk) }),
4411
+ request.sideEffect && /* @__PURE__ */ jsx19("span", { className: "june-approval__badge", children: labels.sideEffect[request.sideEffect] })
4117
4412
  ] }),
4118
- request.description && /* @__PURE__ */ jsx18("p", { className: "june-approval__description", children: request.description }),
4119
- draft !== null ? /* @__PURE__ */ jsxs11("div", { className: "june-approval__edit", children: [
4120
- /* @__PURE__ */ jsx18(
4413
+ request.description && /* @__PURE__ */ jsx19("p", { className: "june-approval__description", children: request.description }),
4414
+ draft !== null ? /* @__PURE__ */ jsxs12("div", { className: "june-approval__edit", children: [
4415
+ /* @__PURE__ */ jsx19(
4121
4416
  "textarea",
4122
4417
  {
4123
4418
  "aria-label": labels.edit,
@@ -4128,12 +4423,12 @@ function ToolApproval({
4128
4423
  spellCheck: false
4129
4424
  }
4130
4425
  ),
4131
- draftError && /* @__PURE__ */ jsx18("p", { role: "alert", className: "june-approval__error", children: draftError })
4132
- ] }) : hasArguments(request.arguments) ? renderArguments ? renderArguments(request.arguments) : /* @__PURE__ */ jsx18("pre", { className: "june-approval__args", children: format(request.arguments) }) : null,
4133
- pending ? /* @__PURE__ */ jsx18("div", { className: "june-approval__actions", children: draft !== null ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
4134
- /* @__PURE__ */ jsx18("button", { type: "button", className: "june-approval__button", "data-variant": "primary", onClick: saveEdit, disabled, children: labels.save }),
4135
- /* @__PURE__ */ jsx18("button", { type: "button", className: "june-approval__button", onClick: () => setDraft(null), disabled, children: labels.cancel })
4136
- ] }) : options.map((choice) => /* @__PURE__ */ jsx18(
4426
+ draftError && /* @__PURE__ */ jsx19("p", { role: "alert", className: "june-approval__error", children: draftError })
4427
+ ] }) : hasArguments(request.arguments) ? renderArguments ? renderArguments(request.arguments) : /* @__PURE__ */ jsx19("pre", { className: "june-approval__args", children: format(request.arguments) }) : null,
4428
+ pending ? /* @__PURE__ */ jsx19("div", { className: "june-approval__actions", children: draft !== null ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
4429
+ /* @__PURE__ */ jsx19("button", { type: "button", className: "june-approval__button", "data-variant": "primary", onClick: saveEdit, disabled, children: labels.save }),
4430
+ /* @__PURE__ */ jsx19("button", { type: "button", className: "june-approval__button", onClick: () => setDraft(null), disabled, children: labels.cancel })
4431
+ ] }) : options.map((choice) => /* @__PURE__ */ jsx19(
4137
4432
  "button",
4138
4433
  {
4139
4434
  type: "button",
@@ -4144,7 +4439,7 @@ function ToolApproval({
4144
4439
  children: labels[choice]
4145
4440
  },
4146
4441
  choice
4147
- )) }) : /* @__PURE__ */ jsxs11("p", { className: "june-approval__status", role: "status", children: [
4442
+ )) }) : /* @__PURE__ */ jsxs12("p", { className: "june-approval__status", role: "status", children: [
4148
4443
  labels.status[status],
4149
4444
  status === "failed" && error ? `: ${error}` : null
4150
4445
  ] })
@@ -4154,12 +4449,12 @@ function ToolApproval({
4154
4449
  }
4155
4450
 
4156
4451
  // src/features/tool-approval/tool-approval-list.tsx
4157
- import { jsx as jsx19 } from "react/jsx-runtime";
4452
+ import { jsx as jsx20 } from "react/jsx-runtime";
4158
4453
  function ToolApprovalList({ approvals, pendingOnly = false, renderItem, labels, className }) {
4159
4454
  const entries = pendingOnly ? approvals.entries.filter((e) => e.status === "pending") : approvals.entries;
4160
4455
  if (entries.length === 0) return null;
4161
- return /* @__PURE__ */ jsx19("div", { className: className ? `june-approval-list ${className}` : "june-approval-list", children: entries.map(
4162
- (entry) => renderItem ? /* @__PURE__ */ jsx19("div", { children: renderItem(entry, approvals.decide) }, entry.request.id) : /* @__PURE__ */ jsx19(
4456
+ return /* @__PURE__ */ jsx20("div", { className: className ? `june-approval-list ${className}` : "june-approval-list", children: entries.map(
4457
+ (entry) => renderItem ? /* @__PURE__ */ jsx20("div", { children: renderItem(entry, approvals.decide) }, entry.request.id) : /* @__PURE__ */ jsx20(
4163
4458
  ToolApproval,
4164
4459
  {
4165
4460
  request: entry.request,
@@ -4174,11 +4469,11 @@ function ToolApprovalList({ approvals, pendingOnly = false, renderItem, labels,
4174
4469
  }
4175
4470
 
4176
4471
  // src/features/tool-approval/use-tool-approval.ts
4177
- import { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMemo6, useRef as useRef9, useState as useState8 } from "react";
4472
+ import { useCallback as useCallback5, useEffect as useEffect9, useMemo as useMemo8, useRef as useRef9, useState as useState9 } from "react";
4178
4473
  var counter = 0;
4179
4474
  var nextId = () => `june-approval-${++counter}`;
4180
4475
  function useToolApproval() {
4181
- const [entries, setEntries] = useState8([]);
4476
+ const [entries, setEntries] = useState9([]);
4182
4477
  const waiters = useRef9(/* @__PURE__ */ new Map());
4183
4478
  const settle = useCallback5((id, decision) => {
4184
4479
  const resolve = waiters.current.get(id);
@@ -4223,15 +4518,15 @@ function useToolApproval() {
4223
4518
  },
4224
4519
  [request]
4225
4520
  );
4226
- useEffect8(() => {
4521
+ useEffect9(() => {
4227
4522
  const map = waiters.current;
4228
4523
  return () => {
4229
4524
  map.forEach((resolve) => resolve({ choice: "reject" }));
4230
4525
  map.clear();
4231
4526
  };
4232
4527
  }, []);
4233
- const pending = useMemo6(() => entries.filter((e) => e.status === "pending"), [entries]);
4234
- return useMemo6(
4528
+ const pending = useMemo8(() => entries.filter((e) => e.status === "pending"), [entries]);
4529
+ return useMemo8(
4235
4530
  () => ({ entries, pending, request, decide, setStatus, dismiss, confirmAction }),
4236
4531
  [entries, pending, request, decide, setStatus, dismiss, confirmAction]
4237
4532
  );
@@ -4335,6 +4630,8 @@ export {
4335
4630
  runAction,
4336
4631
  createActionHandler,
4337
4632
  JuneDocumentContext,
4633
+ DocumentEditorConfigContext,
4634
+ useDocumentEditorConfig,
4338
4635
  useDocumentOptions,
4339
4636
  mergeDocuments,
4340
4637
  createDocumentStore,
@@ -4379,6 +4676,8 @@ export {
4379
4676
  downloadResult,
4380
4677
  copyDocumentToClipboard,
4381
4678
  ALL_EXPORT_FORMATS,
4679
+ resolveDocumentPresentation,
4680
+ documentPanelWidth,
4382
4681
  DOCUMENT_COMPONENT_DESCRIPTION,
4383
4682
  DocumentEditor,
4384
4683
  JuneDocumentEditorComponent,