@estiva-app/ui 0.53.1 → 0.55.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 (54) hide show
  1. package/dist/BlockHandle.d.ts +47 -0
  2. package/dist/BlockHandle.d.ts.map +1 -0
  3. package/dist/EditorBlockHandle.d.ts +57 -0
  4. package/dist/EditorBlockHandle.d.ts.map +1 -0
  5. package/dist/NavItem.d.ts +14 -1
  6. package/dist/NavItem.d.ts.map +1 -1
  7. package/dist/NavTree.d.ts +93 -13
  8. package/dist/NavTree.d.ts.map +1 -1
  9. package/dist/UnreadDot.d.ts +12 -0
  10. package/dist/UnreadDot.d.ts.map +1 -0
  11. package/dist/editor.d.ts +1 -0
  12. package/dist/editor.d.ts.map +1 -1
  13. package/dist/editor.js +127 -11
  14. package/dist/editor.js.map +4 -4
  15. package/dist/eslint/index.js +1 -1
  16. package/dist/eslint/index.js.map +2 -2
  17. package/dist/eslint/no-native-title.d.ts +1 -1
  18. package/dist/eslint/no-native-title.d.ts.map +1 -1
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +801 -584
  22. package/dist/index.js.map +4 -4
  23. package/dist/looks.d.ts +1 -1
  24. package/dist/looks.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/registry.json +362 -11
  27. package/src/BlockHandle.mdx +58 -0
  28. package/src/BlockHandle.stories.tsx +44 -0
  29. package/src/BlockHandle.test.tsx +216 -0
  30. package/src/BlockHandle.tsx +220 -0
  31. package/src/EditorBlockHandle.mdx +65 -0
  32. package/src/EditorBlockHandle.stories.tsx +81 -0
  33. package/src/EditorBlockHandle.tsx +181 -0
  34. package/src/NavItem.mdx +14 -1
  35. package/src/NavItem.stories.tsx +15 -0
  36. package/src/NavItem.test.tsx +31 -0
  37. package/src/NavItem.tsx +49 -12
  38. package/src/NavTree.mdx +25 -0
  39. package/src/NavTree.stories.tsx +57 -1
  40. package/src/NavTree.test.tsx +78 -1
  41. package/src/NavTree.tsx +175 -40
  42. package/src/SectionLabel.stories.tsx +2 -3
  43. package/src/UnreadDot.mdx +57 -0
  44. package/src/UnreadDot.stories.tsx +22 -0
  45. package/src/UnreadDot.test.tsx +17 -0
  46. package/src/UnreadDot.tsx +17 -0
  47. package/src/editor.ts +1 -0
  48. package/src/eslint/no-native-title.ts +1 -1
  49. package/src/index.ts +3 -1
  50. package/src/looks.ts +2 -1
  51. package/src/registry/registry.test.ts +5 -5
  52. package/stories/Choosing.mdx +3 -0
  53. package/tailwind-preset.js +1 -0
  54. package/tokens.css +5 -0
@@ -0,0 +1,47 @@
1
+ import { type ReactNode } from 'react';
2
+ /** The block the pointer is on: its §13.3 id, its element, and — in an editor — where it starts. */
3
+ export interface HandleBlock {
4
+ /** `undefined` only for a block that has none yet, which nothing can address. */
5
+ id: string | undefined;
6
+ element: HTMLElement;
7
+ /** Position before the block in the editor's document; absent when reading. */
8
+ pos?: number;
9
+ }
10
+ /** What `EditorBlockHandle` hands this part to make it edit. A reading surface leaves it off. */
11
+ export interface BlockHandleEditing {
12
+ /** The block level with `y`, in the editor's own document. */
13
+ find: (y: number) => HandleBlock | null;
14
+ /** The editing rows, before the caller's. */
15
+ rows: (block: HandleBlock) => ReactNode;
16
+ /** Starts the browser's drag of the block; the handle is draggable only with this. */
17
+ startDrag: (block: HandleBlock, event: globalThis.DragEvent) => void;
18
+ /** Drops the dragged block before `target`, or after it: a drop in the gutter, where the editor has none. */
19
+ drop: (target: HandleBlock, after: boolean) => void;
20
+ endDrag: () => void;
21
+ /** Calls back when the document changes, so a block found before is found again. Returns the unsubscribe. */
22
+ onChange: (changed: () => void) => () => void;
23
+ }
24
+ export interface BlockHandleProps {
25
+ /** The caller's own rows — a link to the block, a comment on it. Reading, they are the whole menu; with none, no handle is drawn. */
26
+ actions?: (block: HandleBlock) => ReactNode;
27
+ /** Given by `EditorBlockHandle` (from `@estiva-app/ui/editor`). Leave it off to read. */
28
+ editing?: BlockHandleEditing;
29
+ /** What the blocks are drawn in. Reading: a surface whose top-level elements carry `data-block-id`, as `RichText` draws. */
30
+ children: ReactNode;
31
+ className?: string;
32
+ }
33
+ /** The last of `blocks` whose top is at or above `y` — the gap between two blocks belongs to the one before. */
34
+ export declare function blockAtY(blocks: readonly HandleBlock[], y: number): HandleBlock | null;
35
+ /**
36
+ * The handle at the left of the block under the pointer, and the block's menu
37
+ * behind it. Reading, the menu is the caller's rows only, so someone who
38
+ * cannot edit can still point at one block — copy a link to it, comment on
39
+ * it. In an editor, `EditorBlockHandle` adds the drag, Turn into, Duplicate
40
+ * and Delete.
41
+ *
42
+ * It sits in the gutter left of the content, 28px out, so wrapping the content
43
+ * moves nothing. The gutter counts as the block's, as in Notion: the handle
44
+ * stays while the pointer crosses to it.
45
+ */
46
+ export declare function BlockHandle({ actions, editing, children, className }: BlockHandleProps): import("react").JSX.Element;
47
+ //# sourceMappingURL=BlockHandle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BlockHandle.d.ts","sourceRoot":"","sources":["../src/BlockHandle.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6E,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAOjH,oGAAoG;AACpG,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,EAAE,EAAE,MAAM,GAAG,SAAS,CAAA;IACtB,OAAO,EAAE,WAAW,CAAA;IACpB,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,iGAAiG;AACjG,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAA;IACvC,6CAA6C;IAC7C,IAAI,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,CAAA;IACvC,sFAAsF;IACtF,SAAS,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,CAAC,SAAS,KAAK,IAAI,CAAA;IACpE,6GAA6G;IAC7G,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACnD,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,6GAA6G;IAC7G,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,qIAAqI;IACrI,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,CAAA;IAC3C,yFAAyF;IACzF,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B,4HAA4H;IAC5H,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAQD,gHAAgH;AAChH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAOtF;AA2BD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,gBAAgB,+BA8HtF"}
@@ -0,0 +1,57 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { Editor } from '@tiptap/core';
3
+ import type { Node as ProseMirrorNode } from '@tiptap/pm/model';
4
+ import { type HandleBlock } from './BlockHandle';
5
+ /** A row of the block menu's Turn into: what the block becomes. */
6
+ export interface BlockTurnInto {
7
+ label: string;
8
+ /** Turns the block the caret is in. The handle puts the caret there first. */
9
+ run: (editor: Editor) => void;
10
+ /** Whether the block already is this — drawn as the selected row. */
11
+ active?: (node: ProseMirrorNode) => boolean;
12
+ }
13
+ export interface EditorBlockHandleProps {
14
+ /** The editor. Not editable, or not there yet, and the handle reads: the caller's rows only. */
15
+ editor: Editor | null;
16
+ /** Turn into's rows. Offered for a block of text — a paragraph, a heading, code. */
17
+ turnInto?: readonly BlockTurnInto[];
18
+ /** The caller's own rows, after the editing ones. */
19
+ actions?: (block: HandleBlock) => ReactNode;
20
+ /** The `EditorContent`. */
21
+ children: ReactNode;
22
+ className?: string;
23
+ }
24
+ /** A copy of the block straight after it, the caret in the copy. `BlockId` gives the copy a fresh id; the original keeps its own. */
25
+ export declare function duplicateBlock(editor: Editor, pos: number): boolean;
26
+ /** Removes the block, the caret where it was. The last block of a document becomes an empty paragraph, since a document holds at least one. */
27
+ export declare function deleteBlock(editor: Editor, pos: number): boolean;
28
+ /**
29
+ * Moves the block at `from` to `to` (a position between top-level blocks), the
30
+ * caret in it: what a drop in the gutter does, where the editor has no drop of
31
+ * its own. The node moves whole, attributes and all, so it keeps its id, as
32
+ * ProseMirror's drop keeps it.
33
+ */
34
+ export declare function moveBlock(editor: Editor, from: number, to: number): boolean;
35
+ /**
36
+ * Starts the browser's own drag of the block, as ProseMirror starts one of a
37
+ * selected node: the block selected, its slice on `view.dragging` with `move`,
38
+ * and its HTML on the transfer. ProseMirror's drop then deletes the selection
39
+ * and inserts the slice with its attributes — so the block arrives with the
40
+ * id it left with, and every comment anchored to it still finds it.
41
+ */
42
+ export declare function startBlockDrag(editor: Editor, pos: number, event: Pick<globalThis.DragEvent, 'dataTransfer'>, image?: Element): void;
43
+ /**
44
+ * The drop line a dragged block shows, in the package's accent instead of the
45
+ * editor's default (the text's colour). Give it to the editor's StarterKit:
46
+ * `StarterKit.configure({ dropcursor: blockDropCursor })`.
47
+ */
48
+ export declare const blockDropCursor: {
49
+ readonly color: false;
50
+ readonly class: "bg-accent-primary";
51
+ };
52
+ /**
53
+ * `BlockHandle` in an editor: drag the handle to move the block, and the menu
54
+ * gains Turn into, Duplicate and Delete before the caller's rows.
55
+ */
56
+ export declare function EditorBlockHandle({ editor, turnInto, actions, children, className }: EditorBlockHandleProps): import("react").JSX.Element;
57
+ //# sourceMappingURL=EditorBlockHandle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorBlockHandle.d.ts","sourceRoot":"","sources":["../src/EditorBlockHandle.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,IAAI,IAAI,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAG/D,OAAO,EAAkD,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAGhG,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,8EAA8E;IAC9E,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,qEAAqE;IACrE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAAA;CAC5C;AAED,MAAM,WAAW,sBAAsB;IACrC,gGAAgG;IAChG,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;IACnC,qDAAqD;IACrD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS,CAAA;IAC3C,2BAA2B;IAC3B,QAAQ,EAAE,SAAS,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmBD,qIAAqI;AACrI,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAMnE;AAED,+IAA+I;AAC/I,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQhE;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAQ3E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,QAc7H;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe;;;CAAwD,CAAA;AAIpF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,sBAAsB,+BA8DhH"}
package/dist/NavItem.d.ts CHANGED
@@ -26,6 +26,15 @@ import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
26
26
  * `hint` is shown only while the row is pointed at or focused, left of the
27
27
  * count — a neutral `Chip`, typically. It takes its own room while it shows,
28
28
  * so the label ends with "…" before it instead of running under it.
29
+ *
30
+ * `unread` brightens the row's words and icon, sets the label in medium, and
31
+ * draws the `UnreadDot` at the right; `urgent` with it draws the warning badge
32
+ * in the dot's place (Katerina, 2 October: Peek's row, moved in). The ⋮ lands
33
+ * on whatever is in that place, and with nothing there it takes no room until
34
+ * the row is pointed at, so a label at rest keeps the row's whole width.
35
+ *
36
+ * Active is `bg-nav-active`, the current row in a sidebar, which each theme
37
+ * sets: the selected tab's neutral fill in most, Signal's blue in Signal.
29
38
  */
30
39
  export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'children'> {
31
40
  label: string;
@@ -43,6 +52,10 @@ export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'
43
52
  menuLabel?: string;
44
53
  /** Shown only while the row is pointed at or focused, left of the count: a neutral `Chip`, typically. */
45
54
  hint?: ReactNode;
55
+ /** Something new here: brighter words, the label in medium, and the `UnreadDot` at the right. Say so in the row's own words for assistive tech too. */
56
+ unread?: boolean;
57
+ /** With `unread`: the warning badge in the dot's place. */
58
+ urgent?: boolean;
46
59
  /** Rows under this one, folding under it: `NavItem`s. While the row is pointed at or focused, its icon becomes the arrow. */
47
60
  children?: ReactNode;
48
61
  /** With `children`: open unless told otherwise. */
@@ -52,5 +65,5 @@ export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'
52
65
  onOpenChange?: (open: boolean) => void;
53
66
  className?: string;
54
67
  }
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;
68
+ export declare function NavItem({ label, href, count, countLabel, active, icon, menu, menuLabel, hint, unread, urgent, children, defaultOpen, open: openProp, onOpenChange, className, ...props }: NavItemProps): import("react").JSX.Element;
56
69
  //# sourceMappingURL=NavItem.d.ts.map
@@ -1 +1 @@
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"}
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;AAUtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;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,uJAAuJ;IACvJ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,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,MAAc,EACd,MAAc,EACd,QAAQ,EACR,WAAkB,EAClB,IAAI,EAAE,QAAQ,EACd,YAAY,EACZ,SAAS,EACT,GAAG,KAAK,EACT,EAAE,YAAY,+BAwId"}
package/dist/NavTree.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { MouseEvent, ReactNode } from 'react';
1
+ import { type MouseEvent, type ReactNode } from 'react';
2
+ import { type SectionAction } from './SectionHeader';
2
3
  /** One row of the tree, and the rows under it. */
3
4
  export interface NavTreeNode {
4
5
  id: string;
@@ -12,38 +13,117 @@ export interface NavTreeNode {
12
13
  countLabel?: string;
13
14
  /** Shown only while the row is pointed at or focused, left of the count: a neutral `Chip`, typically. */
14
15
  hint?: ReactNode;
16
+ /** Something new here: NavItem's `unread`. */
17
+ unread?: boolean;
18
+ /** With `unread`: the warning badge in the dot's place. */
19
+ urgent?: boolean;
15
20
  /** The row's "More options" menu: `MenuItem`s. */
16
21
  menu?: ReactNode;
17
- /** Rows under this one. Their row folds them; open unless `defaultOpen` says otherwise. */
18
- children?: NavTreeNode[];
22
+ /**
23
+ * Rows under this one. `null` while they are being read: the row has its
24
+ * arrow, and a skeleton under it once open. Absent with `hasChildren`, the
25
+ * same — for rows read only when the row opens.
26
+ */
27
+ children?: NavTreeNode[] | null;
28
+ /** The row has rows under it that are not read yet: it gets its arrow, and `onOpenChange` says when to read them. */
29
+ hasChildren?: boolean;
30
+ /** Said under the row, level with its rows, in place of them: "Nothing in this yet.", "This could not be read." The caller's words. */
31
+ childrenMessage?: string;
19
32
  defaultOpen?: boolean;
33
+ /** The caller owns whether its rows are open. */
34
+ open?: boolean;
35
+ onOpenChange?: (open: boolean) => void;
20
36
  }
21
37
  /** A group heading over its rows: the top of the tree. */
22
38
  export interface NavTreeGroup {
23
39
  id: string;
24
40
  title: string;
25
- nodes: NavTreeNode[];
41
+ /** The rows. `null` while they are being read: a skeleton in their place. */
42
+ nodes: NavTreeNode[] | null;
43
+ /** Said in the rows' place, level with them: "Nothing in this yet.", "This could not be read." The caller's words. */
44
+ message?: string;
45
+ /** Beside the title, always shown: an `UnreadDot`, typically. */
46
+ trailing?: ReactNode;
47
+ /** Beside the title, shown on hover or focus: "Open", typically. */
48
+ actions?: SectionAction[];
49
+ defaultOpen?: boolean;
50
+ /** The caller owns whether the group is open. */
51
+ open?: boolean;
52
+ onOpenChange?: (open: boolean) => void;
53
+ /**
54
+ * The pointer rests on the group, or the keyboard reaches it: read its rows
55
+ * now, so opening it draws them. Resting, not passing — a pointer sweeping
56
+ * down the column calls nothing.
57
+ */
58
+ onIntent?: () => void;
26
59
  }
27
60
  export interface NavTreeProps {
28
61
  /** The quiet label over the groups. Absent, none is drawn. */
29
62
  title?: string;
30
- /** `null` while the first read is on its way: a skeleton in the rows' place. */
31
- groups: NavTreeGroup[] | null;
63
+ /** Beside the label, always shown: "New", typically. */
64
+ titleActions?: SectionAction[];
65
+ /**
66
+ * `null` while the first read is on its way: a skeleton in the groups' place.
67
+ * Absent when the app draws its own groups as `children`.
68
+ */
69
+ groups?: NavTreeGroup[] | null;
70
+ /**
71
+ * The groups, drawn by the app: `NavTreeSection`s with `NavItem` rows — for
72
+ * an app whose groups and rows each read their own data. Used when `groups`
73
+ * is absent.
74
+ */
75
+ children?: ReactNode;
32
76
  /** The row that is current: `active`, and its group starts open. */
33
77
  selected?: string;
34
78
  /** A row was clicked. A router app prevents the default and navigates. */
35
79
  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;
80
+ /** What to say when `groups` is empty. The words are the caller's; absent, nothing is said. */
81
+ emptyMessage?: string;
38
82
  /** Remember each group open or closed in this browser, under this key and the group's id. The app prefixes it. */
39
83
  storageKey?: string;
40
84
  }
85
+ /** How long the pointer rests on a group before `onIntent` (Peek's FolderTree, 150ms). */
86
+ export declare const NAV_TREE_INTENT_MS = 150;
87
+ export interface NavTreeSectionProps {
88
+ title: string;
89
+ /** Marks the group's box as `data-nav-group`, for tests and photo runs to find it. */
90
+ id?: string;
91
+ /** Said in the rows' place, level with them. The caller's words. */
92
+ message?: string;
93
+ /** Beside the title, always shown: an `UnreadDot`, typically. */
94
+ trailing?: ReactNode;
95
+ /** Beside the title, shown on hover or focus: "Open", typically. */
96
+ actions?: SectionAction[];
97
+ /** Open unless told otherwise. */
98
+ defaultOpen?: boolean;
99
+ /** The caller owns whether the group is open. */
100
+ open?: boolean;
101
+ onOpenChange?: (open: boolean) => void;
102
+ /** The pointer rests on the group, or the keyboard reaches it: read its rows now. */
103
+ onIntent?: () => void;
104
+ /** Its rows are being read: a skeleton in their place. */
105
+ loading?: boolean;
106
+ /** Remember it open or closed in this browser, under this key. */
107
+ storageKey?: string;
108
+ /** The rows: `NavItem`s. */
109
+ children?: ReactNode;
110
+ }
111
+ /**
112
+ * One group of a `NavTree`, for an app that draws its own: the standard
113
+ * heading folding its rows, lightly indented under its title, with the
114
+ * group's line, skeleton, actions and `onIntent`. `NavTree` draws these from
115
+ * `groups`; an app whose groups each read their own data draws them itself,
116
+ * as `NavTree`'s children.
117
+ */
118
+ export declare function NavTreeSection({ id, title, message, loading, trailing, actions, defaultOpen, open, onOpenChange, storageKey, onIntent, children }: NavTreeSectionProps): import("react").JSX.Element;
41
119
  /**
42
120
  * 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.
121
+ * tree, then Peek's and Ship's): a quiet label with its actions, then each
122
+ * group as the standard heading folding its rows, lightly indented under its
123
+ * title; rows under rows to any depth, each row a link whose icon becomes the
124
+ * arrow that folds what is under it. Groups and rows can be read when they
125
+ * open. Only the group that holds the current row starts open, unless the
126
+ * caller owns it.
47
127
  */
48
- export declare function NavTree({ title, groups, selected, onSelect, emptyMessage, storageKey }: NavTreeProps): import("react").JSX.Element;
128
+ export declare function NavTree({ title, titleActions, groups, children, selected, onSelect, emptyMessage, storageKey }: NavTreeProps): import("react").JSX.Element;
49
129
  //# sourceMappingURL=NavTree.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"NavTree.d.ts","sourceRoot":"","sources":["../src/NavTree.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The accent dot that says "something new here": a 6px disc centred in a
3
+ * 24px slot, so it sits where a control would and the row does not shift when
4
+ * one replaces the other (Peek's UnreadDot, moved in on 2 October, Katerina).
5
+ *
6
+ * Purely visual: the row that draws it says "unread" in its own accessible
7
+ * name, because a coloured disc reads as nothing to a screen reader.
8
+ */
9
+ export declare function UnreadDot({ className }: {
10
+ className?: string;
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=UnreadDot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnreadDot.d.ts","sourceRoot":"","sources":["../src/UnreadDot.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,+BAM9D"}
package/dist/editor.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
  export { KeptSelection, SelectionToolbar, normalizeHref, type MarkId, type SelectionToolbarProps } from './SelectionToolbar';
11
11
  export { suggestionPopup, isSuggestionActive, isSuggestionOpen, type SuggestionPopupOptions } from './suggestionPopup';
12
+ export { EditorBlockHandle, blockDropCursor, deleteBlock, moveBlock, duplicateBlock, startBlockDrag, type EditorBlockHandleProps, type BlockTurnInto } from './EditorBlockHandle';
12
13
  export { PersonMention, UrgentPersonMention, CaptionedReference, ReferenceTrigger, SlashCommands, peopleMenu, referenceMenu, filterPeople, urgentPubkeys, filterReferences, formatSection, applyFormat, typeTrigger, filterSlashSections, FORMATS, type MentionPerson, type PersonMentionOptions, type CaptionedItem, type CaptionedReferenceOptions, type ReferenceTriggerOptions, type ReferenceMenuOptions, type SlashCommand, type SlashSection, type SlashCommandsOptions, type FormatId, type FormatDef, type CodeFormat, } from './ComposerTriggers';
13
14
  export { BlockId, UnknownBlock, ReferenceNode, AttachmentNode, type ReferenceNodeOptions, type ReferenceViewProps, type AttachmentNodeOptions, type AttachmentViewProps, } from './BlockNodes';
14
15
  //# sourceMappingURL=editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC5H,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AACtH,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,OAAO,EACP,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,OAAO,EACP,YAAY,EACZ,aAAa,EACb,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC5H,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AACtH,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,sBAAsB,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACjL,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,WAAW,EACX,mBAAmB,EACnB,OAAO,EACP,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,OAAO,EACP,YAAY,EACZ,aAAa,EACb,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,GACzB,MAAM,cAAc,CAAA"}
package/dist/editor.js CHANGED
@@ -364,6 +364,116 @@ function suggestionPopup(options) {
364
364
  };
365
365
  }
366
366
 
367
+ // src/EditorBlockHandle.tsx
368
+ import { useMemo } from "react";
369
+ import { IconCopy, IconTrash } from "@tabler/icons-react";
370
+ import { BlockHandle, blockAtY } from "@estiva-app/ui";
371
+ import { MenuItem, MenuSub } from "@estiva-app/ui";
372
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
373
+ function editorBlocks(view) {
374
+ const out = [];
375
+ view.state.doc.forEach((node, pos) => {
376
+ const element = view.nodeDOM(pos);
377
+ if (element instanceof HTMLElement) out.push({ id: node.attrs.blockId || void 0, element, pos });
378
+ });
379
+ return out;
380
+ }
381
+ function duplicateBlock(editor, pos) {
382
+ const node = editor.state.doc.nodeAt(pos);
383
+ if (!node) return false;
384
+ editor.view.dispatch(editor.state.tr.insert(pos + node.nodeSize, node).scrollIntoView());
385
+ editor.commands.focus(pos + node.nodeSize + 1);
386
+ return true;
387
+ }
388
+ function deleteBlock(editor, pos) {
389
+ const { state } = editor;
390
+ const node = state.doc.nodeAt(pos);
391
+ if (!node) return false;
392
+ const tr = state.doc.childCount === 1 ? state.tr.replaceWith(pos, pos + node.nodeSize, state.schema.nodes.paragraph.create()) : state.tr.delete(pos, pos + node.nodeSize);
393
+ editor.view.dispatch(tr);
394
+ editor.commands.focus(Math.min(pos + 1, editor.state.doc.content.size));
395
+ return true;
396
+ }
397
+ function moveBlock(editor, from, to) {
398
+ const node = editor.state.doc.nodeAt(from);
399
+ if (!node || to >= from && to <= from + node.nodeSize) return false;
400
+ const tr = editor.state.tr.delete(from, from + node.nodeSize);
401
+ const at = tr.mapping.map(to);
402
+ editor.view.dispatch(tr.insert(at, node).scrollIntoView());
403
+ editor.commands.focus(at + 1);
404
+ return true;
405
+ }
406
+ function startBlockDrag(editor, pos, event, image) {
407
+ editor.commands.setNodeSelection(pos);
408
+ const { view } = editor;
409
+ const slice = view.state.selection.content();
410
+ const { dom, text } = view.serializeForClipboard(slice);
411
+ const transfer = event.dataTransfer;
412
+ if (transfer) {
413
+ transfer.clearData();
414
+ transfer.setData("text/html", dom.innerHTML);
415
+ transfer.setData("text/plain", text);
416
+ transfer.effectAllowed = "copyMove";
417
+ if (image) transfer.setDragImage(image, 0, 0);
418
+ }
419
+ view.dragging = { slice, move: true };
420
+ }
421
+ var blockDropCursor = { color: false, class: "bg-accent-primary" };
422
+ var NOTHING_YET = { find: () => null, rows: () => null, startDrag: () => {
423
+ }, drop: () => {
424
+ }, endDrag: () => {
425
+ }, onChange: () => () => {
426
+ } };
427
+ function EditorBlockHandle({ editor, turnInto = [], actions, children, className }) {
428
+ const editing = useMemo(() => {
429
+ if (!editor) return NOTHING_YET;
430
+ let dragged;
431
+ return {
432
+ find: (y) => blockAtY(editorBlocks(editor.view), y),
433
+ rows: (block) => {
434
+ const pos = block.pos;
435
+ if (!editor.isEditable) return null;
436
+ const node = pos === void 0 ? null : editor.state.doc.nodeAt(pos);
437
+ if (pos === void 0 || !node) return null;
438
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
439
+ node.isTextblock && turnInto.length > 0 && /* @__PURE__ */ jsx3(MenuSub, { label: "Turn into", children: turnInto.map((row) => /* @__PURE__ */ jsx3(
440
+ MenuItem,
441
+ {
442
+ label: row.label,
443
+ selected: row.active?.(node) ?? false,
444
+ onClick: () => {
445
+ editor.chain().focus().setTextSelection(pos + 1).run();
446
+ row.run(editor);
447
+ }
448
+ },
449
+ row.label
450
+ )) }),
451
+ /* @__PURE__ */ jsx3(MenuItem, { label: "Duplicate", leading: /* @__PURE__ */ jsx3(IconCopy, { size: 16, stroke: 1.5, className: "text-text-secondary" }), onClick: () => duplicateBlock(editor, pos) }),
452
+ /* @__PURE__ */ jsx3(MenuItem, { label: "Delete", destructive: true, leading: /* @__PURE__ */ jsx3(IconTrash, { size: 16, stroke: 1.5, className: "text-error-default" }), onClick: () => deleteBlock(editor, pos) })
453
+ ] });
454
+ },
455
+ startDrag: (block, event) => {
456
+ dragged = block.pos;
457
+ if (block.pos !== void 0) startBlockDrag(editor, block.pos, event, block.element);
458
+ },
459
+ drop: (target, after) => {
460
+ const node = target.pos === void 0 ? null : editor.state.doc.nodeAt(target.pos);
461
+ if (dragged === void 0 || target.pos === void 0 || !node) return;
462
+ moveBlock(editor, dragged, after ? target.pos + node.nodeSize : target.pos);
463
+ },
464
+ // ProseMirror clears `dragging` on its own dragend, which a drag started outside its element never reaches.
465
+ endDrag: () => setTimeout(() => editor.view.dragging = null, 50),
466
+ onChange: (changed) => {
467
+ editor.on("update", changed);
468
+ return () => {
469
+ editor.off("update", changed);
470
+ };
471
+ }
472
+ };
473
+ }, [editor, turnInto]);
474
+ return /* @__PURE__ */ jsx3(BlockHandle, { editing, actions, className, children });
475
+ }
476
+
367
477
  // src/ComposerTriggers.tsx
368
478
  import { Extension as Extension2 } from "@tiptap/core";
369
479
  import { PluginKey as PluginKey2 } from "@tiptap/pm/state";
@@ -373,7 +483,7 @@ import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
373
483
  import { Avatar } from "@estiva-app/ui";
374
484
  import { EnterHint } from "@estiva-app/ui";
375
485
  import { inlineChipClassName } from "@estiva-app/ui";
376
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
486
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
377
487
  var PEOPLE_LIMIT = 6;
378
488
  function filterPeople(people, query) {
379
489
  const q = query.toLowerCase();
@@ -391,8 +501,8 @@ function peopleMenu(urgent) {
391
501
  row: (person) => ({
392
502
  label: person.name,
393
503
  description: person.description,
394
- leading: person.avatar ?? /* @__PURE__ */ jsx3(Avatar, { size: 32, alt: person.name }),
395
- hint: /* @__PURE__ */ jsx3(EnterHint, {}),
504
+ leading: person.avatar ?? /* @__PURE__ */ jsx4(Avatar, { size: 32, alt: person.name }),
505
+ hint: /* @__PURE__ */ jsx4(EnterHint, {}),
396
506
  size: "tall"
397
507
  })
398
508
  };
@@ -513,9 +623,9 @@ var CaptionedReference = Mention.extend({
513
623
  addNodeView() {
514
624
  const icon = this.options.icon;
515
625
  return ReactNodeViewRenderer(
516
- ({ node }) => /* @__PURE__ */ jsxs2(NodeViewWrapper, { as: "span", className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
517
- /* @__PURE__ */ jsx3("span", { className: "flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary", children: icon }),
518
- /* @__PURE__ */ jsx3("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
626
+ ({ node }) => /* @__PURE__ */ jsxs3(NodeViewWrapper, { as: "span", className: inlineChipClassName("neutral", "max-w-[24ch] cursor-default"), children: [
627
+ /* @__PURE__ */ jsx4("span", { className: "flex items-center justify-center w-4 h-4 shrink-0 text-text-secondary", children: icon }),
628
+ /* @__PURE__ */ jsx4("span", { className: "truncate", children: captionOf(node.attrs.label, node.attrs.snippet) })
519
629
  ] }),
520
630
  { as: "span" }
521
631
  );
@@ -537,7 +647,7 @@ function referenceMenu({ ariaLabel, sectionLabel, icon }) {
537
647
  return groups;
538
648
  },
539
649
  itemKey: (item) => `reference-${item.id}`,
540
- row: (item) => ({ leading: icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx3(EnterHint, {}) })
650
+ row: (item) => ({ leading: icon, label: captionOf(item.label, item.snippet), description: item.description, hint: /* @__PURE__ */ jsx4(EnterHint, {}) })
541
651
  };
542
652
  }
543
653
  var listed = (options, query) => options.sections ? options.sections(query).flatMap((section) => section.items.map((item) => ({ ...item, section: section.label }))) : filterReferences(options.items(), query);
@@ -665,7 +775,7 @@ import { Extension as Extension3, Node as Node2 } from "@tiptap/core";
665
775
  import { Plugin as Plugin2, PluginKey as PluginKey3 } from "@tiptap/pm/state";
666
776
  import { Mapping, ReplaceAroundStep } from "@tiptap/pm/transform";
667
777
  import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
668
- import { jsx as jsx4 } from "react/jsx-runtime";
778
+ import { jsx as jsx5 } from "react/jsx-runtime";
669
779
  var BLOCK_TYPES = [
670
780
  "paragraph",
671
781
  "heading",
@@ -882,7 +992,7 @@ var ReferenceNode = Node2.create({
882
992
  addNodeView() {
883
993
  const View = this.options.view;
884
994
  if (!View) return null;
885
- return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx4(NodeViewWrapper2, { as: "span", className: "inline", children: /* @__PURE__ */ jsx4(View, { uri: String(node.attrs.uri ?? "") }) }));
995
+ return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx5(NodeViewWrapper2, { as: "span", className: "inline", children: /* @__PURE__ */ jsx5(View, { uri: String(node.attrs.uri ?? "") }) }));
886
996
  }
887
997
  });
888
998
  var UnknownBlock = Node2.create({
@@ -910,7 +1020,7 @@ var UnknownBlock = Node2.create({
910
1020
  function UnknownBlockView({ node }) {
911
1021
  const source = node.attrs.source;
912
1022
  const text = String(node.attrs.text ?? "");
913
- return /* @__PURE__ */ jsx4(
1023
+ return /* @__PURE__ */ jsx5(
914
1024
  NodeViewWrapper2,
915
1025
  {
916
1026
  "data-block-id": source?.id,
@@ -950,13 +1060,14 @@ var AttachmentNode = Node2.create({
950
1060
  addNodeView() {
951
1061
  const View = this.options.view;
952
1062
  if (!View) return null;
953
- return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx4(NodeViewWrapper2, { className: "my-2", children: /* @__PURE__ */ jsx4(View, { attrs: node.attrs }) }));
1063
+ return ReactNodeViewRenderer2(({ node }) => /* @__PURE__ */ jsx5(NodeViewWrapper2, { className: "my-2", children: /* @__PURE__ */ jsx5(View, { attrs: node.attrs }) }));
954
1064
  }
955
1065
  });
956
1066
  export {
957
1067
  AttachmentNode,
958
1068
  BlockId,
959
1069
  CaptionedReference,
1070
+ EditorBlockHandle,
960
1071
  FORMATS,
961
1072
  KeptSelection,
962
1073
  PersonMention,
@@ -967,15 +1078,20 @@ export {
967
1078
  UnknownBlock,
968
1079
  UrgentPersonMention,
969
1080
  applyFormat,
1081
+ blockDropCursor,
1082
+ deleteBlock,
1083
+ duplicateBlock,
970
1084
  filterPeople,
971
1085
  filterReferences,
972
1086
  filterSlashSections,
973
1087
  formatSection,
974
1088
  isSuggestionActive,
975
1089
  isSuggestionOpen,
1090
+ moveBlock,
976
1091
  normalizeHref,
977
1092
  peopleMenu,
978
1093
  referenceMenu,
1094
+ startBlockDrag,
979
1095
  suggestionPopup,
980
1096
  typeTrigger,
981
1097
  urgentPubkeys