@formicoidea/labre-core 0.41.0 → 0.42.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 (252) hide show
  1. package/LICENSE +384 -0
  2. package/dist/_pkgs/affine-block-bookmark/bookmark-block.js +2 -2
  3. package/dist/_pkgs/affine-block-edgeless-text/edgeless-toolbar/config.js +7 -1
  4. package/dist/_pkgs/affine-block-embed/embed-figma-block/embed-figma-block.js +2 -1
  5. package/dist/_pkgs/affine-block-embed/embed-github-block/embed-github-block.js +2 -1
  6. package/dist/_pkgs/affine-block-embed/embed-iframe-block/consts.d.ts +2 -0
  7. package/dist/_pkgs/affine-block-embed/embed-iframe-block/consts.js +5 -0
  8. package/dist/_pkgs/affine-block-embed/embed-iframe-block/embed-iframe-block.d.ts +1 -0
  9. package/dist/_pkgs/affine-block-embed/embed-iframe-block/embed-iframe-block.js +30 -8
  10. package/dist/_pkgs/affine-block-embed/embed-iframe-block/utils.d.ts +8 -0
  11. package/dist/_pkgs/affine-block-embed/embed-iframe-block/utils.js +19 -0
  12. package/dist/_pkgs/affine-block-embed/embed-loom-block/embed-loom-block.js +2 -1
  13. package/dist/_pkgs/affine-block-embed/embed-youtube-block/embed-youtube-block.js +2 -1
  14. package/dist/_pkgs/affine-block-frame/frame-manager.js +1 -1
  15. package/dist/_pkgs/affine-block-frame/frame-tool.js +1 -1
  16. package/dist/_pkgs/affine-block-frame/frame-toolbar.js +6 -1
  17. package/dist/_pkgs/affine-block-list/list-keymap.js +7 -0
  18. package/dist/_pkgs/affine-block-note/note-keymap.js +7 -1
  19. package/dist/_pkgs/affine-block-paragraph/paragraph-keymap.js +7 -1
  20. package/dist/_pkgs/affine-block-root/commands/pivot-commands.js +1 -8
  21. package/dist/_pkgs/affine-block-root/commands/tag-commands.js +1 -8
  22. package/dist/_pkgs/affine-block-root/edgeless/edgeless-keyboard.d.ts +9 -0
  23. package/dist/_pkgs/affine-block-root/edgeless/edgeless-keyboard.js +51 -0
  24. package/dist/_pkgs/affine-block-root/edgeless/edgeless-root-block.d.ts +22 -0
  25. package/dist/_pkgs/affine-block-root/edgeless/edgeless-root-block.js +46 -0
  26. package/dist/_pkgs/affine-block-root/edgeless/utils/pinch-zoom.d.ts +21 -0
  27. package/dist/_pkgs/affine-block-root/edgeless/utils/pinch-zoom.js +24 -0
  28. package/dist/_pkgs/affine-block-root/edgeless/utils/query.js +8 -0
  29. package/dist/_pkgs/affine-block-root/keyboard/keymap.js +1 -16
  30. package/dist/_pkgs/affine-block-surface/element-model/index.d.ts +21 -3
  31. package/dist/_pkgs/affine-block-surface/element-model/index.js +14 -2
  32. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.d.ts +23 -0
  33. package/dist/_pkgs/affine-block-surface/extensions/export-svg/command.js +89 -0
  34. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.d.ts +7 -0
  35. package/dist/_pkgs/affine-block-surface/extensions/export-svg/index.js +5 -0
  36. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.d.ts +41 -0
  37. package/dist/_pkgs/affine-block-surface/extensions/export-svg/render.js +70 -0
  38. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.d.ts +49 -0
  39. package/dist/_pkgs/affine-block-surface/extensions/export-svg/svg-context.js +257 -0
  40. package/dist/_pkgs/affine-block-surface/extensions/export-svg/tagged-path.d.ts +16 -0
  41. package/dist/_pkgs/affine-block-surface/extensions/export-svg/tagged-path.js +24 -0
  42. package/dist/_pkgs/affine-block-surface/extensions/export-svg/toolbar.d.ts +24 -0
  43. package/dist/_pkgs/affine-block-surface/extensions/export-svg/toolbar.js +66 -0
  44. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +4 -0
  45. package/dist/_pkgs/affine-block-surface/extensions/index.js +4 -0
  46. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.d.ts +66 -4
  47. package/dist/_pkgs/affine-block-surface/extensions/interchange-import.js +241 -11
  48. package/dist/_pkgs/affine-block-surface/extensions/interchange.d.ts +18 -0
  49. package/dist/_pkgs/affine-block-surface/extensions/legend-toolbar.d.ts +81 -0
  50. package/dist/_pkgs/affine-block-surface/extensions/legend-toolbar.js +81 -0
  51. package/dist/_pkgs/affine-block-surface/extensions/legend.d.ts +136 -0
  52. package/dist/_pkgs/affine-block-surface/extensions/legend.js +439 -0
  53. package/dist/_pkgs/affine-block-surface/extensions/reading-widget.d.ts +17 -3
  54. package/dist/_pkgs/affine-block-surface/extensions/reading-widget.js +35 -13
  55. package/dist/_pkgs/affine-block-surface/extensions/reading.d.ts +61 -1
  56. package/dist/_pkgs/affine-block-surface/extensions/reading.js +85 -39
  57. package/dist/_pkgs/affine-block-surface/extensions/record-action.d.ts +66 -0
  58. package/dist/_pkgs/affine-block-surface/extensions/record-action.js +145 -0
  59. package/dist/_pkgs/affine-block-surface/extensions/validation.d.ts +253 -28
  60. package/dist/_pkgs/affine-block-surface/extensions/validation.js +489 -41
  61. package/dist/_pkgs/affine-block-surface/framework-background/background-view.d.ts +115 -0
  62. package/dist/_pkgs/affine-block-surface/framework-background/background-view.js +185 -0
  63. package/dist/_pkgs/affine-block-surface/framework-background/index.d.ts +1 -0
  64. package/dist/_pkgs/affine-block-surface/framework-background/index.js +1 -0
  65. package/dist/_pkgs/affine-block-surface/framework-background/labels.d.ts +16 -1
  66. package/dist/_pkgs/affine-block-surface/framework-background/labels.js +31 -21
  67. package/dist/_pkgs/affine-block-surface/framework-background/renderer.js +29 -5
  68. package/dist/_pkgs/affine-block-surface/framework-background/stacking.d.ts +6 -4
  69. package/dist/_pkgs/affine-block-surface/framework-background/stacking.js +56 -8
  70. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.d.ts +12 -0
  71. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.js +15 -0
  72. package/dist/_pkgs/affine-block-surface/view.js +8 -1
  73. package/dist/_pkgs/affine-block-surface-ref/configs/slash-menu.js +1 -1
  74. package/dist/_pkgs/affine-components/color-picker/button.d.ts +56 -1
  75. package/dist/_pkgs/affine-components/color-picker/button.js +168 -23
  76. package/dist/_pkgs/affine-components/color-picker/framework-palette.d.ts +106 -0
  77. package/dist/_pkgs/affine-components/color-picker/framework-palette.js +177 -0
  78. package/dist/_pkgs/affine-components/color-picker/index.d.ts +2 -0
  79. package/dist/_pkgs/affine-components/color-picker/index.js +2 -0
  80. package/dist/_pkgs/affine-components/color-picker/palette-carousel.d.ts +85 -0
  81. package/dist/_pkgs/affine-components/color-picker/palette-carousel.js +440 -0
  82. package/dist/_pkgs/affine-components/edgeless-shape-color-picker/color-picker.d.ts +31 -1
  83. package/dist/_pkgs/affine-components/edgeless-shape-color-picker/color-picker.js +189 -63
  84. package/dist/_pkgs/affine-components/resource/resource.js +29 -16
  85. package/dist/_pkgs/affine-components/translations.d.ts +8 -0
  86. package/dist/_pkgs/affine-components/translations.js +17 -0
  87. package/dist/_pkgs/affine-foundation/view.js +2 -1
  88. package/dist/_pkgs/affine-gfx-brush/element-renderer.js +2 -2
  89. package/dist/_pkgs/affine-gfx-brush/toolbar/configs/brush.js +6 -1
  90. package/dist/_pkgs/affine-gfx-brush/toolbar/configs/highlighter.js +6 -1
  91. package/dist/_pkgs/affine-gfx-connector/connector-manager.d.ts +9 -1
  92. package/dist/_pkgs/affine-gfx-connector/connector-manager.js +61 -0
  93. package/dist/_pkgs/affine-gfx-connector/element-renderer/connector-dom/index.d.ts +7 -1
  94. package/dist/_pkgs/affine-gfx-connector/element-renderer/connector-dom/index.js +87 -17
  95. package/dist/_pkgs/affine-gfx-connector/element-renderer/index.js +78 -17
  96. package/dist/_pkgs/affine-gfx-connector/element-renderer/utils.d.ts +24 -2
  97. package/dist/_pkgs/affine-gfx-connector/element-renderer/utils.js +31 -8
  98. package/dist/_pkgs/affine-gfx-connector/text/edgeless-connector-label-editor.d.ts +124 -3
  99. package/dist/_pkgs/affine-gfx-connector/text/edgeless-connector-label-editor.js +216 -54
  100. package/dist/_pkgs/affine-gfx-connector/text/text.d.ts +13 -4
  101. package/dist/_pkgs/affine-gfx-connector/text/text.js +13 -44
  102. package/dist/_pkgs/affine-gfx-connector/toolbar/config.d.ts +56 -0
  103. package/dist/_pkgs/affine-gfx-connector/toolbar/config.js +72 -4
  104. package/dist/_pkgs/affine-gfx-connector/toolbar/hollow-endpoint-icons.d.ts +8 -0
  105. package/dist/_pkgs/affine-gfx-connector/toolbar/hollow-endpoint-icons.js +118 -0
  106. package/dist/_pkgs/affine-gfx-connector/view/view.d.ts +37 -1
  107. package/dist/_pkgs/affine-gfx-connector/view/view.js +97 -8
  108. package/dist/_pkgs/affine-gfx-group/command/group-api.js +1 -1
  109. package/dist/_pkgs/affine-gfx-group/store.js +0 -2
  110. package/dist/_pkgs/affine-gfx-group/translations.d.ts +9 -10
  111. package/dist/_pkgs/affine-gfx-group/translations.js +9 -13
  112. package/dist/_pkgs/affine-gfx-mindmap/templates/index.js +1 -1
  113. package/dist/_pkgs/affine-gfx-mindmap/toolbar/basket-elements.js +1 -1
  114. package/dist/_pkgs/affine-gfx-mindmap/toolbar/utils/import-mindmap.js +2 -2
  115. package/dist/_pkgs/affine-gfx-mindmap/translations.d.ts +1 -1
  116. package/dist/_pkgs/affine-gfx-mindmap/translations.js +1 -1
  117. package/dist/_pkgs/affine-gfx-shape/element-renderer/highlighter.js +2 -2
  118. package/dist/_pkgs/affine-gfx-shape/overlay/polygon-drawing-overlay.d.ts +6 -0
  119. package/dist/_pkgs/affine-gfx-shape/overlay/polygon-drawing-overlay.js +30 -1
  120. package/dist/_pkgs/affine-gfx-shape/polygon-tool.d.ts +20 -1
  121. package/dist/_pkgs/affine-gfx-shape/polygon-tool.js +9 -1
  122. package/dist/_pkgs/affine-gfx-shape/toolbar/config.js +6 -1
  123. package/dist/_pkgs/affine-gfx-shape/toolbar/palette-color-action.js +11 -1
  124. package/dist/_pkgs/affine-gfx-template/builtin/other.js +1 -1
  125. package/dist/_pkgs/affine-gfx-template/snapshot-from-action.js +32 -119
  126. package/dist/_pkgs/affine-gfx-template/toolbar/template-type.d.ts +1 -1
  127. package/dist/_pkgs/affine-gfx-text/dblclick-add-edgeless-text.d.ts +13 -1
  128. package/dist/_pkgs/affine-gfx-text/dblclick-add-edgeless-text.js +19 -4
  129. package/dist/_pkgs/affine-gfx-text/edgeless-text-editor.d.ts +41 -0
  130. package/dist/_pkgs/affine-gfx-text/edgeless-text-editor.js +57 -2
  131. package/dist/_pkgs/affine-gfx-text/toolbar/actions.d.ts +8 -1
  132. package/dist/_pkgs/affine-gfx-text/toolbar/actions.js +12 -2
  133. package/dist/_pkgs/affine-gfx-text/translations.d.ts +1 -0
  134. package/dist/_pkgs/affine-gfx-text/translations.js +2 -1
  135. package/dist/_pkgs/affine-inline-preset/markdown.js +5 -1
  136. package/dist/_pkgs/affine-model/consts/connector.d.ts +28 -2
  137. package/dist/_pkgs/affine-model/consts/connector.js +26 -0
  138. package/dist/_pkgs/affine-model/elements/bpmn/pool.js +1 -1
  139. package/dist/_pkgs/affine-model/elements/brush/brush.js +1 -1
  140. package/dist/_pkgs/affine-model/elements/c4/board.js +1 -1
  141. package/dist/_pkgs/affine-model/elements/c4/boundary.js +1 -1
  142. package/dist/_pkgs/affine-model/elements/connector/connector.d.ts +135 -0
  143. package/dist/_pkgs/affine-model/elements/connector/connector.js +246 -5
  144. package/dist/_pkgs/affine-model/elements/connector/end-label.d.ts +83 -0
  145. package/dist/_pkgs/affine-model/elements/connector/end-label.js +119 -0
  146. package/dist/_pkgs/affine-model/elements/connector/index.d.ts +1 -0
  147. package/dist/_pkgs/affine-model/elements/connector/index.js +1 -0
  148. package/dist/_pkgs/affine-model/elements/context-map/context-map.js +1 -1
  149. package/dist/_pkgs/affine-model/elements/core-domain/core-domain.d.ts +28 -0
  150. package/dist/_pkgs/affine-model/elements/core-domain/core-domain.js +129 -2
  151. package/dist/_pkgs/affine-model/elements/cynefin/cynefin.d.ts +8 -0
  152. package/dist/_pkgs/affine-model/elements/cynefin/cynefin.js +45 -2
  153. package/dist/_pkgs/affine-model/elements/edgy/board.js +1 -1
  154. package/dist/_pkgs/affine-model/elements/edgy/facets.js +1 -1
  155. package/dist/_pkgs/affine-model/elements/estuarine/estuarine.d.ts +6 -0
  156. package/dist/_pkgs/affine-model/elements/estuarine/estuarine.js +36 -2
  157. package/dist/_pkgs/affine-model/elements/event-storming/event-storming.d.ts +18 -7
  158. package/dist/_pkgs/affine-model/elements/event-storming/event-storming.js +23 -8
  159. package/dist/_pkgs/affine-model/elements/framework-background/background.d.ts +16 -0
  160. package/dist/_pkgs/affine-model/elements/framework-background/background.js +18 -0
  161. package/dist/_pkgs/affine-model/elements/highlighter/highlighter.js +1 -1
  162. package/dist/_pkgs/affine-model/elements/index.d.ts +8 -0
  163. package/dist/_pkgs/affine-model/elements/index.js +1 -0
  164. package/dist/_pkgs/affine-model/elements/shape/shape.js +1 -1
  165. package/dist/_pkgs/affine-model/elements/text/text.js +1 -1
  166. package/dist/_pkgs/affine-model/elements/uml/band.d.ts +39 -0
  167. package/dist/_pkgs/affine-model/elements/uml/band.js +31 -0
  168. package/dist/_pkgs/affine-model/elements/uml/diagram.d.ts +158 -0
  169. package/dist/_pkgs/affine-model/elements/uml/diagram.js +229 -0
  170. package/dist/_pkgs/affine-model/elements/uml/fragment.d.ts +161 -0
  171. package/dist/_pkgs/affine-model/elements/uml/fragment.js +205 -0
  172. package/dist/_pkgs/affine-model/elements/uml/index.d.ts +8 -0
  173. package/dist/_pkgs/affine-model/elements/uml/index.js +8 -0
  174. package/dist/_pkgs/affine-model/elements/uml/lifeline.d.ts +77 -0
  175. package/dist/_pkgs/affine-model/elements/uml/lifeline.js +89 -0
  176. package/dist/_pkgs/affine-model/elements/uml/node.d.ts +142 -0
  177. package/dist/_pkgs/affine-model/elements/uml/node.js +190 -0
  178. package/dist/_pkgs/affine-model/elements/uml/partition.d.ts +106 -0
  179. package/dist/_pkgs/affine-model/elements/uml/partition.js +172 -0
  180. package/dist/_pkgs/affine-model/elements/uml/region.d.ts +75 -0
  181. package/dist/_pkgs/affine-model/elements/uml/region.js +150 -0
  182. package/dist/_pkgs/affine-model/elements/uml/subject.d.ts +37 -0
  183. package/dist/_pkgs/affine-model/elements/uml/subject.js +108 -0
  184. package/dist/_pkgs/affine-model/elements/wardley/wardley.js +1 -1
  185. package/dist/_pkgs/affine-shared/services/embed-iframe/embed-iframe-config.d.ts +1 -0
  186. package/dist/_pkgs/affine-shared/services/font-loader/config.d.ts +7 -0
  187. package/dist/_pkgs/affine-shared/services/font-loader/config.js +43 -0
  188. package/dist/_pkgs/affine-shared/services/pivot-materiality-publisher.js +1 -9
  189. package/dist/_pkgs/affine-shared/services/telemetry-service/document-damage-watcher.d.ts +53 -0
  190. package/dist/_pkgs/affine-shared/services/telemetry-service/document-damage-watcher.js +95 -0
  191. package/dist/_pkgs/affine-shared/services/telemetry-service/index.d.ts +1 -0
  192. package/dist/_pkgs/affine-shared/services/telemetry-service/index.js +1 -0
  193. package/dist/_pkgs/affine-shared/services/telemetry-service/lifecycle.d.ts +26 -1
  194. package/dist/_pkgs/affine-shared/services/telemetry-service/telemetry-service.d.ts +2 -2
  195. package/dist/_pkgs/affine-shared/services/toolbar-service/command-action.d.ts +34 -0
  196. package/dist/_pkgs/affine-shared/services/toolbar-service/command-action.js +63 -0
  197. package/dist/_pkgs/affine-shared/services/toolbar-service/index.d.ts +1 -0
  198. package/dist/_pkgs/affine-shared/services/toolbar-service/index.js +1 -0
  199. package/dist/_pkgs/affine-shared/services/translation-service/chrome.d.ts +77 -7
  200. package/dist/_pkgs/affine-shared/services/translation-service/chrome.js +97 -7
  201. package/dist/_pkgs/affine-shared/styles/index.d.ts +1 -0
  202. package/dist/_pkgs/affine-shared/styles/index.js +1 -0
  203. package/dist/_pkgs/affine-shared/styles/motion.d.ts +14 -0
  204. package/dist/_pkgs/affine-shared/styles/motion.js +38 -0
  205. package/dist/_pkgs/affine-shared/utils/event.d.ts +10 -0
  206. package/dist/_pkgs/affine-shared/utils/event.js +20 -0
  207. package/dist/_pkgs/affine-shared/utils/file/filesys.d.ts +16 -0
  208. package/dist/_pkgs/affine-shared/utils/file/filesys.js +25 -0
  209. package/dist/_pkgs/affine-shared/utils/index.d.ts +1 -0
  210. package/dist/_pkgs/affine-shared/utils/index.js +1 -0
  211. package/dist/_pkgs/affine-shared/utils/link-preview-refresh.d.ts +12 -0
  212. package/dist/_pkgs/affine-shared/utils/link-preview-refresh.js +13 -0
  213. package/dist/_pkgs/affine-widget-edgeless-toolbar/catalogue/artefact-catalogue-widget.d.ts +12 -4
  214. package/dist/_pkgs/affine-widget-edgeless-toolbar/catalogue/artefact-catalogue-widget.js +27 -4
  215. package/dist/_pkgs/affine-widget-edgeless-toolbar/create-popper.js +13 -0
  216. package/dist/_pkgs/affine-widget-edgeless-toolbar/index.d.ts +2 -0
  217. package/dist/_pkgs/affine-widget-edgeless-toolbar/index.js +2 -0
  218. package/dist/_pkgs/affine-widget-edgeless-toolbar/menu/command-menu.d.ts +80 -0
  219. package/dist/_pkgs/affine-widget-edgeless-toolbar/menu/command-menu.js +270 -81
  220. package/dist/_pkgs/affine-widget-edgeless-toolbar/menu/senior-menu-selection.d.ts +15 -0
  221. package/dist/_pkgs/affine-widget-edgeless-toolbar/menu/senior-menu-selection.js +15 -0
  222. package/dist/_pkgs/affine-widget-edgeless-toolbar/panel/font-weight-and-style-panel.js +8 -1
  223. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/artefact-ghost-overlay.d.ts +42 -0
  224. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/artefact-ghost-overlay.js +100 -0
  225. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/artefact-placement-tool.d.ts +100 -0
  226. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/artefact-placement-tool.js +274 -0
  227. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/index.d.ts +3 -0
  228. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/index.js +3 -0
  229. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/translate-created.d.ts +25 -0
  230. package/dist/_pkgs/affine-widget-edgeless-toolbar/placement/translate-created.js +67 -0
  231. package/dist/_pkgs/affine-widget-edgeless-toolbar/view.js +5 -0
  232. package/dist/_pkgs/global/gfx/xywh.js +7 -0
  233. package/dist/_pkgs/std/extension/command-registry.d.ts +158 -1
  234. package/dist/_pkgs/std/extension/command-registry.js +22 -0
  235. package/dist/_pkgs/std/gfx/index.d.ts +1 -1
  236. package/dist/_pkgs/std/gfx/interactivity/gfx-view-event-handler.d.ts +12 -0
  237. package/dist/_pkgs/std/gfx/interactivity/gfx-view-event-handler.js +20 -1
  238. package/dist/_pkgs/std/gfx/interactivity/manager.d.ts +10 -0
  239. package/dist/_pkgs/std/gfx/interactivity/manager.js +12 -0
  240. package/dist/_pkgs/std/gfx/model/surface/element-model.js +12 -7
  241. package/dist/_pkgs/std/gfx/model/surface/surface-model.d.ts +56 -0
  242. package/dist/_pkgs/std/gfx/model/surface/surface-model.js +84 -3
  243. package/dist/_pkgs/std/gfx/view/view.d.ts +11 -2
  244. package/dist/_pkgs/std/gfx/view/view.js +16 -2
  245. package/dist/commands.d.ts +6 -0
  246. package/dist/commands.js +8 -1
  247. package/dist/frameworks.d.ts +9 -0
  248. package/dist/frameworks.js +19 -0
  249. package/dist/translations.js +16 -0
  250. package/package.json +9 -4
  251. package/dist/_pkgs/affine-gfx-group/group-watcher.d.ts +0 -1
  252. package/dist/_pkgs/affine-gfx-group/group-watcher.js +0 -20
@@ -0,0 +1,81 @@
1
+ import { BOARD_LEGEND_NOTATION, TelemetryProvider, } from '../../affine-shared/services/index.js';
2
+ import { html } from 'lit';
3
+ import { createBoardLegend } from './legend.js';
4
+ /**
5
+ * Contextual-toolbar glyph — "generate the legend of what is on this board".
6
+ * One icon because it is one gesture; the Core Domain chart has drawn this exact
7
+ * box-and-rows since its legend button shipped and the others borrowed it.
8
+ *
9
+ * `html` rather than `svg`: a toolbar action's `icon` is rendered as a
10
+ * standalone template, unlike the senior-button glyphs that are interpolated
11
+ * into an outer `<svg>`.
12
+ */
13
+ export const legendIcon = html `<svg
14
+ width="24"
15
+ height="24"
16
+ viewBox="0 0 24 24"
17
+ fill="none"
18
+ xmlns="http://www.w3.org/2000/svg"
19
+ >
20
+ <rect
21
+ x="3"
22
+ y="4"
23
+ width="18"
24
+ height="16"
25
+ rx="2"
26
+ stroke="currentColor"
27
+ stroke-width="1.6"
28
+ />
29
+ <circle cx="7" cy="9" r="1.6" fill="currentColor" />
30
+ <circle cx="7" cy="14" r="1.6" fill="currentColor" />
31
+ <path d="M11 9 H18 M11 14 H18" stroke="currentColor" stroke-width="1.4" />
32
+ </svg>`;
33
+ /**
34
+ * The ONE `FrameworkLegendCreated` emission, with the wire values every
35
+ * framework's own button has been sending.
36
+ *
37
+ * `framework` is the HISTORICAL PostHog value (`FrameworkDescriptor.
38
+ * telemetryKey`): `context-map`, not `ddd-context-map`. It is a parameter and
39
+ * not derived from anything, because the two spellings differ for three of the
40
+ * eight frameworks and a prefix rule would quietly rename a metric.
41
+ */
42
+ export function trackLegendCreated(std, framework, module) {
43
+ std.getOptional(TelemetryProvider)?.track('FrameworkLegendCreated', {
44
+ framework,
45
+ element: 'legend',
46
+ page: 'whiteboard editor',
47
+ segment: 'element toolbar',
48
+ module,
49
+ });
50
+ }
51
+ /**
52
+ * The shared legend entry of a board's contextual toolbar.
53
+ *
54
+ * Registered by each framework in its OWN flag-gated module
55
+ * (`custom:affine:surface:<board>`), merged with `validationToolbarConfig`'s
56
+ * actions: a flavour may carry exactly one toolbar module, and generating a
57
+ * legend is tooling a flag may take away. What the gesture already WROTE is
58
+ * document content and keeps being painted with the flag off — the box is made
59
+ * of plain shapes, text and the framework's own always-on renderers.
60
+ */
61
+ export function legendToolbarAction({ id = 'b.legend', Model, owner, framework, module = `${framework} toolbar`, tooltipWording = BOARD_LEGEND_NOTATION, icon = legendIcon, }) {
62
+ const boardOf = (ctx) => ctx.getSurfaceModelsByType(Model)[0];
63
+ return {
64
+ id,
65
+ tooltipWording,
66
+ icon,
67
+ run(ctx) {
68
+ if (ctx.std.store.readonly)
69
+ return;
70
+ // The FIRST selected board and no other: a legend is placed relative to
71
+ // one board, and two of them would put two boxes on top of whatever sits
72
+ // in that corner.
73
+ const board = boardOf(ctx);
74
+ if (!board)
75
+ return;
76
+ createBoardLegend(ctx.std, board, owner);
77
+ trackLegendCreated(ctx.std, framework, module);
78
+ },
79
+ when: (ctx) => boardOf(ctx) !== undefined,
80
+ };
81
+ }
@@ -0,0 +1,136 @@
1
+ import { Bound } from '../../global/gfx/index.js';
2
+ import { type BlockStdScope, type CommandLegendExtra, type CommandLegendRow, type CommandOwner } from '../../std/index.js';
3
+ import { type GfxController, type RoleId } from '../../std/gfx/index.js';
4
+ /**
5
+ * The legend PLATFORM: one engine, one box, for every framework that documents
6
+ * itself.
7
+ *
8
+ * Two things it deliberately does not do, and they are the whole design:
9
+ *
10
+ * - **detection is by ROLE, never by `instanceof` or by fill colour.** A role is
11
+ * the semantic identity of an artefact (`framework/std`'s `role.ts`); a shape
12
+ * type is not, and a colour is a restyle away from lying. Reading roles also
13
+ * means a legend and a validation rule agree on what is on the board, because
14
+ * they read the same field;
15
+ * - **a framework writes no legend code.** {@link legendFromCommands} derives
16
+ * the whole thing from the commands it already declares — their order, their
17
+ * category, the role each stamps and the swatch each subscribes
18
+ * (`CommandDescriptor.legend`). Scanning the perimeter, resolving
19
+ * specialisations, dropping empty sections, placing and grouping the box all
20
+ * happen here, once.
21
+ *
22
+ * It lives in the surface block rather than with any framework bundle for the
23
+ * reason `validation.ts` next door does: BPMN and Wardley must not take a
24
+ * dependency on a DDD bundle to draw their own legend.
25
+ *
26
+ * See `docs/adr/0026`.
27
+ */
28
+ type Surface = NonNullable<GfxController['surface']>;
29
+ /** A {@link CommandLegendRow} with the label the engine resolved for it. */
30
+ export interface LegendRow extends CommandLegendRow {
31
+ label: string;
32
+ }
33
+ export interface LegendSection {
34
+ title?: string;
35
+ rows: LegendRow[];
36
+ }
37
+ /** How the box is laid out, for a legend whose swatches are not 16-unit chips. */
38
+ export interface LegendLayout {
39
+ width?: number;
40
+ /** Row pitch; the swatch is drawn centred in it. Defaults to 28. */
41
+ rowHeight?: number;
42
+ /** Swatch box, defaults to a 16 × 16 chip. */
43
+ swatchWidth?: number;
44
+ swatchHeight?: number;
45
+ }
46
+ /** A block of the box that is not a row. See {@link CommandLegendBox.extras}. */
47
+ export type LegendExtra = CommandLegendExtra;
48
+ /**
49
+ * The ink and the type a notation writes its labels in: this box's rows, and
50
+ * the words the DDD prefabs set beside a dot or a marker.
51
+ *
52
+ * Declared HERE and re-exported by `@labre/affine-gfx-ddd-shared`, rather than
53
+ * the other way round, because that bundle depends on this one and a legend
54
+ * must not depend on a DDD bundle to be drawn.
55
+ */
56
+ export declare const LABEL_COLOR: string;
57
+ export declare const LABEL_FONT = "blocksuite:surface:Inter";
58
+ export declare const LABEL_FONT_SIZE = 14;
59
+ /**
60
+ * The role the legend's GROUP carries — the one thing on a generated legend
61
+ * that says what it is.
62
+ *
63
+ * Its glyphs deliberately carry none (see {@link addShape}), which is what
64
+ * keeps a legend out of the scan and out of every validation rule. But "made of
65
+ * nothing in particular" is unreadable to anything that has to tell a legend
66
+ * from a drawing, and something does: the `.bpmn` export counts what it left
67
+ * behind inside a pool, and a legend's fourteen glyphs would be reported as
68
+ * fourteen losses. The wrapper says so instead, once, structurally — no title
69
+ * to match on, no framework name, no geometry.
70
+ *
71
+ * `core:` because it belongs to no framework: no `RoleDefs` declares it, so
72
+ * `roleIsA` matches it against nothing, no rule evaluates it and no legend row
73
+ * lights because of it. Nothing is backfilled, as ever — a legend drawn before
74
+ * this shipped carries no role and is not recognised.
75
+ */
76
+ export declare const LEGEND_ROLE = "core:legend";
77
+ /**
78
+ * How big the box {@link addLegend} would draw for these sections is — without
79
+ * drawing it. The engine needs the height to drop the box bottom-left of a
80
+ * board, and measuring is the only honest way to get it: the box grows with the
81
+ * number of sub-titles, rows and extras, which is exactly what the detection
82
+ * pass decides.
83
+ */
84
+ export declare function measureLegend(sections: readonly LegendSection[], layout?: LegendLayout, extras?: readonly LegendExtra[]): {
85
+ width: number;
86
+ height: number;
87
+ };
88
+ /**
89
+ * A boxed legend: a bordered container with a bold title and bold section
90
+ * sub-titles, each row a swatch + label, then whatever `extras` add below.
91
+ * Returns the grouped id.
92
+ */
93
+ export declare function addLegend(surface: Surface, std: BlockStdScope, x: number, y: number, opts: {
94
+ title: string;
95
+ sections: LegendSection[];
96
+ extras?: readonly LegendExtra[];
97
+ } & LegendLayout): string;
98
+ /**
99
+ * Every role carried by an element inside `bound`.
100
+ *
101
+ * Neutral elements (no role) contribute nothing, which is the whole
102
+ * compatibility promise: a board drawn before the vocabularies existed produces
103
+ * an empty set and therefore a legend with no rows, exactly like an empty board.
104
+ * It is also why a legend never lists itself: nothing it draws carries a role.
105
+ */
106
+ export declare function rolesInBound(gfx: GfxController, bound: Bound): Set<RoleId>;
107
+ /**
108
+ * The legend of a board, derived from the commands of the framework that owns
109
+ * it: every subscribed row whose role is present (directly or through a
110
+ * specialisation, unless the entry asks for {@link CommandLegendEntry.exact}),
111
+ * in command order, grouped under the section each declares — or under its
112
+ * command's category — with empty sections dropped.
113
+ *
114
+ * The SUB-TITLES come out in the order the framework first declares them,
115
+ * walking every command whether its row lights or not: a legend is a key to a
116
+ * notation, so the same framework reads in the same order on every board, and
117
+ * only the rows change.
118
+ *
119
+ * Rows are de-duplicated BY ROLE, first declaration winning: two commands draw
120
+ * a Wardley area (a rectangle and a polygon) and the notation has one "Area".
121
+ *
122
+ * When nothing is recognised the result is an EMPTY array, and the caller still
123
+ * draws the box: a legend of a board with nothing on it lists nothing, rather
124
+ * than inventing the full notation the user has not used.
125
+ */
126
+ export declare function legendFromCommands(std: BlockStdScope, owner: CommandOwner, present: ReadonlySet<RoleId>): LegendSection[];
127
+ /**
128
+ * Build the legend of what is drawn inside `board` and drop it bottom-left of
129
+ * it, grouped and selected. THE gesture the shared toolbar button runs.
130
+ *
131
+ * Returns the group id, or `undefined` when there is no surface to draw on.
132
+ */
133
+ export declare function createBoardLegend(std: BlockStdScope, board: {
134
+ xywh: string;
135
+ }, owner: CommandOwner): string | undefined;
136
+ export {};
@@ -0,0 +1,439 @@
1
+ import { ConnectorMode, FontWeight, PointStyle, ShapeStyle, } from '../../affine-model/index.js';
2
+ import { NOTATION_NEUTRALS } from '../../affine-shared/consts/index.js';
3
+ import { BOARD_LEGEND_TITLE, GROUP_SEED_NAME, translateKey, } from '../../affine-shared/services/index.js';
4
+ import { Bound } from '../../global/gfx/index.js';
5
+ import { CATALOGUE_CATEGORY_KEY_PREFIX, getRegisteredCommands, humanizeCategory, } from '../../std/index.js';
6
+ import { GfxControllerIdentifier, roleIsA, RoleVocabularyIdentifier, } from '../../std/gfx/index.js';
7
+ import { EdgelessCRUDIdentifier } from './crud-extension.js';
8
+ /**
9
+ * The ink and the type a notation writes its labels in: this box's rows, and
10
+ * the words the DDD prefabs set beside a dot or a marker.
11
+ *
12
+ * Declared HERE and re-exported by `@labre/affine-gfx-ddd-shared`, rather than
13
+ * the other way round, because that bundle depends on this one and a legend
14
+ * must not depend on a DDD bundle to be drawn.
15
+ */
16
+ export const LABEL_COLOR = NOTATION_NEUTRALS.ink;
17
+ export const LABEL_FONT = 'blocksuite:surface:Inter';
18
+ export const LABEL_FONT_SIZE = 14;
19
+ const NO_STROKE = '#00000000';
20
+ /**
21
+ * The box's geometry, in one place because two callers need it: the one that
22
+ * DRAWS the box and the one that has to know how tall it will be before drawing
23
+ * it (see {@link measureLegend}).
24
+ */
25
+ const LEGEND_METRICS = {
26
+ DEFAULT_W: 260,
27
+ PAD: 16,
28
+ TITLE_H: 32,
29
+ SUB_H: 26,
30
+ ROW_H: 28,
31
+ /** Swatch side (a `line` swatch is this long). */
32
+ SW: 16,
33
+ };
34
+ /**
35
+ * The minimal shape write this module needs — deliberately NOT the DDD
36
+ * `dddShapeProps`, and deliberately without a `role` parameter.
37
+ *
38
+ * That absence is the invariant: a legend is drawn ON the board it describes
39
+ * and the scan detects by role, so a glyph carrying one would list itself the
40
+ * next time a legend was generated and would be counted by every validation
41
+ * rule. Here it is not a rule to remember but a parameter that does not exist.
42
+ */
43
+ function addShape(surface, x, y, w, h, opts) {
44
+ const { shapeType = 'rect', fill, stroke = NO_STROKE, strokeWidth = 0, radius = 0, } = opts;
45
+ return surface.addElement({
46
+ type: 'shape',
47
+ shapeType,
48
+ filled: true,
49
+ fillColor: fill,
50
+ strokeColor: stroke,
51
+ strokeWidth,
52
+ shapeStyle: ShapeStyle.General,
53
+ roughness: 0,
54
+ radius,
55
+ xywh: new Bound(x, y, w, h).serialize(),
56
+ });
57
+ }
58
+ /** A left-aligned (or centred) line of text. No `role`, same reason as above. */
59
+ function addText(surface, x, y, w, text, fontSize, textAlign = 'center', bold = false) {
60
+ return surface.addElement({
61
+ type: 'text',
62
+ text,
63
+ color: LABEL_COLOR,
64
+ fontFamily: LABEL_FONT,
65
+ fontSize,
66
+ textAlign,
67
+ fontWeight: bold ? FontWeight.SemiBold : FontWeight.Regular,
68
+ xywh: new Bound(x, y, w, fontSize + 10).serialize(),
69
+ });
70
+ }
71
+ /**
72
+ * A swatch the FRAMEWORK described: its props, plus the geometry the layout
73
+ * decides — and MINUS any `role`, stripped here so the invariant holds
74
+ * structurally whatever a table says.
75
+ *
76
+ * Cast in one place because the props arrive as data — a table in a framework
77
+ * module, not a literal this file can type — and the surface's own factory is
78
+ * what validates them.
79
+ */
80
+ function addSwatchElement(surface, props) {
81
+ const { role: _role, ...neutral } = props;
82
+ return surface.addElement(neutral);
83
+ }
84
+ /**
85
+ * The role the legend's GROUP carries — the one thing on a generated legend
86
+ * that says what it is.
87
+ *
88
+ * Its glyphs deliberately carry none (see {@link addShape}), which is what
89
+ * keeps a legend out of the scan and out of every validation rule. But "made of
90
+ * nothing in particular" is unreadable to anything that has to tell a legend
91
+ * from a drawing, and something does: the `.bpmn` export counts what it left
92
+ * behind inside a pool, and a legend's fourteen glyphs would be reported as
93
+ * fourteen losses. The wrapper says so instead, once, structurally — no title
94
+ * to match on, no framework name, no geometry.
95
+ *
96
+ * `core:` because it belongs to no framework: no `RoleDefs` declares it, so
97
+ * `roleIsA` matches it against nothing, no rule evaluates it and no legend row
98
+ * lights because of it. Nothing is backfilled, as ever — a legend drawn before
99
+ * this shipped carries no role and is not recognised.
100
+ */
101
+ export const LEGEND_ROLE = 'core:legend';
102
+ /**
103
+ * Group the box's elements under one titled group, without
104
+ * `@labre/affine-gfx-group`'s `createGroupCommand`: that package depends on
105
+ * THIS one, so importing it would close a cycle. The twelve lines below are
106
+ * that command's whole body, run against the same `EdgelessCRUDIdentifier` it
107
+ * uses, so a legend group is what the gesture says a group is — plus the one
108
+ * thing a plain group is not, {@link LEGEND_ROLE}.
109
+ */
110
+ function groupLegendElements(std, ids) {
111
+ const gfx = std.get(GfxControllerIdentifier);
112
+ const crud = std.get(EdgelessCRUDIdentifier);
113
+ const groups = gfx.layer.canvasElements.filter(el => el.type === 'group');
114
+ const groupId = crud.addElement('group', {
115
+ children: Object.fromEntries(ids.map(id => [id, true])),
116
+ // Translated HERE and once: the title is document content the moment it
117
+ // lands (ADR 0023), so the host's catalogue is asked at placement and never
118
+ // again — a renamed group keeps its name.
119
+ title: translateKey(std, ...GROUP_SEED_NAME, { n: groups.length + 1 }),
120
+ role: LEGEND_ROLE,
121
+ });
122
+ return groupId || ids[0];
123
+ }
124
+ /**
125
+ * How big the box {@link addLegend} would draw for these sections is — without
126
+ * drawing it. The engine needs the height to drop the box bottom-left of a
127
+ * board, and measuring is the only honest way to get it: the box grows with the
128
+ * number of sub-titles, rows and extras, which is exactly what the detection
129
+ * pass decides.
130
+ */
131
+ export function measureLegend(sections, layout = {}, extras = []) {
132
+ const { DEFAULT_W, PAD, TITLE_H, SUB_H, ROW_H } = LEGEND_METRICS;
133
+ let subs = 0;
134
+ let rows = 0;
135
+ for (const s of sections) {
136
+ if (s.title)
137
+ subs++;
138
+ rows += s.rows.length;
139
+ }
140
+ return {
141
+ width: layout.width ?? DEFAULT_W,
142
+ height: PAD * 2 +
143
+ TITLE_H +
144
+ subs * SUB_H +
145
+ rows * (layout.rowHeight ?? ROW_H) +
146
+ extras.reduce((sum, extra) => sum + extra.height, 0),
147
+ };
148
+ }
149
+ /**
150
+ * A boxed legend: a bordered container with a bold title and bold section
151
+ * sub-titles, each row a swatch + label, then whatever `extras` add below.
152
+ * Returns the grouped id.
153
+ */
154
+ export function addLegend(surface, std, x, y, opts) {
155
+ const { PAD, TITLE_H, SUB_H, SW } = LEGEND_METRICS;
156
+ const ROW_H = opts.rowHeight ?? LEGEND_METRICS.ROW_H;
157
+ /** The swatch COLUMN: as wide as the widest sample any row may draw. */
158
+ const SWW = opts.swatchWidth ?? SW;
159
+ const SWH = opts.swatchHeight ?? SW;
160
+ const extras = opts.extras ?? [];
161
+ const { width: W, height: H } = measureLegend(opts.sections, opts, extras);
162
+ const ids = [
163
+ addShape(surface, x, y, W, H, {
164
+ fill: NOTATION_NEUTRALS.cardFill,
165
+ stroke: NOTATION_NEUTRALS.legendBorder,
166
+ strokeWidth: 1.5,
167
+ radius: 8,
168
+ }),
169
+ ];
170
+ let cy = y + PAD;
171
+ ids.push(addText(surface, x + PAD, cy, W - PAD * 2, opts.title, 18, 'left', true));
172
+ cy += TITLE_H;
173
+ for (const sec of opts.sections) {
174
+ if (sec.title) {
175
+ ids.push(addText(surface, x + PAD, cy, W - PAD * 2, sec.title, 14, 'left', true));
176
+ cy += SUB_H;
177
+ }
178
+ for (const row of sec.rows) {
179
+ const sx = x + PAD;
180
+ const midY = cy + ROW_H / 2;
181
+ if (row.swatch === 'dot') {
182
+ ids.push(addShape(surface, sx, midY - SW / 2, SW, SW, {
183
+ shapeType: 'ellipse',
184
+ fill: row.color,
185
+ stroke: NOTATION_NEUTRALS.ink,
186
+ strokeWidth: 1,
187
+ }));
188
+ }
189
+ else if (row.swatch === 'square') {
190
+ ids.push(addShape(surface, sx, midY - SW / 2, SW, SW, {
191
+ fill: row.color,
192
+ stroke: NOTATION_NEUTRALS.ink,
193
+ strokeWidth: 1,
194
+ radius: 3,
195
+ }));
196
+ if (row.letter)
197
+ ids.push(addText(surface, sx, midY - 8, SW, row.letter, 11));
198
+ }
199
+ else if (row.swatch === 'glyph') {
200
+ // The artefact itself, at swatch size. `size` wins when the framework
201
+ // declares one — a gradation of sizes IS notation for Wardley, and
202
+ // fitting every pastille to the same box would erase it; otherwise the
203
+ // artefact's own aspect is fitted inside the box and centred, so a
204
+ // portrait picture stays portrait.
205
+ const aspect = row.aspect && row.aspect > 0 ? row.aspect : 1;
206
+ const [gw, gh] = row.size ?? [
207
+ Math.min(SWW, SWH * aspect),
208
+ Math.min(SWW, SWH * aspect) / aspect,
209
+ ];
210
+ ids.push(addSwatchElement(surface, {
211
+ ...row.props,
212
+ xywh: new Bound(sx + (SWW - gw) / 2, midY - gh / 2, gw, gh).serialize(),
213
+ }));
214
+ }
215
+ else if (row.swatch === 'edge') {
216
+ // A real connector across the swatch column, so the endpoint the
217
+ // notation puts on the line — a hollow diamond, a hollow triangle, a
218
+ // stick head — is drawn by the connector renderer rather than guessed at.
219
+ ids.push(addSwatchElement(surface, {
220
+ mode: ConnectorMode.Straight,
221
+ stroke: row.color,
222
+ strokeWidth: 1.5,
223
+ frontEndpointStyle: PointStyle.None,
224
+ rearEndpointStyle: PointStyle.None,
225
+ ...row.props,
226
+ type: 'connector',
227
+ source: { position: [sx, midY] },
228
+ target: { position: [sx + SWW, midY] },
229
+ }));
230
+ }
231
+ else if (row.swatch === 'custom' && row.draw) {
232
+ // A composite the framework draws itself, in the box we hand it. The
233
+ // ids come back so the whole thing joins the group like any other row.
234
+ ids.push(...row.draw(surface, { x: sx, y: midY - SWH / 2, w: SWW, h: SWH }, std));
235
+ }
236
+ else if (row.dashed) {
237
+ // Two 6-unit segments with a 4-unit gap: the same 16 units as a solid
238
+ // bar, read as a dash.
239
+ const seg = 6;
240
+ ids.push(addShape(surface, sx, midY - 2, seg, 4, {
241
+ fill: row.color,
242
+ radius: 1,
243
+ }));
244
+ ids.push(addShape(surface, sx + SW - seg, midY - 2, seg, 4, {
245
+ fill: row.color,
246
+ radius: 1,
247
+ }));
248
+ }
249
+ else {
250
+ ids.push(addShape(surface, sx, midY - 2, SW, 4, { fill: row.color, radius: 1 }));
251
+ }
252
+ ids.push(addText(surface, sx + SWW + 10, midY - LABEL_FONT_SIZE / 2, W - PAD * 2 - SWW - 10, row.label, 13, 'left'));
253
+ cy += ROW_H;
254
+ }
255
+ }
256
+ for (const extra of extras) {
257
+ ids.push(...extra.draw(surface, std, x, cy, W));
258
+ cy += extra.height;
259
+ }
260
+ return groupLegendElements(std, ids);
261
+ }
262
+ /* ── The scan ──────────────────────────────────────────────────────────── */
263
+ /**
264
+ * Distance from the board's left edge / bottom edge, in model units. Wardley's
265
+ * numbers, kept identical so every legend lands in the same place relative to
266
+ * its board.
267
+ */
268
+ const INSET_X = 50;
269
+ const INSET_BOTTOM = 56;
270
+ /**
271
+ * Every role carried by an element inside `bound`.
272
+ *
273
+ * Neutral elements (no role) contribute nothing, which is the whole
274
+ * compatibility promise: a board drawn before the vocabularies existed produces
275
+ * an empty set and therefore a legend with no rows, exactly like an empty board.
276
+ * It is also why a legend never lists itself: nothing it draws carries a role.
277
+ */
278
+ export function rolesInBound(gfx, bound) {
279
+ const present = new Set();
280
+ for (const el of gfx.getElementsByBound(bound, { type: 'canvas' })) {
281
+ if (el.role !== undefined)
282
+ present.add(el.role);
283
+ }
284
+ return present;
285
+ }
286
+ /** Every registered vocabulary, merged — a role id is namespaced, so it is flat. */
287
+ function vocabularyOf(std) {
288
+ const merged = {};
289
+ for (const defs of std.provider.getAll(RoleVocabularyIdentifier).values()) {
290
+ Object.assign(merged, defs);
291
+ }
292
+ return merged;
293
+ }
294
+ function entriesOf(command) {
295
+ const { legend } = command;
296
+ if (!legend)
297
+ return [];
298
+ return Array.isArray(legend) ? legend : [legend];
299
+ }
300
+ /**
301
+ * One row's label, through the host's catalogue, in the order of who owns the
302
+ * word:
303
+ *
304
+ * 1. the entry's own {@link CommandLegendEntry.labelWording} — a row that
305
+ * EXPLAINS the notation rather than naming it (Wardley's "Need / capability
306
+ * (activity, practice, data…)" against the role's bare "Component");
307
+ * 2. the ROLE's wording, which is the usual case and the reason a table never
308
+ * restates a label: renaming a role renames its legend row;
309
+ * 3. the COMMAND's wording, for a row whose role declares none.
310
+ *
311
+ * `labelPrefix` (the Context Map's "PS — ") is stitched back on AFTER
312
+ * translation: an abbreviation is never a translatable word.
313
+ */
314
+ function resolveRowLabel(std, roles, command, entry) {
315
+ const def = roles[entry.role];
316
+ const text = entry.labelWording
317
+ ? translateKey(std, ...entry.labelWording)
318
+ : def?.labelKey
319
+ ? translateKey(std, def.labelKey, def.labelFallback ?? entry.role)
320
+ : (def?.labelFallback ??
321
+ translateKey(std, command.labelKey, command.labelFallback ?? command.id));
322
+ return entry.labelPrefix ? `${entry.labelPrefix} — ${text}` : text;
323
+ }
324
+ /**
325
+ * The sub-title a row files itself under: its own {@link
326
+ * CommandLegendEntry.section} when it declares one, otherwise the command's
327
+ * `category` through the catalogue's own header key — the same word the
328
+ * artefact panel puts above the same group of commands, and therefore no new
329
+ * i18n key.
330
+ *
331
+ * A row with neither goes into an untitled bucket, which is what a framework
332
+ * with one flat notation wants.
333
+ */
334
+ function resolveSection(std, command, entry) {
335
+ if (entry.section) {
336
+ return {
337
+ key: entry.section[0],
338
+ title: translateKey(std, ...entry.section),
339
+ };
340
+ }
341
+ const category = command.category;
342
+ if (!category)
343
+ return { key: '' };
344
+ return {
345
+ key: `${CATALOGUE_CATEGORY_KEY_PREFIX}${category}`,
346
+ title: translateKey(std, `${CATALOGUE_CATEGORY_KEY_PREFIX}${category}`, humanizeCategory(category)),
347
+ };
348
+ }
349
+ /**
350
+ * The legend of a board, derived from the commands of the framework that owns
351
+ * it: every subscribed row whose role is present (directly or through a
352
+ * specialisation, unless the entry asks for {@link CommandLegendEntry.exact}),
353
+ * in command order, grouped under the section each declares — or under its
354
+ * command's category — with empty sections dropped.
355
+ *
356
+ * The SUB-TITLES come out in the order the framework first declares them,
357
+ * walking every command whether its row lights or not: a legend is a key to a
358
+ * notation, so the same framework reads in the same order on every board, and
359
+ * only the rows change.
360
+ *
361
+ * Rows are de-duplicated BY ROLE, first declaration winning: two commands draw
362
+ * a Wardley area (a rectangle and a polygon) and the notation has one "Area".
363
+ *
364
+ * When nothing is recognised the result is an EMPTY array, and the caller still
365
+ * draws the box: a legend of a board with nothing on it lists nothing, rather
366
+ * than inventing the full notation the user has not used.
367
+ */
368
+ export function legendFromCommands(std, owner, present) {
369
+ const roles = vocabularyOf(std);
370
+ const commands = getRegisteredCommands(std)
371
+ .filter(command => command.owner === owner && command.legend)
372
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
373
+ const drafts = [];
374
+ const seen = new Set();
375
+ const presentRoles = [...present];
376
+ for (const command of commands) {
377
+ for (const entry of entriesOf(command)) {
378
+ // The bucket is opened for EVERY declared row, lit or not, and emptied
379
+ // ones are dropped at the end. That is what makes the order of the
380
+ // sub-titles a property of the framework's declarations rather than of
381
+ // what happens to be on this board: opening it only when a row lit put
382
+ // BPMN's "Activities" after "Gateways" and "Flows" on a pool that had a
383
+ // user task but no plain task, and the same pool with a plain task read
384
+ // in a different order again.
385
+ const { key, title } = resolveSection(std, command, entry);
386
+ let draft = drafts.find(d => d.key === key);
387
+ if (!draft)
388
+ drafts.push((draft = { key, title, rows: [] }));
389
+ const lit = entry.exact
390
+ ? present.has(entry.role)
391
+ : presentRoles.some(role => roleIsA(role, entry.role, roles));
392
+ if (!lit || seen.has(entry.role))
393
+ continue;
394
+ seen.add(entry.role);
395
+ draft.rows.push({
396
+ ...entry.row,
397
+ label: resolveRowLabel(std, roles, command, entry),
398
+ });
399
+ }
400
+ }
401
+ return drafts
402
+ .filter(draft => draft.rows.length > 0)
403
+ .map(({ title, rows }) => ({ title, rows }));
404
+ }
405
+ /** The `legendBox` of the framework's BOARD command, if it declares one. */
406
+ function boardLegendBox(std, owner) {
407
+ return getRegisteredCommands(std).find(command => command.owner === owner && command.legendBox)?.legendBox;
408
+ }
409
+ /**
410
+ * Build the legend of what is drawn inside `board` and drop it bottom-left of
411
+ * it, grouped and selected. THE gesture the shared toolbar button runs.
412
+ *
413
+ * Returns the group id, or `undefined` when there is no surface to draw on.
414
+ */
415
+ export function createBoardLegend(std, board, owner) {
416
+ const gfx = std.get(GfxControllerIdentifier);
417
+ const surface = gfx.surface;
418
+ if (!surface)
419
+ return undefined;
420
+ const bound = Bound.deserialize(board.xywh);
421
+ const present = rolesInBound(gfx, bound);
422
+ const sections = legendFromCommands(std, owner, present);
423
+ const box = boardLegendBox(std, owner);
424
+ const layout = {
425
+ width: box?.width,
426
+ rowHeight: box?.rowHeight,
427
+ swatchWidth: box?.swatchWidth,
428
+ swatchHeight: box?.swatchHeight,
429
+ };
430
+ // Resolved ONCE, in front of the drawing: an extra states its height as a
431
+ // value, so the box can be measured before it is placed.
432
+ const extras = box?.extras?.({ std, board, present }) ?? [];
433
+ const title = translateKey(std, ...(box?.titleWording ?? BOARD_LEGEND_TITLE));
434
+ const { height } = measureLegend(sections, layout, extras);
435
+ std.store.captureSync();
436
+ const id = addLegend(surface, std, bound.x + INSET_X, bound.y + bound.h - INSET_BOTTOM - height, { title, sections, extras, ...layout });
437
+ gfx.selection.set({ elements: [id], editing: false });
438
+ return id;
439
+ }
@@ -82,12 +82,26 @@ export declare class ReadingProposalWidget extends EditorAnchoredPanel {
82
82
  */
83
83
  private _renderNature;
84
84
  /**
85
- * The typed edges touching the subject, grouped by side and named with the
86
- * FRAMEWORK's own two words (`ReadingRelationDef.sides`).
85
+ * The typed edges touching the subject, grouped by TABLE and then by side,
86
+ * and named with the FRAMEWORK's own two words
87
+ * (`ReadingRelationDef.sides`).
88
+ *
89
+ * One line per (table, side) rather than per side, since a profile may
90
+ * declare several tables (`ReadingProfile.alsoRelations`): a UML use case
91
+ * associated with an actor and including another use case has two sentences
92
+ * to say and they are not the same sentence. A framework declaring one table
93
+ * — which is every framework but UML — renders exactly what it did before.
87
94
  *
88
95
  * The `data-testid` keeps the shape it shipped with — `reading-consumers`,
89
96
  * `reading-suppliers` — because it names the SIDE, which is generic, and not
90
- * the wording, which is now the framework's.
97
+ * the wording, which is the framework's. `data-relation-role` is what tells
98
+ * two lines of the same side apart.
99
+ *
100
+ * The FAR end's label rides with the name, in parentheses: a UML association
101
+ * writes its multiplicity there (ADR 0020) and "Associated with: OrderLine
102
+ * (1..*)" is the sentence an author drew. The subject's OWN end label is read
103
+ * too ({@link ReadingRelation.ownEndLabel}) and deliberately not printed —
104
+ * one line about the other end, not a transcription of the connector.
91
105
  */
92
106
  private _renderRelations;
93
107
  /**