@jini-ai/ui 0.5.5 → 0.6.10

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 (263) hide show
  1. package/CHANGELOG.md +91 -2
  2. package/dist/core/theme/validation.js +40 -38
  3. package/dist/features/a2ui/renderer.js +15 -13
  4. package/dist/features/about/react/components/AboutTab.js +64 -34
  5. package/dist/features/about/rules.d.ts +2 -2
  6. package/dist/features/about/rules.js +156 -129
  7. package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
  8. package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
  9. package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
  10. package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
  11. package/dist/features/admin-widgets/components/InfoTip.js +34 -7
  12. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
  13. package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
  14. package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
  15. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
  16. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
  17. package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
  18. package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
  19. package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
  20. package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
  21. package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
  22. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
  23. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
  24. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
  25. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
  26. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
  27. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
  28. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  29. package/dist/features/admin-widgets/index.d.ts +14 -2
  30. package/dist/features/admin-widgets/index.js +7 -1
  31. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  32. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  33. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  34. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  35. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  36. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  37. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  38. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  39. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  40. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  41. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  42. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  44. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  45. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  46. package/dist/features/asset-tree-browser/rules.js +40 -22
  47. package/dist/features/browser-chrome/rules.js +74 -70
  48. package/dist/features/charts/browser.js +11 -5
  49. package/dist/features/charts/react.js +2 -2
  50. package/dist/features/charts/table.js +2 -2
  51. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  52. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  53. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  54. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  55. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  56. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  57. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  58. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  59. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  60. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  61. package/dist/features/connectors/rules.d.ts +8 -1
  62. package/dist/features/connectors/rules.js +42 -20
  63. package/dist/features/connectors/types.d.ts +11 -0
  64. package/dist/features/execution/constants.d.ts +4 -10
  65. package/dist/features/execution/constants.js +9 -94
  66. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  67. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  68. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  69. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  70. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  71. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  72. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  73. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  74. package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
  75. package/dist/features/execution/rules.d.ts +2 -2
  76. package/dist/features/execution/rules.js +31 -15
  77. package/dist/features/execution/types.d.ts +4 -42
  78. package/dist/features/execution/types.js +0 -17
  79. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  80. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  81. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  82. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  83. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  84. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  85. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  86. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  87. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  88. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  89. package/dist/features/html-editor/css.js +76 -69
  90. package/dist/features/html-editor/editor-blocks.js +47 -33
  91. package/dist/features/html-editor/editor-serialization.js +6 -5
  92. package/dist/features/html-editor/protected-elements.js +39 -28
  93. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  94. package/dist/features/html-editor/source-splice.js +51 -30
  95. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  96. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  97. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  98. package/dist/features/i18n/locale.js +20 -16
  99. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  100. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  101. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  102. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  103. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  104. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  105. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  106. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  107. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  108. package/dist/features/mcp-ui/secret-card.js +111 -61
  109. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  110. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  111. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  112. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  113. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  114. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  115. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  116. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  117. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  118. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  119. package/dist/features/media-providers/constants.d.ts +1 -0
  120. package/dist/features/media-providers/constants.js +2 -81
  121. package/dist/features/media-providers/index.d.ts +1 -1
  122. package/dist/features/media-providers/ports.d.ts +2 -0
  123. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  124. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  125. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  126. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  127. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  128. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  129. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  130. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  131. package/dist/features/media-providers/rules.js +17 -10
  132. package/dist/features/media-providers/types.d.ts +4 -22
  133. package/dist/features/media-providers/types.js +3 -8
  134. package/dist/features/memory/formatters.js +127 -92
  135. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  136. package/dist/features/memory/react/components/MemoryList.js +37 -18
  137. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  138. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  139. package/dist/features/observability/long-task.d.ts +0 -12
  140. package/dist/features/observability/long-task.js +15 -19
  141. package/dist/features/observability/resource-error.d.ts +0 -10
  142. package/dist/features/observability/resource-error.js +18 -13
  143. package/dist/features/observability/stuck-run.js +10 -6
  144. package/dist/features/observability/telemetry-url.d.ts +6 -0
  145. package/dist/features/observability/telemetry-url.js +16 -0
  146. package/dist/features/observability/white-screen.js +12 -24
  147. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  148. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  149. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  150. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  151. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  152. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  153. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  154. package/dist/features/progress-card/reference-adapters.js +59 -54
  155. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  156. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  157. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  158. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  159. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  160. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  161. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  162. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  163. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  164. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  165. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  166. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  167. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  168. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  170. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  171. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  172. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  174. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  175. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  176. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  177. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  178. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  179. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  180. package/dist/features/resource-dashboard/rules.js +2 -8
  181. package/dist/features/sketch-editor/dom.js +150 -99
  182. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  183. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  184. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  185. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  186. package/dist/features/skills/react/components/SkillRow.js +38 -18
  187. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  188. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  189. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  190. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  191. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  192. package/dist/features/skills/rules.js +32 -23
  193. package/dist/features/source-config-list/agent-handles.js +2 -1
  194. package/dist/features/source-config-list/dependencies.js +53 -49
  195. package/dist/features/source-config-list/index.d.ts +3 -3
  196. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  197. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  198. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  199. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  200. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  201. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  203. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  204. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  205. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  206. package/dist/features/source-config-list/types.d.ts +14 -0
  207. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  208. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  209. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  210. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  211. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  212. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  213. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  214. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  215. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  217. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  218. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  219. package/dist/features/version-manager/dependencies.d.ts +3 -1
  220. package/dist/features/version-manager/dependencies.js +3 -2
  221. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  222. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  223. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  224. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  225. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  226. package/dist/features/viewer-shell/rules.js +19 -23
  227. package/dist/react/components/CustomSelect.d.ts +38 -1
  228. package/dist/react/components/CustomSelect.js +94 -38
  229. package/dist/react/components/OnboardingDropdown.js +20 -8
  230. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  231. package/dist/react/components/Toast.js +13 -2
  232. package/dist/react/components/TooltipLayer.js +5 -14
  233. package/dist/react/components/WorkingDirPicker.js +78 -50
  234. package/dist/react/components/remixicon-font-recovery.js +38 -30
  235. package/dist/react/components/select-popover.d.ts +8 -6
  236. package/dist/react/components/select-popover.js +33 -29
  237. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  238. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  239. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  240. package/dist/react/mcp-ui/host-message-source.js +15 -5
  241. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  242. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  243. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  244. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  245. package/dist/renderers/react/components/ArtifactView.js +9 -6
  246. package/dist/renderers/srcdoc/build.d.ts +4 -1
  247. package/dist/renderers/srcdoc/build.js +62 -48
  248. package/dist/styles/admin.css +29 -14
  249. package/dist/styles/lightbox.css +142 -0
  250. package/dist/styles/variables.css +128 -0
  251. package/dist/theme/apply.js +9 -5
  252. package/dist/utils/auto-open-file.d.ts +2 -1
  253. package/dist/utils/auto-open-file.js +69 -85
  254. package/dist/utils/markdown-scroll-sync.js +108 -87
  255. package/dist/utils/polygon-selection.js +25 -26
  256. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  257. package/dist/utils/smooth-scroll-to-top.js +2 -32
  258. package/dist/utils/sse.js +26 -26
  259. package/dist/utils/unit-bezier.d.ts +7 -0
  260. package/dist/utils/unit-bezier.js +38 -0
  261. package/package.json +9 -12
  262. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  263. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -29,6 +29,154 @@ export const ABOUT_UPDATE_KEYS = {
29
29
  notChecked: 'settings.updateStatusNotChecked',
30
30
  check: 'settings.updateCheck',
31
31
  };
32
+ const K = ABOUT_UPDATE_KEYS;
33
+ /**
34
+ * Shared by the capability gate and the reported `unsupported` state — one
35
+ * outcome, since the operator can do nothing about either from this panel.
36
+ */
37
+ const UNSUPPORTED_CONTROL = {
38
+ primaryAction: null,
39
+ primaryLabelKey: null,
40
+ showReleaseLink: true,
41
+ statusKey: K.unsupported,
42
+ statusTone: 'warning',
43
+ };
44
+ const DEVELOPMENT_CONTROL = {
45
+ primaryAction: null,
46
+ primaryLabelKey: null,
47
+ showReleaseLink: true,
48
+ statusKey: K.development,
49
+ statusTone: 'neutral',
50
+ };
51
+ /** `{ statusVars: { version } }` when the version is known (truthy), else nothing. */
52
+ function versionVars(model) {
53
+ return model.availableVersion ? { statusVars: { version: model.availableVersion } } : {};
54
+ }
55
+ /** Whether either install route (hand off to an installer, or apply in place) is open. */
56
+ function canInstall(model) {
57
+ return Boolean(model.canOpenInstaller || model.canApplyInPlace);
58
+ }
59
+ function deriveAvailable(model) {
60
+ return {
61
+ primaryAction: model.canDownload ? 'download' : null,
62
+ primaryLabelKey: model.canDownload ? K.download : null,
63
+ showReleaseLink: true,
64
+ statusKey: model.availableVersion ? K.available : K.availableUnknown,
65
+ statusTone: 'warning',
66
+ ...versionVars(model),
67
+ };
68
+ }
69
+ function deriveDownloading(model) {
70
+ const percent = model.downloadProgress?.percent;
71
+ const known = typeof percent === 'number';
72
+ return {
73
+ primaryAction: null,
74
+ primaryLabelKey: K.downloadingLabel,
75
+ showReleaseLink: true,
76
+ statusKey: known ? K.downloadingPercent : K.downloading,
77
+ statusTone: 'neutral',
78
+ ...(known ? { statusVars: { percent } } : {}),
79
+ };
80
+ }
81
+ function deriveDownloaded(model) {
82
+ const payload = model.updateKind === 'payload';
83
+ // The installer is already running; the only useful act left is to get
84
+ // out of its way.
85
+ if (model.installerOpened && model.canQuitAfterInstallerOpen) {
86
+ return {
87
+ primaryAction: 'quit',
88
+ primaryLabelKey: K.quit,
89
+ showReleaseLink: false,
90
+ statusKey: payload ? K.installingRestart : K.opening,
91
+ statusTone: 'neutral',
92
+ };
93
+ }
94
+ const installable = canInstall(model);
95
+ return {
96
+ primaryAction: installable ? 'install' : null,
97
+ primaryLabelKey: installable ? (payload ? K.installRestart : K.installNow) : null,
98
+ showReleaseLink: true,
99
+ statusKey: model.availableVersion ? K.ready : K.readyUnknown,
100
+ statusTone: 'success',
101
+ ...versionVars(model),
102
+ };
103
+ }
104
+ /**
105
+ * Retry the furthest-along step that is still possible: re-install a
106
+ * download we already have, else re-download a version we know about,
107
+ * else start over. Sending the operator back to "check" when a finished
108
+ * download is sitting on disk would throw away the expensive part.
109
+ */
110
+ function errorRetryAction(model) {
111
+ if (model.status?.downloadPath != null && canInstall(model))
112
+ return 'install';
113
+ if (model.availableVersion != null && model.canDownload)
114
+ return 'download';
115
+ return 'check';
116
+ }
117
+ function deriveError(model) {
118
+ return {
119
+ primaryAction: errorRetryAction(model),
120
+ primaryLabelKey: K.retry,
121
+ showReleaseLink: true,
122
+ statusKey: K.failed,
123
+ statusTone: 'error',
124
+ };
125
+ }
126
+ /**
127
+ * `idle`, and any state a newer backend reports that this build predates.
128
+ * Falling through to "you can check" is the safe default: it offers the
129
+ * one action that is valid from any unknown state.
130
+ */
131
+ const IDLE_CONTROL = {
132
+ primaryAction: 'check',
133
+ primaryLabelKey: K.check,
134
+ showReleaseLink: true,
135
+ statusKey: K.notChecked,
136
+ statusTone: 'neutral',
137
+ };
138
+ /**
139
+ * One deriver per reported state. A `Map` (not an object literal) so a
140
+ * state string that happens to name an `Object.prototype` member can never
141
+ * resolve to anything but the idle fallback.
142
+ */
143
+ const STATE_DERIVERS = new Map([
144
+ [
145
+ 'checking',
146
+ () => ({
147
+ primaryAction: null,
148
+ primaryLabelKey: K.checkingLabel,
149
+ showReleaseLink: true,
150
+ statusKey: K.checking,
151
+ statusTone: 'neutral',
152
+ }),
153
+ ],
154
+ [
155
+ 'not-available',
156
+ () => ({
157
+ primaryAction: 'check',
158
+ primaryLabelKey: K.recheck,
159
+ showReleaseLink: true,
160
+ statusKey: K.upToDate,
161
+ statusTone: 'success',
162
+ }),
163
+ ],
164
+ ['available', deriveAvailable],
165
+ ['downloading', deriveDownloading],
166
+ ['downloaded', deriveDownloaded],
167
+ [
168
+ 'installing',
169
+ () => ({
170
+ primaryAction: null,
171
+ primaryLabelKey: K.installingRestart,
172
+ showReleaseLink: false,
173
+ statusKey: K.installing,
174
+ statusTone: 'neutral',
175
+ }),
176
+ ],
177
+ ['error', deriveError],
178
+ ['unsupported', () => ({ ...UNSUPPORTED_CONTROL })],
179
+ ]);
32
180
  /**
33
181
  * Derives what the About panel's update row should show.
34
182
  *
@@ -45,7 +193,7 @@ export const ABOUT_UPDATE_KEYS = {
45
193
  * build — one shared "unsupported" outcome, since the operator can do
46
194
  * nothing about any of them from this panel.
47
195
  *
48
- * After that it is a switch over the reported state. Two subtleties worth
196
+ * After that it is a lookup of the reported state in `STATE_DERIVERS`. Two subtleties worth
49
197
  * keeping when reading it:
50
198
  *
51
199
  * - `primaryLabelKey` can be set while `primaryAction` is `null`. That is the
@@ -55,136 +203,15 @@ export const ABOUT_UPDATE_KEYS = {
55
203
  * about to disappear (installing, or quitting to let an installer run).
56
204
  * Offering a navigation there invites a click that cannot complete.
57
205
  *
58
- * @complexity O(1) — a fixed switch, no iteration.
206
+ * @complexity O(1) — a fixed-size map lookup, no iteration.
59
207
  */
60
208
  export function deriveAboutUpdateControl(model, appVersionInfo) {
61
- const K = ABOUT_UPDATE_KEYS;
62
- if (appVersionInfo?.packaged === false) {
63
- return {
64
- primaryAction: null,
65
- primaryLabelKey: null,
66
- showReleaseLink: true,
67
- statusKey: K.development,
68
- statusTone: 'neutral',
69
- };
70
- }
71
- if (model.environment !== 'desktop' || !model.enabled || !model.supported) {
72
- return {
73
- primaryAction: null,
74
- primaryLabelKey: null,
75
- showReleaseLink: true,
76
- statusKey: K.unsupported,
77
- statusTone: 'warning',
78
- };
79
- }
80
- switch (model.status?.state) {
81
- case 'checking':
82
- return {
83
- primaryAction: null,
84
- primaryLabelKey: K.checkingLabel,
85
- showReleaseLink: true,
86
- statusKey: K.checking,
87
- statusTone: 'neutral',
88
- };
89
- case 'not-available':
90
- return {
91
- primaryAction: 'check',
92
- primaryLabelKey: K.recheck,
93
- showReleaseLink: true,
94
- statusKey: K.upToDate,
95
- statusTone: 'success',
96
- };
97
- case 'available':
98
- return {
99
- primaryAction: model.canDownload ? 'download' : null,
100
- primaryLabelKey: model.canDownload ? K.download : null,
101
- showReleaseLink: true,
102
- statusKey: model.availableVersion ? K.available : K.availableUnknown,
103
- statusTone: 'warning',
104
- ...(model.availableVersion ? { statusVars: { version: model.availableVersion } } : {}),
105
- };
106
- case 'downloading': {
107
- const percent = model.downloadProgress?.percent;
108
- const known = typeof percent === 'number';
109
- return {
110
- primaryAction: null,
111
- primaryLabelKey: K.downloadingLabel,
112
- showReleaseLink: true,
113
- statusKey: known ? K.downloadingPercent : K.downloading,
114
- statusTone: 'neutral',
115
- ...(known ? { statusVars: { percent } } : {}),
116
- };
117
- }
118
- case 'downloaded': {
119
- // The installer is already running; the only useful act left is to get
120
- // out of its way.
121
- if (model.installerOpened && model.canQuitAfterInstallerOpen) {
122
- return {
123
- primaryAction: 'quit',
124
- primaryLabelKey: K.quit,
125
- showReleaseLink: false,
126
- statusKey: model.updateKind === 'payload' ? K.installingRestart : K.opening,
127
- statusTone: 'neutral',
128
- };
129
- }
130
- const canInstall = Boolean(model.canOpenInstaller || model.canApplyInPlace);
131
- return {
132
- primaryAction: canInstall ? 'install' : null,
133
- primaryLabelKey: canInstall ? (model.updateKind === 'payload' ? K.installRestart : K.installNow) : null,
134
- showReleaseLink: true,
135
- statusKey: model.availableVersion ? K.ready : K.readyUnknown,
136
- statusTone: 'success',
137
- ...(model.availableVersion ? { statusVars: { version: model.availableVersion } } : {}),
138
- };
139
- }
140
- case 'installing':
141
- return {
142
- primaryAction: null,
143
- primaryLabelKey: K.installingRestart,
144
- showReleaseLink: false,
145
- statusKey: K.installing,
146
- statusTone: 'neutral',
147
- };
148
- case 'error': {
149
- // Retry the furthest-along step that is still possible: re-install a
150
- // download we already have, else re-download a version we know about,
151
- // else start over. Sending the operator back to "check" when a finished
152
- // download is sitting on disk would throw away the expensive part.
153
- const canRetryInstall = model.status?.downloadPath != null && Boolean(model.canOpenInstaller || model.canApplyInPlace);
154
- const primaryAction = canRetryInstall
155
- ? 'install'
156
- : model.availableVersion != null && model.canDownload
157
- ? 'download'
158
- : 'check';
159
- return {
160
- primaryAction,
161
- primaryLabelKey: K.retry,
162
- showReleaseLink: true,
163
- statusKey: K.failed,
164
- statusTone: 'error',
165
- };
166
- }
167
- case 'unsupported':
168
- return {
169
- primaryAction: null,
170
- primaryLabelKey: null,
171
- showReleaseLink: true,
172
- statusKey: K.unsupported,
173
- statusTone: 'warning',
174
- };
175
- // `idle`, and any state a newer backend reports that this build predates.
176
- // Falling through to "you can check" is the safe default: it offers the
177
- // one action that is valid from any unknown state.
178
- case 'idle':
179
- default:
180
- return {
181
- primaryAction: 'check',
182
- primaryLabelKey: K.check,
183
- showReleaseLink: true,
184
- statusKey: K.notChecked,
185
- statusTone: 'neutral',
186
- };
187
- }
209
+ if (appVersionInfo?.packaged === false)
210
+ return { ...DEVELOPMENT_CONTROL };
211
+ if (model.environment !== 'desktop' || !model.enabled || !model.supported)
212
+ return { ...UNSUPPORTED_CONTROL };
213
+ const derive = model.status ? STATE_DERIVERS.get(model.status.state) : undefined;
214
+ return derive ? derive(model) : { ...IDLE_CONTROL };
188
215
  }
189
216
  /**
190
217
  * Whether the primary button should be disabled.
@@ -1,16 +1,17 @@
1
- import { useImagePreviewModal } from "./ImagePreviewModal.hooks.js";
1
+ import type { useLightbox } from "./Lightbox/Lightbox.hooks.js";
2
2
  export interface ImagePreviewModalProps {
3
3
  open: boolean;
4
4
  src: string;
5
5
  alt: string;
6
6
  onClose: () => void;
7
7
  /** A fake controller can exercise rendering without a real native-dialog lifecycle. */
8
- useModal?: typeof useImagePreviewModal | undefined;
8
+ useModal?: typeof useLightbox | undefined;
9
9
  /** Optional tagging preserves existing untagged renders when no agent handle is supplied. */
10
10
  agentHandle?: string | undefined;
11
11
  /** No locale is owned here; a translated caller must pass its own accessible close label. */
12
12
  closeLabel?: string | undefined;
13
13
  }
14
- /** Controlled native-dialog lightbox. The host supplies image copy and a translated close label. */
14
+ /** One image in the shared `Lightbox`; the alt text doubles as its title. Kept as a named
15
+ * single-image entry point so callers need not assemble the lightbox for the common case. */
15
16
  export declare function ImagePreviewModal({ open, src, alt, onClose, useModal, agentHandle: handle, closeLabel, }: ImagePreviewModalProps): import("react").JSX.Element;
16
17
  //# sourceMappingURL=ImagePreviewModal.d.ts.map
@@ -1,13 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { agentHandle } from "@jini-ai/agentic";
3
- import { useImagePreviewModal } from "./ImagePreviewModal.hooks.js";
4
- // Native dialog supplies focus trapping, Escape and a backdrop through the browser's top layer,
5
- // avoiding a hand-built overlay and z-index policy. A confirm/cancel dialog's two-action contract
6
- // does not fit a read-only image with one close affordance. The injectable hook owns lifecycle
7
- // and DOM-environment fallbacks so this component stays focused on rendering.
8
- /** Controlled native-dialog lightbox. The host supplies image copy and a translated close label. */
9
- export function ImagePreviewModal({ open, src, alt, onClose, useModal = useImagePreviewModal, agentHandle: handle, closeLabel = "Close preview", }) {
10
- const { dialogRef, handleNativeCancel, handleBackdropClick } = useModal({ open, onClose });
11
- return (_jsxs("dialog", { ref: dialogRef, className: "image-preview-modal", "aria-label": alt, onCancel: handleNativeCancel, onClick: handleBackdropClick, children: [_jsx("button", { type: "button", className: "image-preview-modal-close", onClick: onClose, "aria-label": closeLabel, ...(handle ? agentHandle({ handle: handle }, { role: "button", label: closeLabel }) : {}), children: "\u00D7" }), _jsx("img", { src: src, alt: alt })] }));
3
+ import { Lightbox } from "./Lightbox/Lightbox.js";
4
+ /** One image in the shared `Lightbox`; the alt text doubles as its title. Kept as a named
5
+ * single-image entry point so callers need not assemble the lightbox for the common case. */
6
+ export function ImagePreviewModal({ open, src, alt, onClose, useModal, agentHandle: handle, closeLabel = "Close preview", }) {
7
+ return (_jsx(Lightbox, { open: open, title: alt, onClose: onClose, className: "image-preview-modal", labels: { close: closeLabel }, useLightboxHook: useModal, partAttrs: handle ? { close: { ...agentHandle({ handle: handle }, { role: "button", label: closeLabel }) } } : undefined, children: _jsx("img", { src: src, alt: alt }) }));
12
8
  }
13
9
  //# sourceMappingURL=ImagePreviewModal.js.map
@@ -5,7 +5,8 @@ import { useInfoTip } from "./InfoTip.hooks.js";
5
5
  * and no touch visibility. Hover/focus opens this affordance; Escape dismisses without moving
6
6
  * focus, which onBlur alone cannot do.
7
7
  * A body portal escapes overflow:hidden/scrolling ancestors: z-index cannot rescue an absolutely
8
- * positioned descendant from clipping. Measured viewport coordinates position it above by
8
+ * positioned descendant from clipping. Inside a modal <dialog> it portals into that dialog
9
+ * instead, because the dialog's top layer covers everything in body. Measured viewport coordinates position it above by
9
10
  * preference, with the hook's headroom guard choosing below near the viewport's top; near a side
10
11
  * edge (or a host panel marked `data-jini-tip-bounds`) it shifts by its real width, never squeezes.
11
12
  * Keep state and DOM measurement in the injectable hook so rendering can use fixed fake state.
@@ -13,7 +14,7 @@ import { useInfoTip } from "./InfoTip.hooks.js";
13
14
  * tap, blur or Escape; a hover-opened tip still closes when the pointer leaves.
14
15
  */
15
16
  export interface InfoTipProps {
16
- /** The explanation the bubble shows. */
17
+ /** The explanation the bubble shows; newlines separate lines, and "• " lines show as a hanging list. */
17
18
  label: string;
18
19
  /**
19
20
  * Short accessible name for the icon ("About subagents"). Omitted, the icon is named by `label`
@@ -78,6 +78,8 @@ export function useInfoTip(_input = {}, { placement: preferredPlacement } = {})
78
78
  }, [open]);
79
79
  function handleIconKeyDown(e) {
80
80
  if (e.key === "Escape" && open) {
81
+ // preventDefault also stops an enclosing modal <dialog> from closing on the same keypress.
82
+ e.preventDefault();
81
83
  e.stopPropagation();
82
84
  hide();
83
85
  return;
@@ -3,18 +3,45 @@ import { useId } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { agentHandle } from "@jini-ai/agentic";
5
5
  import { useInfoTip } from "./InfoTip.hooks.js";
6
- import { infoTipBubbleStyle } from "./InfoTip.rules.js";
6
+ import { infoTipBubbleStyle, infoTipLines } from "./InfoTip.rules.js";
7
7
  /** Focus/hover tooltip that portals outside clipping ancestors; Escape dismisses in place. */
8
8
  export function InfoTip({ label, triggerLabel, descriptionId, placement: preferredPlacement, useTip = useInfoTip, agentHandle: handle, icon }) {
9
- const { open, placement, coords, iconRef, bubbleRef, show, hide, leave, toggle, handleIconKeyDown } = useTip({}, { placement: preferredPlacement === "inline" ? undefined : preferredPlacement });
9
+ const inline = preferredPlacement === "inline";
10
+ const { open, placement, coords, iconRef, bubbleRef, show, hide, leave, toggle, handleIconKeyDown } = useTip({}, { placement: inline ? undefined : preferredPlacement });
10
11
  const autoId = useId();
11
- const name = triggerLabel ?? label;
12
+ const { name, textId, describedBy } = infoTipNaming({ label, autoId }, { triggerLabel, descriptionId });
13
+ return (_jsxs("span", { className: inline ? "info-tip info-tip--inline" : "info-tip", children: [_jsx("span", { ref: iconRef, className: icon ? "info-tip-icon info-tip-icon--custom" : "info-tip-icon", role: "button", tabIndex: 0, "aria-label": name, "aria-expanded": open, ...(describedBy ? { "aria-describedby": describedBy } : {}), onMouseEnter: show, onMouseLeave: leave, onFocus: show, onBlur: hide, onClick: toggle, onKeyDown: handleIconKeyDown, ...(handle ? agentHandle({ handle }, { role: "button", label: name }) : {}), children: icon ?? "ⓘ" }), textId ? _jsx("span", { id: textId, hidden: true, children: label }) : null, open ? _jsx(InfoTipOpenCopy, { label: label, inline: inline, placement: placement, coords: coords, iconRef: iconRef, bubbleRef: bubbleRef }) : null] }));
14
+ }
15
+ /**
16
+ * The icon's accessible name and the id of the hidden copy of `label` (if any).
17
+ * @returns `name` for `aria-label`, `textId` for the hidden copy, `describedBy` for `aria-describedby`.
18
+ * @complexity O(1).
19
+ */
20
+ function infoTipNaming({ label, autoId }, { triggerLabel, descriptionId }) {
12
21
  // With no short name the icon is already named by the full text; describing it again would
13
22
  // make a screen reader read it twice.
14
23
  const textId = descriptionId ?? (triggerLabel ? autoId : undefined);
15
- const describedBy = triggerLabel ? textId : undefined;
16
- return (_jsxs("span", { className: preferredPlacement === "inline" ? "info-tip info-tip--inline" : "info-tip", children: [_jsx("span", { ref: iconRef, className: icon ? "info-tip-icon info-tip-icon--custom" : "info-tip-icon", role: "button", tabIndex: 0, "aria-label": name, "aria-expanded": open, ...(describedBy ? { "aria-describedby": describedBy } : {}), onMouseEnter: show, onMouseLeave: leave, onFocus: show, onBlur: hide, onClick: toggle, onKeyDown: handleIconKeyDown, ...(handle ? agentHandle({ handle: handle }, { role: "button", label: name }) : {}), children: icon ?? "ⓘ" }), textId ? _jsx("span", { id: textId, hidden: true, children: label }) : null, open && preferredPlacement === "inline" ? _jsx("span", { className: "info-tip-inline-copy", "aria-hidden": "true", children: label }) : open
17
- ? createPortal(_jsx("span", { ref: bubbleRef, className: placement === "below" ? "info-tip-bubble info-tip-bubble-below" : "info-tip-bubble", role: "presentation", "aria-hidden": "true", style: infoTipBubbleStyle(coords), children: label }), document.body)
18
- : null] }));
24
+ return { name: triggerLabel ?? label, textId, describedBy: triggerLabel ? textId : undefined };
25
+ }
26
+ /** The open tip: inline copy beside the icon, or the bubble portaled out of clipping ancestors. */
27
+ function InfoTipOpenCopy({ label, inline, placement, coords, iconRef, bubbleRef }) {
28
+ if (inline)
29
+ return _jsx("span", { className: "info-tip-inline-copy", "aria-hidden": "true", children: label });
30
+ return createPortal(_jsx("span", { ref: bubbleRef, className: placement === "below" ? "info-tip-bubble info-tip-bubble-below" : "info-tip-bubble", role: "presentation", "aria-hidden": "true", style: { ...infoTipBubbleStyle(coords), whiteSpace: "pre-line" }, children: _jsx(InfoTipBubbleText, { label: label }) }),
31
+ // A modal <dialog> sits in the browser's top layer, above anything portaled to body.
32
+ iconRef.current?.closest("dialog") ?? document.body);
33
+ }
34
+ /** The bubble's text: a hanging list when any line is a "• " bullet, else the label as-is. */
35
+ function InfoTipBubbleText({ label }) {
36
+ const lines = infoTipLines(label);
37
+ if (!lines)
38
+ return label;
39
+ // Keep the label's text separators; flex layout alone loses them from textContent.
40
+ return lines.map((line, index) => {
41
+ const separator = index < lines.length - 1 ? "\n" : null;
42
+ return line.bullet
43
+ ? _jsxs("span", { className: "info-tip-bullet", children: [_jsx("span", { "aria-hidden": "true", children: "\u2022" }), " ", _jsx("span", { children: line.text }), separator] }, index)
44
+ : _jsxs("span", { className: "info-tip-line", children: [line.text, separator] }, index);
45
+ });
19
46
  }
20
47
  //# sourceMappingURL=InfoTip.js.map
@@ -69,4 +69,17 @@ export declare function infoTipBubbleStyle(coords: {
69
69
  left: number;
70
70
  arrowLeft?: number | undefined;
71
71
  }): CSSProperties;
72
+ /** One line of a bubble that holds a bulleted list. */
73
+ export interface InfoTipLine {
74
+ text: string;
75
+ bullet: boolean;
76
+ }
77
+ /**
78
+ * Splits a label into lines when any line is a "• " bullet (e.g. a settings group's one tip, a line
79
+ * per option), so each bullet's wrapped text can hang past its dot; `pre-line` alone wraps under it.
80
+ * @param label The tip text; newlines separate lines.
81
+ * @returns The lines with their "• " removed, or `null` when the label has no bullet line.
82
+ * @complexity O(n) in the label's length.
83
+ */
84
+ export declare function infoTipLines(label: string): InfoTipLine[] | null;
72
85
  //# sourceMappingURL=InfoTip.rules.d.ts.map
@@ -32,25 +32,45 @@ const ARROW_INSET_PX = 10;
32
32
  export function placeInfoTipBubble(input, { bounds, preferred } = {}) {
33
33
  const { anchor, bubble, viewport } = input;
34
34
  const width = bubble && bubble.width > 0 ? bubble.width : MAX_BUBBLE_WIDTH_PX;
35
+ const height = bubble && bubble.height > 0 ? bubble.height : 0;
36
+ const { left, arrowLeft } = placeHorizontally({ anchor, width, viewportWidth: viewport.width }, { bounds });
37
+ const placement = placeVertically({ anchor, height, viewportHeight: viewport.height }, { preferred });
38
+ return { placement, top: placement === "above" ? anchor.top : anchor.top + anchor.height, left, arrowLeft };
39
+ }
40
+ /**
41
+ * The horizontal half of {@link placeInfoTipBubble}: the bubble's centre x and the arrow's offset.
42
+ * @param input.width The bubble's measured width, or the worst-case estimate before measurement.
43
+ * @param options.bounds A host panel's limits; used only when the bubble fits inside them.
44
+ * @returns The bubble's centre x and the arrow's x inside the bubble.
45
+ * @complexity O(1).
46
+ */
47
+ function placeHorizontally({ anchor, width, viewportWidth }, { bounds }) {
35
48
  const anchorCentre = anchor.left + anchor.width / 2;
36
- const viewportSpan = { lo: EDGE_MARGIN_PX, hi: viewport.width - EDGE_MARGIN_PX };
49
+ const viewportSpan = { lo: EDGE_MARGIN_PX, hi: viewportWidth - EDGE_MARGIN_PX };
37
50
  const boundsSpan = bounds
38
- ? { lo: Math.max(bounds.left, 0) + EDGE_MARGIN_PX, hi: Math.min(bounds.right, viewport.width) - EDGE_MARGIN_PX }
51
+ ? { lo: Math.max(bounds.left, 0) + EDGE_MARGIN_PX, hi: Math.min(bounds.right, viewportWidth) - EDGE_MARGIN_PX }
39
52
  : null;
40
53
  const span = boundsSpan && boundsSpan.hi - boundsSpan.lo >= width ? boundsSpan : viewportSpan;
41
54
  const left = span.hi - span.lo < width
42
- ? viewport.width / 2
55
+ ? viewportWidth / 2
43
56
  : Math.min(Math.max(anchorCentre, span.lo + width / 2), span.hi - width / 2);
44
57
  const bubbleLeftEdge = left - width / 2;
45
58
  const arrowLeft = Math.min(Math.max(anchorCentre - bubbleLeftEdge, ARROW_INSET_PX), width - ARROW_INSET_PX);
46
- const anchorBottom = anchor.top + anchor.height;
47
- const height = bubble && bubble.height > 0 ? bubble.height : 0;
59
+ return { left, arrowLeft };
60
+ }
61
+ /**
62
+ * The vertical half of {@link placeInfoTipBubble}: which side of the icon the bubble opens on.
63
+ * @param input.height The bubble's measured height, or 0 before measurement.
64
+ * @param options.preferred A host's preferred side; only "bottom" changes the default above-first rule.
65
+ * @returns The side the bubble opens on.
66
+ * @complexity O(1).
67
+ */
68
+ function placeVertically({ anchor, height, viewportHeight }, { preferred }) {
48
69
  const fitsAbove = height > 0 ? anchor.top >= height + GAP_PX : anchor.top >= ABOVE_HEADROOM_PX;
49
- const fitsBelow = height === 0 || anchorBottom + GAP_PX + height <= viewport.height;
50
- const placement = preferred === "bottom"
51
- ? (!fitsBelow && fitsAbove ? "above" : "below")
52
- : fitsAbove ? "above" : "below";
53
- return { placement, top: placement === "above" ? anchor.top : anchorBottom, left, arrowLeft };
70
+ if (preferred !== "bottom")
71
+ return fitsAbove ? "above" : "below";
72
+ const fitsBelow = height === 0 || anchor.top + anchor.height + GAP_PX + height <= viewportHeight;
73
+ return !fitsBelow && fitsAbove ? "above" : "below";
54
74
  }
55
75
  /**
56
76
  * The portal's inline style: position plus, once measured, the arrow offset as a CSS variable
@@ -65,4 +85,17 @@ export function infoTipBubbleStyle(coords) {
65
85
  style["--info-tip-arrow-left"] = `${coords.arrowLeft}px`;
66
86
  return style;
67
87
  }
88
+ /**
89
+ * Splits a label into lines when any line is a "• " bullet (e.g. a settings group's one tip, a line
90
+ * per option), so each bullet's wrapped text can hang past its dot; `pre-line` alone wraps under it.
91
+ * @param label The tip text; newlines separate lines.
92
+ * @returns The lines with their "• " removed, or `null` when the label has no bullet line.
93
+ * @complexity O(n) in the label's length.
94
+ */
95
+ export function infoTipLines(label) {
96
+ const lines = label.split("\n");
97
+ if (!lines.some((line) => line.startsWith("• ")))
98
+ return null;
99
+ return lines.map((line) => line.startsWith("• ") ? { text: line.slice(2), bullet: true } : { text: line, bullet: false });
100
+ }
68
101
  //# sourceMappingURL=InfoTip.rules.js.map
@@ -0,0 +1,30 @@
1
+ import { type ReactNode } from "react";
2
+ import { useLightbox } from "./Lightbox.hooks.js";
3
+ type LightboxPart = "dialog" | "close" | "previous" | "next";
4
+ export interface LightboxProps {
5
+ open: boolean;
6
+ /** Shown above the media (usually the file name) and used as the dialog's accessible name. */
7
+ title: string;
8
+ onClose: () => void;
9
+ /** The media itself. `img`/`video` children are contain-fit to the viewport and never upscaled. */
10
+ children?: ReactNode;
11
+ /** Zero-based position and set size; with `onNavigate` and `count > 1` the arrow pair renders. */
12
+ index?: number | undefined;
13
+ count?: number | undefined;
14
+ onNavigate?: ((index: number) => void) | undefined;
15
+ /** Caller-specific controls (download, copy, metadata), placed bottom-left opposite the arrows. */
16
+ actions?: ReactNode;
17
+ /** No locale is owned here; a translated caller passes its own accessible labels. */
18
+ labels?: Partial<Record<"close" | "previous" | "next", string>> | undefined;
19
+ /** Extra class on the `<dialog>` so a caller can scope its own media rules. */
20
+ className?: string | undefined;
21
+ /** Per-part attributes (agent handles, `data-jini-part` markers) without the shell knowing them. */
22
+ partAttrs?: Partial<Record<LightboxPart, Record<string, string>>> | undefined;
23
+ /** A fake controller can exercise rendering without a real native-dialog lifecycle. */
24
+ useLightboxHook?: typeof useLightbox | undefined;
25
+ }
26
+ /** Full-viewport media lightbox: title and counter top-left, close top-right, the media as large
27
+ * as fits, caller actions bottom-left and the previous/next pair together bottom-right. */
28
+ export declare function Lightbox(props: LightboxProps): import("react").JSX.Element;
29
+ export {};
30
+ //# sourceMappingURL=Lightbox.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { type MouseEvent, type RefObject, type SyntheticEvent } from "react";
2
+ /**
3
+ * Lifecycle for the shared `Lightbox`: native `<dialog>` open/close, focus capture and return,
4
+ * arrow-key navigation, Escape and backdrop dismissal. Absorbs the open/close and focus logic the
5
+ * media lightboxes and `ImagePreviewModal` each carried, so every caller gets the same behavior.
6
+ */
7
+ /** Idempotent native open. `showModal()` throws on an already-open dialog in real browsers; the
8
+ * attribute fallback covers DOM environments without it (jsdom). */
9
+ export declare function openLightboxDialog(dialog: HTMLDialogElement): void;
10
+ /** The close-side mirror of {@link openLightboxDialog} — same fallback and already-closed guard. */
11
+ export declare function closeLightboxDialog(dialog: HTMLDialogElement): void;
12
+ export interface LightboxHookInput {
13
+ open: boolean;
14
+ onClose: () => void;
15
+ /** Zero-based position in the set; with `count > 1` and `onNavigate` the arrow pair renders. */
16
+ index?: number | undefined;
17
+ count?: number | undefined;
18
+ onNavigate?: ((index: number) => void) | undefined;
19
+ }
20
+ export interface LightboxController {
21
+ dialogRef: RefObject<HTMLDialogElement | null>;
22
+ closeRef: RefObject<HTMLButtonElement | null>;
23
+ prevRef: RefObject<HTMLButtonElement | null>;
24
+ nextRef: RefObject<HTMLButtonElement | null>;
25
+ /** Whether the arrow pair renders at all (a single item has nowhere to go). */
26
+ navigable: boolean;
27
+ hasPrev: boolean;
28
+ hasNext: boolean;
29
+ /** "3 / 12", or "" when there is no set to count. */
30
+ counter: string;
31
+ previous: () => void;
32
+ next: () => void;
33
+ handleCancel: (event: SyntheticEvent<HTMLDialogElement>) => void;
34
+ handleClick: (event: MouseEvent<HTMLDialogElement>) => void;
35
+ }
36
+ /** Marks the stage's empty area: the dialog fills the viewport, so "backdrop" is any click that
37
+ * lands on the dialog itself or the stage around the media rather than on a control or the media. */
38
+ export declare const LIGHTBOX_DISMISS_ATTR = "data-jini-lightbox-dismiss";
39
+ export declare function useLightbox(input: LightboxHookInput): LightboxController;
40
+ //# sourceMappingURL=Lightbox.hooks.d.ts.map