@codella-software/react 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useMemo, createContext, useContext, useCallback } from "react";
1
+ import { useState, useEffect, useMemo, createContext, useContext, useCallback, useRef } from "react";
2
2
  import { TableBuilder } from "@codella/core";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { fromEvent, Observable as Observable$1, of, EMPTY, Subject as Subject$1, BehaviorSubject, timer } from "rxjs";
@@ -1629,6 +1629,782 @@ const useLiveUpdates = (eventName) => {
1629
1629
  }, [eventName, service]);
1630
1630
  return { data, error };
1631
1631
  };
1632
+ class ContentModel {
1633
+ constructor(doc) {
1634
+ this.document = doc || this.createEmptyDocument();
1635
+ }
1636
+ /**
1637
+ * Create empty document with single empty paragraph
1638
+ */
1639
+ static createEmpty() {
1640
+ return {
1641
+ type: "document",
1642
+ version: "1.0",
1643
+ children: [
1644
+ {
1645
+ type: "paragraph",
1646
+ children: [{ type: "text", text: "" }]
1647
+ }
1648
+ ]
1649
+ };
1650
+ }
1651
+ /**
1652
+ * Get the current document
1653
+ */
1654
+ getDocument() {
1655
+ return JSON.parse(JSON.stringify(this.document));
1656
+ }
1657
+ /**
1658
+ * Replace entire document
1659
+ */
1660
+ setDocument(doc) {
1661
+ this.document = JSON.parse(JSON.stringify(doc));
1662
+ }
1663
+ /**
1664
+ * Get all text content as plain string
1665
+ */
1666
+ getPlainText() {
1667
+ let text = "";
1668
+ const traverse = (node) => {
1669
+ if (node.type === "text") {
1670
+ text += node.text;
1671
+ } else if ("children" in node) {
1672
+ node.children.forEach(traverse);
1673
+ } else if (node.type === "image") {
1674
+ text += "[Image]";
1675
+ } else if (node.type === "mention") {
1676
+ text += `@${node.label}`;
1677
+ }
1678
+ };
1679
+ this.document.children.forEach(traverse);
1680
+ return text;
1681
+ }
1682
+ /**
1683
+ * Insert text at selection/cursor position
1684
+ */
1685
+ insertText(text, marks) {
1686
+ const doc = this.cloneDocument();
1687
+ const firstPara = this.getFirstParagraph(doc);
1688
+ if (firstPara && firstPara.children.length > 0) {
1689
+ const firstChild = firstPara.children[0];
1690
+ if (firstChild.type === "text") {
1691
+ firstChild.text += text;
1692
+ if (marks) {
1693
+ firstChild.marks = marks;
1694
+ }
1695
+ }
1696
+ }
1697
+ return doc;
1698
+ }
1699
+ /**
1700
+ * Insert a paragraph at the end
1701
+ */
1702
+ insertParagraph(content) {
1703
+ const doc = this.cloneDocument();
1704
+ doc.children.push({
1705
+ type: "paragraph",
1706
+ children: content || [{ type: "text", text: "" }]
1707
+ });
1708
+ return doc;
1709
+ }
1710
+ /**
1711
+ * Insert heading
1712
+ */
1713
+ insertHeading(level, content) {
1714
+ const doc = this.cloneDocument();
1715
+ doc.children.push({
1716
+ type: "heading",
1717
+ level,
1718
+ children: content || [{ type: "text", text: "" }]
1719
+ });
1720
+ return doc;
1721
+ }
1722
+ /**
1723
+ * Insert image
1724
+ */
1725
+ insertImage(url, attrs) {
1726
+ const doc = this.cloneDocument();
1727
+ const lastBlock = doc.children[doc.children.length - 1];
1728
+ if ((attrs == null ? void 0 : attrs.display) === "block") {
1729
+ doc.children.push({
1730
+ type: "paragraph",
1731
+ children: [
1732
+ {
1733
+ type: "image",
1734
+ url,
1735
+ attrs: attrs || {}
1736
+ }
1737
+ ]
1738
+ });
1739
+ } else {
1740
+ if (lastBlock && lastBlock.type === "paragraph") {
1741
+ lastBlock.children.push({
1742
+ type: "image",
1743
+ url,
1744
+ attrs: attrs || {}
1745
+ });
1746
+ } else {
1747
+ doc.children.push({
1748
+ type: "paragraph",
1749
+ children: [
1750
+ {
1751
+ type: "image",
1752
+ url,
1753
+ attrs: attrs || {}
1754
+ }
1755
+ ]
1756
+ });
1757
+ }
1758
+ }
1759
+ return doc;
1760
+ }
1761
+ /**
1762
+ * Insert mention
1763
+ */
1764
+ insertMention(id, label, meta) {
1765
+ const doc = this.cloneDocument();
1766
+ const lastBlock = doc.children[doc.children.length - 1];
1767
+ if (lastBlock && lastBlock.type === "paragraph") {
1768
+ lastBlock.children.push({
1769
+ type: "mention",
1770
+ id,
1771
+ label,
1772
+ meta
1773
+ });
1774
+ }
1775
+ return doc;
1776
+ }
1777
+ /**
1778
+ * Insert list
1779
+ */
1780
+ insertList(listType, items = [[]]) {
1781
+ const doc = this.cloneDocument();
1782
+ const children = items.map((item) => ({
1783
+ type: "list-item",
1784
+ children: item || [{ type: "text", text: "" }],
1785
+ depth: 0
1786
+ }));
1787
+ doc.children.push({
1788
+ type: "list",
1789
+ listType,
1790
+ children
1791
+ });
1792
+ return doc;
1793
+ }
1794
+ /**
1795
+ * Toggle a mark type on text in a range
1796
+ */
1797
+ toggleMark(mark, selection) {
1798
+ var _a;
1799
+ const doc = this.cloneDocument();
1800
+ const firstPara = this.getFirstParagraph(doc);
1801
+ if (firstPara && ((_a = firstPara.children[0]) == null ? void 0 : _a.type) === "text") {
1802
+ const textNode = firstPara.children[0];
1803
+ if (!textNode.marks) {
1804
+ textNode.marks = [];
1805
+ }
1806
+ const existingMark = textNode.marks.findIndex((m) => m.type === mark);
1807
+ if (existingMark >= 0) {
1808
+ textNode.marks.splice(existingMark, 1);
1809
+ } else {
1810
+ textNode.marks.push({ type: mark });
1811
+ }
1812
+ }
1813
+ return doc;
1814
+ }
1815
+ /**
1816
+ * Check if a mark type is active at cursor
1817
+ */
1818
+ isMarkActive(mark) {
1819
+ var _a, _b;
1820
+ const firstPara = this.getFirstParagraph(this.document);
1821
+ if (((_a = firstPara == null ? void 0 : firstPara.children[0]) == null ? void 0 : _a.type) === "text") {
1822
+ const textNode = firstPara.children[0];
1823
+ return ((_b = textNode.marks) == null ? void 0 : _b.some((m) => m.type === mark)) || false;
1824
+ }
1825
+ return false;
1826
+ }
1827
+ /**
1828
+ * Get active marks at cursor
1829
+ */
1830
+ getActiveMarks() {
1831
+ var _a, _b;
1832
+ const firstPara = this.getFirstParagraph(this.document);
1833
+ if (((_a = firstPara == null ? void 0 : firstPara.children[0]) == null ? void 0 : _a.type) === "text") {
1834
+ const textNode = firstPara.children[0];
1835
+ return ((_b = textNode.marks) == null ? void 0 : _b.map((m) => m.type)) || [];
1836
+ }
1837
+ return [];
1838
+ }
1839
+ /**
1840
+ * Delete content (simplified - deletes last block)
1841
+ */
1842
+ deleteContent(selection) {
1843
+ const doc = this.cloneDocument();
1844
+ if (doc.children.length > 1) {
1845
+ doc.children.pop();
1846
+ } else if (doc.children.length === 1 && doc.children[0].type === "paragraph") {
1847
+ doc.children[0].children = [{ type: "text", text: "" }];
1848
+ }
1849
+ return doc;
1850
+ }
1851
+ /**
1852
+ * Replace entire content
1853
+ */
1854
+ replaceContent(nodes) {
1855
+ const doc = this.cloneDocument();
1856
+ doc.children = nodes;
1857
+ return doc;
1858
+ }
1859
+ /**
1860
+ * Clone document (deep copy)
1861
+ */
1862
+ cloneDocument() {
1863
+ return JSON.parse(JSON.stringify(this.document));
1864
+ }
1865
+ /**
1866
+ * Get first paragraph in document
1867
+ */
1868
+ getFirstParagraph(doc) {
1869
+ for (const block of doc.children) {
1870
+ if (block.type === "paragraph") {
1871
+ return block;
1872
+ }
1873
+ }
1874
+ return null;
1875
+ }
1876
+ /**
1877
+ * Find node by predicate
1878
+ */
1879
+ findNode(predicate) {
1880
+ const search = (node) => {
1881
+ if (predicate(node)) {
1882
+ return node;
1883
+ }
1884
+ if ("children" in node) {
1885
+ for (const child of node.children) {
1886
+ const result = search(child);
1887
+ if (result) return result;
1888
+ }
1889
+ }
1890
+ return null;
1891
+ };
1892
+ for (const child of this.document.children) {
1893
+ const result = search(child);
1894
+ if (result) return result;
1895
+ }
1896
+ return null;
1897
+ }
1898
+ /**
1899
+ * Count all nodes of a type
1900
+ */
1901
+ countNodes(type) {
1902
+ let count = 0;
1903
+ const traverse = (node) => {
1904
+ if (node.type === type) count++;
1905
+ if ("children" in node) {
1906
+ node.children.forEach(traverse);
1907
+ }
1908
+ };
1909
+ this.document.children.forEach(traverse);
1910
+ return count;
1911
+ }
1912
+ createEmptyDocument() {
1913
+ return ContentModel.createEmpty();
1914
+ }
1915
+ }
1916
+ class RichContentService {
1917
+ constructor(config2 = {}) {
1918
+ this.history = [];
1919
+ this.historyIndex = -1;
1920
+ this.adapter = null;
1921
+ this.defaultImageHandler = async (file) => {
1922
+ return new Promise((resolve) => {
1923
+ const reader = new FileReader();
1924
+ reader.onload = (e) => {
1925
+ var _a;
1926
+ resolve({ url: (_a = e.target) == null ? void 0 : _a.result });
1927
+ };
1928
+ reader.readAsDataURL(file);
1929
+ });
1930
+ };
1931
+ this.config = {
1932
+ initialContent: config2.initialContent || ContentModel.createEmpty(),
1933
+ allowedMarks: config2.allowedMarks || [
1934
+ "bold",
1935
+ "italic",
1936
+ "underline",
1937
+ "strikethrough",
1938
+ "code"
1939
+ ],
1940
+ allowedBlocks: config2.allowedBlocks || [
1941
+ "document",
1942
+ "paragraph",
1943
+ "heading",
1944
+ "list",
1945
+ "blockquote",
1946
+ "code-block",
1947
+ "horizontal-rule"
1948
+ ],
1949
+ maxListDepth: config2.maxListDepth ?? 3,
1950
+ imageUploadHandler: config2.imageUploadHandler || this.defaultImageHandler,
1951
+ mentionProvider: config2.mentionProvider || (() => Promise.resolve([])),
1952
+ middleware: config2.middleware || {},
1953
+ enableHistory: config2.enableHistory ?? true,
1954
+ maxHistorySteps: config2.maxHistorySteps ?? 100,
1955
+ placeholder: config2.placeholder || "",
1956
+ readOnly: config2.readOnly ?? false
1957
+ };
1958
+ this.middleware = this.config.middleware;
1959
+ this.contentModel = new ContentModel(this.config.initialContent);
1960
+ this.contentSubject = new BehaviorSubject(
1961
+ this.contentModel.getDocument()
1962
+ );
1963
+ this.commandSubject = new Subject$1();
1964
+ this.mentionSubject = new Subject$1();
1965
+ this.stateSubject = new BehaviorSubject({
1966
+ content: this.contentModel.getDocument(),
1967
+ selection: null,
1968
+ canUndo: false,
1969
+ canRedo: false,
1970
+ isFocused: false,
1971
+ isDirty: false,
1972
+ mentions: {
1973
+ isActive: false,
1974
+ query: "",
1975
+ position: { x: 0, y: 0 }
1976
+ },
1977
+ selectedFormats: /* @__PURE__ */ new Set()
1978
+ });
1979
+ if (this.config.enableHistory) {
1980
+ this.history.push({
1981
+ content: this.contentModel.getDocument(),
1982
+ timestamp: Date.now()
1983
+ });
1984
+ this.historyIndex = 0;
1985
+ }
1986
+ this.commandSubject.subscribe((cmd) => this.executeCommand(cmd));
1987
+ }
1988
+ /**
1989
+ * Create service from empty state
1990
+ */
1991
+ static create(config2) {
1992
+ return new RichContentService(config2);
1993
+ }
1994
+ // =========================================================================
1995
+ // PUBLIC OBSERVABLES
1996
+ // =========================================================================
1997
+ /**
1998
+ * Get content$ observable
1999
+ */
2000
+ getContent$() {
2001
+ return this.contentSubject.asObservable();
2002
+ }
2003
+ /**
2004
+ * Get full state$ observable
2005
+ */
2006
+ getState$() {
2007
+ return this.stateSubject.asObservable();
2008
+ }
2009
+ /**
2010
+ * Get canUndo$ observable
2011
+ */
2012
+ getCanUndo$() {
2013
+ return this.stateSubject.pipe(
2014
+ map((s) => s.canUndo),
2015
+ distinctUntilChanged()
2016
+ );
2017
+ }
2018
+ /**
2019
+ * Get canRedo$ observable
2020
+ */
2021
+ getCanRedo$() {
2022
+ return this.stateSubject.pipe(
2023
+ map((s) => s.canRedo),
2024
+ distinctUntilChanged()
2025
+ );
2026
+ }
2027
+ /**
2028
+ * Get isFocused$ observable
2029
+ */
2030
+ getIsFocused$() {
2031
+ return this.stateSubject.pipe(
2032
+ map((s) => s.isFocused),
2033
+ distinctUntilChanged()
2034
+ );
2035
+ }
2036
+ /**
2037
+ * Get mentions$ observable - for autocomplete dropdown
2038
+ */
2039
+ getMentions$() {
2040
+ return this.mentionSubject.asObservable();
2041
+ }
2042
+ /**
2043
+ * Get selectedFormats$ observable
2044
+ */
2045
+ getSelectedFormats$() {
2046
+ return this.stateSubject.pipe(
2047
+ map((s) => s.selectedFormats || /* @__PURE__ */ new Set()),
2048
+ distinctUntilChanged()
2049
+ );
2050
+ }
2051
+ // =========================================================================
2052
+ // STATE GETTERS
2053
+ // =========================================================================
2054
+ getContent() {
2055
+ return this.contentSubject.getValue();
2056
+ }
2057
+ getState() {
2058
+ return this.stateSubject.getValue();
2059
+ }
2060
+ getPlainText() {
2061
+ return this.contentModel.getPlainText();
2062
+ }
2063
+ canUndo() {
2064
+ return this.historyIndex > 0;
2065
+ }
2066
+ canRedo() {
2067
+ return this.historyIndex < this.history.length - 1;
2068
+ }
2069
+ isFocused() {
2070
+ return this.getState().isFocused;
2071
+ }
2072
+ // =========================================================================
2073
+ // ADAPTER MANAGEMENT
2074
+ // =========================================================================
2075
+ /**
2076
+ * Attach DOM adapter for contentEditable syncing
2077
+ */
2078
+ attachAdapter(adapter) {
2079
+ this.adapter = adapter;
2080
+ const docFromDom = adapter.syncFromDOM();
2081
+ this.setContent(docFromDom);
2082
+ }
2083
+ /**
2084
+ * Detach adapter
2085
+ */
2086
+ detachAdapter() {
2087
+ if (this.adapter) {
2088
+ this.adapter.unmount();
2089
+ this.adapter = null;
2090
+ }
2091
+ }
2092
+ /**
2093
+ * Get attached adapter
2094
+ */
2095
+ getAdapter() {
2096
+ return this.adapter;
2097
+ }
2098
+ // =========================================================================
2099
+ // COMMAND EXECUTION
2100
+ // =========================================================================
2101
+ /**
2102
+ * Execute a command
2103
+ */
2104
+ execute(command, payload) {
2105
+ const cmd = typeof command === "string" ? { type: command, payload } : command;
2106
+ this.commandSubject.next(cmd);
2107
+ }
2108
+ /**
2109
+ * Execute text insertion
2110
+ */
2111
+ insertText(text, marks) {
2112
+ this.execute("insertText", { text, marks });
2113
+ }
2114
+ /**
2115
+ * Execute paragraph insertion
2116
+ */
2117
+ insertParagraph() {
2118
+ this.execute("insertParagraph");
2119
+ }
2120
+ /**
2121
+ * Execute heading insertion
2122
+ */
2123
+ insertHeading(level) {
2124
+ this.execute("insertHeading", { level });
2125
+ }
2126
+ /**
2127
+ * Execute image insertion
2128
+ */
2129
+ insertImage(url, attrs) {
2130
+ this.execute("insertImage", { url, attrs });
2131
+ }
2132
+ /**
2133
+ * Execute image upload
2134
+ */
2135
+ async uploadImage(file) {
2136
+ try {
2137
+ const result = await this.config.imageUploadHandler(file);
2138
+ this.insertImage(result.url, result.attrs);
2139
+ } catch (error) {
2140
+ console.error("Image upload failed:", error);
2141
+ throw error;
2142
+ }
2143
+ }
2144
+ /**
2145
+ * Execute mention insertion
2146
+ */
2147
+ insertMention(id, label, meta) {
2148
+ this.execute("insertMention", { id, label, meta });
2149
+ }
2150
+ /**
2151
+ * Execute list insertion
2152
+ */
2153
+ insertList(listType) {
2154
+ this.execute("insertList", { listType });
2155
+ }
2156
+ /**
2157
+ * Execute mark toggle (bold, italic, etc.)
2158
+ */
2159
+ toggleMark(mark) {
2160
+ this.execute("toggleMark", { mark });
2161
+ }
2162
+ /**
2163
+ * Delete content
2164
+ */
2165
+ deleteContent() {
2166
+ this.execute("deleteContent");
2167
+ }
2168
+ /**
2169
+ * Set focus state
2170
+ */
2171
+ setFocus(focused) {
2172
+ const state = this.getState();
2173
+ state.isFocused = focused;
2174
+ this.stateSubject.next(state);
2175
+ }
2176
+ /**
2177
+ * Set selection
2178
+ */
2179
+ setSelection(selection) {
2180
+ const state = this.getState();
2181
+ state.selection = selection;
2182
+ this.stateSubject.next(state);
2183
+ }
2184
+ /**
2185
+ * Set mention query (triggers autocomplete)
2186
+ */
2187
+ setMentionQuery(query, position) {
2188
+ if (query.length > 0) {
2189
+ this.mentionSubject.next({ query, position });
2190
+ }
2191
+ }
2192
+ // =========================================================================
2193
+ // HISTORY (UNDO/REDO)
2194
+ // =========================================================================
2195
+ /**
2196
+ * Undo to previous state
2197
+ */
2198
+ undo() {
2199
+ if (this.canUndo()) {
2200
+ this.historyIndex--;
2201
+ this.applyHistoryState();
2202
+ }
2203
+ }
2204
+ /**
2205
+ * Redo to next state
2206
+ */
2207
+ redo() {
2208
+ if (this.canRedo()) {
2209
+ this.historyIndex++;
2210
+ this.applyHistoryState();
2211
+ }
2212
+ }
2213
+ /**
2214
+ * Clear history
2215
+ */
2216
+ clearHistory() {
2217
+ this.history = [{ content: this.getContent(), timestamp: Date.now() }];
2218
+ this.historyIndex = 0;
2219
+ this.updateHistoryState();
2220
+ }
2221
+ // =========================================================================
2222
+ // PRIVATE METHODS
2223
+ // =========================================================================
2224
+ executeCommand(cmd) {
2225
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
2226
+ if (this.config.readOnly) {
2227
+ console.warn("Editor is in read-only mode");
2228
+ return;
2229
+ }
2230
+ this.getContent();
2231
+ let newContent;
2232
+ switch (cmd.type) {
2233
+ case "insertText":
2234
+ newContent = this.contentModel.insertText((_a = cmd.payload) == null ? void 0 : _a.text, (_b = cmd.payload) == null ? void 0 : _b.marks);
2235
+ break;
2236
+ case "insertParagraph":
2237
+ newContent = this.contentModel.insertParagraph((_c = cmd.payload) == null ? void 0 : _c.children);
2238
+ break;
2239
+ case "insertHeading":
2240
+ newContent = this.contentModel.insertHeading(
2241
+ ((_d = cmd.payload) == null ? void 0 : _d.level) || 1,
2242
+ (_e = cmd.payload) == null ? void 0 : _e.children
2243
+ );
2244
+ break;
2245
+ case "insertImage":
2246
+ newContent = this.contentModel.insertImage((_f = cmd.payload) == null ? void 0 : _f.url, (_g = cmd.payload) == null ? void 0 : _g.attrs);
2247
+ break;
2248
+ case "insertMention":
2249
+ newContent = this.contentModel.insertMention(
2250
+ (_h = cmd.payload) == null ? void 0 : _h.id,
2251
+ (_i = cmd.payload) == null ? void 0 : _i.label,
2252
+ (_j = cmd.payload) == null ? void 0 : _j.meta
2253
+ );
2254
+ break;
2255
+ case "insertList":
2256
+ newContent = this.contentModel.insertList((_k = cmd.payload) == null ? void 0 : _k.listType, (_l = cmd.payload) == null ? void 0 : _l.items);
2257
+ break;
2258
+ case "toggleMark":
2259
+ newContent = this.contentModel.toggleMark((_m = cmd.payload) == null ? void 0 : _m.mark, (_n = cmd.payload) == null ? void 0 : _n.selection);
2260
+ break;
2261
+ case "deleteContent":
2262
+ newContent = this.contentModel.deleteContent((_o = cmd.payload) == null ? void 0 : _o.selection);
2263
+ break;
2264
+ case "replaceContent":
2265
+ newContent = this.contentModel.replaceContent((_p = cmd.payload) == null ? void 0 : _p.nodes);
2266
+ break;
2267
+ default:
2268
+ console.warn(`Unknown command type: ${cmd.type}`);
2269
+ return;
2270
+ }
2271
+ if (this.config.enableHistory) {
2272
+ this.history = this.history.slice(0, this.historyIndex + 1);
2273
+ this.history.push({
2274
+ content: newContent,
2275
+ timestamp: Date.now()
2276
+ });
2277
+ if (this.history.length > this.config.maxHistorySteps) {
2278
+ this.history.shift();
2279
+ } else {
2280
+ this.historyIndex++;
2281
+ }
2282
+ }
2283
+ this.setContent(newContent);
2284
+ }
2285
+ setContent(doc) {
2286
+ this.contentModel.setDocument(doc);
2287
+ this.contentSubject.next(doc);
2288
+ const state = this.getState();
2289
+ state.content = doc;
2290
+ state.isDirty = true;
2291
+ state.selectedFormats = new Set(this.contentModel.getActiveMarks());
2292
+ this.stateSubject.next(state);
2293
+ if (this.adapter) {
2294
+ this.adapter.syncToDOM(doc);
2295
+ }
2296
+ this.updateHistoryState();
2297
+ }
2298
+ applyHistoryState() {
2299
+ const entry = this.history[this.historyIndex];
2300
+ this.contentModel.setDocument(entry.content);
2301
+ this.contentSubject.next(entry.content);
2302
+ const state = this.getState();
2303
+ state.content = entry.content;
2304
+ this.stateSubject.next(state);
2305
+ if (this.adapter) {
2306
+ this.adapter.syncToDOM(entry.content);
2307
+ }
2308
+ this.updateHistoryState();
2309
+ }
2310
+ updateHistoryState() {
2311
+ const state = this.getState();
2312
+ state.canUndo = this.canUndo();
2313
+ state.canRedo = this.canRedo();
2314
+ this.stateSubject.next(state);
2315
+ }
2316
+ }
2317
+ function useRichContent(options = {}) {
2318
+ const service = useMemo(() => RichContentService.create(options), []);
2319
+ const [content, setContent] = useState(service.getContent());
2320
+ const [state, setState] = useState(service.getState());
2321
+ const [isFocused, setIsFocused] = useState(false);
2322
+ const [canUndo, setCanUndo] = useState(false);
2323
+ const [canRedo, setCanRedo] = useState(false);
2324
+ const [selectedFormats, setSelectedFormats] = useState(/* @__PURE__ */ new Set());
2325
+ useEffect(() => {
2326
+ const contentSub = service.getContent$().subscribe(setContent);
2327
+ const stateSub = service.getState$().subscribe((newState) => {
2328
+ setState(newState);
2329
+ setIsFocused(newState.isFocused);
2330
+ setCanUndo(newState.canUndo);
2331
+ setCanRedo(newState.canRedo);
2332
+ setSelectedFormats(newState.selectedFormats || /* @__PURE__ */ new Set());
2333
+ });
2334
+ return () => {
2335
+ contentSub.unsubscribe();
2336
+ stateSub.unsubscribe();
2337
+ };
2338
+ }, [service]);
2339
+ useEffect(() => {
2340
+ if (options.adapter) {
2341
+ service.attachAdapter(options.adapter);
2342
+ return () => service.detachAdapter();
2343
+ }
2344
+ }, [options.adapter, service]);
2345
+ const defaultAdapterRef = useRef(null);
2346
+ useEffect(() => {
2347
+ var _a;
2348
+ if (((_a = options.editorRef) == null ? void 0 : _a.current) && !options.adapter && !defaultAdapterRef.current) {
2349
+ const { DefaultContentEditableAdapter } = require("@codella/core/rich-content");
2350
+ const adapter = new DefaultContentEditableAdapter();
2351
+ adapter.mount(options.editorRef.current);
2352
+ service.attachAdapter(adapter);
2353
+ defaultAdapterRef.current = adapter;
2354
+ return () => {
2355
+ adapter.unmount();
2356
+ service.detachAdapter();
2357
+ defaultAdapterRef.current = null;
2358
+ };
2359
+ }
2360
+ }, [options.editorRef, options.adapter, service]);
2361
+ const insertText = useCallback((text) => service.insertText(text), [service]);
2362
+ const insertParagraph = useCallback(() => service.insertParagraph(), [service]);
2363
+ const insertHeading = useCallback((level) => service.insertHeading(level), [service]);
2364
+ const insertImage = useCallback((url) => service.insertImage(url), [service]);
2365
+ const uploadImage = useCallback((file) => service.uploadImage(file), [service]);
2366
+ const insertMention = useCallback((id, label) => service.insertMention(id, label), [service]);
2367
+ const insertList = useCallback((type) => service.insertList(type), [service]);
2368
+ const toggleMark = useCallback((mark) => service.toggleMark(mark), [service]);
2369
+ const deleteContent = useCallback(() => service.deleteContent(), [service]);
2370
+ const undo = useCallback(() => service.undo(), [service]);
2371
+ const redo = useCallback(() => service.redo(), [service]);
2372
+ const clearHistory = useCallback(() => service.clearHistory(), [service]);
2373
+ const focus = useCallback(() => {
2374
+ var _a;
2375
+ return (_a = service.getAdapter()) == null ? void 0 : _a.focus();
2376
+ }, [service]);
2377
+ const setFocus = useCallback((focused) => service.setFocus(focused), [service]);
2378
+ const getPlainText = useCallback(() => service.getPlainText(), [service]);
2379
+ const setSelection = useCallback((sel) => service.setSelection(sel), [service]);
2380
+ return {
2381
+ service,
2382
+ content,
2383
+ state,
2384
+ isFocused,
2385
+ canUndo,
2386
+ canRedo,
2387
+ selectedFormats,
2388
+ selection: state.selection || null,
2389
+ isDirty: state.isDirty,
2390
+ insertText,
2391
+ insertParagraph,
2392
+ insertHeading,
2393
+ insertImage,
2394
+ uploadImage,
2395
+ insertMention,
2396
+ insertList,
2397
+ toggleMark,
2398
+ deleteContent,
2399
+ undo,
2400
+ redo,
2401
+ clearHistory,
2402
+ focus,
2403
+ setFocus,
2404
+ getPlainText,
2405
+ setSelection
2406
+ };
2407
+ }
1632
2408
  function useTableService(options) {
1633
2409
  const { config: config2, data } = options;
1634
2410
  const builder = useMemo(() => {
@@ -1992,6 +2768,7 @@ export {
1992
2768
  useLiveUpdateContext,
1993
2769
  useLiveUpdateListener,
1994
2770
  useLiveUpdates,
2771
+ useRichContent,
1995
2772
  useSetActiveTab,
1996
2773
  useTabChange,
1997
2774
  useTableService,