@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.
- package/CHANGELOG.md +4 -0
- package/dist/core/theme/validation.js +40 -38
- package/dist/features/a2ui/renderer.js +15 -13
- package/dist/features/about/react/components/AboutTab.js +64 -34
- package/dist/features/about/rules.d.ts +2 -2
- package/dist/features/about/rules.js +156 -129
- package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
- package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
- package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
- package/dist/features/admin-widgets/components/InfoTip.js +34 -7
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
- package/dist/features/admin-widgets/index.d.ts +14 -2
- package/dist/features/admin-widgets/index.js +7 -1
- package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
- package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
- package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
- package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
- package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
- package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
- package/dist/features/asset-tree-browser/rules.js +40 -22
- package/dist/features/browser-chrome/rules.js +74 -70
- package/dist/features/charts/browser.js +11 -5
- package/dist/features/charts/react.js +2 -2
- package/dist/features/charts/table.js +2 -2
- package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
- package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
- package/dist/features/connectors/ConnectorsBrowser.js +26 -14
- package/dist/features/connectors/components/ConnectorCard.js +67 -38
- package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
- package/dist/features/connectors/components/ConnectorGrid.js +11 -6
- package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
- package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
- package/dist/features/connectors/rules.d.ts +8 -1
- package/dist/features/connectors/rules.js +42 -20
- package/dist/features/connectors/types.d.ts +11 -0
- package/dist/features/execution/constants.d.ts +4 -10
- package/dist/features/execution/constants.js +9 -94
- package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
- package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
- package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
- package/dist/features/execution/react/components/ExecutionTab.js +52 -30
- package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
- package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
- package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
- package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
- package/dist/features/execution/rules.d.ts +2 -2
- package/dist/features/execution/rules.js +31 -15
- package/dist/features/execution/types.d.ts +4 -42
- package/dist/features/execution/types.js +0 -17
- package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
- package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
- package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
- package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
- package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
- package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
- package/dist/features/html-editor/css.js +76 -69
- package/dist/features/html-editor/editor-blocks.js +47 -33
- package/dist/features/html-editor/editor-serialization.js +6 -5
- package/dist/features/html-editor/protected-elements.js +39 -28
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
- package/dist/features/html-editor/source-splice.js +51 -30
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
- package/dist/features/i18n/locale.js +20 -16
- package/dist/features/iframe-pool/react/dom-sync.js +8 -6
- package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
- package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
- package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
- package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
- package/dist/features/lexical-rich-text-editor/rules.js +87 -74
- package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
- package/dist/features/mcp-ui/secret-card.js +111 -61
- package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +10 -2
- package/dist/features/mcp-ui/surfaces/fields.js +30 -50
- package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
- package/dist/features/mcp-ui/surfaces/form.js +43 -21
- package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
- package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
- package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
- package/dist/features/media-providers/constants.d.ts +1 -0
- package/dist/features/media-providers/constants.js +2 -81
- package/dist/features/media-providers/index.d.ts +1 -1
- package/dist/features/media-providers/ports.d.ts +2 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
- package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
- package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
- package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
- package/dist/features/media-providers/rules.js +17 -10
- package/dist/features/media-providers/types.d.ts +4 -22
- package/dist/features/media-providers/types.js +3 -8
- package/dist/features/memory/formatters.js +127 -92
- package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
- package/dist/features/memory/react/components/MemoryList.js +37 -18
- package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
- package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
- package/dist/features/observability/long-task.d.ts +0 -12
- package/dist/features/observability/long-task.js +15 -19
- package/dist/features/observability/resource-error.d.ts +0 -10
- package/dist/features/observability/resource-error.js +18 -13
- package/dist/features/observability/stuck-run.js +10 -6
- package/dist/features/observability/telemetry-url.d.ts +6 -0
- package/dist/features/observability/telemetry-url.js +16 -0
- package/dist/features/observability/white-screen.js +12 -24
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
- package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
- package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
- package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
- package/dist/features/progress-card/components/ProgressCard.js +7 -3
- package/dist/features/progress-card/reference-adapters.js +59 -54
- package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
- package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
- package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
- package/dist/features/resource-dashboard/rules.js +2 -8
- package/dist/features/sketch-editor/dom.js +150 -99
- package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
- package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
- package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
- package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
- package/dist/features/skills/react/components/SkillRow.js +38 -18
- package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
- package/dist/features/skills/react/components/SkillsTab.js +24 -14
- package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
- package/dist/features/skills/rules.js +32 -23
- package/dist/features/source-config-list/agent-handles.js +2 -1
- package/dist/features/source-config-list/dependencies.js +53 -49
- package/dist/features/source-config-list/index.d.ts +3 -3
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
- package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
- package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
- package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
- package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
- package/dist/features/source-config-list/types.d.ts +14 -0
- package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
- package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
- package/dist/features/tab-strip/react/components/TabBar.js +8 -1
- package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
- package/dist/features/version-manager/dependencies.d.ts +3 -1
- package/dist/features/version-manager/dependencies.js +3 -2
- package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
- package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
- package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
- package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
- package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
- package/dist/features/viewer-shell/rules.js +19 -23
- package/dist/react/components/CustomSelect.d.ts +38 -1
- package/dist/react/components/CustomSelect.js +94 -38
- package/dist/react/components/OnboardingDropdown.js +20 -8
- package/dist/react/components/RemixIcon.stylesheet.js +3 -1
- package/dist/react/components/Toast.js +13 -2
- package/dist/react/components/TooltipLayer.js +5 -14
- package/dist/react/components/WorkingDirPicker.js +78 -50
- package/dist/react/components/remixicon-font-recovery.js +38 -30
- package/dist/react/components/select-popover.d.ts +8 -6
- package/dist/react/components/select-popover.js +33 -29
- package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
- package/dist/react/mcp-ui/McpUiHost.js +10 -11
- package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
- package/dist/react/mcp-ui/host-message-source.js +15 -5
- package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
- package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
- package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
- package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
- package/dist/renderers/react/components/ArtifactView.js +9 -6
- package/dist/renderers/srcdoc/build.d.ts +4 -1
- package/dist/renderers/srcdoc/build.js +62 -48
- package/dist/styles/admin.css +29 -14
- package/dist/styles/lightbox.css +142 -0
- package/dist/styles/variables.css +128 -0
- package/dist/theme/apply.js +9 -5
- package/dist/utils/auto-open-file.d.ts +2 -1
- package/dist/utils/auto-open-file.js +69 -85
- package/dist/utils/markdown-scroll-sync.js +108 -87
- package/dist/utils/polygon-selection.js +25 -26
- package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
- package/dist/utils/smooth-scroll-to-top.js +2 -32
- package/dist/utils/sse.js +26 -26
- package/dist/utils/unit-bezier.d.ts +7 -0
- package/dist/utils/unit-bezier.js +38 -0
- package/package.json +7 -10
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
- 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: [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
|
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
|
|
30
|
+
return _jsx("div", { className: className, dangerouslySetInnerHTML: { __html: html } });
|
|
28
31
|
}
|
|
29
32
|
default:
|
|
30
|
-
return
|
|
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
|
|
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 {
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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(
|
|
242
|
-
:
|
|
255
|
+
? applySrcDocBridges(withSafety, options.bridges, ctx)
|
|
256
|
+
: withSafety;
|
|
243
257
|
return injectSrcdocTransportActivationBridge(withBridges);
|
|
244
258
|
}
|
|
245
259
|
//# sourceMappingURL=build.js.map
|
package/dist/styles/admin.css
CHANGED
|
@@ -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; } }
|