@jini-ai/ui 0.5.5 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -2
- 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 +9 -12
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
|
@@ -29,6 +29,154 @@ export const ABOUT_UPDATE_KEYS = {
|
|
|
29
29
|
notChecked: 'settings.updateStatusNotChecked',
|
|
30
30
|
check: 'settings.updateCheck',
|
|
31
31
|
};
|
|
32
|
+
const K = ABOUT_UPDATE_KEYS;
|
|
33
|
+
/**
|
|
34
|
+
* Shared by the capability gate and the reported `unsupported` state — one
|
|
35
|
+
* outcome, since the operator can do nothing about either from this panel.
|
|
36
|
+
*/
|
|
37
|
+
const UNSUPPORTED_CONTROL = {
|
|
38
|
+
primaryAction: null,
|
|
39
|
+
primaryLabelKey: null,
|
|
40
|
+
showReleaseLink: true,
|
|
41
|
+
statusKey: K.unsupported,
|
|
42
|
+
statusTone: 'warning',
|
|
43
|
+
};
|
|
44
|
+
const DEVELOPMENT_CONTROL = {
|
|
45
|
+
primaryAction: null,
|
|
46
|
+
primaryLabelKey: null,
|
|
47
|
+
showReleaseLink: true,
|
|
48
|
+
statusKey: K.development,
|
|
49
|
+
statusTone: 'neutral',
|
|
50
|
+
};
|
|
51
|
+
/** `{ statusVars: { version } }` when the version is known (truthy), else nothing. */
|
|
52
|
+
function versionVars(model) {
|
|
53
|
+
return model.availableVersion ? { statusVars: { version: model.availableVersion } } : {};
|
|
54
|
+
}
|
|
55
|
+
/** Whether either install route (hand off to an installer, or apply in place) is open. */
|
|
56
|
+
function canInstall(model) {
|
|
57
|
+
return Boolean(model.canOpenInstaller || model.canApplyInPlace);
|
|
58
|
+
}
|
|
59
|
+
function deriveAvailable(model) {
|
|
60
|
+
return {
|
|
61
|
+
primaryAction: model.canDownload ? 'download' : null,
|
|
62
|
+
primaryLabelKey: model.canDownload ? K.download : null,
|
|
63
|
+
showReleaseLink: true,
|
|
64
|
+
statusKey: model.availableVersion ? K.available : K.availableUnknown,
|
|
65
|
+
statusTone: 'warning',
|
|
66
|
+
...versionVars(model),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function deriveDownloading(model) {
|
|
70
|
+
const percent = model.downloadProgress?.percent;
|
|
71
|
+
const known = typeof percent === 'number';
|
|
72
|
+
return {
|
|
73
|
+
primaryAction: null,
|
|
74
|
+
primaryLabelKey: K.downloadingLabel,
|
|
75
|
+
showReleaseLink: true,
|
|
76
|
+
statusKey: known ? K.downloadingPercent : K.downloading,
|
|
77
|
+
statusTone: 'neutral',
|
|
78
|
+
...(known ? { statusVars: { percent } } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function deriveDownloaded(model) {
|
|
82
|
+
const payload = model.updateKind === 'payload';
|
|
83
|
+
// The installer is already running; the only useful act left is to get
|
|
84
|
+
// out of its way.
|
|
85
|
+
if (model.installerOpened && model.canQuitAfterInstallerOpen) {
|
|
86
|
+
return {
|
|
87
|
+
primaryAction: 'quit',
|
|
88
|
+
primaryLabelKey: K.quit,
|
|
89
|
+
showReleaseLink: false,
|
|
90
|
+
statusKey: payload ? K.installingRestart : K.opening,
|
|
91
|
+
statusTone: 'neutral',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const installable = canInstall(model);
|
|
95
|
+
return {
|
|
96
|
+
primaryAction: installable ? 'install' : null,
|
|
97
|
+
primaryLabelKey: installable ? (payload ? K.installRestart : K.installNow) : null,
|
|
98
|
+
showReleaseLink: true,
|
|
99
|
+
statusKey: model.availableVersion ? K.ready : K.readyUnknown,
|
|
100
|
+
statusTone: 'success',
|
|
101
|
+
...versionVars(model),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Retry the furthest-along step that is still possible: re-install a
|
|
106
|
+
* download we already have, else re-download a version we know about,
|
|
107
|
+
* else start over. Sending the operator back to "check" when a finished
|
|
108
|
+
* download is sitting on disk would throw away the expensive part.
|
|
109
|
+
*/
|
|
110
|
+
function errorRetryAction(model) {
|
|
111
|
+
if (model.status?.downloadPath != null && canInstall(model))
|
|
112
|
+
return 'install';
|
|
113
|
+
if (model.availableVersion != null && model.canDownload)
|
|
114
|
+
return 'download';
|
|
115
|
+
return 'check';
|
|
116
|
+
}
|
|
117
|
+
function deriveError(model) {
|
|
118
|
+
return {
|
|
119
|
+
primaryAction: errorRetryAction(model),
|
|
120
|
+
primaryLabelKey: K.retry,
|
|
121
|
+
showReleaseLink: true,
|
|
122
|
+
statusKey: K.failed,
|
|
123
|
+
statusTone: 'error',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* `idle`, and any state a newer backend reports that this build predates.
|
|
128
|
+
* Falling through to "you can check" is the safe default: it offers the
|
|
129
|
+
* one action that is valid from any unknown state.
|
|
130
|
+
*/
|
|
131
|
+
const IDLE_CONTROL = {
|
|
132
|
+
primaryAction: 'check',
|
|
133
|
+
primaryLabelKey: K.check,
|
|
134
|
+
showReleaseLink: true,
|
|
135
|
+
statusKey: K.notChecked,
|
|
136
|
+
statusTone: 'neutral',
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* One deriver per reported state. A `Map` (not an object literal) so a
|
|
140
|
+
* state string that happens to name an `Object.prototype` member can never
|
|
141
|
+
* resolve to anything but the idle fallback.
|
|
142
|
+
*/
|
|
143
|
+
const STATE_DERIVERS = new Map([
|
|
144
|
+
[
|
|
145
|
+
'checking',
|
|
146
|
+
() => ({
|
|
147
|
+
primaryAction: null,
|
|
148
|
+
primaryLabelKey: K.checkingLabel,
|
|
149
|
+
showReleaseLink: true,
|
|
150
|
+
statusKey: K.checking,
|
|
151
|
+
statusTone: 'neutral',
|
|
152
|
+
}),
|
|
153
|
+
],
|
|
154
|
+
[
|
|
155
|
+
'not-available',
|
|
156
|
+
() => ({
|
|
157
|
+
primaryAction: 'check',
|
|
158
|
+
primaryLabelKey: K.recheck,
|
|
159
|
+
showReleaseLink: true,
|
|
160
|
+
statusKey: K.upToDate,
|
|
161
|
+
statusTone: 'success',
|
|
162
|
+
}),
|
|
163
|
+
],
|
|
164
|
+
['available', deriveAvailable],
|
|
165
|
+
['downloading', deriveDownloading],
|
|
166
|
+
['downloaded', deriveDownloaded],
|
|
167
|
+
[
|
|
168
|
+
'installing',
|
|
169
|
+
() => ({
|
|
170
|
+
primaryAction: null,
|
|
171
|
+
primaryLabelKey: K.installingRestart,
|
|
172
|
+
showReleaseLink: false,
|
|
173
|
+
statusKey: K.installing,
|
|
174
|
+
statusTone: 'neutral',
|
|
175
|
+
}),
|
|
176
|
+
],
|
|
177
|
+
['error', deriveError],
|
|
178
|
+
['unsupported', () => ({ ...UNSUPPORTED_CONTROL })],
|
|
179
|
+
]);
|
|
32
180
|
/**
|
|
33
181
|
* Derives what the About panel's update row should show.
|
|
34
182
|
*
|
|
@@ -45,7 +193,7 @@ export const ABOUT_UPDATE_KEYS = {
|
|
|
45
193
|
* build — one shared "unsupported" outcome, since the operator can do
|
|
46
194
|
* nothing about any of them from this panel.
|
|
47
195
|
*
|
|
48
|
-
* After that it is a
|
|
196
|
+
* After that it is a lookup of the reported state in `STATE_DERIVERS`. Two subtleties worth
|
|
49
197
|
* keeping when reading it:
|
|
50
198
|
*
|
|
51
199
|
* - `primaryLabelKey` can be set while `primaryAction` is `null`. That is the
|
|
@@ -55,136 +203,15 @@ export const ABOUT_UPDATE_KEYS = {
|
|
|
55
203
|
* about to disappear (installing, or quitting to let an installer run).
|
|
56
204
|
* Offering a navigation there invites a click that cannot complete.
|
|
57
205
|
*
|
|
58
|
-
* @complexity O(1) — a fixed
|
|
206
|
+
* @complexity O(1) — a fixed-size map lookup, no iteration.
|
|
59
207
|
*/
|
|
60
208
|
export function deriveAboutUpdateControl(model, appVersionInfo) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
statusKey: K.development,
|
|
68
|
-
statusTone: 'neutral',
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
if (model.environment !== 'desktop' || !model.enabled || !model.supported) {
|
|
72
|
-
return {
|
|
73
|
-
primaryAction: null,
|
|
74
|
-
primaryLabelKey: null,
|
|
75
|
-
showReleaseLink: true,
|
|
76
|
-
statusKey: K.unsupported,
|
|
77
|
-
statusTone: 'warning',
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
switch (model.status?.state) {
|
|
81
|
-
case 'checking':
|
|
82
|
-
return {
|
|
83
|
-
primaryAction: null,
|
|
84
|
-
primaryLabelKey: K.checkingLabel,
|
|
85
|
-
showReleaseLink: true,
|
|
86
|
-
statusKey: K.checking,
|
|
87
|
-
statusTone: 'neutral',
|
|
88
|
-
};
|
|
89
|
-
case 'not-available':
|
|
90
|
-
return {
|
|
91
|
-
primaryAction: 'check',
|
|
92
|
-
primaryLabelKey: K.recheck,
|
|
93
|
-
showReleaseLink: true,
|
|
94
|
-
statusKey: K.upToDate,
|
|
95
|
-
statusTone: 'success',
|
|
96
|
-
};
|
|
97
|
-
case 'available':
|
|
98
|
-
return {
|
|
99
|
-
primaryAction: model.canDownload ? 'download' : null,
|
|
100
|
-
primaryLabelKey: model.canDownload ? K.download : null,
|
|
101
|
-
showReleaseLink: true,
|
|
102
|
-
statusKey: model.availableVersion ? K.available : K.availableUnknown,
|
|
103
|
-
statusTone: 'warning',
|
|
104
|
-
...(model.availableVersion ? { statusVars: { version: model.availableVersion } } : {}),
|
|
105
|
-
};
|
|
106
|
-
case 'downloading': {
|
|
107
|
-
const percent = model.downloadProgress?.percent;
|
|
108
|
-
const known = typeof percent === 'number';
|
|
109
|
-
return {
|
|
110
|
-
primaryAction: null,
|
|
111
|
-
primaryLabelKey: K.downloadingLabel,
|
|
112
|
-
showReleaseLink: true,
|
|
113
|
-
statusKey: known ? K.downloadingPercent : K.downloading,
|
|
114
|
-
statusTone: 'neutral',
|
|
115
|
-
...(known ? { statusVars: { percent } } : {}),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
case 'downloaded': {
|
|
119
|
-
// The installer is already running; the only useful act left is to get
|
|
120
|
-
// out of its way.
|
|
121
|
-
if (model.installerOpened && model.canQuitAfterInstallerOpen) {
|
|
122
|
-
return {
|
|
123
|
-
primaryAction: 'quit',
|
|
124
|
-
primaryLabelKey: K.quit,
|
|
125
|
-
showReleaseLink: false,
|
|
126
|
-
statusKey: model.updateKind === 'payload' ? K.installingRestart : K.opening,
|
|
127
|
-
statusTone: 'neutral',
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
const canInstall = Boolean(model.canOpenInstaller || model.canApplyInPlace);
|
|
131
|
-
return {
|
|
132
|
-
primaryAction: canInstall ? 'install' : null,
|
|
133
|
-
primaryLabelKey: canInstall ? (model.updateKind === 'payload' ? K.installRestart : K.installNow) : null,
|
|
134
|
-
showReleaseLink: true,
|
|
135
|
-
statusKey: model.availableVersion ? K.ready : K.readyUnknown,
|
|
136
|
-
statusTone: 'success',
|
|
137
|
-
...(model.availableVersion ? { statusVars: { version: model.availableVersion } } : {}),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
case 'installing':
|
|
141
|
-
return {
|
|
142
|
-
primaryAction: null,
|
|
143
|
-
primaryLabelKey: K.installingRestart,
|
|
144
|
-
showReleaseLink: false,
|
|
145
|
-
statusKey: K.installing,
|
|
146
|
-
statusTone: 'neutral',
|
|
147
|
-
};
|
|
148
|
-
case 'error': {
|
|
149
|
-
// Retry the furthest-along step that is still possible: re-install a
|
|
150
|
-
// download we already have, else re-download a version we know about,
|
|
151
|
-
// else start over. Sending the operator back to "check" when a finished
|
|
152
|
-
// download is sitting on disk would throw away the expensive part.
|
|
153
|
-
const canRetryInstall = model.status?.downloadPath != null && Boolean(model.canOpenInstaller || model.canApplyInPlace);
|
|
154
|
-
const primaryAction = canRetryInstall
|
|
155
|
-
? 'install'
|
|
156
|
-
: model.availableVersion != null && model.canDownload
|
|
157
|
-
? 'download'
|
|
158
|
-
: 'check';
|
|
159
|
-
return {
|
|
160
|
-
primaryAction,
|
|
161
|
-
primaryLabelKey: K.retry,
|
|
162
|
-
showReleaseLink: true,
|
|
163
|
-
statusKey: K.failed,
|
|
164
|
-
statusTone: 'error',
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
case 'unsupported':
|
|
168
|
-
return {
|
|
169
|
-
primaryAction: null,
|
|
170
|
-
primaryLabelKey: null,
|
|
171
|
-
showReleaseLink: true,
|
|
172
|
-
statusKey: K.unsupported,
|
|
173
|
-
statusTone: 'warning',
|
|
174
|
-
};
|
|
175
|
-
// `idle`, and any state a newer backend reports that this build predates.
|
|
176
|
-
// Falling through to "you can check" is the safe default: it offers the
|
|
177
|
-
// one action that is valid from any unknown state.
|
|
178
|
-
case 'idle':
|
|
179
|
-
default:
|
|
180
|
-
return {
|
|
181
|
-
primaryAction: 'check',
|
|
182
|
-
primaryLabelKey: K.check,
|
|
183
|
-
showReleaseLink: true,
|
|
184
|
-
statusKey: K.notChecked,
|
|
185
|
-
statusTone: 'neutral',
|
|
186
|
-
};
|
|
187
|
-
}
|
|
209
|
+
if (appVersionInfo?.packaged === false)
|
|
210
|
+
return { ...DEVELOPMENT_CONTROL };
|
|
211
|
+
if (model.environment !== 'desktop' || !model.enabled || !model.supported)
|
|
212
|
+
return { ...UNSUPPORTED_CONTROL };
|
|
213
|
+
const derive = model.status ? STATE_DERIVERS.get(model.status.state) : undefined;
|
|
214
|
+
return derive ? derive(model) : { ...IDLE_CONTROL };
|
|
188
215
|
}
|
|
189
216
|
/**
|
|
190
217
|
* Whether the primary button should be disabled.
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { useLightbox } from "./Lightbox/Lightbox.hooks.js";
|
|
2
2
|
export interface ImagePreviewModalProps {
|
|
3
3
|
open: boolean;
|
|
4
4
|
src: string;
|
|
5
5
|
alt: string;
|
|
6
6
|
onClose: () => void;
|
|
7
7
|
/** A fake controller can exercise rendering without a real native-dialog lifecycle. */
|
|
8
|
-
useModal?: typeof
|
|
8
|
+
useModal?: typeof useLightbox | undefined;
|
|
9
9
|
/** Optional tagging preserves existing untagged renders when no agent handle is supplied. */
|
|
10
10
|
agentHandle?: string | undefined;
|
|
11
11
|
/** No locale is owned here; a translated caller must pass its own accessible close label. */
|
|
12
12
|
closeLabel?: string | undefined;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
14
|
+
/** One image in the shared `Lightbox`; the alt text doubles as its title. Kept as a named
|
|
15
|
+
* single-image entry point so callers need not assemble the lightbox for the common case. */
|
|
15
16
|
export declare function ImagePreviewModal({ open, src, alt, onClose, useModal, agentHandle: handle, closeLabel, }: ImagePreviewModalProps): import("react").JSX.Element;
|
|
16
17
|
//# sourceMappingURL=ImagePreviewModal.d.ts.map
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { agentHandle } from "@jini-ai/agentic";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Controlled native-dialog lightbox. The host supplies image copy and a translated close label. */
|
|
9
|
-
export function ImagePreviewModal({ open, src, alt, onClose, useModal = useImagePreviewModal, agentHandle: handle, closeLabel = "Close preview", }) {
|
|
10
|
-
const { dialogRef, handleNativeCancel, handleBackdropClick } = useModal({ open, onClose });
|
|
11
|
-
return (_jsxs("dialog", { ref: dialogRef, className: "image-preview-modal", "aria-label": alt, onCancel: handleNativeCancel, onClick: handleBackdropClick, children: [_jsx("button", { type: "button", className: "image-preview-modal-close", onClick: onClose, "aria-label": closeLabel, ...(handle ? agentHandle({ handle: handle }, { role: "button", label: closeLabel }) : {}), children: "\u00D7" }), _jsx("img", { src: src, alt: alt })] }));
|
|
3
|
+
import { Lightbox } from "./Lightbox/Lightbox.js";
|
|
4
|
+
/** One image in the shared `Lightbox`; the alt text doubles as its title. Kept as a named
|
|
5
|
+
* single-image entry point so callers need not assemble the lightbox for the common case. */
|
|
6
|
+
export function ImagePreviewModal({ open, src, alt, onClose, useModal, agentHandle: handle, closeLabel = "Close preview", }) {
|
|
7
|
+
return (_jsx(Lightbox, { open: open, title: alt, onClose: onClose, className: "image-preview-modal", labels: { close: closeLabel }, useLightboxHook: useModal, partAttrs: handle ? { close: { ...agentHandle({ handle: handle }, { role: "button", label: closeLabel }) } } : undefined, children: _jsx("img", { src: src, alt: alt }) }));
|
|
12
8
|
}
|
|
13
9
|
//# sourceMappingURL=ImagePreviewModal.js.map
|
|
@@ -5,7 +5,8 @@ import { useInfoTip } from "./InfoTip.hooks.js";
|
|
|
5
5
|
* and no touch visibility. Hover/focus opens this affordance; Escape dismisses without moving
|
|
6
6
|
* focus, which onBlur alone cannot do.
|
|
7
7
|
* A body portal escapes overflow:hidden/scrolling ancestors: z-index cannot rescue an absolutely
|
|
8
|
-
* positioned descendant from clipping.
|
|
8
|
+
* positioned descendant from clipping. Inside a modal <dialog> it portals into that dialog
|
|
9
|
+
* instead, because the dialog's top layer covers everything in body. Measured viewport coordinates position it above by
|
|
9
10
|
* preference, with the hook's headroom guard choosing below near the viewport's top; near a side
|
|
10
11
|
* edge (or a host panel marked `data-jini-tip-bounds`) it shifts by its real width, never squeezes.
|
|
11
12
|
* Keep state and DOM measurement in the injectable hook so rendering can use fixed fake state.
|
|
@@ -13,7 +14,7 @@ import { useInfoTip } from "./InfoTip.hooks.js";
|
|
|
13
14
|
* tap, blur or Escape; a hover-opened tip still closes when the pointer leaves.
|
|
14
15
|
*/
|
|
15
16
|
export interface InfoTipProps {
|
|
16
|
-
/** The explanation the bubble shows. */
|
|
17
|
+
/** The explanation the bubble shows; newlines separate lines, and "• " lines show as a hanging list. */
|
|
17
18
|
label: string;
|
|
18
19
|
/**
|
|
19
20
|
* Short accessible name for the icon ("About subagents"). Omitted, the icon is named by `label`
|
|
@@ -78,6 +78,8 @@ export function useInfoTip(_input = {}, { placement: preferredPlacement } = {})
|
|
|
78
78
|
}, [open]);
|
|
79
79
|
function handleIconKeyDown(e) {
|
|
80
80
|
if (e.key === "Escape" && open) {
|
|
81
|
+
// preventDefault also stops an enclosing modal <dialog> from closing on the same keypress.
|
|
82
|
+
e.preventDefault();
|
|
81
83
|
e.stopPropagation();
|
|
82
84
|
hide();
|
|
83
85
|
return;
|
|
@@ -3,18 +3,45 @@ import { useId } from "react";
|
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { agentHandle } from "@jini-ai/agentic";
|
|
5
5
|
import { useInfoTip } from "./InfoTip.hooks.js";
|
|
6
|
-
import { infoTipBubbleStyle } from "./InfoTip.rules.js";
|
|
6
|
+
import { infoTipBubbleStyle, infoTipLines } from "./InfoTip.rules.js";
|
|
7
7
|
/** Focus/hover tooltip that portals outside clipping ancestors; Escape dismisses in place. */
|
|
8
8
|
export function InfoTip({ label, triggerLabel, descriptionId, placement: preferredPlacement, useTip = useInfoTip, agentHandle: handle, icon }) {
|
|
9
|
-
const
|
|
9
|
+
const inline = preferredPlacement === "inline";
|
|
10
|
+
const { open, placement, coords, iconRef, bubbleRef, show, hide, leave, toggle, handleIconKeyDown } = useTip({}, { placement: inline ? undefined : preferredPlacement });
|
|
10
11
|
const autoId = useId();
|
|
11
|
-
const name = triggerLabel
|
|
12
|
+
const { name, textId, describedBy } = infoTipNaming({ label, autoId }, { triggerLabel, descriptionId });
|
|
13
|
+
return (_jsxs("span", { className: inline ? "info-tip info-tip--inline" : "info-tip", children: [_jsx("span", { ref: iconRef, className: icon ? "info-tip-icon info-tip-icon--custom" : "info-tip-icon", role: "button", tabIndex: 0, "aria-label": name, "aria-expanded": open, ...(describedBy ? { "aria-describedby": describedBy } : {}), onMouseEnter: show, onMouseLeave: leave, onFocus: show, onBlur: hide, onClick: toggle, onKeyDown: handleIconKeyDown, ...(handle ? agentHandle({ handle }, { role: "button", label: name }) : {}), children: icon ?? "ⓘ" }), textId ? _jsx("span", { id: textId, hidden: true, children: label }) : null, open ? _jsx(InfoTipOpenCopy, { label: label, inline: inline, placement: placement, coords: coords, iconRef: iconRef, bubbleRef: bubbleRef }) : null] }));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The icon's accessible name and the id of the hidden copy of `label` (if any).
|
|
17
|
+
* @returns `name` for `aria-label`, `textId` for the hidden copy, `describedBy` for `aria-describedby`.
|
|
18
|
+
* @complexity O(1).
|
|
19
|
+
*/
|
|
20
|
+
function infoTipNaming({ label, autoId }, { triggerLabel, descriptionId }) {
|
|
12
21
|
// With no short name the icon is already named by the full text; describing it again would
|
|
13
22
|
// make a screen reader read it twice.
|
|
14
23
|
const textId = descriptionId ?? (triggerLabel ? autoId : undefined);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
return { name: triggerLabel ?? label, textId, describedBy: triggerLabel ? textId : undefined };
|
|
25
|
+
}
|
|
26
|
+
/** The open tip: inline copy beside the icon, or the bubble portaled out of clipping ancestors. */
|
|
27
|
+
function InfoTipOpenCopy({ label, inline, placement, coords, iconRef, bubbleRef }) {
|
|
28
|
+
if (inline)
|
|
29
|
+
return _jsx("span", { className: "info-tip-inline-copy", "aria-hidden": "true", children: label });
|
|
30
|
+
return createPortal(_jsx("span", { ref: bubbleRef, className: placement === "below" ? "info-tip-bubble info-tip-bubble-below" : "info-tip-bubble", role: "presentation", "aria-hidden": "true", style: { ...infoTipBubbleStyle(coords), whiteSpace: "pre-line" }, children: _jsx(InfoTipBubbleText, { label: label }) }),
|
|
31
|
+
// A modal <dialog> sits in the browser's top layer, above anything portaled to body.
|
|
32
|
+
iconRef.current?.closest("dialog") ?? document.body);
|
|
33
|
+
}
|
|
34
|
+
/** The bubble's text: a hanging list when any line is a "• " bullet, else the label as-is. */
|
|
35
|
+
function InfoTipBubbleText({ label }) {
|
|
36
|
+
const lines = infoTipLines(label);
|
|
37
|
+
if (!lines)
|
|
38
|
+
return label;
|
|
39
|
+
// Keep the label's text separators; flex layout alone loses them from textContent.
|
|
40
|
+
return lines.map((line, index) => {
|
|
41
|
+
const separator = index < lines.length - 1 ? "\n" : null;
|
|
42
|
+
return line.bullet
|
|
43
|
+
? _jsxs("span", { className: "info-tip-bullet", children: [_jsx("span", { "aria-hidden": "true", children: "\u2022" }), " ", _jsx("span", { children: line.text }), separator] }, index)
|
|
44
|
+
: _jsxs("span", { className: "info-tip-line", children: [line.text, separator] }, index);
|
|
45
|
+
});
|
|
19
46
|
}
|
|
20
47
|
//# sourceMappingURL=InfoTip.js.map
|
|
@@ -69,4 +69,17 @@ export declare function infoTipBubbleStyle(coords: {
|
|
|
69
69
|
left: number;
|
|
70
70
|
arrowLeft?: number | undefined;
|
|
71
71
|
}): CSSProperties;
|
|
72
|
+
/** One line of a bubble that holds a bulleted list. */
|
|
73
|
+
export interface InfoTipLine {
|
|
74
|
+
text: string;
|
|
75
|
+
bullet: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Splits a label into lines when any line is a "• " bullet (e.g. a settings group's one tip, a line
|
|
79
|
+
* per option), so each bullet's wrapped text can hang past its dot; `pre-line` alone wraps under it.
|
|
80
|
+
* @param label The tip text; newlines separate lines.
|
|
81
|
+
* @returns The lines with their "• " removed, or `null` when the label has no bullet line.
|
|
82
|
+
* @complexity O(n) in the label's length.
|
|
83
|
+
*/
|
|
84
|
+
export declare function infoTipLines(label: string): InfoTipLine[] | null;
|
|
72
85
|
//# sourceMappingURL=InfoTip.rules.d.ts.map
|
|
@@ -32,25 +32,45 @@ const ARROW_INSET_PX = 10;
|
|
|
32
32
|
export function placeInfoTipBubble(input, { bounds, preferred } = {}) {
|
|
33
33
|
const { anchor, bubble, viewport } = input;
|
|
34
34
|
const width = bubble && bubble.width > 0 ? bubble.width : MAX_BUBBLE_WIDTH_PX;
|
|
35
|
+
const height = bubble && bubble.height > 0 ? bubble.height : 0;
|
|
36
|
+
const { left, arrowLeft } = placeHorizontally({ anchor, width, viewportWidth: viewport.width }, { bounds });
|
|
37
|
+
const placement = placeVertically({ anchor, height, viewportHeight: viewport.height }, { preferred });
|
|
38
|
+
return { placement, top: placement === "above" ? anchor.top : anchor.top + anchor.height, left, arrowLeft };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The horizontal half of {@link placeInfoTipBubble}: the bubble's centre x and the arrow's offset.
|
|
42
|
+
* @param input.width The bubble's measured width, or the worst-case estimate before measurement.
|
|
43
|
+
* @param options.bounds A host panel's limits; used only when the bubble fits inside them.
|
|
44
|
+
* @returns The bubble's centre x and the arrow's x inside the bubble.
|
|
45
|
+
* @complexity O(1).
|
|
46
|
+
*/
|
|
47
|
+
function placeHorizontally({ anchor, width, viewportWidth }, { bounds }) {
|
|
35
48
|
const anchorCentre = anchor.left + anchor.width / 2;
|
|
36
|
-
const viewportSpan = { lo: EDGE_MARGIN_PX, hi:
|
|
49
|
+
const viewportSpan = { lo: EDGE_MARGIN_PX, hi: viewportWidth - EDGE_MARGIN_PX };
|
|
37
50
|
const boundsSpan = bounds
|
|
38
|
-
? { lo: Math.max(bounds.left, 0) + EDGE_MARGIN_PX, hi: Math.min(bounds.right,
|
|
51
|
+
? { lo: Math.max(bounds.left, 0) + EDGE_MARGIN_PX, hi: Math.min(bounds.right, viewportWidth) - EDGE_MARGIN_PX }
|
|
39
52
|
: null;
|
|
40
53
|
const span = boundsSpan && boundsSpan.hi - boundsSpan.lo >= width ? boundsSpan : viewportSpan;
|
|
41
54
|
const left = span.hi - span.lo < width
|
|
42
|
-
?
|
|
55
|
+
? viewportWidth / 2
|
|
43
56
|
: Math.min(Math.max(anchorCentre, span.lo + width / 2), span.hi - width / 2);
|
|
44
57
|
const bubbleLeftEdge = left - width / 2;
|
|
45
58
|
const arrowLeft = Math.min(Math.max(anchorCentre - bubbleLeftEdge, ARROW_INSET_PX), width - ARROW_INSET_PX);
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
return { left, arrowLeft };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The vertical half of {@link placeInfoTipBubble}: which side of the icon the bubble opens on.
|
|
63
|
+
* @param input.height The bubble's measured height, or 0 before measurement.
|
|
64
|
+
* @param options.preferred A host's preferred side; only "bottom" changes the default above-first rule.
|
|
65
|
+
* @returns The side the bubble opens on.
|
|
66
|
+
* @complexity O(1).
|
|
67
|
+
*/
|
|
68
|
+
function placeVertically({ anchor, height, viewportHeight }, { preferred }) {
|
|
48
69
|
const fitsAbove = height > 0 ? anchor.top >= height + GAP_PX : anchor.top >= ABOVE_HEADROOM_PX;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return { placement, top: placement === "above" ? anchor.top : anchorBottom, left, arrowLeft };
|
|
70
|
+
if (preferred !== "bottom")
|
|
71
|
+
return fitsAbove ? "above" : "below";
|
|
72
|
+
const fitsBelow = height === 0 || anchor.top + anchor.height + GAP_PX + height <= viewportHeight;
|
|
73
|
+
return !fitsBelow && fitsAbove ? "above" : "below";
|
|
54
74
|
}
|
|
55
75
|
/**
|
|
56
76
|
* The portal's inline style: position plus, once measured, the arrow offset as a CSS variable
|
|
@@ -65,4 +85,17 @@ export function infoTipBubbleStyle(coords) {
|
|
|
65
85
|
style["--info-tip-arrow-left"] = `${coords.arrowLeft}px`;
|
|
66
86
|
return style;
|
|
67
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Splits a label into lines when any line is a "• " bullet (e.g. a settings group's one tip, a line
|
|
90
|
+
* per option), so each bullet's wrapped text can hang past its dot; `pre-line` alone wraps under it.
|
|
91
|
+
* @param label The tip text; newlines separate lines.
|
|
92
|
+
* @returns The lines with their "• " removed, or `null` when the label has no bullet line.
|
|
93
|
+
* @complexity O(n) in the label's length.
|
|
94
|
+
*/
|
|
95
|
+
export function infoTipLines(label) {
|
|
96
|
+
const lines = label.split("\n");
|
|
97
|
+
if (!lines.some((line) => line.startsWith("• ")))
|
|
98
|
+
return null;
|
|
99
|
+
return lines.map((line) => line.startsWith("• ") ? { text: line.slice(2), bullet: true } : { text: line, bullet: false });
|
|
100
|
+
}
|
|
68
101
|
//# sourceMappingURL=InfoTip.rules.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { useLightbox } from "./Lightbox.hooks.js";
|
|
3
|
+
type LightboxPart = "dialog" | "close" | "previous" | "next";
|
|
4
|
+
export interface LightboxProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
/** Shown above the media (usually the file name) and used as the dialog's accessible name. */
|
|
7
|
+
title: string;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
/** The media itself. `img`/`video` children are contain-fit to the viewport and never upscaled. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
/** Zero-based position and set size; with `onNavigate` and `count > 1` the arrow pair renders. */
|
|
12
|
+
index?: number | undefined;
|
|
13
|
+
count?: number | undefined;
|
|
14
|
+
onNavigate?: ((index: number) => void) | undefined;
|
|
15
|
+
/** Caller-specific controls (download, copy, metadata), placed bottom-left opposite the arrows. */
|
|
16
|
+
actions?: ReactNode;
|
|
17
|
+
/** No locale is owned here; a translated caller passes its own accessible labels. */
|
|
18
|
+
labels?: Partial<Record<"close" | "previous" | "next", string>> | undefined;
|
|
19
|
+
/** Extra class on the `<dialog>` so a caller can scope its own media rules. */
|
|
20
|
+
className?: string | undefined;
|
|
21
|
+
/** Per-part attributes (agent handles, `data-jini-part` markers) without the shell knowing them. */
|
|
22
|
+
partAttrs?: Partial<Record<LightboxPart, Record<string, string>>> | undefined;
|
|
23
|
+
/** A fake controller can exercise rendering without a real native-dialog lifecycle. */
|
|
24
|
+
useLightboxHook?: typeof useLightbox | undefined;
|
|
25
|
+
}
|
|
26
|
+
/** Full-viewport media lightbox: title and counter top-left, close top-right, the media as large
|
|
27
|
+
* as fits, caller actions bottom-left and the previous/next pair together bottom-right. */
|
|
28
|
+
export declare function Lightbox(props: LightboxProps): import("react").JSX.Element;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=Lightbox.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type MouseEvent, type RefObject, type SyntheticEvent } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Lifecycle for the shared `Lightbox`: native `<dialog>` open/close, focus capture and return,
|
|
4
|
+
* arrow-key navigation, Escape and backdrop dismissal. Absorbs the open/close and focus logic the
|
|
5
|
+
* media lightboxes and `ImagePreviewModal` each carried, so every caller gets the same behavior.
|
|
6
|
+
*/
|
|
7
|
+
/** Idempotent native open. `showModal()` throws on an already-open dialog in real browsers; the
|
|
8
|
+
* attribute fallback covers DOM environments without it (jsdom). */
|
|
9
|
+
export declare function openLightboxDialog(dialog: HTMLDialogElement): void;
|
|
10
|
+
/** The close-side mirror of {@link openLightboxDialog} — same fallback and already-closed guard. */
|
|
11
|
+
export declare function closeLightboxDialog(dialog: HTMLDialogElement): void;
|
|
12
|
+
export interface LightboxHookInput {
|
|
13
|
+
open: boolean;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
/** Zero-based position in the set; with `count > 1` and `onNavigate` the arrow pair renders. */
|
|
16
|
+
index?: number | undefined;
|
|
17
|
+
count?: number | undefined;
|
|
18
|
+
onNavigate?: ((index: number) => void) | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface LightboxController {
|
|
21
|
+
dialogRef: RefObject<HTMLDialogElement | null>;
|
|
22
|
+
closeRef: RefObject<HTMLButtonElement | null>;
|
|
23
|
+
prevRef: RefObject<HTMLButtonElement | null>;
|
|
24
|
+
nextRef: RefObject<HTMLButtonElement | null>;
|
|
25
|
+
/** Whether the arrow pair renders at all (a single item has nowhere to go). */
|
|
26
|
+
navigable: boolean;
|
|
27
|
+
hasPrev: boolean;
|
|
28
|
+
hasNext: boolean;
|
|
29
|
+
/** "3 / 12", or "" when there is no set to count. */
|
|
30
|
+
counter: string;
|
|
31
|
+
previous: () => void;
|
|
32
|
+
next: () => void;
|
|
33
|
+
handleCancel: (event: SyntheticEvent<HTMLDialogElement>) => void;
|
|
34
|
+
handleClick: (event: MouseEvent<HTMLDialogElement>) => void;
|
|
35
|
+
}
|
|
36
|
+
/** Marks the stage's empty area: the dialog fills the viewport, so "backdrop" is any click that
|
|
37
|
+
* lands on the dialog itself or the stage around the media rather than on a control or the media. */
|
|
38
|
+
export declare const LIGHTBOX_DISMISS_ATTR = "data-jini-lightbox-dismiss";
|
|
39
|
+
export declare function useLightbox(input: LightboxHookInput): LightboxController;
|
|
40
|
+
//# sourceMappingURL=Lightbox.hooks.d.ts.map
|