@formicoidea/labre-core 0.31.0 → 0.32.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 (483) hide show
  1. package/dist/_pkgs/affine-block-callout/callout-block.d.ts +6 -0
  2. package/dist/_pkgs/affine-block-callout/callout-block.js +25 -3
  3. package/dist/_pkgs/affine-block-callout/callout-keymap.js +59 -19
  4. package/dist/_pkgs/affine-block-callout/commands/callout-to-paragraph.d.ts +20 -0
  5. package/dist/_pkgs/affine-block-callout/commands/callout-to-paragraph.js +60 -0
  6. package/dist/_pkgs/affine-block-callout/commands/split-callout.d.ts +17 -0
  7. package/dist/_pkgs/affine-block-callout/commands/split-callout.js +42 -0
  8. package/dist/_pkgs/affine-block-callout/configs/slash-menu.js +9 -6
  9. package/dist/_pkgs/affine-block-callout/emoji-margin.d.ts +9 -0
  10. package/dist/_pkgs/affine-block-callout/emoji-margin.js +36 -0
  11. package/dist/_pkgs/affine-block-callout/emoji-menu.d.ts +5 -1
  12. package/dist/_pkgs/affine-block-callout/emoji-menu.js +5 -0
  13. package/dist/_pkgs/affine-block-code/code-block.d.ts +14 -0
  14. package/dist/_pkgs/affine-block-code/code-block.js +44 -6
  15. package/dist/_pkgs/affine-block-code/code-toolbar/components/code-toolbar.d.ts +2 -0
  16. package/dist/_pkgs/affine-block-code/code-toolbar/components/code-toolbar.js +23 -14
  17. package/dist/_pkgs/affine-block-code/code-toolbar/components/more-menu.d.ts +16 -0
  18. package/dist/_pkgs/affine-block-code/code-toolbar/components/more-menu.js +93 -0
  19. package/dist/_pkgs/affine-block-code/code-toolbar/config.js +52 -5
  20. package/dist/_pkgs/affine-block-code/effects.d.ts +2 -0
  21. package/dist/_pkgs/affine-block-code/effects.js +2 -0
  22. package/dist/_pkgs/affine-block-code/highlight/affine-code-unit.js +6 -1
  23. package/dist/_pkgs/affine-block-code/index.d.ts +1 -0
  24. package/dist/_pkgs/affine-block-code/index.js +1 -0
  25. package/dist/_pkgs/affine-block-code/line-numbers.d.ts +25 -0
  26. package/dist/_pkgs/affine-block-code/line-numbers.js +27 -0
  27. package/dist/_pkgs/affine-block-code/styles.js +31 -0
  28. package/dist/_pkgs/affine-block-data-view/data-view-block.js +1 -0
  29. package/dist/_pkgs/affine-block-database/components/title/index.js +4 -1
  30. package/dist/_pkgs/affine-block-database/configs/slash-menu.js +28 -1
  31. package/dist/_pkgs/affine-block-database/database-block.js +13 -0
  32. package/dist/_pkgs/affine-block-database/properties/paste-url.d.ts +24 -0
  33. package/dist/_pkgs/affine-block-database/properties/paste-url.js +98 -0
  34. package/dist/_pkgs/affine-block-database/properties/rich-text/cell-renderer.js +9 -19
  35. package/dist/_pkgs/affine-block-database/properties/title/text.js +20 -25
  36. package/dist/_pkgs/affine-block-database/views/index.js +1 -0
  37. package/dist/_pkgs/affine-block-edgeless-text/edgeless-text-block.js +10 -0
  38. package/dist/_pkgs/affine-block-edgeless-text/edgeless-toolbar/config.d.ts +3 -0
  39. package/dist/_pkgs/affine-block-edgeless-text/edgeless-toolbar/config.js +48 -1
  40. package/dist/_pkgs/affine-block-edgeless-text/edgeless-toolbar/font-size.d.ts +17 -0
  41. package/dist/_pkgs/affine-block-edgeless-text/edgeless-toolbar/font-size.js +24 -0
  42. package/dist/_pkgs/affine-block-embed-doc/common/doc-trashed.d.ts +9 -0
  43. package/dist/_pkgs/affine-block-embed-doc/common/doc-trashed.js +10 -0
  44. package/dist/_pkgs/affine-block-embed-doc/embed-linked-doc-block/embed-linked-doc-block.js +8 -4
  45. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/embed-synced-doc-block.d.ts +4 -0
  46. package/dist/_pkgs/affine-block-embed-doc/embed-synced-doc-block/embed-synced-doc-block.js +22 -3
  47. package/dist/_pkgs/affine-block-frame/frame-manager.d.ts +13 -0
  48. package/dist/_pkgs/affine-block-frame/frame-manager.js +32 -2
  49. package/dist/_pkgs/affine-block-latex/styles.js +6 -1
  50. package/dist/_pkgs/affine-block-note/note-edgeless-block.d.ts +9 -0
  51. package/dist/_pkgs/affine-block-note/note-edgeless-block.js +29 -1
  52. package/dist/_pkgs/affine-block-root/commands/pivot-commands.d.ts +37 -0
  53. package/dist/_pkgs/affine-block-root/commands/pivot-commands.js +168 -0
  54. package/dist/_pkgs/affine-block-root/commands/reading-commands.d.ts +23 -0
  55. package/dist/_pkgs/affine-block-root/commands/reading-commands.js +107 -0
  56. package/dist/_pkgs/affine-block-root/commands/tag-commands.d.ts +47 -0
  57. package/dist/_pkgs/affine-block-root/commands/tag-commands.js +159 -0
  58. package/dist/_pkgs/affine-block-root/configs/toolbar.d.ts +1 -0
  59. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/more.js +9 -2
  60. package/dist/_pkgs/affine-block-root/edgeless/configs/toolbar/render-linked-doc.js +1 -1
  61. package/dist/_pkgs/affine-block-root/edgeless/edgeless-keyboard.js +26 -1
  62. package/dist/_pkgs/affine-block-root/edgeless/edgeless-root-block.js +11 -2
  63. package/dist/_pkgs/affine-block-root/edgeless/edgeless-root-service.js +14 -0
  64. package/dist/_pkgs/affine-block-root/edgeless/index.d.ts +1 -0
  65. package/dist/_pkgs/affine-block-root/edgeless/index.js +1 -0
  66. package/dist/_pkgs/affine-block-root/edgeless/reserved-keys.d.ts +21 -0
  67. package/dist/_pkgs/affine-block-root/edgeless/reserved-keys.js +34 -0
  68. package/dist/_pkgs/affine-block-root/edgeless/utils/clone-utils.d.ts +7 -1
  69. package/dist/_pkgs/affine-block-root/edgeless/utils/clone-utils.js +11 -2
  70. package/dist/_pkgs/affine-block-root/index.d.ts +4 -1
  71. package/dist/_pkgs/affine-block-root/index.js +4 -1
  72. package/dist/_pkgs/affine-block-root/keyboard/commands.d.ts +2 -0
  73. package/dist/_pkgs/affine-block-root/keyboard/commands.js +147 -0
  74. package/dist/_pkgs/affine-block-root/view.js +13 -4
  75. package/dist/_pkgs/affine-block-surface/commands/reassociate-connectors.js +0 -1
  76. package/dist/_pkgs/affine-block-surface/effects.js +6 -0
  77. package/dist/_pkgs/affine-block-surface/element-model/index.d.ts +5 -2
  78. package/dist/_pkgs/affine-block-surface/element-model/index.js +4 -2
  79. package/dist/_pkgs/affine-block-surface/extensions/audit.d.ts +107 -0
  80. package/dist/_pkgs/affine-block-surface/extensions/audit.js +472 -0
  81. package/dist/_pkgs/affine-block-surface/extensions/crud-extension.d.ts +14 -0
  82. package/dist/_pkgs/affine-block-surface/extensions/crud-extension.js +27 -0
  83. package/dist/_pkgs/affine-block-surface/extensions/editor-anchored-panel.d.ts +133 -0
  84. package/dist/_pkgs/affine-block-surface/extensions/editor-anchored-panel.js +326 -0
  85. package/dist/_pkgs/affine-block-surface/extensions/index.d.ts +13 -0
  86. package/dist/_pkgs/affine-block-surface/extensions/index.js +13 -0
  87. package/dist/_pkgs/affine-block-surface/extensions/map-quality-widget.d.ts +146 -0
  88. package/dist/_pkgs/affine-block-surface/extensions/map-quality-widget.js +528 -0
  89. package/dist/_pkgs/affine-block-surface/extensions/map-quality.d.ts +109 -0
  90. package/dist/_pkgs/affine-block-surface/extensions/map-quality.js +84 -0
  91. package/dist/_pkgs/affine-block-surface/extensions/reading-toolbar.d.ts +15 -0
  92. package/dist/_pkgs/affine-block-surface/extensions/reading-toolbar.js +158 -0
  93. package/dist/_pkgs/affine-block-surface/extensions/reading-widget.d.ts +145 -0
  94. package/dist/_pkgs/affine-block-surface/extensions/reading-widget.js +590 -0
  95. package/dist/_pkgs/affine-block-surface/extensions/reading.d.ts +494 -0
  96. package/dist/_pkgs/affine-block-surface/extensions/reading.js +767 -0
  97. package/dist/_pkgs/affine-block-surface/extensions/spotlight.d.ts +45 -0
  98. package/dist/_pkgs/affine-block-surface/extensions/spotlight.js +177 -0
  99. package/dist/_pkgs/affine-block-surface/extensions/tags-toolbar.d.ts +10 -0
  100. package/dist/_pkgs/affine-block-surface/extensions/tags-toolbar.js +295 -0
  101. package/dist/_pkgs/affine-block-surface/extensions/validation-toolbar.d.ts +27 -0
  102. package/dist/_pkgs/affine-block-surface/extensions/validation-toolbar.js +361 -0
  103. package/dist/_pkgs/affine-block-surface/extensions/validation.d.ts +1340 -0
  104. package/dist/_pkgs/affine-block-surface/extensions/validation.js +3009 -0
  105. package/dist/_pkgs/affine-block-surface/extensions/violation-detail-widget.d.ts +286 -0
  106. package/dist/_pkgs/affine-block-surface/extensions/violation-detail-widget.js +907 -0
  107. package/dist/_pkgs/affine-block-surface/extensions/violation-timeline.d.ts +80 -0
  108. package/dist/_pkgs/affine-block-surface/extensions/violation-timeline.js +123 -0
  109. package/dist/_pkgs/affine-block-surface/framework-background/def.d.ts +321 -0
  110. package/dist/_pkgs/affine-block-surface/framework-background/def.js +81 -0
  111. package/dist/_pkgs/affine-block-surface/framework-background/facts.d.ts +188 -0
  112. package/dist/_pkgs/affine-block-surface/framework-background/facts.js +284 -0
  113. package/dist/_pkgs/affine-block-surface/framework-background/index.d.ts +5 -0
  114. package/dist/_pkgs/affine-block-surface/framework-background/index.js +5 -0
  115. package/dist/_pkgs/affine-block-surface/framework-background/interaction.d.ts +12 -0
  116. package/dist/_pkgs/affine-block-surface/framework-background/interaction.js +24 -0
  117. package/dist/_pkgs/affine-block-surface/framework-background/labels.d.ts +59 -0
  118. package/dist/_pkgs/affine-block-surface/framework-background/labels.js +117 -0
  119. package/dist/_pkgs/affine-block-surface/framework-background/renderer.d.ts +8 -0
  120. package/dist/_pkgs/affine-block-surface/framework-background/renderer.js +238 -0
  121. package/dist/_pkgs/affine-block-surface/index.d.ts +1 -0
  122. package/dist/_pkgs/affine-block-surface/index.js +1 -0
  123. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.d.ts +69 -3
  124. package/dist/_pkgs/affine-block-surface/renderer/canvas-renderer.js +333 -44
  125. package/dist/_pkgs/affine-block-surface/renderer/dom-renderer.js +18 -2
  126. package/dist/_pkgs/affine-block-surface/tool/default-tool.js +8 -0
  127. package/dist/_pkgs/affine-block-surface/utils/font.js +9 -3
  128. package/dist/_pkgs/affine-block-surface/view.d.ts +17 -0
  129. package/dist/_pkgs/affine-block-surface/view.js +57 -2
  130. package/dist/_pkgs/affine-block-table/selection-controller.d.ts +6 -0
  131. package/dist/_pkgs/affine-block-table/selection-controller.js +24 -1
  132. package/dist/_pkgs/affine-block-table/table-block.js +8 -2
  133. package/dist/_pkgs/affine-block-table/table-cell.js +5 -6
  134. package/dist/_pkgs/affine-block-table/table-keymap.d.ts +18 -0
  135. package/dist/_pkgs/affine-block-table/table-keymap.js +41 -0
  136. package/dist/_pkgs/affine-block-table/utils.d.ts +7 -0
  137. package/dist/_pkgs/affine-block-table/utils.js +12 -0
  138. package/dist/_pkgs/affine-block-table/view.js +2 -0
  139. package/dist/_pkgs/affine-components/caption/captioned-block-component.js +2 -1
  140. package/dist/_pkgs/affine-components/context-menu/button.d.ts +7 -0
  141. package/dist/_pkgs/affine-components/context-menu/button.js +7 -2
  142. package/dist/_pkgs/affine-components/context-menu/input.d.ts +4 -0
  143. package/dist/_pkgs/affine-components/context-menu/input.js +12 -0
  144. package/dist/_pkgs/affine-components/context-menu/menu-all.d.ts +2 -0
  145. package/dist/_pkgs/affine-components/context-menu/menu-renderer.js +8 -1
  146. package/dist/_pkgs/affine-components/context-menu/sub-menu.js +3 -0
  147. package/dist/_pkgs/affine-components/highlight-dropdown-menu/highlight-duotone-icon.js +8 -8
  148. package/dist/_pkgs/affine-components/highlight-dropdown-menu/text-duotone-icon.js +11 -11
  149. package/dist/_pkgs/affine-components/icon-picker/affine-icon-picker-panel.d.ts +28 -0
  150. package/dist/_pkgs/affine-components/icon-picker/affine-icon-picker-panel.js +231 -0
  151. package/dist/_pkgs/affine-components/icon-picker/emoji-data.d.ts +40 -0
  152. package/dist/_pkgs/affine-components/icon-picker/emoji-data.js +99 -0
  153. package/dist/_pkgs/affine-components/icon-picker/emoji-picker-panel.d.ts +32 -0
  154. package/dist/_pkgs/affine-components/icon-picker/emoji-picker-panel.js +276 -0
  155. package/dist/_pkgs/affine-components/icon-picker/icon-data.d.ts +34 -0
  156. package/dist/_pkgs/affine-components/icon-picker/icon-data.js +83 -0
  157. package/dist/_pkgs/affine-components/icon-picker/icon-picker.d.ts +39 -0
  158. package/dist/_pkgs/affine-components/icon-picker/icon-picker.js +233 -0
  159. package/dist/_pkgs/affine-components/icon-picker/index.d.ts +8 -0
  160. package/dist/_pkgs/affine-components/icon-picker/index.js +15 -0
  161. package/dist/_pkgs/affine-components/icon-picker/recent-store.d.ts +8 -0
  162. package/dist/_pkgs/affine-components/icon-picker/recent-store.js +41 -0
  163. package/dist/_pkgs/affine-components/icon-picker/styles.d.ts +5 -0
  164. package/dist/_pkgs/affine-components/icon-picker/styles.js +166 -0
  165. package/dist/_pkgs/affine-components/icon-picker/types.d.ts +22 -0
  166. package/dist/_pkgs/affine-components/icon-picker/types.js +10 -0
  167. package/dist/_pkgs/affine-components/icons/text.js +426 -426
  168. package/dist/_pkgs/affine-components/open-doc-dropdown-menu/dropdown-menu.d.ts +1 -0
  169. package/dist/_pkgs/affine-components/portal/portal.d.ts +1 -1
  170. package/dist/_pkgs/affine-components/toolbar/menu-button.js +1 -0
  171. package/dist/_pkgs/affine-components/toolbar/toolbar.js +11 -10
  172. package/dist/_pkgs/affine-foundation/view.js +11 -2
  173. package/dist/_pkgs/affine-gfx-brush/index.d.ts +1 -0
  174. package/dist/_pkgs/affine-gfx-brush/index.js +1 -0
  175. package/dist/_pkgs/affine-gfx-brush/renderer/dom/brush.d.ts +1 -0
  176. package/dist/_pkgs/affine-gfx-brush/renderer/dom/brush.js +11 -0
  177. package/dist/_pkgs/affine-gfx-brush/renderer/dom/highlighter.d.ts +1 -0
  178. package/dist/_pkgs/affine-gfx-brush/renderer/dom/highlighter.js +11 -0
  179. package/dist/_pkgs/affine-gfx-brush/renderer/dom/index.d.ts +2 -0
  180. package/dist/_pkgs/affine-gfx-brush/renderer/dom/index.js +2 -0
  181. package/dist/_pkgs/affine-gfx-brush/renderer/dom/shared.d.ts +14 -0
  182. package/dist/_pkgs/affine-gfx-brush/renderer/dom/shared.js +54 -0
  183. package/dist/_pkgs/affine-gfx-brush/renderer/index.d.ts +1 -0
  184. package/dist/_pkgs/affine-gfx-brush/renderer/index.js +4 -0
  185. package/dist/_pkgs/affine-gfx-brush/view.d.ts +14 -0
  186. package/dist/_pkgs/affine-gfx-brush/view.js +29 -3
  187. package/dist/_pkgs/affine-gfx-connector/connector-manager.js +32 -6
  188. package/dist/_pkgs/affine-gfx-connector/connector-tool.d.ts +7 -0
  189. package/dist/_pkgs/affine-gfx-connector/connector-tool.js +2 -1
  190. package/dist/_pkgs/affine-gfx-connector/direction/direction-reveal.d.ts +123 -0
  191. package/dist/_pkgs/affine-gfx-connector/direction/direction-reveal.js +281 -0
  192. package/dist/_pkgs/affine-gfx-connector/direction/direction-widget.d.ts +65 -0
  193. package/dist/_pkgs/affine-gfx-connector/direction/direction-widget.js +395 -0
  194. package/dist/_pkgs/affine-gfx-connector/direction/index.d.ts +4 -0
  195. package/dist/_pkgs/affine-gfx-connector/direction/index.js +4 -0
  196. package/dist/_pkgs/affine-gfx-connector/direction/invert-direction.d.ts +72 -0
  197. package/dist/_pkgs/affine-gfx-connector/direction/invert-direction.js +187 -0
  198. package/dist/_pkgs/affine-gfx-connector/direction/typed-edge.d.ts +69 -0
  199. package/dist/_pkgs/affine-gfx-connector/direction/typed-edge.js +66 -0
  200. package/dist/_pkgs/affine-gfx-connector/effects.d.ts +2 -0
  201. package/dist/_pkgs/affine-gfx-connector/effects.js +2 -0
  202. package/dist/_pkgs/affine-gfx-connector/element-renderer/connector-dom/index.js +78 -41
  203. package/dist/_pkgs/affine-gfx-connector/index.d.ts +1 -0
  204. package/dist/_pkgs/affine-gfx-connector/index.js +1 -0
  205. package/dist/_pkgs/affine-gfx-connector/toolbar/config.d.ts +50 -0
  206. package/dist/_pkgs/affine-gfx-connector/toolbar/config.js +67 -1
  207. package/dist/_pkgs/affine-gfx-connector/view.js +16 -0
  208. package/dist/_pkgs/affine-gfx-group/command/group-api.js +150 -62
  209. package/dist/_pkgs/affine-gfx-group/text/edgeless-group-title-editor.js +4 -1
  210. package/dist/_pkgs/affine-gfx-pointer/tools/pan-tool.d.ts +16 -0
  211. package/dist/_pkgs/affine-gfx-pointer/tools/pan-tool.js +102 -32
  212. package/dist/_pkgs/affine-gfx-shape/commands.d.ts +2 -0
  213. package/dist/_pkgs/affine-gfx-shape/commands.js +33 -0
  214. package/dist/_pkgs/affine-gfx-shape/element-renderer/shape-dom/index.js +48 -19
  215. package/dist/_pkgs/affine-gfx-shape/index.d.ts +1 -1
  216. package/dist/_pkgs/affine-gfx-shape/index.js +1 -1
  217. package/dist/_pkgs/affine-gfx-shape/text/edgeless-shape-text-editor.d.ts +26 -0
  218. package/dist/_pkgs/affine-gfx-shape/text/edgeless-shape-text-editor.js +124 -13
  219. package/dist/_pkgs/affine-gfx-shape/view.js +3 -3
  220. package/dist/_pkgs/affine-gfx-template/toolbar/template-tool-button.d.ts +2 -0
  221. package/dist/_pkgs/affine-gfx-template/toolbar/template-tool-button.js +18 -1
  222. package/dist/_pkgs/affine-gfx-text/edgeless-text-editor.js +9 -5
  223. package/dist/_pkgs/affine-inline-latex/command.js +46 -26
  224. package/dist/_pkgs/affine-inline-latex/latex-node/latex-node.d.ts +10 -0
  225. package/dist/_pkgs/affine-inline-latex/latex-node/latex-node.js +23 -0
  226. package/dist/_pkgs/affine-inline-link/link-node/affine-link.js +4 -1
  227. package/dist/_pkgs/affine-inline-preset/adapters/html/html-inline.d.ts +3 -0
  228. package/dist/_pkgs/affine-inline-preset/adapters/html/html-inline.js +29 -1
  229. package/dist/_pkgs/affine-model/blocks/code/code-model.d.ts +1 -0
  230. package/dist/_pkgs/affine-model/blocks/code/code-model.js +1 -0
  231. package/dist/_pkgs/affine-model/elements/connector/connector.js +24 -0
  232. package/dist/_pkgs/affine-model/elements/edgy/board.d.ts +32 -0
  233. package/dist/_pkgs/affine-model/elements/edgy/board.js +112 -0
  234. package/dist/_pkgs/affine-model/elements/edgy/facets.d.ts +12 -0
  235. package/dist/_pkgs/affine-model/elements/edgy/facets.js +25 -1
  236. package/dist/_pkgs/affine-model/elements/edgy/index.d.ts +1 -0
  237. package/dist/_pkgs/affine-model/elements/edgy/index.js +1 -0
  238. package/dist/_pkgs/affine-model/elements/framework-background/background.d.ts +42 -0
  239. package/dist/_pkgs/affine-model/elements/framework-background/background.js +42 -0
  240. package/dist/_pkgs/affine-model/elements/framework-background/index.d.ts +1 -0
  241. package/dist/_pkgs/affine-model/elements/framework-background/index.js +1 -0
  242. package/dist/_pkgs/affine-model/elements/index.d.ts +3 -1
  243. package/dist/_pkgs/affine-model/elements/index.js +1 -0
  244. package/dist/_pkgs/affine-model/elements/mindmap/mindmap.js +8 -3
  245. package/dist/_pkgs/affine-model/elements/mindmap/style.d.ts +10 -0
  246. package/dist/_pkgs/affine-model/elements/mindmap/style.js +8 -0
  247. package/dist/_pkgs/affine-model/elements/wardley/wardley.d.ts +21 -29
  248. package/dist/_pkgs/affine-model/elements/wardley/wardley.js +41 -48
  249. package/dist/_pkgs/affine-shared/adapters/markdown/delta-converter.d.ts +6 -1
  250. package/dist/_pkgs/affine-shared/adapters/markdown/delta-converter.js +160 -2
  251. package/dist/_pkgs/affine-shared/adapters/markdown/markdown.js +5 -1
  252. package/dist/_pkgs/affine-shared/adapters/types/delta-converter.d.ts +1 -0
  253. package/dist/_pkgs/affine-shared/adapters/utils/color.d.ts +24 -0
  254. package/dist/_pkgs/affine-shared/adapters/utils/color.js +280 -0
  255. package/dist/_pkgs/affine-shared/adapters/utils/index.d.ts +1 -0
  256. package/dist/_pkgs/affine-shared/adapters/utils/index.js +1 -0
  257. package/dist/_pkgs/affine-shared/commands/block-crud/get-selected-blocks.js +10 -2
  258. package/dist/_pkgs/affine-shared/services/audit-service.d.ts +300 -0
  259. package/dist/_pkgs/affine-shared/services/audit-service.js +228 -0
  260. package/dist/_pkgs/affine-shared/services/edit-props-store.d.ts +7 -0
  261. package/dist/_pkgs/affine-shared/services/edit-props-store.js +11 -0
  262. package/dist/_pkgs/affine-shared/services/font-loader/font-loader-service.d.ts +26 -1
  263. package/dist/_pkgs/affine-shared/services/font-loader/font-loader-service.js +124 -7
  264. package/dist/_pkgs/affine-shared/services/index.d.ts +5 -0
  265. package/dist/_pkgs/affine-shared/services/index.js +5 -0
  266. package/dist/_pkgs/affine-shared/services/pivot-materiality-publisher.d.ts +129 -0
  267. package/dist/_pkgs/affine-shared/services/pivot-materiality-publisher.js +311 -0
  268. package/dist/_pkgs/affine-shared/services/pivot-properties-service.d.ts +217 -0
  269. package/dist/_pkgs/affine-shared/services/pivot-properties-service.js +114 -0
  270. package/dist/_pkgs/affine-shared/services/telemetry-service/code-block.d.ts +1 -1
  271. package/dist/_pkgs/affine-shared/services/telemetry-service/lifecycle.d.ts +182 -0
  272. package/dist/_pkgs/affine-shared/services/telemetry-service/telemetry-service.d.ts +2 -2
  273. package/dist/_pkgs/affine-shared/services/telemetry-service/types.d.ts +6 -1
  274. package/dist/_pkgs/affine-shared/services/toolbar-service/action.d.ts +10 -0
  275. package/dist/_pkgs/affine-shared/services/toolbar-service/index.d.ts +1 -0
  276. package/dist/_pkgs/affine-shared/services/toolbar-service/index.js +1 -0
  277. package/dist/_pkgs/affine-shared/services/toolbar-service/layout.d.ts +134 -0
  278. package/dist/_pkgs/affine-shared/services/toolbar-service/layout.js +146 -0
  279. package/dist/_pkgs/affine-shared/services/translation-service/index.d.ts +68 -0
  280. package/dist/_pkgs/affine-shared/services/translation-service/index.js +39 -0
  281. package/dist/_pkgs/affine-shared/services/universe-tag-defs-service.d.ts +155 -0
  282. package/dist/_pkgs/affine-shared/services/universe-tag-defs-service.js +254 -0
  283. package/dist/_pkgs/affine-shared/utils/dom/point-to-range.d.ts +4 -0
  284. package/dist/_pkgs/affine-shared/utils/dom/point-to-range.js +58 -2
  285. package/dist/_pkgs/affine-shared/utils/dom/viewport.d.ts +60 -0
  286. package/dist/_pkgs/affine-shared/utils/dom/viewport.js +58 -0
  287. package/dist/_pkgs/affine-shared/utils/print-to-pdf.d.ts +21 -0
  288. package/dist/_pkgs/affine-shared/utils/print-to-pdf.js +166 -32
  289. package/dist/_pkgs/affine-shared/utils/zod-schema.d.ts +7 -0
  290. package/dist/_pkgs/affine-shared/utils/zod-schema.js +27 -1
  291. package/dist/_pkgs/affine-widget-drag-handle/watchers/edgeless-watcher.d.ts +19 -10
  292. package/dist/_pkgs/affine-widget-drag-handle/watchers/edgeless-watcher.js +122 -23
  293. package/dist/_pkgs/affine-widget-drag-handle/watchers/pointer-event-watcher.d.ts +2 -0
  294. package/dist/_pkgs/affine-widget-drag-handle/watchers/pointer-event-watcher.js +25 -4
  295. package/dist/_pkgs/affine-widget-edgeless-selected-rect/auto-complete-panel.js +10 -5
  296. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-auto-complete.d.ts +15 -1
  297. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-auto-complete.js +37 -32
  298. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-element-link.js +5 -4
  299. package/dist/_pkgs/affine-widget-edgeless-selected-rect/edgeless-selected-rect.js +19 -9
  300. package/dist/_pkgs/affine-widget-edgeless-selected-rect/utils.d.ts +16 -2
  301. package/dist/_pkgs/affine-widget-edgeless-selected-rect/utils.js +19 -2
  302. package/dist/_pkgs/affine-widget-edgeless-toolbar/index.d.ts +1 -0
  303. package/dist/_pkgs/affine-widget-edgeless-toolbar/index.js +1 -0
  304. package/dist/_pkgs/affine-widget-edgeless-toolbar/menu/command-menu.d.ts +38 -0
  305. package/dist/_pkgs/affine-widget-edgeless-toolbar/menu/command-menu.js +99 -0
  306. package/dist/_pkgs/affine-widget-frame-title/edgeless-frame-title-editor.js +4 -1
  307. package/dist/_pkgs/affine-widget-keyboard-toolbar/icons.js +98 -98
  308. package/dist/_pkgs/affine-widget-linked-doc/linked-doc-popover.js +2 -15
  309. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.d.ts +10 -1
  310. package/dist/_pkgs/affine-widget-linked-doc/transformers/markdown.js +133 -6
  311. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.d.ts +10 -0
  312. package/dist/_pkgs/affine-widget-linked-doc/transformers/utils.js +120 -3
  313. package/dist/_pkgs/affine-widget-linked-doc/utils.d.ts +12 -0
  314. package/dist/_pkgs/affine-widget-linked-doc/utils.js +33 -0
  315. package/dist/_pkgs/affine-widget-remote-selection/edgeless/index.js +6 -4
  316. package/dist/_pkgs/affine-widget-slash-menu/slash-menu-popover.js +8 -4
  317. package/dist/_pkgs/affine-widget-slash-menu/utils.d.ts +9 -0
  318. package/dist/_pkgs/affine-widget-slash-menu/utils.js +24 -0
  319. package/dist/_pkgs/affine-widget-toolbar/toolbar.d.ts +6 -0
  320. package/dist/_pkgs/affine-widget-toolbar/toolbar.js +34 -6
  321. package/dist/_pkgs/affine-widget-toolbar/utils.d.ts +146 -3
  322. package/dist/_pkgs/affine-widget-toolbar/utils.js +738 -56
  323. package/dist/_pkgs/data-view/core/common/property-menu.js +3 -16
  324. package/dist/_pkgs/data-view/core/component/tags/multi-tag-select.d.ts +4 -0
  325. package/dist/_pkgs/data-view/core/component/tags/multi-tag-select.js +28 -1
  326. package/dist/_pkgs/data-view/core/data-view.d.ts +3 -1
  327. package/dist/_pkgs/data-view/core/data-view.js +25 -3
  328. package/dist/_pkgs/data-view/core/detail/field.js +2 -2
  329. package/dist/_pkgs/data-view/core/detail/selection.d.ts +4 -2
  330. package/dist/_pkgs/data-view/core/detail/selection.js +23 -7
  331. package/dist/_pkgs/data-view/core/effect.js +2 -0
  332. package/dist/_pkgs/data-view/core/filter/eval.js +36 -23
  333. package/dist/_pkgs/data-view/core/filter/filter-fn/matcher.d.ts +7 -0
  334. package/dist/_pkgs/data-view/core/filter/filter-fn/matcher.js +12 -0
  335. package/dist/_pkgs/data-view/core/group-by/compare-date-keys.d.ts +11 -0
  336. package/dist/_pkgs/data-view/core/group-by/compare-date-keys.js +55 -0
  337. package/dist/_pkgs/data-view/core/group-by/define.d.ts +6 -0
  338. package/dist/_pkgs/data-view/core/group-by/define.js +114 -0
  339. package/dist/_pkgs/data-view/core/group-by/matcher.d.ts +7 -0
  340. package/dist/_pkgs/data-view/core/group-by/matcher.js +13 -0
  341. package/dist/_pkgs/data-view/core/group-by/renderer/boolean-group.js +11 -11
  342. package/dist/_pkgs/data-view/core/group-by/renderer/date-group.d.ts +9 -0
  343. package/dist/_pkgs/data-view/core/group-by/renderer/date-group.js +97 -0
  344. package/dist/_pkgs/data-view/core/group-by/setting.js +86 -7
  345. package/dist/_pkgs/data-view/core/group-by/trait.d.ts +34 -0
  346. package/dist/_pkgs/data-view/core/group-by/trait.js +106 -10
  347. package/dist/_pkgs/data-view/core/utils/menu-title.js +17 -17
  348. package/dist/_pkgs/data-view/property-presets/multi-select/cell-renderer.js +3 -1
  349. package/dist/_pkgs/data-view/property-presets/number/define.js +2 -1
  350. package/dist/_pkgs/data-view/property-presets/select/cell-renderer.js +3 -1
  351. package/dist/_pkgs/data-view/view-presets/calendar/calendar-view-manager.d.ts +70 -0
  352. package/dist/_pkgs/data-view/view-presets/calendar/calendar-view-manager.js +449 -0
  353. package/dist/_pkgs/data-view/view-presets/calendar/define.d.ts +10 -0
  354. package/dist/_pkgs/data-view/view-presets/calendar/define.js +27 -0
  355. package/dist/_pkgs/data-view/view-presets/calendar/effect.d.ts +1 -0
  356. package/dist/_pkgs/data-view/view-presets/calendar/effect.js +4 -0
  357. package/dist/_pkgs/data-view/view-presets/calendar/index.d.ts +6 -0
  358. package/dist/_pkgs/data-view/view-presets/calendar/index.js +6 -0
  359. package/dist/_pkgs/data-view/view-presets/calendar/layout.d.ts +37 -0
  360. package/dist/_pkgs/data-view/view-presets/calendar/layout.js +138 -0
  361. package/dist/_pkgs/data-view/view-presets/calendar/pc/actions.d.ts +7 -0
  362. package/dist/_pkgs/data-view/view-presets/calendar/pc/actions.js +68 -0
  363. package/dist/_pkgs/data-view/view-presets/calendar/pc/dnd.d.ts +32 -0
  364. package/dist/_pkgs/data-view/view-presets/calendar/pc/dnd.js +159 -0
  365. package/dist/_pkgs/data-view/view-presets/calendar/pc/effect.d.ts +1 -0
  366. package/dist/_pkgs/data-view/view-presets/calendar/pc/effect.js +7 -0
  367. package/dist/_pkgs/data-view/view-presets/calendar/pc/hit-test.d.ts +1 -0
  368. package/dist/_pkgs/data-view/view-presets/calendar/pc/hit-test.js +28 -0
  369. package/dist/_pkgs/data-view/view-presets/calendar/pc/styles.d.ts +1 -0
  370. package/dist/_pkgs/data-view/view-presets/calendar/pc/styles.js +707 -0
  371. package/dist/_pkgs/data-view/view-presets/calendar/pc/view.d.ts +89 -0
  372. package/dist/_pkgs/data-view/view-presets/calendar/pc/view.js +837 -0
  373. package/dist/_pkgs/data-view/view-presets/calendar/renderer.d.ts +2 -0
  374. package/dist/_pkgs/data-view/view-presets/calendar/renderer.js +8 -0
  375. package/dist/_pkgs/data-view/view-presets/calendar/source.d.ts +8 -0
  376. package/dist/_pkgs/data-view/view-presets/calendar/source.js +3 -0
  377. package/dist/_pkgs/data-view/view-presets/calendar/types.d.ts +79 -0
  378. package/dist/_pkgs/data-view/view-presets/calendar/types.js +1 -0
  379. package/dist/_pkgs/data-view/view-presets/convert.js +28 -0
  380. package/dist/_pkgs/data-view/view-presets/effect.js +2 -0
  381. package/dist/_pkgs/data-view/view-presets/index.d.ts +2 -0
  382. package/dist/_pkgs/data-view/view-presets/index.js +3 -0
  383. package/dist/_pkgs/data-view/view-presets/kanban/kanban-view-manager.js +3 -1
  384. package/dist/_pkgs/data-view/view-presets/kanban/mobile/cell.js +2 -2
  385. package/dist/_pkgs/data-view/view-presets/table/collapsed-state.d.ts +18 -0
  386. package/dist/_pkgs/data-view/view-presets/table/collapsed-state.js +36 -0
  387. package/dist/_pkgs/data-view/view-presets/table/mobile/cell.js +2 -2
  388. package/dist/_pkgs/data-view/view-presets/table/mobile/group.d.ts +11 -0
  389. package/dist/_pkgs/data-view/view-presets/table/mobile/group.js +87 -4
  390. package/dist/_pkgs/data-view/view-presets/table/pc/cell.d.ts +3 -0
  391. package/dist/_pkgs/data-view/view-presets/table/pc/cell.js +11 -2
  392. package/dist/_pkgs/data-view/view-presets/table/pc/controller/hotkeys.d.ts +1 -0
  393. package/dist/_pkgs/data-view/view-presets/table/pc/controller/hotkeys.js +39 -33
  394. package/dist/_pkgs/data-view/view-presets/table/pc/group.d.ts +10 -0
  395. package/dist/_pkgs/data-view/view-presets/table/pc/group.js +84 -5
  396. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/controller/hotkeys.d.ts +1 -0
  397. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/controller/hotkeys.js +39 -33
  398. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/group/top/group-title.js +18 -18
  399. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/row/cell.d.ts +3 -0
  400. package/dist/_pkgs/data-view/view-presets/table/pc-virtual/row/cell.js +8 -0
  401. package/dist/_pkgs/data-view/view-presets/table/table-view-manager.js +3 -1
  402. package/dist/_pkgs/data-view/view-presets/table/utils.d.ts +3 -0
  403. package/dist/_pkgs/data-view/view-presets/table/utils.js +16 -1
  404. package/dist/_pkgs/data-view/widget-presets/tools/presets/view-options/view-options.js +3 -1
  405. package/dist/_pkgs/global/gfx/curve.js +4 -3
  406. package/dist/_pkgs/std/clipboard/clipboard.js +13 -3
  407. package/dist/_pkgs/std/clipboard/utils.d.ts +5 -0
  408. package/dist/_pkgs/std/clipboard/utils.js +7 -0
  409. package/dist/_pkgs/std/event/dispatcher.js +27 -1
  410. package/dist/_pkgs/std/event/keymap.js +16 -6
  411. package/dist/_pkgs/std/extension/command-registry.d.ts +321 -0
  412. package/dist/_pkgs/std/extension/command-registry.js +233 -0
  413. package/dist/_pkgs/std/extension/dnd/index.d.ts +1 -1
  414. package/dist/_pkgs/std/extension/index.d.ts +2 -0
  415. package/dist/_pkgs/std/extension/index.js +2 -0
  416. package/dist/_pkgs/std/extension/translation-manifest.d.ts +49 -0
  417. package/dist/_pkgs/std/extension/translation-manifest.js +71 -0
  418. package/dist/_pkgs/std/gfx/index.d.ts +7 -3
  419. package/dist/_pkgs/std/gfx/index.js +6 -2
  420. package/dist/_pkgs/std/gfx/interactivity/gfx-view-event-handler.js +6 -1
  421. package/dist/_pkgs/std/gfx/interactivity/manager.d.ts +8 -0
  422. package/dist/_pkgs/std/gfx/interactivity/manager.js +18 -0
  423. package/dist/_pkgs/std/gfx/layer.d.ts +1 -1
  424. package/dist/_pkgs/std/gfx/layer.js +1 -2
  425. package/dist/_pkgs/std/gfx/model/surface/decorators/field.js +9 -0
  426. package/dist/_pkgs/std/gfx/model/surface/decorators/index.d.ts +2 -2
  427. package/dist/_pkgs/std/gfx/model/surface/decorators/index.js +2 -2
  428. package/dist/_pkgs/std/gfx/model/surface/decorators/local.d.ts +14 -0
  429. package/dist/_pkgs/std/gfx/model/surface/decorators/local.js +22 -0
  430. package/dist/_pkgs/std/gfx/model/surface/decorators/observer.d.ts +2 -0
  431. package/dist/_pkgs/std/gfx/model/surface/decorators/observer.js +40 -1
  432. package/dist/_pkgs/std/gfx/model/surface/element-model.d.ts +282 -0
  433. package/dist/_pkgs/std/gfx/model/surface/element-model.js +436 -25
  434. package/dist/_pkgs/std/gfx/model/surface/pivot.d.ts +48 -0
  435. package/dist/_pkgs/std/gfx/model/surface/pivot.js +34 -0
  436. package/dist/_pkgs/std/gfx/model/surface/role.d.ts +153 -0
  437. package/dist/_pkgs/std/gfx/model/surface/role.js +115 -0
  438. package/dist/_pkgs/std/gfx/model/surface/surface-model.d.ts +70 -0
  439. package/dist/_pkgs/std/gfx/model/surface/surface-model.js +304 -17
  440. package/dist/_pkgs/std/gfx/model/surface/tags.d.ts +77 -0
  441. package/dist/_pkgs/std/gfx/model/surface/tags.js +246 -0
  442. package/dist/_pkgs/std/gfx/perf.d.ts +10 -0
  443. package/dist/_pkgs/std/gfx/perf.js +30 -0
  444. package/dist/_pkgs/std/gfx/tool/tool-controller.d.ts +13 -0
  445. package/dist/_pkgs/std/gfx/tool/tool-controller.js +26 -0
  446. package/dist/_pkgs/std/gfx/viewport.d.ts +12 -0
  447. package/dist/_pkgs/std/gfx/viewport.js +19 -1
  448. package/dist/_pkgs/std/inline/services/event.d.ts +2 -0
  449. package/dist/_pkgs/std/inline/services/event.js +96 -21
  450. package/dist/_pkgs/std/utils/layer.js +2 -6
  451. package/dist/_pkgs/std/utils/tree.d.ts +14 -0
  452. package/dist/_pkgs/std/utils/tree.js +59 -8
  453. package/dist/_pkgs/std/view/element/gfx-block-component.js +23 -13
  454. package/dist/_pkgs/store/extension/workspace/workspace-meta.d.ts +10 -0
  455. package/dist/_pkgs/store/model/block/flat-sync-controller.js +2 -12
  456. package/dist/_pkgs/store/model/store/crud.js +7 -4
  457. package/dist/_pkgs/store/reactive/flat-native-y/index.d.ts +1 -1
  458. package/dist/_pkgs/store/reactive/flat-native-y/index.js +24 -2
  459. package/dist/commands.d.ts +58 -0
  460. package/dist/commands.js +66 -0
  461. package/dist/extensions/command-telemetry.d.ts +21 -0
  462. package/dist/extensions/command-telemetry.js +89 -0
  463. package/dist/extensions/store.d.ts +9 -4
  464. package/dist/extensions/store.js +27 -24
  465. package/dist/extensions/view.d.ts +9 -4
  466. package/dist/extensions/view.js +23 -8
  467. package/dist/flags.d.ts +98 -21
  468. package/dist/flags.js +103 -22
  469. package/dist/frameworks.d.ts +45 -0
  470. package/dist/frameworks.js +154 -0
  471. package/dist/schemas.d.ts +8 -5
  472. package/dist/schemas.js +28 -32
  473. package/dist/shortcuts.d.ts +16 -16
  474. package/dist/shortcuts.js +8 -36
  475. package/dist/translations.d.ts +56 -0
  476. package/dist/translations.js +112 -0
  477. package/package.json +16 -4
  478. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.d.ts +0 -8
  479. package/dist/_pkgs/affine-block-root/keyboard/shortcuts.js +0 -48
  480. package/dist/_pkgs/affine-gfx-shape/shortcuts.d.ts +0 -6
  481. package/dist/_pkgs/affine-gfx-shape/shortcuts.js +0 -28
  482. package/dist/_pkgs/data-view/core/utils/cache.d.ts +0 -5
  483. package/dist/_pkgs/data-view/core/utils/cache.js +0 -28
@@ -1,6 +1,7 @@
1
1
  import { renderToolbarSeparator, } from '../affine-components/toolbar/index.js';
2
- import { ActionPlacement, } from '../affine-shared/services/index.js';
2
+ import { ActionPlacement, planToolbarLayout, TOOLBAR_SETTLE_DELAY, toolbarDegradationSteps, toolbarRoomChanged, } from '../affine-shared/services/index.js';
3
3
  import { nextTick } from '../global/utils/index.js';
4
+ import { GfxControllerIdentifier } from '../std/gfx/index.js';
4
5
  import { MoreVerticalIcon } from '@blocksuite/icons/lit';
5
6
  import { autoUpdate, computePosition, flip, hide, inline, limitShift, offset, shift, size, } from '@floating-ui/dom';
6
7
  import { html, render } from 'lit';
@@ -21,7 +22,13 @@ export const sideMap = new Map([
21
22
  // has only one shape element
22
23
  ['affine:surface:shape', { top: 26, bottom: -26 }],
23
24
  ]);
24
- export function autoUpdatePosition(signal, toolbar, referenceElement, flavour, placement, sideOptions, options = { elementResize: false, animationFrame: true }) {
25
+ export function autoUpdatePosition(signal, toolbar, referenceElement, flavour, placement, sideOptions, options = { elementResize: false },
26
+ /**
27
+ * Called whenever the room the row has just changed. This is the only place
28
+ * that knows it: `size` is what writes the cap, and it re-runs on every
29
+ * scroll, resize and flip.
30
+ */
31
+ onAvailableWidth) {
25
32
  const isInline = flavour === 'affine:note';
26
33
  const hasSurfaceScope = flavour.includes('surface');
27
34
  const isInner = placement === 'inner';
@@ -49,9 +56,14 @@ export function autoUpdatePosition(signal, toolbar, referenceElement, flavour, p
49
56
  padding: 10,
50
57
  apply: ({ elements, availableWidth }) => {
51
58
  elements.floating.style.width = 'fit-content';
52
- // ponytail: cap to viewport so the toolbar scrolls instead of
53
- // overflowing off-screen on mobile; drop into "⋮" if scroll feels off
54
- elements.floating.style.maxWidth = `${availableWidth}px`;
59
+ // The room the row has. The toolbar never wraps and never
60
+ // scrolls: past this width its entries give way, in the order
61
+ // they declared, into the "⋮" menu — see `ToolbarFitter`.
62
+ const capped = `${availableWidth}px`;
63
+ if (elements.floating.style.maxWidth !== capped) {
64
+ elements.floating.style.maxWidth = capped;
65
+ onAvailableWidth?.();
66
+ }
55
67
  },
56
68
  }),
57
69
  isInline ? inline() : undefined,
@@ -65,7 +77,16 @@ export function autoUpdatePosition(signal, toolbar, referenceElement, flavour, p
65
77
  crossAxis: state.placement.includes('bottom'),
66
78
  limiter: limitShift(),
67
79
  })),
68
- flip({ padding: 10 }),
80
+ // `flip` answers ONE question here — above or below — so both of its
81
+ // sideways ideas are off. Left on (their default), they teleport the
82
+ // row: on a reference far wider than the screen, every alignment
83
+ // overflows somewhere, `bestFit` picks between `-start` and `-end`
84
+ // by a margin of a few pixels, and under a zoom that verdict changes
85
+ // from one frame to the next — the row jumps between the LEFT clamp
86
+ // and the RIGHT clamp of the very same geometry (PO recette of
87
+ // 25/08/2026, third video: the map background's row). Horizontal
88
+ // placement belongs to `shift`, which slides — it never teleports.
89
+ flip({ padding: 10, crossAxis: false, flipAlignment: false }),
69
90
  hide(),
70
91
  ],
71
92
  };
@@ -84,6 +105,10 @@ export function autoUpdatePosition(signal, toolbar, referenceElement, flavour, p
84
105
  if (signal.aborted)
85
106
  return;
86
107
  const result = await computePosition(referenceElement, toolbar, config);
108
+ // The gesture may have aborted this loop while the position was being
109
+ // computed. A dying loop that still writes is a second anchor on screen.
110
+ if (signal.aborted)
111
+ return;
87
112
  const { x, middlewareData, placement: currentPlacement } = result;
88
113
  const y = result.y -
89
114
  (currentPlacement.includes('top') ? 0 : offsetTop + offsetBottom);
@@ -105,7 +130,10 @@ export function autoUpdatePosition(signal, toolbar, referenceElement, flavour, p
105
130
  };
106
131
  return autoUpdate(referenceElement, toolbar, () => {
107
132
  update().catch(console.error);
108
- }, options);
133
+ },
134
+ // Only a canvas anchor moves without a scroll or a resize to announce it,
135
+ // so only a canvas anchor pays for a per-frame re-measure.
136
+ { animationFrame: hasSurfaceScope, ...options });
109
137
  }
110
138
  export function combine(actions, context) {
111
139
  const grouped = group(actions);
@@ -143,6 +171,179 @@ function group(actions) {
143
171
  return paired;
144
172
  }
145
173
  const merge = (a, b) => mergeWith(a, b, (obj, src) => Array.isArray(obj) ? group(obj.concat(src)) : src);
174
+ export function emptyToolbarLayout() {
175
+ return { shrunk: new Set(), collapsed: new Set() };
176
+ }
177
+ /**
178
+ * Whether the widget can render this action as an entry of the "⋮" menu.
179
+ *
180
+ * Mirrors {@link renderActions}' own precedence exactly: an action that brings
181
+ * its own template (`content`) or its own sub-actions is opaque — the widget
182
+ * has no honest way to turn it into a menu line, and a dropdown nested inside a
183
+ * dropdown is worse than a dropdown that stayed. Those keep their place on the
184
+ * row, which is also what the PO asked for the two qualification dropdowns.
185
+ */
186
+ function isPlainAction(action) {
187
+ if ('content' in action)
188
+ return false;
189
+ if ('actions' in action)
190
+ return false;
191
+ return typeof action.run === 'function';
192
+ }
193
+ function resolveContent(action, context) {
194
+ if (!('content' in action))
195
+ return undefined;
196
+ if (typeof action.content === 'function')
197
+ return action.content(context);
198
+ return action.content ?? null;
199
+ }
200
+ /**
201
+ * Resolves every entry of the row, in order, dropping the ones that render
202
+ * nothing — the same precedence {@link renderResolvedActions} then applies.
203
+ */
204
+ function resolveActions(actions, context) {
205
+ const resolved = [];
206
+ for (const action of actions) {
207
+ if ('content' in action) {
208
+ const template = resolveContent(action, context);
209
+ if (template === null || template === undefined)
210
+ continue;
211
+ resolved.push({ kind: 'template', action, template });
212
+ continue;
213
+ }
214
+ if ('actions' in action && action.actions.length) {
215
+ const combined = combine(action.actions, context);
216
+ if (!combined.length)
217
+ continue;
218
+ const ordered = orderBy(combined, ['id', 'score'], ['asc', 'asc']);
219
+ resolved.push({
220
+ kind: 'group',
221
+ action,
222
+ children: ordered.map(child => ({
223
+ action: child,
224
+ template: resolveContent(child, context),
225
+ })),
226
+ });
227
+ continue;
228
+ }
229
+ if ('run' in action && action.run) {
230
+ resolved.push({ kind: 'action', action });
231
+ }
232
+ }
233
+ return resolved;
234
+ }
235
+ /**
236
+ * A stable name for one template, by the only thing about it that is stable.
237
+ *
238
+ * A tagged template literal hands the SAME frozen strings array to every
239
+ * evaluation of its call site — that array IS which template this is, and it is
240
+ * what Lit itself diffs on. The `TemplateResult` wrapped around it, on the other
241
+ * hand, is a fresh object every single render, which is precisely why an entry's
242
+ * template can never be compared by identity.
243
+ */
244
+ const templateNames = new WeakMap();
245
+ let templateCount = 0;
246
+ function templateName(strings) {
247
+ let name = templateNames.get(strings);
248
+ if (name === undefined) {
249
+ name = `t${++templateCount}`;
250
+ templateNames.set(strings, name);
251
+ }
252
+ return name;
253
+ }
254
+ /** How deep a template may nest before the digest stops caring. */
255
+ const MAX_DIGEST_DEPTH = 8;
256
+ /**
257
+ * What a template SAYS, as values.
258
+ *
259
+ * Only the things that end up as characters on the row are collected — the
260
+ * words and the numbers, plus a name for each template so that a different
261
+ * shape reads differently. Everything else is deliberately dropped:
262
+ *
263
+ * - **Functions** are event handlers. A fresh closure per render is the normal
264
+ * way to write one, and it changes nothing the eye can see.
265
+ * - **Booleans** are attributes (`?active`, `?disabled`). They change how an
266
+ * entry LOOKS, never how wide it is, and a row does not need re-planning
267
+ * because a toggle went blue.
268
+ * - **Anything else** — a model, a context, a DOM node — is an object whose
269
+ * identity says nothing about the row.
270
+ *
271
+ * That asymmetry is the whole point: same words, same digest, whatever objects
272
+ * were built to carry them.
273
+ */
274
+ function digestValue(value, out, depth = 0) {
275
+ if (depth > MAX_DIGEST_DEPTH || value === null || value === undefined)
276
+ return;
277
+ switch (typeof value) {
278
+ case 'string':
279
+ case 'number':
280
+ case 'bigint':
281
+ out.push(String(value));
282
+ return;
283
+ case 'boolean':
284
+ case 'function':
285
+ case 'symbol':
286
+ return;
287
+ default:
288
+ break;
289
+ }
290
+ if (Array.isArray(value)) {
291
+ for (const item of value)
292
+ digestValue(item, out, depth + 1);
293
+ return;
294
+ }
295
+ // A `TemplateResult` (`strings` + `values`) or a directive result (`values`
296
+ // alone: `repeat`, `join`, `keyed`, `ifDefined`…). Both are walked the same
297
+ // way, and anything shaped like neither contributes nothing.
298
+ const parts = value;
299
+ if (Array.isArray(parts.strings))
300
+ out.push(templateName(parts.strings));
301
+ if (Array.isArray(parts.values)) {
302
+ for (const item of parts.values)
303
+ digestValue(item, out, depth + 1);
304
+ }
305
+ }
306
+ /**
307
+ * What the row SAYS, entry by entry — the half of its signature that the
308
+ * entries' own state decides.
309
+ *
310
+ * The other half ({@link renderToolbar}'s list of ids, priorities, words and
311
+ * icons) describes the entries the widget itself draws, and for a row made of
312
+ * nothing else it is a complete description: same list, same widths. A row that
313
+ * carries OPAQUE entries — a dropdown that names the profile in force, a group
314
+ * whose members come and go with the element they are about — is not described
315
+ * by that list at all, and two rows it calls identical can be twenty pixels
316
+ * apart. This is what closes that gap.
317
+ *
318
+ * Plain entries add nothing here: their word and their icon are already in the
319
+ * other half, and repeating them would only make the string longer.
320
+ */
321
+ function digestEntries(entries) {
322
+ const out = [];
323
+ for (const entry of entries) {
324
+ if (entry.kind === 'action')
325
+ continue;
326
+ out.push(entry.action.id);
327
+ if (entry.kind === 'template') {
328
+ digestValue(entry.template, out, 0);
329
+ continue;
330
+ }
331
+ for (const child of entry.children) {
332
+ out.push(child.action.id);
333
+ if (child.template !== undefined) {
334
+ digestValue(child.template, out, 0);
335
+ continue;
336
+ }
337
+ // A plain member of a group: the widget draws it, so what it will say is
338
+ // known without resolving anything.
339
+ out.push(child.action.showLabel && child.action.label
340
+ ? String(child.action.label)
341
+ : '');
342
+ out.push(child.action.icon ? 'i' : '');
343
+ }
344
+ }
345
+ return out.join('');
346
+ }
146
347
  /**
147
348
  * Renders toolbar
148
349
  *
@@ -152,7 +353,7 @@ const merge = (a, b) => mergeWith(a, b, (obj, src) => Array.isArray(obj) ? group
152
353
  * 3. `affine:*`
153
354
  * 4. `custom:affine:*`
154
355
  */
155
- export function renderToolbar(toolbar, context, flavour) {
356
+ export function renderToolbar(toolbar, context, flavour, source = () => emptyToolbarLayout()) {
156
357
  const hasSurfaceScope = flavour.includes('surface');
157
358
  const toolbarRegistry = context.toolbarRegistry;
158
359
  const actions = [
@@ -176,16 +377,65 @@ export function renderToolbar(toolbar, context, flavour) {
176
377
  // Resets
177
378
  if (primaryActionGroup.length === 0) {
178
379
  context.reset();
179
- return;
380
+ return null;
180
381
  }
382
+ // What this row could give up if it ran out of width, decided entirely by
383
+ // what the entries declare — the widget never names a block or a framework.
384
+ const items = primaryActionGroup.map(action => {
385
+ const plain = isPlainAction(action);
386
+ return {
387
+ id: action.id,
388
+ priority: action.priority ?? 0,
389
+ shrinkable: plain && Boolean(action.icon && action.showLabel && action.label),
390
+ collapsible: plain,
391
+ };
392
+ });
393
+ const steps = toolbarDegradationSteps(items);
394
+ // Resolved BEFORE the plan is asked for, and drawn from this same answer: an
395
+ // entry that brings its own template only says what it says once it has been
396
+ // called, and the signature below has to know.
397
+ const resolved = resolveActions(primaryActionGroup, context);
398
+ // What this row IS, before anything about how it is currently degraded — in
399
+ // the two halves {@link ToolbarRow} describes. Both are VALUES: nothing that
400
+ // varies with the objects a render happens to allocate — a fresh template, a
401
+ // fresh event handler — reaches either string, which is what keeps them
402
+ // identical across two identical rebuilds.
403
+ //
404
+ // The words are in the first half, not just the ids: an entry that changes
405
+ // its label ("Add exception" becoming "Revoke exception") changes what the
406
+ // row costs, and a plan measured on the old words no longer describes it.
407
+ const row = {
408
+ entries: [
409
+ flavour,
410
+ ...primaryActionGroup.map(action => [
411
+ action.id,
412
+ action.priority ?? 0,
413
+ action.showLabel && action.label ? action.label : '',
414
+ action.icon ? 'i' : '',
415
+ isPlainAction(action) ? 'p' : 'o',
416
+ ].join('~')),
417
+ ].join('|'),
418
+ state: digestEntries(resolved),
419
+ };
420
+ const layout = source(row);
421
+ // Entries the last measurement sent into the menu lead it: they came off the
422
+ // row, so they are the first thing the user looks for after opening it.
423
+ const collapsed = primaryActionGroup.filter(a => layout.collapsed.has(a.id));
424
+ const primary = resolved.filter(entry => !layout.collapsed.has(entry.action.id));
425
+ const more = [...collapsed, ...moreActionGroup];
181
426
  const innerToolbar = context.placement$.value === 'inner';
182
- if (moreActionGroup.length) {
183
- const moreMenuItems = renderActions(moreActionGroup, context, renderMenuActionItem);
427
+ let hasMenu = false;
428
+ if (more.length) {
429
+ const moreMenuItems = renderActions(more, context, renderMenuActionItem);
184
430
  if (moreMenuItems.length) {
185
431
  const key = `${context.getCurrentModel()?.id}`;
186
- primaryActionGroup.push({
187
- id: 'more',
188
- content: html `${keyed(`${flavour}:${key}`, html `
432
+ hasMenu = true;
433
+ primary.push({
434
+ kind: 'template',
435
+ // Not an entry of the row's signature: the "⋮" is DERIVED from the
436
+ // plan, and a row is never re-planned because its own plan opened one.
437
+ action: { id: 'more' },
438
+ template: html `${keyed(`${flavour}:${key}`, html `
189
439
  <editor-menu-button
190
440
  aria-label="More menu"
191
441
  .contentPadding="${'8px'}"
@@ -211,7 +461,7 @@ export function renderToolbar(toolbar, context, flavour) {
211
461
  });
212
462
  }
213
463
  }
214
- render(join(renderActions(primaryActionGroup, context), innerToolbar ? null : renderToolbarSeparator()), toolbar);
464
+ render(join(renderResolvedActions(primary, context, renderActionItem, layout.shrunk), innerToolbar ? null : renderToolbarSeparator()), toolbar);
215
465
  // Avoids shaking
216
466
  if (flavour === 'affine:note' && context.std.range.value) {
217
467
  if (!('inline' in toolbar.dataset)) {
@@ -224,47 +474,34 @@ export function renderToolbar(toolbar, context, flavour) {
224
474
  else {
225
475
  delete toolbar.dataset.inline;
226
476
  }
227
- if (toolbar.dataset.open)
228
- return;
229
- toolbar.dataset.open = 'true';
477
+ if (!toolbar.dataset.open) {
478
+ toolbar.dataset.open = 'true';
479
+ }
480
+ return { steps, hasMenu, ...row };
230
481
  }
231
- function renderActions(actions, context, render = renderActionItem) {
232
- return actions
233
- .map(action => {
234
- if ('content' in action) {
235
- if (typeof action.content === 'function') {
236
- return action.content(context);
237
- }
238
- else {
239
- return action.content ?? null;
240
- }
241
- }
242
- if ('actions' in action && action.actions.length) {
243
- const combined = combine(action.actions, context);
244
- if (!combined.length)
245
- return null;
246
- const ordered = orderBy(combined, ['id', 'score'], ['asc', 'asc']);
247
- return repeat(ordered, a => a.id, a => {
248
- if ('content' in a) {
249
- if (typeof a.content === 'function') {
250
- return a.content(context);
251
- }
252
- else {
253
- return a.content ?? null;
254
- }
255
- }
256
- return render(a, context);
257
- });
258
- }
259
- if ('run' in action && action.run) {
260
- return render(action, context);
482
+ function renderResolvedActions(entries, context, render = renderActionItem, shrunk) {
483
+ return entries.map(entry => {
484
+ if (entry.kind === 'template')
485
+ return entry.template;
486
+ if (entry.kind === 'group') {
487
+ return repeat(entry.children, child => child.action.id, child => child.template !== undefined
488
+ ? child.template
489
+ : render(child.action, context, shrunk?.has(child.action.id) ?? false));
261
490
  }
262
- return null;
263
- })
264
- .filter(action => action !== null);
491
+ return render(entry.action, context, shrunk?.has(entry.action.id) ?? false);
492
+ });
493
+ }
494
+ function renderActions(actions, context, render = renderActionItem, shrunk) {
495
+ return renderResolvedActions(resolveActions(actions, context), context, render, shrunk);
265
496
  }
266
497
  // TODO(@fundon): supports templates
267
- function renderActionItem(action, context) {
498
+ function renderActionItem(action, context,
499
+ /**
500
+ * The row is tight and this entry has already given up its label. It keeps
501
+ * its icon and its behaviour; the word moves to the tooltip, which is the
502
+ * cheapest thing an entry can lose (see `layout.ts`).
503
+ */
504
+ iconOnly = false) {
268
505
  const innerToolbar = context.placement$.value === 'inner';
269
506
  const ids = action.id.split('.');
270
507
  const id = ids[ids.length - 1];
@@ -275,21 +512,22 @@ function renderActionItem(action, context) {
275
512
  const disabled = typeof action.disabled === 'function'
276
513
  ? action.disabled(context)
277
514
  : action.disabled;
515
+ const showLabel = Boolean(action.showLabel && action.label) && !iconOnly;
278
516
  return html `
279
517
  <editor-icon-button
280
518
  data-testid=${ifDefined(id)}
519
+ data-toolbar-action-id=${action.id}
520
+ data-icon-only=${ifDefined(iconOnly ? 'true' : undefined)}
281
521
  aria-label=${ifDefined(label)}
282
522
  ?active=${actived}
283
523
  ?disabled=${disabled}
284
- .tooltip=${action.tooltip}
524
+ .tooltip=${action.tooltip ?? (iconOnly ? action.label : undefined)}
285
525
  .iconContainerPadding=${innerToolbar ? 4 : 2}
286
526
  .iconSize=${innerToolbar ? '16px' : undefined}
287
527
  @click=${() => action.run?.(context)}
288
528
  >
289
529
  ${action.icon}
290
- ${action.showLabel && action.label
291
- ? html `<span class="label">${action.label}</span>`
292
- : null}
530
+ ${showLabel ? html `<span class="label">${action.label}</span>` : null}
293
531
  </editor-icon-button>
294
532
  `;
295
533
  }
@@ -308,6 +546,7 @@ function renderMenuActionItem(action, context) {
308
546
  return html `
309
547
  <editor-menu-action
310
548
  data-testid=${ifDefined(id)}
549
+ data-toolbar-action-id=${action.id}
311
550
  aria-label=${ifDefined(label)}
312
551
  class="${ifDefined(destructive)}"
313
552
  ?active=${actived}
@@ -322,3 +561,446 @@ function renderMenuActionItem(action, context) {
322
561
  </editor-menu-action>
323
562
  `;
324
563
  }
564
+ /**
565
+ * What a "⋮" button costs a row that has none yet: one icon, no label.
566
+ *
567
+ * Estimated rather than measured, because the button does not exist at the
568
+ * moment the planner has to decide whether opening one pays for itself. The
569
+ * estimate is only ever used for that one comparison, and a round of
570
+ * verification measures the real row right afterwards.
571
+ */
572
+ const MENU_BUTTON_WIDTH = 24;
573
+ /** How many times the fitter is willing to re-measure before settling. */
574
+ const MAX_FIT_ROUNDS = 3;
575
+ /**
576
+ * The width the row may occupy, as `size()` wrote it.
577
+ *
578
+ * Two shapes, because the middleware has two: a floating toolbar gets a cap and
579
+ * keeps its natural width under it, while an `inner` toolbar is pinned to the
580
+ * exact width of the block it sits on. Anything else — a row nothing has
581
+ * positioned yet — has all the room in the world and collapses nothing.
582
+ */
583
+ function availableWidthOf(toolbar) {
584
+ const max = Number.parseFloat(toolbar.style.maxWidth);
585
+ if (Number.isFinite(max))
586
+ return max;
587
+ const width = Number.parseFloat(toolbar.style.width);
588
+ return Number.isFinite(width) ? width : Number.POSITIVE_INFINITY;
589
+ }
590
+ /**
591
+ * Waits until the row is on screen as it was just described.
592
+ *
593
+ * One animation frame: every pending Lit update is a microtask, and microtasks
594
+ * are drained — exhaustively, including the ones those updates queue for their
595
+ * own children — before a frame callback runs. Measuring any earlier reads
596
+ * elements whose shadow root has not rendered yet, which is to say elements
597
+ * that are still zero pixels wide.
598
+ */
599
+ function settled() {
600
+ return new Promise(resolve => requestAnimationFrame(() => resolve()));
601
+ }
602
+ /**
603
+ * Reads what the row costs, entry by entry.
604
+ *
605
+ * Only the entries the widget itself rendered ({@link renderActionItem}) carry
606
+ * an id, and they are exactly the entries that can give way — anything opaque
607
+ * stays on the row, so its width is counted into the total and never offered as
608
+ * a saving.
609
+ */
610
+ function measureToolbar(toolbar, hasMenu) {
611
+ const style = getComputedStyle(toolbar);
612
+ const gap = Number.parseFloat(style.columnGap) || 0;
613
+ const children = Array.from(toolbar.children);
614
+ // Separators are uniform, and an entry that leaves the row takes one with it.
615
+ const separator = children.find(child => child.localName === 'editor-toolbar-separator');
616
+ const separatorCost = separator ? separator.offsetWidth + gap : 0;
617
+ const label = {};
618
+ const entry = {};
619
+ let content = 0;
620
+ for (const child of children) {
621
+ content += child.offsetWidth + gap;
622
+ const id = child.dataset.toolbarActionId;
623
+ if (!id)
624
+ continue;
625
+ entry[id] = child.offsetWidth + gap + separatorCost;
626
+ label[id] = child.querySelector('.label')?.offsetWidth ?? 0;
627
+ }
628
+ if (children.length)
629
+ content -= gap;
630
+ return {
631
+ content,
632
+ available: availableWidthOf(toolbar),
633
+ label,
634
+ entry,
635
+ menuCost: MENU_BUTTON_WIDTH + gap + separatorCost,
636
+ hasMenu,
637
+ };
638
+ }
639
+ /**
640
+ * The WHOLE row's measurements, read from the row as it is currently DRAWN.
641
+ *
642
+ * A state change — a dropdown naming a different profile, a group that gained
643
+ * a member — invalidates the widths the plan was made from. It does not
644
+ * invalidate the plan itself: the entries are the same entries and they give
645
+ * way in the same order. So the row must be re-measured, and the one thing it
646
+ * must not do to be re-measured is appear undegraded for a frame, which is the
647
+ * flash the PO reported and the second pass removed.
648
+ *
649
+ * It does not have to. The row is measured wearing its plan, and what the plan
650
+ * took off is added back from the numbers that made it — an entry that is not
651
+ * on the row is not on screen, so nothing about it can have moved under it.
652
+ * What comes out is the row whole, measured without ever having been shown
653
+ * whole.
654
+ */
655
+ function wholeRowMetrics(drawn, previous, layout) {
656
+ const label = { ...previous.label, ...drawn.label };
657
+ const entry = { ...previous.entry, ...drawn.entry };
658
+ let content = drawn.content;
659
+ // An entry drawn as its icon alone: it is on the row, so its icon was just
660
+ // measured; the word it is not wearing is worth what it was worth.
661
+ for (const id of layout.shrunk) {
662
+ const word = previous.label[id] ?? 0;
663
+ label[id] = word;
664
+ entry[id] = (drawn.entry[id] ?? previous.entry[id] ?? 0) + word;
665
+ content += word;
666
+ }
667
+ // An entry in the "⋮": nothing of it is on the row, so it costs the row what
668
+ // it cost the last time the row carried it.
669
+ for (const id of layout.collapsed) {
670
+ const cost = previous.entry[id] ?? 0;
671
+ entry[id] = cost;
672
+ label[id] = previous.label[id] ?? 0;
673
+ content += cost;
674
+ }
675
+ // And the "⋮" the plan opened is not part of the row it opened it for.
676
+ if (layout.collapsed.size > 0 && !previous.hasMenu) {
677
+ content -= drawn.menuCost;
678
+ }
679
+ return {
680
+ content,
681
+ available: drawn.available,
682
+ label,
683
+ entry,
684
+ menuCost: drawn.menuCost,
685
+ // What the row carries on its OWN account, which the plan never changed.
686
+ hasMenu: previous.hasMenu,
687
+ };
688
+ }
689
+ function stepKey(step) {
690
+ return `${step.kind}:${step.id}`;
691
+ }
692
+ function sameLayout(a, b) {
693
+ if (a.shrunk.size !== b.shrunk.size)
694
+ return false;
695
+ if (a.collapsed.size !== b.collapsed.size)
696
+ return false;
697
+ for (const id of a.shrunk)
698
+ if (!b.shrunk.has(id))
699
+ return false;
700
+ for (const id of a.collapsed)
701
+ if (!b.collapsed.has(id))
702
+ return false;
703
+ return true;
704
+ }
705
+ /**
706
+ * Keeps the contextual toolbar on ONE line.
707
+ *
708
+ * Owns the two halves the pure planner cannot: MEASURING the row, and
709
+ * RE-RENDERING it once the planner has said which entries give way. The order
710
+ * they give way in is not decided here — it comes from what the entries
711
+ * declared (`priority`, `icon`, `label`), so this class never learns that a
712
+ * block or a framework exists.
713
+ *
714
+ * The row is measured ONCE per ROW, while it is still whole. Every later
715
+ * resize replans from those numbers instead of flashing the row back to its
716
+ * full width just to measure it again — which is also what makes the collapse
717
+ * reversible: widen the editor and the plan simply spends fewer steps.
718
+ *
719
+ * "Per row", not per render: the widget re-renders for reasons that have
720
+ * nothing to do with width, and a re-render is not a new row. Which row it is
721
+ * is answered by {@link ToolbarRow}, and a row that has not changed keeps the
722
+ * plan it is wearing — see {@link ToolbarFitter.render}.
723
+ *
724
+ * A row that changed only what it SAYS keeps it too, and is simply re-measured
725
+ * where it stands ({@link ToolbarFitter.#remeasure}). That is the generalisation
726
+ * the PO's recette of 25/08/2026 asked for: the components' row is made
727
+ * entirely of entries the widget draws itself, so the list of entries is a
728
+ * complete description of it and the first half of {@link ToolbarRow} was
729
+ * enough; a framework background's row is mostly OPAQUE — toggles grouped by
730
+ * the framework, a dropdown naming the level of requirement in force — and for
731
+ * that row the list says nothing about what it costs.
732
+ *
733
+ * And it replans only at the ACCALMIE. While the room is still moving — a zoom,
734
+ * a pan, a window being dragged — the plan on screen is frozen: see
735
+ * {@link ToolbarFitter.resize}.
736
+ */
737
+ export class ToolbarFitter {
738
+ #context = null;
739
+ #flavour = '';
740
+ #hasMenu = false;
741
+ #layout = emptyToolbarLayout();
742
+ /** The whole row, measured once. `null` until the first frame lands. */
743
+ #metrics = null;
744
+ /** The room the row is waiting to be replanned for, once it holds still. */
745
+ #pending = null;
746
+ /** Armed while the room is still moving; fires at the accalmie. */
747
+ #settleTimer = null;
748
+ /** Which entries the row on screen has. `''` when there is no row. */
749
+ #entries = '';
750
+ /** What those entries currently say. See {@link ToolbarRow}. */
751
+ #state = '';
752
+ #steps = [];
753
+ /** Bumped by anything that invalidates a measurement still in flight. */
754
+ #token = 0;
755
+ constructor(toolbar) {
756
+ this.toolbar = toolbar;
757
+ }
758
+ /** Nothing is waiting on the room any more. */
759
+ #cancelSettle() {
760
+ if (this.#settleTimer !== null) {
761
+ clearTimeout(this.#settleTimer);
762
+ this.#settleTimer = null;
763
+ }
764
+ this.#pending = null;
765
+ }
766
+ #apply(plan) {
767
+ const next = emptyToolbarLayout();
768
+ for (const step of plan) {
769
+ if (step.kind === 'shrink')
770
+ next.shrunk.add(step.id);
771
+ else
772
+ next.collapsed.add(step.id);
773
+ }
774
+ if (sameLayout(next, this.#layout))
775
+ return false;
776
+ this.#layout = next;
777
+ renderToolbar(this.toolbar, this.#context, this.#flavour, () => next);
778
+ return true;
779
+ }
780
+ /**
781
+ * Applies a plan, then checks the row against the estimates that produced it.
782
+ *
783
+ * A plan is arithmetic on measured widths, but a rendered row is not: a
784
+ * dropped label frees a little more or a little less than its own span, and
785
+ * the "⋮" that appears is the width it turns out to be. So the row is read
786
+ * back and, while it still overflows, the next unspent steps are added — at
787
+ * most {@link MAX_FIT_ROUNDS} times, after which the row keeps what it has
788
+ * and stays, above all, one line.
789
+ */
790
+ async #converge(plan, token) {
791
+ for (let round = 0; round < MAX_FIT_ROUNDS; round++) {
792
+ if (token !== this.#token || !this.#context)
793
+ return;
794
+ // An empty plan is the answer when the room came back: it restores the
795
+ // whole row, and there is then nothing left to verify.
796
+ if (!this.#apply(plan) || plan.length === 0)
797
+ return;
798
+ await settled();
799
+ if (token !== this.#token || !this.#context)
800
+ return;
801
+ const now = measureToolbar(this.toolbar, this.#hasMenu || this.#layout.collapsed.size > 0);
802
+ if (now.content <= now.available)
803
+ return;
804
+ const spent = new Set(plan.map(stepKey));
805
+ const extra = planToolbarLayout(this.#steps.filter(step => !spent.has(stepKey(step))), now);
806
+ if (extra.length === 0)
807
+ return;
808
+ plan = [...plan, ...extra];
809
+ }
810
+ }
811
+ /**
812
+ * Spends the room the last measurement was waiting on.
813
+ *
814
+ * The hysteresis is checked a second time here, against the plan that is
815
+ * actually on screen: a gesture that wandered away and came back has changed
816
+ * nothing, and a row is not rebuilt to arrive where it already is.
817
+ */
818
+ #replan() {
819
+ const metrics = this.#metrics;
820
+ const available = this.#pending;
821
+ this.#pending = null;
822
+ if (!metrics || available === null || !this.#context)
823
+ return;
824
+ if (!toolbarRoomChanged(metrics.available, available))
825
+ return;
826
+ this.#metrics = { ...metrics, available };
827
+ void this.#converge(planToolbarLayout(this.#steps, this.#metrics), ++this.#token);
828
+ }
829
+ /**
830
+ * Waits for the room to hold still, then replans once.
831
+ *
832
+ * Re-armed by every further change, so a gesture of any length spends exactly
833
+ * one plan — the one for the width it ended on.
834
+ */
835
+ #settle() {
836
+ if (this.#settleTimer !== null)
837
+ clearTimeout(this.#settleTimer);
838
+ this.#settleTimer = setTimeout(() => {
839
+ this.#settleTimer = null;
840
+ // A wheel zoom breathes: between two notches the room can hold still for
841
+ // longer than the delay and the gesture still be under way. The viewport
842
+ // knows it is, and says so for as long as it lasts.
843
+ if (this.#zooming()) {
844
+ this.#settle();
845
+ return;
846
+ }
847
+ this.#replan();
848
+ }, TOOLBAR_SETTLE_DELAY);
849
+ }
850
+ /**
851
+ * Whether a zoom gesture is under way, as the viewport itself reports it.
852
+ *
853
+ * Only the zoom. A pan changes the room on every frame of the drag, so the
854
+ * delay above already spans it whole; and `panning$` is raised by every
855
+ * programmatic recentring too, which would freeze the row for gestures no one
856
+ * is making. In page mode there is no viewport at all, and the delay is then
857
+ * the only signal there is — which is all it needs to be.
858
+ */
859
+ #zooming() {
860
+ const gfx = this.#context?.std.getOptional(GfxControllerIdentifier);
861
+ return gfx?.viewport.zooming$.value === true;
862
+ }
863
+ async #measure(token) {
864
+ await settled();
865
+ if (token !== this.#token || !this.#context)
866
+ return;
867
+ const metrics = measureToolbar(this.toolbar, this.#hasMenu);
868
+ this.#metrics = metrics;
869
+ if (metrics.content <= metrics.available)
870
+ return;
871
+ await this.#converge(planToolbarLayout(this.#steps, metrics), token);
872
+ }
873
+ /**
874
+ * The row said something new. Measure it again, wearing what it is wearing.
875
+ *
876
+ * The plan is untouched — the entries are the same entries — so this never
877
+ * draws the whole row, and the eye sees the row change at most once: when the
878
+ * new widths genuinely no longer fit the same plan.
879
+ */
880
+ async #remeasure(token) {
881
+ await settled();
882
+ if (token !== this.#token || !this.#context)
883
+ return;
884
+ const previous = this.#metrics;
885
+ const drawn = measureToolbar(this.toolbar, this.#hasMenu || this.#layout.collapsed.size > 0);
886
+ const metrics = previous
887
+ ? wholeRowMetrics(drawn, previous, this.#layout)
888
+ : drawn;
889
+ this.#metrics = metrics;
890
+ await this.#converge(planToolbarLayout(this.#steps, metrics), token);
891
+ }
892
+ /**
893
+ * Renders the row, in the mode the plan on screen says.
894
+ *
895
+ * The widget re-renders for a great many reasons that have nothing to do
896
+ * with the row's width — an element updated anywhere on the canvas, a block
897
+ * updated, a selection re-emitted, something hovered — and any of them can
898
+ * land on any frame of a gesture. So this asks a question first: is the row
899
+ * about to be drawn the row already on screen?
900
+ *
901
+ * - **Yes** (same {@link ToolbarRow}, both halves): it is drawn wearing the
902
+ * plan it is already wearing, entry by entry, and nothing is measured.
903
+ * The DOM does not change, so there is nothing to see — which is the
904
+ * PO's recette of 25/08/2026. Before, every one of these re-renders threw
905
+ * the plan away, painted the whole row, and only then measured and
906
+ * degraded it again: one flash of "Read this component" per re-render,
907
+ * plus a full replan at whatever width the gesture happened to be at,
908
+ * straight through the freeze the accalmie was supposed to give.
909
+ * - **The same entries, saying something new** — a dropdown naming another
910
+ * profile, a group that gained a member: the PLAN still describes the row
911
+ * and stays on it, so again there is nothing to see. Only the widths it was
912
+ * made from are out of date, and {@link ToolbarFitter.#remeasure} reads
913
+ * them back off the row as it is drawn.
914
+ * - **No**: a different row. Then, and only then, it is rendered whole,
915
+ * measured, and spends what it must.
916
+ */
917
+ render(context, flavour) {
918
+ this.#context = context;
919
+ this.#flavour = flavour;
920
+ // Answered while the entries are resolved and before they are drawn: the
921
+ // mode an entry reads in is an argument of its own render, never a fixup.
922
+ let change = 'none';
923
+ const fit = renderToolbar(this.toolbar, context, flavour, row => {
924
+ if (row.entries !== this.#entries) {
925
+ change = 'row';
926
+ this.#entries = row.entries;
927
+ this.#state = row.state;
928
+ this.#layout = emptyToolbarLayout();
929
+ return this.#layout;
930
+ }
931
+ if (row.state !== this.#state) {
932
+ change = 'state';
933
+ this.#state = row.state;
934
+ }
935
+ // Either way the row keeps what it is wearing: a plan is a list of
936
+ // entries, and this is the same list.
937
+ return this.#layout;
938
+ });
939
+ if (!fit) {
940
+ this.reset();
941
+ return;
942
+ }
943
+ // The same row saying the same things: it is already right, and a
944
+ // measurement now would be a measurement of a room that is still moving.
945
+ if (change === 'none')
946
+ return;
947
+ const token = ++this.#token;
948
+ this.#cancelSettle();
949
+ if (change === 'state') {
950
+ // The entries did not move, so neither did what they can give up nor
951
+ // whether the row carries a "⋮" of its own. Only the numbers changed.
952
+ if (this.#steps.length === 0)
953
+ return;
954
+ void this.#remeasure(token);
955
+ return;
956
+ }
957
+ // A new row. Whatever the old one was waiting for describes nothing.
958
+ this.#metrics = null;
959
+ this.#steps = fit.steps;
960
+ this.#hasMenu = fit.hasMenu;
961
+ if (fit.steps.length === 0)
962
+ return;
963
+ void this.#measure(token);
964
+ }
965
+ /** The toolbar is going away: stop measuring it. */
966
+ reset() {
967
+ this.#token++;
968
+ this.#cancelSettle();
969
+ this.#context = null;
970
+ this.#metrics = null;
971
+ this.#layout = emptyToolbarLayout();
972
+ this.#steps = [];
973
+ this.#hasMenu = false;
974
+ // The row that comes back is measured from scratch: `#metrics` went with
975
+ // it, and a plan with nothing behind it could never be revised.
976
+ this.#entries = '';
977
+ this.#state = '';
978
+ }
979
+ /**
980
+ * The room the row has may have changed.
981
+ *
982
+ * Two answers are refused before anything is replanned, and the PO's second
983
+ * pass of 02/08/2026 is about both:
984
+ *
985
+ * 1. **A change too small to mean anything.** Two measurements a pixel apart
986
+ * are the same measurement, and a row that believes them alternates
987
+ * between two compositions for as long as they alternate.
988
+ * 2. **A change that is still happening.** A zoom moves the room on every
989
+ * frame; replanning on each of them re-composes the row under the cursor,
990
+ * and every new width moves the anchoring with it — which is the toolbar
991
+ * "hesitating between anchor points" the PO reported. The plan on screen
992
+ * is FROZEN for the whole gesture and spent once, when the viewport lands.
993
+ *
994
+ * The room is remembered as it goes by, so the plan that is finally made is
995
+ * the one for the width the gesture ended on, not the width it started from.
996
+ */
997
+ resize() {
998
+ if (!this.#metrics || !this.#context)
999
+ return;
1000
+ const available = availableWidthOf(this.toolbar);
1001
+ if (!toolbarRoomChanged(this.#metrics.available, available))
1002
+ return;
1003
+ this.#pending = available;
1004
+ this.#settle();
1005
+ }
1006
+ }