@june-ai/june-ui 0.2.2 → 0.2.4

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/index.cjs CHANGED
@@ -1,8 +1,13 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
6
11
  var __export = (target, all) => {
7
12
  for (var name in all)
8
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -15,63 +20,261 @@ var __copyProps = (to, from, except, desc) => {
15
20
  }
16
21
  return to;
17
22
  };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
18
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
32
 
33
+ // src/errors/errors.ts
34
+ function isJuneError(value) {
35
+ return value instanceof JuneError;
36
+ }
37
+ function toJuneError(error, fallbackCode, message) {
38
+ if (error instanceof JuneError) return error;
39
+ const text = message ?? (error instanceof Error ? error.message : String(error));
40
+ return new JuneError(fallbackCode, text, { cause: error });
41
+ }
42
+ var JuneError;
43
+ var init_errors = __esm({
44
+ "src/errors/errors.ts"() {
45
+ "use strict";
46
+ JuneError = class _JuneError extends Error {
47
+ constructor(code2, message, options = {}) {
48
+ super(message);
49
+ Object.setPrototypeOf(this, _JuneError.prototype);
50
+ this.name = "JuneError";
51
+ this.code = code2;
52
+ this.details = options.details;
53
+ if (options.cause !== void 0) {
54
+ this.cause = options.cause;
55
+ }
56
+ }
57
+ };
58
+ }
59
+ });
60
+
61
+ // src/components/document-editor/document-extensions.ts
62
+ function createDocumentExtensions() {
63
+ return [
64
+ import_starter_kit.default.configure({
65
+ heading: { levels: [1, 2, 3] },
66
+ // Tiptap v3 StarterKit already includes Link and Underline
67
+ link: { openOnClick: false, autolink: true, defaultProtocol: "https" }
68
+ }),
69
+ import_extension_text_align.default.configure({ types: ["heading", "paragraph"] }),
70
+ import_extension_table.TableKit,
71
+ import_markdown.Markdown
72
+ ];
73
+ }
74
+ var import_extension_table, import_extension_text_align, import_markdown, import_starter_kit;
75
+ var init_document_extensions = __esm({
76
+ "src/components/document-editor/document-extensions.ts"() {
77
+ "use strict";
78
+ import_extension_table = require("@tiptap/extension-table");
79
+ import_extension_text_align = __toESM(require("@tiptap/extension-text-align"), 1);
80
+ import_markdown = require("@tiptap/markdown");
81
+ import_starter_kit = __toESM(require("@tiptap/starter-kit"), 1);
82
+ }
83
+ });
84
+
85
+ // src/components/document-editor/document-utils.ts
86
+ function isDocJSON(value) {
87
+ if (!value || typeof value !== "object") return false;
88
+ const v = value;
89
+ return v.type === "doc" && (v.content === void 0 || Array.isArray(v.content));
90
+ }
91
+ function looksLikeDocJSON(text) {
92
+ return /^\s*\{\s*"type"\s*:\s*"doc"/.test(text);
93
+ }
94
+ function tryParseDocJSON(text) {
95
+ try {
96
+ const value = JSON.parse(text);
97
+ return isDocJSON(value) ? value : null;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ function docKey(doc) {
103
+ let key = keyCache.get(doc);
104
+ if (key === void 0) {
105
+ key = JSON.stringify(doc);
106
+ keyCache.set(doc, key);
107
+ }
108
+ return key;
109
+ }
110
+ function plainText(node) {
111
+ const n = node;
112
+ if (typeof n.text === "string") return n.text;
113
+ return (n.content ?? []).map(plainText).join(" ");
114
+ }
115
+ function countWords(text) {
116
+ const trimmed = text.trim();
117
+ return trimmed ? trimmed.split(/\s+/).length : 0;
118
+ }
119
+ function wordCountOf(doc) {
120
+ let count = wordCache.get(doc);
121
+ if (count === void 0) {
122
+ count = countWords(plainText(doc));
123
+ wordCache.set(doc, count);
124
+ }
125
+ return count;
126
+ }
127
+ function slugify(value) {
128
+ return value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
129
+ }
130
+ function debounce(fn, wait) {
131
+ let timer = null;
132
+ const run = () => {
133
+ timer = null;
134
+ fn();
135
+ };
136
+ const debounced = (() => {
137
+ if (timer !== null) clearTimeout(timer);
138
+ timer = setTimeout(run, wait);
139
+ });
140
+ debounced.cancel = () => {
141
+ if (timer !== null) {
142
+ clearTimeout(timer);
143
+ timer = null;
144
+ }
145
+ };
146
+ debounced.flush = () => {
147
+ if (timer !== null) {
148
+ clearTimeout(timer);
149
+ run();
150
+ }
151
+ };
152
+ return debounced;
153
+ }
154
+ function sanitizeHref(input) {
155
+ const value = input.trim();
156
+ if (!value) return null;
157
+ if (/^(https?:|mailto:|tel:)/i.test(value)) return value;
158
+ if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return null;
159
+ if (value.startsWith("/") || value.startsWith("#")) return value;
160
+ return `https://${value}`;
161
+ }
162
+ var emptyDoc, keyCache, wordCache;
163
+ var init_document_utils = __esm({
164
+ "src/components/document-editor/document-utils.ts"() {
165
+ "use strict";
166
+ emptyDoc = () => ({ type: "doc", content: [{ type: "paragraph" }] });
167
+ keyCache = /* @__PURE__ */ new WeakMap();
168
+ wordCache = /* @__PURE__ */ new WeakMap();
169
+ }
170
+ });
171
+
172
+ // src/components/document-editor/document-engine.ts
173
+ var document_engine_exports = {};
174
+ __export(document_engine_exports, {
175
+ docToHtml: () => docToHtml,
176
+ docToMarkdown: () => docToMarkdown,
177
+ docToText: () => docToText,
178
+ parseDocumentContent: () => parseDocumentContent
179
+ });
180
+ function scratchEditor() {
181
+ if (!scratch || scratch.isDestroyed) {
182
+ scratch = new import_core.Editor({ extensions: createDocumentExtensions(), content: "" });
183
+ }
184
+ return scratch;
185
+ }
186
+ function parseDocumentContent(text) {
187
+ const editor = scratchEditor();
188
+ const trimmed = text.trim();
189
+ try {
190
+ if (looksLikeDocJSON(trimmed)) {
191
+ const json = tryParseDocJSON(trimmed);
192
+ if (!json) throw new Error("The document JSON could not be parsed");
193
+ editor.schema.nodeFromJSON(json).check();
194
+ editor.commands.setContent(json, { emitUpdate: false });
195
+ } else {
196
+ editor.commands.setContent(trimmed, { contentType: "markdown", emitUpdate: false });
197
+ }
198
+ return editor.getJSON();
199
+ } catch (error) {
200
+ throw toJuneError(error, "DOCUMENT_ERROR", "The document content is invalid");
201
+ }
202
+ }
203
+ function load(doc) {
204
+ const editor = scratchEditor();
205
+ editor.commands.setContent(doc, { emitUpdate: false });
206
+ return editor;
207
+ }
208
+ var import_core, scratch, docToHtml, docToMarkdown, docToText;
209
+ var init_document_engine = __esm({
210
+ "src/components/document-editor/document-engine.ts"() {
211
+ "use strict";
212
+ import_core = require("@tiptap/core");
213
+ init_errors();
214
+ init_document_extensions();
215
+ init_document_utils();
216
+ scratch = null;
217
+ docToHtml = (doc) => load(doc).getHTML();
218
+ docToMarkdown = (doc) => load(doc).getMarkdown();
219
+ docToText = (doc) => load(doc).getText({ blockSeparator: "\n\n" });
220
+ }
221
+ });
222
+
20
223
  // src/index.ts
21
224
  var index_exports = {};
22
225
  __export(index_exports, {
226
+ ALL_EXPORT_FORMATS: () => ALL_EXPORT_FORMATS,
227
+ DocumentEditor: () => DocumentEditor,
228
+ JuneDocumentContext: () => JuneDocumentContext,
229
+ JuneDocumentEditorComponent: () => JuneDocumentEditorComponent,
23
230
  JuneError: () => JuneError,
24
231
  JuneErrorBoundary: () => JuneErrorBoundary,
25
232
  JuneRenderer: () => JuneRenderer,
233
+ JuneRuntimeContext: () => JuneRuntimeContext,
26
234
  JuneThemeProvider: () => JuneThemeProvider,
27
235
  JuneUI: () => JuneUI,
28
236
  consumeStream: () => consumeStream,
237
+ copyDocumentToClipboard: () => copyDocumentToClipboard,
29
238
  createActionHandler: () => createActionHandler,
239
+ createDocumentStore: () => createDocumentStore,
30
240
  createJuneLibrary: () => createJuneLibrary,
31
241
  darkTheme: () => darkTheme,
32
242
  defaultParseActionEvent: () => defaultParseActionEvent,
33
243
  defineJuneAction: () => defineJuneAction,
34
244
  defineJuneComponent: () => defineJuneComponent,
245
+ documentPromptOptions: () => documentPromptOptions,
246
+ downloadResult: () => downloadResult,
247
+ exportDocument: () => exportDocument,
35
248
  fetchStream: () => fetchStream,
249
+ getDefaultDocumentStore: () => getDefaultDocumentStore,
250
+ getDocumentEditorInstructions: () => getDocumentEditorInstructions,
251
+ getDocumentEditorRules: () => getDocumentEditorRules,
36
252
  isJuneError: () => isJuneError,
37
253
  jsonFieldExtractor: () => jsonFieldExtractor,
254
+ juneLibrary: () => juneLibrary,
38
255
  lightTheme: () => lightTheme,
256
+ listDocumentExporters: () => listDocumentExporters,
257
+ mergeDocuments: () => mergeDocuments,
258
+ registerDocumentExporter: () => registerDocumentExporter,
39
259
  runAction: () => runAction,
260
+ serializeDocumentsForAgent: () => serializeDocumentsForAgent,
40
261
  sseToText: () => sseToText,
41
262
  toJuneError: () => toJuneError,
263
+ useDocumentOptions: () => useDocumentOptions,
264
+ useJuneRuntime: () => useJuneRuntime,
42
265
  useJuneStream: () => useJuneStream,
43
266
  useJuneTheme: () => useJuneTheme,
44
- useResolvedTheme: () => useResolvedTheme
267
+ useResolvedTheme: () => useResolvedTheme,
268
+ withDocumentEditor: () => withDocumentEditor
45
269
  });
46
270
  module.exports = __toCommonJS(index_exports);
47
271
 
48
272
  // src/renderer/renderer.tsx
49
273
  var import_react_lang = require("@openuidev/react-lang");
50
- var import_react4 = require("react");
51
-
52
- // src/errors/errors.ts
53
- var JuneError = class _JuneError extends Error {
54
- constructor(code, message, options = {}) {
55
- super(message);
56
- Object.setPrototypeOf(this, _JuneError.prototype);
57
- this.name = "JuneError";
58
- this.code = code;
59
- this.details = options.details;
60
- if (options.cause !== void 0) {
61
- this.cause = options.cause;
62
- }
63
- }
64
- };
65
- function isJuneError(value) {
66
- return value instanceof JuneError;
67
- }
68
- function toJuneError(error, fallbackCode, message) {
69
- if (error instanceof JuneError) return error;
70
- const text = message ?? (error instanceof Error ? error.message : String(error));
71
- return new JuneError(fallbackCode, text, { cause: error });
72
- }
274
+ var import_react5 = require("react");
73
275
 
74
276
  // src/actions/action-handler.ts
277
+ init_errors();
75
278
  function firstString(...values) {
76
279
  for (const v of values) if (typeof v === "string" && v.length > 0) return v;
77
280
  return null;
@@ -133,19 +336,37 @@ function createActionHandler(options) {
133
336
  };
134
337
  }
135
338
 
136
- // src/themes/theme-provider.tsx
137
- var import_react2 = require("react");
339
+ // src/components/document-editor/document-context.ts
340
+ var import_react = require("react");
341
+ var JuneDocumentContext = (0, import_react.createContext)({});
342
+ function useDocumentOptions() {
343
+ return (0, import_react.useContext)(JuneDocumentContext);
344
+ }
345
+ function useDocumentValue(store, id, selector) {
346
+ const read = () => selector(store.get(id));
347
+ return (0, import_react.useSyncExternalStore)(store.subscribe, read, read);
348
+ }
138
349
 
139
350
  // src/core/context.ts
140
- var import_react = require("react");
141
- var JuneContext = (0, import_react.createContext)({
351
+ var import_react2 = require("react");
352
+ var JuneContext = (0, import_react2.createContext)({
142
353
  mode: "light",
143
354
  theme: "light"
144
355
  });
145
356
  function useJuneTheme() {
146
- return (0, import_react.useContext)(JuneContext);
357
+ return (0, import_react2.useContext)(JuneContext);
358
+ }
359
+ var JuneRuntimeContext = (0, import_react2.createContext)({ isStreaming: false });
360
+ function useJuneRuntime() {
361
+ return (0, import_react2.useContext)(JuneRuntimeContext);
147
362
  }
148
363
 
364
+ // src/renderer/renderer.tsx
365
+ init_errors();
366
+
367
+ // src/themes/theme-provider.tsx
368
+ var import_react3 = require("react");
369
+
149
370
  // src/themes/dark.ts
150
371
  var darkTheme = {
151
372
  "--june-foreground": "#f3f4f6",
@@ -182,7 +403,7 @@ function getServerSnapshot() {
182
403
  return "light";
183
404
  }
184
405
  function useResolvedTheme(mode) {
185
- const system = (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
406
+ const system = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
186
407
  return mode === "system" ? system : mode;
187
408
  }
188
409
  function JuneThemeProvider({
@@ -194,7 +415,7 @@ function JuneThemeProvider({
194
415
  }) {
195
416
  const explicit = mode !== void 0;
196
417
  const resolved = useResolvedTheme(mode ?? "system");
197
- const mergedStyle = (0, import_react2.useMemo)(() => {
418
+ const mergedStyle = (0, import_react3.useMemo)(() => {
198
419
  if (!explicit && !vars && !style) return void 0;
199
420
  const base = explicit ? resolved === "dark" ? darkTheme : lightTheme : void 0;
200
421
  return {
@@ -204,7 +425,7 @@ function JuneThemeProvider({
204
425
  ...style
205
426
  };
206
427
  }, [explicit, resolved, vars, style]);
207
- const context = (0, import_react2.useMemo)(
428
+ const context = (0, import_react3.useMemo)(
208
429
  () => ({ mode: mode ?? "system", theme: resolved }),
209
430
  [mode, resolved]
210
431
  );
@@ -233,6 +454,7 @@ var ATTACHABLE = /* @__PURE__ */ new Set([
233
454
  "CodeBlock",
234
455
  "CompositeCardBlock",
235
456
  "ContextCardBlock",
457
+ "DocumentEditor",
236
458
  "EditableTable",
237
459
  "EntityList",
238
460
  "Form",
@@ -628,12 +850,12 @@ function splitResponse(source, options = {}) {
628
850
  }
629
851
 
630
852
  // src/renderer/error-boundary.tsx
631
- var import_react3 = require("react");
853
+ var import_react4 = require("react");
632
854
  var import_jsx_runtime2 = require("react/jsx-runtime");
633
855
  function keysChanged(a = [], b = []) {
634
856
  return a.length !== b.length || a.some((v, i) => !Object.is(v, b[i]));
635
857
  }
636
- var JuneErrorBoundary = class extends import_react3.Component {
858
+ var JuneErrorBoundary = class extends import_react4.Component {
637
859
  constructor() {
638
860
  super(...arguments);
639
861
  this.state = { error: null };
@@ -671,6 +893,7 @@ function JuneRenderer(props) {
671
893
  isStreaming = false,
672
894
  mixedContent = true,
673
895
  renderText,
896
+ repair = true,
674
897
  theme,
675
898
  themeVars,
676
899
  className,
@@ -681,29 +904,16 @@ function JuneRenderer(props) {
681
904
  onAction,
682
905
  onError,
683
906
  onJuneError,
907
+ documents,
684
908
  loadingFallback = null,
685
909
  emptyFallback = null,
686
910
  errorFallback,
687
911
  resetKeys,
688
912
  ...rendererProps
689
913
  } = props;
690
- const latest = (0, import_react4.useRef)({
691
- actions,
692
- confirmAction,
693
- parseActionEvent,
694
- onAction,
695
- onError,
696
- onJuneError
697
- });
698
- latest.current = {
699
- actions,
700
- confirmAction,
701
- parseActionEvent,
702
- onAction,
703
- onError,
704
- onJuneError
705
- };
706
- const handleAction = (0, import_react4.useCallback)((event) => {
914
+ const latest = (0, import_react5.useRef)({ actions, confirmAction, parseActionEvent, onAction, onError, onJuneError });
915
+ latest.current = { actions, confirmAction, parseActionEvent, onAction, onError, onJuneError };
916
+ const handleAction = (0, import_react5.useCallback)((event) => {
707
917
  const l = latest.current;
708
918
  l.onAction?.(event);
709
919
  if (l.actions) {
@@ -715,21 +925,21 @@ function JuneRenderer(props) {
715
925
  });
716
926
  }
717
927
  }, []);
718
- const handleParserErrors = (0, import_react4.useCallback)((errors) => {
719
- const list = Array.isArray(errors) ? errors : [];
928
+ const handleParserErrors = (0, import_react5.useCallback)((errors) => {
929
+ const list2 = Array.isArray(errors) ? errors : [];
720
930
  const l = latest.current;
721
- l.onError?.(list);
722
- const real = list.filter((e) => e?.severity !== "warning");
931
+ l.onError?.(list2);
932
+ const real = list2.filter((e) => e?.severity !== "warning");
723
933
  if (real.length > 0) {
724
934
  l.onJuneError?.(
725
935
  new JuneError("PARSE_ERROR", `OpenUI reported ${real.length} error(s)`, { details: real })
726
936
  );
727
937
  }
728
- if (list.length > 0 && !l.onError && !l.onJuneError) {
729
- console.warn("[june-ui] OpenUI reported errors:", list);
938
+ if (list2.length > 0 && !l.onError && !l.onJuneError) {
939
+ console.warn("[june-ui] OpenUI reported errors:", list2);
730
940
  }
731
941
  }, []);
732
- const handleBoundaryError = (0, import_react4.useCallback)((error, info) => {
942
+ const handleBoundaryError = (0, import_react5.useCallback)((error, info) => {
733
943
  latest.current.onJuneError?.(
734
944
  new JuneError("RENDER_ERROR", error.message, {
735
945
  cause: error,
@@ -737,21 +947,32 @@ function JuneRenderer(props) {
737
947
  })
738
948
  );
739
949
  }, []);
740
- const parts = (0, import_react4.useMemo)(
950
+ const runtime = (0, import_react5.useMemo)(() => ({ isStreaming }), [isStreaming]);
951
+ const documentOptions = (0, import_react5.useMemo)(
952
+ () => ({
953
+ ...documents,
954
+ onError: (error) => {
955
+ documents?.onError?.(error);
956
+ latest.current.onJuneError?.(error);
957
+ }
958
+ }),
959
+ [documents]
960
+ );
961
+ const parts = (0, import_react5.useMemo)(
741
962
  () => mixedContent && response ? splitResponse(response, { streaming: isStreaming }) : null,
742
963
  [mixedContent, response, isStreaming]
743
964
  );
744
- const uiText = (0, import_react4.useMemo)(() => {
965
+ const uiText = (0, import_react5.useMemo)(() => {
745
966
  const raw = parts && !parts.pure ? parts.ui : response;
746
- if (!mixedContent || isStreaming || typeof raw !== "string" || raw.length === 0) return raw;
967
+ if (!repair || !mixedContent || isStreaming || typeof raw !== "string" || raw.length === 0) return raw;
747
968
  return normalizeProgram(raw);
748
- }, [parts, response, mixedContent, isStreaming]);
969
+ }, [parts, response, mixedContent, isStreaming, repair]);
749
970
  const hasUi = typeof uiText === "string" && uiText.trim().length > 0;
750
971
  const before = parts?.before ?? [];
751
972
  const after = parts?.after ?? [];
752
973
  const hasText = before.length + after.length > 0;
753
- const text = (value, index, key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react4.Fragment, { children: renderText ? renderText(value, index) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "june-text", children: value }) }, key);
754
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneThemeProvider, { mode: theme, vars: themeVars, className, style, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
974
+ const text = (value, index, key) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react5.Fragment, { children: renderText ? renderText(value, index) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "june-text", children: value }) }, key);
975
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneThemeProvider, { mode: theme, vars: themeVars, className, style, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneRuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JuneDocumentContext.Provider, { value: documentOptions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
755
976
  JuneErrorBoundary,
756
977
  {
757
978
  fallback: errorFallback,
@@ -774,11 +995,12 @@ function JuneRenderer(props) {
774
995
  !hasUi && !hasText && (isStreaming ? loadingFallback : emptyFallback)
775
996
  ]
776
997
  }
777
- ) });
998
+ ) }) }) });
778
999
  }
779
1000
 
780
1001
  // src/components/library.ts
781
1002
  var import_react_lang2 = require("@openuidev/react-lang");
1003
+ init_errors();
782
1004
  var defineJuneComponent = import_react_lang2.defineComponent;
783
1005
  function createJuneLibrary(options) {
784
1006
  const { allow, deny, ...rest } = options;
@@ -820,6 +1042,7 @@ function createJuneLibrary(options) {
820
1042
  }
821
1043
 
822
1044
  // src/actions/define-action.ts
1045
+ init_errors();
823
1046
  function defineJuneAction(definition) {
824
1047
  if (typeof definition.handler !== "function") {
825
1048
  throw new JuneError("ACTION_VALIDATION_ERROR", "defineJuneAction requires a handler function");
@@ -828,9 +1051,17 @@ function defineJuneAction(definition) {
828
1051
  }
829
1052
 
830
1053
  // src/streaming/use-june-stream.ts
831
- var import_react5 = require("react");
1054
+ var import_react6 = require("react");
1055
+ init_errors();
1056
+
1057
+ // src/streaming/fetch-adapter.ts
1058
+ init_errors();
1059
+
1060
+ // src/streaming/sse-adapter.ts
1061
+ init_errors();
832
1062
 
833
1063
  // src/streaming/stream-handler.ts
1064
+ init_errors();
834
1065
  function isReadableStream(source) {
835
1066
  return typeof source.getReader === "function";
836
1067
  }
@@ -1001,20 +1232,20 @@ var INITIAL = {
1001
1232
  error: null
1002
1233
  };
1003
1234
  function useJuneStream(options = {}) {
1004
- const [state, setState] = (0, import_react5.useState)(INITIAL);
1005
- const optionsRef = (0, import_react5.useRef)(options);
1235
+ const [state, setState] = (0, import_react6.useState)(INITIAL);
1236
+ const optionsRef = (0, import_react6.useRef)(options);
1006
1237
  optionsRef.current = options;
1007
- const controllerRef = (0, import_react5.useRef)(null);
1008
- const runIdRef = (0, import_react5.useRef)(0);
1009
- const textRef = (0, import_react5.useRef)("");
1010
- const timerRef = (0, import_react5.useRef)(null);
1011
- const clearTimer = (0, import_react5.useCallback)(() => {
1238
+ const controllerRef = (0, import_react6.useRef)(null);
1239
+ const runIdRef = (0, import_react6.useRef)(0);
1240
+ const textRef = (0, import_react6.useRef)("");
1241
+ const timerRef = (0, import_react6.useRef)(null);
1242
+ const clearTimer = (0, import_react6.useCallback)(() => {
1012
1243
  if (timerRef.current !== null) {
1013
1244
  clearTimeout(timerRef.current);
1014
1245
  timerRef.current = null;
1015
1246
  }
1016
1247
  }, []);
1017
- const schedule = (0, import_react5.useCallback)(() => {
1248
+ const schedule = (0, import_react6.useCallback)(() => {
1018
1249
  if (timerRef.current !== null) return;
1019
1250
  timerRef.current = setTimeout(() => {
1020
1251
  timerRef.current = null;
@@ -1024,7 +1255,7 @@ function useJuneStream(options = {}) {
1024
1255
  );
1025
1256
  }, FLUSH_MS);
1026
1257
  }, []);
1027
- const start = (0, import_react5.useCallback)(
1258
+ const start = (0, import_react6.useCallback)(
1028
1259
  async (input) => {
1029
1260
  controllerRef.current?.abort();
1030
1261
  const controller = new AbortController();
@@ -1069,21 +1300,21 @@ function useJuneStream(options = {}) {
1069
1300
  },
1070
1301
  [clearTimer, schedule]
1071
1302
  );
1072
- const startFetch = (0, import_react5.useCallback)(
1303
+ const startFetch = (0, import_react6.useCallback)(
1073
1304
  (input, fetchOptions) => start((signal) => fetchStream(input, { ...fetchOptions, signal })),
1074
1305
  [start]
1075
1306
  );
1076
- const abort = (0, import_react5.useCallback)(() => {
1307
+ const abort = (0, import_react6.useCallback)(() => {
1077
1308
  controllerRef.current?.abort();
1078
1309
  }, []);
1079
- const reset = (0, import_react5.useCallback)(() => {
1310
+ const reset = (0, import_react6.useCallback)(() => {
1080
1311
  controllerRef.current?.abort();
1081
1312
  runIdRef.current++;
1082
1313
  textRef.current = "";
1083
1314
  clearTimer();
1084
1315
  setState(INITIAL);
1085
1316
  }, [clearTimer]);
1086
- (0, import_react5.useEffect)(
1317
+ (0, import_react6.useEffect)(
1087
1318
  () => () => {
1088
1319
  runIdRef.current++;
1089
1320
  controllerRef.current?.abort();
@@ -1091,40 +1322,1185 @@ function useJuneStream(options = {}) {
1091
1322
  },
1092
1323
  [clearTimer]
1093
1324
  );
1094
- return (0, import_react5.useMemo)(
1325
+ return (0, import_react6.useMemo)(
1095
1326
  () => ({ ...state, start, startFetch, abort, reset }),
1096
1327
  [state, start, startFetch, abort, reset]
1097
1328
  );
1098
1329
  }
1099
1330
 
1100
- // src/core/JuneUI.tsx
1101
- var import_react_ui = require("@openuidev/react-ui");
1331
+ // src/entry-core.ts
1332
+ init_errors();
1333
+
1334
+ // src/components/document-editor/document-component.tsx
1335
+ var import_react_lang3 = require("@openuidev/react-lang");
1336
+ var import_v4 = require("zod/v4");
1337
+
1338
+ // src/components/document-editor/document-draft.tsx
1102
1339
  var import_jsx_runtime4 = require("react/jsx-runtime");
1103
- function JuneUI({ library = import_react_ui.openuiLibrary, ...props }) {
1104
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(JuneRenderer, { library, ...props });
1340
+ function DocumentDraft({ title, words, state }) {
1341
+ if (state === "empty") {
1342
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "june-doc june-doc--draft", role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "june-doc__draft-text", children: "No document content was received." }) });
1343
+ }
1344
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "june-doc june-doc--draft", role: "status", "aria-live": "polite", children: [
1345
+ title ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "june-doc__draft-title", children: title }) : null,
1346
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "june-doc__draft-text", children: state === "drafting" ? `Drafting document\u2026${words ? ` ${words} words so far` : ""}` : "Loading editor\u2026" }),
1347
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "june-doc__pulse", "aria-hidden": "true", children: [
1348
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", {}),
1349
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", {}),
1350
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", {})
1351
+ ] })
1352
+ ] });
1353
+ }
1354
+
1355
+ // src/components/document-editor/document-editor.tsx
1356
+ var import_react10 = require("@tiptap/react");
1357
+ var import_react11 = require("react");
1358
+ init_errors();
1359
+
1360
+ // src/components/document-editor/document-content.tsx
1361
+ var import_react7 = require("@tiptap/react");
1362
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1363
+ function DocumentContent({ editor }) {
1364
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "june-doc__content", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react7.EditorContent, { editor }) });
1365
+ }
1366
+
1367
+ // src/components/document-editor/document-editor.tsx
1368
+ init_document_engine();
1369
+
1370
+ // src/components/document-editor/document-export.ts
1371
+ init_errors();
1372
+ init_document_utils();
1373
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1374
+ var PRINT_CSS = 'body{font-family:system-ui,-apple-system,"Segoe UI",sans-serif;line-height:1.6;color:#111827;max-width:780px;margin:32px auto;padding:0 20px}h1,h2,h3{line-height:1.25}table{border-collapse:collapse;width:100%}th,td{border:1px solid #d1d5db;padding:6px 10px;text-align:left}blockquote{border-left:4px solid #d1d5db;margin:1em 0;padding-left:1em;color:#4b5563}pre,code{font-family:ui-monospace,Menlo,monospace;background:#f3f4f6}pre{padding:12px;overflow:auto}@media print{body{margin:0;max-width:none}}';
1375
+ function buildHtmlDocument(title, bodyHtml) {
1376
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${escapeHtml(
1377
+ title
1378
+ )}</title><style>${PRINT_CSS}</style></head><body><h1>${escapeHtml(title)}</h1>${bodyHtml}</body></html>`;
1379
+ }
1380
+ var textResult = (text, filename, mimeType) => ({
1381
+ filename,
1382
+ mimeType,
1383
+ text,
1384
+ blob: new Blob([text], { type: `${mimeType};charset=utf-8` })
1385
+ });
1386
+ var exportHtml = async (ctx) => {
1387
+ const { docToHtml: docToHtml2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
1388
+ return textResult(
1389
+ buildHtmlDocument(ctx.title, docToHtml2(ctx.json)),
1390
+ `${ctx.basename}.html`,
1391
+ "text/html"
1392
+ );
1393
+ };
1394
+ var exportMarkdown = async (ctx) => {
1395
+ const { docToMarkdown: docToMarkdown2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
1396
+ return textResult(
1397
+ `# ${ctx.title}
1398
+
1399
+ ${docToMarkdown2(ctx.json)}
1400
+ `,
1401
+ `${ctx.basename}.md`,
1402
+ "text/markdown"
1403
+ );
1404
+ };
1405
+ var exportTxt = async (ctx) => {
1406
+ const { docToText: docToText2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
1407
+ return textResult(`${ctx.title}
1408
+
1409
+ ${docToText2(ctx.json)}
1410
+ `, `${ctx.basename}.txt`, "text/plain");
1411
+ };
1412
+ var ORDERED_REF = "june-ordered";
1413
+ var DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
1414
+ function runs(d, node, fmt) {
1415
+ const out = [];
1416
+ for (const child of node.content ?? []) {
1417
+ if (child.type === "hardBreak") {
1418
+ out.push(new d.TextRun({ break: 1 }));
1419
+ continue;
1420
+ }
1421
+ if (child.type !== "text" || !child.text) continue;
1422
+ const marks = child.marks ?? [];
1423
+ const has = (type) => marks.some((m) => m.type === type);
1424
+ const link = marks.find((m) => m.type === "link");
1425
+ const run = new d.TextRun({
1426
+ text: child.text,
1427
+ bold: fmt.bold || has("bold") || void 0,
1428
+ italics: has("italic") || void 0,
1429
+ underline: has("underline") || link ? {} : void 0,
1430
+ strike: has("strike") || void 0,
1431
+ font: has("code") ? "Courier New" : void 0,
1432
+ color: link ? "0563C1" : void 0
1433
+ });
1434
+ const href = typeof link?.attrs?.href === "string" ? sanitizeHref(link.attrs.href) : null;
1435
+ out.push(href ? new d.ExternalHyperlink({ link: href, children: [run] }) : run);
1436
+ }
1437
+ return out;
1438
+ }
1439
+ function paragraph(d, node, fmt, extra = {}) {
1440
+ const align = node.attrs?.textAlign;
1441
+ return new d.Paragraph({
1442
+ children: runs(d, node, fmt),
1443
+ alignment: align === "center" ? d.AlignmentType.CENTER : align === "right" ? d.AlignmentType.RIGHT : align === "justify" ? d.AlignmentType.JUSTIFIED : void 0,
1444
+ indent: fmt.indent ? { left: fmt.indent } : void 0,
1445
+ border: fmt.quote ? { left: { color: "9CA3AF", space: 8, style: d.BorderStyle.SINGLE, size: 12 } } : void 0,
1446
+ spacing: { after: 120 },
1447
+ ...extra
1448
+ });
1449
+ }
1450
+ function list(d, node, fmt, level, state) {
1451
+ const ordered = node.type === "orderedList";
1452
+ const instance = ordered ? ++state.instance : 0;
1453
+ const out = [];
1454
+ for (const item of node.content ?? []) {
1455
+ let first = true;
1456
+ for (const child of item.content ?? []) {
1457
+ if (child.type === "bulletList" || child.type === "orderedList") {
1458
+ out.push(...list(d, child, fmt, level + 1, state));
1459
+ } else if (child.type === "paragraph" && first) {
1460
+ first = false;
1461
+ const lvl = Math.min(level, 2);
1462
+ out.push(
1463
+ paragraph(
1464
+ d,
1465
+ child,
1466
+ fmt,
1467
+ ordered ? { numbering: { reference: ORDERED_REF, level: lvl, instance } } : { bullet: { level: lvl } }
1468
+ )
1469
+ );
1470
+ } else {
1471
+ out.push(...blocks(d, [child], { ...fmt, indent: (fmt.indent ?? 0) + 720 * (level + 1) }, state));
1472
+ }
1473
+ }
1474
+ }
1475
+ return out;
1476
+ }
1477
+ function code(d, node) {
1478
+ const lines = (node.content ?? []).map((c) => c.text ?? "").join("").split("\n");
1479
+ return new d.Paragraph({
1480
+ children: lines.map((line, i) => new d.TextRun({ text: line, font: "Courier New", break: i ? 1 : void 0 })),
1481
+ spacing: { after: 120 },
1482
+ shading: { type: d.ShadingType.CLEAR, fill: "F3F4F6" }
1483
+ });
1484
+ }
1485
+ function table(d, node, state) {
1486
+ const rows = (node.content ?? []).map(
1487
+ (row) => new d.TableRow({
1488
+ children: (row.content ?? []).map((cell) => {
1489
+ const header = cell.type === "tableHeader";
1490
+ const children = blocks(d, cell.content, { bold: header }, state);
1491
+ return new d.TableCell({
1492
+ children: children.length ? children : [new d.Paragraph("")],
1493
+ shading: header ? { type: d.ShadingType.CLEAR, fill: "F3F4F6" } : void 0
1494
+ });
1495
+ })
1496
+ })
1497
+ );
1498
+ if (rows.length === 0) return new d.Paragraph("");
1499
+ return new d.Table({ rows, width: { size: 100, type: d.WidthType.PERCENTAGE } });
1500
+ }
1501
+ function blocks(d, nodes, fmt, state) {
1502
+ const headings = [d.HeadingLevel.HEADING_1, d.HeadingLevel.HEADING_2, d.HeadingLevel.HEADING_3];
1503
+ const out = [];
1504
+ for (const node of nodes ?? []) {
1505
+ switch (node.type) {
1506
+ case "paragraph":
1507
+ out.push(paragraph(d, node, fmt));
1508
+ break;
1509
+ case "heading": {
1510
+ const level = Math.min(Math.max(Number(node.attrs?.level) || 1, 1), 3);
1511
+ out.push(paragraph(d, node, fmt, { heading: headings[level - 1] }));
1512
+ break;
1513
+ }
1514
+ case "bulletList":
1515
+ case "orderedList":
1516
+ out.push(...list(d, node, fmt, 0, state));
1517
+ break;
1518
+ case "blockquote":
1519
+ out.push(...blocks(d, node.content, { ...fmt, quote: true, indent: (fmt.indent ?? 0) + 360 }, state));
1520
+ break;
1521
+ case "codeBlock":
1522
+ out.push(code(d, node));
1523
+ break;
1524
+ case "horizontalRule":
1525
+ out.push(
1526
+ new d.Paragraph({
1527
+ border: { bottom: { color: "9CA3AF", space: 1, style: d.BorderStyle.SINGLE, size: 6 } },
1528
+ spacing: { after: 120 }
1529
+ })
1530
+ );
1531
+ break;
1532
+ case "table":
1533
+ out.push(table(d, node, state));
1534
+ break;
1535
+ default:
1536
+ if (node.content) out.push(...blocks(d, node.content, fmt, state));
1537
+ }
1538
+ }
1539
+ return out;
1540
+ }
1541
+ var exportDocx = async (ctx) => {
1542
+ const d = await import("docx");
1543
+ const state = { instance: 0 };
1544
+ const children = [
1545
+ new d.Paragraph({ text: ctx.title, heading: d.HeadingLevel.TITLE }),
1546
+ ...blocks(d, ctx.json.content, {}, state)
1547
+ ];
1548
+ const doc = new d.Document({
1549
+ title: ctx.title,
1550
+ numbering: {
1551
+ config: [
1552
+ {
1553
+ reference: ORDERED_REF,
1554
+ levels: [0, 1, 2].map((level) => ({
1555
+ level,
1556
+ format: d.LevelFormat.DECIMAL,
1557
+ text: `%${level + 1}.`,
1558
+ alignment: d.AlignmentType.START,
1559
+ style: { paragraph: { indent: { left: 720 * (level + 1), hanging: 360 } } }
1560
+ }))
1561
+ }
1562
+ ]
1563
+ },
1564
+ sections: [{ children }]
1565
+ });
1566
+ const blob = await d.Packer.toBlob(doc);
1567
+ return { filename: `${ctx.basename}.docx`, mimeType: DOCX_MIME, blob };
1568
+ };
1569
+ function printHtml(html) {
1570
+ return new Promise((resolve, reject) => {
1571
+ const frame = document.createElement("iframe");
1572
+ frame.setAttribute("aria-hidden", "true");
1573
+ frame.style.cssText = "position:fixed;right:0;bottom:0;width:0;height:0;border:0;";
1574
+ const cleanup = () => setTimeout(() => frame.remove(), 500);
1575
+ frame.onload = () => {
1576
+ try {
1577
+ const win = frame.contentWindow;
1578
+ if (!win) throw new Error("The print frame is unavailable");
1579
+ win.addEventListener("afterprint", cleanup);
1580
+ win.focus();
1581
+ win.print();
1582
+ setTimeout(cleanup, 12e4);
1583
+ resolve();
1584
+ } catch (error) {
1585
+ cleanup();
1586
+ reject(error);
1587
+ }
1588
+ };
1589
+ frame.srcdoc = html;
1590
+ document.body.appendChild(frame);
1591
+ });
1592
+ }
1593
+ var exportPdf = async (ctx) => {
1594
+ const { docToHtml: docToHtml2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
1595
+ await printHtml(buildHtmlDocument(ctx.title, docToHtml2(ctx.json)));
1596
+ return { filename: `${ctx.basename}.pdf`, mimeType: "application/pdf", handled: true };
1597
+ };
1598
+ var registry = /* @__PURE__ */ new Map([
1599
+ ["html", exportHtml],
1600
+ ["markdown", exportMarkdown],
1601
+ ["txt", exportTxt],
1602
+ ["docx", exportDocx],
1603
+ ["pdf", exportPdf]
1604
+ ]);
1605
+ function registerDocumentExporter(format, exporter) {
1606
+ registry.set(format, exporter);
1607
+ }
1608
+ function listDocumentExporters() {
1609
+ return [...registry.keys()];
1610
+ }
1611
+ async function exportDocument(format, doc) {
1612
+ const exporter = registry.get(format);
1613
+ if (!exporter) throw new JuneError("EXPORT_ERROR", `No exporter is registered for "${format}"`);
1614
+ try {
1615
+ return await exporter({ ...doc, basename: slugify(doc.title) || "document" });
1616
+ } catch (error) {
1617
+ throw toJuneError(error, "EXPORT_ERROR", `Exporting ${format} failed`);
1618
+ }
1619
+ }
1620
+ function downloadResult(result) {
1621
+ if (!result.blob) return;
1622
+ const url = URL.createObjectURL(result.blob);
1623
+ const link = document.createElement("a");
1624
+ link.href = url;
1625
+ link.download = result.filename;
1626
+ document.body.appendChild(link);
1627
+ link.click();
1628
+ link.remove();
1629
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
1630
+ }
1631
+ async function copyDocumentToClipboard(doc) {
1632
+ const { docToHtml: docToHtml2, docToText: docToText2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
1633
+ const html = docToHtml2(doc.json);
1634
+ const text = `${doc.title}
1635
+
1636
+ ${docToText2(doc.json)}`;
1637
+ try {
1638
+ if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
1639
+ await navigator.clipboard.write([
1640
+ new ClipboardItem({
1641
+ "text/html": new Blob([html], { type: "text/html" }),
1642
+ "text/plain": new Blob([text], { type: "text/plain" })
1643
+ })
1644
+ ]);
1645
+ return true;
1646
+ }
1647
+ await navigator.clipboard.writeText(text);
1648
+ return true;
1649
+ } catch {
1650
+ return false;
1651
+ }
1652
+ }
1653
+
1654
+ // src/components/document-editor/document-editor.tsx
1655
+ init_document_extensions();
1656
+
1657
+ // src/components/document-editor/document-merge.ts
1658
+ init_document_utils();
1659
+ var MAX_CELLS = 4e6;
1660
+ function matchIndices(a, b) {
1661
+ const n = a.length;
1662
+ const m = b.length;
1663
+ if (n * m > MAX_CELLS) return null;
1664
+ const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
1665
+ for (let i2 = n - 1; i2 >= 0; i2--) {
1666
+ for (let j2 = m - 1; j2 >= 0; j2--) {
1667
+ dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
1668
+ }
1669
+ }
1670
+ const matches = /* @__PURE__ */ new Map();
1671
+ let i = 0;
1672
+ let j = 0;
1673
+ while (i < n && j < m) {
1674
+ if (a[i] === b[j]) {
1675
+ matches.set(i, j);
1676
+ i++;
1677
+ j++;
1678
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) i++;
1679
+ else j++;
1680
+ }
1681
+ return matches;
1682
+ }
1683
+ var same = (x, y) => x.length === y.length && x.every((v, i) => v === y[i]);
1684
+ function mergeBlocks(base, local, incoming) {
1685
+ if (same(local, base)) return { blocks: incoming, conflicts: 0 };
1686
+ if (same(incoming, base) || same(local, incoming)) return { blocks: local, conflicts: 0 };
1687
+ const ml = matchIndices(base, local);
1688
+ const mn = matchIndices(base, incoming);
1689
+ if (!ml || !mn) return { blocks: null, conflicts: 1 };
1690
+ const anchors = [...ml.keys()].filter((i) => mn.has(i)).sort((x, y) => x - y);
1691
+ const out = [];
1692
+ let conflicts = 0;
1693
+ let pb = -1;
1694
+ let pl = -1;
1695
+ let pn = -1;
1696
+ const region = (bEnd, lEnd, nEnd) => {
1697
+ const B = base.slice(pb + 1, bEnd);
1698
+ const L = local.slice(pl + 1, lEnd);
1699
+ const N = incoming.slice(pn + 1, nEnd);
1700
+ if (B.length === L.length && L.length === N.length) {
1701
+ for (let k = 0; k < B.length; k++) {
1702
+ if (L[k] === B[k]) out.push(N[k]);
1703
+ else if (N[k] === B[k] || N[k] === L[k]) out.push(L[k]);
1704
+ else {
1705
+ conflicts++;
1706
+ out.push(L[k]);
1707
+ }
1708
+ }
1709
+ return;
1710
+ }
1711
+ if (same(L, B)) out.push(...N);
1712
+ else if (same(N, B) || same(L, N)) out.push(...L);
1713
+ else {
1714
+ conflicts++;
1715
+ out.push(...L);
1716
+ }
1717
+ };
1718
+ for (const a of anchors) {
1719
+ const la = ml.get(a);
1720
+ const na = mn.get(a);
1721
+ region(a, la, na);
1722
+ out.push(local[la]);
1723
+ pb = a;
1724
+ pl = la;
1725
+ pn = na;
1726
+ }
1727
+ region(base.length, local.length, incoming.length);
1728
+ return { blocks: out, conflicts };
1729
+ }
1730
+ function mergeDocuments(base, local, incoming) {
1731
+ const toBlocks = (d) => (d.content ?? []).map((node) => JSON.stringify(node));
1732
+ const { blocks: blocks2, conflicts } = mergeBlocks(toBlocks(base), toBlocks(local), toBlocks(incoming));
1733
+ if (!blocks2) return { doc: local, conflicts: Math.max(1, conflicts) };
1734
+ if (blocks2.length === 0) return { doc: emptyDoc(), conflicts };
1735
+ return { doc: { type: "doc", content: blocks2.map((b) => JSON.parse(b)) }, conflicts };
1736
+ }
1737
+
1738
+ // src/components/document-editor/document-store.ts
1739
+ init_document_utils();
1740
+ var incomingKey = (doc) => JSON.stringify([doc.title, docKey(doc.json)]);
1741
+ function createDocumentStore() {
1742
+ const records = /* @__PURE__ */ new Map();
1743
+ const listeners = /* @__PURE__ */ new Set();
1744
+ const emit = () => listeners.forEach((listener) => listener());
1745
+ const put = (record) => {
1746
+ records.set(record.id, record);
1747
+ emit();
1748
+ };
1749
+ const fresh2 = (doc) => ({
1750
+ id: doc.id,
1751
+ title: doc.title,
1752
+ documentType: doc.documentType,
1753
+ status: doc.status,
1754
+ json: doc.json,
1755
+ baseJson: doc.json,
1756
+ baseTitle: doc.title,
1757
+ lastIncomingKey: incomingKey(doc),
1758
+ revision: 0,
1759
+ origin: "external",
1760
+ pending: null,
1761
+ updatedAt: Date.now()
1762
+ });
1763
+ return {
1764
+ get: (id) => records.get(id),
1765
+ list: () => [...records.values()],
1766
+ subscribe: (listener) => {
1767
+ listeners.add(listener);
1768
+ return () => {
1769
+ listeners.delete(listener);
1770
+ };
1771
+ },
1772
+ receive(incoming) {
1773
+ const current = records.get(incoming.id);
1774
+ if (!current) {
1775
+ put(fresh2(incoming));
1776
+ return "created";
1777
+ }
1778
+ const key = incomingKey(incoming);
1779
+ if (current.lastIncomingKey === key) return "unchanged";
1780
+ const userEditedBody = docKey(current.json) !== docKey(current.baseJson);
1781
+ const userEditedTitle = current.title !== current.baseTitle;
1782
+ const title = userEditedTitle ? current.title : incoming.title;
1783
+ const meta = {
1784
+ documentType: incoming.documentType ?? current.documentType,
1785
+ status: incoming.status ?? current.status
1786
+ };
1787
+ if (!userEditedBody) {
1788
+ put({
1789
+ ...current,
1790
+ ...meta,
1791
+ title,
1792
+ baseTitle: incoming.title,
1793
+ json: incoming.json,
1794
+ baseJson: incoming.json,
1795
+ lastIncomingKey: key,
1796
+ revision: current.revision + 1,
1797
+ origin: "external",
1798
+ pending: null,
1799
+ updatedAt: Date.now()
1800
+ });
1801
+ return "applied";
1802
+ }
1803
+ const { doc, conflicts } = mergeDocuments(current.baseJson, current.json, incoming.json);
1804
+ if (conflicts === 0) {
1805
+ put({
1806
+ ...current,
1807
+ ...meta,
1808
+ title,
1809
+ baseTitle: incoming.title,
1810
+ json: doc,
1811
+ baseJson: incoming.json,
1812
+ lastIncomingKey: key,
1813
+ revision: current.revision + 1,
1814
+ origin: "external",
1815
+ pending: null,
1816
+ updatedAt: Date.now()
1817
+ });
1818
+ return "merged";
1819
+ }
1820
+ put({
1821
+ ...current,
1822
+ ...meta,
1823
+ lastIncomingKey: key,
1824
+ pending: { json: incoming.json, title: incoming.title, merged: doc, conflicts }
1825
+ });
1826
+ return "conflict";
1827
+ },
1828
+ hydrate(doc) {
1829
+ put(fresh2(doc));
1830
+ },
1831
+ edit(id, json, origin) {
1832
+ const current = records.get(id);
1833
+ if (!current || docKey(current.json) === docKey(json)) return;
1834
+ put({ ...current, json, origin, revision: current.revision + 1, updatedAt: Date.now() });
1835
+ },
1836
+ setTitle(id, title) {
1837
+ const current = records.get(id);
1838
+ if (!current || current.title === title) return;
1839
+ put({ ...current, title, updatedAt: Date.now() });
1840
+ },
1841
+ resolve(id, choice) {
1842
+ const current = records.get(id);
1843
+ const pending = current?.pending;
1844
+ if (!current || !pending) return;
1845
+ if (choice === "keep") {
1846
+ put({ ...current, baseJson: pending.json, baseTitle: pending.title, pending: null });
1847
+ return;
1848
+ }
1849
+ const json = choice === "merge" ? pending.merged : pending.json;
1850
+ const title = choice === "merge" ? current.title : pending.title;
1851
+ put({
1852
+ ...current,
1853
+ title,
1854
+ json,
1855
+ baseJson: pending.json,
1856
+ baseTitle: pending.title,
1857
+ revision: current.revision + 1,
1858
+ origin: "external",
1859
+ pending: null,
1860
+ updatedAt: Date.now()
1861
+ });
1862
+ },
1863
+ remove(id) {
1864
+ if (records.delete(id)) emit();
1865
+ }
1866
+ };
1867
+ }
1868
+ var defaultStore = null;
1869
+ function getDefaultDocumentStore() {
1870
+ if (!defaultStore) defaultStore = createDocumentStore();
1871
+ return defaultStore;
1872
+ }
1873
+
1874
+ // src/components/document-editor/document-title.tsx
1875
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1876
+ function DocumentTitle({ value, disabled, onChange }) {
1877
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1878
+ "input",
1879
+ {
1880
+ className: "june-doc__title",
1881
+ "aria-label": "Document title",
1882
+ value,
1883
+ disabled,
1884
+ placeholder: "Untitled document",
1885
+ onChange: (e) => onChange(e.target.value)
1886
+ }
1887
+ );
1888
+ }
1889
+
1890
+ // src/components/document-editor/document-toolbar.tsx
1891
+ var import_react8 = require("@tiptap/react");
1892
+ var import_react9 = require("react");
1893
+ init_document_utils();
1894
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1895
+ var FORMAT_LABELS = {
1896
+ html: "HTML",
1897
+ markdown: "Markdown",
1898
+ txt: "Plain text",
1899
+ docx: "Word (.docx)",
1900
+ pdf: "PDF (print)"
1901
+ };
1902
+ function Btn({ label, active, disabled, toggle, onClick, children }) {
1903
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1904
+ "button",
1905
+ {
1906
+ type: "button",
1907
+ className: "june-doc__btn",
1908
+ "aria-label": label,
1909
+ title: label,
1910
+ "aria-pressed": toggle ? !!active : void 0,
1911
+ "data-active": active || void 0,
1912
+ disabled,
1913
+ onMouseDown: (e) => e.preventDefault(),
1914
+ onClick,
1915
+ children
1916
+ }
1917
+ );
1918
+ }
1919
+ function DocumentToolbar(props) {
1920
+ const { editor, editable, exportFormats, canSave, saveState, onExport, onCopy, onSave } = props;
1921
+ const [linkOpen, setLinkOpen] = (0, import_react9.useState)(false);
1922
+ const [linkValue, setLinkValue] = (0, import_react9.useState)("");
1923
+ const s = (0, import_react8.useEditorState)({
1924
+ editor,
1925
+ selector: ({ editor: ed }) => ed ? {
1926
+ bold: ed.isActive("bold"),
1927
+ italic: ed.isActive("italic"),
1928
+ underline: ed.isActive("underline"),
1929
+ strike: ed.isActive("strike"),
1930
+ bullet: ed.isActive("bulletList"),
1931
+ ordered: ed.isActive("orderedList"),
1932
+ quote: ed.isActive("blockquote"),
1933
+ link: ed.isActive("link"),
1934
+ linkHref: ed.getAttributes("link").href ?? "",
1935
+ h1: ed.isActive("heading", { level: 1 }),
1936
+ h2: ed.isActive("heading", { level: 2 }),
1937
+ h3: ed.isActive("heading", { level: 3 }),
1938
+ left: ed.isActive({ textAlign: "left" }),
1939
+ center: ed.isActive({ textAlign: "center" }),
1940
+ right: ed.isActive({ textAlign: "right" }),
1941
+ justify: ed.isActive({ textAlign: "justify" }),
1942
+ inTable: ed.isActive("table"),
1943
+ canUndo: ed.can().undo(),
1944
+ canRedo: ed.can().redo()
1945
+ } : null
1946
+ });
1947
+ const run = () => editor?.chain().focus();
1948
+ const off = !editor || !editable;
1949
+ const heading = s?.h1 ? "1" : s?.h2 ? "2" : s?.h3 ? "3" : "p";
1950
+ const openLink = () => {
1951
+ setLinkValue(s?.linkHref ?? "");
1952
+ setLinkOpen(true);
1953
+ };
1954
+ const applyLink = () => {
1955
+ const href = sanitizeHref(linkValue);
1956
+ if (href) run()?.extendMarkRange("link").setLink({ href }).run();
1957
+ setLinkOpen(false);
1958
+ };
1959
+ const removeLink = () => {
1960
+ run()?.extendMarkRange("link").unsetLink().run();
1961
+ setLinkOpen(false);
1962
+ };
1963
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__toolbar", role: "toolbar", "aria-label": "Document formatting", children: [
1964
+ editable ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1965
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
1966
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Undo", disabled: off || !s?.canUndo, onClick: () => run()?.undo().run(), children: "\u21B6" }),
1967
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Redo", disabled: off || !s?.canRedo, onClick: () => run()?.redo().run(), children: "\u21B7" })
1968
+ ] }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "june-doc__group", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1970
+ "select",
1971
+ {
1972
+ className: "june-doc__select",
1973
+ "aria-label": "Text style",
1974
+ disabled: off,
1975
+ value: heading,
1976
+ onChange: (e) => {
1977
+ const v = e.target.value;
1978
+ if (v === "p") run()?.setParagraph().run();
1979
+ else run()?.toggleHeading({ level: Number(v) }).run();
1980
+ },
1981
+ children: [
1982
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "p", children: "Paragraph" }),
1983
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "1", children: "Heading 1" }),
1984
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "2", children: "Heading 2" }),
1985
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "3", children: "Heading 3" })
1986
+ ]
1987
+ }
1988
+ ) }),
1989
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
1990
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Bold", toggle: true, active: s?.bold, disabled: off, onClick: () => run()?.toggleBold().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("b", { children: "B" }) }),
1991
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Italic", toggle: true, active: s?.italic, disabled: off, onClick: () => run()?.toggleItalic().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("i", { children: "I" }) }),
1992
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Underline", toggle: true, active: s?.underline, disabled: off, onClick: () => run()?.toggleUnderline().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("u", { children: "U" }) }),
1993
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Strikethrough", toggle: true, active: s?.strike, disabled: off, onClick: () => run()?.toggleStrike().run(), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("s", { children: "S" }) })
1994
+ ] }),
1995
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
1996
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Bullet list", toggle: true, active: s?.bullet, disabled: off, onClick: () => run()?.toggleBulletList().run(), children: "\u2022 List" }),
1997
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Numbered list", toggle: true, active: s?.ordered, disabled: off, onClick: () => run()?.toggleOrderedList().run(), children: "1. List" }),
1998
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Blockquote", toggle: true, active: s?.quote, disabled: off, onClick: () => run()?.toggleBlockquote().run(), children: "Quote" }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Link", toggle: true, active: s?.link, disabled: off, onClick: openLink, children: "Link" })
2000
+ ] }),
2001
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
2002
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Align left", toggle: true, active: s?.left, disabled: off, onClick: () => run()?.setTextAlign("left").run(), children: "L" }),
2003
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Align center", toggle: true, active: s?.center, disabled: off, onClick: () => run()?.setTextAlign("center").run(), children: "C" }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Align right", toggle: true, active: s?.right, disabled: off, onClick: () => run()?.setTextAlign("right").run(), children: "R" }),
2005
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Justify", toggle: true, active: s?.justify, disabled: off, onClick: () => run()?.setTextAlign("justify").run(), children: "J" })
2006
+ ] }),
2007
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group", children: [
2008
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Insert table", disabled: off, onClick: () => run()?.insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), children: "Table" }),
2009
+ s?.inTable ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
2010
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Add row below", disabled: off, onClick: () => run()?.addRowAfter().run(), children: "+Row" }),
2011
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Add column right", disabled: off, onClick: () => run()?.addColumnAfter().run(), children: "+Col" }),
2012
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Delete row", disabled: off, onClick: () => run()?.deleteRow().run(), children: "\u2212Row" }),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Delete column", disabled: off, onClick: () => run()?.deleteColumn().run(), children: "\u2212Col" }),
2014
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Delete table", disabled: off, onClick: () => run()?.deleteTable().run(), children: "Del table" })
2015
+ ] }) : null
2016
+ ] })
2017
+ ] }) : null,
2018
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__group june-doc__group--end", children: [
2019
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Copy document", onClick: onCopy, children: "Copy" }),
2020
+ exportFormats.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2021
+ "select",
2022
+ {
2023
+ className: "june-doc__select",
2024
+ "aria-label": "Export document",
2025
+ value: "",
2026
+ onChange: (e) => e.target.value && onExport(e.target.value),
2027
+ children: [
2028
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: "", children: "Export\u2026" }),
2029
+ exportFormats.map((f) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("option", { value: f, children: FORMAT_LABELS[f] ?? f }, f))
2030
+ ]
2031
+ }
2032
+ ) : null,
2033
+ canSave ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Btn, { label: "Save", disabled: saveState === "saving" || saveState === "saved" || saveState === "idle", onClick: onSave, children: "Save" }) : null
2034
+ ] }),
2035
+ linkOpen ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "june-doc__linkbar", children: [
2036
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2037
+ "input",
2038
+ {
2039
+ "aria-label": "Link address",
2040
+ className: "june-doc__input",
2041
+ placeholder: "https://example.com",
2042
+ value: linkValue,
2043
+ autoFocus: true,
2044
+ onChange: (e) => setLinkValue(e.target.value),
2045
+ onKeyDown: (e) => {
2046
+ if (e.key === "Enter") applyLink();
2047
+ if (e.key === "Escape") setLinkOpen(false);
2048
+ }
2049
+ }
2050
+ ),
2051
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "june-doc__btn", onClick: applyLink, children: "Apply" }),
2052
+ s?.link ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "june-doc__btn", onClick: removeLink, children: "Remove" }) : null,
2053
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => setLinkOpen(false), children: "Cancel" })
2054
+ ] }) : null
2055
+ ] });
2056
+ }
2057
+
2058
+ // src/components/document-editor/document-types.ts
2059
+ var ALL_EXPORT_FORMATS = [
2060
+ "html",
2061
+ "markdown",
2062
+ "txt",
2063
+ "docx",
2064
+ "pdf"
2065
+ ];
2066
+
2067
+ // src/components/document-editor/document-editor.tsx
2068
+ init_document_utils();
2069
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2070
+ var snapshotOf = (r) => ({
2071
+ documentId: r.id,
2072
+ title: r.title,
2073
+ documentType: r.documentType,
2074
+ status: r.status,
2075
+ json: r.json
2076
+ });
2077
+ var SAVE_LABEL = {
2078
+ idle: "",
2079
+ dirty: "Unsaved changes",
2080
+ saving: "Saving\u2026",
2081
+ saved: "Saved",
2082
+ error: "Save failed"
2083
+ };
2084
+ function DocumentEditorImpl(props) {
2085
+ const {
2086
+ documentId,
2087
+ title: titleProp = "Untitled document",
2088
+ content,
2089
+ documentType,
2090
+ status = "draft",
2091
+ editable = true,
2092
+ exportFormats,
2093
+ theme = "auto",
2094
+ className
2095
+ } = props;
2096
+ const ctxOptions = useDocumentOptions();
2097
+ const options = (0, import_react11.useMemo)(() => ({ ...ctxOptions, ...props.options }), [ctxOptions, props.options]);
2098
+ const optionsRef = (0, import_react11.useRef)(options);
2099
+ optionsRef.current = options;
2100
+ const store = (0, import_react11.useMemo)(() => options.store ?? getDefaultDocumentStore(), [options.store]);
2101
+ const instanceId = (0, import_react11.useId)();
2102
+ const { theme: contextTheme } = useJuneTheme();
2103
+ const resolvedTheme = theme === "auto" ? contextTheme : theme;
2104
+ const contentKey = typeof content === "string" ? content : content ? JSON.stringify(content) : "";
2105
+ const parsed = (0, import_react11.useMemo)(() => {
2106
+ if (!contentKey) return { json: null, error: null };
2107
+ try {
2108
+ return { json: parseDocumentContent(contentKey), error: null };
2109
+ } catch (e) {
2110
+ return { json: null, error: toJuneError(e, "DOCUMENT_ERROR") };
2111
+ }
2112
+ }, [contentKey]);
2113
+ (0, import_react11.useEffect)(() => {
2114
+ if (parsed.error) optionsRef.current.onError?.(parsed.error);
2115
+ }, [parsed.error]);
2116
+ const title = useDocumentValue(store, documentId, (r) => r?.title ?? titleProp);
2117
+ const pending = useDocumentValue(store, documentId, (r) => r?.pending ?? null);
2118
+ const words = useDocumentValue(store, documentId, (r) => r ? wordCountOf(r.json) : 0);
2119
+ const recordStatus = useDocumentValue(store, documentId, (r) => r?.status ?? status);
2120
+ const [saveState, setSaveState] = (0, import_react11.useState)("idle");
2121
+ const [notice, setNotice] = (0, import_react11.useState)(null);
2122
+ const lastSavedKey = (0, import_react11.useRef)("");
2123
+ const seenRevision = (0, import_react11.useRef)(store.get(documentId)?.revision ?? 0);
2124
+ const readyRef = (0, import_react11.useRef)(false);
2125
+ const flash = (0, import_react11.useCallback)((message) => {
2126
+ setNotice(message);
2127
+ }, []);
2128
+ (0, import_react11.useEffect)(() => {
2129
+ if (!notice) return;
2130
+ const t = setTimeout(() => setNotice(null), 3e3);
2131
+ return () => clearTimeout(t);
2132
+ }, [notice]);
2133
+ const emitChange = (0, import_react11.useCallback)(
2134
+ (source) => {
2135
+ const record = store.get(documentId);
2136
+ if (record) optionsRef.current.onDocumentChange?.({ ...snapshotOf(record), source });
2137
+ },
2138
+ [store, documentId]
2139
+ );
2140
+ const saveRef = (0, import_react11.useRef)(async () => {
2141
+ });
2142
+ const autosave = (0, import_react11.useMemo)(() => {
2143
+ const delay = options.autosaveDelay ?? 1500;
2144
+ return delay > 0 ? debounce(() => void saveRef.current(), delay) : null;
2145
+ }, [options.autosaveDelay]);
2146
+ (0, import_react11.useEffect)(() => () => autosave?.flush(), [autosave]);
2147
+ saveRef.current = async () => {
2148
+ const record = store.get(documentId);
2149
+ const onSave = optionsRef.current.onSave;
2150
+ if (!record || !onSave) return;
2151
+ const key = JSON.stringify([record.title, docKey(record.json)]);
2152
+ if (key === lastSavedKey.current) {
2153
+ setSaveState("saved");
2154
+ return;
2155
+ }
2156
+ setSaveState("saving");
2157
+ try {
2158
+ await onSave({
2159
+ ...snapshotOf(record),
2160
+ html: docToHtml(record.json),
2161
+ markdown: docToMarkdown(record.json)
2162
+ });
2163
+ lastSavedKey.current = key;
2164
+ const latest = store.get(documentId);
2165
+ const latestKey = latest ? JSON.stringify([latest.title, docKey(latest.json)]) : key;
2166
+ setSaveState(latestKey === key ? "saved" : "dirty");
2167
+ } catch (error) {
2168
+ setSaveState("error");
2169
+ optionsRef.current.onError?.(toJuneError(error, "DOCUMENT_ERROR", "Saving the document failed"));
2170
+ }
2171
+ };
2172
+ const markDirty = (0, import_react11.useCallback)(() => {
2173
+ if (!optionsRef.current.onSave) return;
2174
+ setSaveState("dirty");
2175
+ autosave?.();
2176
+ }, [autosave]);
2177
+ const [initialJson] = (0, import_react11.useState)(() => store.get(documentId)?.json ?? parsed.json ?? emptyDoc());
2178
+ const extensions = (0, import_react11.useMemo)(() => createDocumentExtensions(), []);
2179
+ const onUpdateRef = (0, import_react11.useRef)(() => {
2180
+ });
2181
+ onUpdateRef.current = (json) => {
2182
+ store.edit(documentId, json, instanceId);
2183
+ emitChange("user");
2184
+ markDirty();
2185
+ };
2186
+ const editor = (0, import_react10.useEditor)({
2187
+ extensions,
2188
+ content: initialJson,
2189
+ editable,
2190
+ immediatelyRender: false,
2191
+ editorProps: {
2192
+ attributes: {
2193
+ class: "june-doc__prose",
2194
+ role: "textbox",
2195
+ "aria-multiline": "true",
2196
+ "aria-label": "Document content"
2197
+ }
2198
+ },
2199
+ onUpdate: ({ editor: ed }) => onUpdateRef.current(ed.getJSON())
2200
+ });
2201
+ (0, import_react11.useEffect)(() => {
2202
+ editor?.setEditable(editable);
2203
+ }, [editor, editable]);
2204
+ (0, import_react11.useEffect)(() => {
2205
+ if (!parsed.json) return;
2206
+ const outcome = store.receive({
2207
+ id: documentId,
2208
+ title: titleProp,
2209
+ json: parsed.json,
2210
+ documentType,
2211
+ status
2212
+ });
2213
+ if (outcome === "applied" || outcome === "merged") {
2214
+ emitChange(outcome === "applied" ? "ai" : "merge");
2215
+ markDirty();
2216
+ } else if (outcome === "conflict") {
2217
+ const record = store.get(documentId);
2218
+ optionsRef.current.onConflict?.({
2219
+ documentId,
2220
+ title: record?.title ?? titleProp,
2221
+ conflicts: record?.pending?.conflicts ?? 1
2222
+ });
2223
+ }
2224
+ }, [store, documentId, titleProp, parsed.json, documentType, status, emitChange, markDirty]);
2225
+ (0, import_react11.useEffect)(() => {
2226
+ if (!editor) return;
2227
+ const apply = () => {
2228
+ const record = store.get(documentId);
2229
+ if (!record || record.revision === seenRevision.current) return;
2230
+ seenRevision.current = record.revision;
2231
+ if (record.origin === instanceId) return;
2232
+ const { from, to } = editor.state.selection;
2233
+ editor.commands.setContent(record.json, { emitUpdate: false });
2234
+ if (editor.isFocused) {
2235
+ const size = editor.state.doc.content.size;
2236
+ editor.commands.setTextSelection({ from: Math.min(from, size), to: Math.min(to, size) });
2237
+ }
2238
+ };
2239
+ apply();
2240
+ return store.subscribe(apply);
2241
+ }, [editor, store, documentId, instanceId]);
2242
+ (0, import_react11.useEffect)(() => {
2243
+ if (!editor || readyRef.current) return;
2244
+ const record = store.get(documentId);
2245
+ if (!record) return;
2246
+ readyRef.current = true;
2247
+ lastSavedKey.current = JSON.stringify([record.title, docKey(record.json)]);
2248
+ optionsRef.current.onDocumentReady?.(snapshotOf(record));
2249
+ }, [editor, store, documentId, title]);
2250
+ const handleTitle = (value) => {
2251
+ store.setTitle(documentId, value);
2252
+ emitChange("user");
2253
+ markDirty();
2254
+ };
2255
+ const resolve = (choice) => {
2256
+ store.resolve(documentId, choice);
2257
+ if (choice !== "keep") {
2258
+ emitChange(choice === "merge" ? "merge" : "resolve");
2259
+ markDirty();
2260
+ }
2261
+ };
2262
+ const handleExport = async (format) => {
2263
+ const record = store.get(documentId);
2264
+ if (!record) return;
2265
+ try {
2266
+ const result = await exportDocument(format, {
2267
+ documentId,
2268
+ title: record.title,
2269
+ json: record.json
2270
+ // always the latest editor content
2271
+ });
2272
+ if (result.blob) downloadResult(result);
2273
+ optionsRef.current.onExport?.({ documentId, format, ...result });
2274
+ } catch (error) {
2275
+ optionsRef.current.onError?.(toJuneError(error, "EXPORT_ERROR"));
2276
+ flash("Export failed");
2277
+ }
2278
+ };
2279
+ const handleCopy = async () => {
2280
+ const record = store.get(documentId);
2281
+ if (!record) return;
2282
+ flash(await copyDocumentToClipboard({ title: record.title, json: record.json }) ? "Copied" : "Copy failed");
2283
+ };
2284
+ const formats = (exportFormats ?? [...ALL_EXPORT_FORMATS]).filter((f) => listDocumentExporters().includes(f));
2285
+ if (parsed.error && !store.get(documentId)) {
2286
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc june-doc--error", "data-theme": resolvedTheme, role: "alert", children: [
2287
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "june-doc__draft-text", children: "This document could not be displayed." }),
2288
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("details", { children: [
2289
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("summary", { children: "Show raw content" }),
2290
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("pre", { className: "june-doc__raw", children: contentKey })
2291
+ ] })
2292
+ ] });
2293
+ }
2294
+ if (!editor) return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocumentDraft, { title, state: "loading" });
2295
+ const isDraft = recordStatus === "draft";
2296
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2297
+ "div",
2298
+ {
2299
+ className: className ? `june-doc ${className}` : "june-doc",
2300
+ "data-theme": resolvedTheme,
2301
+ "data-status": recordStatus,
2302
+ onKeyDown: (e) => {
2303
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s" && options.onSave) {
2304
+ e.preventDefault();
2305
+ void saveRef.current();
2306
+ }
2307
+ },
2308
+ children: [
2309
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc__header", children: [
2310
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocumentTitle, { value: title, disabled: !editable, onChange: handleTitle }),
2311
+ isDraft ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "june-doc__badge", children: "AI draft" }) : null
2312
+ ] }),
2313
+ pending ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc__banner", role: "status", children: [
2314
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
2315
+ "The assistant suggested changes that overlap your edits (",
2316
+ pending.conflicts,
2317
+ " ",
2318
+ pending.conflicts === 1 ? "section" : "sections",
2319
+ ")."
2320
+ ] }),
2321
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "june-doc__banner-actions", children: [
2322
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("merge"), children: "Merge, keep my edits" }),
2323
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("replace"), children: "Use AI version" }),
2324
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: "june-doc__btn", onClick: () => resolve("keep"), children: "Keep mine" })
2325
+ ] })
2326
+ ] }) : null,
2327
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2328
+ DocumentToolbar,
2329
+ {
2330
+ editor,
2331
+ editable,
2332
+ exportFormats: formats,
2333
+ canSave: !!options.onSave,
2334
+ saveState,
2335
+ onExport: (f) => void handleExport(f),
2336
+ onCopy: () => void handleCopy(),
2337
+ onSave: () => void saveRef.current()
2338
+ }
2339
+ ),
2340
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DocumentContent, { editor }),
2341
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "june-doc__footer", "aria-live": "polite", children: [
2342
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
2343
+ words,
2344
+ " words"
2345
+ ] }),
2346
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: notice ?? SAVE_LABEL[saveState] })
2347
+ ] })
2348
+ ]
2349
+ }
2350
+ );
2351
+ }
2352
+
2353
+ // src/components/document-editor/document-component.tsx
2354
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2355
+ 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.';
2356
+ var documentProps = import_v4.z.object({
2357
+ documentId: import_v4.z.string().describe("Stable id. Reuse it when revising the same document."),
2358
+ title: import_v4.z.string().describe("Document title. Do not repeat it as a heading in content."),
2359
+ content: import_v4.z.string().describe("The whole document body as Markdown."),
2360
+ documentType: import_v4.z.string().optional().describe("Kind of document, such as proposal, contract or invoice."),
2361
+ status: import_v4.z.string().optional().describe('Use "draft" for generated documents.'),
2362
+ editable: import_v4.z.boolean().optional().describe("Whether the user can edit. Defaults to true."),
2363
+ exportFormats: import_v4.z.array(import_v4.z.string()).optional().describe("Export formats to offer, such as html, markdown, docx.")
2364
+ });
2365
+ function DocumentEditor(props) {
2366
+ const { isStreaming } = useJuneRuntime();
2367
+ if (props.generating || isStreaming) {
2368
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocumentDraft, { title: props.title, state: "drafting" });
2369
+ }
2370
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DocumentEditorImpl, { ...props });
2371
+ }
2372
+ function OpenUIDocument({ props }) {
2373
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2374
+ DocumentEditor,
2375
+ {
2376
+ documentId: props.documentId,
2377
+ title: props.title,
2378
+ content: props.content,
2379
+ documentType: props.documentType,
2380
+ status: props.status,
2381
+ editable: props.editable,
2382
+ exportFormats: props.exportFormats
2383
+ }
2384
+ );
2385
+ }
2386
+ var JuneDocumentEditorComponent = (0, import_react_lang3.defineComponent)({
2387
+ name: "DocumentEditor",
2388
+ description: DOCUMENT_COMPONENT_DESCRIPTION,
2389
+ props: documentProps,
2390
+ component: OpenUIDocument
2391
+ });
2392
+ function withDocumentEditor(library) {
2393
+ return library.extend({
2394
+ components: {
2395
+ add: [{ component: JuneDocumentEditorComponent, slots: ["Card"] }]
2396
+ }
2397
+ });
2398
+ }
2399
+
2400
+ // src/components/document-editor/document-instructions.ts
2401
+ var DEFAULT_TYPES = [
2402
+ "proposal",
2403
+ "report",
2404
+ "email",
2405
+ "letter",
2406
+ "invoice",
2407
+ "contract",
2408
+ "NDA",
2409
+ "memo",
2410
+ "meeting notes"
2411
+ ];
2412
+ function getDocumentEditorRules(options = {}) {
2413
+ const name = options.componentName ?? "DocumentEditor";
2414
+ const types = (options.documentTypes ?? DEFAULT_TYPES).join(", ");
2415
+ return [
2416
+ `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}.`,
2417
+ `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.`,
2418
+ `${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.`,
2419
+ `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.`,
2420
+ `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.`,
2421
+ `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.`,
2422
+ `Never invent legal, financial or business facts, prices, dates, names or figures. Use placeholders instead. Set status to "draft" for generated documents, and for legal or financial documents say in one short sentence outside the component that the document is a draft that should be reviewed by a professional.`,
2423
+ `You may add one short sentence before or after the component. Do not repeat the document text outside the component.`,
2424
+ ...options.extraRules ?? []
2425
+ ];
2426
+ }
2427
+ function getDocumentEditorInstructions(options = {}) {
2428
+ const rules = getDocumentEditorRules(options);
2429
+ return ["Document drafting:", ...rules.map((rule, i) => `${i + 1}. ${rule}`)].join("\n");
2430
+ }
2431
+ var EXAMPLE_NEW = 'root = Stack([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |", "proposal", "draft")';
2432
+ var EXAMPLE_EDIT = 'root = Stack([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |\\n| Delivery | [AMOUNT] |", "proposal", "draft")';
2433
+ function documentPromptOptions(options = {}) {
2434
+ return {
2435
+ additionalRules: getDocumentEditorRules(options),
2436
+ examples: [EXAMPLE_NEW, EXAMPLE_EDIT]
2437
+ };
2438
+ }
2439
+
2440
+ // src/components/document-editor/document-agent.ts
2441
+ var attr = (value) => value.replace(/"/g, "&quot;");
2442
+ async function serializeDocumentsForAgent(options = {}) {
2443
+ const store = options.store ?? getDefaultDocumentStore();
2444
+ const { docToMarkdown: docToMarkdown2 } = await Promise.resolve().then(() => (init_document_engine(), document_engine_exports));
2445
+ return store.list().filter((r) => !options.ids || options.ids.includes(r.id)).map(
2446
+ (r) => `<document id="${attr(r.id)}" title="${attr(r.title)}"${r.documentType ? ` type="${attr(r.documentType)}"` : ""}>
2447
+ ${docToMarkdown2(r.json)}
2448
+ </document>`
2449
+ ).join("\n\n");
2450
+ }
2451
+
2452
+ // src/components/june-library.ts
2453
+ var import_react_ui = require("@openuidev/react-ui");
2454
+ var juneLibrary = withDocumentEditor(import_react_ui.openuiLibrary);
2455
+
2456
+ // src/core/JuneUI.tsx
2457
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2458
+ function JuneUI({ library = juneLibrary, ...props }) {
2459
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(JuneRenderer, { library, ...props });
1105
2460
  }
1106
2461
  // Annotate the CommonJS export names for ESM import in node:
1107
2462
  0 && (module.exports = {
2463
+ ALL_EXPORT_FORMATS,
2464
+ DocumentEditor,
2465
+ JuneDocumentContext,
2466
+ JuneDocumentEditorComponent,
1108
2467
  JuneError,
1109
2468
  JuneErrorBoundary,
1110
2469
  JuneRenderer,
2470
+ JuneRuntimeContext,
1111
2471
  JuneThemeProvider,
1112
2472
  JuneUI,
1113
2473
  consumeStream,
2474
+ copyDocumentToClipboard,
1114
2475
  createActionHandler,
2476
+ createDocumentStore,
1115
2477
  createJuneLibrary,
1116
2478
  darkTheme,
1117
2479
  defaultParseActionEvent,
1118
2480
  defineJuneAction,
1119
2481
  defineJuneComponent,
2482
+ documentPromptOptions,
2483
+ downloadResult,
2484
+ exportDocument,
1120
2485
  fetchStream,
2486
+ getDefaultDocumentStore,
2487
+ getDocumentEditorInstructions,
2488
+ getDocumentEditorRules,
1121
2489
  isJuneError,
1122
2490
  jsonFieldExtractor,
2491
+ juneLibrary,
1123
2492
  lightTheme,
2493
+ listDocumentExporters,
2494
+ mergeDocuments,
2495
+ registerDocumentExporter,
1124
2496
  runAction,
2497
+ serializeDocumentsForAgent,
1125
2498
  sseToText,
1126
2499
  toJuneError,
2500
+ useDocumentOptions,
2501
+ useJuneRuntime,
1127
2502
  useJuneStream,
1128
2503
  useJuneTheme,
1129
- useResolvedTheme
2504
+ useResolvedTheme,
2505
+ withDocumentEditor
1130
2506
  });