@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
@@ -1,36 +1,42 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useMemo, useRef, useState } from 'react';
3
3
  import { useT } from '../../../i18n/index.js';
4
- import { useDebouncedValue } from '../../../../react/hooks/useDebouncedValue.js';
5
- import { DEFAULT_SEARCH_DEBOUNCE_MS } from '../../constants.js';
6
- import { buildFacetLabelMap, dayKeyFromTimestamp, resolveFacetLabel } from '../../rules.js';
4
+ import { dayKeyFromTimestamp, resolveFacetLabel } from '../../rules.js';
7
5
  import { useWiredAssetGridData } from '../hooks/useAssetGridData.js';
8
6
  import { useWiredAssetGridLiveUpdates } from '../hooks/useAssetGridLiveUpdates.js';
9
7
  import { useAssetGridSelection } from '../hooks/useAssetGridSelection.js';
10
8
  import { useRubberBandDrag } from '../hooks/useRubberBandDrag.js';
11
9
  import { useAssetGridKeyboardShortcuts } from '../hooks/useAssetGridKeyboardShortcuts.js';
10
+ import { useAssetGridFilters } from './useAssetGridFilters.js';
12
11
  import { AssetCard } from './AssetCard.js';
13
12
  import { AssetGridBody } from './AssetGridBody.js';
14
13
  import { AssetGridToolbar } from './AssetGridToolbar.js';
15
14
  import { SelectionActionBar } from './SelectionActionBar.js';
16
15
  import { SelectionBand } from './SelectionBand.js';
17
16
  import { DeleteConfirmDialog } from './DeleteConfirmDialog.js';
17
+ function renderGridContent({ loading, emptyState, t, ...bodyProps }, _options = {}) {
18
+ if (loading && bodyProps.assets.length === 0)
19
+ return _jsx("p", { className: "asset-grid-empty", children: t('Loading…') });
20
+ if (bodyProps.assets.length === 0)
21
+ return emptyState ?? _jsx("div", { className: "asset-grid-empty", children: t('No assets yet.') });
22
+ return _jsx(AssetGridBody, { ...bodyProps });
23
+ }
18
24
  /**
19
25
  * A live-updating, filterable, multi-selectable asset grid — the generic
20
26
  * pattern behind a photo library / DAM. See `packages/ui/source-map.md` for
21
27
  * the full retained-behavior manifest against the original `LibrarySection.tsx`.
22
28
  */
23
- export function AssetGrid({ active = true, selectors, dependencies, kindFacets = [], sourceFacets = [], renderThumbnail, renderThumbnailPlaceholder, renderCardExtra, renderBulkActions, onDeleteAsset, onDeleteSelected, onPreview, isPreviewOpen = false, toolbarActions, emptyState, searchPlaceholder, searchDebounceMs = DEFAULT_SEARCH_DEBOUNCE_MS, liveUpdateCoalesceMs, initialViewMode = 'grid', useWiredAssetGridData: useWiredAssetGridDataHook = useWiredAssetGridData, useAssetGridSelection: useAssetGridSelectionHook = useAssetGridSelection, }) {
29
+ export function AssetGrid({ active = true, selectors, dependencies, kindFacets: configuredKindFacets, sourceFacets: configuredSourceFacets, renderThumbnail, renderThumbnailPlaceholder, renderCardExtra, renderBulkActions, onDeleteAsset, onDeleteSelected, onPreview, isPreviewOpen = false, toolbarActions, emptyState, searchPlaceholder, searchDebounceMs, liveUpdateCoalesceMs, initialViewMode = 'grid', useWiredAssetGridData: useWiredAssetGridDataHook = useWiredAssetGridData, useAssetGridSelection: useAssetGridSelectionHook = useAssetGridSelection, }) {
24
30
  const t = useT();
25
- const [kind, setKind] = useState('');
26
- const [source, setSource] = useState('');
27
- const [search, setSearch] = useState('');
31
+ const { kind, setKind, source, setSource, search, setSearch, debouncedSearch, filtersActive, kindFacets, sourceFacets, kindFacetLabels, sourceFacetLabels, } = useAssetGridFilters({
32
+ kindFacets: configuredKindFacets,
33
+ sourceFacets: configuredSourceFacets,
34
+ searchDebounceMs,
35
+ });
28
36
  const [viewMode, setViewMode] = useState(initialViewMode);
29
37
  const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
30
38
  /** Message from a delete the host rejected. Rendered inline — see `runDelete`. */
31
39
  const [deleteError, setDeleteError] = useState(null);
32
- const debouncedSearch = useDebouncedValue({ value: search, delayMs: searchDebounceMs });
33
- const filtersActive = !!(kind || source || debouncedSearch.trim());
34
40
  // `dependencies` (host-supplied, or omitted to fall back to the package's
35
41
  // in-memory fake) is resolved by each wirer independently — neither this
36
42
  // component nor the real `useAssetGridData`/`useAssetGridLiveUpdates`
@@ -102,11 +108,16 @@ export function AssetGrid({ active = true, selectors, dependencies, kindFacets =
102
108
  setSelectedIds(new Set());
103
109
  });
104
110
  }, [selectedIds, onDeleteSelected, setAssets, setSelectedIds, runDelete]);
105
- const handleDeleteAsset = useCallback(async (id) => {
106
- await runDelete(onDeleteAsset ? (batch) => onDeleteAsset(batch[0]) : null, [id], () => {
107
- setAssets((prev) => prev.filter((a) => a.id !== id));
108
- });
109
- }, [onDeleteAsset, setAssets, runDelete]);
111
+ // Built only when the host supplied `onDeleteAsset`: the card shows its per-asset delete control
112
+ // only when it receives a handler, so there is no "no handler" path to route through `runDelete`
113
+ // here (the old inline `onDeleteAsset ? … : null` could never see `null` from a rendered card).
114
+ const handleDeleteAsset = useMemo(() => onDeleteAsset
115
+ ? async (id) => {
116
+ await runDelete((batch) => onDeleteAsset(batch[0]), [id], () => {
117
+ setAssets((prev) => prev.filter((a) => a.id !== id));
118
+ });
119
+ }
120
+ : undefined, [onDeleteAsset, setAssets, runDelete]);
110
121
  const handlePreview = useCallback((id) => {
111
122
  const asset = assets.find((a) => a.id === id);
112
123
  if (asset)
@@ -122,13 +133,11 @@ export function AssetGrid({ active = true, selectors, dependencies, kindFacets =
122
133
  onClearSelection: clearSelection,
123
134
  onRequestDeleteSelected: requestDeleteSelected,
124
135
  });
125
- const kindFacetLabels = useMemo(() => buildFacetLabelMap(kindFacets), [kindFacets]);
126
- const sourceFacetLabels = useMemo(() => buildFacetLabelMap(sourceFacets), [sourceFacets]);
127
136
  const getDayKey = useMemo(() => selectors.getDayKey ?? ((asset) => dayKeyFromTimestamp(selectors.getTimestamp(asset))), [selectors]);
128
137
  const renderCard = useCallback((asset, index) => {
129
138
  const kindValue = selectors.getKind(asset);
130
139
  const sourceValue = selectors.getSource?.(asset);
131
- return (_jsx(AssetCard, { asset: asset, index: index, selected: selectedIds.has(asset.id), title: selectors.getTitle(asset), subtitle: selectors.getSubtitle?.(asset), kindLabel: resolveFacetLabel(kindValue, kindFacetLabels), sourceLabel: sourceValue ? resolveFacetLabel(sourceValue, sourceFacetLabels) : undefined, renderThumbnail: renderThumbnail, renderThumbnailPlaceholder: renderThumbnailPlaceholder, onToggle: toggleOne, onRange: rangeTo, onPreview: handlePreview, onDeleteAsset: onDeleteAsset ? handleDeleteAsset : undefined, renderCardExtra: renderCardExtra }, asset.id));
140
+ return (_jsx(AssetCard, { asset: asset, index: index, selected: selectedIds.has(asset.id), title: selectors.getTitle(asset), subtitle: selectors.getSubtitle?.(asset), kindLabel: resolveFacetLabel(kindValue, kindFacetLabels), sourceLabel: sourceValue ? resolveFacetLabel(sourceValue, sourceFacetLabels) : undefined, renderThumbnail: renderThumbnail, renderThumbnailPlaceholder: renderThumbnailPlaceholder, onToggle: toggleOne, onRange: rangeTo, onPreview: handlePreview, onDeleteAsset: handleDeleteAsset, renderCardExtra: renderCardExtra }, asset.id));
132
141
  }, [
133
142
  selectedIds,
134
143
  selectors,
@@ -143,6 +152,17 @@ export function AssetGrid({ active = true, selectors, dependencies, kindFacets =
143
152
  handleDeleteAsset,
144
153
  renderCardExtra,
145
154
  ]);
146
- return (_jsxs("div", { className: "asset-grid-root", children: [_jsx(AssetGridToolbar, { search: search, onSearchChange: setSearch, searchPlaceholder: searchPlaceholder, kind: kind, onKindChange: setKind, kindFacets: kindFacets, source: source, onSourceChange: setSource, sourceFacets: sourceFacets, viewMode: viewMode, onViewModeChange: setViewMode, onRefresh: () => void reload(), loading: loading, toolbarActions: toolbarActions }), selectedIds.size > 0 && !dragging ? (_jsx(SelectionActionBar, { selectedIds: [...selectedIds], onSelectAll: selectAll, onClear: clearSelection, onRequestDelete: requestDeleteSelected, renderBulkActions: renderBulkActions })) : null, loading && assets.length === 0 ? (_jsx("p", { className: "asset-grid-empty", children: t('Loading…') })) : assets.length === 0 ? ((emptyState ?? _jsx("div", { className: "asset-grid-empty", children: t('No assets yet.') }))) : (_jsx(AssetGridBody, { viewMode: viewMode, assets: assets, getDayKey: getDayKey, containerRef: containerRef, onMouseDown: onMouseDown, selecting: selectedIds.size > 0, renderCard: renderCard })), _jsx(SelectionBand, { band: band }), deleteError ? (_jsx("p", { className: "jini-hint jini-hint-error", role: "alert", children: deleteError })) : null, confirmDeleteOpen ? (_jsx(DeleteConfirmDialog, { count: selectedIds.size, onCancel: () => setConfirmDeleteOpen(false), onConfirm: () => void confirmDelete() })) : null] }));
155
+ return (_jsxs("div", { className: "asset-grid-root", children: [_jsx(AssetGridToolbar, { search: search, onSearchChange: setSearch, searchPlaceholder: searchPlaceholder, kind: kind, onKindChange: setKind, kindFacets: kindFacets, source: source, onSourceChange: setSource, sourceFacets: sourceFacets, viewMode: viewMode, onViewModeChange: setViewMode, onRefresh: () => void reload(), loading: loading, toolbarActions: toolbarActions }), selectedIds.size > 0 && !dragging ? (_jsx(SelectionActionBar, { selectedIds: [...selectedIds], onSelectAll: selectAll, onClear: clearSelection, onRequestDelete: requestDeleteSelected, renderBulkActions: renderBulkActions })) : null, renderGridContent({
156
+ loading,
157
+ emptyState,
158
+ t,
159
+ viewMode,
160
+ assets,
161
+ getDayKey,
162
+ containerRef,
163
+ onMouseDown,
164
+ selecting: selectedIds.size > 0,
165
+ renderCard,
166
+ }), _jsx(SelectionBand, { band: band }), deleteError ? (_jsx("p", { className: "jini-hint jini-hint-error", role: "alert", children: deleteError })) : null, confirmDeleteOpen ? (_jsx(DeleteConfirmDialog, { count: selectedIds.size, onCancel: () => setConfirmDeleteOpen(false), onConfirm: () => void confirmDelete() })) : null] }));
147
167
  }
148
168
  //# sourceMappingURL=AssetGrid.js.map
@@ -0,0 +1,21 @@
1
+ import type { AssetGridFacetOption } from '../../types.js';
2
+ /** Owns the editable filters and the debounced membership used by data/live updates. */
3
+ export declare function useAssetGridFilters({ kindFacets, sourceFacets, searchDebounceMs, }: {
4
+ kindFacets: AssetGridFacetOption[] | undefined;
5
+ sourceFacets: AssetGridFacetOption[] | undefined;
6
+ searchDebounceMs: number | undefined;
7
+ }, _options?: Record<string, never>): {
8
+ kind: string;
9
+ setKind: import("react").Dispatch<import("react").SetStateAction<string>>;
10
+ source: string;
11
+ setSource: import("react").Dispatch<import("react").SetStateAction<string>>;
12
+ search: string;
13
+ setSearch: import("react").Dispatch<import("react").SetStateAction<string>>;
14
+ debouncedSearch: string;
15
+ filtersActive: boolean;
16
+ kindFacets: AssetGridFacetOption[];
17
+ sourceFacets: AssetGridFacetOption[];
18
+ kindFacetLabels: Map<string, string>;
19
+ sourceFacetLabels: Map<string, string>;
20
+ };
21
+ //# sourceMappingURL=useAssetGridFilters.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { useDebouncedValue } from '../../../../react/hooks/useDebouncedValue.js';
3
+ import { DEFAULT_SEARCH_DEBOUNCE_MS } from '../../constants.js';
4
+ import { buildFacetLabelMap } from '../../rules.js';
5
+ /** Owns the editable filters and the debounced membership used by data/live updates. */
6
+ export function useAssetGridFilters({ kindFacets = [], sourceFacets = [], searchDebounceMs = DEFAULT_SEARCH_DEBOUNCE_MS, }, _options = {}) {
7
+ const [kind, setKind] = useState('');
8
+ const [source, setSource] = useState('');
9
+ const [search, setSearch] = useState('');
10
+ const debouncedSearch = useDebouncedValue({ value: search, delayMs: searchDebounceMs });
11
+ const filtersActive = !!(kind || source || debouncedSearch.trim());
12
+ const kindFacetLabels = useMemo(() => buildFacetLabelMap(kindFacets), [kindFacets]);
13
+ const sourceFacetLabels = useMemo(() => buildFacetLabelMap(sourceFacets), [sourceFacets]);
14
+ return {
15
+ kind, setKind, source, setSource, search, setSearch,
16
+ debouncedSearch, filtersActive, kindFacets, sourceFacets, kindFacetLabels, sourceFacetLabels,
17
+ };
18
+ }
19
+ //# sourceMappingURL=useAssetGridFilters.js.map
@@ -1,5 +1,19 @@
1
1
  import { useGlobalKeydown } from '../../../../browser/index.js';
2
2
  import { isTypingTarget } from '../../rules.js';
3
+ function handleSelectionKey({ event, typing, hasSelection, isPreviewOpen, onClearSelection, onRequestDeleteSelected }, _options = {}) {
4
+ if (event.key === 'Escape') {
5
+ if (isPreviewOpen)
6
+ return; // the preview surface owns Escape while it's open
7
+ if (hasSelection)
8
+ onClearSelection();
9
+ }
10
+ else if (event.key === 'Delete' || event.key === 'Backspace') {
11
+ if (typing || isPreviewOpen || !hasSelection)
12
+ return;
13
+ event.preventDefault();
14
+ onRequestDeleteSelected();
15
+ }
16
+ }
3
17
  /** Cmd/Ctrl+A select-all, Escape clear-selection, Delete/Backspace triggers the bulk-delete confirm flow. */
4
18
  export function useAssetGridKeyboardShortcuts(params) {
5
19
  const { active, enabled, hasAssets, hasSelection, isPreviewOpen, onSelectAll, onClearSelection, onRequestDeleteSelected, } = params;
@@ -14,17 +28,8 @@ export function useAssetGridKeyboardShortcuts(params) {
14
28
  e.preventDefault();
15
29
  onSelectAll();
16
30
  }
17
- else if (e.key === 'Escape') {
18
- if (isPreviewOpen)
19
- return; // the preview surface owns Escape while it's open
20
- if (hasSelection)
21
- onClearSelection();
22
- }
23
- else if (e.key === 'Delete' || e.key === 'Backspace') {
24
- if (typing || isPreviewOpen || !hasSelection)
25
- return;
26
- e.preventDefault();
27
- onRequestDeleteSelected();
31
+ else {
32
+ handleSelectionKey({ event: e, typing, hasSelection, isPreviewOpen, onClearSelection, onRequestDeleteSelected });
28
33
  }
29
34
  },
30
35
  }, { enabled: active });
@@ -19,6 +19,8 @@ export interface UseAssetGridLiveUpdatesParams<TAsset extends AssetGridItem> {
19
19
  * id all fall back to a single full reload for that window — see
20
20
  * `packages/ui/archived provenance ledger` for why (the original OD comment: "a filtered
21
21
  * view can't predict membership client-side").
22
+ * Disposed subscriptions discard in-flight incremental reads. Failed passes report a
23
+ * fixed diagnostic and request a full reconciliation on the next live event.
22
24
  */
23
25
  export declare function useAssetGridLiveUpdates<TAsset extends AssetGridItem>(params: UseAssetGridLiveUpdatesParams<TAsset>): void;
24
26
  export type UseWiredAssetGridLiveUpdatesParams<TAsset extends AssetGridItem> = Omit<UseAssetGridLiveUpdatesParams<TAsset>, 'liveUpdates' | 'fetchAssetById'> & {
@@ -2,6 +2,18 @@ import { useEffect, useMemo, useRef } from 'react';
2
2
  import { DEFAULT_LIVE_UPDATE_COALESCE_MS } from '../../constants.js';
3
3
  import { mergeIngestedAssets } from '../../rules.js';
4
4
  import { createFakeAssetGridDependencies } from '../../dependencies.js';
5
+ /** Drains pending deletes and cancels matching ingests before the pass can fetch.
6
+ * Mutates the pending sets and schedules the asset update. @complexity O(d + a) deletes and assets. */
7
+ function applyPendingDeletes({ pendingDelete, pendingIngest, setAssets }, _options = {}) {
8
+ // Deletes are free (no fetch); apply them first.
9
+ if (pendingDelete.size) {
10
+ const del = new Set(pendingDelete);
11
+ pendingDelete.clear();
12
+ for (const id of del)
13
+ pendingIngest.delete(id);
14
+ setAssets((prev) => prev.filter((a) => !del.has(a.id)));
15
+ }
16
+ }
5
17
  /**
6
18
  * Subscribes to a live-update transport and reconciles incremental
7
19
  * ingest/delete events into the current asset list. Events are coalesced
@@ -11,6 +23,8 @@ import { createFakeAssetGridDependencies } from '../../dependencies.js';
11
23
  * id all fall back to a single full reload for that window — see
12
24
  * `packages/ui/archived provenance ledger` for why (the original OD comment: "a filtered
13
25
  * view can't predict membership client-side").
26
+ * Disposed subscriptions discard in-flight incremental reads. Failed passes report a
27
+ * fixed diagnostic and request a full reconciliation on the next live event.
14
28
  */
15
29
  export function useAssetGridLiveUpdates(params) {
16
30
  const { active, liveUpdates, filtersActive, fetchAssetById, setAssets, reload, coalesceMs = DEFAULT_LIVE_UPDATE_COALESCE_MS } = params;
@@ -33,19 +47,17 @@ export function useAssetGridLiveUpdates(params) {
33
47
  useEffect(() => {
34
48
  if (!active || !liveUpdates)
35
49
  return;
50
+ let disposed = false;
36
51
  let timer = null;
37
52
  const pendingIngest = new Set();
38
53
  const pendingDelete = new Set();
39
54
  let pendingFull = false;
55
+ // No `disposed` check on entry: a pass only ever starts from `flush`, whose first pass runs
56
+ // synchronously from a timer that cleanup cancels, and whose later passes are gated by the loop's
57
+ // own `!disposed` condition — so a pass can never begin after disposal. The post-fetch check
58
+ // below still matters, because disposal can land while a pass is awaiting.
40
59
  const runFlushPass = async () => {
41
- // Deletes are free (no fetch); apply them first.
42
- if (pendingDelete.size) {
43
- const del = new Set(pendingDelete);
44
- pendingDelete.clear();
45
- for (const id of del)
46
- pendingIngest.delete(id);
47
- setAssets((prev) => prev.filter((a) => !del.has(a.id)));
48
- }
60
+ applyPendingDeletes({ pendingDelete, pendingIngest, setAssets });
49
61
  if (pendingFull || filtersActiveRef.current) {
50
62
  pendingFull = false;
51
63
  pendingIngest.clear();
@@ -61,6 +73,9 @@ export function useAssetGridLiveUpdates(params) {
61
73
  return;
62
74
  }
63
75
  const fetched = await Promise.all(ids.map((id) => fetchById(id)));
76
+ // The same state setter can outlive this subscription (for example after deactivation).
77
+ if (disposed)
78
+ return;
64
79
  // A missing fetch is ambiguous (filtered out? race?) — reload instead.
65
80
  if (fetched.some((a) => a === null)) {
66
81
  await reloadRef.current();
@@ -105,15 +120,23 @@ export function useAssetGridLiveUpdates(params) {
105
120
  try {
106
121
  do {
107
122
  rerun = false;
108
- await runFlushPass();
109
- } while (rerun);
123
+ try {
124
+ await runFlushPass();
125
+ }
126
+ catch {
127
+ if (!disposed) {
128
+ pendingFull = true;
129
+ console.error('[asset-grid] Live update failed.');
130
+ }
131
+ }
132
+ } while (rerun && !disposed);
110
133
  }
111
134
  finally {
112
135
  flushing = false;
113
136
  }
114
137
  };
115
138
  const schedule = () => {
116
- if (timer)
139
+ if (disposed || timer)
117
140
  return;
118
141
  timer = setTimeout(() => void flush(), coalesceMs);
119
142
  };
@@ -132,6 +155,7 @@ export function useAssetGridLiveUpdates(params) {
132
155
  },
133
156
  });
134
157
  return () => {
158
+ disposed = true;
135
159
  if (timer)
136
160
  clearTimeout(timer);
137
161
  unsubscribe();
@@ -1,10 +1,10 @@
1
- import { type ReactNode } from 'react';
1
+ import type { ReactNode } from 'react';
2
2
  import type { AssetTreeDependencies } from '../../ports.js';
3
3
  import type { AssetTreeFileItem, AssetTreeFolderItem, AssetTreeKindConfigMap, AssetTreeNavState, AssetTreeSelectors, AssetTreeToolbarAction } from '../../types.js';
4
- import { useAssetTreeNavigation } from '../hooks/useAssetTreeNavigation.js';
5
- import { useAssetTreePreview } from '../hooks/useAssetTreePreview.js';
6
- import { useAssetTreeRename } from '../hooks/useAssetTreeRename.js';
7
- import { useAssetTreeSelection } from '../hooks/useAssetTreeSelection.js';
4
+ import type { useAssetTreeNavigation } from '../hooks/useAssetTreeNavigation.js';
5
+ import type { useAssetTreePreview } from '../hooks/useAssetTreePreview.js';
6
+ import type { useAssetTreeRename } from '../hooks/useAssetTreeRename.js';
7
+ import type { useAssetTreeSelection } from '../hooks/useAssetTreeSelection.js';
8
8
  export interface AssetTreeBrowserProps<TFile extends AssetTreeFileItem> {
9
9
  files: TFile[];
10
10
  /** Persisted folders, including empty ones — see `AssetTreeFolderItem`'s doc comment. */
@@ -59,5 +59,5 @@ export interface AssetTreeBrowserProps<TFile extends AssetTreeFileItem> {
59
59
  * section, project menu, rotating-tip footer, and file-kind-specific
60
60
  * preview rendering).
61
61
  */
62
- export declare function AssetTreeBrowser<TFile extends AssetTreeFileItem>({ files, folders, selectors, kindConfig, sectionOrder, dependencies, rootLabel, reloading, navState, onNavStateChange, onSelectionChange, onOpenFile, onRenameFile, onDeleteFile, onDeleteFiles, onUploadFiles, getFileUrl, downloadFiles, selectInitialPreviewFile, renderPreviewThumbnail, thumbnailIsInteractive, toolbarActions, emptyStateActions, footer, useAssetTreeNavigation: useAssetTreeNavigationHook, useAssetTreePreview: useAssetTreePreviewHook, useAssetTreeRename: useAssetTreeRenameHook, useAssetTreeSelection: useAssetTreeSelectionHook, }: AssetTreeBrowserProps<TFile>): import("react").JSX.Element;
62
+ export declare function AssetTreeBrowser<TFile extends AssetTreeFileItem>(props: AssetTreeBrowserProps<TFile>): import("react").JSX.Element;
63
63
  //# sourceMappingURL=AssetTreeBrowser.d.ts.map
@@ -1,27 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useMemo } from 'react';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
2
  import { useT } from '../../../i18n/index.js';
4
- import { DEFAULT_KIND_CONFIG_MAP, DEFAULT_SECTION_ORDER, EMPTY_TOOLBAR_ACTIONS } from '../../constants.js';
5
- import { createBrowserAssetTreeDependencies } from '../../dependencies.js';
6
- import { basenameForRename, canCopyLocalPath, countFilesUnderDir, resolveKindConfig } from '../../rules.js';
7
- import { useAssetTreeBatchActions } from '../hooks/useAssetTreeBatchActions.js';
8
- import { useAssetTreeClipboardPasteUpload } from '../hooks/useAssetTreeClipboardPasteUpload.js';
9
- import { useAssetTreeCopyLocalPath } from '../hooks/useAssetTreeCopyLocalPath.js';
10
- import { useAssetTreeDragUpload } from '../hooks/useAssetTreeDragUpload.js';
11
- import { useAssetTreeNavigation } from '../hooks/useAssetTreeNavigation.js';
12
- import { useAssetTreePreview } from '../hooks/useAssetTreePreview.js';
13
- import { useAssetTreeRename } from '../hooks/useAssetTreeRename.js';
14
- import { useAssetTreeRowMenu } from '../hooks/useAssetTreeRowMenu.js';
15
- import { useAssetTreeSelection } from '../hooks/useAssetTreeSelection.js';
16
- import { AssetTreeBreadcrumbs } from './AssetTreeBreadcrumbs.js';
17
- import { AssetTreeEmptyState } from './AssetTreeEmptyState.js';
18
- import { AssetTreeFileRow } from './AssetTreeFileRow.js';
19
- import { AssetTreeFolderRow } from './AssetTreeFolderRow.js';
20
- import { AssetTreeRowMenu } from './AssetTreeRowMenu.js';
21
- import { AssetTreeSelectionBar } from './AssetTreeSelectionBar.js';
22
- import { AssetTreeToolbar } from './AssetTreeToolbar.js';
23
- import { AssetTreeUploadErrorBanner } from './AssetTreeUploadErrorBanner.js';
24
- import { FilePreviewPane } from './FilePreviewPane.js';
3
+ import { DEFAULT_KIND_CONFIG_MAP } from '../../constants.js';
4
+ import { AssetTreeBrowserBody, AssetTreeBrowserPreview, AssetTreeBrowserRowMenu, AssetTreeBrowserTopbar, } from './AssetTreeBrowserParts.js';
5
+ import { useAssetTreeBrowserController } from './useAssetTreeBrowserController.js';
25
6
  /**
26
7
  * A directory-navigable file tree: breadcrumbs, a host-supplied toolbar,
27
8
  * kind-grouped sections with a pinned Folders section, row selection with
@@ -33,118 +14,18 @@ import { FilePreviewPane } from './FilePreviewPane.js';
33
14
  * section, project menu, rotating-tip footer, and file-kind-specific
34
15
  * preview rendering).
35
16
  */
36
- export function AssetTreeBrowser({ files, folders, selectors, kindConfig = DEFAULT_KIND_CONFIG_MAP, sectionOrder = DEFAULT_SECTION_ORDER, dependencies, rootLabel, reloading = false, navState, onNavStateChange, onSelectionChange, onOpenFile, onRenameFile, onDeleteFile, onDeleteFiles, onUploadFiles, getFileUrl, downloadFiles, selectInitialPreviewFile, renderPreviewThumbnail, thumbnailIsInteractive = false, toolbarActions = EMPTY_TOOLBAR_ACTIONS, emptyStateActions = EMPTY_TOOLBAR_ACTIONS, footer, useAssetTreeNavigation: useAssetTreeNavigationHook = useAssetTreeNavigation, useAssetTreePreview: useAssetTreePreviewHook = useAssetTreePreview, useAssetTreeRename: useAssetTreeRenameHook = useAssetTreeRename, useAssetTreeSelection: useAssetTreeSelectionHook = useAssetTreeSelection, }) {
17
+ export function AssetTreeBrowser(props) {
37
18
  const t = useT();
38
- // KNOWN FOLLOW-UP (flagged by the 2026-07-22 verification pass, not yet
39
- // fixed): this has the same useWiredX gap ConnectorsBrowser had — `deps`
40
- // resolution is still inline instead of behind a `useWiredAssetTreeBrowser`
41
- // wrapper. Deliberately not fixed here: unlike ConnectorsBrowser's `deps`
42
- // (fed to exactly the 3 already-overridable sub-hooks), this `deps` also
43
- // feeds `useAssetTreeRowMenu`, `useAssetTreeClipboardPasteUpload`, and
44
- // `useAssetTreeCopyLocalPath` below — none of which currently have their
45
- // own override props — so collapsing it into one wired hook is a real
46
- // design decision (what belongs in the wired hook vs. stays overridable)
47
- // rather than a mechanical port of the ConnectorsBrowser pattern. Lower
48
- // severity than that one too: these are DOM/clipboard adapters, not
49
- // remote data fetching.
50
- const deps = useMemo(() => dependencies ?? createBrowserAssetTreeDependencies(), [dependencies]);
51
- const navigation = useAssetTreeNavigationHook({
52
- files,
53
- folders,
54
- sectionOrder,
55
- getKind: selectors.getKind,
56
- getModifiedAt: selectors.getModifiedAt,
57
- navState,
58
- onNavStateChange,
59
- });
60
- const preview = useAssetTreePreviewHook(files, selectInitialPreviewFile);
61
- // Declared before `useAssetTreeSelection` so its in-flight `renaming.path`
62
- // (if any) can be threaded into selection's prune logic as
63
- // `pendingRenamePath` — see that hook's doc comment for the race this
64
- // closes. `onRenamed` referencing `selection` here (declared below) is
65
- // safe: it's a closure that only actually runs later, once `selection` is
66
- // fully bound.
67
- const rename = useAssetTreeRenameHook({
68
- currentDir: navigation.currentDir,
69
- onRenameFile,
70
- onRenamed: (oldPath, renamedFile) => {
71
- if (preview.previewPath === oldPath)
72
- preview.setPreviewPath(renamedFile.path);
73
- selection.renamePath(oldPath, renamedFile.path);
74
- },
75
- });
76
- const selection = useAssetTreeSelectionHook(navigation.filesAtCurrentDir, navigation.currentDir, rename.renaming?.path ?? null);
77
- const rowMenu = useAssetTreeRowMenu(deps.dom);
78
- const dragUpload = useAssetTreeDragUpload(onUploadFiles);
79
- useAssetTreeClipboardPasteUpload({
80
- dom: deps.dom,
81
- onUploadFiles,
82
- onBeforeUpload: dragUpload.clearDropReadError,
83
- });
84
- const batchActions = useAssetTreeBatchActions({ selected: selection.selected, onDeleteFiles, downloadFiles });
85
- const copyLocalPath = useAssetTreeCopyLocalPath(deps.clipboard);
86
- useEffect(() => {
87
- onSelectionChange?.(selection.selected);
88
- }, [selection.selected, onSelectionChange]);
89
- function findFile(path) {
90
- return files.find((f) => f.path === path);
91
- }
92
- function openPath(path) {
93
- const target = findFile(path);
94
- if (target)
95
- onOpenFile(target);
96
- }
97
- const menuFile = rowMenu.menuPos ? (findFile(rowMenu.menuPos.path) ?? null) : null;
19
+ const { files, folders, selectors, kindConfig = DEFAULT_KIND_CONFIG_MAP, getFileUrl } = props;
20
+ const controller = useAssetTreeBrowserController(props, props);
21
+ const { navigation, preview, selection, rowMenu, dragUpload } = controller;
98
22
  const hasSelection = selection.selected.size > 0;
99
- const isEmpty = files.length === 0 && (folders?.length ?? 0) === 0;
100
23
  const previewedFile = preview.previewFile;
101
- const previewKindConfig = previewedFile ? resolveKindConfig(selectors.getKind(previewedFile), kindConfig) : null;
102
- return (_jsxs("div", { className: `asset-tree-panel${previewedFile ? '' : ' no-preview'}${hasSelection ? ' has-selection' : ''}`, children: [reloading ? (_jsx("div", { className: "asset-tree-reloading-overlay", "data-testid": "asset-tree-reloading", children: t('Loading…') })) : null, _jsxs("div", { className: "asset-tree-main", children: [_jsxs("div", { className: "asset-tree-topbar", children: [_jsx("div", { className: "asset-tree-topbar-left", children: _jsx(AssetTreeBreadcrumbs, { currentDir: navigation.currentDir, rootLabel: rootLabel ?? t('Files'), onNavigate: navigation.setCurrentDir }) }), _jsx("div", { className: "asset-tree-topbar-right", children: _jsx(AssetTreeToolbar, { actions: toolbarActions }) })] }), _jsxs("div", { className: "asset-tree-body", "data-testid": "asset-tree-body", onDragEnter: dragUpload.onDragEnter, onDragOver: dragUpload.onDragOver, onDragLeave: dragUpload.onDragLeave, onDrop: dragUpload.onDrop, children: [dragUpload.dropReadError && !preview.previewPath ? (_jsx(AssetTreeUploadErrorBanner, { message: dragUpload.dropReadError, onDismiss: dragUpload.clearDropReadError })) : null, hasSelection ? (_jsx(AssetTreeSelectionBar, { count: selection.selected.size, onClear: selection.clearSelection, onDelete: () => void batchActions.deleteSelected(), deleting: batchActions.deleting, onDownload: downloadFiles ? () => void batchActions.downloadSelected() : undefined, downloading: batchActions.downloading, downloadError: batchActions.downloadError })) : null, isEmpty ? (_jsx(AssetTreeEmptyState, { actions: emptyStateActions })) : (_jsxs(_Fragment, { children: [navigation.dirsAtCurrentDir.length > 0 ? (_jsxs("div", { className: "asset-tree-section", children: [_jsxs("div", { className: "asset-tree-section-label", children: [t('Folders'), _jsx("span", { className: "asset-tree-section-count", children: navigation.dirsAtCurrentDir.length })] }), navigation.dirsAtCurrentDir.map((dirName) => {
103
- const fullPath = navigation.currentDir === '' ? dirName : `${navigation.currentDir}/${dirName}`;
104
- return (_jsx(AssetTreeFolderRow, { name: dirName, path: fullPath, fileCount: countFilesUnderDir(files, fullPath), onNavigate: navigation.setCurrentDir }, fullPath));
105
- })] }, "folders")) : null, navigation.sections.map((section) => {
106
- const config = resolveKindConfig(section.kind, kindConfig);
107
- return (_jsxs("div", { className: "asset-tree-section", children: [_jsxs("div", { className: "asset-tree-section-label", children: [t(config.label), _jsx("span", { className: "asset-tree-section-count", children: section.files.length })] }), section.files.map((f) => (_jsx(AssetTreeFileRow, { path: f.path, displayName: basenameForRename(f.path, navigation.currentDir), active: preview.previewPath === f.path, selected: selection.selected.has(f.path), kindLabel: t(config.label), kindGlyph: config.glyph, size: selectors.getSize(f), modifiedAt: selectors.getModifiedAt(f), renaming: rename.renaming?.path === f.path ? rename.renaming : null, onSelectPreview: preview.setPreviewPath, onOpen: openPath, onToggleSelect: selection.toggleSelect, onOpenMenu: rowMenu.openMenuFor, onRenameDraftChange: rename.updateDraft, onCommitRename: () => void rename.commitRename(), onCancelRename: rename.cancelRename }, f.path)))] }, `kind:${section.kind}`));
108
- })] })), footer] }), dragUpload.draggingFiles ? (_jsx("div", { className: "asset-tree-drop-overlay", "aria-hidden": true, "data-testid": "asset-tree-drop-overlay", children: _jsx("div", { className: "asset-tree-drop-overlay-card", children: _jsx("span", { className: "label", children: t('Drop to upload') }) }) })) : null] }), previewedFile && previewKindConfig ? (_jsx(FilePreviewPane
24
+ return (_jsxs("div", { className: `asset-tree-panel${previewedFile ? '' : ' no-preview'}${hasSelection ? ' has-selection' : ''}`, children: [props.reloading ? (_jsx("div", { className: "asset-tree-reloading-overlay", "data-testid": "asset-tree-reloading", children: t('Loading…') })) : null, _jsxs("div", { className: "asset-tree-main", children: [_jsx(AssetTreeBrowserTopbar, { currentDir: navigation.currentDir, rootLabel: props.rootLabel, onNavigate: navigation.setCurrentDir, toolbarActions: props.toolbarActions }), _jsx(AssetTreeBrowserBody, { controller: controller, files: files, folders: folders, selectors: selectors, kindConfig: kindConfig, downloadFiles: props.downloadFiles, emptyStateActions: props.emptyStateActions, footer: props.footer }), dragUpload.draggingFiles ? (_jsx("div", { className: "asset-tree-drop-overlay", "aria-hidden": true, "data-testid": "asset-tree-drop-overlay", children: _jsx("div", { className: "asset-tree-drop-overlay-card", children: _jsx("span", { className: "label", children: t('Drop to upload') }) }) })) : null] }), previewedFile ? (_jsx(AssetTreeBrowserPreview
109
25
  // Keyed on the path so a host's `renderThumbnail` (which may mount
110
26
  // a heavy/stateful element like an iframe or canvas) fully
111
27
  // remounts when the user switches files, instead of React reusing
112
28
  // the same DOM node across two different files.
113
- , { file: previewedFile, path: previewedFile.path, kindLabel: t(previewKindConfig.label), kindGlyph: previewKindConfig.glyph, size: selectors.getSize(previewedFile), modifiedAt: selectors.getModifiedAt(previewedFile), onOpen: () => onOpenFile(previewedFile), onClose: preview.clearPreview, downloadHref: getFileUrl?.(previewedFile), renderThumbnail: renderPreviewThumbnail, thumbnailIsInteractive: thumbnailIsInteractive }, previewedFile.path)) : null, rowMenu.menuPos ? (_jsx(AssetTreeRowMenu, { path: rowMenu.menuPos.path, displayName: basenameForRename(rowMenu.menuPos.path, navigation.currentDir), top: rowMenu.menuPos.top, left: rowMenu.menuPos.left, containerRef: rowMenu.containerRef, canCopyLocalPath: !!menuFile && canCopyLocalPath(menuFile, selectors), copied: copyLocalPath.copiedPath === rowMenu.menuPos.path, download: getFileUrl && menuFile
114
- ? { href: getFileUrl(menuFile), onClick: rowMenu.closeMenu }
115
- : undefined, onOpen: () => {
116
- const path = rowMenu.menuPos.path;
117
- rowMenu.closeMenu();
118
- openPath(path);
119
- }, onRename: () => {
120
- const path = rowMenu.menuPos.path;
121
- rowMenu.closeMenu();
122
- rename.startRename(path);
123
- },
124
- // Deliberately does NOT close the menu (unlike every other action
125
- // here) — the origin `DesignFilesPanel`'s equivalent handler did
126
- // call `setMenuPos(null)` before copying, which meant its
127
- // "Copied" confirmation label could never actually become visible
128
- // to a user (it only ever renders inside the now-closed popover).
129
- // Since the whole point of `copiedPath`/the transient-confirmation
130
- // UI is to be seen, this keeps the menu open so it can be; the
131
- // existing outside-dismiss/Escape handling still closes it
132
- // normally afterward.
133
- onCopyLocalPath: () => {
134
- const path = rowMenu.menuPos.path;
135
- // `canCopyLocalPath` (passed below) already gates the button's
136
- // `disabled` state on `!!menuFile`, so a click can only reach
137
- // this handler when `menuFile` is non-null — asserting that
138
- // instead of re-guarding it here avoids an unreachable `: null`
139
- // ternary arm (a real click can never fire on a disabled
140
- // button, so that arm was dead code, not defensive coding).
141
- const localPath = selectors.getLocalPath?.(menuFile);
142
- if (localPath)
143
- void copyLocalPath.copyLocalPath(path, localPath);
144
- }, onDelete: () => {
145
- const path = rowMenu.menuPos.path;
146
- rowMenu.closeMenu();
147
- onDeleteFile(path);
148
- } })) : null] }));
29
+ , { file: previewedFile, selectors: selectors, kindConfig: kindConfig, onOpenFile: props.onOpenFile, onClose: preview.clearPreview, getFileUrl: getFileUrl, renderPreviewThumbnail: props.renderPreviewThumbnail, thumbnailIsInteractive: props.thumbnailIsInteractive }, previewedFile.path)) : null, rowMenu.menuPos ? (_jsx(AssetTreeBrowserRowMenu, { controller: controller, menuPos: rowMenu.menuPos, selectors: selectors, getFileUrl: getFileUrl, onDeleteFile: props.onDeleteFile })) : null] }));
149
30
  }
150
31
  //# sourceMappingURL=AssetTreeBrowser.js.map
@@ -0,0 +1,50 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { AssetTreeFileItem, AssetTreeFolderItem, AssetTreeKindConfigMap, AssetTreeMenuPosition, AssetTreeSelectors, AssetTreeToolbarAction } from '../../types.js';
3
+ import type { AssetTreeBrowserController } from './useAssetTreeBrowserController.js';
4
+ interface ControllerProps<TFile> {
5
+ controller: AssetTreeBrowserController<TFile>;
6
+ }
7
+ export interface AssetTreeBrowserTopbarProps {
8
+ currentDir: string;
9
+ rootLabel?: string | undefined;
10
+ onNavigate: (dir: string) => void;
11
+ toolbarActions?: readonly AssetTreeToolbarAction[] | undefined;
12
+ }
13
+ /** Breadcrumbs (root label defaults to `t('Files')`) plus the host toolbar. */
14
+ export declare function AssetTreeBrowserTopbar({ currentDir, rootLabel, onNavigate, toolbarActions, }: AssetTreeBrowserTopbarProps): import("react").JSX.Element;
15
+ export interface AssetTreeBrowserBodyProps<TFile extends AssetTreeFileItem> extends ControllerProps<TFile> {
16
+ files: TFile[];
17
+ folders?: AssetTreeFolderItem[] | undefined;
18
+ selectors: AssetTreeSelectors<TFile>;
19
+ kindConfig: AssetTreeKindConfigMap;
20
+ downloadFiles?: ((paths: string[]) => Promise<{
21
+ blob: Blob;
22
+ filename: string;
23
+ }>) | undefined;
24
+ emptyStateActions?: readonly AssetTreeToolbarAction[] | undefined;
25
+ footer?: ReactNode;
26
+ }
27
+ /** The drop-target listing area: upload-error banner, selection bar, folders + kind sections (or the empty state), footer. */
28
+ export declare function AssetTreeBrowserBody<TFile extends AssetTreeFileItem>(props: AssetTreeBrowserBodyProps<TFile>): import("react").JSX.Element;
29
+ export interface AssetTreeBrowserPreviewProps<TFile extends AssetTreeFileItem> {
30
+ file: TFile;
31
+ selectors: AssetTreeSelectors<TFile>;
32
+ kindConfig: AssetTreeKindConfigMap;
33
+ onOpenFile: (file: TFile) => void;
34
+ onClose: () => void;
35
+ getFileUrl?: ((file: TFile) => string) | undefined;
36
+ renderPreviewThumbnail?: ((file: TFile) => ReactNode) | undefined;
37
+ thumbnailIsInteractive?: boolean | undefined;
38
+ }
39
+ /** `FilePreviewPane` for the previewed `file`, with its kind display config resolved. */
40
+ export declare function AssetTreeBrowserPreview<TFile extends AssetTreeFileItem>({ file, selectors, kindConfig, onOpenFile, onClose, getFileUrl, renderPreviewThumbnail, thumbnailIsInteractive, }: AssetTreeBrowserPreviewProps<TFile>): import("react").JSX.Element;
41
+ export interface AssetTreeBrowserRowMenuProps<TFile extends AssetTreeFileItem> extends ControllerProps<TFile> {
42
+ menuPos: AssetTreeMenuPosition;
43
+ selectors: AssetTreeSelectors<TFile>;
44
+ getFileUrl?: ((file: TFile) => string) | undefined;
45
+ onDeleteFile: (path: string) => void;
46
+ }
47
+ /** The open row context menu for `menuPos.path`, with every action wired. */
48
+ export declare function AssetTreeBrowserRowMenu<TFile extends AssetTreeFileItem>({ controller, menuPos, selectors, getFileUrl, onDeleteFile, }: AssetTreeBrowserRowMenuProps<TFile>): import("react").JSX.Element;
49
+ export {};
50
+ //# sourceMappingURL=AssetTreeBrowserParts.d.ts.map