@estiva-app/ui 0.50.3 → 0.51.0

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 (45) hide show
  1. package/dist/BlockNodes.d.ts.map +1 -1
  2. package/dist/CollapsibleSection.d.ts +7 -1
  3. package/dist/CollapsibleSection.d.ts.map +1 -1
  4. package/dist/NavItem.d.ts +23 -3
  5. package/dist/NavItem.d.ts.map +1 -1
  6. package/dist/NavTree.d.ts +49 -0
  7. package/dist/NavTree.d.ts.map +1 -0
  8. package/dist/SectionLabel.d.ts +7 -1
  9. package/dist/SectionLabel.d.ts.map +1 -1
  10. package/dist/editor.js +1 -1
  11. package/dist/editor.js.map +2 -2
  12. package/dist/eslint/index.js +1 -1
  13. package/dist/eslint/index.js.map +2 -2
  14. package/dist/eslint/no-native-title.d.ts +1 -1
  15. package/dist/eslint/no-native-title.d.ts.map +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +332 -212
  19. package/dist/index.js.map +4 -4
  20. package/dist/looks.d.ts +7 -0
  21. package/dist/looks.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/registry.json +115 -5
  24. package/src/BlockNodes.test.tsx +43 -0
  25. package/src/BlockNodes.tsx +5 -2
  26. package/src/CollapsibleSection.mdx +6 -0
  27. package/src/CollapsibleSection.stories.tsx +5 -0
  28. package/src/CollapsibleSection.tsx +12 -4
  29. package/src/NavItem.mdx +30 -0
  30. package/src/NavItem.stories.tsx +34 -0
  31. package/src/NavItem.test.tsx +82 -0
  32. package/src/NavItem.tsx +123 -25
  33. package/src/NavTree.mdx +83 -0
  34. package/src/NavTree.stories.tsx +84 -0
  35. package/src/NavTree.test.tsx +54 -0
  36. package/src/NavTree.tsx +112 -0
  37. package/src/SectionHeader.tsx +1 -1
  38. package/src/SectionLabel.mdx +4 -1
  39. package/src/SectionLabel.stories.tsx +3 -0
  40. package/src/SectionLabel.tsx +10 -2
  41. package/src/eslint/no-native-title.ts +1 -1
  42. package/src/index.ts +1 -0
  43. package/src/looks.ts +8 -0
  44. package/src/registry/registry.test.ts +4 -4
  45. package/stories/Choosing.mdx +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"BlockNodes.d.ts","sourceRoot":"","sources":["../src/BlockNodes.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAkP9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,OAAO,qBA+ClB,CAAA;AAEF,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,IAAI,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAA;CAC/C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,iCAgCxB,CAAA;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,gBAqBvB,CAAA;AAgBF,uEAAuE;AACvE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,8FAA8F;IAC9F,IAAI,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAAA;CAChD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,kCAqCzB,CAAA"}
1
+ {"version":3,"file":"BlockNodes.d.ts","sourceRoot":"","sources":["../src/BlockNodes.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAqP9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,OAAO,qBA+ClB,CAAA;AAEF,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,IAAI,EAAE,aAAa,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAAA;CAC/C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,iCAgCxB,CAAA;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,gBAqBvB,CAAA;AAgBF,uEAAuE;AACvE,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,8FAA8F;IAC9F,IAAI,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAAA;CAChD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,kCAqCzB,CAAA"}
@@ -39,6 +39,12 @@ export interface CollapsibleSectionProps {
39
39
  * its title, past the chevron (`SectionHeader`'s `look`).
40
40
  */
41
41
  look?: 'heading' | 'row';
42
+ /**
43
+ * With the `heading` look: the rows start under the title, past the
44
+ * chevron — a folder in a sidebar, its files lightly indented under its
45
+ * name (Katerina, 2 October). The `row` look always indents.
46
+ */
47
+ indent?: boolean;
42
48
  /** The rows. */
43
49
  children: ReactNode;
44
50
  /** On the section's box: `mt-2` between groups, `shrink-0` in a scrolling column. */
@@ -46,5 +52,5 @@ export interface CollapsibleSectionProps {
46
52
  /** On the box the rows sit in: the gap between them. */
47
53
  contentClassName?: string;
48
54
  }
49
- export declare function CollapsibleSection({ title, defaultOpen, open: openProp, onOpenChange, storageKey, trailing, actions, showActions, look, children, className, contentClassName }: CollapsibleSectionProps): import("react").JSX.Element;
55
+ export declare function CollapsibleSection({ title, defaultOpen, open: openProp, onOpenChange, storageKey, trailing, actions, showActions, look, indent, children, className, contentClassName }: CollapsibleSectionProps): import("react").JSX.Element;
50
56
  //# sourceMappingURL=CollapsibleSection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollapsibleSection.d.ts","sourceRoot":"","sources":["../src/CollapsibleSection.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGhD,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,kCAAkC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;IACzB,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAChC;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,KAAK,CAAA;IACxB,gBAAgB;IAChB,QAAQ,EAAE,SAAS,CAAA;IACnB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAwBD,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,WAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,IAAgB,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,uBAAuB,+BA0B3N"}
1
+ {"version":3,"file":"CollapsibleSection.d.ts","sourceRoot":"","sources":["../src/CollapsibleSection.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGhD,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAGnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,kCAAkC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,wEAAwE;IACxE,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;IACzB,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAChC;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,KAAK,CAAA;IACxB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gBAAgB;IAChB,QAAQ,EAAE,SAAS,CAAA;IACnB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAwBD,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,WAAkB,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,IAAgB,EAAE,MAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,uBAAuB,+BA2B3O"}
package/dist/NavItem.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ComponentPropsWithoutRef, ReactNode } from 'react';
1
+ import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
2
2
  /**
3
3
  * One row of a sidebar: a label, an optional count, an active state
4
4
  * (Ship's NavItem, 2026-09-02, verbatim).
@@ -15,8 +15,19 @@ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
15
15
  * menu is open, in the count's place — the count steps aside rather than
16
16
  * sitting under it. The ⋮ is beside the link, not inside it (a button inside
17
17
  * an anchor is not a button), and follows it in the Tab order.
18
+ *
19
+ * `children` are rows under this one (Katerina, 2 October: Leaf's folder
20
+ * tree). The row stays a link; while it is pointed at or focused its icon
21
+ * gives way to an arrow that folds the rows, in the icon's own place — so a
22
+ * row with rows under it is as wide as one without. The arrow is beside the
23
+ * link, like the ⋮. The rows slide as `CollapsibleSection`'s do, indented
24
+ * past the icon so their icons start under this row's label.
25
+ *
26
+ * `hint` is shown only while the row is pointed at or focused, left of the
27
+ * count — a neutral `Chip`, typically. It takes its own room while it shows,
28
+ * so the label ends with "…" before it instead of running under it.
18
29
  */
19
- export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'> {
30
+ export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'children'> {
20
31
  label: string;
21
32
  href: string;
22
33
  /** `0` or absent renders no counter. */
@@ -30,7 +41,16 @@ export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'
30
41
  menu?: ReactNode;
31
42
  /** The ⋮'s accessible name and tooltip. Default "More options for {label}". */
32
43
  menuLabel?: string;
44
+ /** Shown only while the row is pointed at or focused, left of the count: a neutral `Chip`, typically. */
45
+ hint?: ReactNode;
46
+ /** Rows under this one, folding under it: `NavItem`s. While the row is pointed at or focused, its icon becomes the arrow. */
47
+ children?: ReactNode;
48
+ /** With `children`: open unless told otherwise. */
49
+ defaultOpen?: boolean;
50
+ /** With `children`: the caller owns the state. */
51
+ open?: boolean;
52
+ onOpenChange?: (open: boolean) => void;
33
53
  className?: string;
34
54
  }
35
- export declare function NavItem({ label, href, count, countLabel, active, icon, menu, menuLabel, className, ...props }: NavItemProps): import("react").JSX.Element;
55
+ export declare function NavItem({ label, href, count, countLabel, active, icon, menu, menuLabel, hint, children, defaultOpen, open: openProp, onOpenChange, className, ...props }: NavItemProps): import("react").JSX.Element;
36
56
  //# sourceMappingURL=NavItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NavItem.d.ts","sourceRoot":"","sources":["../src/NavItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQhE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAC/E,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wBAAwB;IACxB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,4GAA4G;IAC5G,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAc,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,+BAiEnI"}
1
+ {"version":3,"file":"NavItem.d.ts","sourceRoot":"","sources":["../src/NavItem.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,wBAAwB,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAStF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5F,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wBAAwB;IACxB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,4GAA4G;IAC5G,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,yGAAyG;IACzG,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,6HAA6H;IAC7H,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,kDAAkD;IAClD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,KAAK,EACL,UAAU,EACV,MAAc,EACd,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,WAAkB,EAClB,IAAI,EAAE,QAAQ,EACd,YAAY,EACZ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,YAAY,+BA8Hd"}
@@ -0,0 +1,49 @@
1
+ import type { MouseEvent, ReactNode } from 'react';
2
+ /** One row of the tree, and the rows under it. */
3
+ export interface NavTreeNode {
4
+ id: string;
5
+ label: string;
6
+ href: string;
7
+ /** 16px, stroke 1.5. */
8
+ icon?: ReactNode;
9
+ /** `0` or absent renders no counter. */
10
+ count?: number;
11
+ /** What the count counts, for the tooltip. */
12
+ countLabel?: string;
13
+ /** Shown only while the row is pointed at or focused, left of the count: a neutral `Chip`, typically. */
14
+ hint?: ReactNode;
15
+ /** The row's "More options" menu: `MenuItem`s. */
16
+ menu?: ReactNode;
17
+ /** Rows under this one. Their row folds them; open unless `defaultOpen` says otherwise. */
18
+ children?: NavTreeNode[];
19
+ defaultOpen?: boolean;
20
+ }
21
+ /** A group heading over its rows: the top of the tree. */
22
+ export interface NavTreeGroup {
23
+ id: string;
24
+ title: string;
25
+ nodes: NavTreeNode[];
26
+ }
27
+ export interface NavTreeProps {
28
+ /** The quiet label over the groups. Absent, none is drawn. */
29
+ title?: string;
30
+ /** `null` while the first read is on its way: a skeleton in the rows' place. */
31
+ groups: NavTreeGroup[] | null;
32
+ /** The row that is current: `active`, and its group starts open. */
33
+ selected?: string;
34
+ /** A row was clicked. A router app prevents the default and navigates. */
35
+ onSelect?: (node: NavTreeNode, event: MouseEvent<HTMLAnchorElement>) => void;
36
+ /** What to say when there are no groups. The words are the caller's. */
37
+ emptyMessage: string;
38
+ /** Remember each group open or closed in this browser, under this key and the group's id. The app prefixes it. */
39
+ storageKey?: string;
40
+ }
41
+ /**
42
+ * A sidebar's tree, from one nested list (Katerina, 2 October: Leaf's folder
43
+ * tree): a quiet label, then each group as the standard heading folding its
44
+ * rows, lightly indented under its title; rows under rows to any depth, each
45
+ * row a link whose icon becomes the arrow that folds what is under it. Only
46
+ * the group that holds the current row starts open.
47
+ */
48
+ export declare function NavTree({ title, groups, selected, onSelect, emptyMessage, storageKey }: NavTreeProps): import("react").JSX.Element;
49
+ //# sourceMappingURL=NavTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavTree.d.ts","sourceRoot":"","sources":["../src/NavTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAOlD,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,wBAAwB;IACxB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,yGAAyG;IACzG,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,kDAAkD;IAClD,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,WAAW,EAAE,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,MAAM,EAAE,YAAY,EAAE,GAAG,IAAI,CAAA;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IAC5E,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;IACpB,kHAAkH;IAClH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AA2BD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,EAAE,YAAY,+BA8BpG"}
@@ -16,16 +16,22 @@ import type { ReactNode } from 'react';
16
16
  * long label short with an ellipsis where its row has no room, as a date line in
17
17
  * a narrow panel must.
18
18
  *
19
+ * `folds` is the title of a group that opens and closes: in Leaf it keeps
20
+ * Ship's type rather than taking Signal's micro-label, so a folder reads as
21
+ * a name (Katerina, 2 October). Every other Leaf label keeps Signal's.
22
+ *
19
23
  * Deliberately NOT the same thing as Property's stacked field label (the
20
24
  * 9px `menu`-token one): they were merged for a day and unmerged by
21
25
  * Katerina's ruling (2026-09-01) — a section title and a field label are
22
26
  * different voices, at different sizes, on purpose.
23
27
  */
24
- export declare function SectionLabel({ children, tone, truncate, className, }: {
28
+ export declare function SectionLabel({ children, tone, truncate, folds, className, }: {
25
29
  children: ReactNode;
26
30
  tone?: 'primary' | 'secondary' | 'muted';
27
31
  /** Cut a long label short with an ellipsis instead of letting it wrap or spill. */
28
32
  truncate?: boolean;
33
+ /** The title of a group that opens and closes. In Leaf, Ship's type instead of the micro-label. */
34
+ folds?: boolean;
29
35
  className?: string;
30
36
  }): import("react").JSX.Element;
31
37
  //# sourceMappingURL=SectionLabel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SectionLabel.d.ts","sourceRoot":"","sources":["../src/SectionLabel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,IAAgB,EAChB,QAAgB,EAChB,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,CAAA;IACxC,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BAeA"}
1
+ {"version":3,"file":"SectionLabel.d.ts","sourceRoot":"","sources":["../src/SectionLabel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,IAAgB,EAChB,QAAgB,EAChB,KAAa,EACb,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,CAAA;IACxC,mFAAmF;IACnF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,mGAAmG;IACnG,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BAgBA"}
package/dist/editor.js CHANGED
@@ -470,7 +470,7 @@ function handedOver(doc, before, mapping, unnamed) {
470
470
  const gone = addressedBlocks(before).filter((b) => !b.node.isLeaf && idOf(b.node) !== void 0 && !now.has(idOf(b.node))).map((b) => ({ ...b, start: textStart(b) })).sort((a, b) => a.start - b.start || b.pos - a.pos);
471
471
  for (const old of gone) {
472
472
  const id = idOf(old.node);
473
- const { pos: at, deleted } = mapping.mapResult(old.start, 1);
473
+ const { pos: at, deleted } = mapping.mapResult(old.start, -1);
474
474
  if (deleted) continue;
475
475
  let inner;
476
476
  for (const pos of unnamed) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/SelectionToolbar.tsx", "../src/suggestionPopup.tsx", "../src/BlockNodes.tsx"],
4
- "sourcesContent": ["import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { Editor } from '@tiptap/react'\nimport { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\nimport { IconBold, IconCode, IconItalic, IconLink, IconUnderline } from '@tabler/icons-react'\nimport { Form } from './Form'\nimport { Popover } from './Popover'\nimport { TextInput } from './TextInput'\nimport { Toolbar, ToolbarButton } from './Toolbar'\nimport { shortcutLabel } from './shortcuts'\n\n/**\n * The selection, kept visible while something else has the focus \u2014 Peek's\n * `src/extensions/keptSelection.ts` (PEE-19), moved into the package with the\n * `SelectionToolbar` that needs it (UIG-31, Katerina's ruling 29 September).\n *\n * The toolbar's address field takes the focus, and with it the document's\n * one selection, so the words being linked lost their highlight the moment\n * the field opened (measured 2026-09-07). Linear keeps it, and Katerina asked\n * for the same. The editor's own selection never moved \u2014 ProseMirror keeps it\n * while unfocused \u2014 so this only *draws* it: an inline decoration over the\n * range, in `bg-selected` \u2014 the token Peek's composer paints `::selection` in,\n * mapped through edits and cleared when the field closes.\n *\n * A decoration rather than a second selection, because a document has one\n * and the field needs it. Editor-internal by design (the editor owns its own\n * drawing); nothing here floats, focuses or handles a key.\n */\n\nexport interface KeptRange {\n from: number\n to: number\n}\n\n/** The plugin's key: read what is being kept, or dispatch a change to it. */\nexport const keptSelectionKey = new PluginKey<KeptRange | null>('keptSelection')\n\n/** The extension an editor registers so a selection can stay drawn while a field has the focus. */\nexport const KeptSelection = Extension.create({\n name: 'keptSelection',\n\n addProseMirrorPlugins() {\n return [\n new Plugin<KeptRange | null>({\n key: keptSelectionKey,\n state: {\n init: () => null,\n apply(tr, previous) {\n const meta = tr.getMeta(keptSelectionKey) as KeptRange | null | undefined\n if (meta !== undefined) return meta\n if (!previous) return null\n // Follow the words through an edit, so applying the link to them\n // (or typing before them) does not leave the highlight behind.\n return { from: tr.mapping.map(previous.from), to: tr.mapping.map(previous.to) }\n },\n },\n props: {\n decorations(state) {\n const range = keptSelectionKey.getState(state)\n if (!range || range.from === range.to) return null\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, { class: 'kept-selection bg-bg-selected' }),\n ])\n },\n },\n }),\n ]\n },\n})\n\n/** Draw the selection as it is now, until `releaseSelection`. */\nexport function keepSelection(editor: Editor) {\n const { from, to } = editor.state.selection\n editor.view.dispatch(editor.state.tr.setMeta(keptSelectionKey, { from, to }))\n}\n\n/** Stop drawing it. Nothing kept, nothing dispatched. */\nexport function releaseSelection(editor: Editor) {\n if (editor.isDestroyed || !keptSelectionKey.getState(editor.state)) return\n editor.view.dispatch(editor.state.tr.setMeta(keptSelectionKey, null))\n}\n\n/** What is being drawn, or null. */\nexport function keptSelection(editor: Editor): KeptRange | null {\n return keptSelectionKey.getState(editor.state) ?? null\n}\n\n/** A mark the strip can toggle \u2014 the suite's inline vocabulary (SPEC \u00A713.1) less link and reference. */\nexport type MarkId = 'bold' | 'italic' | 'underline' | 'code'\n\nconst MARKS: Record<MarkId, { label: string; keys: string }> = {\n bold: { label: 'Bold', keys: 'Mod-B' },\n italic: { label: 'Italic', keys: 'Mod-I' },\n underline: { label: 'Underline', keys: 'Mod-U' },\n code: { label: 'Code', keys: 'Mod-E' },\n}\n\n/**\n * Toggle one mark over the current selection. The core `toggleMark`, which\n * each mark's own `toggleBold` and the like wrap: the package names no Tiptap\n * extension, only the marks the suite's format has (SPEC \u00A713.1).\n */\nfunction toggleMark(editor: Editor, id: MarkId): boolean {\n return editor.chain().focus().toggleMark(id).run()\n}\n\n/** The address the selection sits in, or '' \u2014 what the link field opens with. */\nexport function currentLink(editor: Editor): string {\n const href: unknown = editor.getAttributes('link').href\n return typeof href === 'string' ? href : ''\n}\n\n/**\n * What somebody typed into the link field \u2192 an `href`, or null when it cannot\n * be one. A bare host (`google.com`) gets `https://`. A scheme is kept only when\n * SPEC \u00A713.1 lets a reader follow it (http, https, mailto); `javascript:` and\n * the like are refused here. `localhost:5173` is a host and a port: a scheme is\n * never followed by a digit.\n */\nexport function normalizeHref(raw: string): string | null {\n const s = raw.trim()\n if (!s || /\\s/.test(s)) return null\n if (/^(https?:|mailto:)/i.test(s)) return s\n if (/^[a-z][a-z0-9+.-]*:(?!\\d)/i.test(s)) return null\n return `https://${s}`\n}\n\n/**\n * Apply the link field to the selection. Empty removes the link; an address\n * sets it, over the whole link when the caret sits inside one. `false` means\n * the text could not be an address, and the field should stay open.\n */\nexport function applyLink(editor: Editor, raw: string): boolean {\n const chain = editor.chain().focus().extendMarkRange('link')\n // The core commands the Link extension's `setLink` and `unsetLink` wrap, with\n // the same meta; `normalizeHref` has already refused what it would refuse.\n if (!raw.trim()) return chain.unsetMark('link', { extendEmptyMarkRange: true }).setMeta('preventAutolink', true).run()\n const href = normalizeHref(raw)\n if (!href) return false\n return chain.setMark('link', { href }).setMeta('preventAutolink', true).run()\n}\n\n/**\n * Bold / italic / underline / link over selected text \u2014 Peek's\n * `SelectionToolbar` (PEE-18, PEE-19), moved into the package at Katerina's\n * word (29 September, UIG-31) when Ship's description became its second user.\n * Unchanged but for two props: `marks`, which buttons the strip has (Peek's\n * three by default; Ship adds code), and `link`, whether it has the Link button\n * and its field (Peek's editor has a link mark; an editor without one leaves it\n * off). The editor must register `KeptSelection` when `link` is on.\n *\n * The marks apply to a run you have already written, which is why they are\n * not in the `/` menu: typing `/` replaces the selection, so by definition\n * there is nothing selected to apply them to.\n *\n * **The chrome is the shared `Popover`, anchored** (P26). It was a `Menu`, and\n * it never was one: a menu gives its contents roving focus and typeahead,\n * which would fight the link field this panel exists for. An anchored\n * `Popover` is the mode built for exactly this \u2014 it takes no focus, so the\n * caret stays in the text. It is laid out as a row instead of a column\n * through `className`, which `cn` merges last. The panel still portals out of\n * the composer, so no overflow clips it, and Floating UI clamps it on screen.\n *\n * **The anchor is the selection's own point** since 0.12.2. This toolbar is\n * *centred* over the selection (her design, PEE-19, after Linear), and the\n * package's `Popover` aligns left or right only \u2014 Base UI has `center`, the\n * package does not expose it (ADOPTION **B21**). So the placement arithmetic\n * and the hand-measured `SIZE` box stay for now, and the rect handed over is\n * positioned so the panel lands on the same pixel it always has: the\n * package's own 4px gap is subtracted from this file's 8px, and the rect's\n * left edge is the panel's. When `align=\"center\"` exists, `SIZE`, `GAP` and\n * `placement` all go and the anchor becomes the selection's own rect.\n *\n * **Two faces** (PEE-19 \u2014 Katerina, 2026-09-07, after Linear). `marks` is the\n * row of four buttons. `link` is one text field for the address: it opens from\n * the Link button, and opens by itself when the settled selection sits inside\n * a link, with that link's address in it. There is no open, embed or delete\n * button, by her call: the field is the whole control.\n *\n * **What applies the field** (her rule, same day): Enter, and clicking away \u2014\n * anywhere outside the toolbar, the editor included. Enter on an empty field\n * removes the link; clicking away with an empty field changes nothing, since\n * removing is deliberate and leaving is not. Escape drops what was typed and\n * puts the caret back. Once applied, the caret lands after the link, and\n * typing goes on as plain text \u2014 the mark is not inclusive (editorKit).\n *\n * While the field has focus the editor has not, so everything below that\n * hides the toolbar on blur or on an empty editor selection stands down in\n * `link` mode. The editor keeps its selection while unfocused; `focus()` in\n * the apply command puts it back.\n *\n * **Why it waits.** Showing on every selection change put the toolbar under the\n * pointer mid-drag, and made it blink on a plain click \u2014 ProseMirror restores\n * the previous selection when the editor takes focus and collapses it a tick\n * later, so there is a real, non-empty range in between. Both go away by\n * refusing to show while the pointer is down and re-reading the selection after\n * a short settle rather than trusting the event that woke us.\n */\n\nconst ICONS: Record<MarkId, typeof IconBold> = {\n bold: IconBold,\n italic: IconItalic,\n underline: IconUnderline,\n code: IconCode,\n}\n\ntype Mode = 'marks' | 'link'\n\n/** How long a selection must stand still before the toolbar appears. */\nconst SETTLE_MS = 250\nconst GAP = 8\n/** The gap the package's `Popover` adds of its own, subtracted above so the\n * panel keeps this file's 8px. Read from `Popover.tsx`, not guessed. */\nconst POPOVER_GAP = 4\n\nexport interface SelectionToolbarProps {\n /** The editor whose selection the strip follows. `null` while it loads. */\n editor: Editor | null\n /** Which marks the strip toggles, in order. Default bold, italic, underline. */\n marks?: readonly MarkId[]\n /** The Link button and its address field. Needs a link mark in the editor and `KeptSelection`. Default off. */\n link?: boolean\n}\n\nexport function SelectionToolbar({ editor, marks = ['bold', 'italic', 'underline'], link = false }: SelectionToolbarProps) {\n /** The selection's top edge and horizontal centre; the face decides the rest. */\n const [anchor, setAnchor] = useState<{ top: number; centre: number } | null>(null)\n const [mode, setMode] = useState<Mode>('marks')\n const [url, setUrl] = useState('')\n // Re-render as marks turn on and off while the caret moves through the\n // selection, so the pressed states stay honest.\n const [, setTick] = useState(0)\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const pointerDown = useRef(false)\n const field = useRef<HTMLInputElement>(null)\n /** What the field holds while it is open; null while it is closed. The\n * listeners below are bound once, so they read this rather than state. */\n const draft = useRef<string | null>(null)\n // Read by the same listeners. Kept in an effect, declared before the focus\n // effect below so the ref already says `link` when the field's focus blurs\n // the editor.\n const modeRef = useRef<Mode>('marks')\n useEffect(() => {\n modeRef.current = mode\n }, [mode])\n // Read by `settle`, which is bound once: words already a link open the field only when there is one.\n const linkOn = useRef(link)\n linkOn.current = link\n\n const hide = useCallback(() => {\n clearTimeout(timer.current)\n draft.current = null\n if (editor) releaseSelection(editor)\n setAnchor(null)\n setMode('marks')\n }, [editor])\n\n /**\n * Leaving the field applies it (Enter or a click away). An address links the\n * words and the caret moves past them; an empty field on the way out is a\n * shrug, not a removal. Idempotent: the outside click reaches this twice,\n * once from the editor press and once from the Menu's own listener.\n */\n const commit = useCallback(() => {\n const text = draft.current\n if (text === null || !editor) return\n draft.current = null\n if (text.trim() && applyLink(editor, text)) {\n editor.commands.setTextSelection(editor.state.selection.to)\n }\n hide()\n }, [editor, hide])\n\n /** Read the selection as it is *now* and place the toolbar, or hide it. */\n const settle = useCallback(() => {\n if (!editor || editor.isDestroyed) return\n // Keeping the selection drawn is itself a transaction, which schedules\n // this; by then the field has the focus, and reading `hasFocus()` here\n // would close it.\n if (modeRef.current === 'link') return\n const { state, view } = editor\n const { empty, from, to } = state.selection\n if (empty || from === to || !view.hasFocus() || pointerDown.current) {\n setAnchor(null)\n setMode('marks')\n return\n }\n const start = view.coordsAtPos(from)\n const end = view.coordsAtPos(to)\n const centre = (Math.min(start.left, end.left) + Math.max(start.right, end.right)) / 2\n // Words that are already a link open on the field, with their address in it.\n const inLink = linkOn.current && editor.isActive('link')\n const address = inLink ? currentLink(editor) : ''\n draft.current = inLink ? address : null\n // The field is about to take the focus; the words stay highlighted.\n if (inLink) keepSelection(editor)\n setMode(inLink ? 'link' : 'marks')\n setUrl(address)\n setAnchor({ top: start.top, centre })\n }, [editor])\n\n useEffect(() => {\n if (!editor) return\n\n const schedule = () => {\n // The field owns the keyboard and the focus; the editor's own events\n // stand down until it closes.\n if (modeRef.current === 'link') return\n setTick((t) => t + 1)\n clearTimeout(timer.current)\n // A collapsed selection hides at once \u2014 waiting to hide is what made the\n // click blink. Only *showing* is deferred.\n if (editor.state.selection.empty) {\n setAnchor(null)\n return\n }\n timer.current = setTimeout(settle, SETTLE_MS)\n }\n\n // The field taking focus is the one blur that must not hide us.\n const onBlur = () => {\n if (modeRef.current !== 'link') hide()\n }\n\n // Only a press *inside the editor* starts a selection. A press on the\n // toolbar itself must not hide it \u2014 doing so unmounted the button before\n // its click could land, so nothing ever applied.\n const startsSelection = (e: MouseEvent) =>\n e.target instanceof Node && editor.view.dom.contains(e.target)\n\n const onPointerDown = (e: MouseEvent) => {\n if (!startsSelection(e)) return\n // A press in the editor while the field is open is a click away: apply\n // first, before the editor moves the caret under the press.\n commit()\n pointerDown.current = true\n hide()\n }\n const onPointerUp = () => {\n if (!pointerDown.current) return\n pointerDown.current = false\n schedule()\n }\n\n editor.on('selectionUpdate', schedule)\n editor.on('transaction', schedule)\n editor.on('blur', onBlur)\n // @estiva-escape(no-hand-rolled-behaviour): watches presses in the editor to follow a text selection; the Popover it opens in has no trigger to own that (Peek's UIG-8 escape, moved here with the part, UIG-31)\n document.addEventListener('mousedown', onPointerDown, true)\n document.addEventListener('mouseup', onPointerUp, true)\n\n return () => {\n clearTimeout(timer.current)\n editor.off('selectionUpdate', schedule)\n editor.off('transaction', schedule)\n editor.off('blur', onBlur)\n document.removeEventListener('mousedown', onPointerDown, true)\n document.removeEventListener('mouseup', onPointerUp, true)\n }\n }, [editor, settle, hide, commit])\n\n // The field takes focus once it is on screen. Not `autoFocus`, and not in\n // the same frame: the Menu renders once hidden to measure itself, and a\n // hidden input cannot take focus \u2014 measured 2026-09-07, the field opened\n // unfocused on a re-selected link. An address already in it is selected\n // whole, so typing replaces it.\n useEffect(() => {\n if (mode !== 'link' || !anchor) return\n const frame = requestAnimationFrame(() => {\n field.current?.focus()\n field.current?.select()\n })\n return () => cancelAnimationFrame(frame)\n }, [mode, anchor])\n\n if (!editor || !anchor) return null\n\n const openField = () => {\n const address = currentLink(editor)\n draft.current = address\n keepSelection(editor)\n setUrl(address)\n setMode('link')\n }\n /** Enter. Unlike a click away, an empty field here means remove. */\n const submit = () => {\n if (!url.trim()) {\n applyLink(editor, '')\n hide()\n editor.commands.focus()\n return\n }\n commit()\n }\n const discard = () => {\n hide()\n editor.commands.focus()\n }\n\n /* A zero-sized rect at the selection's own top edge and centre. The panel's\n width is no longer this file's business: `align=\"center\"` puts the\n panel's middle over this point (B21, package 0.12.2), so the measured\n `SIZE` table and the placement arithmetic that stood in for it are gone.\n The top is this file's GAP less the package's own 4, so the panel keeps\n the 8px it has always had. */\n const anchorRect = new DOMRect(anchor.centre, anchor.top - (GAP - POPOVER_GAP), 0, 0)\n\n return (\n <Popover\n anchor={anchorRect}\n open\n side=\"top\"\n align=\"center\"\n onOpenChange={(next) => {\n if (!next) commit()\n }}\n ariaLabel=\"Text formatting\"\n // The panel gives up its column; the strip inside draws the row. The 4px\n // around it is `contentClassName`'s: on `className` it was added to the\n // package's 8px from 0.12.6 on, and the toolbar grew 8px a side\n // (Katerina, 2026-09-13; migration docs D68, Finding 60). This is the\n // package's own `Popover/A toolbar` story, class for class.\n className=\"w-auto min-w-0\"\n contentClassName=\"p-1\"\n >\n {mode === 'link' ? (\n /* One field, so no strip: a toolbar of one control is a toolbar in\n name only, and `ToolbarInput` cannot take the ref this field is\n focused by (ADOPTION B25). */\n // The package Form (UIG-7): Enter applies the link. `free` and `flex`: one field, which keeps its own height.\n <Form onSubmit={submit} layout=\"free\" className=\"flex\">\n <TextInput\n ref={field}\n value={url}\n onChange={(e) => {\n setUrl(e.target.value)\n draft.current = e.target.value\n }}\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n // Before the Menu's own Escape, which would apply instead.\n e.stopPropagation()\n discard()\n }\n }}\n placeholder=\"Enter link URL\"\n aria-label=\"Link URL\"\n className=\"w-[264px]\"\n />\n </Form>\n ) : (\n /* Four marks and a link button: one Tab stop, \u2190 \u2192 along them (D29). */\n <Toolbar aria-label=\"Text formatting\" surface={false}>\n {marks.map((id) => {\n const Icon = ICONS[id]\n const active = editor.isActive(id)\n return (\n <ToolbarButton\n key={id}\n aria-label={MARKS[id].label}\n pressed={active}\n tooltip={MARKS[id].label}\n tooltipShortcut={shortcutLabel(MARKS[id].keys)}\n // Keep the selection: focus must not leave the editor on press.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => toggleMark(editor, id)}\n >\n <Icon size={16} stroke={1.5} />\n </ToolbarButton>\n )\n })}\n {link && (\n <ToolbarButton\n aria-label=\"Link\"\n tooltip=\"Link\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={openField}\n >\n <IconLink size={16} stroke={1.5} />\n </ToolbarButton>\n )}\n </Toolbar>\n )}\n </Popover>\n )\n}\n", "// @estiva-escape(component-has-a-page, component-has-a-story): draws nothing of its own \u2014 it opens a SuggestionMenu from a Tiptap suggestion plugin, and SuggestionMenu's page and stories show it (UIG-31)\nimport { forwardRef, useRef, useImperativeHandle } from 'react'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionKeyDownProps, SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport { SuggestionMenu, type SuggestionMenuHandle, type SuggestionMenuRow, type SuggestionMenuSection } from './SuggestionMenu'\n\n/*\n * Is a type-ahead list open? \u2014 Peek's counter (`mention.tsx`), moved here with\n * the lists it counts (UIG-31).\n *\n * A counter rather than a boolean: two suggestion plugins can overlap for a\n * moment while one hands over to the other (`/` choosing \"Mention\" types `@`).\n * An editor asks before it acts on Enter or Escape, so a key that chose a row\n * does not also send the message or cancel the edit.\n */\nlet openCount = 0\nlet lastClose = 0\n\n/**\n * Open, or closed less than 100ms ago \u2014 what an Enter handler asks. Choosing a\n * row with Enter closes the list, and that same Enter must not also send.\n */\nexport function isSuggestionActive(): boolean {\n return openCount > 0 || Date.now() - lastClose < 100\n}\n\n/**\n * Open *now*, with no grace period \u2014 what an Escape handler asks. The first\n * Escape closes the list, the second is somebody's deliberate press.\n */\nexport function isSuggestionOpen(): boolean {\n return openCount > 0\n}\n\n/** How a list looks and what fills it \u2014 everything but the editor's own wiring. */\nexport interface SuggestionPopupOptions<T> {\n /** Names the list \u2014 \"Commands\", \"People\". */\n ariaLabel: string\n /** The box's width, e.g. `w-[300px]`. */\n width: string\n /** The cap before the rows scroll, e.g. `max-h-[400px]`. */\n maxHeight: string\n /** The plugin's items, and the typed query, \u2192 the sections to draw. One unlabeled section by default. */\n sections?: (items: T[], query: string) => readonly SuggestionMenuSection<T>[]\n itemKey: (item: T) => string\n row: (item: T) => SuggestionMenuRow\n}\n\ntype Props<T> = SuggestionProps<T> & { options: SuggestionPopupOptions<T> }\n\n// `forwardRef`, so Tiptap's `ReactRenderer` hands its `ref` through whichever\n// way it detects a component that takes one.\nconst Popup = forwardRef<SuggestionMenuHandle, Props<unknown>>(function Popup({ options, ...props }, handle) {\n const menu = useRef<SuggestionMenuHandle>(null)\n useImperativeHandle(handle, () => ({ onKeyDown: (event) => menu.current?.onKeyDown(event) ?? false }))\n const sections = options.sections ? options.sections(props.items, props.query) : [{ items: props.items }]\n return (\n <SuggestionMenu\n handle={menu}\n rect={props.clientRect?.() ?? null}\n editorElement={props.editor.view.dom}\n ariaLabel={options.ariaLabel}\n width={options.width}\n maxHeight={options.maxHeight}\n sections={sections}\n itemKey={options.itemKey}\n row={options.row}\n onSelect={(item) => props.command(item)}\n />\n )\n})\n\n/**\n * The `render` of a Tiptap suggestion plugin, drawn as a `SuggestionMenu` \u2014\n * the popup controller Peek wrote four times (`mention.tsx`, `slashCommands.tsx`),\n * once.\n *\n * Suggestion({ editor, char: '/', items, command,\n * render: suggestionPopup({ ariaLabel: 'Commands', width: 'w-[300px]',\n * maxHeight: 'max-h-[400px]', itemKey, row, sections }) })\n *\n * It mounts the list when the query starts and removes it when it ends: the\n * plugin owns the list's life. Escape closes it and is used up; \u2191, \u2193 and Enter\n * go to the list; every other key stays the editor's. The list portals itself\n * (the package `Popover`), so the mount point is only a mount point.\n */\nexport function suggestionPopup<T>(options: SuggestionPopupOptions<T>): NonNullable<SuggestionOptions<T>['render']> {\n return () => {\n let component: ReactRenderer<SuggestionMenuHandle, Props<T>> | null = null\n let container: HTMLDivElement | null = null\n const exit = () => {\n if (!component) return\n openCount = Math.max(0, openCount - 1)\n lastClose = Date.now()\n component.destroy()\n container?.remove()\n component = null\n container = null\n }\n return {\n onStart: (props) => {\n openCount++\n component = new ReactRenderer<SuggestionMenuHandle, Props<T>>(Popup as never, { props: { ...props, options }, editor: props.editor })\n container = document.createElement('div')\n document.body.appendChild(container)\n container.appendChild(component.element)\n },\n onUpdate: (props) => component?.updateProps({ ...props, options }),\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'Escape') {\n exit()\n return true\n }\n return component?.ref?.onKeyDown(event) ?? false\n },\n onExit: exit,\n }\n }\n}\n", "import type { ComponentType } from 'react'\nimport { Extension, Node } from '@tiptap/core'\nimport type { Node as ProseMirrorNode, Slice } from '@tiptap/pm/model'\nimport { Plugin, PluginKey, type Transaction } from '@tiptap/pm/state'\nimport { Mapping, ReplaceAroundStep } from '@tiptap/pm/transform'\nimport { NodeViewWrapper, ReactNodeViewRenderer, type ReactNodeViewProps } from '@tiptap/react'\n\n/*\n * The four nodes an editor of a block document needs that a stock schema does\n * not have (SPEC \u00A713.3) \u2014 moved here from Ship's `editorSchema.tsx` (RIC-14,\n * SHI-2) when Peek became the second editor of the same documents (MAN-9).\n *\n * They are one half of a pair. The other half is `@estiva-app/protocol`'s\n * `toEditorDocument` / `fromEditorDocument`, which turn a stored document into\n * the JSON these nodes read and back: the node names (`unknownBlock`,\n * `reference`, `attachment`) and the `blockId` attribute are that translation's,\n * and must not drift from it. This package does not import protocol, as\n * `RichText` does not: the translation hands over everything a view draws.\n *\n * Everything else a document can contain is already a ProseMirror node of the\n * same name \u2014 StarterKit's and TableKit's \u2014 which is why this file is short.\n */\n\n/**\n * The block types \u00A713.3 puts an id on, as the editor's node names \u2014\n * protocol's `EDITOR_BLOCK_TYPES`, less `unknownBlock`, which declares its own.\n */\nconst BLOCK_TYPES = [\n 'paragraph',\n 'heading',\n 'bulletList',\n 'orderedList',\n 'listItem',\n 'blockquote',\n 'codeBlock',\n 'horizontalRule',\n // A table and everything inside it (UIG-18): a row or a cell whose id was\n // re-minted on each save would detach any comment anchored to it.\n 'table',\n 'tableRow',\n 'tableHeader',\n 'tableCell',\n // Without this an attachment's id is re-minted on every save, and every\n // comment anchored to it stops resolving.\n 'attachment',\n]\nconst BLOCK_TYPE_SET = new Set(BLOCK_TYPES)\n\n/** protocol's `newBlockId`: six random bytes as hex. */\nfunction newBlockId(): string {\n return Array.from(crypto.getRandomValues(new Uint8Array(6)), (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\n/** Whether anything typed or inserted is inside \u2014 protocol's `holdsText`. An empty paragraph is not; a lone line break is. */\nfunction holdsText(node: ProseMirrorNode): boolean {\n let found = false\n node.descendants((child) => {\n if (found) return false\n if ((child.isText && child.text !== '') || child.type.name === 'hardBreak' || child.type.name === 'reference') found = true\n return !found\n })\n return found\n}\n\ninterface Claim {\n pos: number\n node: ProseMirrorNode\n /** An unknown block: the translation hands its source back untouched, so re-minting it here would be undone on save. */\n fixed: boolean\n}\n\n/** Every block that claims an id, by id, in document order. */\nfunction claimsIn(doc: ProseMirrorNode): Map<string, Claim[]> {\n const claims = new Map<string, Claim[]>()\n doc.descendants((node, pos) => {\n const fixed = node.type.name === 'unknownBlock'\n if (!fixed && !BLOCK_TYPE_SET.has(node.type.name)) return\n const id = node.attrs.blockId\n if (typeof id !== 'string' || id === '') return\n const copies = claims.get(id)\n if (copies) copies.push({ pos, node, fixed })\n else claims.set(id, [{ pos, node, fixed }])\n })\n return claims\n}\n\n/**\n * The copy the block that was there before carried on into: where the start\n * of its content landed. Enter at the very start pushes that content into the\n * lower half; a split anywhere else leaves it in the upper; a pasted copy,\n * above or below, never moves it out of the original.\n */\nfunction carriedOn(copies: Claim[], before: Claim, mapping: Mapping): Claim | undefined {\n if (before.node.isLeaf) {\n const at = mapping.map(before.pos, 1)\n return copies.find((c) => c.pos === at)\n }\n const at = mapping.map(textStart(before), 1)\n return copies.find((c) => c.pos < at && at < c.pos + c.node.nodeSize)\n}\n\n/** The start of a block's text, inside the paragraph a list item or quote wraps it in. */\nfunction textStart({ pos, node }: { pos: number; node: ProseMirrorNode }): number {\n let start = pos + 1\n if (!node.isTextblock) {\n let found = false\n node.descendants((child, at) => {\n if (found) return false\n if (child.isTextblock) {\n start = pos + 1 + at + 1\n found = true\n }\n return !found\n })\n }\n return start\n}\n\n/**\n * Where a block claims an id another block keeps \u2014 see {@link BlockId}. An\n * unknown block always keeps its id; otherwise the copy the original carried\n * on into, and failing that protocol's rule: the first that holds text, or\n * the first.\n */\nfunction repeatedBlockIds(doc: ProseMirrorNode, before: ProseMirrorNode, mapping: Mapping): number[] {\n const claims = claimsIn(doc)\n let previous: Map<string, Claim[]> | undefined\n const losers: number[] = []\n for (const [id, copies] of claims) {\n if (copies.length < 2) continue\n previous ??= claimsIn(before)\n const original = previous.get(id)?.[0]\n const owner =\n copies.find((c) => c.fixed) ??\n (original && carriedOn(copies, original, mapping)) ??\n copies.find((c) => holdsText(c.node)) ??\n copies[0]\n for (const copy of copies) if (copy !== owner && !copy.fixed) losers.push(copy.pos)\n }\n return losers\n}\n\n/**\n * The blocks a save turns into blocks \u2014 exactly what protocol's\n * `blockFromEditor` walks \u2014 so a list, a table and its rows and cells are\n * walked into, and a list item or a quote is not: the save flattens their\n * paragraph wrappers into inline runs, so an id there would be dropped.\n */\nconst BLOCK_CONTAINERS = new Set(['bulletList', 'orderedList', 'table', 'tableRow', 'tableHeader', 'tableCell'])\n\n/** The wrappers the save flattens: their paragraphs become inline runs of theirs. */\nconst FLATTENED = new Set(['listItem', 'blockquote'])\n\nconst idOf = (node: ProseMirrorNode): string | undefined => {\n const id = node.attrs.blockId\n return typeof id === 'string' && id !== '' ? id : undefined\n}\n\n/** Every block the save turns into a block, in document order. */\nfunction addressedBlocks(doc: ProseMirrorNode): { pos: number; node: ProseMirrorNode }[] {\n const out: { pos: number; node: ProseMirrorNode }[] = []\n const visit = (node: ProseMirrorNode, pos: number) => {\n if (!BLOCK_TYPE_SET.has(node.type.name)) return\n out.push({ pos, node })\n if (BLOCK_CONTAINERS.has(node.type.name)) node.forEach((child, offset) => visit(child, pos + 1 + offset))\n }\n doc.forEach((node, offset) => visit(node, offset))\n return out\n}\n\n/** Where a block the save would give an id has none, so it would get a new one on every save. */\nfunction unnamedBlocks(doc: ProseMirrorNode): number[] {\n return addressedBlocks(doc)\n .filter((b) => idOf(b.node) === undefined)\n .map((b) => b.pos)\n}\n\n/** Where a paragraph inside a list item or a quote claims an id \u2014 which the save drops. */\nfunction hiddenIds(doc: ProseMirrorNode): number[] {\n const out: number[] = []\n doc.descendants((node, pos) => {\n if (!FLATTENED.has(node.type.name)) return\n node.forEach((child, offset) => {\n if (child.isTextblock && idOf(child) !== undefined) out.push(pos + 1 + offset)\n })\n })\n return out\n}\n\n/**\n * The id each block a wrap or a lift brought in takes over \u2014 see\n * {@link BlockId}. An id the save addressed before the change and does not\n * now goes to the innermost new block its text was carried into: the quote or\n * the list item around a paragraph, or the paragraph lifted out of one.\n * Text the change deleted hands nothing on.\n */\nfunction handedOver(doc: ProseMirrorNode, before: ProseMirrorNode, mapping: Mapping, unnamed: number[]): Map<number, string> {\n const out = new Map<number, string>()\n if (unnamed.length === 0) return out\n const now = new Set(addressedBlocks(doc).map((b) => idOf(b.node)))\n // By where their text starts, and of a list and its item around the same text, the item first.\n const gone = addressedBlocks(before)\n .filter((b) => !b.node.isLeaf && idOf(b.node) !== undefined && !now.has(idOf(b.node)))\n .map((b) => ({ ...b, start: textStart(b) }))\n .sort((a, b) => a.start - b.start || b.pos - a.pos)\n for (const old of gone) {\n const id = idOf(old.node)!\n const { pos: at, deleted } = mapping.mapResult(old.start, 1)\n if (deleted) continue\n let inner: number | undefined\n for (const pos of unnamed) {\n if (out.has(pos)) continue\n if (pos < at && at < pos + doc.nodeAt(pos)!.nodeSize && (inner === undefined || pos > inner)) inner = pos\n }\n if (inner !== undefined) {\n out.set(inner, id)\n now.add(id)\n }\n }\n return out\n}\n\n/**\n * Whether a change could have made a repeat or a block without an id: only a\n * step that brings in blocks can, or one that wraps or lifts them \u2014 lifting\n * the paragraph out of a quote or a one-item list brings in nothing, yet\n * leaves a block the save addresses with the wrapper's missing id. Typing,\n * deleting and marks do neither, so most keystrokes skip the walk.\n */\nfunction bringsInBlocks(transactions: readonly Transaction[]): boolean {\n return transactions.some((tr) =>\n tr.steps.some((step) => {\n if (step instanceof ReplaceAroundStep) return true\n const slice = (step as { slice?: Slice }).slice\n let blocks = false\n slice?.content.forEach((node) => {\n if (node.isBlock) blocks = true\n })\n return blocks\n }),\n )\n}\n\n/**\n * `id` on every block, carried as the `blockId` attribute.\n *\n * A global attribute rather than one per node type, because \u00A713.3 puts an id\n * on **every** block and a type that quietly lacked one would mint a fresh id\n * on every save \u2014 detaching each anchored comment on it (RFC 0.4 \u00A76) with\n * nothing on screen to say so.\n *\n * Rendered as `data-block-id`, which is what `RichText` already emits, so the\n * reading and the editing surface address a block the same way.\n *\n * **A split gives the new half its own id, the moment it happens** (MAN-10).\n * ProseMirror copies a split node's attrs to both halves, so Enter leaves two\n * blocks claiming one id. Enter at the very start of a paragraph puts the\n * copy *above* it \u2014 and once something is typed there, nothing at save time\n * can tell which half was the original, so protocol's `fromEditorDocument`\n * gave the id, and every comment anchored to it, to the new line. Settled\n * here instead, in the same transaction, where the change itself says which\n * copy the original carried on into \u2014 so a copy pasted above a block does\n * not take its id either. The others get fresh ids. `fromEditorDocument`\n * keeps its own rule (the copy that holds text) as the backstop for an\n * editor without this extension; where the change says nothing, this falls\n * back to that rule too.\n *\n * **A block that arrives without an id gets one, the moment it arrives**\n * (MAN-11): a paste, a `/` menu insert, the empty paragraph a new editor\n * starts with and the one StarterKit keeps at the end. `fromEditorDocument`\n * mints an id for such a block on every save and the editor never sees it, so\n * each save gave it a different one \u2014 a blur with no edit published again, and\n * a comment anchored to it in between was detached. The editor's first\n * document is filled in when it is created, outside the undo history and\n * without an update, so opening a description is not an edit.\n *\n * **A wrap or a lift hands the id on** (MAN-12). Turning a paragraph into a\n * quote or a list wraps it, and ProseMirror leaves its attrs on the paragraph\n * inside \u2014 where the save flattens it and drops the id, so the quote or list\n * item was published under a new one and every comment anchored to the\n * paragraph was detached. The quote or the list item takes the paragraph's id\n * instead, and the list around it a fresh one; lifting the text back out hands\n * the id to the paragraph again. The paragraph inside keeps no id, as one\n * loaded from a stored document has none.\n */\nexport const BlockId = Extension.create({\n name: 'blockId',\n onCreate() {\n const { state, view } = this.editor\n const unnamed = unnamedBlocks(state.doc)\n if (unnamed.length === 0) return\n const tr = state.tr\n for (const pos of unnamed) tr.setNodeAttribute(pos, 'blockId', newBlockId())\n view.dispatch(tr.setMeta('addToHistory', false).setMeta('preventUpdate', true))\n },\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('blockIdSplit'),\n appendTransaction: (transactions, old, state) => {\n if (!bringsInBlocks(transactions)) return null\n const mapping = new Mapping()\n for (const tr of transactions) mapping.appendMapping(tr.mapping)\n const unnamed = unnamedBlocks(state.doc)\n const handed = handedOver(state.doc, old.doc, mapping, unnamed)\n const ids = new Map<number, string | null>()\n for (const pos of repeatedBlockIds(state.doc, old.doc, mapping)) ids.set(pos, newBlockId())\n for (const pos of unnamed) ids.set(pos, handed.get(pos) ?? newBlockId())\n for (const pos of hiddenIds(state.doc)) ids.set(pos, null)\n if (ids.size === 0) return null\n const tr = state.tr\n for (const [pos, id] of ids) tr.setNodeAttribute(pos, 'blockId', id)\n return tr\n },\n }),\n ]\n },\n addGlobalAttributes() {\n return [\n {\n types: BLOCK_TYPES,\n attributes: {\n blockId: {\n default: null,\n parseHTML: (element: HTMLElement) => element.getAttribute('data-block-id'),\n renderHTML: (attributes: Record<string, unknown>) =>\n attributes.blockId ? { 'data-block-id': String(attributes.blockId) } : {},\n },\n },\n },\n ]\n },\n})\n\n/** What a reference's view is handed: the `nostr:` URI it stands for. */\nexport interface ReferenceViewProps {\n uri: string\n}\n\nexport interface ReferenceNodeOptions {\n /**\n * Draws the reference as the thing it points at \u2014 the app's own chip, which\n * knows how to resolve it. Without one, the URI is drawn as text.\n */\n view: ComponentType<ReferenceViewProps> | null\n}\n\n/**\n * A `nostr:` reference, drawn as the thing it points at \u2014 SPEC \u00A713.1.\n *\n * An **atom**: it has no editable interior, because its text is the URI and\n * nobody edits half a `naddr`. Selecting it selects the whole reference, which\n * is also how deleting one works. `marks` carries the run's other marks, so a\n * bold reference comes back bold.\n */\nexport const ReferenceNode = Node.create<ReferenceNodeOptions>({\n name: 'reference',\n group: 'inline',\n inline: true,\n atom: true,\n selectable: true,\n draggable: false,\n addOptions() {\n return { view: null }\n },\n addAttributes() {\n return {\n uri: { default: '' },\n marks: { default: [] as string[] },\n }\n },\n parseHTML() {\n return [{ tag: 'span[data-reference]' }]\n },\n renderHTML({ node }) {\n // A copy to the clipboard as HTML, and the whole drawing when no view is given.\n return ['span', { 'data-reference': String(node.attrs.uri) }, String(node.attrs.uri)]\n },\n addNodeView() {\n const View = this.options.view\n if (!View) return null\n return ReactNodeViewRenderer(({ node }: ReactNodeViewProps) => (\n <NodeViewWrapper as=\"span\" className=\"inline\">\n <View uri={String(node.attrs.uri ?? '')} />\n </NodeViewWrapper>\n ))\n },\n})\n\n/**\n * A block this editor has no design for, kept whole.\n *\n * \u00A713.3: a reader MUST render an unknown block's inline text and MUST NOT drop\n * it silently. **An editor is the dangerous case** \u2014 ProseMirror discards a\n * node its schema does not know, and the save after that would delete somebody\n * else's block with no error anywhere on the way. So the block's own JSON\n * rides in `source`, and protocol's `fromEditorDocument` hands it back\n * untouched; `text` is its inline text, worked out by `toEditorDocument`.\n *\n * Not editable, deliberately: an affordance that lets a person change\n * something they cannot see is worse than one that says the content is there\n * and belongs to another surface.\n */\nexport const UnknownBlock = Node.create({\n name: 'unknownBlock',\n group: 'block',\n atom: true,\n selectable: true,\n addAttributes() {\n return {\n blockId: { default: null },\n source: { default: null },\n text: { default: '' },\n }\n },\n parseHTML() {\n return [{ tag: 'div[data-unknown-block]' }]\n },\n renderHTML({ node }) {\n return ['div', { 'data-unknown-block': String(node.attrs.blockId ?? '') }, String(node.attrs.text ?? '')]\n },\n addNodeView() {\n return ReactNodeViewRenderer(UnknownBlockView)\n },\n})\n\nfunction UnknownBlockView({ node }: ReactNodeViewProps) {\n const source = node.attrs.source as { id?: string; type?: string } | null\n const text = String(node.attrs.text ?? '')\n return (\n <NodeViewWrapper\n data-block-id={source?.id}\n data-block-type={source?.type}\n className=\"rounded-md border border-border-default bg-bg-inset px-2 py-1 text-text-secondary\"\n >\n {text || `A ${source?.type ?? 'block'} this app cannot edit yet.`}\n </NodeViewWrapper>\n )\n}\n\n/** What an attachment's view is handed: the block's `imeta` fields. */\nexport interface AttachmentViewProps {\n attrs: Record<string, unknown>\n}\n\nexport interface AttachmentNodeOptions {\n /** Draws the file \u2014 the app's own attachment card. Without one, its name is drawn as text. */\n view: ComponentType<AttachmentViewProps> | null\n}\n\n/**\n * A file placed in a document \u2014 SPEC \u00A713.3's `attachment` block (SHI-2).\n *\n * An atom: the file is the block, there is nothing inside it to edit, and a\n * caret that could sit \"inside\" an image is a way to produce documents no\n * reader has a rule for. Selectable and deletable, so removing one is Backspace\n * like anything else.\n *\n * Its attributes are the `imeta` fields exactly, because that is what \u00A713.3\n * specifies. Each is declared, and that is not boilerplate: ProseMirror **drops\n * an attribute its schema does not name**, so an undeclared `thumb` would\n * survive the editor and vanish on save \u2014 the silent deletion `UnknownBlock`\n * exists to prevent.\n */\nexport const AttachmentNode = Node.create<AttachmentNodeOptions>({\n name: 'attachment',\n group: 'block',\n atom: true,\n selectable: true,\n draggable: false,\n addOptions() {\n return { view: null }\n },\n addAttributes() {\n return {\n url: { default: '' },\n m: { default: '' },\n x: { default: '' },\n size: { default: 0 },\n dim: { default: null },\n thumb: { default: null },\n alt: { default: null },\n filename: { default: null },\n }\n },\n parseHTML() {\n return [{ tag: 'div[data-attachment-block]' }]\n },\n renderHTML({ node }) {\n // A copy to the clipboard, and the whole drawing when no view is given.\n return ['div', { 'data-attachment-block': String(node.attrs.x ?? '') }, String(node.attrs.filename ?? 'attachment')]\n },\n addNodeView() {\n const View = this.options.view\n if (!View) return null\n return ReactNodeViewRenderer(({ node }: ReactNodeViewProps) => (\n <NodeViewWrapper className=\"my-2\">\n <View attrs={node.attrs} />\n </NodeViewWrapper>\n ))\n },\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { Editor } from '@tiptap/react'\nimport { Extension } from '@tiptap/core'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\nimport { IconBold, IconCode, IconItalic, IconLink, IconUnderline } from '@tabler/icons-react'\nimport { Form } from './Form'\nimport { Popover } from './Popover'\nimport { TextInput } from './TextInput'\nimport { Toolbar, ToolbarButton } from './Toolbar'\nimport { shortcutLabel } from './shortcuts'\n\n/**\n * The selection, kept visible while something else has the focus \u2014 Peek's\n * `src/extensions/keptSelection.ts` (PEE-19), moved into the package with the\n * `SelectionToolbar` that needs it (UIG-31, Katerina's ruling 29 September).\n *\n * The toolbar's address field takes the focus, and with it the document's\n * one selection, so the words being linked lost their highlight the moment\n * the field opened (measured 2026-09-07). Linear keeps it, and Katerina asked\n * for the same. The editor's own selection never moved \u2014 ProseMirror keeps it\n * while unfocused \u2014 so this only *draws* it: an inline decoration over the\n * range, in `bg-selected` \u2014 the token Peek's composer paints `::selection` in,\n * mapped through edits and cleared when the field closes.\n *\n * A decoration rather than a second selection, because a document has one\n * and the field needs it. Editor-internal by design (the editor owns its own\n * drawing); nothing here floats, focuses or handles a key.\n */\n\nexport interface KeptRange {\n from: number\n to: number\n}\n\n/** The plugin's key: read what is being kept, or dispatch a change to it. */\nexport const keptSelectionKey = new PluginKey<KeptRange | null>('keptSelection')\n\n/** The extension an editor registers so a selection can stay drawn while a field has the focus. */\nexport const KeptSelection = Extension.create({\n name: 'keptSelection',\n\n addProseMirrorPlugins() {\n return [\n new Plugin<KeptRange | null>({\n key: keptSelectionKey,\n state: {\n init: () => null,\n apply(tr, previous) {\n const meta = tr.getMeta(keptSelectionKey) as KeptRange | null | undefined\n if (meta !== undefined) return meta\n if (!previous) return null\n // Follow the words through an edit, so applying the link to them\n // (or typing before them) does not leave the highlight behind.\n return { from: tr.mapping.map(previous.from), to: tr.mapping.map(previous.to) }\n },\n },\n props: {\n decorations(state) {\n const range = keptSelectionKey.getState(state)\n if (!range || range.from === range.to) return null\n return DecorationSet.create(state.doc, [\n Decoration.inline(range.from, range.to, { class: 'kept-selection bg-bg-selected' }),\n ])\n },\n },\n }),\n ]\n },\n})\n\n/** Draw the selection as it is now, until `releaseSelection`. */\nexport function keepSelection(editor: Editor) {\n const { from, to } = editor.state.selection\n editor.view.dispatch(editor.state.tr.setMeta(keptSelectionKey, { from, to }))\n}\n\n/** Stop drawing it. Nothing kept, nothing dispatched. */\nexport function releaseSelection(editor: Editor) {\n if (editor.isDestroyed || !keptSelectionKey.getState(editor.state)) return\n editor.view.dispatch(editor.state.tr.setMeta(keptSelectionKey, null))\n}\n\n/** What is being drawn, or null. */\nexport function keptSelection(editor: Editor): KeptRange | null {\n return keptSelectionKey.getState(editor.state) ?? null\n}\n\n/** A mark the strip can toggle \u2014 the suite's inline vocabulary (SPEC \u00A713.1) less link and reference. */\nexport type MarkId = 'bold' | 'italic' | 'underline' | 'code'\n\nconst MARKS: Record<MarkId, { label: string; keys: string }> = {\n bold: { label: 'Bold', keys: 'Mod-B' },\n italic: { label: 'Italic', keys: 'Mod-I' },\n underline: { label: 'Underline', keys: 'Mod-U' },\n code: { label: 'Code', keys: 'Mod-E' },\n}\n\n/**\n * Toggle one mark over the current selection. The core `toggleMark`, which\n * each mark's own `toggleBold` and the like wrap: the package names no Tiptap\n * extension, only the marks the suite's format has (SPEC \u00A713.1).\n */\nfunction toggleMark(editor: Editor, id: MarkId): boolean {\n return editor.chain().focus().toggleMark(id).run()\n}\n\n/** The address the selection sits in, or '' \u2014 what the link field opens with. */\nexport function currentLink(editor: Editor): string {\n const href: unknown = editor.getAttributes('link').href\n return typeof href === 'string' ? href : ''\n}\n\n/**\n * What somebody typed into the link field \u2192 an `href`, or null when it cannot\n * be one. A bare host (`google.com`) gets `https://`. A scheme is kept only when\n * SPEC \u00A713.1 lets a reader follow it (http, https, mailto); `javascript:` and\n * the like are refused here. `localhost:5173` is a host and a port: a scheme is\n * never followed by a digit.\n */\nexport function normalizeHref(raw: string): string | null {\n const s = raw.trim()\n if (!s || /\\s/.test(s)) return null\n if (/^(https?:|mailto:)/i.test(s)) return s\n if (/^[a-z][a-z0-9+.-]*:(?!\\d)/i.test(s)) return null\n return `https://${s}`\n}\n\n/**\n * Apply the link field to the selection. Empty removes the link; an address\n * sets it, over the whole link when the caret sits inside one. `false` means\n * the text could not be an address, and the field should stay open.\n */\nexport function applyLink(editor: Editor, raw: string): boolean {\n const chain = editor.chain().focus().extendMarkRange('link')\n // The core commands the Link extension's `setLink` and `unsetLink` wrap, with\n // the same meta; `normalizeHref` has already refused what it would refuse.\n if (!raw.trim()) return chain.unsetMark('link', { extendEmptyMarkRange: true }).setMeta('preventAutolink', true).run()\n const href = normalizeHref(raw)\n if (!href) return false\n return chain.setMark('link', { href }).setMeta('preventAutolink', true).run()\n}\n\n/**\n * Bold / italic / underline / link over selected text \u2014 Peek's\n * `SelectionToolbar` (PEE-18, PEE-19), moved into the package at Katerina's\n * word (29 September, UIG-31) when Ship's description became its second user.\n * Unchanged but for two props: `marks`, which buttons the strip has (Peek's\n * three by default; Ship adds code), and `link`, whether it has the Link button\n * and its field (Peek's editor has a link mark; an editor without one leaves it\n * off). The editor must register `KeptSelection` when `link` is on.\n *\n * The marks apply to a run you have already written, which is why they are\n * not in the `/` menu: typing `/` replaces the selection, so by definition\n * there is nothing selected to apply them to.\n *\n * **The chrome is the shared `Popover`, anchored** (P26). It was a `Menu`, and\n * it never was one: a menu gives its contents roving focus and typeahead,\n * which would fight the link field this panel exists for. An anchored\n * `Popover` is the mode built for exactly this \u2014 it takes no focus, so the\n * caret stays in the text. It is laid out as a row instead of a column\n * through `className`, which `cn` merges last. The panel still portals out of\n * the composer, so no overflow clips it, and Floating UI clamps it on screen.\n *\n * **The anchor is the selection's own point** since 0.12.2. This toolbar is\n * *centred* over the selection (her design, PEE-19, after Linear), and the\n * package's `Popover` aligns left or right only \u2014 Base UI has `center`, the\n * package does not expose it (ADOPTION **B21**). So the placement arithmetic\n * and the hand-measured `SIZE` box stay for now, and the rect handed over is\n * positioned so the panel lands on the same pixel it always has: the\n * package's own 4px gap is subtracted from this file's 8px, and the rect's\n * left edge is the panel's. When `align=\"center\"` exists, `SIZE`, `GAP` and\n * `placement` all go and the anchor becomes the selection's own rect.\n *\n * **Two faces** (PEE-19 \u2014 Katerina, 2026-09-07, after Linear). `marks` is the\n * row of four buttons. `link` is one text field for the address: it opens from\n * the Link button, and opens by itself when the settled selection sits inside\n * a link, with that link's address in it. There is no open, embed or delete\n * button, by her call: the field is the whole control.\n *\n * **What applies the field** (her rule, same day): Enter, and clicking away \u2014\n * anywhere outside the toolbar, the editor included. Enter on an empty field\n * removes the link; clicking away with an empty field changes nothing, since\n * removing is deliberate and leaving is not. Escape drops what was typed and\n * puts the caret back. Once applied, the caret lands after the link, and\n * typing goes on as plain text \u2014 the mark is not inclusive (editorKit).\n *\n * While the field has focus the editor has not, so everything below that\n * hides the toolbar on blur or on an empty editor selection stands down in\n * `link` mode. The editor keeps its selection while unfocused; `focus()` in\n * the apply command puts it back.\n *\n * **Why it waits.** Showing on every selection change put the toolbar under the\n * pointer mid-drag, and made it blink on a plain click \u2014 ProseMirror restores\n * the previous selection when the editor takes focus and collapses it a tick\n * later, so there is a real, non-empty range in between. Both go away by\n * refusing to show while the pointer is down and re-reading the selection after\n * a short settle rather than trusting the event that woke us.\n */\n\nconst ICONS: Record<MarkId, typeof IconBold> = {\n bold: IconBold,\n italic: IconItalic,\n underline: IconUnderline,\n code: IconCode,\n}\n\ntype Mode = 'marks' | 'link'\n\n/** How long a selection must stand still before the toolbar appears. */\nconst SETTLE_MS = 250\nconst GAP = 8\n/** The gap the package's `Popover` adds of its own, subtracted above so the\n * panel keeps this file's 8px. Read from `Popover.tsx`, not guessed. */\nconst POPOVER_GAP = 4\n\nexport interface SelectionToolbarProps {\n /** The editor whose selection the strip follows. `null` while it loads. */\n editor: Editor | null\n /** Which marks the strip toggles, in order. Default bold, italic, underline. */\n marks?: readonly MarkId[]\n /** The Link button and its address field. Needs a link mark in the editor and `KeptSelection`. Default off. */\n link?: boolean\n}\n\nexport function SelectionToolbar({ editor, marks = ['bold', 'italic', 'underline'], link = false }: SelectionToolbarProps) {\n /** The selection's top edge and horizontal centre; the face decides the rest. */\n const [anchor, setAnchor] = useState<{ top: number; centre: number } | null>(null)\n const [mode, setMode] = useState<Mode>('marks')\n const [url, setUrl] = useState('')\n // Re-render as marks turn on and off while the caret moves through the\n // selection, so the pressed states stay honest.\n const [, setTick] = useState(0)\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const pointerDown = useRef(false)\n const field = useRef<HTMLInputElement>(null)\n /** What the field holds while it is open; null while it is closed. The\n * listeners below are bound once, so they read this rather than state. */\n const draft = useRef<string | null>(null)\n // Read by the same listeners. Kept in an effect, declared before the focus\n // effect below so the ref already says `link` when the field's focus blurs\n // the editor.\n const modeRef = useRef<Mode>('marks')\n useEffect(() => {\n modeRef.current = mode\n }, [mode])\n // Read by `settle`, which is bound once: words already a link open the field only when there is one.\n const linkOn = useRef(link)\n linkOn.current = link\n\n const hide = useCallback(() => {\n clearTimeout(timer.current)\n draft.current = null\n if (editor) releaseSelection(editor)\n setAnchor(null)\n setMode('marks')\n }, [editor])\n\n /**\n * Leaving the field applies it (Enter or a click away). An address links the\n * words and the caret moves past them; an empty field on the way out is a\n * shrug, not a removal. Idempotent: the outside click reaches this twice,\n * once from the editor press and once from the Menu's own listener.\n */\n const commit = useCallback(() => {\n const text = draft.current\n if (text === null || !editor) return\n draft.current = null\n if (text.trim() && applyLink(editor, text)) {\n editor.commands.setTextSelection(editor.state.selection.to)\n }\n hide()\n }, [editor, hide])\n\n /** Read the selection as it is *now* and place the toolbar, or hide it. */\n const settle = useCallback(() => {\n if (!editor || editor.isDestroyed) return\n // Keeping the selection drawn is itself a transaction, which schedules\n // this; by then the field has the focus, and reading `hasFocus()` here\n // would close it.\n if (modeRef.current === 'link') return\n const { state, view } = editor\n const { empty, from, to } = state.selection\n if (empty || from === to || !view.hasFocus() || pointerDown.current) {\n setAnchor(null)\n setMode('marks')\n return\n }\n const start = view.coordsAtPos(from)\n const end = view.coordsAtPos(to)\n const centre = (Math.min(start.left, end.left) + Math.max(start.right, end.right)) / 2\n // Words that are already a link open on the field, with their address in it.\n const inLink = linkOn.current && editor.isActive('link')\n const address = inLink ? currentLink(editor) : ''\n draft.current = inLink ? address : null\n // The field is about to take the focus; the words stay highlighted.\n if (inLink) keepSelection(editor)\n setMode(inLink ? 'link' : 'marks')\n setUrl(address)\n setAnchor({ top: start.top, centre })\n }, [editor])\n\n useEffect(() => {\n if (!editor) return\n\n const schedule = () => {\n // The field owns the keyboard and the focus; the editor's own events\n // stand down until it closes.\n if (modeRef.current === 'link') return\n setTick((t) => t + 1)\n clearTimeout(timer.current)\n // A collapsed selection hides at once \u2014 waiting to hide is what made the\n // click blink. Only *showing* is deferred.\n if (editor.state.selection.empty) {\n setAnchor(null)\n return\n }\n timer.current = setTimeout(settle, SETTLE_MS)\n }\n\n // The field taking focus is the one blur that must not hide us.\n const onBlur = () => {\n if (modeRef.current !== 'link') hide()\n }\n\n // Only a press *inside the editor* starts a selection. A press on the\n // toolbar itself must not hide it \u2014 doing so unmounted the button before\n // its click could land, so nothing ever applied.\n const startsSelection = (e: MouseEvent) =>\n e.target instanceof Node && editor.view.dom.contains(e.target)\n\n const onPointerDown = (e: MouseEvent) => {\n if (!startsSelection(e)) return\n // A press in the editor while the field is open is a click away: apply\n // first, before the editor moves the caret under the press.\n commit()\n pointerDown.current = true\n hide()\n }\n const onPointerUp = () => {\n if (!pointerDown.current) return\n pointerDown.current = false\n schedule()\n }\n\n editor.on('selectionUpdate', schedule)\n editor.on('transaction', schedule)\n editor.on('blur', onBlur)\n // @estiva-escape(no-hand-rolled-behaviour): watches presses in the editor to follow a text selection; the Popover it opens in has no trigger to own that (Peek's UIG-8 escape, moved here with the part, UIG-31)\n document.addEventListener('mousedown', onPointerDown, true)\n document.addEventListener('mouseup', onPointerUp, true)\n\n return () => {\n clearTimeout(timer.current)\n editor.off('selectionUpdate', schedule)\n editor.off('transaction', schedule)\n editor.off('blur', onBlur)\n document.removeEventListener('mousedown', onPointerDown, true)\n document.removeEventListener('mouseup', onPointerUp, true)\n }\n }, [editor, settle, hide, commit])\n\n // The field takes focus once it is on screen. Not `autoFocus`, and not in\n // the same frame: the Menu renders once hidden to measure itself, and a\n // hidden input cannot take focus \u2014 measured 2026-09-07, the field opened\n // unfocused on a re-selected link. An address already in it is selected\n // whole, so typing replaces it.\n useEffect(() => {\n if (mode !== 'link' || !anchor) return\n const frame = requestAnimationFrame(() => {\n field.current?.focus()\n field.current?.select()\n })\n return () => cancelAnimationFrame(frame)\n }, [mode, anchor])\n\n if (!editor || !anchor) return null\n\n const openField = () => {\n const address = currentLink(editor)\n draft.current = address\n keepSelection(editor)\n setUrl(address)\n setMode('link')\n }\n /** Enter. Unlike a click away, an empty field here means remove. */\n const submit = () => {\n if (!url.trim()) {\n applyLink(editor, '')\n hide()\n editor.commands.focus()\n return\n }\n commit()\n }\n const discard = () => {\n hide()\n editor.commands.focus()\n }\n\n /* A zero-sized rect at the selection's own top edge and centre. The panel's\n width is no longer this file's business: `align=\"center\"` puts the\n panel's middle over this point (B21, package 0.12.2), so the measured\n `SIZE` table and the placement arithmetic that stood in for it are gone.\n The top is this file's GAP less the package's own 4, so the panel keeps\n the 8px it has always had. */\n const anchorRect = new DOMRect(anchor.centre, anchor.top - (GAP - POPOVER_GAP), 0, 0)\n\n return (\n <Popover\n anchor={anchorRect}\n open\n side=\"top\"\n align=\"center\"\n onOpenChange={(next) => {\n if (!next) commit()\n }}\n ariaLabel=\"Text formatting\"\n // The panel gives up its column; the strip inside draws the row. The 4px\n // around it is `contentClassName`'s: on `className` it was added to the\n // package's 8px from 0.12.6 on, and the toolbar grew 8px a side\n // (Katerina, 2026-09-13; migration docs D68, Finding 60). This is the\n // package's own `Popover/A toolbar` story, class for class.\n className=\"w-auto min-w-0\"\n contentClassName=\"p-1\"\n >\n {mode === 'link' ? (\n /* One field, so no strip: a toolbar of one control is a toolbar in\n name only, and `ToolbarInput` cannot take the ref this field is\n focused by (ADOPTION B25). */\n // The package Form (UIG-7): Enter applies the link. `free` and `flex`: one field, which keeps its own height.\n <Form onSubmit={submit} layout=\"free\" className=\"flex\">\n <TextInput\n ref={field}\n value={url}\n onChange={(e) => {\n setUrl(e.target.value)\n draft.current = e.target.value\n }}\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n // Before the Menu's own Escape, which would apply instead.\n e.stopPropagation()\n discard()\n }\n }}\n placeholder=\"Enter link URL\"\n aria-label=\"Link URL\"\n className=\"w-[264px]\"\n />\n </Form>\n ) : (\n /* Four marks and a link button: one Tab stop, \u2190 \u2192 along them (D29). */\n <Toolbar aria-label=\"Text formatting\" surface={false}>\n {marks.map((id) => {\n const Icon = ICONS[id]\n const active = editor.isActive(id)\n return (\n <ToolbarButton\n key={id}\n aria-label={MARKS[id].label}\n pressed={active}\n tooltip={MARKS[id].label}\n tooltipShortcut={shortcutLabel(MARKS[id].keys)}\n // Keep the selection: focus must not leave the editor on press.\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => toggleMark(editor, id)}\n >\n <Icon size={16} stroke={1.5} />\n </ToolbarButton>\n )\n })}\n {link && (\n <ToolbarButton\n aria-label=\"Link\"\n tooltip=\"Link\"\n onMouseDown={(e) => e.preventDefault()}\n onClick={openField}\n >\n <IconLink size={16} stroke={1.5} />\n </ToolbarButton>\n )}\n </Toolbar>\n )}\n </Popover>\n )\n}\n", "// @estiva-escape(component-has-a-page, component-has-a-story): draws nothing of its own \u2014 it opens a SuggestionMenu from a Tiptap suggestion plugin, and SuggestionMenu's page and stories show it (UIG-31)\nimport { forwardRef, useRef, useImperativeHandle } from 'react'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionKeyDownProps, SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'\nimport { SuggestionMenu, type SuggestionMenuHandle, type SuggestionMenuRow, type SuggestionMenuSection } from './SuggestionMenu'\n\n/*\n * Is a type-ahead list open? \u2014 Peek's counter (`mention.tsx`), moved here with\n * the lists it counts (UIG-31).\n *\n * A counter rather than a boolean: two suggestion plugins can overlap for a\n * moment while one hands over to the other (`/` choosing \"Mention\" types `@`).\n * An editor asks before it acts on Enter or Escape, so a key that chose a row\n * does not also send the message or cancel the edit.\n */\nlet openCount = 0\nlet lastClose = 0\n\n/**\n * Open, or closed less than 100ms ago \u2014 what an Enter handler asks. Choosing a\n * row with Enter closes the list, and that same Enter must not also send.\n */\nexport function isSuggestionActive(): boolean {\n return openCount > 0 || Date.now() - lastClose < 100\n}\n\n/**\n * Open *now*, with no grace period \u2014 what an Escape handler asks. The first\n * Escape closes the list, the second is somebody's deliberate press.\n */\nexport function isSuggestionOpen(): boolean {\n return openCount > 0\n}\n\n/** How a list looks and what fills it \u2014 everything but the editor's own wiring. */\nexport interface SuggestionPopupOptions<T> {\n /** Names the list \u2014 \"Commands\", \"People\". */\n ariaLabel: string\n /** The box's width, e.g. `w-[300px]`. */\n width: string\n /** The cap before the rows scroll, e.g. `max-h-[400px]`. */\n maxHeight: string\n /** The plugin's items, and the typed query, \u2192 the sections to draw. One unlabeled section by default. */\n sections?: (items: T[], query: string) => readonly SuggestionMenuSection<T>[]\n itemKey: (item: T) => string\n row: (item: T) => SuggestionMenuRow\n}\n\ntype Props<T> = SuggestionProps<T> & { options: SuggestionPopupOptions<T> }\n\n// `forwardRef`, so Tiptap's `ReactRenderer` hands its `ref` through whichever\n// way it detects a component that takes one.\nconst Popup = forwardRef<SuggestionMenuHandle, Props<unknown>>(function Popup({ options, ...props }, handle) {\n const menu = useRef<SuggestionMenuHandle>(null)\n useImperativeHandle(handle, () => ({ onKeyDown: (event) => menu.current?.onKeyDown(event) ?? false }))\n const sections = options.sections ? options.sections(props.items, props.query) : [{ items: props.items }]\n return (\n <SuggestionMenu\n handle={menu}\n rect={props.clientRect?.() ?? null}\n editorElement={props.editor.view.dom}\n ariaLabel={options.ariaLabel}\n width={options.width}\n maxHeight={options.maxHeight}\n sections={sections}\n itemKey={options.itemKey}\n row={options.row}\n onSelect={(item) => props.command(item)}\n />\n )\n})\n\n/**\n * The `render` of a Tiptap suggestion plugin, drawn as a `SuggestionMenu` \u2014\n * the popup controller Peek wrote four times (`mention.tsx`, `slashCommands.tsx`),\n * once.\n *\n * Suggestion({ editor, char: '/', items, command,\n * render: suggestionPopup({ ariaLabel: 'Commands', width: 'w-[300px]',\n * maxHeight: 'max-h-[400px]', itemKey, row, sections }) })\n *\n * It mounts the list when the query starts and removes it when it ends: the\n * plugin owns the list's life. Escape closes it and is used up; \u2191, \u2193 and Enter\n * go to the list; every other key stays the editor's. The list portals itself\n * (the package `Popover`), so the mount point is only a mount point.\n */\nexport function suggestionPopup<T>(options: SuggestionPopupOptions<T>): NonNullable<SuggestionOptions<T>['render']> {\n return () => {\n let component: ReactRenderer<SuggestionMenuHandle, Props<T>> | null = null\n let container: HTMLDivElement | null = null\n const exit = () => {\n if (!component) return\n openCount = Math.max(0, openCount - 1)\n lastClose = Date.now()\n component.destroy()\n container?.remove()\n component = null\n container = null\n }\n return {\n onStart: (props) => {\n openCount++\n component = new ReactRenderer<SuggestionMenuHandle, Props<T>>(Popup as never, { props: { ...props, options }, editor: props.editor })\n container = document.createElement('div')\n document.body.appendChild(container)\n container.appendChild(component.element)\n },\n onUpdate: (props) => component?.updateProps({ ...props, options }),\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'Escape') {\n exit()\n return true\n }\n return component?.ref?.onKeyDown(event) ?? false\n },\n onExit: exit,\n }\n }\n}\n", "import type { ComponentType } from 'react'\nimport { Extension, Node } from '@tiptap/core'\nimport type { Node as ProseMirrorNode, Slice } from '@tiptap/pm/model'\nimport { Plugin, PluginKey, type Transaction } from '@tiptap/pm/state'\nimport { Mapping, ReplaceAroundStep } from '@tiptap/pm/transform'\nimport { NodeViewWrapper, ReactNodeViewRenderer, type ReactNodeViewProps } from '@tiptap/react'\n\n/*\n * The four nodes an editor of a block document needs that a stock schema does\n * not have (SPEC \u00A713.3) \u2014 moved here from Ship's `editorSchema.tsx` (RIC-14,\n * SHI-2) when Peek became the second editor of the same documents (MAN-9).\n *\n * They are one half of a pair. The other half is `@estiva-app/protocol`'s\n * `toEditorDocument` / `fromEditorDocument`, which turn a stored document into\n * the JSON these nodes read and back: the node names (`unknownBlock`,\n * `reference`, `attachment`) and the `blockId` attribute are that translation's,\n * and must not drift from it. This package does not import protocol, as\n * `RichText` does not: the translation hands over everything a view draws.\n *\n * Everything else a document can contain is already a ProseMirror node of the\n * same name \u2014 StarterKit's and TableKit's \u2014 which is why this file is short.\n */\n\n/**\n * The block types \u00A713.3 puts an id on, as the editor's node names \u2014\n * protocol's `EDITOR_BLOCK_TYPES`, less `unknownBlock`, which declares its own.\n */\nconst BLOCK_TYPES = [\n 'paragraph',\n 'heading',\n 'bulletList',\n 'orderedList',\n 'listItem',\n 'blockquote',\n 'codeBlock',\n 'horizontalRule',\n // A table and everything inside it (UIG-18): a row or a cell whose id was\n // re-minted on each save would detach any comment anchored to it.\n 'table',\n 'tableRow',\n 'tableHeader',\n 'tableCell',\n // Without this an attachment's id is re-minted on every save, and every\n // comment anchored to it stops resolving.\n 'attachment',\n]\nconst BLOCK_TYPE_SET = new Set(BLOCK_TYPES)\n\n/** protocol's `newBlockId`: six random bytes as hex. */\nfunction newBlockId(): string {\n return Array.from(crypto.getRandomValues(new Uint8Array(6)), (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\n/** Whether anything typed or inserted is inside \u2014 protocol's `holdsText`. An empty paragraph is not; a lone line break is. */\nfunction holdsText(node: ProseMirrorNode): boolean {\n let found = false\n node.descendants((child) => {\n if (found) return false\n if ((child.isText && child.text !== '') || child.type.name === 'hardBreak' || child.type.name === 'reference') found = true\n return !found\n })\n return found\n}\n\ninterface Claim {\n pos: number\n node: ProseMirrorNode\n /** An unknown block: the translation hands its source back untouched, so re-minting it here would be undone on save. */\n fixed: boolean\n}\n\n/** Every block that claims an id, by id, in document order. */\nfunction claimsIn(doc: ProseMirrorNode): Map<string, Claim[]> {\n const claims = new Map<string, Claim[]>()\n doc.descendants((node, pos) => {\n const fixed = node.type.name === 'unknownBlock'\n if (!fixed && !BLOCK_TYPE_SET.has(node.type.name)) return\n const id = node.attrs.blockId\n if (typeof id !== 'string' || id === '') return\n const copies = claims.get(id)\n if (copies) copies.push({ pos, node, fixed })\n else claims.set(id, [{ pos, node, fixed }])\n })\n return claims\n}\n\n/**\n * The copy the block that was there before carried on into: where the start\n * of its content landed. Enter at the very start pushes that content into the\n * lower half; a split anywhere else leaves it in the upper; a pasted copy,\n * above or below, never moves it out of the original.\n */\nfunction carriedOn(copies: Claim[], before: Claim, mapping: Mapping): Claim | undefined {\n if (before.node.isLeaf) {\n const at = mapping.map(before.pos, 1)\n return copies.find((c) => c.pos === at)\n }\n const at = mapping.map(textStart(before), 1)\n return copies.find((c) => c.pos < at && at < c.pos + c.node.nodeSize)\n}\n\n/** The start of a block's text, inside the paragraph a list item or quote wraps it in. */\nfunction textStart({ pos, node }: { pos: number; node: ProseMirrorNode }): number {\n let start = pos + 1\n if (!node.isTextblock) {\n let found = false\n node.descendants((child, at) => {\n if (found) return false\n if (child.isTextblock) {\n start = pos + 1 + at + 1\n found = true\n }\n return !found\n })\n }\n return start\n}\n\n/**\n * Where a block claims an id another block keeps \u2014 see {@link BlockId}. An\n * unknown block always keeps its id; otherwise the copy the original carried\n * on into, and failing that protocol's rule: the first that holds text, or\n * the first.\n */\nfunction repeatedBlockIds(doc: ProseMirrorNode, before: ProseMirrorNode, mapping: Mapping): number[] {\n const claims = claimsIn(doc)\n let previous: Map<string, Claim[]> | undefined\n const losers: number[] = []\n for (const [id, copies] of claims) {\n if (copies.length < 2) continue\n previous ??= claimsIn(before)\n const original = previous.get(id)?.[0]\n const owner =\n copies.find((c) => c.fixed) ??\n (original && carriedOn(copies, original, mapping)) ??\n copies.find((c) => holdsText(c.node)) ??\n copies[0]\n for (const copy of copies) if (copy !== owner && !copy.fixed) losers.push(copy.pos)\n }\n return losers\n}\n\n/**\n * The blocks a save turns into blocks \u2014 exactly what protocol's\n * `blockFromEditor` walks \u2014 so a list, a table and its rows and cells are\n * walked into, and a list item or a quote is not: the save flattens their\n * paragraph wrappers into inline runs, so an id there would be dropped.\n */\nconst BLOCK_CONTAINERS = new Set(['bulletList', 'orderedList', 'table', 'tableRow', 'tableHeader', 'tableCell'])\n\n/** The wrappers the save flattens: their paragraphs become inline runs of theirs. */\nconst FLATTENED = new Set(['listItem', 'blockquote'])\n\nconst idOf = (node: ProseMirrorNode): string | undefined => {\n const id = node.attrs.blockId\n return typeof id === 'string' && id !== '' ? id : undefined\n}\n\n/** Every block the save turns into a block, in document order. */\nfunction addressedBlocks(doc: ProseMirrorNode): { pos: number; node: ProseMirrorNode }[] {\n const out: { pos: number; node: ProseMirrorNode }[] = []\n const visit = (node: ProseMirrorNode, pos: number) => {\n if (!BLOCK_TYPE_SET.has(node.type.name)) return\n out.push({ pos, node })\n if (BLOCK_CONTAINERS.has(node.type.name)) node.forEach((child, offset) => visit(child, pos + 1 + offset))\n }\n doc.forEach((node, offset) => visit(node, offset))\n return out\n}\n\n/** Where a block the save would give an id has none, so it would get a new one on every save. */\nfunction unnamedBlocks(doc: ProseMirrorNode): number[] {\n return addressedBlocks(doc)\n .filter((b) => idOf(b.node) === undefined)\n .map((b) => b.pos)\n}\n\n/** Where a paragraph inside a list item or a quote claims an id \u2014 which the save drops. */\nfunction hiddenIds(doc: ProseMirrorNode): number[] {\n const out: number[] = []\n doc.descendants((node, pos) => {\n if (!FLATTENED.has(node.type.name)) return\n node.forEach((child, offset) => {\n if (child.isTextblock && idOf(child) !== undefined) out.push(pos + 1 + offset)\n })\n })\n return out\n}\n\n/**\n * The id each block a wrap or a lift brought in takes over \u2014 see\n * {@link BlockId}. An id the save addressed before the change and does not\n * now goes to the innermost new block its text was carried into: the quote or\n * the list item around a paragraph, or the paragraph lifted out of one.\n * A block the change deleted hands nothing on. What it asks is whether the\n * block's own opening survived, the token left of its text, not its first\n * character: the `/` menu and a typed `> ` or `- ` delete what was typed at\n * the very start of the text in the same change as the wrap.\n */\nfunction handedOver(doc: ProseMirrorNode, before: ProseMirrorNode, mapping: Mapping, unnamed: number[]): Map<number, string> {\n const out = new Map<number, string>()\n if (unnamed.length === 0) return out\n const now = new Set(addressedBlocks(doc).map((b) => idOf(b.node)))\n // By where their text starts, and of a list and its item around the same text, the item first.\n const gone = addressedBlocks(before)\n .filter((b) => !b.node.isLeaf && idOf(b.node) !== undefined && !now.has(idOf(b.node)))\n .map((b) => ({ ...b, start: textStart(b) }))\n .sort((a, b) => a.start - b.start || b.pos - a.pos)\n for (const old of gone) {\n const id = idOf(old.node)!\n const { pos: at, deleted } = mapping.mapResult(old.start, -1)\n if (deleted) continue\n let inner: number | undefined\n for (const pos of unnamed) {\n if (out.has(pos)) continue\n if (pos < at && at < pos + doc.nodeAt(pos)!.nodeSize && (inner === undefined || pos > inner)) inner = pos\n }\n if (inner !== undefined) {\n out.set(inner, id)\n now.add(id)\n }\n }\n return out\n}\n\n/**\n * Whether a change could have made a repeat or a block without an id: only a\n * step that brings in blocks can, or one that wraps or lifts them \u2014 lifting\n * the paragraph out of a quote or a one-item list brings in nothing, yet\n * leaves a block the save addresses with the wrapper's missing id. Typing,\n * deleting and marks do neither, so most keystrokes skip the walk.\n */\nfunction bringsInBlocks(transactions: readonly Transaction[]): boolean {\n return transactions.some((tr) =>\n tr.steps.some((step) => {\n if (step instanceof ReplaceAroundStep) return true\n const slice = (step as { slice?: Slice }).slice\n let blocks = false\n slice?.content.forEach((node) => {\n if (node.isBlock) blocks = true\n })\n return blocks\n }),\n )\n}\n\n/**\n * `id` on every block, carried as the `blockId` attribute.\n *\n * A global attribute rather than one per node type, because \u00A713.3 puts an id\n * on **every** block and a type that quietly lacked one would mint a fresh id\n * on every save \u2014 detaching each anchored comment on it (RFC 0.4 \u00A76) with\n * nothing on screen to say so.\n *\n * Rendered as `data-block-id`, which is what `RichText` already emits, so the\n * reading and the editing surface address a block the same way.\n *\n * **A split gives the new half its own id, the moment it happens** (MAN-10).\n * ProseMirror copies a split node's attrs to both halves, so Enter leaves two\n * blocks claiming one id. Enter at the very start of a paragraph puts the\n * copy *above* it \u2014 and once something is typed there, nothing at save time\n * can tell which half was the original, so protocol's `fromEditorDocument`\n * gave the id, and every comment anchored to it, to the new line. Settled\n * here instead, in the same transaction, where the change itself says which\n * copy the original carried on into \u2014 so a copy pasted above a block does\n * not take its id either. The others get fresh ids. `fromEditorDocument`\n * keeps its own rule (the copy that holds text) as the backstop for an\n * editor without this extension; where the change says nothing, this falls\n * back to that rule too.\n *\n * **A block that arrives without an id gets one, the moment it arrives**\n * (MAN-11): a paste, a `/` menu insert, the empty paragraph a new editor\n * starts with and the one StarterKit keeps at the end. `fromEditorDocument`\n * mints an id for such a block on every save and the editor never sees it, so\n * each save gave it a different one \u2014 a blur with no edit published again, and\n * a comment anchored to it in between was detached. The editor's first\n * document is filled in when it is created, outside the undo history and\n * without an update, so opening a description is not an edit.\n *\n * **A wrap or a lift hands the id on** (MAN-12). Turning a paragraph into a\n * quote or a list wraps it, and ProseMirror leaves its attrs on the paragraph\n * inside \u2014 where the save flattens it and drops the id, so the quote or list\n * item was published under a new one and every comment anchored to the\n * paragraph was detached. The quote or the list item takes the paragraph's id\n * instead, and the list around it a fresh one; lifting the text back out hands\n * the id to the paragraph again. The paragraph inside keeps no id, as one\n * loaded from a stored document has none.\n */\nexport const BlockId = Extension.create({\n name: 'blockId',\n onCreate() {\n const { state, view } = this.editor\n const unnamed = unnamedBlocks(state.doc)\n if (unnamed.length === 0) return\n const tr = state.tr\n for (const pos of unnamed) tr.setNodeAttribute(pos, 'blockId', newBlockId())\n view.dispatch(tr.setMeta('addToHistory', false).setMeta('preventUpdate', true))\n },\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: new PluginKey('blockIdSplit'),\n appendTransaction: (transactions, old, state) => {\n if (!bringsInBlocks(transactions)) return null\n const mapping = new Mapping()\n for (const tr of transactions) mapping.appendMapping(tr.mapping)\n const unnamed = unnamedBlocks(state.doc)\n const handed = handedOver(state.doc, old.doc, mapping, unnamed)\n const ids = new Map<number, string | null>()\n for (const pos of repeatedBlockIds(state.doc, old.doc, mapping)) ids.set(pos, newBlockId())\n for (const pos of unnamed) ids.set(pos, handed.get(pos) ?? newBlockId())\n for (const pos of hiddenIds(state.doc)) ids.set(pos, null)\n if (ids.size === 0) return null\n const tr = state.tr\n for (const [pos, id] of ids) tr.setNodeAttribute(pos, 'blockId', id)\n return tr\n },\n }),\n ]\n },\n addGlobalAttributes() {\n return [\n {\n types: BLOCK_TYPES,\n attributes: {\n blockId: {\n default: null,\n parseHTML: (element: HTMLElement) => element.getAttribute('data-block-id'),\n renderHTML: (attributes: Record<string, unknown>) =>\n attributes.blockId ? { 'data-block-id': String(attributes.blockId) } : {},\n },\n },\n },\n ]\n },\n})\n\n/** What a reference's view is handed: the `nostr:` URI it stands for. */\nexport interface ReferenceViewProps {\n uri: string\n}\n\nexport interface ReferenceNodeOptions {\n /**\n * Draws the reference as the thing it points at \u2014 the app's own chip, which\n * knows how to resolve it. Without one, the URI is drawn as text.\n */\n view: ComponentType<ReferenceViewProps> | null\n}\n\n/**\n * A `nostr:` reference, drawn as the thing it points at \u2014 SPEC \u00A713.1.\n *\n * An **atom**: it has no editable interior, because its text is the URI and\n * nobody edits half a `naddr`. Selecting it selects the whole reference, which\n * is also how deleting one works. `marks` carries the run's other marks, so a\n * bold reference comes back bold.\n */\nexport const ReferenceNode = Node.create<ReferenceNodeOptions>({\n name: 'reference',\n group: 'inline',\n inline: true,\n atom: true,\n selectable: true,\n draggable: false,\n addOptions() {\n return { view: null }\n },\n addAttributes() {\n return {\n uri: { default: '' },\n marks: { default: [] as string[] },\n }\n },\n parseHTML() {\n return [{ tag: 'span[data-reference]' }]\n },\n renderHTML({ node }) {\n // A copy to the clipboard as HTML, and the whole drawing when no view is given.\n return ['span', { 'data-reference': String(node.attrs.uri) }, String(node.attrs.uri)]\n },\n addNodeView() {\n const View = this.options.view\n if (!View) return null\n return ReactNodeViewRenderer(({ node }: ReactNodeViewProps) => (\n <NodeViewWrapper as=\"span\" className=\"inline\">\n <View uri={String(node.attrs.uri ?? '')} />\n </NodeViewWrapper>\n ))\n },\n})\n\n/**\n * A block this editor has no design for, kept whole.\n *\n * \u00A713.3: a reader MUST render an unknown block's inline text and MUST NOT drop\n * it silently. **An editor is the dangerous case** \u2014 ProseMirror discards a\n * node its schema does not know, and the save after that would delete somebody\n * else's block with no error anywhere on the way. So the block's own JSON\n * rides in `source`, and protocol's `fromEditorDocument` hands it back\n * untouched; `text` is its inline text, worked out by `toEditorDocument`.\n *\n * Not editable, deliberately: an affordance that lets a person change\n * something they cannot see is worse than one that says the content is there\n * and belongs to another surface.\n */\nexport const UnknownBlock = Node.create({\n name: 'unknownBlock',\n group: 'block',\n atom: true,\n selectable: true,\n addAttributes() {\n return {\n blockId: { default: null },\n source: { default: null },\n text: { default: '' },\n }\n },\n parseHTML() {\n return [{ tag: 'div[data-unknown-block]' }]\n },\n renderHTML({ node }) {\n return ['div', { 'data-unknown-block': String(node.attrs.blockId ?? '') }, String(node.attrs.text ?? '')]\n },\n addNodeView() {\n return ReactNodeViewRenderer(UnknownBlockView)\n },\n})\n\nfunction UnknownBlockView({ node }: ReactNodeViewProps) {\n const source = node.attrs.source as { id?: string; type?: string } | null\n const text = String(node.attrs.text ?? '')\n return (\n <NodeViewWrapper\n data-block-id={source?.id}\n data-block-type={source?.type}\n className=\"rounded-md border border-border-default bg-bg-inset px-2 py-1 text-text-secondary\"\n >\n {text || `A ${source?.type ?? 'block'} this app cannot edit yet.`}\n </NodeViewWrapper>\n )\n}\n\n/** What an attachment's view is handed: the block's `imeta` fields. */\nexport interface AttachmentViewProps {\n attrs: Record<string, unknown>\n}\n\nexport interface AttachmentNodeOptions {\n /** Draws the file \u2014 the app's own attachment card. Without one, its name is drawn as text. */\n view: ComponentType<AttachmentViewProps> | null\n}\n\n/**\n * A file placed in a document \u2014 SPEC \u00A713.3's `attachment` block (SHI-2).\n *\n * An atom: the file is the block, there is nothing inside it to edit, and a\n * caret that could sit \"inside\" an image is a way to produce documents no\n * reader has a rule for. Selectable and deletable, so removing one is Backspace\n * like anything else.\n *\n * Its attributes are the `imeta` fields exactly, because that is what \u00A713.3\n * specifies. Each is declared, and that is not boilerplate: ProseMirror **drops\n * an attribute its schema does not name**, so an undeclared `thumb` would\n * survive the editor and vanish on save \u2014 the silent deletion `UnknownBlock`\n * exists to prevent.\n */\nexport const AttachmentNode = Node.create<AttachmentNodeOptions>({\n name: 'attachment',\n group: 'block',\n atom: true,\n selectable: true,\n draggable: false,\n addOptions() {\n return { view: null }\n },\n addAttributes() {\n return {\n url: { default: '' },\n m: { default: '' },\n x: { default: '' },\n size: { default: 0 },\n dim: { default: null },\n thumb: { default: null },\n alt: { default: null },\n filename: { default: null },\n }\n },\n parseHTML() {\n return [{ tag: 'div[data-attachment-block]' }]\n },\n renderHTML({ node }) {\n // A copy to the clipboard, and the whole drawing when no view is given.\n return ['div', { 'data-attachment-block': String(node.attrs.x ?? '') }, String(node.attrs.filename ?? 'attachment')]\n },\n addNodeView() {\n const View = this.options.view\n if (!View) return null\n return ReactNodeViewRenderer(({ node }: ReactNodeViewProps) => (\n <NodeViewWrapper className=\"my-2\">\n <View attrs={node.attrs} />\n </NodeViewWrapper>\n ))\n },\n})\n"],
5
+ "mappings": "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",
6
6
  "names": ["useRef", "jsx", "Popup", "Extension", "Node", "Plugin", "PluginKey", "jsx", "at", "tr"]
7
7
  }
@@ -1521,7 +1521,7 @@ var noFieldOutsideAForm = {
1521
1521
  };
1522
1522
 
1523
1523
  // src/eslint/no-native-title.ts
1524
- var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "Panel", "SectionHeader"];
1524
+ var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "NavTree", "Panel", "SectionHeader"];
1525
1525
  var child4 = (node, key) => node[key];
1526
1526
  var nameOf5 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
1527
1527
  var noNativeTitle = {