@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useEffect, useRef } 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 function openLightboxDialog(dialog) {
|
|
10
|
+
if (typeof dialog.showModal === "function") {
|
|
11
|
+
if (!dialog.open)
|
|
12
|
+
dialog.showModal();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
dialog.setAttribute("open", "");
|
|
16
|
+
}
|
|
17
|
+
/** The close-side mirror of {@link openLightboxDialog} — same fallback and already-closed guard. */
|
|
18
|
+
export function closeLightboxDialog(dialog) {
|
|
19
|
+
if (typeof dialog.close === "function") {
|
|
20
|
+
if (dialog.open)
|
|
21
|
+
dialog.close();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
dialog.removeAttribute("open");
|
|
25
|
+
}
|
|
26
|
+
/** Marks the stage's empty area: the dialog fills the viewport, so "backdrop" is any click that
|
|
27
|
+
* lands on the dialog itself or the stage around the media rather than on a control or the media. */
|
|
28
|
+
export const LIGHTBOX_DISMISS_ATTR = "data-jini-lightbox-dismiss";
|
|
29
|
+
function isDismissTarget(target) {
|
|
30
|
+
return target instanceof Element && (target.tagName === "DIALOG" || target.hasAttribute(LIGHTBOX_DISMISS_ATTR));
|
|
31
|
+
}
|
|
32
|
+
function navState(input) {
|
|
33
|
+
const count = input.count ?? 0;
|
|
34
|
+
const index = input.index ?? 0;
|
|
35
|
+
const navigable = count > 1 && typeof input.onNavigate === "function";
|
|
36
|
+
return { count, index, navigable, hasPrev: navigable && index > 0, hasNext: navigable && index + 1 < count };
|
|
37
|
+
}
|
|
38
|
+
export function useLightbox(input) {
|
|
39
|
+
const dialogRef = useRef(null);
|
|
40
|
+
const closeRef = useRef(null);
|
|
41
|
+
const prevRef = useRef(null);
|
|
42
|
+
const nextRef = useRef(null);
|
|
43
|
+
// Captured when the dialog opens, before focus moves inside: by construction the element that
|
|
44
|
+
// opened it (a card's expand button). Restored on close so keyboard users land where they were.
|
|
45
|
+
const triggerRef = useRef(null);
|
|
46
|
+
const pendingFocusRef = useRef(null);
|
|
47
|
+
const nav = navState(input);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
pendingFocusRef.current?.current?.focus({ preventScroll: true });
|
|
50
|
+
pendingFocusRef.current = null;
|
|
51
|
+
}, [input.index]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const dialog = dialogRef.current;
|
|
54
|
+
if (!dialog)
|
|
55
|
+
return;
|
|
56
|
+
if (input.open) {
|
|
57
|
+
triggerRef.current = dialog.ownerDocument.activeElement;
|
|
58
|
+
openLightboxDialog(dialog);
|
|
59
|
+
// Close exists at every position (a single item has no arrows), so it is the stable initial
|
|
60
|
+
// focus target. Only opening focuses it; navigation preserves the operator's current focus.
|
|
61
|
+
closeRef.current?.focus({ preventScroll: true });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
closeLightboxDialog(dialog);
|
|
65
|
+
const trigger = triggerRef.current;
|
|
66
|
+
trigger?.focus?.({ preventScroll: true });
|
|
67
|
+
// Keyed on `open`, not `index`: navigating while open must not reopen the dialog (showModal on
|
|
68
|
+
// an open dialog throws) or recapture its trigger from inside the lightbox.
|
|
69
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
+
}, [input.open]);
|
|
71
|
+
function go(target) {
|
|
72
|
+
// Clamps rather than wrapping: staying on the last asset is the less surprising default for an
|
|
73
|
+
// operator navigating a specific set of uploads, not a slideshow.
|
|
74
|
+
if (!nav.navigable || target < 0 || target >= nav.count)
|
|
75
|
+
return;
|
|
76
|
+
// An arrow becomes disabled at its end, and a disabled focused button drops focus out of the
|
|
77
|
+
// dialog. Hand focus to its neighbour first; interior moves leave focus where the operator put it.
|
|
78
|
+
// The neighbour may itself be disabled until the caller re-renders, so the move is applied
|
|
79
|
+
// after the index changes.
|
|
80
|
+
const active = dialogRef.current?.ownerDocument.activeElement;
|
|
81
|
+
if (target === 0 && active === prevRef.current)
|
|
82
|
+
pendingFocusRef.current = nextRef;
|
|
83
|
+
if (target === nav.count - 1 && active === nextRef.current)
|
|
84
|
+
pendingFocusRef.current = prevRef;
|
|
85
|
+
input.onNavigate?.(target);
|
|
86
|
+
}
|
|
87
|
+
// Listens on the document rather than the dialog: when the focused media is swapped out (an
|
|
88
|
+
// image that failed over to a video) focus drops to the body, and arrow keys must keep working.
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const dialog = dialogRef.current;
|
|
91
|
+
if (!dialog || !input.open)
|
|
92
|
+
return;
|
|
93
|
+
const onKey = (event) => {
|
|
94
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight")
|
|
95
|
+
return;
|
|
96
|
+
// Known limitation: a focused `<video controls>` also seeks on these keys natively, so the
|
|
97
|
+
// press both seeks and navigates. Callers know their media; the shell does not.
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
go(nav.index + (event.key === "ArrowRight" ? 1 : -1));
|
|
100
|
+
};
|
|
101
|
+
dialog.ownerDocument.addEventListener("keydown", onKey);
|
|
102
|
+
return () => dialog.ownerDocument.removeEventListener("keydown", onKey);
|
|
103
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
104
|
+
}, [input.open, nav.index, nav.count, input.onNavigate]);
|
|
105
|
+
return {
|
|
106
|
+
dialogRef,
|
|
107
|
+
closeRef,
|
|
108
|
+
prevRef,
|
|
109
|
+
nextRef,
|
|
110
|
+
navigable: nav.navigable,
|
|
111
|
+
hasPrev: nav.hasPrev,
|
|
112
|
+
hasNext: nav.hasNext,
|
|
113
|
+
counter: nav.count > 1 ? `${nav.index + 1} / ${nav.count}` : "",
|
|
114
|
+
previous: () => go(nav.index - 1),
|
|
115
|
+
next: () => go(nav.index + 1),
|
|
116
|
+
// Always prevented so the `open` prop stays the single source of truth; Escape routes through
|
|
117
|
+
// onClose and the caller's state change is what actually closes the dialog.
|
|
118
|
+
handleCancel: (event) => {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
input.onClose();
|
|
121
|
+
},
|
|
122
|
+
handleClick: (event) => {
|
|
123
|
+
if (isDismissTarget(event.target))
|
|
124
|
+
input.onClose();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=Lightbox.hooks.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from "react";
|
|
3
|
+
import { Icon } from "../../../../react/components/Icon.js";
|
|
4
|
+
import { LIGHTBOX_DISMISS_ATTR, useLightbox } from "./Lightbox.hooks.js";
|
|
5
|
+
const DEFAULT_LABELS = { close: "Close preview", previous: "Previous", next: "Next" };
|
|
6
|
+
// Native dialog supplies the focus trap, Escape and top-layer stacking, so there is no hand-built
|
|
7
|
+
// overlay or z-index policy. The dialog itself fills the viewport as the dark gray stage (rather
|
|
8
|
+
// than a card over a dim backdrop) so the media can take nearly all of the screen.
|
|
9
|
+
/** Full-viewport media lightbox: title and counter top-left, close top-right, the media as large
|
|
10
|
+
* as fits, caller actions bottom-left and the previous/next pair together bottom-right. */
|
|
11
|
+
export function Lightbox(props) {
|
|
12
|
+
const { open, title, children, actions, className, partAttrs = {}, useLightboxHook = useLightbox } = props;
|
|
13
|
+
const labels = { ...DEFAULT_LABELS, ...props.labels };
|
|
14
|
+
const titleId = useId();
|
|
15
|
+
const vm = useLightboxHook({ open, onClose: props.onClose, index: props.index, count: props.count, onNavigate: props.onNavigate });
|
|
16
|
+
return (_jsx("dialog", { ref: vm.dialogRef, className: className ? `jini-lightbox ${className}` : "jini-lightbox", "aria-labelledby": open ? titleId : undefined, onCancel: vm.handleCancel, onClick: vm.handleClick, ...partAttrs.dialog, children: open ? (_jsxs(_Fragment, { children: [_jsxs("header", { className: "jini-lightbox-header", children: [_jsx("h2", { id: titleId, className: "jini-lightbox-title", title: title, children: title }), vm.counter ? _jsx("span", { className: "jini-lightbox-counter", children: vm.counter }) : null, _jsx("button", { type: "button", ref: vm.closeRef, className: "jini-lightbox-button jini-lightbox-close", "aria-label": labels.close, onClick: props.onClose, ...partAttrs.close, children: _jsx(Icon, { name: "close", size: 16, strokeWidth: 1.8 }) })] }), _jsx("div", { className: "jini-lightbox-stage", [LIGHTBOX_DISMISS_ATTR]: "", children: children }), actions || vm.navigable ? (_jsxs("footer", { className: "jini-lightbox-footer", children: [_jsx("div", { className: "jini-lightbox-actions", children: actions }), vm.navigable ? (_jsxs("div", { className: "jini-lightbox-nav", children: [_jsx("button", { type: "button", ref: vm.prevRef, className: "jini-lightbox-button", "aria-label": labels.previous, disabled: !vm.hasPrev, onClick: vm.previous, ...partAttrs.previous, children: _jsx(Icon, { name: "chevron-left", size: 18, strokeWidth: 1.8 }) }), _jsx("button", { type: "button", ref: vm.nextRef, className: "jini-lightbox-button", "aria-label": labels.next, disabled: !vm.hasNext, onClick: vm.next, ...partAttrs.next, children: _jsx(Icon, { name: "chevron-right", size: 18, strokeWidth: 1.8 }) })] })) : null] })) : null] })) : null }));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=Lightbox.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface MiddleTruncateProps {
|
|
2
|
+
/** The full text, e.g. a file name. */
|
|
3
|
+
text: string;
|
|
4
|
+
/** Stem characters kept beside the extension; see `splitMiddleTruncation`. */
|
|
5
|
+
keepChars?: number | undefined;
|
|
6
|
+
className?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* One line of text that ellipsizes in the middle, keeping the end (a file's extension) visible.
|
|
10
|
+
* The two halves are separate boxes, so a screen reader may read them as two pieces: name the
|
|
11
|
+
* enclosing control with the full text (`aria-label`) rather than relying on this content.
|
|
12
|
+
*/
|
|
13
|
+
export declare function MiddleTruncate({ text, keepChars, className }: MiddleTruncateProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=MiddleTruncate.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { splitMiddleTruncation } from "./MiddleTruncate.rules.js";
|
|
3
|
+
/**
|
|
4
|
+
* One line of text that ellipsizes in the middle, keeping the end (a file's extension) visible.
|
|
5
|
+
* The two halves are separate boxes, so a screen reader may read them as two pieces: name the
|
|
6
|
+
* enclosing control with the full text (`aria-label`) rather than relying on this content.
|
|
7
|
+
*/
|
|
8
|
+
export function MiddleTruncate({ text, keepChars, className }) {
|
|
9
|
+
const { head, tail } = splitMiddleTruncation({ text }, keepChars === undefined ? {} : { keepChars });
|
|
10
|
+
return (_jsxs("span", { className: className ? `jini-middle-truncate ${className}` : "jini-middle-truncate", children: [_jsx("span", { className: "jini-middle-truncate__head", children: head }), tail ? _jsx("span", { className: "jini-middle-truncate__tail", children: tail }) : null] }));
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=MiddleTruncate.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface MiddleTruncationParts {
|
|
2
|
+
/** The part that shrinks with an ellipsis when space runs out. */
|
|
3
|
+
head: string;
|
|
4
|
+
/** The part that always stays visible: the extension plus a few characters before it. */
|
|
5
|
+
tail: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Splits a name so CSS can cut it in the MIDDLE: `head` ellipsizes, `tail` never shrinks, so
|
|
9
|
+
* `woodnest-cabin-booking.mp4` narrows to `woodnest-cabin-bo…ing.mp4` and the extension stays
|
|
10
|
+
* readable. CSS does the measuring, so the same split works at any card width.
|
|
11
|
+
* @param required.text The full name.
|
|
12
|
+
* @param optional.keepChars Characters of the stem kept beside the extension.
|
|
13
|
+
* @complexity O(n) in the name's length.
|
|
14
|
+
*/
|
|
15
|
+
export declare function splitMiddleTruncation({ text }: {
|
|
16
|
+
text: string;
|
|
17
|
+
}, { keepChars }?: {
|
|
18
|
+
keepChars?: number;
|
|
19
|
+
}): MiddleTruncationParts;
|
|
20
|
+
//# sourceMappingURL=MiddleTruncate.rules.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Longest trailing ".ext" still treated as an extension; a longer dotted tail is just text. */
|
|
2
|
+
const MAX_EXTENSION_CHARS = 10;
|
|
3
|
+
/**
|
|
4
|
+
* Splits a name so CSS can cut it in the MIDDLE: `head` ellipsizes, `tail` never shrinks, so
|
|
5
|
+
* `woodnest-cabin-booking.mp4` narrows to `woodnest-cabin-bo…ing.mp4` and the extension stays
|
|
6
|
+
* readable. CSS does the measuring, so the same split works at any card width.
|
|
7
|
+
* @param required.text The full name.
|
|
8
|
+
* @param optional.keepChars Characters of the stem kept beside the extension.
|
|
9
|
+
* @complexity O(n) in the name's length.
|
|
10
|
+
*/
|
|
11
|
+
export function splitMiddleTruncation({ text }, { keepChars = 3 } = {}) {
|
|
12
|
+
const dot = text.lastIndexOf(".");
|
|
13
|
+
// A leading dot (".env") is a hidden-file name, not an extension.
|
|
14
|
+
const extensionChars = dot > 0 && text.length - dot <= MAX_EXTENSION_CHARS ? text.length - dot : 0;
|
|
15
|
+
const tailChars = extensionChars + keepChars;
|
|
16
|
+
// No extension to protect, or a name too short to cut: one piece that ellipsizes at its end.
|
|
17
|
+
if (extensionChars === 0 || text.length <= tailChars + 1)
|
|
18
|
+
return { head: text, tail: "" };
|
|
19
|
+
return { head: text.slice(0, text.length - tailChars), tail: text.slice(text.length - tailChars) };
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=MiddleTruncate.rules.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useScrollJump } from "./ScrollJump.hooks.js";
|
|
2
|
+
export interface ScrollJumpButtonsProps {
|
|
3
|
+
/** The scrolling element the buttons control (an admin shell's `<main>`); `null` renders nothing. */
|
|
4
|
+
container: HTMLElement | null;
|
|
5
|
+
/** Accessible names, already translated by the host. */
|
|
6
|
+
labels?: {
|
|
7
|
+
toTop: string;
|
|
8
|
+
toBottom: string;
|
|
9
|
+
} | undefined;
|
|
10
|
+
useJump?: typeof useScrollJump | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A quiet ↑/↓ pair for long pages. Hidden on short pages; ↑ hides at the top and ↓ at the bottom.
|
|
14
|
+
* Position it with the host's CSS (`--jini-scroll-jump-right`/`-bottom`/`-direction`) so it
|
|
15
|
+
* clears that host's own floating controls.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ScrollJumpButtons({ container, labels, useJump }: ScrollJumpButtonsProps): import("react").JSX.Element | null;
|
|
18
|
+
//# sourceMappingURL=ScrollJump.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ScrollJumpVisibility } from "./ScrollJump.rules.js";
|
|
2
|
+
export interface ScrollJumpController extends ScrollJumpVisibility {
|
|
3
|
+
jump: (edge: "top" | "bottom") => void;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Tracks whether `container` is tall enough for jump buttons and which edges it is away from.
|
|
7
|
+
* Scroll events alone miss content that loads or grows without scrolling (a media grid filling in),
|
|
8
|
+
* so a ResizeObserver watches the container and each direct child, and a MutationObserver
|
|
9
|
+
* re-observes when a route swaps the children out.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useScrollJump({ container }: {
|
|
12
|
+
container: HTMLElement | null;
|
|
13
|
+
}, { reducedMotion }?: {
|
|
14
|
+
reducedMotion?: () => boolean;
|
|
15
|
+
}): ScrollJumpController;
|
|
16
|
+
//# sourceMappingURL=ScrollJump.hooks.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { scrollJumpBehavior, scrollJumpTarget, scrollJumpVisibility } from "./ScrollJump.rules.js";
|
|
3
|
+
const HIDDEN = { showTop: false, showBottom: false };
|
|
4
|
+
const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
5
|
+
const prefersReducedMotion = () => typeof window !== "undefined" && (window.matchMedia?.(REDUCED_MOTION_QUERY).matches ?? false);
|
|
6
|
+
/**
|
|
7
|
+
* Tracks whether `container` is tall enough for jump buttons and which edges it is away from.
|
|
8
|
+
* Scroll events alone miss content that loads or grows without scrolling (a media grid filling in),
|
|
9
|
+
* so a ResizeObserver watches the container and each direct child, and a MutationObserver
|
|
10
|
+
* re-observes when a route swaps the children out.
|
|
11
|
+
*/
|
|
12
|
+
export function useScrollJump({ container }, { reducedMotion = prefersReducedMotion } = {}) {
|
|
13
|
+
const [visibility, setVisibility] = useState(HIDDEN);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!container)
|
|
16
|
+
return;
|
|
17
|
+
// Same-value updates keep the previous object so a scroll that changes nothing does not re-render.
|
|
18
|
+
const update = () => setVisibility((previous) => {
|
|
19
|
+
const next = scrollJumpVisibility(container);
|
|
20
|
+
return next.showTop === previous.showTop && next.showBottom === previous.showBottom ? previous : next;
|
|
21
|
+
});
|
|
22
|
+
update();
|
|
23
|
+
container.addEventListener("scroll", update, { passive: true });
|
|
24
|
+
const resize = typeof ResizeObserver === "function" ? new ResizeObserver(update) : null;
|
|
25
|
+
const observeChildren = () => {
|
|
26
|
+
resize?.disconnect();
|
|
27
|
+
resize?.observe(container);
|
|
28
|
+
for (const child of Array.from(container.children))
|
|
29
|
+
resize?.observe(child);
|
|
30
|
+
};
|
|
31
|
+
observeChildren();
|
|
32
|
+
const mutation = typeof MutationObserver === "function" ? new MutationObserver(() => { observeChildren(); update(); }) : null;
|
|
33
|
+
mutation?.observe(container, { childList: true });
|
|
34
|
+
return () => {
|
|
35
|
+
container.removeEventListener("scroll", update);
|
|
36
|
+
resize?.disconnect();
|
|
37
|
+
mutation?.disconnect();
|
|
38
|
+
};
|
|
39
|
+
}, [container]);
|
|
40
|
+
const jump = useCallback((edge) => {
|
|
41
|
+
container?.scrollTo({ top: scrollJumpTarget({ edge, scrollHeight: container.scrollHeight }), behavior: scrollJumpBehavior({ reducedMotion: reducedMotion() }) });
|
|
42
|
+
}, [container, reducedMotion]);
|
|
43
|
+
return { ...(container ? visibility : HIDDEN), jump };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=ScrollJump.hooks.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useScrollJump } from "./ScrollJump.hooks.js";
|
|
3
|
+
const DEFAULT_LABELS = { toTop: "Scroll to top", toBottom: "Scroll to bottom" };
|
|
4
|
+
/** An edge button that does not apply keeps its slot (CSS `visibility: hidden`, which also drops it
|
|
5
|
+
* from the tab order), so the other button never slides under the pointer that just clicked. */
|
|
6
|
+
const offProps = (show) => (show ? {} : { "data-off": "" });
|
|
7
|
+
/**
|
|
8
|
+
* A quiet ↑/↓ pair for long pages. Hidden on short pages; ↑ hides at the top and ↓ at the bottom.
|
|
9
|
+
* Position it with the host's CSS (`--jini-scroll-jump-right`/`-bottom`/`-direction`) so it
|
|
10
|
+
* clears that host's own floating controls.
|
|
11
|
+
*/
|
|
12
|
+
export function ScrollJumpButtons({ container, labels = DEFAULT_LABELS, useJump = useScrollJump }) {
|
|
13
|
+
const { showTop, showBottom, jump } = useJump({ container });
|
|
14
|
+
if (!showTop && !showBottom)
|
|
15
|
+
return null;
|
|
16
|
+
return (_jsxs("div", { className: "jini-scroll-jump", children: [_jsx("button", { type: "button", className: "jini-scroll-jump__btn", ...offProps(showTop), "aria-label": labels.toTop, title: labels.toTop, onClick: () => jump("top"), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m18 15-6-6-6 6" }) }) }), _jsx("button", { type: "button", className: "jini-scroll-jump__btn", ...offProps(showBottom), "aria-label": labels.toBottom, title: labels.toBottom, onClick: () => jump("bottom"), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m6 9 6 6 6-6" }) }) })] }));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=ScrollJump.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A page must be at least this many viewports tall before the jump buttons are worth their space. */
|
|
2
|
+
export declare const SCROLL_JUMP_MIN_VIEWPORTS = 2;
|
|
3
|
+
/** Within this many px of an edge counts as AT that edge (fractional scroll positions never hit 0 exactly). */
|
|
4
|
+
export declare const SCROLL_JUMP_EDGE_SLACK_PX = 8;
|
|
5
|
+
export interface ScrollMetrics {
|
|
6
|
+
scrollTop: number;
|
|
7
|
+
scrollHeight: number;
|
|
8
|
+
clientHeight: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ScrollJumpVisibility {
|
|
11
|
+
showTop: boolean;
|
|
12
|
+
showBottom: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Which jump buttons a scroll container shows: none on a short page, ↑ only once scrolled away from
|
|
16
|
+
* the top, ↓ only while there is more below.
|
|
17
|
+
* @complexity O(1).
|
|
18
|
+
*/
|
|
19
|
+
export declare function scrollJumpVisibility({ scrollTop, scrollHeight, clientHeight }: ScrollMetrics, { minViewports, edgeSlackPx }?: {
|
|
20
|
+
minViewports?: number;
|
|
21
|
+
edgeSlackPx?: number;
|
|
22
|
+
}): ScrollJumpVisibility;
|
|
23
|
+
/** Reduced-motion users get an instant jump instead of a long animated scroll. */
|
|
24
|
+
export declare function scrollJumpBehavior({ reducedMotion }: {
|
|
25
|
+
reducedMotion: boolean;
|
|
26
|
+
}): ScrollBehavior;
|
|
27
|
+
/** The `scrollTo` target for an edge: 0 for the top, the full content height for the bottom. */
|
|
28
|
+
export declare function scrollJumpTarget({ edge, scrollHeight }: {
|
|
29
|
+
edge: "top" | "bottom";
|
|
30
|
+
scrollHeight: number;
|
|
31
|
+
}): number;
|
|
32
|
+
//# sourceMappingURL=ScrollJump.rules.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A page must be at least this many viewports tall before the jump buttons are worth their space. */
|
|
2
|
+
export const SCROLL_JUMP_MIN_VIEWPORTS = 2;
|
|
3
|
+
/** Within this many px of an edge counts as AT that edge (fractional scroll positions never hit 0 exactly). */
|
|
4
|
+
export const SCROLL_JUMP_EDGE_SLACK_PX = 8;
|
|
5
|
+
/**
|
|
6
|
+
* Which jump buttons a scroll container shows: none on a short page, ↑ only once scrolled away from
|
|
7
|
+
* the top, ↓ only while there is more below.
|
|
8
|
+
* @complexity O(1).
|
|
9
|
+
*/
|
|
10
|
+
export function scrollJumpVisibility({ scrollTop, scrollHeight, clientHeight }, { minViewports = SCROLL_JUMP_MIN_VIEWPORTS, edgeSlackPx = SCROLL_JUMP_EDGE_SLACK_PX } = {}) {
|
|
11
|
+
if (clientHeight <= 0 || scrollHeight < clientHeight * minViewports)
|
|
12
|
+
return { showTop: false, showBottom: false };
|
|
13
|
+
return {
|
|
14
|
+
showTop: scrollTop > edgeSlackPx,
|
|
15
|
+
showBottom: scrollHeight - scrollTop - clientHeight > edgeSlackPx,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Reduced-motion users get an instant jump instead of a long animated scroll. */
|
|
19
|
+
export function scrollJumpBehavior({ reducedMotion }) {
|
|
20
|
+
return reducedMotion ? "auto" : "smooth";
|
|
21
|
+
}
|
|
22
|
+
/** The `scrollTo` target for an edge: 0 for the top, the full content height for the bottom. */
|
|
23
|
+
export function scrollJumpTarget({ edge, scrollHeight }) {
|
|
24
|
+
return edge === "top" ? 0 : scrollHeight;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=ScrollJump.rules.js.map
|
|
@@ -13,6 +13,13 @@ export function useSuggestInput({ value, suggestions }, { limit } = {}) {
|
|
|
13
13
|
const matches = matchSuggestions({ typed: value, suggestions }, { limit });
|
|
14
14
|
const visible = open && matches.length > 0;
|
|
15
15
|
const current = highlighted < matches.length ? highlighted : -1;
|
|
16
|
+
const close = () => { setOpen(false); setHighlighted(-1); };
|
|
17
|
+
const move = (step) => {
|
|
18
|
+
setOpen(true);
|
|
19
|
+
const next = nextHighlight({ current, step, count: matches.length });
|
|
20
|
+
setHighlighted(next);
|
|
21
|
+
setScrollTarget({ index: next });
|
|
22
|
+
};
|
|
16
23
|
return {
|
|
17
24
|
open: visible,
|
|
18
25
|
matches,
|
|
@@ -20,33 +27,41 @@ export function useSuggestInput({ value, suggestions }, { limit } = {}) {
|
|
|
20
27
|
setHighlighted,
|
|
21
28
|
scrollTarget,
|
|
22
29
|
openList: () => setOpen(true),
|
|
23
|
-
closeList:
|
|
30
|
+
closeList: close,
|
|
24
31
|
handleKey: (key) => {
|
|
25
|
-
|
|
32
|
+
const step = arrowStep(key);
|
|
33
|
+
if (step !== undefined) {
|
|
26
34
|
if (matches.length === 0)
|
|
27
35
|
return { consumed: false };
|
|
28
|
-
|
|
29
|
-
const step = key === 'ArrowDown' ? 1 : -1;
|
|
30
|
-
const next = (current + step + matches.length + (current === -1 && step === -1 ? 1 : 0)) % matches.length;
|
|
31
|
-
setHighlighted(next);
|
|
32
|
-
setScrollTarget({ index: next });
|
|
36
|
+
move(step);
|
|
33
37
|
return { consumed: true };
|
|
34
38
|
}
|
|
35
39
|
if (key === 'Escape' && visible) {
|
|
36
|
-
|
|
37
|
-
setHighlighted(-1);
|
|
40
|
+
close();
|
|
38
41
|
return { consumed: true };
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
setHighlighted(-1);
|
|
44
|
-
return { consumed: true, picked };
|
|
43
|
+
if (key === 'Enter' && visible && current >= 0) {
|
|
44
|
+
close();
|
|
45
|
+
return { consumed: true, picked: matches[current] };
|
|
45
46
|
}
|
|
46
47
|
return { consumed: false };
|
|
47
48
|
},
|
|
48
49
|
};
|
|
49
50
|
}
|
|
51
|
+
/** +1 for ArrowDown, -1 for ArrowUp, undefined for every other key. */
|
|
52
|
+
function arrowStep(key) {
|
|
53
|
+
if (key === 'ArrowDown')
|
|
54
|
+
return 1;
|
|
55
|
+
if (key === 'ArrowUp')
|
|
56
|
+
return -1;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/** The row an arrow key moves to, wrapping at either end; from no highlight, ArrowUp lands on the last row. */
|
|
60
|
+
function nextHighlight({ current, step, count }) {
|
|
61
|
+
if (current === -1)
|
|
62
|
+
return step === 1 ? 0 : count - 1;
|
|
63
|
+
return (current + step + count) % count;
|
|
64
|
+
}
|
|
50
65
|
/** Default scroller: `block: 'nearest'` moves the list only as far as needed. Optional call because
|
|
51
66
|
* non-browser DOMs (jsdom in host test suites) don't implement `scrollIntoView`. */
|
|
52
67
|
export function scrollOptionIntoView(option) {
|
|
@@ -14,8 +14,10 @@ export type { SeeMoreProps, SeeMoreView } from './components/SeeMore/SeeMore.js'
|
|
|
14
14
|
export { useSeeMoreClamp } from './components/SeeMore/SeeMore.hooks.js';
|
|
15
15
|
export { ImagePreviewModal } from './components/ImagePreviewModal.js';
|
|
16
16
|
export type { ImagePreviewModalProps } from './components/ImagePreviewModal.js';
|
|
17
|
-
export {
|
|
18
|
-
export type {
|
|
17
|
+
export { Lightbox } from './components/Lightbox/Lightbox.js';
|
|
18
|
+
export type { LightboxProps } from './components/Lightbox/Lightbox.js';
|
|
19
|
+
export { useLightbox, openLightboxDialog, closeLightboxDialog } from './components/Lightbox/Lightbox.hooks.js';
|
|
20
|
+
export type { LightboxController, LightboxHookInput } from './components/Lightbox/Lightbox.hooks.js';
|
|
19
21
|
export { ComingSoonPanel } from './components/ComingSoonPanel.js';
|
|
20
22
|
export type { ComingSoonPanelProps } from './components/ComingSoonPanel.js';
|
|
21
23
|
export { ComingSoonNotice } from './components/ComingSoonNotice.js';
|
|
@@ -26,4 +28,14 @@ export { useSuggestInput, matchSuggestions } from './components/SuggestInput/Sug
|
|
|
26
28
|
export type { SuggestInputController } from './components/SuggestInput/SuggestInput.hooks.js';
|
|
27
29
|
export { useDebouncedHtmlPreview } from './components/useDebouncedHtmlPreview.hooks.js';
|
|
28
30
|
export type { HtmlPreviewPort, HtmlPreviewState } from './components/useDebouncedHtmlPreview.hooks.js';
|
|
31
|
+
export { MiddleTruncate } from './components/MiddleTruncate.js';
|
|
32
|
+
export type { MiddleTruncateProps } from './components/MiddleTruncate.js';
|
|
33
|
+
export { splitMiddleTruncation } from './components/MiddleTruncate.rules.js';
|
|
34
|
+
export type { MiddleTruncationParts } from './components/MiddleTruncate.rules.js';
|
|
35
|
+
export { ScrollJumpButtons } from './components/ScrollJump/ScrollJump.js';
|
|
36
|
+
export type { ScrollJumpButtonsProps } from './components/ScrollJump/ScrollJump.js';
|
|
37
|
+
export { useScrollJump } from './components/ScrollJump/ScrollJump.hooks.js';
|
|
38
|
+
export type { ScrollJumpController } from './components/ScrollJump/ScrollJump.hooks.js';
|
|
39
|
+
export { scrollJumpVisibility, scrollJumpBehavior, scrollJumpTarget } from './components/ScrollJump/ScrollJump.rules.js';
|
|
40
|
+
export type { ScrollJumpVisibility, ScrollMetrics } from './components/ScrollJump/ScrollJump.rules.js';
|
|
29
41
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -7,10 +7,16 @@ export { placeInfoTipBubble, infoTipBubbleStyle } from './components/InfoTip.rul
|
|
|
7
7
|
export { SeeMore, resolveSeeMoreView } from './components/SeeMore/SeeMore.js';
|
|
8
8
|
export { useSeeMoreClamp } from './components/SeeMore/SeeMore.hooks.js';
|
|
9
9
|
export { ImagePreviewModal } from './components/ImagePreviewModal.js';
|
|
10
|
-
export {
|
|
10
|
+
export { Lightbox } from './components/Lightbox/Lightbox.js';
|
|
11
|
+
export { useLightbox, openLightboxDialog, closeLightboxDialog } from './components/Lightbox/Lightbox.hooks.js';
|
|
11
12
|
export { ComingSoonPanel } from './components/ComingSoonPanel.js';
|
|
12
13
|
export { ComingSoonNotice } from './components/ComingSoonNotice.js';
|
|
13
14
|
export { SuggestInput } from './components/SuggestInput/SuggestInput.js';
|
|
14
15
|
export { useSuggestInput, matchSuggestions } from './components/SuggestInput/SuggestInput.hooks.js';
|
|
15
16
|
export { useDebouncedHtmlPreview } from './components/useDebouncedHtmlPreview.hooks.js';
|
|
17
|
+
export { MiddleTruncate } from './components/MiddleTruncate.js';
|
|
18
|
+
export { splitMiddleTruncation } from './components/MiddleTruncate.rules.js';
|
|
19
|
+
export { ScrollJumpButtons } from './components/ScrollJump/ScrollJump.js';
|
|
20
|
+
export { useScrollJump } from './components/ScrollJump/ScrollJump.hooks.js';
|
|
21
|
+
export { scrollJumpVisibility, scrollJumpBehavior, scrollJumpTarget } from './components/ScrollJump/ScrollJump.rules.js';
|
|
16
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -3,9 +3,15 @@ import { useCallback } from 'react';
|
|
|
3
3
|
import { useT } from '../../../i18n/index.js';
|
|
4
4
|
import { useInView } from '../../../../react/hooks/useInView.js';
|
|
5
5
|
import { resolveCheckboxClickAction, resolvePreviewClickAction } from '../../rules.js';
|
|
6
|
+
function renderCardActions({ asset, renderCardExtra, onDeleteAsset, t }, _options = {}) {
|
|
7
|
+
if (!renderCardExtra && !onDeleteAsset)
|
|
8
|
+
return null;
|
|
9
|
+
return (_jsxs("div", { className: "asset-grid-card-actions", children: [renderCardExtra ? renderCardExtra(asset) : _jsx("span", {}), onDeleteAsset ? (_jsx("button", { type: "button", className: "asset-grid-card-delete-btn", onClick: () => onDeleteAsset(asset.id), children: t('Remove') })) : null] }));
|
|
10
|
+
}
|
|
6
11
|
export function AssetCard({ asset, index, selected, title, subtitle, kindLabel, sourceLabel, renderThumbnail, renderThumbnailPlaceholder, onToggle, onRange, onPreview, onDeleteAsset, renderCardExtra, }) {
|
|
7
12
|
const t = useT();
|
|
8
13
|
const { ref, inView } = useInView({ once: true, rootMargin: '300px' });
|
|
14
|
+
const selectionValue = selected ? 'true' : 'false';
|
|
9
15
|
const handlePreviewClick = useCallback((e) => {
|
|
10
16
|
const action = resolvePreviewClickAction(e);
|
|
11
17
|
if (action === 'toggle')
|
|
@@ -22,6 +28,6 @@ export function AssetCard({ asset, index, selected, title, subtitle, kindLabel,
|
|
|
22
28
|
else
|
|
23
29
|
onToggle(asset.id, index);
|
|
24
30
|
}, [asset.id, index, onToggle, onRange]);
|
|
25
|
-
return (_jsxs("figure", { className: "asset-grid-card", "data-asset-grid-id": asset.id, "data-selected":
|
|
31
|
+
return (_jsxs("figure", { className: "asset-grid-card", "data-asset-grid-id": asset.id, "data-selected": selectionValue, children: [_jsxs("div", { className: "asset-grid-card-thumb", children: [_jsx("div", { ref: ref, className: "asset-grid-card-thumb-lazy", children: inView ? renderThumbnail(asset) : (renderThumbnailPlaceholder?.(asset) ?? null) }), _jsx("button", { type: "button", className: "asset-grid-card-preview-btn", onClick: handlePreviewClick, "aria-label": t('Preview {title}', { title }), children: _jsx("span", { className: "asset-grid-card-preview-overlay", "aria-hidden": true, children: _jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("circle", { cx: "11", cy: "11", r: "7" }), _jsx("path", { d: "m21 21-4.3-4.3" })] }) }) }), _jsx("button", { type: "button", className: "asset-grid-card-select-check", "data-checked": selectionValue, "aria-pressed": selected, "aria-label": selected ? t('Deselect asset') : t('Select asset'), onClick: handleCheckboxClick, children: selected ? (_jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: _jsx("path", { d: "M20 6 9 17l-5-5" }) })) : null }), sourceLabel ? (_jsx("span", { className: "asset-grid-card-badge", "data-badge": "source", children: sourceLabel })) : null, kindLabel ? (_jsx("span", { className: "asset-grid-card-badge", "data-badge": "kind", children: kindLabel })) : null] }), _jsxs("figcaption", { className: "asset-grid-card-meta", children: [_jsx("button", { type: "button", className: "asset-grid-card-title", title: title, onClick: () => onPreview(asset.id), children: title }), subtitle ? _jsx("span", { className: "asset-grid-card-subtitle", children: subtitle }) : null] }), renderCardActions({ asset, renderCardExtra, onDeleteAsset, t })] }));
|
|
26
32
|
}
|
|
27
33
|
//# sourceMappingURL=AssetCard.js.map
|
|
@@ -41,5 +41,5 @@ export interface AssetGridProps<TAsset extends AssetGridItem> {
|
|
|
41
41
|
* pattern behind a photo library / DAM. See `packages/ui/source-map.md` for
|
|
42
42
|
* the full retained-behavior manifest against the original `LibrarySection.tsx`.
|
|
43
43
|
*/
|
|
44
|
-
export declare function AssetGrid<TAsset extends AssetGridItem>({ active, selectors, dependencies, kindFacets, sourceFacets, renderThumbnail, renderThumbnailPlaceholder, renderCardExtra, renderBulkActions, onDeleteAsset, onDeleteSelected, onPreview, isPreviewOpen, toolbarActions, emptyState, searchPlaceholder, searchDebounceMs, liveUpdateCoalesceMs, initialViewMode, useWiredAssetGridData: useWiredAssetGridDataHook, useAssetGridSelection: useAssetGridSelectionHook, }: AssetGridProps<TAsset>): import("react").JSX.Element;
|
|
44
|
+
export declare function AssetGrid<TAsset extends AssetGridItem>({ active, selectors, dependencies, kindFacets: configuredKindFacets, sourceFacets: configuredSourceFacets, renderThumbnail, renderThumbnailPlaceholder, renderCardExtra, renderBulkActions, onDeleteAsset, onDeleteSelected, onPreview, isPreviewOpen, toolbarActions, emptyState, searchPlaceholder, searchDebounceMs, liveUpdateCoalesceMs, initialViewMode, useWiredAssetGridData: useWiredAssetGridDataHook, useAssetGridSelection: useAssetGridSelectionHook, }: AssetGridProps<TAsset>): import("react").JSX.Element;
|
|
45
45
|
//# sourceMappingURL=AssetGrid.d.ts.map
|