@ecsplico/editor 3.1.0 → 3.2.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 (270) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/adapters/direct-ai.d.ts +36 -0
  3. package/dist/adapters/direct-ai.js +169 -0
  4. package/dist/adapters/direct-ai.js.map +1 -0
  5. package/dist/adapters/memory.js.map +1 -1
  6. package/dist/code/CodeSurface.vue.d.ts +2 -2
  7. package/dist/core/block-drag.js +3 -0
  8. package/dist/core/block-drag.js.map +1 -1
  9. package/dist/core/block-transfer.d.ts +44 -0
  10. package/dist/core/block-transfer.js +68 -0
  11. package/dist/core/block-transfer.js.map +1 -0
  12. package/dist/core/commands.js +5 -0
  13. package/dist/core/commands.js.map +1 -1
  14. package/dist/core/core-feature.d.ts +4 -1
  15. package/dist/core/core-feature.js +8 -3
  16. package/dist/core/core-feature.js.map +1 -1
  17. package/dist/core/editor-ui.d.ts +3 -1
  18. package/dist/core/editor-ui.js.map +1 -1
  19. package/dist/core/extension-points.d.ts +17 -1
  20. package/dist/core/feature.d.ts +6 -1
  21. package/dist/core/feature.js.map +1 -1
  22. package/dist/core/index.d.ts +4 -2
  23. package/dist/core/index.js +3 -2
  24. package/dist/core/schema/index.d.ts +24 -3
  25. package/dist/core/schema/index.js +21 -4
  26. package/dist/core/schema/index.js.map +1 -1
  27. package/dist/core/surface-session.js +3 -1
  28. package/dist/core/surface-session.js.map +1 -1
  29. package/dist/core/toolbar-items.js +1 -1
  30. package/dist/core/toolbar-items.js.map +1 -1
  31. package/dist/document/DocumentSurface.vue.d.ts +2 -2
  32. package/dist/features/ai/agent-op-review.js +1 -1
  33. package/dist/features/ai/agent-ops.js +2 -2
  34. package/dist/features/ai/ai-apply.js +1 -1
  35. package/dist/features/ai/assistant.js +1 -1
  36. package/dist/features/ai/components/AiSelectionMenu.js +2 -2
  37. package/dist/features/ai/components/AiSidebar.js +2 -2
  38. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  39. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  40. package/dist/features/ai/document-chat-model.js +1 -1
  41. package/dist/features/ai/document-chat.js +2 -2
  42. package/dist/features/ai/ghost-autocomplete.js +1 -1
  43. package/dist/features/ai/index.d.ts +4 -3
  44. package/dist/features/ai/index.js +6 -5
  45. package/dist/features/ai/protocol/complete.d.ts +19 -0
  46. package/dist/features/ai/protocol/complete.js +27 -1
  47. package/dist/features/ai/protocol/complete.js.map +1 -1
  48. package/dist/features/ai/protocol/generate.d.ts +95 -0
  49. package/dist/features/ai/protocol/generate.js +58 -0
  50. package/dist/features/ai/protocol/generate.js.map +1 -0
  51. package/dist/features/ai/protocol/index.d.ts +4 -3
  52. package/dist/features/ai/protocol/index.js +6 -5
  53. package/dist/features/ai/protocol/tasks.d.ts +3 -1
  54. package/dist/features/ai/protocol/tasks.js +74 -2
  55. package/dist/features/ai/protocol/tasks.js.map +1 -1
  56. package/dist/features/ai/react/AiSelectionMenu.js +2 -2
  57. package/dist/features/ai/react/AiSidebar.js +2 -2
  58. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  59. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  60. package/dist/features/ai/selection-flow.js +1 -1
  61. package/dist/features/ai/sidebar-flow.js +1 -1
  62. package/dist/features/block-ui/block-actions.d.ts +19 -3
  63. package/dist/features/block-ui/block-actions.js +49 -3
  64. package/dist/features/block-ui/block-actions.js.map +1 -1
  65. package/dist/features/block-ui/components/BlockActionsOverlay.js +24 -13
  66. package/dist/features/block-ui/components/BlockActionsOverlay.js.map +1 -1
  67. package/dist/features/block-ui/components/BlockActionsOverlay.vue.d.ts +12 -5
  68. package/dist/features/block-ui/components/BlockUi.js +52 -20
  69. package/dist/features/block-ui/components/BlockUi.js.map +1 -1
  70. package/dist/features/block-ui/components/BlockUi.vue.d.ts +6 -4
  71. package/dist/features/block-ui/feature.d.ts +8 -2
  72. package/dist/features/block-ui/feature.js +7 -2
  73. package/dist/features/block-ui/feature.js.map +1 -1
  74. package/dist/features/block-ui/index.d.ts +3 -2
  75. package/dist/features/block-ui/index.js +2 -2
  76. package/dist/features/block-ui/react/BlockActionsOverlay.d.ts +14 -7
  77. package/dist/features/block-ui/react/BlockActionsOverlay.js +25 -13
  78. package/dist/features/block-ui/react/BlockActionsOverlay.js.map +1 -1
  79. package/dist/features/block-ui/react/BlockUi.d.ts +6 -4
  80. package/dist/features/block-ui/react/BlockUi.js +43 -15
  81. package/dist/features/block-ui/react/BlockUi.js.map +1 -1
  82. package/dist/features/block-ui/react.d.ts +4 -2
  83. package/dist/features/block-ui/react.js +3 -3
  84. package/dist/features/block-ui/react.js.map +1 -1
  85. package/dist/features/block-ui/vue.d.ts +4 -2
  86. package/dist/features/block-ui/vue.js +3 -3
  87. package/dist/features/block-ui/vue.js.map +1 -1
  88. package/dist/features/composites/baskets.d.ts +27 -0
  89. package/dist/features/composites/baskets.js +46 -0
  90. package/dist/features/composites/baskets.js.map +1 -0
  91. package/dist/features/composites/components/CompositeBasketMenu.js +97 -0
  92. package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -0
  93. package/dist/features/composites/components/CompositeChrome.js +79 -23
  94. package/dist/features/composites/components/CompositeChrome.js.map +1 -1
  95. package/dist/features/composites/components/CompositeFieldEditor.js +1 -0
  96. package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
  97. package/dist/features/composites/components/CompositeView.js +17 -2
  98. package/dist/features/composites/components/CompositeView.js.map +1 -1
  99. package/dist/features/composites/components/DerivationBadge.js +12 -3
  100. package/dist/features/composites/components/DerivationBadge.js.map +1 -1
  101. package/dist/features/composites/components/FocusChip.js +53 -0
  102. package/dist/features/composites/components/FocusChip.js.map +1 -0
  103. package/dist/features/composites/components/FocusChip.vue.d.ts +14 -0
  104. package/dist/features/composites/components/SettingsEditingTab.js +40 -4
  105. package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
  106. package/dist/features/composites/derivation.d.ts +2 -1
  107. package/dist/features/composites/derivation.js +5 -1
  108. package/dist/features/composites/derivation.js.map +1 -1
  109. package/dist/features/composites/derived-chrome.js +13 -3
  110. package/dist/features/composites/derived-chrome.js.map +1 -1
  111. package/dist/features/composites/feature.d.ts +7 -1
  112. package/dist/features/composites/feature.js +96 -23
  113. package/dist/features/composites/feature.js.map +1 -1
  114. package/dist/features/composites/focus.d.ts +38 -0
  115. package/dist/features/composites/focus.js +86 -0
  116. package/dist/features/composites/focus.js.map +1 -0
  117. package/dist/features/composites/index.d.ts +6 -3
  118. package/dist/features/composites/index.js +6 -3
  119. package/dist/features/composites/meta-keys.d.ts +4 -0
  120. package/dist/features/composites/meta-keys.js +8 -4
  121. package/dist/features/composites/meta-keys.js.map +1 -1
  122. package/dist/features/composites/nodes.d.ts +14 -2
  123. package/dist/features/composites/nodes.js +61 -5
  124. package/dist/features/composites/nodes.js.map +1 -1
  125. package/dist/features/composites/outline.d.ts +4 -0
  126. package/dist/features/composites/outline.js +3 -0
  127. package/dist/features/composites/outline.js.map +1 -1
  128. package/dist/features/composites/react/CompositeBasketMenu.js +88 -0
  129. package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -0
  130. package/dist/features/composites/react/CompositeChrome.js +52 -4
  131. package/dist/features/composites/react/CompositeChrome.js.map +1 -1
  132. package/dist/features/composites/react/CompositeView.js +12 -2
  133. package/dist/features/composites/react/CompositeView.js.map +1 -1
  134. package/dist/features/composites/react/DerivationBadge.js +10 -1
  135. package/dist/features/composites/react/DerivationBadge.js.map +1 -1
  136. package/dist/features/composites/react/FocusChip.d.ts +11 -0
  137. package/dist/features/composites/react/FocusChip.js +50 -0
  138. package/dist/features/composites/react/FocusChip.js.map +1 -0
  139. package/dist/features/composites/react/SettingsEditingTab.js +36 -1
  140. package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
  141. package/dist/features/composites/react.d.ts +2 -1
  142. package/dist/features/composites/react.js +3 -1
  143. package/dist/features/composites/react.js.map +1 -1
  144. package/dist/features/composites/schema.d.ts +5 -3
  145. package/dist/features/composites/schema.js +8 -4
  146. package/dist/features/composites/schema.js.map +1 -1
  147. package/dist/features/composites/split.d.ts +62 -0
  148. package/dist/features/composites/split.js +172 -0
  149. package/dist/features/composites/split.js.map +1 -0
  150. package/dist/features/composites/vue.d.ts +4 -3
  151. package/dist/features/composites/vue.js +7 -5
  152. package/dist/features/composites/vue.js.map +1 -1
  153. package/dist/features/media/index.js +1 -1
  154. package/dist/features/media/react/AudioRecorderModal.js +1 -1
  155. package/dist/features/media/react.js +1 -1
  156. package/dist/features/transfer/components/TransferMenu.js +93 -0
  157. package/dist/features/transfer/components/TransferMenu.js.map +1 -0
  158. package/dist/features/transfer/components/TransferMenu.vue.d.ts +15 -0
  159. package/dist/features/transfer/feature.d.ts +76 -0
  160. package/dist/features/transfer/feature.js +169 -0
  161. package/dist/features/transfer/feature.js.map +1 -0
  162. package/dist/features/transfer/index.d.ts +3 -0
  163. package/dist/features/transfer/index.js +3 -0
  164. package/dist/features/transfer/react/TransferMenu.d.ts +12 -0
  165. package/dist/features/transfer/react/TransferMenu.js +71 -0
  166. package/dist/features/transfer/react/TransferMenu.js.map +1 -0
  167. package/dist/features/transfer/react.d.ts +10 -0
  168. package/dist/features/transfer/react.js +11 -0
  169. package/dist/features/transfer/react.js.map +1 -0
  170. package/dist/features/transfer/transforms.d.ts +83 -0
  171. package/dist/features/transfer/transforms.js +137 -0
  172. package/dist/features/transfer/transforms.js.map +1 -0
  173. package/dist/features/transfer/vue.d.ts +9 -0
  174. package/dist/features/transfer/vue.js +11 -0
  175. package/dist/features/transfer/vue.js.map +1 -0
  176. package/dist/i18n/de/ai.js +4 -0
  177. package/dist/i18n/de/ai.js.map +1 -1
  178. package/dist/i18n/de/composites.js +20 -1
  179. package/dist/i18n/de/composites.js.map +1 -1
  180. package/dist/i18n/de/integrations.js +16 -0
  181. package/dist/i18n/de/integrations.js.map +1 -1
  182. package/dist/i18n/de/transfer.js +17 -0
  183. package/dist/i18n/de/transfer.js.map +1 -0
  184. package/dist/i18n/de/tree.js +6 -0
  185. package/dist/i18n/de/tree.js.map +1 -1
  186. package/dist/i18n/de.d.ts +55 -0
  187. package/dist/i18n/de.js +3 -1
  188. package/dist/i18n/de.js.map +1 -1
  189. package/dist/i18n/en/ai.js +4 -0
  190. package/dist/i18n/en/ai.js.map +1 -1
  191. package/dist/i18n/en/composites.js +20 -1
  192. package/dist/i18n/en/composites.js.map +1 -1
  193. package/dist/i18n/en/integrations.js +16 -0
  194. package/dist/i18n/en/integrations.js.map +1 -1
  195. package/dist/i18n/en/transfer.js +17 -0
  196. package/dist/i18n/en/transfer.js.map +1 -0
  197. package/dist/i18n/en/tree.js +6 -0
  198. package/dist/i18n/en/tree.js.map +1 -1
  199. package/dist/i18n/en.js +3 -1
  200. package/dist/i18n/en.js.map +1 -1
  201. package/dist/integrations/composite-ai/composite-fill.js +1 -1
  202. package/dist/integrations/composite-ai/composite-summary.js +1 -1
  203. package/dist/integrations/composite-ai/derive.d.ts +21 -0
  204. package/dist/integrations/composite-ai/derive.js +69 -47
  205. package/dist/integrations/composite-ai/derive.js.map +1 -1
  206. package/dist/integrations/composite-ai/index.d.ts +8 -3
  207. package/dist/integrations/composite-ai/index.js +50 -4
  208. package/dist/integrations/composite-ai/index.js.map +1 -1
  209. package/dist/integrations/composite-ai/outline-sanitize.d.ts +48 -0
  210. package/dist/integrations/composite-ai/outline-sanitize.js +175 -0
  211. package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -0
  212. package/dist/integrations/composite-ai/split.d.ts +44 -0
  213. package/dist/integrations/composite-ai/split.js +187 -0
  214. package/dist/integrations/composite-ai/split.js.map +1 -0
  215. package/dist/integrations/composite-ai/transfer-transforms.d.ts +27 -0
  216. package/dist/integrations/composite-ai/transfer-transforms.js +153 -0
  217. package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -0
  218. package/dist/integrations/transcript-ai/protocol.d.ts +13 -0
  219. package/dist/integrations/transcript-ai/protocol.js +49 -1
  220. package/dist/integrations/transcript-ai/protocol.js.map +1 -1
  221. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  222. package/dist/markdown/MarkdownSurface.vue.d.ts +6 -6
  223. package/dist/nuxt/module.d.ts +110 -0
  224. package/dist/presets/compose.d.ts +24 -8
  225. package/dist/presets/compose.js +117 -58
  226. package/dist/presets/compose.js.map +1 -1
  227. package/dist/react/compose.js +6 -2
  228. package/dist/react/compose.js.map +1 -1
  229. package/dist/react/composed-surface.d.ts +9 -0
  230. package/dist/react/composed-surface.js +4 -1
  231. package/dist/react/composed-surface.js.map +1 -1
  232. package/dist/server/ai.d.ts +2 -5
  233. package/dist/server/ai.js.map +1 -1
  234. package/dist/server/handlers/ai.d.ts +7 -78
  235. package/dist/server/handlers/ai.js +30 -94
  236. package/dist/server/handlers/ai.js.map +1 -1
  237. package/dist/server/handlers/editor.d.ts +1 -0
  238. package/dist/server/handlers/index.d.ts +3 -1
  239. package/dist/server/handlers/index.js +3 -1
  240. package/dist/server/index.d.ts +2 -1
  241. package/dist/settings/index.d.ts +8 -0
  242. package/dist/settings/index.js +16 -4
  243. package/dist/settings/index.js.map +1 -1
  244. package/dist/styles/composites.css +32 -0
  245. package/dist/tree/DocumentTree.js +229 -78
  246. package/dist/tree/DocumentTree.js.map +1 -1
  247. package/dist/tree/drop-rules.js +8 -0
  248. package/dist/tree/drop-rules.js.map +1 -1
  249. package/dist/tree/react/DocumentTree.js +152 -31
  250. package/dist/tree/react/DocumentTree.js.map +1 -1
  251. package/dist/tree/tree-host.js +30 -1
  252. package/dist/tree/tree-host.js.map +1 -1
  253. package/dist/tree/tree-items.js +52 -5
  254. package/dist/tree/tree-items.js.map +1 -1
  255. package/dist/tree/tree-model.js +113 -10
  256. package/dist/tree/tree-model.js.map +1 -1
  257. package/dist/tree/tree-rows.js +20 -7
  258. package/dist/tree/tree-rows.js.map +1 -1
  259. package/dist/tree/tree-view-model.d.ts +5 -0
  260. package/dist/tree/tree-view-model.js +13 -0
  261. package/dist/tree/tree-view-model.js.map +1 -1
  262. package/dist/vue/AdvancedEditor.js +6 -0
  263. package/dist/vue/AdvancedEditor.js.map +1 -1
  264. package/dist/vue/AdvancedEditor.vue.d.ts +13 -5
  265. package/dist/vue/SimpleEditor.vue.d.ts +4 -4
  266. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  267. package/dist/vue/compose.js +6 -2
  268. package/dist/vue/compose.js.map +1 -1
  269. package/dist/vue/kit/types.d.ts +2 -0
  270. package/package.json +21 -1
@@ -0,0 +1,88 @@
1
+ import { basketOf } from "../baskets.js";
2
+ import { compositeBasketsOf } from "../nodes.js";
3
+ import { useEditorI18n } from "../../../react/use-editor-i18n.js";
4
+ import { Icon } from "../../../react/Icon.js";
5
+ import { Popover, PopoverContent, PopoverTrigger } from "../../../react/ui/popover.js";
6
+ import { compositeOfRow } from "../rows.js";
7
+ import { useState } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/features/composites/react/CompositeBasketMenu.tsx
10
+ const OPTION = "flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-foreground transition-colors duration-150 hover:bg-muted focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary";
11
+ /**
12
+ * Basket choice of a Baustein: the child (a button) opens a list of the editor's baskets (a dot in the basket's colour
13
+ * and its label) and „Kein Korb“. The menu is a popover, not a dropdown menu: a dropdown item cannot show a dot in an
14
+ * arbitrary CSS colour.
15
+ */
16
+ function CompositeBasketMenu({ editor, row, children }) {
17
+ const { t } = useEditorI18n();
18
+ const [open, setOpen] = useState(false);
19
+ const current = basketOf(row);
20
+ function choose(next) {
21
+ setOpen(false);
22
+ const found = compositeOfRow(editor.state.doc, row);
23
+ if (found) editor.commands.setCompositeBasket(next, found.pos);
24
+ }
25
+ return /* @__PURE__ */ jsxs(Popover, {
26
+ open,
27
+ onOpenChange: setOpen,
28
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
29
+ asChild: true,
30
+ children
31
+ }), /* @__PURE__ */ jsx(PopoverContent, {
32
+ side: "bottom",
33
+ align: "start",
34
+ sideOffset: 4,
35
+ className: "w-40 gap-0 p-1",
36
+ onCloseAutoFocus: (event) => event.preventDefault(),
37
+ children: /* @__PURE__ */ jsxs("div", {
38
+ role: "menu",
39
+ "aria-label": t("composites.basket.label"),
40
+ children: [compositeBasketsOf(editor).map((candidate) => /* @__PURE__ */ jsxs("button", {
41
+ type: "button",
42
+ role: "menuitemradio",
43
+ "aria-checked": current === candidate.id,
44
+ className: OPTION,
45
+ onClick: () => choose(candidate.id),
46
+ children: [
47
+ /* @__PURE__ */ jsx("span", {
48
+ className: "size-2.5 shrink-0 rounded-full",
49
+ style: { backgroundColor: candidate.color }
50
+ }),
51
+ /* @__PURE__ */ jsx("span", {
52
+ className: "truncate",
53
+ children: candidate.label
54
+ }),
55
+ current === candidate.id && /* @__PURE__ */ jsx(Icon, {
56
+ name: "i-lucide-check",
57
+ className: "ml-auto size-3.5 shrink-0"
58
+ })
59
+ ]
60
+ }, candidate.id)), /* @__PURE__ */ jsxs("button", {
61
+ type: "button",
62
+ role: "menuitemradio",
63
+ "aria-checked": current === null,
64
+ className: OPTION,
65
+ onClick: () => choose(null),
66
+ children: [
67
+ /* @__PURE__ */ jsx(Icon, {
68
+ name: "i-lucide-circle-dashed",
69
+ className: "size-3 shrink-0 text-muted-foreground"
70
+ }),
71
+ /* @__PURE__ */ jsx("span", {
72
+ className: "truncate",
73
+ children: t("composites.basket.none")
74
+ }),
75
+ current === null && /* @__PURE__ */ jsx(Icon, {
76
+ name: "i-lucide-check",
77
+ className: "ml-auto size-3.5 shrink-0"
78
+ })
79
+ ]
80
+ })]
81
+ })
82
+ })]
83
+ });
84
+ }
85
+ //#endregion
86
+ export { CompositeBasketMenu };
87
+
88
+ //# sourceMappingURL=CompositeBasketMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CompositeBasketMenu.js","names":[],"sources":["../../../../src/features/composites/react/CompositeBasketMenu.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { type ReactNode, useState } from 'react'\nimport { Icon } from '../../../react/Icon'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../../react/ui/popover'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { basketOf } from '../baskets'\nimport { compositeBasketsOf } from '../nodes'\nimport { compositeOfRow, type CompositeRow } from '../rows'\n\nconst OPTION = 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-foreground transition-colors duration-150 hover:bg-muted focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary'\n\n/**\n * Basket choice of a Baustein: the child (a button) opens a list of the editor's baskets (a dot in the basket's colour\n * and its label) and „Kein Korb“. The menu is a popover, not a dropdown menu: a dropdown item cannot show a dot in an\n * arbitrary CSS colour.\n */\nexport function CompositeBasketMenu({ editor, row, children }: { editor: Editor, row: CompositeRow, children: ReactNode }) {\n const { t } = useEditorI18n()\n const [open, setOpen] = useState(false)\n\n const current = basketOf(row)\n\n function choose(next: string | null) {\n setOpen(false)\n const found = compositeOfRow(editor.state.doc, row)\n if (found) editor.commands.setCompositeBasket(next, found.pos)\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>{children}</PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n className=\"w-40 gap-0 p-1\"\n onCloseAutoFocus={event => event.preventDefault()}\n >\n <div role=\"menu\" aria-label={t('composites.basket.label')}>\n {compositeBasketsOf(editor).map(candidate => (\n <button\n key={candidate.id}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={current === candidate.id}\n className={OPTION}\n onClick={() => choose(candidate.id)}\n >\n <span className=\"size-2.5 shrink-0 rounded-full\" style={{ backgroundColor: candidate.color }} />\n <span className=\"truncate\">{candidate.label}</span>\n {current === candidate.id && <Icon name=\"i-lucide-check\" className=\"ml-auto size-3.5 shrink-0\" />}\n </button>\n ))}\n <button type=\"button\" role=\"menuitemradio\" aria-checked={current === null} className={OPTION} onClick={() => choose(null)}>\n <Icon name=\"i-lucide-circle-dashed\" className=\"size-3 shrink-0 text-muted-foreground\" />\n <span className=\"truncate\">{t('composites.basket.none')}</span>\n {current === null && <Icon name=\"i-lucide-check\" className=\"ml-auto size-3.5 shrink-0\" />}\n </button>\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,SAAS;;;;;;AAOf,SAAgB,oBAAoB,EAAE,QAAQ,KAAK,YAAwE;CACzH,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,MAAM,UAAU,SAAS,GAAG;CAE5B,SAAS,OAAO,MAAqB;EACnC,QAAQ,KAAK;EACb,MAAM,QAAQ,eAAe,OAAO,MAAM,KAAK,GAAG;EAClD,IAAI,OAAO,OAAO,SAAS,mBAAmB,MAAM,MAAM,GAAG;CAC/D;CAEA,OACE,qBAAC,SAAD;EAAe;EAAM,cAAc;EAAnC,UAAA,CACE,oBAAC,gBAAD;GAAgB,SAAA;GAAS;EAAyB,CAAA,GAClD,oBAAC,gBAAD;GACE,MAAK;GACL,OAAM;GACN,YAAY;GACZ,WAAU;GACV,mBAAkB,UAAS,MAAM,eAAe;GAEhD,UAAA,qBAAC,OAAD;IAAK,MAAK;IAAO,cAAY,EAAE,yBAAyB;IAAxD,UAAA,CACG,mBAAmB,MAAM,CAAC,CAAC,KAAI,cAC9B,qBAAC,UAAD;KAEE,MAAK;KACL,MAAK;KACL,gBAAc,YAAY,UAAU;KACpC,WAAW;KACX,eAAe,OAAO,UAAU,EAAE;KANpC,UAAA;MAQE,oBAAC,QAAD;OAAM,WAAU;OAAiC,OAAO,EAAE,iBAAiB,UAAU,MAAM;MAAI,CAAA;MAC/F,oBAAC,QAAD;OAAM,WAAU;OAAY,UAAA,UAAU;MAAY,CAAA;MACjD,YAAY,UAAU,MAAM,oBAAC,MAAD;OAAM,MAAK;OAAiB,WAAU;MAA6B,CAAA;KAC1F;IAVD,GAAA,UAAU,EAUT,CACT,GACD,qBAAC,UAAD;KAAQ,MAAK;KAAS,MAAK;KAAgB,gBAAc,YAAY;KAAM,WAAW;KAAQ,eAAe,OAAO,IAAI;KAAxH,UAAA;MACE,oBAAC,MAAD;OAAM,MAAK;OAAyB,WAAU;MAAyC,CAAA;MACvF,oBAAC,QAAD;OAAM,WAAU;OAAY,UAAA,EAAE,wBAAwB;MAAQ,CAAA;MAC7D,YAAY,QAAQ,oBAAC,MAAD;OAAM,MAAK;OAAiB,WAAU;MAA6B,CAAA;KAClF;IACL,CAAA,CAAA;;EACS,CAAA,CACT;;AAEb"}
@@ -1,4 +1,6 @@
1
1
  import { startBlockDrag } from "../../../core/block-drag.js";
2
+ import { basketById, basketOf, titleHiddenOf } from "../baskets.js";
3
+ import { compositeBasketsOf } from "../nodes.js";
2
4
  import { useEditorI18n } from "../../../react/use-editor-i18n.js";
3
5
  import { cn } from "../../../react/ui/utils.js";
4
6
  import { Icon } from "../../../react/Icon.js";
@@ -9,6 +11,7 @@ import { compositeOfRow, setCompositeTags } from "../rows.js";
9
11
  import { compositeSectionOf } from "../anchors.js";
10
12
  import { derivedChromeOf } from "../derived-chrome.js";
11
13
  import { rowStatus } from "../meta-display.js";
14
+ import { CompositeBasketMenu } from "./CompositeBasketMenu.js";
12
15
  import { useCompositeRows } from "./use-composite-rows.js";
13
16
  import { CompositeDetails } from "./CompositeDetails.js";
14
17
  import { CompositeStatusMenu } from "./CompositeStatusMenu.js";
@@ -81,7 +84,9 @@ function CompositeChrome({ editor }) {
81
84
  if (!found || !startBlockDrag(editor, event, found.pos)) event.preventDefault();
82
85
  }
83
86
  const expanded = useStore(editor.storage.composite.expanded);
87
+ const revealedTitles = useStore(editor.storage.composite.revealedTitles);
84
88
  const placed = useMemo(() => {
89
+ const baskets = compositeBasketsOf(editor);
85
90
  let hiddenUntil = -1;
86
91
  return rows.flatMap((row) => {
87
92
  if (row.pos < hiddenUntil) return [];
@@ -90,19 +95,23 @@ function CompositeChrome({ editor }) {
90
95
  if (!open) hiddenUntil = row.end;
91
96
  const anchor = anchors.get(row.key);
92
97
  if (!anchor) return [];
93
- const left = anchor.lineEnd + (anchor.titleEmpty ? 116 : 12);
98
+ const titleHidden = titleHiddenOf(row);
99
+ const inStrip = titleHidden && !(row.id && revealedTitles.has(row.id)) && anchor.toggleSize > 0;
100
+ const left = inStrip ? anchor.toggleLeft + anchor.toggleSize + 6 : anchor.lineEnd + (anchor.titleEmpty ? 116 : 12);
94
101
  return [{
95
102
  row,
96
103
  anchor,
97
104
  left,
98
- top: anchor.lastCenter - ROW_HEIGHT / 2,
105
+ top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : anchor.lastCenter) - ROW_HEIGHT / 2,
99
106
  width: Math.max((frame?.right ?? anchor.right) - left, 120),
100
107
  status: rowStatus(row, t),
101
108
  derived: derived && {
102
109
  stale: derived.stale,
103
110
  open,
104
111
  toggleable: !!row.id
105
- }
112
+ },
113
+ basket: basketById(baskets, basketOf(row)) ?? null,
114
+ titleHidden
106
115
  }];
107
116
  });
108
117
  }, [
@@ -111,6 +120,7 @@ function CompositeChrome({ editor }) {
111
120
  anchors,
112
121
  frame,
113
122
  expanded,
123
+ revealedTitles,
114
124
  t
115
125
  ]);
116
126
  function toggleExpanded(row) {
@@ -119,6 +129,10 @@ function CompositeChrome({ editor }) {
119
129
  function removeTag(row, tag) {
120
130
  setCompositeTags(editor, row, row.tags.filter((candidate) => candidate !== tag));
121
131
  }
132
+ function toggleTitleHidden(row) {
133
+ const found = compositeOfRow(editor.state.doc, row);
134
+ if (found) editor.commands.setCompositeTitleHidden(!titleHiddenOf(row), found.pos);
135
+ }
122
136
  if (!host) return null;
123
137
  return createPortal(/* @__PURE__ */ jsx("div", {
124
138
  className: "absolute top-0 left-0 h-0 w-0",
@@ -158,7 +172,7 @@ function CompositeChrome({ editor }) {
158
172
  row: item.row,
159
173
  editable
160
174
  })
161
- })] }), (item.status || item.derived || item.row.tagsVisible && (item.row.tags.length > 0 || editable)) && /* @__PURE__ */ jsxs("div", {
175
+ })] }), (editable || item.basket || item.status || item.derived || item.row.tagsVisible && item.row.tags.length > 0) && /* @__PURE__ */ jsxs("div", {
162
176
  className: "absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-background",
163
177
  style: {
164
178
  top: `${item.top}px`,
@@ -169,6 +183,40 @@ function CompositeChrome({ editor }) {
169
183
  onMouseEnter: () => setChipsHoveredKey(item.row.key),
170
184
  onMouseLeave: () => setChipsHoveredKey(null),
171
185
  children: [
186
+ editable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(CompositeBasketMenu, {
187
+ editor,
188
+ row: item.row,
189
+ children: /* @__PURE__ */ jsx("button", {
190
+ type: "button",
191
+ className: CHIP_BUTTON,
192
+ "aria-label": t("composites.basket.label"),
193
+ title: item.basket ? `${t("composites.basket.label")}: ${item.basket.label}` : t("composites.basket.label"),
194
+ children: item.basket ? /* @__PURE__ */ jsx("span", {
195
+ className: "size-2.5 rounded-full",
196
+ style: { backgroundColor: item.basket.color }
197
+ }) : /* @__PURE__ */ jsx(Icon, {
198
+ name: "i-lucide-circle-dashed",
199
+ className: "size-3.5"
200
+ })
201
+ })
202
+ }), /* @__PURE__ */ jsx("button", {
203
+ type: "button",
204
+ className: CHIP_BUTTON,
205
+ "aria-label": t(item.titleHidden ? "composites.titleHidden.show" : "composites.titleHidden.hide"),
206
+ title: t(item.titleHidden ? "composites.titleHidden.show" : "composites.titleHidden.hide"),
207
+ onClick: () => toggleTitleHidden(item.row),
208
+ children: /* @__PURE__ */ jsx(Icon, {
209
+ name: item.titleHidden ? "i-lucide-eye" : "i-lucide-eye-off",
210
+ className: "size-3.5"
211
+ })
212
+ })] }) : item.basket && /* @__PURE__ */ jsx("span", {
213
+ className: CHIP,
214
+ title: item.basket.label,
215
+ children: /* @__PURE__ */ jsx("span", {
216
+ className: "size-2.5 rounded-full",
217
+ style: { backgroundColor: item.basket.color }
218
+ })
219
+ }),
172
220
  item.derived && /* @__PURE__ */ jsxs(Fragment$1, { children: [
173
221
  item.derived.toggleable && /* @__PURE__ */ jsx("button", {
174
222
  type: "button",
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeChrome.js","names":[],"sources":["../../../../src/features/composites/react/CompositeChrome.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { Fragment, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { startBlockDrag } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useStore } from '../../../react/use-store'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../../react/ui/popover'\nimport { cn } from '../../../react/ui/utils'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { CompositeDetails } from './CompositeDetails'\nimport { CompositeStatusMenu } from './CompositeStatusMenu'\nimport { CompositeTagsEditor } from './CompositeTagsEditor'\nimport { useCompositeAnchors } from './use-composite-anchors'\nimport { useCompositeRows } from './use-composite-rows'\n\n/**\n * Chrome of every composite, laid over the editor (mounted by the composites feature): on the title line the status\n * chip and one chip per tag (with a `+` chip to add tags), and on the front the kind icon. The icon is a button that\n * opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;\n * `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite\n * (`startBlockDrag`). With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is\n * open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.\n * The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.\n */\nexport function CompositeChrome({ editor }: { editor: Editor }) {\n const { rows } = useCompositeRows(editor)\n const { host, anchors, frame } = useCompositeAnchors(editor, rows)\n\n // `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\n const [editable, setEditable] = useState(() => !editor.isDestroyed && editor.isEditable)\n useEffect(() => {\n const sync = () => setEditable(!editor.isDestroyed && editor.isEditable)\n sync()\n editor.on('transaction', sync)\n return () => {\n editor.off('transaction', sync)\n }\n }, [editor])\n\n const { settings } = useEditorSettings()\n const { t } = useEditorI18n()\n const iconOnHover = settings.blocks.kindIcon === 'hover'\n\n // Hover state of `kindIcon: 'hover'`: the keys of the rows whose composite is under the pointer (that one and every\n // composite around it), plus the row whose chips the pointer is on (they sit outside the editor's DOM).\n const [hoveredKeys, setHoveredKeys] = useState<ReadonlySet<string>>(NO_KEYS)\n const [chipsHoveredKey, setChipsHoveredKey] = useState<string | null>(null)\n const latestRows = useRef(rows)\n latestRows.current = rows\n\n useEffect(() => {\n let hoveredSection: Element | null = null\n const onMouseleave = () => {\n hoveredSection = null\n setHoveredKeys(NO_KEYS)\n }\n const onMouseover = (event: MouseEvent) => {\n const section = event.target instanceof Element ? event.target.closest('.composite') : null\n if (section === hoveredSection) return\n hoveredSection = section\n const chain = new Set<Element>()\n for (let el = section; el; el = el.parentElement?.closest('.composite') ?? null) chain.add(el)\n const { view, state } = editor\n setHoveredKeys(new Set(latestRows.current.filter((row) => {\n const found = compositeOfRow(state.doc, row)\n const dom = found ? view.nodeDOM(found.pos) : null\n const section = dom instanceof Element ? compositeSectionOf(dom) : null\n return section !== null && chain.has(section)\n }).map(row => row.key)))\n }\n\n onMouseleave()\n if (!iconOnHover || editor.isDestroyed) return\n const { dom } = editor.view\n dom.addEventListener('mouseover', onMouseover)\n dom.addEventListener('mouseleave', onMouseleave)\n return () => {\n dom.removeEventListener('mouseover', onMouseover)\n dom.removeEventListener('mouseleave', onMouseleave)\n }\n }, [editor, iconOnHover])\n\n function onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(editor.state.doc, row)\n if (!found || !startBlockDrag(editor, event, found.pos)) event.preventDefault()\n }\n\n const expanded = useStore(editor.storage.composite.expanded)\n\n const placed = useMemo(() => {\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(editor, row)\n const open = !derived || !row.id || expanded.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.get(row.key)\n if (!anchor) return []\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: anchor.lastCenter - ROW_HEIGHT / 2,\n width: Math.max((frame?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n }]\n })\n }, [editor, rows, anchors, frame, expanded, t])\n\n function toggleExpanded(row: CompositeRow) {\n if (row.id) editor.commands.toggleCompositeExpanded(row.id)\n }\n\n function removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(editor, row, row.tags.filter(candidate => candidate !== tag))\n }\n\n if (!host) return null\n\n return createPortal(\n <div className=\"absolute top-0 left-0 h-0 w-0\">\n {placed.map(item => (\n <Fragment key={item.row.key}>\n {item.anchor.toggleSize > 0 && (\n <Popover>\n <PopoverTrigger asChild>\n {/* A span, not a `button`: Firefox does not start drags from buttons. */}\n <span\n role=\"button\"\n tabIndex={0}\n draggable={editable}\n className={cn(\n 'absolute z-10 flex items-center justify-center rounded-sm bg-background text-muted-foreground transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary',\n editable ? 'cursor-grab active:cursor-grabbing' : 'cursor-pointer',\n iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && 'opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100',\n )}\n style={{ top: `${item.anchor.toggleTop}px`, left: `${item.anchor.toggleLeft}px`, width: `${item.anchor.toggleSize}px`, height: `${item.anchor.toggleSize}px` }}\n aria-label={t('composites.chrome.details', { kind: item.row.kindLabel })}\n title={editable ? t('composites.chrome.detailsDrag', { kind: item.row.kindLabel }) : t('composites.chrome.details', { kind: item.row.kindLabel })}\n onKeyDown={(event: KeyboardEvent<HTMLElement>) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.currentTarget.click()\n }}\n onDragStart={event => onIconDragStart(event.nativeEvent, item.row)}\n >\n <Icon key={item.row.icon} name={item.row.icon} className=\"size-3.5\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n className={DETAILS_POPOVER}\n >\n <CompositeDetails editor={editor} row={item.row} editable={editable} />\n </PopoverContent>\n </Popover>\n )}\n\n {(item.status || item.derived || (item.row.tagsVisible && (item.row.tags.length > 0 || editable))) && (\n <div\n className=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-background\"\n style={{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }}\n onMouseEnter={() => setChipsHoveredKey(item.row.key)}\n onMouseLeave={() => setChipsHoveredKey(null)}\n >\n {item.derived && (\n <>\n {item.derived.toggleable && (\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-expanded={item.derived.open}\n aria-label={t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')}\n title={t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')}\n onClick={() => toggleExpanded(item.row)}\n >\n <Icon name={item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'} className=\"size-3.5\" />\n </button>\n )}\n <span className={CHIP}>\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5\" />{t('composites.derivation.badge')}\n </span>\n {item.derived.stale && (\n <span className={cn(CHIP, 'text-(--sk-warning)')}>\n <Icon name=\"i-lucide-triangle-alert\" className=\"size-3.5\" />{t('composites.derivation.stale')}\n </span>\n )}\n </>\n )}\n\n {item.status && (editable\n ? (\n <CompositeStatusMenu editor={editor} row={item.row}>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.status.label', { status: item.status.label })}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </button>\n </CompositeStatusMenu>\n )\n : (\n <span className={CHIP}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </span>\n ))}\n\n {(item.row.tagsVisible ? item.row.tags : []).map(tag => (\n <span key={tag} className={cn(CHIP, 'group max-w-40 gap-0')}>\n <span className=\"truncate\">#{tag}</span>\n {editable && (\n <button\n type=\"button\"\n className=\"flex h-4 w-0 shrink-0 items-center justify-center overflow-hidden rounded-sm opacity-0 transition-all duration-150 group-hover:ml-1 group-hover:w-4 group-hover:opacity-100 hover:bg-muted hover:text-foreground focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-primary\"\n aria-label={t('composites.tag.remove', { tag })}\n onClick={() => removeTag(item.row, tag)}\n >\n <Icon name=\"i-lucide-x\" className=\"size-3\" />\n </button>\n )}\n </span>\n ))}\n\n {editable && item.row.tagsVisible && (\n <Popover>\n <PopoverTrigger asChild>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.tag.add')} title={t('composites.tag.add')}>\n <Icon name=\"i-lucide-plus\" className=\"size-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={6}\n className=\"w-auto gap-0 p-0\"\n onCloseAutoFocus={event => event.preventDefault()}\n >\n <div className=\"w-64 p-3\">\n <CompositeTagsEditor editor={editor} row={item.row} />\n </div>\n </PopoverContent>\n </Popover>\n )}\n </div>\n )}\n </Fragment>\n ))}\n </div>,\n host,\n )\n}\n\nconst NO_KEYS: ReadonlySet<string> = new Set()\n\n/** Chips start right after the title's last character and run on to the editor's right edge. */\nconst ROW_HEIGHT = 24\n\nconst CHIP = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-muted-foreground'\nconst CHIP_BUTTON = `${CHIP} transition-colors duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary`\n\n/** `statusStyle` names Nuxt UI text colors: the shadcn counterparts (`--sk-info` and `--sk-success` come from the token stylesheets). */\nconst STATUS_COLOR: Record<string, string> = {\n 'text-muted': 'text-muted-foreground',\n 'text-info': 'text-(--sk-info)',\n 'text-success': 'text-(--sk-success)',\n 'text-dimmed': 'text-muted-foreground/70',\n}\n\n/** Scale and fade in from the icon (`transform-origin` is Radix's), a bit longer than the default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst DETAILS_POPOVER = 'w-auto gap-0 p-0 composite-popover data-[state=open]:animate-[composite-pop-in_160ms_var(--ease-out)] data-[state=closed]:animate-[composite-pop-out_120ms_var(--ease-out)]'\n"],"mappings":"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"}
1
+ {"version":3,"file":"CompositeChrome.js","names":[],"sources":["../../../../src/features/composites/react/CompositeChrome.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { Fragment, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { startBlockDrag } from '../../../core'\nimport { Icon } from '../../../react/Icon'\nimport { useStore } from '../../../react/use-store'\nimport { useEditorSettings } from '../../../react/settings'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { Popover, PopoverContent, PopoverTrigger } from '../../../react/ui/popover'\nimport { cn } from '../../../react/ui/utils'\nimport { compositeSectionOf } from '../anchors'\nimport { derivedChromeOf } from '../derived-chrome'\nimport { rowStatus } from '../meta-display'\nimport { basketById, basketOf, titleHiddenOf } from '../baskets'\nimport { compositeBasketsOf } from '../nodes'\nimport { compositeOfRow, type CompositeRow, setCompositeTags } from '../rows'\nimport { CompositeBasketMenu } from './CompositeBasketMenu'\nimport { CompositeDetails } from './CompositeDetails'\nimport { CompositeStatusMenu } from './CompositeStatusMenu'\nimport { CompositeTagsEditor } from './CompositeTagsEditor'\nimport { useCompositeAnchors } from './use-composite-anchors'\nimport { useCompositeRows } from './use-composite-rows'\n\n/**\n * Chrome of every composite, laid over the editor (mounted by the composites feature): on the title line the status\n * chip and one chip per tag (with a `+` chip to add tags), and on the front the kind icon. The icon is a button that\n * opens the details popover (kind with a confirmation before it changes, the kind's fields and the summary;\n * `CompositeDetails`) and, in an editable editor, the composite's drag handle: dragging it moves the composite\n * (`startBlockDrag`). With `blocks.kindIcon: 'hover'` it shows only while the composite is hovered, its popover is\n * open or the icon has keyboard focus. Chips and popover also render in read-only editors, without edit affordances.\n * The node view's own description and tags rows are hidden by CSS; this overlay shows and edits them.\n */\nexport function CompositeChrome({ editor }: { editor: Editor }) {\n const { rows } = useCompositeRows(editor)\n const { host, anchors, frame } = useCompositeAnchors(editor, rows)\n\n // `isEditable` is not reactive: follow it on every transaction (`EditorSurface` dispatches one after `setEditable`).\n const [editable, setEditable] = useState(() => !editor.isDestroyed && editor.isEditable)\n useEffect(() => {\n const sync = () => setEditable(!editor.isDestroyed && editor.isEditable)\n sync()\n editor.on('transaction', sync)\n return () => {\n editor.off('transaction', sync)\n }\n }, [editor])\n\n const { settings } = useEditorSettings()\n const { t } = useEditorI18n()\n const iconOnHover = settings.blocks.kindIcon === 'hover'\n\n // Hover state of `kindIcon: 'hover'`: the keys of the rows whose composite is under the pointer (that one and every\n // composite around it), plus the row whose chips the pointer is on (they sit outside the editor's DOM).\n const [hoveredKeys, setHoveredKeys] = useState<ReadonlySet<string>>(NO_KEYS)\n const [chipsHoveredKey, setChipsHoveredKey] = useState<string | null>(null)\n const latestRows = useRef(rows)\n latestRows.current = rows\n\n useEffect(() => {\n let hoveredSection: Element | null = null\n const onMouseleave = () => {\n hoveredSection = null\n setHoveredKeys(NO_KEYS)\n }\n const onMouseover = (event: MouseEvent) => {\n const section = event.target instanceof Element ? event.target.closest('.composite') : null\n if (section === hoveredSection) return\n hoveredSection = section\n const chain = new Set<Element>()\n for (let el = section; el; el = el.parentElement?.closest('.composite') ?? null) chain.add(el)\n const { view, state } = editor\n setHoveredKeys(new Set(latestRows.current.filter((row) => {\n const found = compositeOfRow(state.doc, row)\n const dom = found ? view.nodeDOM(found.pos) : null\n const section = dom instanceof Element ? compositeSectionOf(dom) : null\n return section !== null && chain.has(section)\n }).map(row => row.key)))\n }\n\n onMouseleave()\n if (!iconOnHover || editor.isDestroyed) return\n const { dom } = editor.view\n dom.addEventListener('mouseover', onMouseover)\n dom.addEventListener('mouseleave', onMouseleave)\n return () => {\n dom.removeEventListener('mouseover', onMouseover)\n dom.removeEventListener('mouseleave', onMouseleave)\n }\n }, [editor, iconOnHover])\n\n function onIconDragStart(event: DragEvent, row: CompositeRow) {\n const found = compositeOfRow(editor.state.doc, row)\n if (!found || !startBlockDrag(editor, event, found.pos)) event.preventDefault()\n }\n\n const expanded = useStore(editor.storage.composite.expanded)\n const revealedTitles = useStore(editor.storage.composite.revealedTitles)\n\n const placed = useMemo(() => {\n const baskets = compositeBasketsOf(editor)\n // End of the collapsed derived Baustein the rows being walked sit in: its clipped content shows no chrome.\n let hiddenUntil = -1\n return rows.flatMap((row) => {\n if (row.pos < hiddenUntil) return []\n const derived = derivedChromeOf(editor, row)\n const open = !derived || !row.id || expanded.has(row.id)\n if (!open) hiddenUntil = row.end\n const anchor = anchors.get(row.key)\n if (!anchor) return []\n const titleHidden = titleHiddenOf(row)\n // A hidden title leaves nothing to measure: the chips sit in the strip beside the kind icon (`composites.css`).\n const inStrip = titleHidden && !(row.id && revealedTitles.has(row.id)) && anchor.toggleSize > 0\n // An empty title shows a placeholder the anchor cannot measure: keep clear of it.\n const left = inStrip ? anchor.toggleLeft + anchor.toggleSize + 6 : anchor.lineEnd + (anchor.titleEmpty ? 116 : 12)\n return [{\n row,\n anchor,\n left,\n top: (inStrip ? anchor.toggleTop + anchor.toggleSize / 2 : anchor.lastCenter) - ROW_HEIGHT / 2,\n width: Math.max((frame?.right ?? anchor.right) - left, 120),\n status: rowStatus(row, t),\n derived: derived && { stale: derived.stale, open, toggleable: !!row.id },\n basket: basketById(baskets, basketOf(row)) ?? null,\n titleHidden,\n }]\n })\n }, [editor, rows, anchors, frame, expanded, revealedTitles, t])\n\n function toggleExpanded(row: CompositeRow) {\n if (row.id) editor.commands.toggleCompositeExpanded(row.id)\n }\n\n function removeTag(row: CompositeRow, tag: string) {\n setCompositeTags(editor, row, row.tags.filter(candidate => candidate !== tag))\n }\n\n function toggleTitleHidden(row: CompositeRow) {\n const found = compositeOfRow(editor.state.doc, row)\n if (found) editor.commands.setCompositeTitleHidden(!titleHiddenOf(row), found.pos)\n }\n\n if (!host) return null\n\n return createPortal(\n <div className=\"absolute top-0 left-0 h-0 w-0\">\n {placed.map(item => (\n <Fragment key={item.row.key}>\n {item.anchor.toggleSize > 0 && (\n <Popover>\n <PopoverTrigger asChild>\n {/* A span, not a `button`: Firefox does not start drags from buttons. */}\n <span\n role=\"button\"\n tabIndex={0}\n draggable={editable}\n className={cn(\n 'absolute z-10 flex items-center justify-center rounded-sm bg-background text-muted-foreground transition-[color,background-color,opacity] duration-120 motion-reduce:transition-none hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary',\n editable ? 'cursor-grab active:cursor-grabbing' : 'cursor-pointer',\n iconOnHover && !hoveredKeys.has(item.row.key) && chipsHoveredKey !== item.row.key && 'opacity-0 hover:opacity-100 focus-visible:opacity-100 data-[state=open]:opacity-100',\n )}\n style={{ top: `${item.anchor.toggleTop}px`, left: `${item.anchor.toggleLeft}px`, width: `${item.anchor.toggleSize}px`, height: `${item.anchor.toggleSize}px` }}\n aria-label={t('composites.chrome.details', { kind: item.row.kindLabel })}\n title={editable ? t('composites.chrome.detailsDrag', { kind: item.row.kindLabel }) : t('composites.chrome.details', { kind: item.row.kindLabel })}\n onKeyDown={(event: KeyboardEvent<HTMLElement>) => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n event.currentTarget.click()\n }}\n onDragStart={event => onIconDragStart(event.nativeEvent, item.row)}\n >\n <Icon key={item.row.icon} name={item.row.icon} className=\"size-3.5\" />\n </span>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={4}\n className={DETAILS_POPOVER}\n >\n <CompositeDetails editor={editor} row={item.row} editable={editable} />\n </PopoverContent>\n </Popover>\n )}\n\n {(editable || item.basket || item.status || item.derived || (item.row.tagsVisible && item.row.tags.length > 0)) && (\n <div\n className=\"absolute z-10 flex items-center gap-px overflow-hidden rounded-md bg-background\"\n style={{ top: `${item.top}px`, left: `${item.left}px`, maxWidth: `${item.width}px`, height: `${ROW_HEIGHT}px` }}\n onMouseEnter={() => setChipsHoveredKey(item.row.key)}\n onMouseLeave={() => setChipsHoveredKey(null)}\n >\n {editable\n ? (\n <>\n <CompositeBasketMenu editor={editor} row={item.row}>\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-label={t('composites.basket.label')}\n title={item.basket ? `${t('composites.basket.label')}: ${item.basket.label}` : t('composites.basket.label')}\n >\n {item.basket\n ? <span className=\"size-2.5 rounded-full\" style={{ backgroundColor: item.basket.color }} />\n : <Icon name=\"i-lucide-circle-dashed\" className=\"size-3.5\" />}\n </button>\n </CompositeBasketMenu>\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-label={t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')}\n title={t(item.titleHidden ? 'composites.titleHidden.show' : 'composites.titleHidden.hide')}\n onClick={() => toggleTitleHidden(item.row)}\n >\n <Icon name={item.titleHidden ? 'i-lucide-eye' : 'i-lucide-eye-off'} className=\"size-3.5\" />\n </button>\n </>\n )\n : item.basket && (\n <span className={CHIP} title={item.basket.label}>\n <span className=\"size-2.5 rounded-full\" style={{ backgroundColor: item.basket.color }} />\n </span>\n )}\n\n {item.derived && (\n <>\n {item.derived.toggleable && (\n <button\n type=\"button\"\n className={CHIP_BUTTON}\n aria-expanded={item.derived.open}\n aria-label={t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')}\n title={t(item.derived.open ? 'composites.derivation.collapse' : 'composites.derivation.expand')}\n onClick={() => toggleExpanded(item.row)}\n >\n <Icon name={item.derived.open ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'} className=\"size-3.5\" />\n </button>\n )}\n <span className={CHIP}>\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5\" />{t('composites.derivation.badge')}\n </span>\n {item.derived.stale && (\n <span className={cn(CHIP, 'text-(--sk-warning)')}>\n <Icon name=\"i-lucide-triangle-alert\" className=\"size-3.5\" />{t('composites.derivation.stale')}\n </span>\n )}\n </>\n )}\n\n {item.status && (editable\n ? (\n <CompositeStatusMenu editor={editor} row={item.row}>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.status.label', { status: item.status.label })}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </button>\n </CompositeStatusMenu>\n )\n : (\n <span className={CHIP}>\n <Icon name={item.status.icon} className={cn('size-3.5', STATUS_COLOR[item.status.color] ?? item.status.color)} />{item.status.label}\n </span>\n ))}\n\n {(item.row.tagsVisible ? item.row.tags : []).map(tag => (\n <span key={tag} className={cn(CHIP, 'group max-w-40 gap-0')}>\n <span className=\"truncate\">#{tag}</span>\n {editable && (\n <button\n type=\"button\"\n className=\"flex h-4 w-0 shrink-0 items-center justify-center overflow-hidden rounded-sm opacity-0 transition-all duration-150 group-hover:ml-1 group-hover:w-4 group-hover:opacity-100 hover:bg-muted hover:text-foreground focus-visible:ml-1 focus-visible:w-4 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-primary\"\n aria-label={t('composites.tag.remove', { tag })}\n onClick={() => removeTag(item.row, tag)}\n >\n <Icon name=\"i-lucide-x\" className=\"size-3\" />\n </button>\n )}\n </span>\n ))}\n\n {editable && item.row.tagsVisible && (\n <Popover>\n <PopoverTrigger asChild>\n <button type=\"button\" className={CHIP_BUTTON} aria-label={t('composites.tag.add')} title={t('composites.tag.add')}>\n <Icon name=\"i-lucide-plus\" className=\"size-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent\n side=\"bottom\"\n align=\"start\"\n sideOffset={6}\n className=\"w-auto gap-0 p-0\"\n onCloseAutoFocus={event => event.preventDefault()}\n >\n <div className=\"w-64 p-3\">\n <CompositeTagsEditor editor={editor} row={item.row} />\n </div>\n </PopoverContent>\n </Popover>\n )}\n </div>\n )}\n </Fragment>\n ))}\n </div>,\n host,\n )\n}\n\nconst NO_KEYS: ReadonlySet<string> = new Set()\n\n/** Chips start right after the title's last character and run on to the editor's right edge. */\nconst ROW_HEIGHT = 24\n\nconst CHIP = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap text-muted-foreground'\nconst CHIP_BUTTON = `${CHIP} transition-colors duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary`\n\n/** `statusStyle` names Nuxt UI text colors: the shadcn counterparts (`--sk-info` and `--sk-success` come from the token stylesheets). */\nconst STATUS_COLOR: Record<string, string> = {\n 'text-muted': 'text-muted-foreground',\n 'text-info': 'text-(--sk-info)',\n 'text-success': 'text-(--sk-success)',\n 'text-dimmed': 'text-muted-foreground/70',\n}\n\n/** Scale and fade in from the icon (`transform-origin` is Radix's), a bit longer than the default; `composites.css` defines the keyframes and the reduced-motion override. */\nconst DETAILS_POPOVER = 'w-auto gap-0 p-0 composite-popover data-[state=open]:animate-[composite-pop-in_160ms_var(--ease-out)] data-[state=closed]:animate-[composite-pop-out_120ms_var(--ease-out)]'\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { compositeKind } from "../kinds.js";
2
+ import { basketOf, titleHiddenOf } from "../baskets.js";
2
3
  import { derivationOf } from "../derivation.js";
3
- import { compositeKindsOf } from "../nodes.js";
4
+ import { compositeBasketsOf, compositeKindsOf } from "../nodes.js";
4
5
  import { cn } from "../../../react/ui/utils.js";
5
6
  import { useStore } from "../../../react/use-store.js";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -14,13 +15,22 @@ function CompositeView({ editor, node, selected }) {
14
15
  const expanded = useStore(editor.storage.composite.expanded);
15
16
  const id = node.attrs.id;
16
17
  const collapsed = typeof id === "string" && id !== "" && derivationOf(node) !== null && !expanded.has(id);
17
- const placeholderVars = { "--composite-title-placeholder": cssString(kind.titlePlaceholder) };
18
+ const basket = basketOf(node);
19
+ const basketColor = compositeBasketsOf(editor).find((candidate) => candidate.id === basket)?.color;
20
+ const revealedTitles = useStore(editor.storage.composite.revealedTitles);
21
+ const titleHidden = titleHiddenOf(node) && !(typeof id === "string" && revealedTitles.has(id));
22
+ const placeholderVars = {
23
+ "--composite-title-placeholder": cssString(kind.titlePlaceholder),
24
+ ...basketColor ? { "--sk-basket-color": basketColor } : {}
25
+ };
18
26
  return /* @__PURE__ */ jsxs(NodeViewWrapper, {
19
27
  as: "section",
20
28
  className: cn("composite", collapsed && "is-collapsed", selected && "is-selected rounded-md ring-1 ring-primary"),
21
29
  "data-type": "composite",
22
30
  "data-kind": node.attrs.kind,
23
31
  "data-id": node.attrs.id || void 0,
32
+ "data-basket": basket ?? void 0,
33
+ "data-title-hidden": titleHidden ? "" : void 0,
24
34
  style: placeholderVars,
25
35
  children: [/* @__PURE__ */ jsx("span", {
26
36
  className: "composite__toggle",
@@ -1 +1 @@
1
- {"version":3,"file":"CompositeView.js","names":[],"sources":["../../../../src/features/composites/react/CompositeView.tsx"],"sourcesContent":["import { NodeViewContent, NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport type { CSSProperties } from 'react'\nimport { cn } from '../../../react/ui/utils'\nimport { compositeKind } from '../kinds'\nimport { useStore } from '../../../react/use-store'\nimport { derivationOf } from '../derivation'\nimport { compositeKindsOf } from '../nodes'\n\n/** `text` as a CSS string literal (for `content:`). */\nconst cssString = (text: string) => `\"${text.replace(/[\"\\\\]/g, '\\\\$&').replace(/\\n/g, '\\\\a ')}\"`\n\n/** The composite block: the section with its content. The kind icon is drawn by the composite chrome overlay. */\nexport function CompositeView({ editor, node, selected }: NodeViewProps) {\n const kind = compositeKind(compositeKindsOf(editor), String(node.attrs.kind))\n // A derived Baustein (one with a `meta.derivation`) shows collapsed to a faded preview until it is opened; the content\n // stays laid out (CSS clips it), so editing positions are unaffected. The chrome's chevron and a caret inside open it.\n const expanded = useStore(editor.storage.composite.expanded)\n const id: unknown = node.attrs.id\n const collapsed = typeof id === 'string' && id !== '' && derivationOf(node) !== null && !expanded.has(id)\n // Placeholder of the empty title (composites.css `content: var(…)`); a nested composite sets its own.\n const placeholderVars = { '--composite-title-placeholder': cssString(kind.titlePlaceholder) } as CSSProperties\n return (\n <NodeViewWrapper\n as=\"section\"\n className={cn('composite', collapsed && 'is-collapsed', selected && 'is-selected rounded-md ring-1 ring-primary')}\n data-type=\"composite\"\n data-kind={node.attrs.kind}\n data-id={node.attrs.id || undefined}\n style={placeholderVars}\n >\n {/* Anchor of the kind icon on the left border: it only takes the icon's place and size. The composite chrome overlay\n measures it and draws the icon as the button that opens the details popover and drags the composite. */}\n <span className=\"composite__toggle\" contentEditable={false} aria-hidden=\"true\" />\n <NodeViewContent className=\"composite__content\" />\n </NodeViewWrapper>\n )\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,aAAa,SAAiB,IAAI,KAAK,QAAQ,UAAU,MAAM,CAAC,CAAC,QAAQ,OAAO,MAAM,EAAE;;AAG9F,SAAgB,cAAc,EAAE,QAAQ,MAAM,YAA2B;CACvE,MAAM,OAAO,cAAc,iBAAiB,MAAM,GAAG,OAAO,KAAK,MAAM,IAAI,CAAC;CAG5E,MAAM,WAAW,SAAS,OAAO,QAAQ,UAAU,QAAQ;CAC3D,MAAM,KAAc,KAAK,MAAM;CAC/B,MAAM,YAAY,OAAO,OAAO,YAAY,OAAO,MAAM,aAAa,IAAI,MAAM,QAAQ,CAAC,SAAS,IAAI,EAAE;CAExG,MAAM,kBAAkB,EAAE,iCAAiC,UAAU,KAAK,gBAAgB,EAAE;CAC5F,OACE,qBAAC,iBAAD;EACE,IAAG;EACH,WAAW,GAAG,aAAa,aAAa,gBAAgB,YAAY,4CAA4C;EAChH,aAAU;EACV,aAAW,KAAK,MAAM;EACtB,WAAS,KAAK,MAAM,MAAM,KAAA;EAC1B,OAAO;EANT,UAAA,CAUE,oBAAC,QAAD;GAAM,WAAU;GAAoB,iBAAiB;GAAO,eAAY;EAAQ,CAAA,GAChF,oBAAC,iBAAD,EAAiB,WAAU,qBAAsB,CAAA,CAClC;;AAErB"}
1
+ {"version":3,"file":"CompositeView.js","names":[],"sources":["../../../../src/features/composites/react/CompositeView.tsx"],"sourcesContent":["import { NodeViewContent, NodeViewWrapper, type NodeViewProps } from '@tiptap/react'\nimport type { CSSProperties } from 'react'\nimport { cn } from '../../../react/ui/utils'\nimport { basketOf, titleHiddenOf } from '../baskets'\nimport { compositeKind } from '../kinds'\nimport { useStore } from '../../../react/use-store'\nimport { derivationOf } from '../derivation'\nimport { compositeBasketsOf, compositeKindsOf } from '../nodes'\n\n/** `text` as a CSS string literal (for `content:`). */\nconst cssString = (text: string) => `\"${text.replace(/[\"\\\\]/g, '\\\\$&').replace(/\\n/g, '\\\\a ')}\"`\n\n/** The composite block: the section with its content. The kind icon is drawn by the composite chrome overlay. */\nexport function CompositeView({ editor, node, selected }: NodeViewProps) {\n const kind = compositeKind(compositeKindsOf(editor), String(node.attrs.kind))\n // A derived Baustein (one with a `meta.derivation`) shows collapsed to a faded preview until it is opened; the content\n // stays laid out (CSS clips it), so editing positions are unaffected. The chrome's chevron and a caret inside open it.\n const expanded = useStore(editor.storage.composite.expanded)\n const id: unknown = node.attrs.id\n const collapsed = typeof id === 'string' && id !== '' && derivationOf(node) !== null && !expanded.has(id)\n // Colour basket (`meta.basket`): `data-basket` carries the id, the stripe takes the basket's colour from `--sk-basket-color`.\n const basket = basketOf(node)\n const basketColor = compositeBasketsOf(editor).find(candidate => candidate.id === basket)?.color\n // A hidden title (`meta.titleHidden`) stays hidden until a caret enters it (`revealedTitles`, see `CompositeNode`).\n const revealedTitles = useStore(editor.storage.composite.revealedTitles)\n const titleHidden = titleHiddenOf(node) && !(typeof id === 'string' && revealedTitles.has(id))\n // Placeholder of the empty title (composites.css `content: var(…)`); a nested composite sets its own.\n const placeholderVars = {\n '--composite-title-placeholder': cssString(kind.titlePlaceholder),\n ...(basketColor ? { '--sk-basket-color': basketColor } : {}),\n } as CSSProperties\n return (\n <NodeViewWrapper\n as=\"section\"\n className={cn('composite', collapsed && 'is-collapsed', selected && 'is-selected rounded-md ring-1 ring-primary')}\n data-type=\"composite\"\n data-kind={node.attrs.kind}\n data-id={node.attrs.id || undefined}\n data-basket={basket ?? undefined}\n data-title-hidden={titleHidden ? '' : undefined}\n style={placeholderVars}\n >\n {/* Anchor of the kind icon on the left border: it only takes the icon's place and size. The composite chrome overlay\n measures it and draws the icon as the button that opens the details popover and drags the composite. */}\n <span className=\"composite__toggle\" contentEditable={false} aria-hidden=\"true\" />\n <NodeViewContent className=\"composite__content\" />\n </NodeViewWrapper>\n )\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAM,aAAa,SAAiB,IAAI,KAAK,QAAQ,UAAU,MAAM,CAAC,CAAC,QAAQ,OAAO,MAAM,EAAE;;AAG9F,SAAgB,cAAc,EAAE,QAAQ,MAAM,YAA2B;CACvE,MAAM,OAAO,cAAc,iBAAiB,MAAM,GAAG,OAAO,KAAK,MAAM,IAAI,CAAC;CAG5E,MAAM,WAAW,SAAS,OAAO,QAAQ,UAAU,QAAQ;CAC3D,MAAM,KAAc,KAAK,MAAM;CAC/B,MAAM,YAAY,OAAO,OAAO,YAAY,OAAO,MAAM,aAAa,IAAI,MAAM,QAAQ,CAAC,SAAS,IAAI,EAAE;CAExG,MAAM,SAAS,SAAS,IAAI;CAC5B,MAAM,cAAc,mBAAmB,MAAM,CAAC,CAAC,MAAK,cAAa,UAAU,OAAO,MAAM,CAAC,EAAE;CAE3F,MAAM,iBAAiB,SAAS,OAAO,QAAQ,UAAU,cAAc;CACvE,MAAM,cAAc,cAAc,IAAI,KAAK,EAAE,OAAO,OAAO,YAAY,eAAe,IAAI,EAAE;CAE5F,MAAM,kBAAkB;EACtB,iCAAiC,UAAU,KAAK,gBAAgB;EAChE,GAAI,cAAc,EAAE,qBAAqB,YAAY,IAAI,CAAC;CAC5D;CACA,OACE,qBAAC,iBAAD;EACE,IAAG;EACH,WAAW,GAAG,aAAa,aAAa,gBAAgB,YAAY,4CAA4C;EAChH,aAAU;EACV,aAAW,KAAK,MAAM;EACtB,WAAS,KAAK,MAAM,MAAM,KAAA;EAC1B,eAAa,UAAU,KAAA;EACvB,qBAAmB,cAAc,KAAK,KAAA;EACtC,OAAO;EART,UAAA,CAYE,oBAAC,QAAD;GAAM,WAAU;GAAoB,iBAAiB;GAAO,eAAY;EAAQ,CAAA,GAChF,oBAAC,iBAAD,EAAiB,WAAU,qBAAsB,CAAA,CAClC;;AAErB"}
@@ -2,7 +2,7 @@ import { derivationOf } from "../derivation.js";
2
2
  import { useEditorI18n } from "../../../react/use-editor-i18n.js";
3
3
  import { cn } from "../../../react/ui/utils.js";
4
4
  import { Icon } from "../../../react/Icon.js";
5
- import { derivationDetailsOf, derivationMethodLabel } from "../derived-chrome.js";
5
+ import { derivationDetailsOf, derivationIsForeign, derivationMethodLabel } from "../derived-chrome.js";
6
6
  import { formatDate } from "../meta-display.js";
7
7
  import { useMemo } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -24,6 +24,7 @@ function DerivationBadge({ editor, node, detailed }) {
24
24
  ]);
25
25
  if (!derivation) return null;
26
26
  const method = derivationMethodLabel(t, derivation.method);
27
+ const foreign = derivationIsForeign(editor, derivation);
27
28
  const created = derivation.createdAt ? formatDate(derivation.createdAt) : "";
28
29
  if (!detailed) return /* @__PURE__ */ jsxs("div", {
29
30
  className: "flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground",
@@ -35,6 +36,7 @@ function DerivationBadge({ editor, node, detailed }) {
35
36
  children: [
36
37
  t("composites.derivation.badge"),
37
38
  method,
39
+ foreign ? t("composites.derivation.foreign") : "",
38
40
  created
39
41
  ].filter(Boolean).join(" · ")
40
42
  })]
@@ -53,6 +55,13 @@ function DerivationBadge({ editor, node, detailed }) {
53
55
  className: "size-3.5"
54
56
  }), t("composites.derivation.badge")]
55
57
  }),
58
+ details.foreign && /* @__PURE__ */ jsxs("span", {
59
+ className: cn(CHIP, "text-muted-foreground"),
60
+ children: [/* @__PURE__ */ jsx(Icon, {
61
+ name: "i-lucide-file-symlink",
62
+ className: "size-3.5"
63
+ }), t("composites.derivation.foreign")]
64
+ }),
56
65
  details.stale && /* @__PURE__ */ jsxs("span", {
57
66
  className: cn(CHIP, "text-(--sk-warning)"),
58
67
  children: [/* @__PURE__ */ jsx(Icon, {
@@ -1 +1 @@
1
- {"version":3,"file":"DerivationBadge.js","names":[],"sources":["../../../../src/features/composites/react/DerivationBadge.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { useMemo } from 'react'\nimport { Icon } from '../../../react/Icon'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { derivationOf } from '../derivation'\nimport { derivationDetailsOf, derivationMethodLabel } from '../derived-chrome'\nimport { formatDate } from '../meta-display'\n\nconst CHIP = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap'\n\n/**\n * Where a derived Baustein comes from. Compact (the `derivation` field type): the „abgeleitet“ badge with method and\n * date. `detailed` (the details card): also the model, whether the source changed („Quelle geändert“), whether the body\n * was edited since it was generated („bearbeitet“) and a link to the stored raw output. The detailed variant hashes\n * the body, so it belongs only where one Baustein is shown.\n */\nexport function DerivationBadge({ editor, node, detailed }: { editor: Editor, node: ProseMirrorNode, detailed?: boolean }) {\n const { t } = useEditorI18n()\n const derivation = derivationOf(node)\n const details = useMemo(() => detailed ? derivationDetailsOf(editor, node) : null, [detailed, editor, node])\n if (!derivation) return null\n const method = derivationMethodLabel(t, derivation.method)\n const created = derivation.createdAt ? formatDate(derivation.createdAt) : ''\n\n if (!detailed) {\n return (\n <div className=\"flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground\">\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5 shrink-0\" />\n <span className=\"truncate\">{[t('composites.derivation.badge'), method, created].filter(Boolean).join(' · ')}</span>\n </div>\n )\n }\n if (!details) return null\n\n return (\n <div className=\"flex flex-col gap-1.5 rounded-md p-2 text-xs ring-1 ring-border\">\n <div className=\"flex flex-wrap items-center gap-1\">\n <span className={cn(CHIP, 'text-muted-foreground')}>\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5\" />{t('composites.derivation.badge')}\n </span>\n {details.stale && (\n <span className={cn(CHIP, 'text-(--sk-warning)')}>\n <Icon name=\"i-lucide-triangle-alert\" className=\"size-3.5\" />{t('composites.derivation.stale')}\n </span>\n )}\n {details.edited && (\n <span className={cn(CHIP, 'text-muted-foreground')}>\n <Icon name=\"i-lucide-pencil\" className=\"size-3.5\" />{t('composites.derivation.edited')}\n </span>\n )}\n </div>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-2 gap-y-0.5\">\n <dt className=\"text-muted-foreground\">{t('composites.derivation.method')}</dt>\n <dd className=\"truncate text-foreground\">{method}</dd>\n {derivation.model && (\n <>\n <dt className=\"text-muted-foreground\">{t('composites.derivation.model')}</dt>\n <dd className=\"truncate text-foreground\" title={derivation.model}>{derivation.model}</dd>\n </>\n )}\n {created && (\n <>\n <dt className=\"text-muted-foreground\">{t('composites.derivation.createdAt')}</dt>\n <dd className=\"text-foreground\">{created}</dd>\n </>\n )}\n </dl>\n {details.outputUrl && (\n <a href={details.outputUrl} target=\"_blank\" rel=\"noopener\" className=\"inline-flex w-fit items-center gap-1 text-primary hover:underline\">\n <Icon name=\"i-lucide-external-link\" className=\"size-3.5\" />{t('composites.derivation.openOutput')}\n </a>\n )}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,OAAO;;;;;;;AAQb,SAAgB,gBAAgB,EAAE,QAAQ,MAAM,YAA2E;CACzH,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,aAAa,aAAa,IAAI;CACpC,MAAM,UAAU,cAAc,WAAW,oBAAoB,QAAQ,IAAI,IAAI,MAAM;EAAC;EAAU;EAAQ;CAAI,CAAC;CAC3G,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,SAAS,sBAAsB,GAAG,WAAW,MAAM;CACzD,MAAM,UAAU,WAAW,YAAY,WAAW,WAAW,SAAS,IAAI;CAE1E,IAAI,CAAC,UACH,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,MAAD;GAAM,MAAK;GAAsB,WAAU;EAAqB,CAAA,GAChE,oBAAC,QAAD;GAAM,WAAU;GAAY,UAAA;IAAC,EAAE,6BAA6B;IAAG;IAAQ;GAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;EAAQ,CAAA,CAC/G;;CAGT,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,qBAAC,QAAD;MAAM,WAAW,GAAG,MAAM,uBAAuB;MAAjD,UAAA,CACE,oBAAC,MAAD;OAAM,MAAK;OAAsB,WAAU;MAAY,CAAA,GAAE,EAAE,6BAA6B,CACpF;;KACL,QAAQ,SACP,qBAAC,QAAD;MAAM,WAAW,GAAG,MAAM,qBAAqB;MAA/C,UAAA,CACE,oBAAC,MAAD;OAAM,MAAK;OAA0B,WAAU;MAAY,CAAA,GAAE,EAAE,6BAA6B,CACxF;;KAEP,QAAQ,UACP,qBAAC,QAAD;MAAM,WAAW,GAAG,MAAM,uBAAuB;MAAjD,UAAA,CACE,oBAAC,MAAD;OAAM,MAAK;OAAkB,WAAU;MAAY,CAAA,GAAE,EAAE,8BAA8B,CACjF;;IAEL;;GACL,qBAAC,MAAD;IAAI,WAAU;IAAd,UAAA;KACE,oBAAC,MAAD;MAAI,WAAU;MAAyB,UAAA,EAAE,8BAA8B;KAAM,CAAA;KAC7E,oBAAC,MAAD;MAAI,WAAU;MAA4B,UAAA;KAAW,CAAA;KACpD,WAAW,SACV,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,WAAU;MAAyB,UAAA,EAAE,6BAA6B;KAAM,CAAA,GAC5E,oBAAC,MAAD;MAAI,WAAU;MAA2B,OAAO,WAAW;MAAQ,UAAA,WAAW;KAAU,CAAA,CACxF,EAAA,CAAA;KAEH,WACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,WAAU;MAAyB,UAAA,EAAE,iCAAiC;KAAM,CAAA,GAChF,oBAAC,MAAD;MAAI,WAAU;MAAmB,UAAA;KAAY,CAAA,CAC7C,EAAA,CAAA;IAEF;;GACH,QAAQ,aACP,qBAAC,KAAD;IAAG,MAAM,QAAQ;IAAW,QAAO;IAAS,KAAI;IAAW,WAAU;IAArE,UAAA,CACE,oBAAC,MAAD;KAAM,MAAK;KAAyB,WAAU;IAAY,CAAA,GAAE,EAAE,kCAAkC,CAC/F;;EAEF;;AAET"}
1
+ {"version":3,"file":"DerivationBadge.js","names":[],"sources":["../../../../src/features/composites/react/DerivationBadge.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { useMemo } from 'react'\nimport { Icon } from '../../../react/Icon'\nimport { cn } from '../../../react/ui/utils'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { derivationOf } from '../derivation'\nimport { derivationDetailsOf, derivationIsForeign, derivationMethodLabel } from '../derived-chrome'\nimport { formatDate } from '../meta-display'\n\nconst CHIP = 'inline-flex h-5 shrink-0 items-center gap-1 rounded-sm px-1.5 text-xs leading-none whitespace-nowrap'\n\n/**\n * Where a derived Baustein comes from. Compact (the `derivation` field type): the „abgeleitet“ badge with method and\n * date. `detailed` (the details card): also the model, whether the source changed („Quelle geändert“), whether the body\n * was edited since it was generated („bearbeitet“) and a link to the stored raw output. The detailed variant hashes\n * the body, so it belongs only where one Baustein is shown.\n */\nexport function DerivationBadge({ editor, node, detailed }: { editor: Editor, node: ProseMirrorNode, detailed?: boolean }) {\n const { t } = useEditorI18n()\n const derivation = derivationOf(node)\n const details = useMemo(() => detailed ? derivationDetailsOf(editor, node) : null, [detailed, editor, node])\n if (!derivation) return null\n const method = derivationMethodLabel(t, derivation.method)\n const foreign = derivationIsForeign(editor, derivation)\n const created = derivation.createdAt ? formatDate(derivation.createdAt) : ''\n\n if (!detailed) {\n return (\n <div className=\"flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground\">\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5 shrink-0\" />\n <span className=\"truncate\">{[t('composites.derivation.badge'), method, foreign ? t('composites.derivation.foreign') : '', created].filter(Boolean).join(' · ')}</span>\n </div>\n )\n }\n if (!details) return null\n\n return (\n <div className=\"flex flex-col gap-1.5 rounded-md p-2 text-xs ring-1 ring-border\">\n <div className=\"flex flex-wrap items-center gap-1\">\n <span className={cn(CHIP, 'text-muted-foreground')}>\n <Icon name=\"i-lucide-git-branch\" className=\"size-3.5\" />{t('composites.derivation.badge')}\n </span>\n {details.foreign && (\n <span className={cn(CHIP, 'text-muted-foreground')}>\n <Icon name=\"i-lucide-file-symlink\" className=\"size-3.5\" />{t('composites.derivation.foreign')}\n </span>\n )}\n {details.stale && (\n <span className={cn(CHIP, 'text-(--sk-warning)')}>\n <Icon name=\"i-lucide-triangle-alert\" className=\"size-3.5\" />{t('composites.derivation.stale')}\n </span>\n )}\n {details.edited && (\n <span className={cn(CHIP, 'text-muted-foreground')}>\n <Icon name=\"i-lucide-pencil\" className=\"size-3.5\" />{t('composites.derivation.edited')}\n </span>\n )}\n </div>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-2 gap-y-0.5\">\n <dt className=\"text-muted-foreground\">{t('composites.derivation.method')}</dt>\n <dd className=\"truncate text-foreground\">{method}</dd>\n {derivation.model && (\n <>\n <dt className=\"text-muted-foreground\">{t('composites.derivation.model')}</dt>\n <dd className=\"truncate text-foreground\" title={derivation.model}>{derivation.model}</dd>\n </>\n )}\n {created && (\n <>\n <dt className=\"text-muted-foreground\">{t('composites.derivation.createdAt')}</dt>\n <dd className=\"text-foreground\">{created}</dd>\n </>\n )}\n </dl>\n {details.outputUrl && (\n <a href={details.outputUrl} target=\"_blank\" rel=\"noopener\" className=\"inline-flex w-fit items-center gap-1 text-primary hover:underline\">\n <Icon name=\"i-lucide-external-link\" className=\"size-3.5\" />{t('composites.derivation.openOutput')}\n </a>\n )}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,OAAO;;;;;;;AAQb,SAAgB,gBAAgB,EAAE,QAAQ,MAAM,YAA2E;CACzH,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,aAAa,aAAa,IAAI;CACpC,MAAM,UAAU,cAAc,WAAW,oBAAoB,QAAQ,IAAI,IAAI,MAAM;EAAC;EAAU;EAAQ;CAAI,CAAC;CAC3G,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,SAAS,sBAAsB,GAAG,WAAW,MAAM;CACzD,MAAM,UAAU,oBAAoB,QAAQ,UAAU;CACtD,MAAM,UAAU,WAAW,YAAY,WAAW,WAAW,SAAS,IAAI;CAE1E,IAAI,CAAC,UACH,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,MAAD;GAAM,MAAK;GAAsB,WAAU;EAAqB,CAAA,GAChE,oBAAC,QAAD;GAAM,WAAU;GAAY,UAAA;IAAC,EAAE,6BAA6B;IAAG;IAAQ,UAAU,EAAE,+BAA+B,IAAI;IAAI;GAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;EAAQ,CAAA,CAClK;;CAGT,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA;GACE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,qBAAC,QAAD;MAAM,WAAW,GAAG,MAAM,uBAAuB;MAAjD,UAAA,CACE,oBAAC,MAAD;OAAM,MAAK;OAAsB,WAAU;MAAY,CAAA,GAAE,EAAE,6BAA6B,CACpF;;KACL,QAAQ,WACP,qBAAC,QAAD;MAAM,WAAW,GAAG,MAAM,uBAAuB;MAAjD,UAAA,CACE,oBAAC,MAAD;OAAM,MAAK;OAAwB,WAAU;MAAY,CAAA,GAAE,EAAE,+BAA+B,CACxF;;KAEP,QAAQ,SACP,qBAAC,QAAD;MAAM,WAAW,GAAG,MAAM,qBAAqB;MAA/C,UAAA,CACE,oBAAC,MAAD;OAAM,MAAK;OAA0B,WAAU;MAAY,CAAA,GAAE,EAAE,6BAA6B,CACxF;;KAEP,QAAQ,UACP,qBAAC,QAAD;MAAM,WAAW,GAAG,MAAM,uBAAuB;MAAjD,UAAA,CACE,oBAAC,MAAD;OAAM,MAAK;OAAkB,WAAU;MAAY,CAAA,GAAE,EAAE,8BAA8B,CACjF;;IAEL;;GACL,qBAAC,MAAD;IAAI,WAAU;IAAd,UAAA;KACE,oBAAC,MAAD;MAAI,WAAU;MAAyB,UAAA,EAAE,8BAA8B;KAAM,CAAA;KAC7E,oBAAC,MAAD;MAAI,WAAU;MAA4B,UAAA;KAAW,CAAA;KACpD,WAAW,SACV,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,WAAU;MAAyB,UAAA,EAAE,6BAA6B;KAAM,CAAA,GAC5E,oBAAC,MAAD;MAAI,WAAU;MAA2B,OAAO,WAAW;MAAQ,UAAA,WAAW;KAAU,CAAA,CACxF,EAAA,CAAA;KAEH,WACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,MAAD;MAAI,WAAU;MAAyB,UAAA,EAAE,iCAAiC;KAAM,CAAA,GAChF,oBAAC,MAAD;MAAI,WAAU;MAAmB,UAAA;KAAY,CAAA,CAC7C,EAAA,CAAA;IAEF;;GACH,QAAQ,aACP,qBAAC,KAAD;IAAG,MAAM,QAAQ;IAAW,QAAO;IAAS,KAAI;IAAW,WAAU;IAArE,UAAA,CACE,oBAAC,MAAD;KAAM,MAAK;KAAyB,WAAU;IAAY,CAAA,GAAE,EAAE,kCAAkC,CAC/F;;EAEF;;AAET"}
@@ -0,0 +1,11 @@
1
+ import { Editor } from "@tiptap/core";
2
+ //#region src/features/composites/react/FocusChip.d.ts
3
+ /**
4
+ * Focus mode indicator (mounted by the composites feature): while a Baustein is focused (`focusComposite`) a small chip
5
+ * fixed at the bottom of the surface names it and leaves focus mode.
6
+ */
7
+ export declare function FocusChip({ editor }: {
8
+ editor: Editor;
9
+ }): import("react").JSX.Element | null;
10
+ //#endregion
11
+ //# sourceMappingURL=FocusChip.d.ts.map
@@ -0,0 +1,50 @@
1
+ import { findBlockById } from "../../../core/schema/block-ids.js";
2
+ import { useEditorI18n } from "../../../react/use-editor-i18n.js";
3
+ import { Icon } from "../../../react/Icon.js";
4
+ import { Button } from "../../../react/ui/button.js";
5
+ import { compositeFocusOf } from "../focus.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { useEditorState } from "@tiptap/react";
8
+ //#region src/features/composites/react/FocusChip.tsx
9
+ /**
10
+ * Focus mode indicator (mounted by the composites feature): while a Baustein is focused (`focusComposite`) a small chip
11
+ * fixed at the bottom of the surface names it and leaves focus mode.
12
+ */
13
+ function FocusChip({ editor }) {
14
+ const { t } = useEditorI18n();
15
+ const title = useEditorState({
16
+ editor,
17
+ selector: ({ editor: current }) => {
18
+ if (current.isDestroyed) return null;
19
+ const id = compositeFocusOf(current.state);
20
+ const found = id ? findBlockById(current.state.doc, id) : null;
21
+ return found ? found.node.child(0).textContent.trim() || t("composites.untitled") : null;
22
+ }
23
+ });
24
+ if (title === null) return null;
25
+ return /* @__PURE__ */ jsxs("div", {
26
+ className: "fixed bottom-4 left-1/2 z-20 flex max-w-[90vw] -translate-x-1/2 items-center gap-2 rounded-full border border-border bg-background py-1 pl-3 pr-1 shadow-lg",
27
+ role: "status",
28
+ children: [
29
+ /* @__PURE__ */ jsx(Icon, {
30
+ name: "i-lucide-focus",
31
+ className: "shrink-0 text-primary"
32
+ }),
33
+ /* @__PURE__ */ jsx("span", {
34
+ className: "truncate text-sm",
35
+ children: t("composites.focus.chip", { title })
36
+ }),
37
+ /* @__PURE__ */ jsxs(Button, {
38
+ size: "xs",
39
+ variant: "ghost",
40
+ "aria-label": t("composites.focus.leave"),
41
+ onClick: () => editor.commands.focusComposite(null),
42
+ children: [/* @__PURE__ */ jsx(Icon, { name: "i-lucide-x" }), t("composites.focus.release")]
43
+ })
44
+ ]
45
+ });
46
+ }
47
+ //#endregion
48
+ export { FocusChip };
49
+
50
+ //# sourceMappingURL=FocusChip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FocusChip.js","names":[],"sources":["../../../../src/features/composites/react/FocusChip.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { findBlockById } from '../../../core/schema'\nimport { Icon } from '../../../react/Icon'\nimport { Button } from '../../../react/ui/button'\nimport { useEditorI18n } from '../../../react/use-editor-i18n'\nimport { compositeFocusOf } from '../focus'\n\n/**\n * Focus mode indicator (mounted by the composites feature): while a Baustein is focused (`focusComposite`) a small chip\n * fixed at the bottom of the surface names it and leaves focus mode.\n */\nexport function FocusChip({ editor }: { editor: Editor }) {\n const { t } = useEditorI18n()\n const title = useEditorState({\n editor,\n selector: ({ editor: current }) => {\n if (current.isDestroyed) return null\n const id = compositeFocusOf(current.state)\n const found = id ? findBlockById(current.state.doc, id) : null\n return found ? found.node.child(0).textContent.trim() || t('composites.untitled') : null\n },\n })\n if (title === null) return null\n return (\n <div\n className=\"fixed bottom-4 left-1/2 z-20 flex max-w-[90vw] -translate-x-1/2 items-center gap-2 rounded-full border border-border bg-background py-1 pl-3 pr-1 shadow-lg\"\n role=\"status\"\n >\n <Icon name=\"i-lucide-focus\" className=\"shrink-0 text-primary\" />\n <span className=\"truncate text-sm\">{t('composites.focus.chip', { title })}</span>\n <Button size=\"xs\" variant=\"ghost\" aria-label={t('composites.focus.leave')} onClick={() => editor.commands.focusComposite(null)}>\n <Icon name=\"i-lucide-x\" />\n {t('composites.focus.release')}\n </Button>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,UAAU,EAAE,UAA8B;CACxD,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,QAAQ,eAAe;EAC3B;EACA,WAAW,EAAE,QAAQ,cAAc;GACjC,IAAI,QAAQ,aAAa,OAAO;GAChC,MAAM,KAAK,iBAAiB,QAAQ,KAAK;GACzC,MAAM,QAAQ,KAAK,cAAc,QAAQ,MAAM,KAAK,EAAE,IAAI;GAC1D,OAAO,QAAQ,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,KAAK,KAAK,EAAE,qBAAqB,IAAI;EACtF;CACF,CAAC;CACD,IAAI,UAAU,MAAM,OAAO;CAC3B,OACE,qBAAC,OAAD;EACE,WAAU;EACV,MAAK;EAFP,UAAA;GAIE,oBAAC,MAAD;IAAM,MAAK;IAAiB,WAAU;GAAyB,CAAA;GAC/D,oBAAC,QAAD;IAAM,WAAU;IAAoB,UAAA,EAAE,yBAAyB,EAAE,MAAM,CAAC;GAAQ,CAAA;GAChF,qBAAC,QAAD;IAAQ,MAAK;IAAK,SAAQ;IAAQ,cAAY,EAAE,wBAAwB;IAAG,eAAe,OAAO,SAAS,eAAe,IAAI;IAA7H,UAAA,CACE,oBAAC,MAAD,EAAM,MAAK,aAAc,CAAA,GACxB,EAAE,0BAA0B,CACvB;;EACL;;AAET"}
@@ -1,7 +1,9 @@
1
1
  import { DRAG_HANDLE_VISIBILITIES, HEADING_CONVERSION_SCOPES, KIND_ICON_VISIBILITIES, STRUCTURE_MODES, TREE_ROOTS } from "../../../settings/values.js";
2
+ import { SPLIT_TARGET_CHARS_MAX, clampSplitTargetChars } from "../../../settings/index.js";
2
3
  import { useEditorI18n } from "../../../react/use-editor-i18n.js";
3
4
  import { Icon } from "../../../react/Icon.js";
4
5
  import { useEditorSettings } from "../../../react/settings.js";
6
+ import { Input } from "../../../react/ui/input.js";
5
7
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
6
8
  import { SettingsSection } from "../../../react/SettingsSection.js";
7
9
  import { Label } from "../../../react/ui/label.js";
@@ -9,7 +11,7 @@ import { RadioGroup, RadioGroupItem } from "../../../react/ui/radio-group.js";
9
11
  import { STRUCTURE_MODE_KEYS } from "../structure-mode.js";
10
12
  import { DRAG_HANDLE_KEYS, HEADING_SCOPE_KEYS, KIND_ICON_KEYS, optionItems } from "../editing-settings-keys.js";
11
13
  import { TREE_ROOT_KEYS } from "../tree-root.js";
12
- import { useId } from "react";
14
+ import { useEffect, useId, useState } from "react";
13
15
  import { jsx, jsxs } from "react/jsx-runtime";
14
16
  //#region src/features/composites/react/SettingsEditingTab.tsx
15
17
  /** A group of radio options under a legend (the counterpart of Nuxt UI's `URadioGroup`), the options one above the other. */
@@ -58,6 +60,18 @@ function RadioOption({ item }) {
58
60
  function SettingsEditingTab({ kinds }) {
59
61
  const { settings, update } = useEditorSettings();
60
62
  const kindLabelId = useId();
63
+ const splitTargetId = useId();
64
+ const [splitTarget, setSplitTarget] = useState(String(settings.blocks.splitTargetChars));
65
+ useEffect(() => {
66
+ setSplitTarget(String(settings.blocks.splitTargetChars));
67
+ }, [settings.blocks.splitTargetChars]);
68
+ const commitSplitTarget = () => {
69
+ const next = clampSplitTargetChars(splitTarget, settings.blocks.splitTargetChars);
70
+ update((draft) => {
71
+ draft.blocks.splitTargetChars = next;
72
+ });
73
+ setSplitTarget(String(next));
74
+ };
61
75
  const { t } = useEditorI18n();
62
76
  const structureItems = optionItems(STRUCTURE_MODES, STRUCTURE_MODE_KEYS, t);
63
77
  const scopeItems = optionItems(HEADING_CONVERSION_SCOPES, HEADING_SCOPE_KEYS, t);
@@ -126,6 +140,27 @@ function SettingsEditingTab({ kinds }) {
126
140
  }, kind.id)) })]
127
141
  })]
128
142
  }),
143
+ /* @__PURE__ */ jsxs("div", {
144
+ className: "flex items-start justify-between gap-4 py-3",
145
+ children: [/* @__PURE__ */ jsx(Label, {
146
+ htmlFor: splitTargetId,
147
+ className: "min-w-0 flex-1 text-sm font-medium text-foreground",
148
+ children: t("settings.editing.splitTargetChars")
149
+ }), /* @__PURE__ */ jsx(Input, {
150
+ id: splitTargetId,
151
+ type: "number",
152
+ min: 200,
153
+ max: SPLIT_TARGET_CHARS_MAX,
154
+ step: 100,
155
+ className: "w-32",
156
+ value: splitTarget,
157
+ onChange: (event) => setSplitTarget(event.target.value),
158
+ onBlur: commitSplitTarget,
159
+ onKeyDown: (event) => {
160
+ if (event.key === "Enter") commitSplitTarget();
161
+ }
162
+ })]
163
+ }),
129
164
  /* @__PURE__ */ jsx(RadioField, {
130
165
  legend: t("composites.settings.kindIcon.legend"),
131
166
  items: kindIconItems,