@estiva-app/ui 0.44.1 → 0.46.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 (82) hide show
  1. package/dist/CollapsibleSection.d.ts +7 -1
  2. package/dist/CollapsibleSection.d.ts.map +1 -1
  3. package/dist/InlineChip.d.ts +1 -1
  4. package/dist/NavItem.d.ts +11 -1
  5. package/dist/NavItem.d.ts.map +1 -1
  6. package/dist/SectionHeader.d.ts +9 -1
  7. package/dist/SectionHeader.d.ts.map +1 -1
  8. package/dist/SelectionToolbar.d.ts +62 -0
  9. package/dist/SelectionToolbar.d.ts.map +1 -0
  10. package/dist/SuggestionMenu.d.ts +75 -0
  11. package/dist/SuggestionMenu.d.ts.map +1 -0
  12. package/dist/editor.d.ts +12 -0
  13. package/dist/editor.d.ts.map +1 -0
  14. package/dist/editor.js +357 -0
  15. package/dist/editor.js.map +7 -0
  16. package/dist/eslint/index.js +4 -3
  17. package/dist/eslint/index.js.map +2 -2
  18. package/dist/eslint/no-restyled-part.d.ts.map +1 -1
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +274 -133
  22. package/dist/index.js.map +4 -4
  23. package/dist/looks.d.ts +3 -0
  24. package/dist/looks.d.ts.map +1 -1
  25. package/dist/registry/{app-YMABQII6.js → app-LDGOH6UW.js} +3 -3
  26. package/dist/registry/{build-3EANARLQ.js → build-P6X3PTZP.js} +6 -2
  27. package/dist/registry/build.d.ts +4 -0
  28. package/dist/registry/build.d.ts.map +1 -1
  29. package/dist/registry/{chunk-SIB44CES.js → chunk-P55P2QFD.js} +9 -4
  30. package/dist/registry/chunk-P55P2QFD.js.map +7 -0
  31. package/dist/registry/{chunk-EVJW26UR.js → chunk-TQ6UQER3.js} +10 -10
  32. package/dist/registry/chunk-TQ6UQER3.js.map +7 -0
  33. package/dist/registry/cli.js +2 -2
  34. package/dist/registry/index.js +2 -2
  35. package/dist/shortcuts.d.ts +27 -0
  36. package/dist/shortcuts.d.ts.map +1 -0
  37. package/dist/suggestionPopup.d.ts +41 -0
  38. package/dist/suggestionPopup.d.ts.map +1 -0
  39. package/package.json +27 -2
  40. package/registry.json +391 -11
  41. package/src/Chip.tsx +1 -1
  42. package/src/CollapsibleSection.mdx +5 -0
  43. package/src/CollapsibleSection.stories.tsx +5 -0
  44. package/src/CollapsibleSection.tsx +10 -3
  45. package/src/CommandPalette.tsx +1 -1
  46. package/src/InlineChip.test.tsx +1 -1
  47. package/src/InlineChip.tsx +1 -1
  48. package/src/NavItem.mdx +9 -0
  49. package/src/NavItem.stories.tsx +23 -0
  50. package/src/NavItem.tsx +56 -6
  51. package/src/Reaction.tsx +1 -1
  52. package/src/SectionHeader.mdx +9 -0
  53. package/src/SectionHeader.stories.tsx +15 -0
  54. package/src/SectionHeader.tsx +28 -7
  55. package/src/SelectionToolbar.mdx +78 -0
  56. package/src/SelectionToolbar.stories.tsx +63 -0
  57. package/src/SelectionToolbar.test.ts +105 -0
  58. package/src/SelectionToolbar.tsx +487 -0
  59. package/src/SuggestionMenu.mdx +107 -0
  60. package/src/SuggestionMenu.stories.tsx +202 -0
  61. package/src/SuggestionMenu.test.tsx +128 -0
  62. package/src/SuggestionMenu.tsx +189 -0
  63. package/src/editor.ts +11 -0
  64. package/src/eslint/no-rebuilt-behaviour.test.ts +1 -1
  65. package/src/eslint/no-rebuilt-behaviour.ts +2 -2
  66. package/src/eslint/no-restyled-part.ts +2 -1
  67. package/src/how.test.ts +1 -1
  68. package/src/index.ts +2 -0
  69. package/src/keptSelection.test.ts +67 -0
  70. package/src/looks.ts +4 -0
  71. package/src/registry/app.ts +8 -8
  72. package/src/registry/build.ts +10 -2
  73. package/src/registry/registry.test.ts +14 -9
  74. package/src/shortcuts.ts +40 -0
  75. package/src/suggestionPopup.tsx +119 -0
  76. package/stories/Choosing.mdx +2 -0
  77. package/tailwind-preset.js +1 -0
  78. package/tokens.css +19 -4
  79. package/dist/registry/chunk-EVJW26UR.js.map +0 -7
  80. package/dist/registry/chunk-SIB44CES.js.map +0 -7
  81. /package/dist/registry/{app-YMABQII6.js.map → app-LDGOH6UW.js.map} +0 -0
  82. /package/dist/registry/{build-3EANARLQ.js.map → build-P6X3PTZP.js.map} +0 -0
@@ -33,6 +33,12 @@ export interface CollapsibleSectionProps {
33
33
  /** Beside the title, revealed on hover or focus — `SectionHeader`'s. */
34
34
  actions?: SectionAction[];
35
35
  showActions?: 'hover' | 'always';
36
+ /**
37
+ * `heading` (default): the title is a section heading. `row`: the fold is a
38
+ * row among rows — a folder in a sidebar — and its rows sit indented under
39
+ * its title, past the chevron (`SectionHeader`'s `look`).
40
+ */
41
+ look?: 'heading' | 'row';
36
42
  /** The rows. */
37
43
  children: ReactNode;
38
44
  /** On the section's box: `mt-2` between groups, `shrink-0` in a scrolling column. */
@@ -40,5 +46,5 @@ export interface CollapsibleSectionProps {
40
46
  /** On the box the rows sit in: the gap between them. */
41
47
  contentClassName?: string;
42
48
  }
43
- export declare function CollapsibleSection({ title, defaultOpen, open: openProp, onOpenChange, storageKey, trailing, actions, showActions, children, className, contentClassName }: CollapsibleSectionProps): import("react").JSX.Element;
49
+ export declare function CollapsibleSection({ title, defaultOpen, open: openProp, onOpenChange, storageKey, trailing, actions, showActions, look, children, className, contentClassName }: CollapsibleSectionProps): import("react").JSX.Element;
44
50
  //# 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,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,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,EAAE,uBAAuB,+BAyBzM"}
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"}
@@ -28,7 +28,7 @@ export declare const INLINE_CHIP_TONE_CLASSES: {
28
28
  /** A thing or a place — anything that is not a person. */
29
29
  readonly neutral: "bg-bg-active text-text-primary";
30
30
  /** A person. */
31
- readonly person: "bg-accent-muted text-accent-primary";
31
+ readonly person: "bg-accent-muted text-accent-text";
32
32
  /** A person, called urgently. */
33
33
  readonly urgent: "bg-warning-muted text-warning-default";
34
34
  /**
package/dist/NavItem.d.ts CHANGED
@@ -9,6 +9,12 @@ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
9
9
  * count draws its zero (see Tabs) — both rulings stand, deliberately.
10
10
  *
11
11
  * Active is the same fill as a selected tab — `bg-active`, neutral.
12
+ *
13
+ * `menu` gives the row a "More options" menu (UIG-44, Katerina, 30
14
+ * September): a ⋮ that shows while the row is pointed at, focused or its
15
+ * menu is open, in the count's place — the count steps aside rather than
16
+ * sitting under it. The ⋮ is beside the link, not inside it (a button inside
17
+ * an anchor is not a button), and follows it in the Tab order.
12
18
  */
13
19
  export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'> {
14
20
  label: string;
@@ -20,7 +26,11 @@ export interface NavItemProps extends Omit<ComponentPropsWithoutRef<'a'>, 'href'
20
26
  active?: boolean;
21
27
  /** 16px, stroke 1.5. */
22
28
  icon?: ReactNode;
29
+ /** The row's "More options" menu: `MenuItem`s. Absent, the row has no menu and is exactly the plain row. */
30
+ menu?: ReactNode;
31
+ /** The ⋮'s accessible name and tooltip. Default "More options for {label}". */
32
+ menuLabel?: string;
23
33
  className?: string;
24
34
  }
25
- export declare function NavItem({ label, href, count, countLabel, active, icon, className, ...props }: NavItemProps): import("react").JSX.Element;
35
+ export declare function NavItem({ label, href, count, countLabel, active, icon, menu, menuLabel, className, ...props }: NavItemProps): import("react").JSX.Element;
26
36
  //# 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;AAIhE;;;;;;;;;;GAUG;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,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAc,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,+BA6BlH"}
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"}
@@ -59,6 +59,14 @@ export interface SectionHeaderProps {
59
59
  * where the buttons light up on their own (UIG-14, Katerina, 19 September).
60
60
  */
61
61
  hover?: 'fill' | 'none';
62
+ /**
63
+ * `heading` (default): a section's title, the `SectionLabel`. `quiet`: a
64
+ * small grey label over a list (12px, muted) — "Folders" over the folders,
65
+ * which never reads as one of them. `row`: a row among rows — the chevron
66
+ * and the title at a row's weight and colour, as a sidebar row is (Katerina,
67
+ * 30 September: Leaf's folders, in Ship).
68
+ */
69
+ look?: 'heading' | 'quiet' | 'row';
62
70
  /**
63
71
  * What the title renders as, in Base UI's manner. A plain button with
64
72
  * `onToggle` by default; `CollapsibleSection` hands in `Collapsible.Trigger`.
@@ -66,5 +74,5 @@ export interface SectionHeaderProps {
66
74
  render?: useRender.RenderProp;
67
75
  className?: string;
68
76
  }
69
- export declare function SectionHeader({ title, chevron, isExpanded, onToggle, trailing, actions, showActions, hover, render, className }: SectionHeaderProps): import("react").JSX.Element;
77
+ export declare function SectionHeader({ title, chevron, isExpanded, onToggle, trailing, actions, showActions, hover, look, render, className }: SectionHeaderProps): import("react").JSX.Element;
70
78
  //# sourceMappingURL=SectionHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SectionHeader.d.ts","sourceRoot":"","sources":["../src/SectionHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAKrD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,aAAa;IAC5B,wBAAwB;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,yCAAyC;IACzC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;IACzB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC,UAAU,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,OAAe,EAAE,UAAiB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAqB,EAAE,KAAc,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,kBAAkB,+BA2DrL"}
1
+ {"version":3,"file":"SectionHeader.d.ts","sourceRoot":"","sources":["../src/SectionHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAMrD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,WAAW,aAAa;IAC5B,wBAAwB;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,yCAAyC;IACzC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;IACzB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,CAAA;IAClC;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC,UAAU,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,OAAe,EAAE,UAAiB,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAqB,EAAE,KAAc,EAAE,IAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,kBAAkB,+BAuEvM"}
@@ -0,0 +1,62 @@
1
+ import type { Editor } from '@tiptap/react';
2
+ import { Extension } from '@tiptap/core';
3
+ import { PluginKey } from '@tiptap/pm/state';
4
+ /**
5
+ * The selection, kept visible while something else has the focus — Peek's
6
+ * `src/extensions/keptSelection.ts` (PEE-19), moved into the package with the
7
+ * `SelectionToolbar` that needs it (UIG-31, Katerina's ruling 29 September).
8
+ *
9
+ * The toolbar's address field takes the focus, and with it the document's
10
+ * one selection, so the words being linked lost their highlight the moment
11
+ * the field opened (measured 2026-09-07). Linear keeps it, and Katerina asked
12
+ * for the same. The editor's own selection never moved — ProseMirror keeps it
13
+ * while unfocused — so this only *draws* it: an inline decoration over the
14
+ * range, in `bg-selected` — the token Peek's composer paints `::selection` in,
15
+ * mapped through edits and cleared when the field closes.
16
+ *
17
+ * A decoration rather than a second selection, because a document has one
18
+ * and the field needs it. Editor-internal by design (the editor owns its own
19
+ * drawing); nothing here floats, focuses or handles a key.
20
+ */
21
+ export interface KeptRange {
22
+ from: number;
23
+ to: number;
24
+ }
25
+ /** The plugin's key: read what is being kept, or dispatch a change to it. */
26
+ export declare const keptSelectionKey: PluginKey<KeptRange | null>;
27
+ /** The extension an editor registers so a selection can stay drawn while a field has the focus. */
28
+ export declare const KeptSelection: Extension<any, any>;
29
+ /** Draw the selection as it is now, until `releaseSelection`. */
30
+ export declare function keepSelection(editor: Editor): void;
31
+ /** Stop drawing it. Nothing kept, nothing dispatched. */
32
+ export declare function releaseSelection(editor: Editor): void;
33
+ /** What is being drawn, or null. */
34
+ export declare function keptSelection(editor: Editor): KeptRange | null;
35
+ /** A mark the strip can toggle — the suite's inline vocabulary (SPEC §13.1) less link and reference. */
36
+ export type MarkId = 'bold' | 'italic' | 'underline' | 'code';
37
+ /** The address the selection sits in, or '' — what the link field opens with. */
38
+ export declare function currentLink(editor: Editor): string;
39
+ /**
40
+ * What somebody typed into the link field → an `href`, or null when it cannot
41
+ * be one. A bare host (`google.com`) gets `https://`. A scheme is kept only when
42
+ * SPEC §13.1 lets a reader follow it (http, https, mailto); `javascript:` and
43
+ * the like are refused here. `localhost:5173` is a host and a port: a scheme is
44
+ * never followed by a digit.
45
+ */
46
+ export declare function normalizeHref(raw: string): string | null;
47
+ /**
48
+ * Apply the link field to the selection. Empty removes the link; an address
49
+ * sets it, over the whole link when the caret sits inside one. `false` means
50
+ * the text could not be an address, and the field should stay open.
51
+ */
52
+ export declare function applyLink(editor: Editor, raw: string): boolean;
53
+ export interface SelectionToolbarProps {
54
+ /** The editor whose selection the strip follows. `null` while it loads. */
55
+ editor: Editor | null;
56
+ /** Which marks the strip toggles, in order. Default bold, italic, underline. */
57
+ marks?: readonly MarkId[];
58
+ /** The Link button and its address field. Needs a link mark in the editor and `KeptSelection`. Default off. */
59
+ link?: boolean;
60
+ }
61
+ export declare function SelectionToolbar({ editor, marks, link }: SelectionToolbarProps): import("react").JSX.Element | null;
62
+ //# sourceMappingURL=SelectionToolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectionToolbar.d.ts","sourceRoot":"","sources":["../src/SelectionToolbar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAU,SAAS,EAAE,MAAM,kBAAkB,CAAA;AASpD;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,6BAAmD,CAAA;AAEhF,mGAAmG;AACnG,eAAO,MAAM,aAAa,qBA8BxB,CAAA;AAEF,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,QAG3C;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,QAG9C;AAED,oCAAoC;AACpC,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE9D;AAED,wGAAwG;AACxG,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,MAAM,CAAA;AAkB7D,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMxD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ9D;AA2ED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,+GAA+G;IAC/G,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,KAAuC,EAAE,IAAY,EAAE,EAAE,qBAAqB,sCAqQxH"}
@@ -0,0 +1,75 @@
1
+ import { type ReactNode, type Ref } from 'react';
2
+ import { type MenuItemProps } from './Menu';
3
+ /**
4
+ * The list a text editor opens as you type — `/` for commands, `@` for people,
5
+ * `[` for a reference. UIG-31: Peek's four type-ahead menus, made one part.
6
+ *
7
+ * **Why it is not a `Combobox` or a `Menu`.** Base UI's lists need a text field
8
+ * of their own, and the text being completed is an editor, not an `<input>`.
9
+ * So focus never leaves the text: the editor hands its arrow keys and Enter to
10
+ * this list through `handle`, and the list tells the editor which row is
11
+ * active (`aria-activedescendant`), the pattern Base UI's `Combobox` builds.
12
+ * If Base UI ever adds a list that can be driven from outside its own field,
13
+ * this part moves onto it.
14
+ *
15
+ * **What it owns**, all of it written once here instead of in every menu:
16
+ *
17
+ * - the box: the package `Popover`, anchored to the caret, above it and below
18
+ * when there is no room, clamped to the window, scrolling in the package's
19
+ * `ScrollArea` past `maxHeight`;
20
+ * - the highlighted row: ↑ and ↓ (wrapping at the ends), Enter to choose, the
21
+ * pointer moving the highlight, a press choosing without taking focus from
22
+ * the text, the highlight back on the first row whenever the list changes,
23
+ * and the highlighted row scrolled into view;
24
+ * - what a screen reader is told: the list is a `listbox`, each row an
25
+ * `option`, and the editor says it completes into this list and which row
26
+ * is active — all removed again when the list closes.
27
+ *
28
+ * What stays with the app: which rows exist, what choosing one does, and what
29
+ * each row shows (its label, face, keys, hint) — `row` returns the package
30
+ * `MenuItem`'s own props. With nothing to show there is no box at all.
31
+ */
32
+ export interface SuggestionMenuRow {
33
+ label: string;
34
+ description?: string;
35
+ leading?: ReactNode;
36
+ /** What you would type — drawn as keys at the right, e.g. `#` for a heading. */
37
+ shortcut?: string;
38
+ /** Drawn at the right while the row is highlighted, e.g. `<EnterHint />`. */
39
+ hint?: ReactNode;
40
+ /** `tall` for a row with a face and a second line — `MenuItem`'s own sizes. */
41
+ size?: MenuItemProps['size'];
42
+ }
43
+ export interface SuggestionMenuSection<T> {
44
+ /** The section's heading. Without one, the rows stand alone. */
45
+ label?: string;
46
+ items: readonly T[];
47
+ /** On the heading row, as `MenuSection`'s. */
48
+ className?: string;
49
+ }
50
+ /** What the editor calls while the list is open. */
51
+ export interface SuggestionMenuHandle {
52
+ /** ↑, ↓ and Enter. `true` when the list used the key, so the editor must not. */
53
+ onKeyDown: (event: KeyboardEvent) => boolean;
54
+ }
55
+ export interface SuggestionMenuProps<T> {
56
+ /** The caret's rect, from the editor. `null` closes the box. */
57
+ rect: DOMRect | null;
58
+ /** The editor's own element — told which row is active. Absent in a story. */
59
+ editorElement?: HTMLElement | null;
60
+ /** Names the list — "Commands", "People". */
61
+ ariaLabel: string;
62
+ /** The box's width, e.g. `w-[300px]`. */
63
+ width: string;
64
+ /** The cap before the rows scroll, e.g. `max-h-[400px]`. */
65
+ maxHeight: string;
66
+ sections: readonly SuggestionMenuSection<T>[];
67
+ /** A stable key for a row. */
68
+ itemKey: (item: T) => string;
69
+ /** What a row shows. */
70
+ row: (item: T) => SuggestionMenuRow;
71
+ onSelect: (item: T) => void;
72
+ handle?: Ref<SuggestionMenuHandle>;
73
+ }
74
+ export declare function SuggestionMenu<T>({ rect, editorElement, ariaLabel, width, maxHeight, sections, itemKey, row, onSelect, handle }: SuggestionMenuProps<T>): import("react").JSX.Element | null;
75
+ //# sourceMappingURL=SuggestionMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SuggestionMenu.d.ts","sourceRoot":"","sources":["../src/SuggestionMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAyE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAE,MAAM,OAAO,CAAA;AACvH,OAAO,EAAwC,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAA;AAGjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC7B;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,SAAS,CAAC,EAAE,CAAA;IACnB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,oDAAoD;AACpD,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAA;CAC7C;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,gEAAgE;IAChE,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;IACpB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IAClC,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAA;IACjB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,SAAS,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7C,8BAA8B;IAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;IAC5B,wBAAwB;IACxB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,iBAAiB,CAAA;IACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;IAC3B,MAAM,CAAC,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAA;CACnC;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC,sCA6GvJ"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@estiva-app/ui/editor` — the parts that work inside a Tiptap editor (UIG-31,
3
+ * Katerina's ruling 29 September: one editor corner in the package, option A).
4
+ *
5
+ * Its own entry because it imports Tiptap, which only an app with an editor
6
+ * installs: the main entry stays free of it, and the Tiptap peers are optional,
7
+ * as the eslint ones are. The rest of the package it draws with — `Popover`,
8
+ * `Toolbar`, `MenuItem`, `SuggestionMenu` — is the main entry's own, not a copy.
9
+ */
10
+ export { KeptSelection, SelectionToolbar, normalizeHref, type MarkId, type SelectionToolbarProps } from './SelectionToolbar';
11
+ export { suggestionPopup, isSuggestionActive, isSuggestionOpen, type SuggestionPopupOptions } from './suggestionPopup';
12
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +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"}
package/dist/editor.js ADDED
@@ -0,0 +1,357 @@
1
+ // src/SelectionToolbar.tsx
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { Extension } from "@tiptap/core";
4
+ import { Plugin, PluginKey } from "@tiptap/pm/state";
5
+ import { Decoration, DecorationSet } from "@tiptap/pm/view";
6
+ import { IconBold, IconCode, IconItalic, IconLink, IconUnderline } from "@tabler/icons-react";
7
+ import { Form } from "@estiva-app/ui";
8
+ import { Popover } from "@estiva-app/ui";
9
+ import { TextInput } from "@estiva-app/ui";
10
+ import { Toolbar, ToolbarButton } from "@estiva-app/ui";
11
+ import { shortcutLabel } from "@estiva-app/ui";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ var keptSelectionKey = new PluginKey("keptSelection");
14
+ var KeptSelection = Extension.create({
15
+ name: "keptSelection",
16
+ addProseMirrorPlugins() {
17
+ return [
18
+ new Plugin({
19
+ key: keptSelectionKey,
20
+ state: {
21
+ init: () => null,
22
+ apply(tr, previous) {
23
+ const meta = tr.getMeta(keptSelectionKey);
24
+ if (meta !== void 0) return meta;
25
+ if (!previous) return null;
26
+ return { from: tr.mapping.map(previous.from), to: tr.mapping.map(previous.to) };
27
+ }
28
+ },
29
+ props: {
30
+ decorations(state) {
31
+ const range = keptSelectionKey.getState(state);
32
+ if (!range || range.from === range.to) return null;
33
+ return DecorationSet.create(state.doc, [
34
+ Decoration.inline(range.from, range.to, { class: "kept-selection bg-bg-selected" })
35
+ ]);
36
+ }
37
+ }
38
+ })
39
+ ];
40
+ }
41
+ });
42
+ function keepSelection(editor) {
43
+ const { from, to } = editor.state.selection;
44
+ editor.view.dispatch(editor.state.tr.setMeta(keptSelectionKey, { from, to }));
45
+ }
46
+ function releaseSelection(editor) {
47
+ if (editor.isDestroyed || !keptSelectionKey.getState(editor.state)) return;
48
+ editor.view.dispatch(editor.state.tr.setMeta(keptSelectionKey, null));
49
+ }
50
+ var MARKS = {
51
+ bold: { label: "Bold", keys: "Mod-B" },
52
+ italic: { label: "Italic", keys: "Mod-I" },
53
+ underline: { label: "Underline", keys: "Mod-U" },
54
+ code: { label: "Code", keys: "Mod-E" }
55
+ };
56
+ function toggleMark(editor, id) {
57
+ return editor.chain().focus().toggleMark(id).run();
58
+ }
59
+ function currentLink(editor) {
60
+ const href = editor.getAttributes("link").href;
61
+ return typeof href === "string" ? href : "";
62
+ }
63
+ function normalizeHref(raw) {
64
+ const s = raw.trim();
65
+ if (!s || /\s/.test(s)) return null;
66
+ if (/^(https?:|mailto:)/i.test(s)) return s;
67
+ if (/^[a-z][a-z0-9+.-]*:(?!\d)/i.test(s)) return null;
68
+ return `https://${s}`;
69
+ }
70
+ function applyLink(editor, raw) {
71
+ const chain = editor.chain().focus().extendMarkRange("link");
72
+ if (!raw.trim()) return chain.unsetMark("link", { extendEmptyMarkRange: true }).setMeta("preventAutolink", true).run();
73
+ const href = normalizeHref(raw);
74
+ if (!href) return false;
75
+ return chain.setMark("link", { href }).setMeta("preventAutolink", true).run();
76
+ }
77
+ var ICONS = {
78
+ bold: IconBold,
79
+ italic: IconItalic,
80
+ underline: IconUnderline,
81
+ code: IconCode
82
+ };
83
+ var SETTLE_MS = 250;
84
+ var GAP = 8;
85
+ var POPOVER_GAP = 4;
86
+ function SelectionToolbar({ editor, marks = ["bold", "italic", "underline"], link = false }) {
87
+ const [anchor, setAnchor] = useState(null);
88
+ const [mode, setMode] = useState("marks");
89
+ const [url, setUrl] = useState("");
90
+ const [, setTick] = useState(0);
91
+ const timer = useRef(void 0);
92
+ const pointerDown = useRef(false);
93
+ const field = useRef(null);
94
+ const draft = useRef(null);
95
+ const modeRef = useRef("marks");
96
+ useEffect(() => {
97
+ modeRef.current = mode;
98
+ }, [mode]);
99
+ const linkOn = useRef(link);
100
+ linkOn.current = link;
101
+ const hide = useCallback(() => {
102
+ clearTimeout(timer.current);
103
+ draft.current = null;
104
+ if (editor) releaseSelection(editor);
105
+ setAnchor(null);
106
+ setMode("marks");
107
+ }, [editor]);
108
+ const commit = useCallback(() => {
109
+ const text = draft.current;
110
+ if (text === null || !editor) return;
111
+ draft.current = null;
112
+ if (text.trim() && applyLink(editor, text)) {
113
+ editor.commands.setTextSelection(editor.state.selection.to);
114
+ }
115
+ hide();
116
+ }, [editor, hide]);
117
+ const settle = useCallback(() => {
118
+ if (!editor || editor.isDestroyed) return;
119
+ if (modeRef.current === "link") return;
120
+ const { state, view } = editor;
121
+ const { empty, from, to } = state.selection;
122
+ if (empty || from === to || !view.hasFocus() || pointerDown.current) {
123
+ setAnchor(null);
124
+ setMode("marks");
125
+ return;
126
+ }
127
+ const start = view.coordsAtPos(from);
128
+ const end = view.coordsAtPos(to);
129
+ const centre = (Math.min(start.left, end.left) + Math.max(start.right, end.right)) / 2;
130
+ const inLink = linkOn.current && editor.isActive("link");
131
+ const address = inLink ? currentLink(editor) : "";
132
+ draft.current = inLink ? address : null;
133
+ if (inLink) keepSelection(editor);
134
+ setMode(inLink ? "link" : "marks");
135
+ setUrl(address);
136
+ setAnchor({ top: start.top, centre });
137
+ }, [editor]);
138
+ useEffect(() => {
139
+ if (!editor) return;
140
+ const schedule = () => {
141
+ if (modeRef.current === "link") return;
142
+ setTick((t) => t + 1);
143
+ clearTimeout(timer.current);
144
+ if (editor.state.selection.empty) {
145
+ setAnchor(null);
146
+ return;
147
+ }
148
+ timer.current = setTimeout(settle, SETTLE_MS);
149
+ };
150
+ const onBlur = () => {
151
+ if (modeRef.current !== "link") hide();
152
+ };
153
+ const startsSelection = (e) => e.target instanceof Node && editor.view.dom.contains(e.target);
154
+ const onPointerDown = (e) => {
155
+ if (!startsSelection(e)) return;
156
+ commit();
157
+ pointerDown.current = true;
158
+ hide();
159
+ };
160
+ const onPointerUp = () => {
161
+ if (!pointerDown.current) return;
162
+ pointerDown.current = false;
163
+ schedule();
164
+ };
165
+ editor.on("selectionUpdate", schedule);
166
+ editor.on("transaction", schedule);
167
+ editor.on("blur", onBlur);
168
+ document.addEventListener("mousedown", onPointerDown, true);
169
+ document.addEventListener("mouseup", onPointerUp, true);
170
+ return () => {
171
+ clearTimeout(timer.current);
172
+ editor.off("selectionUpdate", schedule);
173
+ editor.off("transaction", schedule);
174
+ editor.off("blur", onBlur);
175
+ document.removeEventListener("mousedown", onPointerDown, true);
176
+ document.removeEventListener("mouseup", onPointerUp, true);
177
+ };
178
+ }, [editor, settle, hide, commit]);
179
+ useEffect(() => {
180
+ if (mode !== "link" || !anchor) return;
181
+ const frame = requestAnimationFrame(() => {
182
+ field.current?.focus();
183
+ field.current?.select();
184
+ });
185
+ return () => cancelAnimationFrame(frame);
186
+ }, [mode, anchor]);
187
+ if (!editor || !anchor) return null;
188
+ const openField = () => {
189
+ const address = currentLink(editor);
190
+ draft.current = address;
191
+ keepSelection(editor);
192
+ setUrl(address);
193
+ setMode("link");
194
+ };
195
+ const submit = () => {
196
+ if (!url.trim()) {
197
+ applyLink(editor, "");
198
+ hide();
199
+ editor.commands.focus();
200
+ return;
201
+ }
202
+ commit();
203
+ };
204
+ const discard = () => {
205
+ hide();
206
+ editor.commands.focus();
207
+ };
208
+ const anchorRect = new DOMRect(anchor.centre, anchor.top - (GAP - POPOVER_GAP), 0, 0);
209
+ return /* @__PURE__ */ jsx(
210
+ Popover,
211
+ {
212
+ anchor: anchorRect,
213
+ open: true,
214
+ side: "top",
215
+ align: "center",
216
+ onOpenChange: (next) => {
217
+ if (!next) commit();
218
+ },
219
+ ariaLabel: "Text formatting",
220
+ className: "w-auto min-w-0",
221
+ contentClassName: "p-1",
222
+ children: mode === "link" ? (
223
+ /* One field, so no strip: a toolbar of one control is a toolbar in
224
+ name only, and `ToolbarInput` cannot take the ref this field is
225
+ focused by (ADOPTION B25). */
226
+ // The package Form (UIG-7): Enter applies the link. `free` and `flex`: one field, which keeps its own height.
227
+ /* @__PURE__ */ jsx(Form, { onSubmit: submit, layout: "free", className: "flex", children: /* @__PURE__ */ jsx(
228
+ TextInput,
229
+ {
230
+ ref: field,
231
+ value: url,
232
+ onChange: (e) => {
233
+ setUrl(e.target.value);
234
+ draft.current = e.target.value;
235
+ },
236
+ onKeyDown: (e) => {
237
+ if (e.key === "Escape") {
238
+ e.stopPropagation();
239
+ discard();
240
+ }
241
+ },
242
+ placeholder: "Enter link URL",
243
+ "aria-label": "Link URL",
244
+ className: "w-[264px]"
245
+ }
246
+ ) })
247
+ ) : (
248
+ /* Four marks and a link button: one Tab stop, ← → along them (D29). */
249
+ /* @__PURE__ */ jsxs(Toolbar, { "aria-label": "Text formatting", surface: false, children: [
250
+ marks.map((id) => {
251
+ const Icon = ICONS[id];
252
+ const active = editor.isActive(id);
253
+ return /* @__PURE__ */ jsx(
254
+ ToolbarButton,
255
+ {
256
+ "aria-label": MARKS[id].label,
257
+ pressed: active,
258
+ tooltip: MARKS[id].label,
259
+ tooltipShortcut: shortcutLabel(MARKS[id].keys),
260
+ onMouseDown: (e) => e.preventDefault(),
261
+ onClick: () => toggleMark(editor, id),
262
+ children: /* @__PURE__ */ jsx(Icon, { size: 16, stroke: 1.5 })
263
+ },
264
+ id
265
+ );
266
+ }),
267
+ link && /* @__PURE__ */ jsx(
268
+ ToolbarButton,
269
+ {
270
+ "aria-label": "Link",
271
+ tooltip: "Link",
272
+ onMouseDown: (e) => e.preventDefault(),
273
+ onClick: openField,
274
+ children: /* @__PURE__ */ jsx(IconLink, { size: 16, stroke: 1.5 })
275
+ }
276
+ )
277
+ ] })
278
+ )
279
+ }
280
+ );
281
+ }
282
+
283
+ // src/suggestionPopup.tsx
284
+ import { forwardRef, useRef as useRef2, useImperativeHandle } from "react";
285
+ import { ReactRenderer } from "@tiptap/react";
286
+ import { SuggestionMenu } from "@estiva-app/ui";
287
+ import { jsx as jsx2 } from "react/jsx-runtime";
288
+ var openCount = 0;
289
+ var lastClose = 0;
290
+ function isSuggestionActive() {
291
+ return openCount > 0 || Date.now() - lastClose < 100;
292
+ }
293
+ function isSuggestionOpen() {
294
+ return openCount > 0;
295
+ }
296
+ var Popup = forwardRef(function Popup2({ options, ...props }, handle) {
297
+ const menu = useRef2(null);
298
+ useImperativeHandle(handle, () => ({ onKeyDown: (event) => menu.current?.onKeyDown(event) ?? false }));
299
+ const sections = options.sections ? options.sections(props.items, props.query) : [{ items: props.items }];
300
+ return /* @__PURE__ */ jsx2(
301
+ SuggestionMenu,
302
+ {
303
+ handle: menu,
304
+ rect: props.clientRect?.() ?? null,
305
+ editorElement: props.editor.view.dom,
306
+ ariaLabel: options.ariaLabel,
307
+ width: options.width,
308
+ maxHeight: options.maxHeight,
309
+ sections,
310
+ itemKey: options.itemKey,
311
+ row: options.row,
312
+ onSelect: (item) => props.command(item)
313
+ }
314
+ );
315
+ });
316
+ function suggestionPopup(options) {
317
+ return () => {
318
+ let component = null;
319
+ let container = null;
320
+ const exit = () => {
321
+ if (!component) return;
322
+ openCount = Math.max(0, openCount - 1);
323
+ lastClose = Date.now();
324
+ component.destroy();
325
+ container?.remove();
326
+ component = null;
327
+ container = null;
328
+ };
329
+ return {
330
+ onStart: (props) => {
331
+ openCount++;
332
+ component = new ReactRenderer(Popup, { props: { ...props, options }, editor: props.editor });
333
+ container = document.createElement("div");
334
+ document.body.appendChild(container);
335
+ container.appendChild(component.element);
336
+ },
337
+ onUpdate: (props) => component?.updateProps({ ...props, options }),
338
+ onKeyDown: ({ event }) => {
339
+ if (event.key === "Escape") {
340
+ exit();
341
+ return true;
342
+ }
343
+ return component?.ref?.onKeyDown(event) ?? false;
344
+ },
345
+ onExit: exit
346
+ };
347
+ };
348
+ }
349
+ export {
350
+ KeptSelection,
351
+ SelectionToolbar,
352
+ isSuggestionActive,
353
+ isSuggestionOpen,
354
+ normalizeHref,
355
+ suggestionPopup
356
+ };
357
+ //# sourceMappingURL=editor.js.map