@dreamtree-org/twreact-ui 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/DESIGN.md +2 -2
  2. package/README.md +90 -5
  3. package/ai-skills/dreamtree-ui.md +43 -4
  4. package/bin/cli.mjs +72 -4
  5. package/charts/package.json +5 -0
  6. package/dist/_shared/cn-DPPQUJu1.js +138 -0
  7. package/dist/ai-schema.json +639 -1
  8. package/dist/charts/index.d.ts +9 -0
  9. package/dist/charts/index.d.ts.map +1 -0
  10. package/dist/charts/index.js +797 -0
  11. package/dist/components/charts/AreaChart.d.ts +4 -0
  12. package/dist/components/charts/AreaChart.d.ts.map +1 -0
  13. package/dist/components/charts/BarChart.d.ts +4 -0
  14. package/dist/components/charts/BarChart.d.ts.map +1 -0
  15. package/dist/components/charts/CartesianChart.d.ts +5 -0
  16. package/dist/components/charts/CartesianChart.d.ts.map +1 -0
  17. package/dist/components/charts/ChartFrame.d.ts +18 -0
  18. package/dist/components/charts/ChartFrame.d.ts.map +1 -0
  19. package/dist/components/charts/DonutChart.d.ts +4 -0
  20. package/dist/components/charts/DonutChart.d.ts.map +1 -0
  21. package/dist/components/charts/LineChart.d.ts +4 -0
  22. package/dist/components/charts/LineChart.d.ts.map +1 -0
  23. package/dist/components/charts/Sparkline.d.ts +5 -0
  24. package/dist/components/charts/Sparkline.d.ts.map +1 -0
  25. package/dist/components/charts/chartTheme.d.ts +36 -0
  26. package/dist/components/charts/chartTheme.d.ts.map +1 -0
  27. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  28. package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
  29. package/dist/components/core/Table/Table.d.ts.map +1 -1
  30. package/dist/components/rich-text/RichTextEditor.d.ts +4 -0
  31. package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -0
  32. package/dist/components/rich-text/RichTextToolbar.d.ts +219 -0
  33. package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -0
  34. package/dist/components/utility/Stepper.d.ts.map +1 -1
  35. package/dist/esm/charts/index.js +5 -0
  36. package/dist/esm/components/blocks/StatCard.js +1 -1
  37. package/dist/esm/components/charts/AreaChart.js +16 -0
  38. package/dist/esm/components/charts/BarChart.js +16 -0
  39. package/dist/esm/components/charts/CartesianChart.js +255 -0
  40. package/dist/esm/components/charts/ChartFrame.js +170 -0
  41. package/dist/esm/components/charts/DonutChart.js +122 -0
  42. package/dist/esm/components/charts/LineChart.js +16 -0
  43. package/dist/esm/components/charts/Sparkline.js +113 -0
  44. package/dist/esm/components/charts/chartTheme.js +126 -0
  45. package/dist/esm/components/core/Select.js +2 -2
  46. package/dist/esm/components/core/Table/MobileCard.js +5 -2
  47. package/dist/esm/components/core/Table/MobileFilters.js +10 -3
  48. package/dist/esm/components/core/Table/Table.js +10 -7
  49. package/dist/esm/components/rich-text/RichTextEditor.js +244 -0
  50. package/dist/esm/components/rich-text/RichTextToolbar.js +397 -0
  51. package/dist/esm/components/utility/Stepper.js +6 -4
  52. package/dist/esm/rich-text/index.js +1 -0
  53. package/dist/index.css +1 -1
  54. package/dist/index.esm.css +1 -1
  55. package/dist/index.esm.js +50 -22
  56. package/dist/index.js +50 -22
  57. package/dist/rich-text/index.d.ts +5 -0
  58. package/dist/rich-text/index.d.ts.map +1 -0
  59. package/dist/rich-text/index.js +654 -0
  60. package/dist/tokens/brands/practicepro.json +1 -0
  61. package/dist/tokens/brands/studio.css +14 -0
  62. package/dist/tokens/brands/studio.json +21 -0
  63. package/dist/types/charts.d.ts +97 -0
  64. package/dist/types/charts.d.ts.map +1 -0
  65. package/dist/types/rich-text.d.ts +50 -0
  66. package/dist/types/rich-text.d.ts.map +1 -0
  67. package/doc/README.md +13 -0
  68. package/mcp/catalog.snapshot.json +1016 -10
  69. package/mcp/server-core.mjs +30 -1
  70. package/package.json +39 -2
  71. package/rich-text/package.json +5 -0
  72. package/tokens/brand.schema.json +612 -0
  73. package/tokens/brands/practicepro.json +1 -0
  74. package/tokens/brands/studio.json +21 -0
  75. package/tokens/lib.cjs +131 -7
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+ import { slicedToArray as _slicedToArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { useSyncExternalStore, useId } from 'react';
4
+ import { prefersReducedMotion } from '../../utils/position.js';
5
+
6
+ var CHART_PALETTES = ["primary", "secondary", "success", "warning", "error", "info", "gray"];
7
+ var DEFAULT_CHART_COLORS = ["primary", "success", "warning", "error", "info", "secondary", "gray"];
8
+ var TOKEN_RE = new RegExp("^(".concat(CHART_PALETTES.join("|"), ")(?:-(50|[1-9]00|950))?$"));
9
+ var CHROME_VARS = {
10
+ grid: "--dt-line",
11
+ axis: "--dt-muted",
12
+ surface: "--dt-surface",
13
+ text: "--dt-foreground"
14
+ };
15
+ /** `"primary"` → `"--dt-primary-500"`, `"error-300"` → `"--dt-error-300"`; null for anything else. */
16
+ function tokenVar(color) {
17
+ var m = TOKEN_RE.exec(String(color || ""));
18
+ return m ? "--dt-".concat(m[1], "-").concat(m[2] || "500") : null;
19
+ }
20
+ function readVar(name) {
21
+ if (typeof window === "undefined" || typeof window.getComputedStyle !== "function") return "";
22
+ return window.getComputedStyle(document.documentElement).getPropertyValue(name).trim();
23
+ }
24
+ // SVG presentation attributes do not resolve var() in every engine, so a token is
25
+ // turned into a concrete rgb() string when the browser can tell us its value. The
26
+ // var() form is the SSR / jsdom fallback, and what the first (hydrating) client
27
+ // render uses so it matches the server's HTML (`live` = false).
28
+ function resolveVar(name) {
29
+ var live = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
30
+ var value = live ? readVar(name) : "";
31
+ return value ? "rgb(".concat(value.split(/[\s,]+/).join(" "), ")") : "rgb(var(".concat(name, "))");
32
+ }
33
+ /**
34
+ * A series colour: a palette token (`primary`, `success-600`, …) resolves to the
35
+ * matching `--dt-*` variable; any other string is treated as a CSS colour supplied
36
+ * by the consumer (e.g. a colour stored with the data).
37
+ */
38
+ function resolveColor(color) {
39
+ var live = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
40
+ var name = tokenVar(color);
41
+ return name ? resolveVar(name, live) : color;
42
+ }
43
+ function seriesColors(categories, colors) {
44
+ var live = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
45
+ var palette = colors && colors.length ? colors : DEFAULT_CHART_COLORS;
46
+ return categories.map(function (_, i) {
47
+ return resolveColor(palette[i % palette.length], live);
48
+ });
49
+ }
50
+ function readChrome(live) {
51
+ return Object.fromEntries(Object.entries(CHROME_VARS).map(function (_ref) {
52
+ var _ref2 = _slicedToArray(_ref, 2),
53
+ k = _ref2[0],
54
+ v = _ref2[1];
55
+ return [k, resolveVar(v, live)];
56
+ }));
57
+ }
58
+ // The theme is an external store: the attributes on <html> that flip it. The
59
+ // server snapshot is null, so a hydrating render keeps the var() fallback the
60
+ // server rendered and React re-renders with live colours right after (#130).
61
+ function subscribeTheme(onChange) {
62
+ if (typeof document === "undefined" || typeof MutationObserver !== "function") return function () {};
63
+ var observer = new MutationObserver(onChange);
64
+ observer.observe(document.documentElement, {
65
+ attributes: true,
66
+ attributeFilter: ["class", "data-theme", "style"]
67
+ });
68
+ return function () {
69
+ return observer.disconnect();
70
+ };
71
+ }
72
+ function themeSnapshot() {
73
+ if (typeof document === "undefined") return null;
74
+ var el = document.documentElement;
75
+ return "".concat(el.className, "|").concat(el.getAttribute("data-theme") || "", "|").concat(el.getAttribute("style") || "");
76
+ }
77
+ var serverThemeSnapshot = function serverThemeSnapshot() {
78
+ return null;
79
+ };
80
+ /**
81
+ * Chart chrome + series colours, re-read whenever the theme flips (the `dark`
82
+ * class or `data-theme` on <html>) so axes and series follow dark mode without
83
+ * a remount.
84
+ */
85
+ function useChartTheme(categories, colors) {
86
+ // The snapshot only signals a change; the values come from the live document.
87
+ var live = useSyncExternalStore(subscribeTheme, themeSnapshot, serverThemeSnapshot) !== null;
88
+ return {
89
+ chrome: readChrome(live),
90
+ series: seriesColors(categories, colors, live)
91
+ };
92
+ }
93
+ function subscribeReducedMotion(onChange) {
94
+ var _query$addEventListen;
95
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return function () {};
96
+ var query = window.matchMedia("(prefers-reduced-motion: reduce)");
97
+ (_query$addEventListen = query.addEventListener) === null || _query$addEventListen === void 0 || _query$addEventListen.call(query, "change", onChange);
98
+ return function () {
99
+ var _query$removeEventLis;
100
+ return (_query$removeEventLis = query.removeEventListener) === null || _query$removeEventLis === void 0 ? void 0 : _query$removeEventLis.call(query, "change", onChange);
101
+ };
102
+ }
103
+ var serverReducedMotion = function serverReducedMotion() {
104
+ return false;
105
+ };
106
+ /**
107
+ * `true` while the user asks the OS for reduced motion; follows changes live.
108
+ * `false` on the server and while hydrating, so the first render matches.
109
+ */
110
+ function useReducedMotion() {
111
+ return useSyncExternalStore(subscribeReducedMotion, prefersReducedMotion, serverReducedMotion);
112
+ }
113
+ var defaultValueFormatter = function defaultValueFormatter(value) {
114
+ return typeof value === "number" ? value.toLocaleString() : value == null ? "" : String(value);
115
+ };
116
+ /**
117
+ * A per-instance id for the SVG defs recharts draws (clip paths). recharts 2
118
+ * falls back to a module-level counter, which differs between the server and the
119
+ * client; an id from React.useId() is the same on both. Only [A-Za-z0-9_-] so it
120
+ * is safe inside `url(#…)`.
121
+ */
122
+ function useChartId() {
123
+ return "dt-chart-".concat(useId().replace(/[^A-Za-z0-9_-]/g, ""));
124
+ }
125
+
126
+ export { CHART_PALETTES, DEFAULT_CHART_COLORS, defaultValueFormatter, resolveColor, seriesColors, tokenVar, useChartId, useChartTheme, useReducedMotion };
@@ -127,7 +127,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
127
127
  onMenuItemRender = _ref.onMenuItemRender,
128
128
  className = _ref.className,
129
129
  _ref$allowClear = _ref.allowClear,
130
- allowClear = _ref$allowClear === void 0 ? true : _ref$allowClear,
130
+ allowClear = _ref$allowClear === void 0 ? false : _ref$allowClear,
131
131
  _ref$creatable = _ref.creatable,
132
132
  creatable = _ref$creatable === void 0 ? false : _ref$creatable,
133
133
  onCreateOption = _ref.onCreateOption,
@@ -564,7 +564,7 @@ var Select = /*#__PURE__*/React.forwardRef(function (_ref, forwardedRef) {
564
564
  }, opt.value);
565
565
  })
566
566
  }), jsxs("div", {
567
- className: "text-xs text-primary-500",
567
+ className: "text-xs text-primary-700",
568
568
  children: ["+", rest]
569
569
  })]
570
570
  });
@@ -128,6 +128,7 @@ function MobileCard(_ref) {
128
128
  }), menuActions.length > 0 && jsxs("div", {
129
129
  className: "relative",
130
130
  children: [jsx("button", {
131
+ type: "button",
131
132
  ref: function ref(el) {
132
133
  return registerActionButton === null || registerActionButton === void 0 ? void 0 : registerActionButton(actionCellKey, el);
133
134
  },
@@ -137,8 +138,10 @@ function MobileCard(_ref) {
137
138
  "aria-expanded": activeActionRowKey === actionCellKey,
138
139
  className: "p-1.5 rounded hover:bg-gray-200",
139
140
  title: "Actions",
141
+ "aria-label": "Actions",
140
142
  children: jsx(MoreVertical, {
141
- className: "h-4 w-4"
143
+ className: "h-4 w-4",
144
+ "aria-hidden": "true"
142
145
  })
143
146
  }), activeActionRowKey === actionCellKey && jsx(ActionMenu, {
144
147
  actions: menuActions,
@@ -166,7 +169,7 @@ function MobileCard(_ref) {
166
169
  return jsxs("div", {
167
170
  className: cn("flex justify-between items-center flex-row py-2 border-b last:border-b-0", safeExtraCellClass),
168
171
  children: [jsx("div", {
169
- className: "text-xs font-medium text-primary-500 uppercase tracking-wide mb-1 sm:mb-0 sm:w-1/3 sm:pr-2",
172
+ className: "text-xs font-medium text-primary-700 uppercase tracking-wide mb-1 sm:mb-0 sm:w-1/3 sm:pr-2",
170
173
  children: column.label
171
174
  }), jsx("div", {
172
175
  className: "text-sm text-primary-900 sm:w-2/3 sm:pl-2 break-words",
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import 'react';
3
+ import React from 'react';
4
4
  import { Search } from 'lucide-react';
5
5
 
6
6
  /**
@@ -17,6 +17,7 @@ function MobileFilters(_ref) {
17
17
  visibleColumns = _ref.visibleColumns,
18
18
  filters = _ref.filters,
19
19
  handleFilter = _ref.handleFilter;
20
+ var idPrefix = React.useId();
20
21
  return jsxs("div", {
21
22
  className: "mb-4 p-3 bg-gray-50 rounded-lg",
22
23
  children: [jsxs("div", {
@@ -25,12 +26,14 @@ function MobileFilters(_ref) {
25
26
  className: "text-sm font-medium",
26
27
  children: "Filters"
27
28
  }), jsx("button", {
29
+ type: "button",
28
30
  onClick: function onClick() {
29
31
  return setShowMobileFilters(function (s) {
30
32
  return !s;
31
33
  });
32
34
  },
33
- className: "text-xs text-primary-500",
35
+ "aria-expanded": !!showMobileFilters,
36
+ className: "text-xs font-medium text-primary-700",
34
37
  children: showMobileFilters ? "Hide" : "Show"
35
38
  })]
36
39
  }), showMobileFilters && jsxs("div", {
@@ -40,6 +43,7 @@ function MobileFilters(_ref) {
40
43
  children: [jsx("input", {
41
44
  type: "text",
42
45
  placeholder: "Search...",
46
+ "aria-label": "Search",
43
47
  className: "w-full rounded-md border border-gray-300 px-4 py-2 text-sm",
44
48
  value: searchInput,
45
49
  onChange: function onChange(e) {
@@ -47,14 +51,17 @@ function MobileFilters(_ref) {
47
51
  },
48
52
  onKeyDown: onGlobalKeyDown
49
53
  }), jsx(Search, {
50
- className: "absolute right-3 top-2.5 h-4 w-4 text-primary-400"
54
+ className: "absolute right-3 top-2.5 h-4 w-4 text-primary-400",
55
+ "aria-hidden": "true"
51
56
  })]
52
57
  }), filterable && visibleColumns.map(function (column) {
53
58
  return jsxs("div", {
54
59
  children: [jsx("label", {
60
+ htmlFor: "".concat(idPrefix, "-filter-").concat(column.key),
55
61
  className: "block text-xs font-medium mb-1",
56
62
  children: column.label
57
63
  }), jsx("input", {
64
+ id: "".concat(idPrefix, "-filter-").concat(column.key),
58
65
  type: "text",
59
66
  placeholder: "Filter ".concat(column.label),
60
67
  className: "w-full rounded-md border border-gray-300 px-3 py-2 text-sm",
@@ -1179,10 +1179,13 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1179
1179
  children: [showReloadButton && jsx(Fragment, {
1180
1180
  children: renderReloadButton ? typeof renderReloadButton === "function" ? renderReloadButton() : renderReloadButton : jsxs("button", {
1181
1181
  type: "button",
1182
+ "aria-label": "Reload",
1183
+ title: "Reload",
1182
1184
  className: "px-4 py-2 bg-primary-100 flex items-center gap-2 rounded-lg md:rounded text-primary-700 hover:bg-primary-200 transition-colors",
1183
1185
  onClick: onReload,
1184
1186
  children: [jsx(RefreshCcw, {
1185
- className: "size-4"
1187
+ className: "size-4",
1188
+ "aria-hidden": "true"
1186
1189
  }), jsx("span", {
1187
1190
  className: "hidden sm:inline",
1188
1191
  children: "Reload"
@@ -1323,7 +1326,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1323
1326
  children: [hasDetailsColumn && jsx("th", {
1324
1327
  className: "w-8 px-4 py-3"
1325
1328
  }), showSerial && jsx("th", {
1326
- className: "w-8 px-4 py-3 text-left text-xs font-medium text-primary-500 uppercase tracking-wider",
1329
+ className: "w-8 px-4 py-3 text-left text-xs font-medium text-primary-700 uppercase tracking-wider",
1327
1330
  children: "#"
1328
1331
  }), selectable && jsx("th", {
1329
1332
  className: "w-12 px-6 py-3",
@@ -1336,7 +1339,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1336
1339
  }), visibleColumns.map(function (column) {
1337
1340
  var _column$rules;
1338
1341
  return jsx("th", {
1339
- className: cn("px-6 py-3 text-left text-xs font-medium text-primary-500 uppercase tracking-wider", {
1342
+ className: cn("px-6 py-3 text-left text-xs font-medium text-primary-700 uppercase tracking-wider", {
1340
1343
  "cursor-pointer hover:bg-gray-100": sortable && column.sortable !== false
1341
1344
  }),
1342
1345
  onClick: function onClick() {
@@ -1427,7 +1430,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1427
1430
  children: [jsx("div", {
1428
1431
  className: "h-6 w-6 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"
1429
1432
  }), jsx("span", {
1430
- className: "text-sm text-primary-500",
1433
+ className: "text-sm text-muted",
1431
1434
  children: "Loading..."
1432
1435
  })]
1433
1436
  })
@@ -1445,7 +1448,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1445
1448
  className: "h-8 w-8"
1446
1449
  })
1447
1450
  }), jsx("span", {
1448
- className: "text-sm text-primary-500",
1451
+ className: "text-sm text-muted",
1449
1452
  children: "No results found."
1450
1453
  })]
1451
1454
  })
@@ -1490,7 +1493,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1490
1493
  children: [jsx("div", {
1491
1494
  className: "h-6 w-6 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"
1492
1495
  }), jsx("span", {
1493
- className: "text-sm text-primary-500",
1496
+ className: "text-sm text-muted",
1494
1497
  children: "Loading..."
1495
1498
  })]
1496
1499
  })
@@ -1504,7 +1507,7 @@ var Table = /*#__PURE__*/forwardRef(function Table(_ref, ref) {
1504
1507
  className: "h-8 w-8"
1505
1508
  })
1506
1509
  }), jsx("span", {
1507
- className: "text-sm text-primary-500",
1510
+ className: "text-sm text-muted",
1508
1511
  children: "No results found."
1509
1512
  })]
1510
1513
  })
@@ -0,0 +1,244 @@
1
+ 'use client';
2
+ import { objectWithoutProperties as _objectWithoutProperties, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray } from '../../_virtual/_rollupPluginBabelHelpers.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import { forwardRef, useId, useRef, useEffect, useImperativeHandle } from 'react';
5
+ import { useEditor, useEditorState, EditorContent } from '@tiptap/react';
6
+ import StarterKit from '@tiptap/starter-kit';
7
+ import { AlertCircle } from 'lucide-react';
8
+ import { cn } from '../../utils/cn.js';
9
+ import { useLatestRef } from '../../hooks/useLatestRef.js';
10
+ import { selectToolbarState, DEFAULT_TOOLBAR, RichTextToolbar } from './RichTextToolbar.js';
11
+
12
+ var _excluded = ["id", "className", "editorClassName", "label", "error", "helperText", "required", "disabled", "readOnly", "placeholder", "value", "defaultValue", "onChange", "outputFormat", "onFocus", "onBlur", "toolbar", "toolbarLabel", "minHeight", "maxHeight", "autoFocus", "extensions", "aria-label", "aria-describedby"];
13
+ // Tailwind's preflight strips list bullets, heading sizes, etc. These arbitrary
14
+ // variants restore readable defaults inside the editable area only.
15
+ var CONTENT_CLASSES = cn("px-3 py-2 text-sm text-gray-900 focus:outline-none dark:text-gray-100", "[&_p]:my-1 [&_h1]:my-2 [&_h1]:text-2xl [&_h1]:font-bold [&_h2]:my-2 [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:my-1.5 [&_h3]:text-lg [&_h3]:font-semibold", "[&_ul]:list-disc [&_ul]:pl-6 [&_ol]:list-decimal [&_ol]:pl-6 [&_li]:my-0.5", "[&_blockquote]:my-2 [&_blockquote]:border-l-4 [&_blockquote]:border-gray-300 [&_blockquote]:pl-3 [&_blockquote]:text-gray-600 dark:[&_blockquote]:border-gray-600 dark:[&_blockquote]:text-gray-300", "[&_code]:rounded [&_code]:bg-gray-100 [&_code]:px-1 [&_code]:font-mono [&_code]:text-[0.85em] dark:[&_code]:bg-gray-800", "[&_pre]:my-2 [&_pre]:rounded-md [&_pre]:bg-gray-100 [&_pre]:p-3 dark:[&_pre]:bg-gray-800 [&_pre_code]:bg-transparent [&_pre_code]:p-0", "[&_a]:text-primary-600 [&_a]:underline dark:[&_a]:text-primary-400 [&_hr]:my-3 [&_hr]:border-gray-200 dark:[&_hr]:border-gray-700");
16
+ var toCss = function toCss(v) {
17
+ return typeof v === "number" ? "".concat(v, "px") : v;
18
+ };
19
+ function sameContent(editor, value) {
20
+ if (typeof value === "string") return value === editor.getHTML();
21
+ return JSON.stringify(value) === JSON.stringify(editor.getJSON());
22
+ }
23
+ var RichTextEditor = /*#__PURE__*/forwardRef(function (_ref, ref) {
24
+ var _ref2;
25
+ var id = _ref.id,
26
+ className = _ref.className,
27
+ editorClassName = _ref.editorClassName,
28
+ label = _ref.label,
29
+ error = _ref.error,
30
+ helperText = _ref.helperText,
31
+ _ref$required = _ref.required,
32
+ required = _ref$required === void 0 ? false : _ref$required,
33
+ _ref$disabled = _ref.disabled,
34
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
35
+ _ref$readOnly = _ref.readOnly,
36
+ readOnly = _ref$readOnly === void 0 ? false : _ref$readOnly,
37
+ placeholder = _ref.placeholder,
38
+ value = _ref.value,
39
+ defaultValue = _ref.defaultValue,
40
+ onChange = _ref.onChange,
41
+ _ref$outputFormat = _ref.outputFormat,
42
+ outputFormat = _ref$outputFormat === void 0 ? "html" : _ref$outputFormat,
43
+ onFocus = _ref.onFocus,
44
+ onBlur = _ref.onBlur,
45
+ _ref$toolbar = _ref.toolbar,
46
+ toolbar = _ref$toolbar === void 0 ? DEFAULT_TOOLBAR : _ref$toolbar,
47
+ _ref$toolbarLabel = _ref.toolbarLabel,
48
+ toolbarLabel = _ref$toolbarLabel === void 0 ? "Formatting" : _ref$toolbarLabel,
49
+ _ref$minHeight = _ref.minHeight,
50
+ minHeight = _ref$minHeight === void 0 ? 120 : _ref$minHeight,
51
+ maxHeight = _ref.maxHeight,
52
+ _ref$autoFocus = _ref.autoFocus,
53
+ autoFocus = _ref$autoFocus === void 0 ? false : _ref$autoFocus,
54
+ extensions = _ref.extensions,
55
+ ariaLabel = _ref["aria-label"],
56
+ ariaDescribedBy = _ref["aria-describedby"],
57
+ rest = _objectWithoutProperties(_ref, _excluded);
58
+ var reactId = useId();
59
+ var editorId = id || "rich-text-".concat(reactId);
60
+ var labelId = "".concat(editorId, "-label");
61
+ var errorId = "".concat(editorId, "-error");
62
+ var helperId = "".concat(editorId, "-helper");
63
+ var editable = !disabled && !readOnly;
64
+ var rootRef = useRef(null);
65
+ var callbacks = useLatestRef({
66
+ onChange: onChange,
67
+ onFocus: onFocus,
68
+ onBlur: onBlur,
69
+ outputFormat: outputFormat
70
+ });
71
+ var describedBy = [error ? errorId : null, helperText ? helperId : null, ariaDescribedBy].filter(Boolean).join(" ") || undefined;
72
+ var attributes = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
73
+ id: editorId,
74
+ role: "textbox",
75
+ "aria-multiline": "true"
76
+ }, label ? {
77
+ "aria-labelledby": labelId
78
+ } : {
79
+ "aria-label": ariaLabel || placeholder || "Rich text editor"
80
+ }), describedBy ? {
81
+ "aria-describedby": describedBy
82
+ } : {}), error ? {
83
+ "aria-invalid": "true"
84
+ } : {}), required ? {
85
+ "aria-required": "true"
86
+ } : {}), readOnly ? {
87
+ "aria-readonly": "true"
88
+ } : {}), disabled ? {
89
+ "aria-disabled": "true"
90
+ } : {}), placeholder ? {
91
+ "aria-placeholder": placeholder
92
+ } : {}), {}, {
93
+ "class": cn(CONTENT_CLASSES, editorClassName)
94
+ });
95
+ var attributesKey = JSON.stringify(attributes);
96
+ var editor = useEditor({
97
+ extensions: [StarterKit.configure({
98
+ link: {
99
+ openOnClick: false,
100
+ autolink: true,
101
+ defaultProtocol: "https"
102
+ }
103
+ })].concat(_toConsumableArray(extensions || [])),
104
+ content: (_ref2 = value !== null && value !== void 0 ? value : defaultValue) !== null && _ref2 !== void 0 ? _ref2 : "",
105
+ editable: editable,
106
+ autofocus: autoFocus,
107
+ // Render on the client after mount: safe for SSR / Next.js hydration.
108
+ immediatelyRender: false,
109
+ editorProps: {
110
+ attributes: attributes
111
+ },
112
+ onUpdate: function onUpdate(_ref3) {
113
+ var instance = _ref3.editor;
114
+ var _callbacks$current = callbacks.current,
115
+ cb = _callbacks$current.onChange,
116
+ format = _callbacks$current.outputFormat;
117
+ cb === null || cb === void 0 || cb(format === "json" ? instance.getJSON() : instance.getHTML(), instance);
118
+ },
119
+ onFocus: function onFocus(_ref4) {
120
+ var _callbacks$current$on, _callbacks$current2;
121
+ var event = _ref4.event;
122
+ return (_callbacks$current$on = (_callbacks$current2 = callbacks.current).onFocus) === null || _callbacks$current$on === void 0 ? void 0 : _callbacks$current$on.call(_callbacks$current2, event);
123
+ },
124
+ onBlur: function onBlur(_ref5) {
125
+ var _callbacks$current$on2, _callbacks$current3;
126
+ var event = _ref5.event;
127
+ return (_callbacks$current$on2 = (_callbacks$current3 = callbacks.current).onBlur) === null || _callbacks$current$on2 === void 0 ? void 0 : _callbacks$current$on2.call(_callbacks$current3, event);
128
+ }
129
+ });
130
+ var watched = useEditorState({
131
+ editor: editor,
132
+ selector: function selector(_ref6) {
133
+ var instance = _ref6.editor;
134
+ return selectToolbarState(instance);
135
+ }
136
+ });
137
+ // useEditorState only re-selects on the next transaction after the editor
138
+ // mounts, so read the state directly until then.
139
+ var state = watched !== null && watched !== void 0 ? watched : selectToolbarState(editor);
140
+ useEffect(function () {
141
+ if (editor && value !== undefined && value !== null && !sameContent(editor, value)) {
142
+ editor.commands.setContent(value, {
143
+ emitUpdate: false
144
+ });
145
+ }
146
+ }, [editor, value]);
147
+ useEffect(function () {
148
+ if (editor && editor.isEditable !== editable) editor.setEditable(editable, false);
149
+ }, [editor, editable]);
150
+ useEffect(function () {
151
+ if (editor) editor.setOptions({
152
+ editorProps: {
153
+ attributes: JSON.parse(attributesKey)
154
+ }
155
+ });
156
+ }, [editor, attributesKey]);
157
+ useImperativeHandle(ref, function () {
158
+ return {
159
+ editor: editor,
160
+ element: rootRef.current,
161
+ focus: function focus() {
162
+ return editor === null || editor === void 0 ? void 0 : editor.commands.focus();
163
+ },
164
+ clear: function clear() {
165
+ return editor === null || editor === void 0 ? void 0 : editor.commands.clearContent(true);
166
+ },
167
+ getHTML: function getHTML() {
168
+ return editor ? editor.getHTML() : "";
169
+ },
170
+ getJSON: function getJSON() {
171
+ return editor ? editor.getJSON() : null;
172
+ },
173
+ setContent: function setContent(content) {
174
+ return editor === null || editor === void 0 ? void 0 : editor.commands.setContent(content);
175
+ }
176
+ };
177
+ }, [editor]);
178
+ var focusEditor = function focusEditor() {
179
+ if (editable) editor === null || editor === void 0 || editor.commands.focus();
180
+ };
181
+ var items = Array.isArray(toolbar) ? toolbar : toolbar === false ? [] : DEFAULT_TOOLBAR;
182
+ var showToolbar = !readOnly && items.length > 0;
183
+ var showPlaceholder = placeholder && (state ? state.isEmpty : !(value !== null && value !== void 0 ? value : defaultValue));
184
+ return jsxs("div", _objectSpread2(_objectSpread2({
185
+ ref: rootRef,
186
+ className: cn("w-full", className),
187
+ "data-disabled": disabled || undefined
188
+ }, rest), {}, {
189
+ children: [label && jsxs("label", {
190
+ id: labelId,
191
+ onClick: focusEditor,
192
+ className: "mb-1 block text-sm font-medium text-gray-700 dark:text-gray-200",
193
+ children: [label, required && jsx("span", {
194
+ className: "ml-1 text-error-500",
195
+ "aria-hidden": "true",
196
+ children: "*"
197
+ })]
198
+ }), jsxs("div", {
199
+ className: cn("overflow-hidden rounded-md border bg-white dark:bg-gray-900", error ? "border-error-500" : "border-gray-300 dark:border-gray-600", disabled && "cursor-not-allowed opacity-50"),
200
+ children: [showToolbar && jsx(RichTextToolbar, {
201
+ editor: editor,
202
+ state: state,
203
+ items: items,
204
+ disabled: disabled,
205
+ controlsId: editorId,
206
+ label: toolbarLabel
207
+ }), jsxs("div", {
208
+ className: cn("relative overflow-y-auto focus-within:ring-2 focus-within:ring-inset", error ? "focus-within:ring-error-500" : "focus-within:ring-primary-500"),
209
+ style: {
210
+ minHeight: toCss(minHeight),
211
+ maxHeight: toCss(maxHeight)
212
+ },
213
+ onMouseDown: function onMouseDown(e) {
214
+ if (e.target === e.currentTarget) {
215
+ e.preventDefault();
216
+ focusEditor();
217
+ }
218
+ },
219
+ children: [showPlaceholder && jsx("div", {
220
+ className: "pointer-events-none absolute left-3 top-3 select-none text-sm text-gray-400 dark:text-gray-500",
221
+ "aria-hidden": "true",
222
+ children: placeholder
223
+ }), jsx(EditorContent, {
224
+ editor: editor
225
+ })]
226
+ })]
227
+ }), error && jsxs("p", {
228
+ id: errorId,
229
+ className: "mt-1 flex items-center gap-1 text-sm text-error-600 dark:text-error-400",
230
+ "aria-live": "polite",
231
+ children: [jsx(AlertCircle, {
232
+ className: "h-4 w-4 shrink-0",
233
+ "aria-hidden": "true"
234
+ }), error]
235
+ }), helperText && jsx("p", {
236
+ id: helperId,
237
+ className: "mt-1 text-sm text-gray-500 dark:text-gray-400",
238
+ children: helperText
239
+ })]
240
+ }));
241
+ });
242
+ RichTextEditor.displayName = "RichTextEditor";
243
+
244
+ export { RichTextEditor as default };