@jini-ai/ui 0.5.5 → 0.5.6

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 +4 -0
  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 +7 -10
  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
@@ -17,41 +17,68 @@ export function PreviewModalShell({ title, subtitle, views, initialViewId, onVie
17
17
  sidebarContentKey: sidebar?.contentKey,
18
18
  designWidth,
19
19
  });
20
- const showTabs = views.length > 1;
21
- const status = deriveContentStatus(c.activeView);
22
20
  return (_jsx("div", { className: className, role: "dialog", "aria-modal": "true", "aria-label": t('{title} preview', { title }), style: { position: 'fixed', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }, "data-preview-modal-backdrop": "", onClick: (event) => {
23
21
  // Only the actual backdrop dismisses. Clicks from the modal surface or any descendant
24
22
  // bubble through this element with a different target and must leave the preview open.
25
23
  if (event.target === event.currentTarget)
26
24
  onClose();
27
- }, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }, "data-preview-modal": "", "data-preview-modal-fullscreen": c.fullscreen ? 'true' : 'false', children: [_jsxs("header", { style: { display: 'flex', alignItems: 'center', gap: 12 }, "data-preview-modal-header": "", children: [_jsxs("div", { "data-preview-modal-title-block": "", children: [_jsx("div", { "data-preview-modal-title": "", children: title }), subtitle ? _jsx("div", { "data-preview-modal-subtitle": "", children: subtitle }) : null] }), showTabs ? (_jsx("div", { role: "tablist", style: { display: 'flex', gap: 4 }, "data-preview-modal-tabs": "", children: views.map((v) => (_jsx("button", { type: "button", role: "tab", "aria-selected": c.activeId === v.id, "data-preview-modal-tab": c.activeId === v.id ? 'active' : '', onClick: () => c.setActiveId(v.id), children: v.label }, v.id))) })) : null, _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }, "data-preview-modal-actions": "", children: [primaryAction ? _jsx(PrimaryAction, { action: primaryAction, controller: c, icons: icons }) : null, sidebar && !hideSidebarToggle ? (_jsx("button", { type: "button", "aria-pressed": c.sidebarOpen, title: sidebar.label, onClick: () => {
28
- const next = !c.sidebarOpen;
29
- onSidebarToggleClick?.(next);
30
- c.setSidebarOpen(next);
31
- }, children: sidebar.label })) : null, headerExtras] }), _jsx("button", { type: "button", onClick: onClose, title: t('Close preview'), "aria-label": t('Close'), "data-preview-modal-close": "", children: icons.close() })] }), _jsxs("div", { style: { display: 'flex', flex: 1, minHeight: 0 }, "data-preview-modal-stage": sidebar && c.sidebarOpen ? 'has-sidebar' : '', ref: c.stageRef, children: [_jsxs("div", { style: { position: 'relative', flex: 1, minWidth: 0 }, ref: c.stageFrameRef, "data-preview-modal-stage-frame": "", children: [status !== 'unavailable' && status !== 'error' ? (_jsx("button", { type: "button", onClick: () => {
32
- onFullscreenClick?.();
33
- if (c.fullscreen)
34
- c.exitFullscreen();
35
- else
36
- c.enterFullscreen();
37
- }, title: c.fullscreen ? t('Exit fullscreen') : t('Fullscreen'), "aria-label": c.fullscreen ? t('Exit fullscreen') : t('Fullscreen'), "data-preview-modal-stage-fullscreen": "", children: c.fullscreen ? icons['fullscreen-exit']() : icons.fullscreen() })) : null, _jsx(ContentStage, { status: status, view: c.activeView, title: title, scalerStyle: c.scalerStyle, onView: onView, srcDocOptions: srcDocOptions, t: t }), sidebar && !c.sidebarOpen ? (_jsx("button", { type: "button", onClick: () => {
38
- onSidebarToggleClick?.(true);
39
- c.setSidebarOpen(true);
40
- }, title: t('Show {label}', { label: sidebar.label }), "aria-label": t('Show {label}', { label: sidebar.label }), "data-preview-modal-stage-handle": "expand", children: _jsx("span", { "aria-hidden": "true", children: "\u2039" }) })) : null] }), sidebar && c.sidebarOpen ? (_jsxs("aside", { "aria-label": sidebar.label, "data-preview-modal-sidebar": "", children: [_jsx("button", { type: "button", onClick: () => {
41
- onSidebarToggleClick?.(false);
42
- c.setSidebarOpen(false);
43
- }, title: t('Hide {label}', { label: sidebar.label }), "aria-label": t('Hide {label}', { label: sidebar.label }), "data-preview-modal-stage-handle": "collapse", children: _jsx("span", { "aria-hidden": "true", children: "\u203A" }) }), sidebar.content] })) : null] })] }) }));
25
+ }, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }, "data-preview-modal": "", "data-preview-modal-fullscreen": c.fullscreen ? 'true' : 'false', children: [_jsx(ShellHeader, { title: title, subtitle: subtitle, views: views, controller: c, primaryAction: primaryAction, icons: icons, sidebar: hideSidebarToggle ? undefined : sidebar, headerExtras: headerExtras, onClose: onClose, onSidebarToggleClick: onSidebarToggleClick }), _jsx(ShellStage, { title: title, controller: c, icons: icons, sidebar: sidebar, onView: onView, srcDocOptions: srcDocOptions, onFullscreenClick: onFullscreenClick, onSidebarToggleClick: onSidebarToggleClick })] }) }));
26
+ }
27
+ /** Header chrome; `sidebar` is already `undefined` when the host hid the header toggle via `hideSidebarToggle`. */
28
+ function ShellHeader({ title, subtitle, views, controller, primaryAction, icons, sidebar, headerExtras, onClose, onSidebarToggleClick, }) {
29
+ const t = useT();
30
+ return (_jsxs("header", { style: { display: 'flex', alignItems: 'center', gap: 12 }, "data-preview-modal-header": "", children: [_jsxs("div", { "data-preview-modal-title-block": "", children: [_jsx("div", { "data-preview-modal-title": "", children: title }), subtitle ? _jsx("div", { "data-preview-modal-subtitle": "", children: subtitle }) : null] }), views.length > 1 ? _jsx(TabBar, { views: views, controller: controller }) : null, _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }, "data-preview-modal-actions": "", children: [primaryAction ? _jsx(PrimaryAction, { action: primaryAction, controller: controller, icons: icons }) : null, sidebar ? (_jsx(SidebarToggle, { sidebar: sidebar, controller: controller, onSidebarToggleClick: onSidebarToggleClick })) : null, headerExtras] }), _jsx("button", { type: "button", onClick: onClose, title: t('Close preview'), "aria-label": t('Close'), "data-preview-modal-close": "", children: icons.close() })] }));
31
+ }
32
+ function ShellStage({ title, controller, icons, sidebar, onView, srcDocOptions, onFullscreenClick, onSidebarToggleClick, }) {
33
+ const t = useT();
34
+ const status = deriveContentStatus(controller.activeView);
35
+ return (_jsxs("div", { style: { display: 'flex', flex: 1, minHeight: 0 }, "data-preview-modal-stage": sidebar && controller.sidebarOpen ? 'has-sidebar' : '', ref: controller.stageRef, children: [_jsxs("div", { style: { position: 'relative', flex: 1, minWidth: 0 }, ref: controller.stageFrameRef, "data-preview-modal-stage-frame": "", children: [status !== 'unavailable' && status !== 'error' ? (_jsx(FullscreenButton, { controller: controller, icons: icons, onFullscreenClick: onFullscreenClick })) : null, _jsx(ContentStage, { status: status, view: controller.activeView, title: title, scalerStyle: controller.scalerStyle, onView: onView, srcDocOptions: srcDocOptions, t: t }), sidebar && !controller.sidebarOpen ? (_jsx(SidebarHandle, { sidebar: sidebar, controller: controller, open: true, onSidebarToggleClick: onSidebarToggleClick })) : null] }), sidebar && controller.sidebarOpen ? (_jsxs("aside", { "aria-label": sidebar.label, "data-preview-modal-sidebar": "", children: [_jsx(SidebarHandle, { sidebar: sidebar, controller: controller, open: false, onSidebarToggleClick: onSidebarToggleClick }), sidebar.content] })) : null] }));
36
+ }
37
+ function TabBar({ views, controller }) {
38
+ return (_jsx("div", { role: "tablist", style: { display: 'flex', gap: 4 }, "data-preview-modal-tabs": "", children: views.map((v) => (_jsx("button", { type: "button", role: "tab", "aria-selected": controller.activeId === v.id, "data-preview-modal-tab": controller.activeId === v.id ? 'active' : '', onClick: () => controller.setActiveId(v.id), children: v.label }, v.id))) }));
39
+ }
40
+ function SidebarToggle({ sidebar, controller, onSidebarToggleClick, }) {
41
+ return (_jsx("button", { type: "button", "aria-pressed": controller.sidebarOpen, title: sidebar.label, onClick: () => {
42
+ const next = !controller.sidebarOpen;
43
+ onSidebarToggleClick?.(next);
44
+ controller.setSidebarOpen(next);
45
+ }, children: sidebar.label }));
46
+ }
47
+ function FullscreenButton({ controller, icons, onFullscreenClick, }) {
48
+ const t = useT();
49
+ const label = controller.fullscreen ? t('Exit fullscreen') : t('Fullscreen');
50
+ return (_jsx("button", { type: "button", onClick: () => {
51
+ onFullscreenClick?.();
52
+ if (controller.fullscreen)
53
+ controller.exitFullscreen();
54
+ else
55
+ controller.enterFullscreen();
56
+ }, title: label, "aria-label": label, "data-preview-modal-stage-fullscreen": "", children: controller.fullscreen ? icons['fullscreen-exit']() : icons.fullscreen() }));
57
+ }
58
+ /** Stage-edge handle: `open` renders the "Show" expand handle (‹), otherwise the "Hide" collapse handle (›) inside the sidebar. */
59
+ function SidebarHandle({ sidebar, controller, open, onSidebarToggleClick, }) {
60
+ const t = useT();
61
+ const label = open ? t('Show {label}', { label: sidebar.label }) : t('Hide {label}', { label: sidebar.label });
62
+ return (_jsx("button", { type: "button", onClick: () => {
63
+ onSidebarToggleClick?.(open);
64
+ controller.setSidebarOpen(open);
65
+ }, title: label, "aria-label": label, "data-preview-modal-stage-handle": open ? 'expand' : 'collapse', children: _jsx("span", { "aria-hidden": "true", children: open ? '‹' : '›' }) }));
66
+ }
67
+ function PrimaryButton({ action }) {
68
+ return (_jsx("button", { type: "button", onClick: action.onClick, disabled: action.disabled || action.busy, "aria-busy": action.busy ? 'true' : undefined, ...(action.testId ? { 'data-testid': action.testId } : {}), "data-preview-modal-primary-action": "", children: action.busy ? action.busyLabel ?? action.label : action.label }));
44
69
  }
45
70
  function PrimaryAction({ action, controller, icons, }) {
46
71
  const t = useT();
47
- const hasMenu = Boolean(action.menu && action.menu.length > 0);
48
- if (!hasMenu) {
49
- return (_jsx("button", { type: "button", onClick: action.onClick, disabled: action.disabled || action.busy, "aria-busy": action.busy ? 'true' : undefined, ...(action.testId ? { 'data-testid': action.testId } : {}), "data-preview-modal-primary-action": "", children: action.busy ? action.busyLabel ?? action.label : action.label }));
50
- }
51
- return (_jsxs("div", { ref: controller.primaryMenuRef, style: { position: 'relative', display: 'flex' }, "data-preview-modal-primary-action-group": "", children: [_jsx("button", { type: "button", onClick: action.onClick, disabled: action.disabled || action.busy, "aria-busy": action.busy ? 'true' : undefined, ...(action.testId ? { 'data-testid': action.testId } : {}), "data-preview-modal-primary-action": "", children: action.busy ? action.busyLabel ?? action.label : action.label }), _jsx("button", { type: "button", onClick: () => controller.setPrimaryMenuOpen((v) => !v), disabled: action.disabled || action.busy, "aria-haspopup": "menu", "aria-expanded": controller.primaryMenuOpen, "aria-label": t('More ways to {label}', { label: action.label }), ...(action.testId ? { 'data-testid': `${action.testId}-menu` } : {}), "data-preview-modal-primary-action-caret": "", children: icons['chevron-down']() }), controller.primaryMenuOpen ? (_jsx("div", { role: "menu", "data-preview-modal-primary-action-popover": "", children: action.menu.map((item, index) => (_jsxs("button", { type: "button", role: "menuitem", onMouseDown: (e) => e.preventDefault(), onClick: () => {
52
- controller.setPrimaryMenuOpen(false);
53
- item.onClick();
54
- }, ...(item.testId ? { 'data-testid': item.testId } : {}), "data-preview-modal-primary-action-option": "", children: [_jsx("span", { "data-preview-modal-primary-action-option-label": "", children: item.label }), item.description ? (_jsx("span", { "data-preview-modal-primary-action-option-desc": "", children: item.description })) : null] }, item.testId ?? `${item.label}-${index}`))) })) : null] }));
72
+ const menu = action.menu ?? [];
73
+ if (menu.length === 0)
74
+ return _jsx(PrimaryButton, { action: action });
75
+ return (_jsxs("div", { ref: controller.primaryMenuRef, style: { position: 'relative', display: 'flex' }, "data-preview-modal-primary-action-group": "", children: [_jsx(PrimaryButton, { action: action }), _jsx("button", { type: "button", onClick: () => controller.setPrimaryMenuOpen((v) => !v), disabled: action.disabled || action.busy, "aria-haspopup": "menu", "aria-expanded": controller.primaryMenuOpen, "aria-label": t('More ways to {label}', { label: action.label }), ...(action.testId ? { 'data-testid': `${action.testId}-menu` } : {}), "data-preview-modal-primary-action-caret": "", children: icons['chevron-down']() }), controller.primaryMenuOpen ? _jsx(PrimaryActionMenu, { menu: menu, controller: controller }) : null] }));
76
+ }
77
+ function PrimaryActionMenu({ menu, controller, }) {
78
+ return (_jsx("div", { role: "menu", "data-preview-modal-primary-action-popover": "", children: menu.map((item, index) => (_jsxs("button", { type: "button", role: "menuitem", onMouseDown: (e) => e.preventDefault(), onClick: () => {
79
+ controller.setPrimaryMenuOpen(false);
80
+ item.onClick();
81
+ }, ...(item.testId ? { 'data-testid': item.testId } : {}), "data-preview-modal-primary-action-option": "", children: [_jsx("span", { "data-preview-modal-primary-action-option-label": "", children: item.label }), item.description ? (_jsx("span", { "data-preview-modal-primary-action-option-desc": "", children: item.description })) : null] }, item.testId ?? `${item.label}-${index}`))) }));
55
82
  }
56
83
  function ContentStage({ status, view, title, scalerStyle, onView, srcDocOptions, t, }) {
57
84
  if (status === 'custom') {
@@ -60,9 +87,8 @@ function ContentStage({ status, view, title, scalerStyle, onView, srcDocOptions,
60
87
  if (status === 'unavailable') {
61
88
  return (_jsx("div", { "data-preview-modal-unavailable": "", children: view?.unavailable?.message }));
62
89
  }
63
- if (status === 'error') {
64
- return (_jsxs("div", { "data-preview-modal-error": "", children: [_jsx("div", { "data-preview-modal-error-title": "", children: t('Something went wrong.') }), _jsx("div", { "data-preview-modal-error-body": "", children: t('This preview could not be loaded.') }), onView && view ? (_jsx("button", { type: "button", onClick: () => onView(view.id), children: t('Retry') })) : null] }));
65
- }
90
+ if (status === 'error')
91
+ return _jsx(ErrorStage, { view: view, onView: onView, t: t });
66
92
  if (status === 'loading') {
67
93
  return (_jsx("div", { "data-preview-modal-loading": "", children: t('Loading {label}…', { label: (view?.label ?? t('preview')).toLowerCase() }) }));
68
94
  }
@@ -71,4 +97,7 @@ function ContentStage({ status, view, title, scalerStyle, onView, srcDocOptions,
71
97
  // (no `??`) is safe by that invariant, not a runtime guess.
72
98
  return (_jsx("div", { style: scalerStyle, "data-preview-modal-stage-scaler": "", children: _jsx(SrcDocSandbox, { html: view.html, options: srcDocOptions, title: `${title} ${view.label}` }, view.id) }));
73
99
  }
100
+ function ErrorStage({ view, onView, t, }) {
101
+ return (_jsxs("div", { "data-preview-modal-error": "", children: [_jsx("div", { "data-preview-modal-error-title": "", children: t('Something went wrong.') }), _jsx("div", { "data-preview-modal-error-body": "", children: t('This preview could not be loaded.') }), onView && view ? (_jsx("button", { type: "button", onClick: () => onView(view.id), children: t('Retry') })) : null] }));
102
+ }
74
103
  //# sourceMappingURL=PreviewModalShell.js.map
@@ -15,19 +15,22 @@ export function ArtifactView({ file, registry, hints, srcDocOptions, slots, clas
15
15
  const slotRenderer = slots?.renderers?.[match.renderer.id];
16
16
  if (slotRenderer)
17
17
  return wrap(_jsx(_Fragment, { children: slotRenderer({ file, match }) }));
18
+ return wrap(renderBuiltIn({ file, match, t }, { className, srcDocOptions }));
19
+ }
20
+ /** Built-in rendering for the renderer ids this package ships; any other id gets the "supply a slot" fallback message. */
21
+ function renderBuiltIn({ file, match, t }, { className, srcDocOptions }) {
22
+ const content = file.content ?? '';
18
23
  switch (match.renderer.id) {
19
24
  case 'html':
20
25
  case 'svg':
21
- return wrap(_jsx(SrcDocSandbox, { className: className, html: file.content ?? '', options: srcDocOptions, title: match.manifest.title }));
26
+ return _jsx(SrcDocSandbox, { className: className, html: content, options: srcDocOptions, title: match.manifest.title });
22
27
  case 'markdown': {
23
- const html = match.renderer.renderPartial
24
- ? match.renderer.renderPartial(file.content ?? '')
25
- : renderMarkdownToSafeHtml(file.content ?? '');
28
+ const html = match.renderer.renderPartial ? match.renderer.renderPartial(content) : renderMarkdownToSafeHtml(content);
26
29
  // eslint-disable-next-line react/no-danger -- html is produced by renderMarkdownToSafeHtml, which escapes/allow-lists links; see markdown.ts.
27
- return wrap(_jsx("div", { className: className, dangerouslySetInnerHTML: { __html: html } }));
30
+ return _jsx("div", { className: className, dangerouslySetInnerHTML: { __html: html } });
28
31
  }
29
32
  default:
30
- return wrap(_jsx("div", { className: className, role: "status", children: t('No renderer registered for "{rendererId}" — supply one via slots.renderers.', {
33
+ return (_jsx("div", { className: className, role: "status", children: t('No renderer registered for "{rendererId}" — supply one via slots.renderers.', {
31
34
  rendererId: match.renderer.id,
32
35
  }) }));
33
36
  }
@@ -62,7 +62,10 @@ export declare function sanitizePreviewTitle(text: string): string;
62
62
  * @example sanitizeTitleInDoc('<title>Q3: Report</title>') // '<title>Q3- Report</title>'
63
63
  */
64
64
  export declare function sanitizeTitleInDoc(html: string): string;
65
- /** Splice `payload` right after the opening `<head …>` tag, prepending it when no `<head>` exists. */
65
+ /** Splice after the parsed head opener, or before content in an implicit head.
66
+ * Source offsets exclude comments, raw text and quoted attributes without rewriting the document.
67
+ * @complexity O(n) time and space for parsing n source characters.
68
+ */
66
69
  export declare function injectAfterHeadOpen(doc: string, payload: string): string;
67
70
  /**
68
71
  * Splice `payload` right before the real `</head>` (the last one before
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { defaultTreeAdapter, parse } from 'parse5';
15
15
  import { escapeHtmlAttribute } from '../html-utils.js';
16
- import { injectBaseHref, wrapFragmentAsDocument } from '../sandboxed-document.js';
16
+ import { wrapFragmentAsDocument } from '../sandboxed-document.js';
17
17
  import { buildFocusScript, buildStorageScript } from '../sandbox-scripts.js';
18
18
  import { applySrcDocBridges } from './bridge.js';
19
19
  /**
@@ -27,9 +27,24 @@ import { applySrcDocBridges } from './bridge.js';
27
27
  * host's storage/cookies/DOM regardless of CSP).
28
28
  */
29
29
  export const DEFAULT_SRC_DOC_CSP = "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: data:; style-src 'unsafe-inline'; img-src * data: blob:; font-src * data:; media-src * data: blob:; connect-src *; frame-src *; child-src *; object-src 'none'; base-uri 'none'; form-action 'none';";
30
- function injectContentSecurityPolicy(doc, csp) {
31
- const tag = `<meta http-equiv="Content-Security-Policy" content="${escapeHtmlAttribute(csp)}">`;
32
- return injectAfterHeadOpen(doc, tag);
30
+ /** Resolve the existing default/disabled/custom policy contract without changing caller options.
31
+ * An empty custom string injects no policy, just like `false`.
32
+ */
33
+ function resolveContentSecurityPolicy({ csp }) {
34
+ if (csp === false)
35
+ return null;
36
+ return csp === true || csp === undefined ? DEFAULT_SRC_DOC_CSP : csp;
37
+ }
38
+ /** The safety tags spliced once at the parsed head opener. Order is load-bearing: the host's
39
+ * own scripts precede the policy, the host `<base>` precedes it so the default `base-uri 'none'`
40
+ * cannot block it, and authored markup follows it. Separate regex splices could match `<head`
41
+ * inside a quoted attribute, and the next tag's quotes then pushed the policy into `<body>`. */
42
+ function buildSafetyHead(options) {
43
+ const csp = resolveContentSecurityPolicy(options);
44
+ return (options.previewFocusGuard ? buildFocusScript({ format: 'srcdoc' }) : '')
45
+ + buildStorageScript({ format: 'srcdoc' })
46
+ + (options.baseHref ? `<base href="${escapeHtmlAttribute(options.baseHref)}">` : '')
47
+ + (csp ? `<meta http-equiv="Content-Security-Policy" content="${escapeHtmlAttribute(csp)}">` : '');
33
48
  }
34
49
  /**
35
50
  * Sanitize a title string so it is safe to use as a downloaded/printed
@@ -82,11 +97,23 @@ function serializeHtmlDocument(doc) {
82
97
  const doctype = doc.doctype ? '<!doctype html>\n' : '';
83
98
  return `${doctype}${doc.documentElement.outerHTML}`;
84
99
  }
85
- /** Splice `payload` right after the opening `<head …>` tag, prepending it when no `<head>` exists. */
100
+ /** An implicit element has no authored opening tag to splice after. */
101
+ function openingTagEnd({ node }) {
102
+ return node?.sourceCodeLocation?.startTag?.endOffset;
103
+ }
104
+ /** Splice after the parsed head opener, or before content in an implicit head.
105
+ * Source offsets exclude comments, raw text and quoted attributes without rewriting the document.
106
+ * @complexity O(n) time and space for parsing n source characters.
107
+ */
86
108
  export function injectAfterHeadOpen(doc, payload) {
87
- if (/<head[^>]*>/i.test(doc))
88
- return doc.replace(/<head[^>]*>/i, (m) => `${m}${payload}`);
89
- return payload + doc;
109
+ const document = parse(doc, { sourceCodeLocationInfo: true });
110
+ const root = findElementChild({ parent: document, tagName: 'html' });
111
+ const head = findElementChild({ parent: root, tagName: 'head' });
112
+ const doctype = document.childNodes.find(defaultTreeAdapter.isDocumentTypeNode);
113
+ const offset = openingTagEnd({ node: head })
114
+ ?? openingTagEnd({ node: root })
115
+ ?? doctype?.sourceCodeLocation?.endOffset ?? 0;
116
+ return doc.slice(0, offset) + payload + doc.slice(offset);
90
117
  }
91
118
  /**
92
119
  * Splice `payload` right before the real `</head>` (the last one before
@@ -103,19 +130,8 @@ export function injectBeforeHeadEnd(doc, payload) {
103
130
  if (idx >= 0)
104
131
  return doc.slice(0, idx) + payload + doc.slice(idx);
105
132
  if (/<head[^>]*>/i.test(doc))
106
- return doc.replace(/<head[^>]*>/i, (m) => `${m}${payload}`);
107
- if (typeof DOMParser !== 'undefined') {
108
- try {
109
- const parsed = new DOMParser().parseFromString(doc, 'text/html');
110
- if (parsed.head)
111
- parsed.head.insertAdjacentHTML('beforeend', payload);
112
- return serializeHtmlDocument(parsed);
113
- }
114
- catch {
115
- /* fall through to prepend */
116
- }
117
- }
118
- return payload + doc;
133
+ return injectAfterHeadOpen(doc, payload);
134
+ return injectViaDomParser({ doc, payload, section: 'head' }) ?? payload + doc;
119
135
  }
120
136
  /** Splice `payload` right before the real `</body>` (the last one before `</html>`). See {@link injectBeforeHeadEnd}. */
121
137
  export function injectBeforeBodyEnd(doc, payload) {
@@ -125,30 +141,31 @@ export function injectBeforeBodyEnd(doc, payload) {
125
141
  const idx = lower.lastIndexOf('</body>', limit - 1);
126
142
  if (idx >= 0)
127
143
  return doc.slice(0, idx) + payload + doc.slice(idx);
128
- if (typeof DOMParser !== 'undefined') {
129
- try {
130
- const parsed = new DOMParser().parseFromString(doc, 'text/html');
131
- if (parsed.body)
132
- parsed.body.insertAdjacentHTML('beforeend', payload);
133
- return serializeHtmlDocument(parsed);
134
- }
135
- catch {
136
- /* fall through to append */
137
- }
138
- }
139
- return doc + payload;
144
+ return injectViaDomParser({ doc, payload, section: 'body' }) ?? doc + payload;
140
145
  }
141
- /** Srcdoc keeps its head/body/prepend fallback instead of the generic builder's string-only head insertion. */
142
- function injectSandboxScript(doc, script) {
143
- if (/<head[^>]*>/i.test(doc))
144
- return doc.replace(/<head[^>]*>/i, (m) => `${m}${script}`);
145
- if (/<body[^>]*>/i.test(doc))
146
- return doc.replace(/<body[^>]*>/i, (m) => `${m}${script}`);
147
- return script + doc;
146
+ /**
147
+ * DOMParser fallback shared by the head/body splicers for fragments with no
148
+ * textual insertion point. Returns `null` when `DOMParser` is unavailable or
149
+ * throws, so the caller falls through to its own prepend/append.
150
+ */
151
+ function injectViaDomParser({ doc, payload, section }, _options = {}) {
152
+ if (typeof DOMParser === 'undefined')
153
+ return null;
154
+ try {
155
+ const parsed = new DOMParser().parseFromString(doc, 'text/html');
156
+ parsed[section]?.insertAdjacentHTML('beforeend', payload);
157
+ return serializeHtmlDocument(parsed);
158
+ }
159
+ catch {
160
+ /* fall through to the caller's prepend/append */
161
+ return null;
162
+ }
148
163
  }
149
164
  function injectSrcdocTransportActivationBridge(doc) {
150
165
  const script = `<script data-jini-srcdoc-transport-activation>(function(){
151
166
  window.addEventListener('message', function(ev){
167
+ // Only the embedding host may replace this document; nested frames are untrusted.
168
+ if (ev.source !== window.parent) return;
152
169
  var data = ev && ev.data;
153
170
  if (!data || data.type !== 'jini:srcdoc-transport-activate' || typeof data.html !== 'string') return;
154
171
  document.open();
@@ -181,6 +198,8 @@ export function buildLazySrcDocTransport() {
181
198
  <meta name="viewport" content="width=device-width, initial-scale=1" />
182
199
  <script data-jini-lazy-srcdoc-transport>(function(){
183
200
  window.addEventListener('message', function(ev){
201
+ // Only the embedding host may replace this document; nested frames are untrusted.
202
+ if (ev.source !== window.parent) return;
184
203
  var data = ev && ev.data;
185
204
  if (!data || data.type !== 'jini:srcdoc-transport-activate' || typeof data.html !== 'string') return;
186
205
  document.open();
@@ -230,16 +249,11 @@ export function buildSrcDoc(html, options = {}) {
230
249
  if (options.lazyTransport)
231
250
  return buildLazySrcDocTransport();
232
251
  const wrapped = wrapFragmentAsDocument(html);
233
- const withSafeTitle = sanitizeTitleInDoc(wrapped);
234
- const cspValue = options.csp === false ? null : options.csp === true || options.csp === undefined ? DEFAULT_SRC_DOC_CSP : options.csp;
235
- const withCsp = cspValue ? injectContentSecurityPolicy(withSafeTitle, cspValue) : withSafeTitle;
236
- const withBase = options.baseHref ? injectBaseHref(withCsp, options.baseHref) : withCsp;
237
- const withShim = injectSandboxScript(withBase, buildStorageScript({ format: 'srcdoc' }));
238
- const withFocusGuard = options.previewFocusGuard ? injectSandboxScript(withShim, buildFocusScript({ format: 'srcdoc' })) : withShim;
252
+ const withSafety = injectAfterHeadOpen(sanitizeTitleInDoc(wrapped), buildSafetyHead(options));
239
253
  const ctx = { baseHref: options.baseHref, ...options.bridgeContext };
240
254
  const withBridges = options.bridges?.length
241
- ? applySrcDocBridges(withFocusGuard, options.bridges, ctx)
242
- : withFocusGuard;
255
+ ? applySrcDocBridges(withSafety, options.bridges, ctx)
256
+ : withSafety;
243
257
  return injectSrcdocTransportActivationBridge(withBridges);
244
258
  }
245
259
  //# sourceMappingURL=build.js.map
@@ -214,11 +214,40 @@
214
214
  border: 5px solid transparent; border-top-color: var(--jini-bg-panel);
215
215
  }
216
216
 
217
+ /* "• " lines (`infoTipLines`): each bullet's wrapped text hangs past its dot. */
218
+ .info-tip-line { display: block; }
219
+ .info-tip-bullet { display: flex; gap: 0.4em; }
220
+ .info-tip-bullet + .info-tip-bullet { margin-top: 0.3em; }
221
+ .info-tip-bullet > span:first-child { flex: none; }
222
+
217
223
  .info-tip-bubble-below { transform: translate(-50%, 8px); }
218
224
  .info-tip-bubble-below::after {
219
225
  top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: var(--jini-bg-panel);
220
226
  }
221
227
 
228
+ /* `MiddleTruncate`: the head ellipsizes, the tail (extension) never shrinks. */
229
+ .jini-middle-truncate { display: inline-flex; max-width: 100%; min-width: 0; white-space: nowrap; }
230
+ .jini-middle-truncate__head { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
231
+ .jini-middle-truncate__tail { flex: none; }
232
+
233
+ /* `ScrollJumpButtons`: absolutely placed in the host's positioned content column; hosts move it
234
+ clear of their own floating controls with the custom properties (a row lines the pair up beside
235
+ a FAB instead of stacking it above). The wrapper takes no taps, so the gap between the buttons
236
+ and a hidden slot never block the page under them. */
237
+ .jini-scroll-jump {
238
+ position: absolute; right: var(--jini-scroll-jump-right, 16px); bottom: var(--jini-scroll-jump-bottom, 16px);
239
+ display: flex; flex-direction: var(--jini-scroll-jump-direction, column); gap: 6px; z-index: 20; pointer-events: none;
240
+ }
241
+ .jini-scroll-jump__btn {
242
+ pointer-events: auto; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0;
243
+ border-radius: 50%; border: 1px solid var(--jini-border); background: var(--jini-bg-elevated); color: var(--jini-text-muted);
244
+ box-shadow: var(--jini-shadow-md); cursor: pointer; opacity: 0.85; transition: opacity 0.12s, color 0.12s;
245
+ }
246
+ .jini-scroll-jump__btn[data-off] { visibility: hidden; }
247
+ .jini-scroll-jump__btn:hover { opacity: 1; color: var(--jini-text); }
248
+ .jini-scroll-jump__btn:focus-visible { opacity: 1; outline: 2px solid var(--jini-accent); outline-offset: 2px; }
249
+ @media (prefers-reduced-motion: reduce) { .jini-scroll-jump__btn { transition: none; } }
250
+
222
251
  .tab-bar { display: flex; flex-wrap: wrap; gap: 0.3rem; border-bottom: 1px solid var(--jini-border); margin-bottom: 1.3rem; }
223
252
 
224
253
  .tab-bar-item {
@@ -255,20 +284,6 @@
255
284
  }
256
285
  .coming-soon-panel .settings-ui-inert-control { opacity: 0.55; }
257
286
 
258
- .image-preview-modal {
259
- border: none; padding: 0; background: transparent; max-width: 90vw; max-height: 90vh;
260
- }
261
- .image-preview-modal[open] { display: block; }
262
- .image-preview-modal::backdrop { background: var(--jini-backdrop); }
263
- .image-preview-modal img { display: block; max-width: 90vw; max-height: 90vh; width: auto; height: auto; border-radius: var(--jini-radius-lg); box-shadow: var(--jini-shadow-md); }
264
- .image-preview-modal-close {
265
- position: absolute; top: 0.7rem; right: 0.7rem; width: 2.1rem; height: 2.1rem; border-radius: 50%;
266
- display: flex; align-items: center; justify-content: center;
267
- background: var(--jini-bg-panel); color: var(--jini-text); font-size: 1.3rem; line-height: 1;
268
-
269
- border: 1px solid var(--jini-border); box-shadow: var(--jini-shadow-md);
270
- }
271
- .image-preview-modal-close:hover { background: var(--jini-bg-subtle); opacity: 1; }
272
287
  .select-search { box-sizing: border-box; padding: .5rem .7rem; background: var(--jini-bg-elevated); color: var(--jini-text); border: 1px solid var(--jini-border); border-radius: var(--jini-radius-sm); font: inherit; }
273
288
  .tab-bar-item:focus-visible { outline: 2px solid var(--jini-accent); outline-offset: 2px; }
274
289
  .coming-soon-notice-kicker { color: var(--jini-text-muted); font-size: .75rem; }
@@ -0,0 +1,142 @@
1
+ /* Shared media lightbox (`Lightbox` in admin-widgets). The dialog itself is the full-viewport dark
2
+ gray stage: header row (title, counter, close), the media, footer row (caller actions left,
3
+ previous/next together right). Rows rather than floating controls, so nothing ever overlaps the
4
+ media or the title. The stage stays dark in both admin themes: photos read best on a neutral dark
5
+ surround, and the white title then stays readable whatever the theme. */
6
+ .jini-lightbox {
7
+ --jini-lightbox-edge: clamp(0.75rem, 2vw, 1.25rem);
8
+ --jini-lightbox-button: 2.25rem;
9
+ /* Header and footer rows: one button tall plus the edge margin above/below it. */
10
+ --jini-lightbox-bar: calc(var(--jini-lightbox-button) + var(--jini-lightbox-edge) * 2);
11
+ display: none;
12
+ }
13
+ /* `display` only on `[open]`: an author `display` outranks the UA `dialog:not([open])` rule. */
14
+ .jini-lightbox[open] {
15
+ position: fixed;
16
+ inset: 0;
17
+ box-sizing: border-box;
18
+ width: 100%;
19
+ height: 100%;
20
+ max-width: none;
21
+ max-height: none;
22
+ margin: 0;
23
+ padding: 0 calc(var(--jini-lightbox-edge) + env(safe-area-inset-right, 0px)) 0 calc(var(--jini-lightbox-edge) + env(safe-area-inset-left, 0px));
24
+ border: 0;
25
+ display: grid;
26
+ grid-template-rows: var(--jini-lightbox-bar) minmax(0, 1fr) var(--jini-lightbox-bar);
27
+ overscroll-behavior: contain;
28
+ color: var(--jini-lightbox-ink);
29
+ background: var(--jini-lightbox-bg);
30
+ font-family: var(--jini-font-sans, inherit);
31
+ }
32
+ .jini-lightbox::backdrop { background: transparent; }
33
+
34
+ .jini-lightbox-header {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 0.75rem;
38
+ min-width: 0;
39
+ padding-top: env(safe-area-inset-top, 0px);
40
+ }
41
+ .jini-lightbox-title {
42
+ margin: 0;
43
+ min-width: 0;
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ font-size: 0.9375rem;
48
+ font-weight: 600;
49
+ line-height: 1.3;
50
+ color: var(--jini-lightbox-ink);
51
+ }
52
+ .jini-lightbox-counter {
53
+ flex-shrink: 0;
54
+ font-size: 0.8125rem;
55
+ font-variant-numeric: tabular-nums;
56
+ color: var(--jini-lightbox-ink-muted);
57
+ }
58
+ .jini-lightbox-close { margin-left: auto; }
59
+
60
+ /* The media cap is computed from the viewport, not from percentages of the stage, so it holds
61
+ however many wrappers a caller puts between the stage and its `img`/`video`. `max-*` with
62
+ `auto` size never upscales past the natural size; `contain` never crops. */
63
+ .jini-lightbox-stage {
64
+ --jini-lightbox-media-max-w: calc(100vw - var(--jini-lightbox-edge) * 2);
65
+ --jini-lightbox-media-max-h: calc(100dvh - var(--jini-lightbox-bar) * 2);
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ min-width: 0;
70
+ min-height: 0;
71
+ overflow: hidden;
72
+ }
73
+ .jini-lightbox-stage :is(img, video) {
74
+ display: block;
75
+ width: auto;
76
+ height: auto;
77
+ max-width: var(--jini-lightbox-media-max-w);
78
+ max-height: var(--jini-lightbox-media-max-h);
79
+ object-fit: contain;
80
+ border-radius: var(--jini-radius-sm, 6px);
81
+ background: var(--jini-lightbox-media-bg);
82
+ }
83
+ /* Documents (PDF, HTML) have no natural size: give them the whole stage. */
84
+ .jini-lightbox-stage iframe {
85
+ width: var(--jini-lightbox-media-max-w);
86
+ height: var(--jini-lightbox-media-max-h);
87
+ border: 0;
88
+ border-radius: var(--jini-radius-sm, 6px);
89
+ background: var(--jini-lightbox-document-bg);
90
+ }
91
+
92
+ .jini-lightbox-footer {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 0.75rem;
96
+ min-width: 0;
97
+ padding-bottom: env(safe-area-inset-bottom, 0px);
98
+ }
99
+ .jini-lightbox-actions {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 0.5rem;
103
+ min-width: 0;
104
+ color: var(--jini-lightbox-ink-muted);
105
+ font-size: 0.8125rem;
106
+ }
107
+ .jini-lightbox-nav {
108
+ display: flex;
109
+ gap: 0.375rem;
110
+ margin-left: auto;
111
+ }
112
+
113
+ /* Small, round, quiet: a translucent chip with a hairline ring reads on a light or dark frame. */
114
+ .jini-lightbox-button {
115
+ display: grid;
116
+ place-items: center;
117
+ flex-shrink: 0;
118
+ width: var(--jini-lightbox-button);
119
+ height: var(--jini-lightbox-button);
120
+ padding: 0;
121
+ border: 1px solid var(--jini-lightbox-button-border);
122
+ border-radius: 50%;
123
+ color: var(--jini-lightbox-ink);
124
+ background: var(--jini-lightbox-button-bg);
125
+ cursor: pointer;
126
+ }
127
+ .jini-lightbox-button:hover:not(:disabled) { background: var(--jini-lightbox-button-bg-hover); border-color: var(--jini-lightbox-button-border-hover); }
128
+ .jini-lightbox-button:focus-visible { outline: 2px solid var(--jini-lightbox-ink); outline-offset: 2px; }
129
+ /* Same chip, only fainter: a host `button:disabled` fill (the Tovu admin's light `--surface-3`) would
130
+ otherwise turn the end-of-set arrow into a bright pressed-looking disc. */
131
+ .jini-lightbox-button:disabled {
132
+ opacity: 0.3;
133
+ cursor: default;
134
+ color: var(--jini-lightbox-ink);
135
+ background: var(--jini-lightbox-button-bg);
136
+ }
137
+
138
+ @media (prefers-reduced-motion: no-preference) {
139
+ .jini-lightbox[open] { animation: jini-lightbox-in 160ms ease-out; }
140
+ .jini-lightbox-button { transition: background-color 120ms ease, border-color 120ms ease; }
141
+ }
142
+ @keyframes jini-lightbox-in { from { opacity: 0; } }