@tristan2828/ui-foundation 4.61.0 → 4.61.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.
@@ -814,8 +814,9 @@ export default function MilkdownEditor({ id, defaultValue, onChange, onProblem,
814
814
  void editor.destroy();
815
815
  root.replaceChildren();
816
816
  };
817
- // Made once per mount: later changes to defaultValue are ignored, as
818
- // with any uncontrolled input. Attributes and read-only follow below.
817
+ // Made once per mount. A new defaultValue the editor didn't report
818
+ // remounts it (RichTextEditor's key), so it isn't read here.
819
+ // Attributes and read-only follow below.
819
820
  // eslint-disable-next-line react-hooks/exhaustive-deps
820
821
  }, []);
821
822
  // Read-only and the ARIA attributes follow their props.
@@ -3,7 +3,11 @@ import type { HeadingFrom, HeadingLevel } from './prose-look.js';
3
3
  export type RichTextEditorProps = {
4
4
  /** The editable element's id, for a label's `htmlFor` or a test. */
5
5
  id?: string;
6
- /** The Markdown it opens with. Read once, when it mounts (uncontrolled). */
6
+ /**
7
+ * The Markdown it opens with. A later value the editor didn't report
8
+ * (a record loaded after it mounted, a reset) replaces the text; the
9
+ * value it reported, passed back, changes nothing.
10
+ */
7
11
  defaultValue: string;
8
12
  /** The Markdown to save, after every edit: untouched blocks as originally written. */
9
13
  onChange: (markdown: string) => void;
@@ -26,13 +26,41 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
26
  // With `foldKey`, the text folds by its headings (NotesOutline lists them
27
27
  // beside it), the Markdown untouched.
28
28
  //
29
+ // `defaultValue` is read when it mounts, then followed: a new value the
30
+ // editor didn't report itself (a form's loaded record arriving after the
31
+ // editor, a reset) replaces the text, as if it opened with it. Its own
32
+ // edits, coming back as the value, change nothing, so the caret stays
33
+ // where it is. A caller that never feeds `onChange` back keeps its text.
34
+ //
29
35
  // The editor (Milkdown) is loaded on first use, so a page that only reads
30
36
  // Markdown never downloads it. Until it arrives, a placeholder the size of
31
37
  // the field shows.
32
- import { lazy, Suspense } from 'react';
38
+ import { lazy, Suspense, useState } from 'react';
33
39
  import { cn } from 'cn';
34
40
  import { Skeleton } from '../ui/skeleton.js';
35
41
  const MilkdownEditor = lazy(() => import('./milkdown-editor.js'));
42
+ // When the editor opens again: each time `defaultValue` changes to
43
+ // Markdown it didn't report. Adjusted while rendering (React's "storing
44
+ // information from previous renders"), so the new text never shows a
45
+ // frame late.
46
+ function useOpenings(defaultValue) {
47
+ const [seen, setSeen] = useState(defaultValue);
48
+ const [reported, setReported] = useState(defaultValue);
49
+ const [opening, setOpening] = useState(0);
50
+ if (defaultValue !== seen) {
51
+ setSeen(defaultValue);
52
+ if (defaultValue !== reported) {
53
+ setReported(defaultValue);
54
+ setOpening(opening + 1);
55
+ }
56
+ }
57
+ return { opening, report: setReported };
58
+ }
36
59
  export function RichTextEditor(props) {
37
- return (_jsx(Suspense, { fallback: _jsxs("div", { "data-slot": "rich-text-editor", "data-state": "loading", className: cn('flex w-full flex-col', !props.inline && 'rounded-lg border border-input', props.className), children: [(props.toolbar ?? true) === true && _jsx("div", { className: "h-9 border-b border-input" }), _jsxs("div", { className: cn('flex flex-col gap-2', props.inline ? 'py-0.5' : 'min-h-24 px-2.5 py-2'), children: [_jsx(Skeleton, { className: "h-4 w-3/4" }), _jsx(Skeleton, { className: "h-4 w-1/2" })] })] }), children: _jsx(MilkdownEditor, { ...props, foldKey: props.foldable === false ? undefined : props.foldKey }) }));
60
+ const { opening, report } = useOpenings(props.defaultValue);
61
+ const { onChange } = props;
62
+ return (_jsx(Suspense, { fallback: _jsxs("div", { "data-slot": "rich-text-editor", "data-state": "loading", className: cn('flex w-full flex-col', !props.inline && 'rounded-lg border border-input', props.className), children: [(props.toolbar ?? true) === true && _jsx("div", { className: "h-9 border-b border-input" }), _jsxs("div", { className: cn('flex flex-col gap-2', props.inline ? 'py-0.5' : 'min-h-24 px-2.5 py-2'), children: [_jsx(Skeleton, { className: "h-4 w-3/4" }), _jsx(Skeleton, { className: "h-4 w-1/2" })] })] }), children: _jsx(MilkdownEditor, { ...props, onChange: (markdown) => {
63
+ report(markdown);
64
+ onChange(markdown);
65
+ }, foldKey: props.foldable === false ? undefined : props.foldKey }, opening) }));
38
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tristan2828/ui-foundation",
3
- "version": "4.61.0",
3
+ "version": "4.61.1",
4
4
  "description": "Shared UI foundation for contract-first, database-backed React apps: shadcn primitives, DataTable/EntityForm composites, auth, the gateway error seam, design tokens, lint rules and the AI playbook.",
5
5
  "license": "MIT",
6
6
  "repository": {