@jini-ai/ui 0.3.13 → 0.4.0
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 +139 -0
- package/README.md +120 -26
- package/dist/browser/index.js +1 -1
- package/dist/browser/useDismissOnOutsideOrEscape.d.ts +10 -1
- package/dist/browser/useDismissOnOutsideOrEscape.js +7 -4
- package/dist/browser/useGlobalKeydown.d.ts +14 -2
- package/dist/browser/useGlobalKeydown.js +8 -17
- package/dist/features/a2ui/catalog-from-registry.d.ts +6 -3
- package/dist/features/a2ui/catalog-from-registry.js +1 -1
- package/dist/features/a2ui/index.d.ts +1 -1
- package/dist/features/a2ui/protocol.d.ts +1 -1
- package/dist/features/a2ui/renderer.js +23 -10
- package/dist/features/a2ui/use-a2ui-surface.d.ts +11 -6
- package/dist/features/a2ui/use-a2ui-surface.js +40 -10
- package/dist/features/admin-widgets/components/ComingSoonNotice.d.ts +11 -0
- package/dist/features/admin-widgets/components/ComingSoonNotice.js +7 -0
- package/dist/features/admin-widgets/components/ComingSoonPanel.d.ts +10 -0
- package/dist/features/admin-widgets/components/ComingSoonPanel.js +10 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +16 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +13 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +46 -0
- package/dist/features/admin-widgets/components/ImagePreviewModal.js +13 -0
- package/dist/features/admin-widgets/components/InfoTip.d.ts +18 -0
- package/dist/features/admin-widgets/components/InfoTip.hooks.d.ts +16 -0
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +45 -0
- package/dist/features/admin-widgets/components/InfoTip.js +12 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.d.ts +41 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.hooks.d.ts +14 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.hooks.js +57 -0
- package/dist/features/admin-widgets/components/SeeMore/SeeMore.js +24 -0
- package/dist/features/admin-widgets/components/Select/Select.d.ts +39 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +105 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.js +359 -0
- package/dist/features/admin-widgets/components/Select/Select.js +46 -0
- package/dist/features/admin-widgets/index.d.ts +20 -0
- package/dist/features/admin-widgets/index.js +11 -0
- package/dist/features/admin-widgets/styles/admin-widgets.css +2 -0
- package/dist/features/appearance/constants.d.ts +2 -2
- package/dist/features/appearance/constants.js +2 -1
- package/dist/features/appearance/react/components/AppearanceTab.js +5 -5
- package/dist/features/appearance/rules.d.ts +10 -4
- package/dist/features/appearance/rules.js +5 -5
- package/dist/features/appearance/types.d.ts +1 -1
- package/dist/features/appearance/types.js +1 -1
- package/dist/features/asset-grid/react/components/AssetGrid.js +1 -1
- package/dist/features/asset-grid/react/components/DeleteConfirmDialog.js +1 -1
- package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +23 -21
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +1 -1
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +1 -1
- package/dist/features/asset-tree-browser/ports.d.ts +3 -1
- package/dist/features/asset-tree-browser/react/hooks/useAssetTreeCopyLocalPath.js +1 -1
- package/dist/features/browser-chrome/react/components/BrowserViewportControls.js +1 -1
- package/dist/features/browser-chrome/types.js +1 -1
- package/dist/features/command-palette/ports.js +1 -1
- package/dist/features/connectors/ConnectorsBrowser.js +2 -2
- package/dist/features/connectors/components/ConnectorCard.js +3 -3
- package/dist/features/connectors/components/ConnectorDetailDrawer.js +1 -1
- package/dist/features/connectors/components/ConnectorGrid.js +2 -2
- package/dist/features/connectors/components/ConnectorSearchBar.js +2 -2
- package/dist/features/connectors/components/ProviderTabBar.js +2 -2
- package/dist/features/connectors/constants.d.ts +1 -1
- package/dist/features/connectors/constants.js +1 -1
- package/dist/features/connectors/hooks/useConnectorAuthorization.js +1 -1
- package/dist/features/connectors/rules.d.ts +2 -2
- package/dist/features/connectors/rules.js +1 -1
- package/dist/features/connectors/types.d.ts +2 -2
- package/dist/features/connectors/types.js +1 -1
- package/dist/features/execution/react/components/ByokProviderForm.js +3 -3
- package/dist/features/execution/react/components/ExecutionTab.js +2 -2
- package/dist/features/execution/react/components/LocalCliAgentCard.js +3 -3
- package/dist/features/execution/react/components/LocalCliAgentList.js +2 -2
- package/dist/features/execution/react/components/ProviderChipGroup.js +2 -2
- package/dist/features/execution/react/components/SearchableModelSelect.js +1 -1
- package/dist/features/execution/rules.js +1 -1
- package/dist/features/file-dropzone/react/components/FileDropzone.js +5 -3
- package/dist/features/file-dropzone/react/hooks/useFileDropzone.d.ts +1 -1
- package/dist/features/file-dropzone/react/hooks/useFileDropzone.js +1 -1
- package/dist/features/file-dropzone/types.d.ts +1 -1
- package/dist/features/file-dropzone/types.js +1 -1
- package/dist/features/folder-path-drop/react/hooks/useFolderPathDropCapture.js +6 -4
- package/dist/features/html-editor/canvas-content-wrapper.js +1 -1
- package/dist/features/html-editor/canvas-embed-placeholders.js +7 -7
- package/dist/features/html-editor/canvas-style.d.ts +4 -0
- package/dist/features/html-editor/canvas-style.js +6 -21
- package/dist/features/html-viewer/constants.d.ts +1 -1
- package/dist/features/html-viewer/constants.js +1 -1
- package/dist/features/html-viewer/react/components/PresentMenu.js +1 -1
- package/dist/features/html-viewer/react/components/ZoomMenu.js +1 -1
- package/dist/features/html-viewer/types.d.ts +1 -1
- package/dist/features/html-viewer/types.js +1 -1
- package/dist/features/i18n/dictionaries/index.d.ts +2 -3
- package/dist/features/i18n/dictionaries/settings-dialog.en.d.ts +1 -1
- package/dist/features/i18n/dictionary-translator.d.ts +16 -0
- package/dist/features/i18n/dictionary-translator.js +14 -0
- package/dist/features/i18n/index.d.ts +3 -0
- package/dist/features/i18n/index.js +2 -0
- package/dist/features/i18n/locale.d.ts +1 -1
- package/dist/features/i18n/template-i18n.d.ts +26 -0
- package/dist/features/i18n/template-i18n.js +36 -0
- package/dist/features/iframe-pool/react/components/IframeKeepAliveProvider.d.ts +1 -1
- package/dist/features/iframe-pool/react/components/IframeKeepAliveProvider.js +1 -1
- package/dist/features/instructions/react/components/InstructionsTab.js +1 -1
- package/dist/features/integrations/react/components/ClientPicker.js +4 -4
- package/dist/features/integrations/react/components/CodexInstallToggleButton.js +1 -1
- package/dist/features/integrations/react/components/IntegrationsTab.js +2 -2
- package/dist/features/integrations/react/components/SnippetBlock.js +1 -1
- package/dist/features/integrations/types.d.ts +1 -1
- package/dist/features/integrations/types.js +1 -1
- package/dist/features/interactive-ui/index.js +13 -13
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/recharts/bar-chart.js +1 -1
- package/dist/features/interactive-ui/providers/recharts/line-chart.js +1 -1
- package/dist/features/interactive-ui/providers/recharts/pie-chart.js +2 -2
- package/dist/features/interactive-ui/providers/shadcn/lib/utils.d.ts +1 -1
- package/dist/features/interactive-ui/providers/shadcn/lib/utils.js +1 -1
- package/dist/features/interactive-ui/registry.d.ts +15 -4
- package/dist/features/interactive-ui/registry.js +5 -5
- package/dist/features/language/react/components/LanguageTab.js +2 -2
- package/dist/features/list-detail-panel/types.d.ts +1 -1
- package/dist/features/list-detail-panel/types.js +1 -1
- package/dist/features/mcp-ui/confirmation-store.js +2 -1
- package/dist/features/mcp-ui/surfaces/bridge.d.ts +2 -1
- package/dist/features/mcp-ui/surfaces/bridge.js +17 -7
- package/dist/features/mcp-ui/surfaces/confirmation.d.ts +20 -0
- package/dist/features/mcp-ui/surfaces/confirmation.js +38 -6
- package/dist/features/mcp-ui/surfaces/document.d.ts +7 -1
- package/dist/features/mcp-ui/surfaces/document.js +19 -1
- package/dist/features/mcp-ui/surfaces/text-input.js +4 -4
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +17 -20
- package/dist/features/mcp-ui/surfaces/tokens.js +33 -35
- package/dist/features/media-providers/index.d.ts +1 -1
- package/dist/features/media-providers/index.js +1 -1
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +4 -4
- package/dist/features/media-providers/rules.js +1 -1
- package/dist/features/memory/async-commit-guard.d.ts +1 -1
- package/dist/features/memory/constants.d.ts +1 -1
- package/dist/features/memory/constants.js +1 -1
- package/dist/features/memory/dependencies.d.ts +11 -23
- package/dist/features/memory/dependencies.js +116 -106
- package/dist/features/memory/index.d.ts +1 -1
- package/dist/features/memory/index.js +1 -1
- package/dist/features/memory/react/components/MemoryAdvancedModal.js +2 -2
- package/dist/features/memory/react/components/MemoryEntryCard.js +1 -1
- package/dist/features/memory/react/components/MemoryExtractionCard.js +1 -1
- package/dist/features/memory/react/components/MemoryHooksPanel.js +1 -1
- package/dist/features/memory/react/components/MemoryList.js +5 -5
- package/dist/features/memory/react/components/MemoryManualEditor.js +5 -5
- package/dist/features/memory/react/components/MemorySettingsPanel.js +1 -1
- package/dist/features/memory/react/hooks/useMemoryEntries.hooks.js +1 -1
- package/dist/features/memory/react/hooks/useMemoryNavigation.hooks.js +1 -1
- package/dist/features/memory/react/styles.js +1 -1
- package/dist/features/memory/rules.js +1 -1
- package/dist/features/memory/types.d.ts +1 -1
- package/dist/features/mention-autocomplete/react/hooks/useMentionAutocomplete.js +1 -1
- package/dist/features/notifications/react/components/NotificationsTab.js +7 -7
- package/dist/features/notifications/types.d.ts +1 -1
- package/dist/features/observability/boot-timing.js +2 -2
- package/dist/features/observability/stuck-run.js +1 -1
- package/dist/features/observability/white-screen.js +4 -4
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +35 -0
- package/dist/features/panel-kit/fetch-query/adapter.react.js +122 -0
- package/dist/features/panel-kit/fetch-query/cache.d.ts +123 -0
- package/dist/features/panel-kit/fetch-query/cache.js +231 -0
- package/dist/features/panel-kit/fetch-query/environment.browser.d.ts +4 -0
- package/dist/features/panel-kit/fetch-query/environment.browser.js +25 -0
- package/dist/features/panel-kit/fetch-query/index.d.ts +4 -0
- package/dist/features/panel-kit/fetch-query/index.js +2 -0
- package/dist/features/panel-kit/fetch-query/types.d.ts +91 -0
- package/dist/features/panel-kit/fetch-query/types.js +2 -0
- package/dist/features/panel-kit/helpers/format-timestamp.d.ts +18 -0
- package/dist/features/panel-kit/helpers/format-timestamp.js +20 -0
- package/dist/features/panel-kit/helpers/permissions.d.ts +11 -0
- package/dist/features/panel-kit/helpers/permissions.js +10 -0
- package/dist/features/panel-kit/helpers/resolve-active-tab-id.d.ts +13 -0
- package/dist/features/panel-kit/helpers/resolve-active-tab-id.js +13 -0
- package/dist/features/panel-kit/helpers/retry-unreachable.d.ts +20 -0
- package/dist/features/panel-kit/helpers/retry-unreachable.js +39 -0
- package/dist/features/panel-kit/hooks/use-async-action.hooks.d.ts +13 -0
- package/dist/features/panel-kit/hooks/use-async-action.hooks.js +27 -0
- package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.d.ts +18 -0
- package/dist/features/panel-kit/hooks/use-dirty-guard.hooks.js +33 -0
- package/dist/features/panel-kit/hooks/use-focus-trap.hooks.d.ts +8 -0
- package/dist/features/panel-kit/hooks/use-focus-trap.hooks.js +66 -0
- package/dist/features/panel-kit/hooks/use-serial-writes.hooks.d.ts +26 -0
- package/dist/features/panel-kit/hooks/use-serial-writes.hooks.js +41 -0
- package/dist/features/panel-kit/hooks/use-settlement-generation.hooks.d.ts +9 -0
- package/dist/features/panel-kit/hooks/use-settlement-generation.hooks.js +25 -0
- package/dist/features/panel-kit/index.d.ts +19 -0
- package/dist/features/panel-kit/index.js +13 -0
- package/dist/features/privacy/react/components/PrivacyTab.js +2 -2
- package/dist/features/privacy/types.d.ts +1 -1
- package/dist/features/privacy/types.js +1 -1
- package/dist/features/progress-card/reference-adapters.d.ts +1 -1
- package/dist/features/progress-card/rules.d.ts +1 -1
- package/dist/features/progress-card/rules.js +1 -1
- package/dist/features/progress-card/types.d.ts +1 -1
- package/dist/features/progress-card/types.js +1 -1
- package/dist/features/resource-dashboard/dependencies.d.ts +1 -1
- package/dist/features/resource-dashboard/ports.d.ts +1 -1
- package/dist/features/resource-dashboard/react/hooks/useResourceBoard.d.ts +1 -1
- package/dist/features/resource-dashboard/react/hooks/useResourceBoard.js +1 -1
- package/dist/features/resource-dashboard/rules.d.ts +1 -1
- package/dist/features/resource-dashboard/rules.js +2 -2
- package/dist/features/resource-dashboard/types.d.ts +4 -4
- package/dist/features/resource-dashboard/types.js +1 -1
- package/dist/features/schedule-picker/react/hooks/useRecurringSchedulePicker.js +1 -1
- package/dist/features/settings/dialog/styles/settings-dialog.css +62 -60
- package/dist/features/sketch-editor/constants.d.ts +1 -1
- package/dist/features/sketch-editor/constants.js +1 -1
- package/dist/features/sketch-editor/dom.d.ts +1 -1
- package/dist/features/sketch-editor/dom.js +1 -1
- package/dist/features/sketch-editor/react/hooks/useSketchDomEnhancements.d.ts +1 -1
- package/dist/features/sketch-editor/react/hooks/useSketchDomEnhancements.js +11 -9
- package/dist/features/sketch-editor/types.d.ts +1 -1
- package/dist/features/sketch-editor/types.js +1 -1
- package/dist/features/skills/react/components/SkillDraftForm.js +1 -1
- package/dist/features/skills/react/components/SkillRow.js +1 -1
- package/dist/features/skills/react/components/SkillsTab.js +2 -2
- package/dist/features/source-config-list/agent-handles.js +2 -2
- package/dist/features/source-config-list/react/hooks/useSourceConfigAddForm.d.ts +1 -1
- package/dist/features/source-config-list/react/hooks/useSourceConfigAddForm.js +1 -1
- package/dist/features/source-config-list/rules.d.ts +1 -1
- package/dist/features/source-config-list/rules.js +3 -3
- package/dist/features/source-config-list/types.d.ts +1 -1
- package/dist/features/source-config-list/types.js +1 -1
- package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +1 -1
- package/dist/features/tab-strip/index.d.ts +4 -0
- package/dist/features/tab-strip/index.js +2 -0
- package/dist/features/tab-strip/react/components/TabBar.d.ts +23 -0
- package/dist/features/tab-strip/react/components/TabBar.hooks.d.ts +25 -0
- package/dist/features/tab-strip/react/components/TabBar.hooks.js +57 -0
- package/dist/features/tab-strip/react/components/TabBar.js +18 -0
- package/dist/features/tab-strip/react/components/TabStrip.js +2 -1
- package/dist/features/tab-strip/react/components/TabStripFrame.d.ts +3 -0
- package/dist/features/tab-strip/react/components/TabStripFrame.js +7 -0
- package/dist/features/tabbed-dialog/react/hooks/useTabbedDialog.js +1 -1
- package/dist/features/tabbed-dialog/styles/tabbed-dialog.css +8 -141
- package/dist/features/tabbed-dialog/types.d.ts +1 -1
- package/dist/features/tabbed-dialog/types.js +1 -1
- package/dist/features/version-manager/ports.d.ts +1 -1
- package/dist/features/version-manager/react/components/VersionPromptPopover.js +5 -3
- package/dist/features/version-manager/react/components/VersionRestoreControl.js +1 -1
- package/dist/features/version-manager/types.d.ts +1 -1
- package/dist/features/version-manager/types.js +1 -1
- package/dist/features/viewer-shell/dependencies.js +1 -1
- package/dist/features/viewer-shell/ports.d.ts +1 -1
- package/dist/features/viewer-shell/react/components/ViewportSwitcher.js +1 -1
- package/dist/features/viewer-shell/react/hooks/useMarkdownScrollSync.d.ts +1 -1
- package/dist/features/viewer-shell/react/hooks/useMarkdownScrollSync.js +1 -1
- package/dist/features/viewer-shell/types.d.ts +1 -1
- package/dist/features/viewer-shell/types.js +1 -1
- package/dist/index.d.ts +420 -0
- package/dist/index.js +421 -1
- package/dist/react/components/ComponentKitPreview.js +1 -1
- package/dist/react/components/EditorIcon.d.ts +1 -1
- package/dist/react/components/EditorIcon.js +22 -22
- package/dist/react/components/ExportDiagnosticsButton.d.ts +8 -1
- package/dist/react/components/ExportDiagnosticsButton.js +6 -6
- package/dist/react/components/PaletteTweaks.js +5 -5
- package/dist/react/hooks/useBrandFonts.d.ts +19 -4
- package/dist/react/hooks/useBrandFonts.js +11 -7
- package/dist/react/hooks/useDebouncedValue.d.ts +12 -1
- package/dist/react/hooks/useDebouncedValue.js +9 -4
- package/dist/react/hooks/useStableHandler.d.ts +3 -1
- package/dist/react/hooks/useStableHandler.js +1 -1
- package/dist/renderers/annotation-canvas/drawing.d.ts +4 -4
- package/dist/renderers/annotation-canvas/drawing.js +28 -15
- package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +2 -2
- package/dist/renderers/annotation-canvas/react/hooks/useAnnotationCanvas.js +4 -3
- package/dist/renderers/annotation-canvas/rules.d.ts +1 -1
- package/dist/renderers/annotation-canvas/theme.d.ts +13 -0
- package/dist/renderers/annotation-canvas/theme.js +16 -0
- package/dist/renderers/annotation-canvas/types.d.ts +1 -1
- package/dist/renderers/index.d.ts +1 -1
- package/dist/renderers/new-tab-preview.d.ts +3 -0
- package/dist/renderers/new-tab-preview.js +3 -1
- package/dist/renderers/preview-modal-shell/react/hooks/usePreviewModalShell.d.ts +1 -1
- package/dist/renderers/preview-modal-shell/react/hooks/usePreviewModalShell.js +1 -1
- package/dist/renderers/preview-modal-shell/rules.d.ts +1 -1
- package/dist/renderers/preview-modal-shell/types.d.ts +3 -3
- package/dist/renderers/preview-modal-shell/types.js +2 -2
- package/dist/renderers/react/components/ArtifactView.d.ts +1 -1
- package/dist/renderers/react/components/ArtifactView.js +1 -1
- package/dist/renderers/registry.d.ts +12 -6
- package/dist/renderers/registry.js +7 -9
- package/dist/renderers/renderers/html.js +2 -2
- package/dist/renderers/renderers/index.js +1 -1
- package/dist/renderers/renderers/markdown.js +2 -2
- package/dist/renderers/renderers/react-component.js +1 -1
- package/dist/renderers/renderers/svg.js +1 -1
- package/dist/renderers/shiki.js +1 -1
- package/dist/renderers/srcdoc/build.d.ts +1 -1
- package/dist/renderers/srcdoc/build.js +1 -1
- package/dist/renderers/url-load-decision.d.ts +1 -1
- package/dist/renderers/url-load-decision.js +1 -1
- package/dist/styles/admin.css +273 -0
- package/dist/styles/variables.css +117 -0
- package/dist/theme/apply.d.ts +9 -0
- package/dist/theme/apply.js +77 -0
- package/dist/theme/color-scheme.d.ts +8 -0
- package/dist/theme/color-scheme.js +9 -0
- package/dist/theme/default.d.ts +4 -0
- package/dist/theme/default.js +11 -0
- package/dist/theme/index.d.ts +6 -0
- package/dist/theme/index.js +5 -0
- package/dist/theme/stylesheet.d.ts +8 -0
- package/dist/theme/stylesheet.js +14 -0
- package/dist/theme/types.d.ts +39 -0
- package/dist/theme/types.js +2 -0
- package/dist/theme/validation.d.ts +15 -0
- package/dist/theme/validation.js +55 -0
- package/dist/utils/appearance.js +1 -1
- package/dist/utils/browser-request.d.ts +12 -0
- package/dist/utils/browser-request.js +10 -0
- package/dist/utils/color-math.d.ts +20 -6
- package/dist/utils/color-math.js +12 -12
- package/dist/utils/copy-to-clipboard.d.ts +19 -8
- package/dist/utils/copy-to-clipboard.js +15 -11
- package/dist/utils/dom-subscriptions.js +1 -1
- package/dist/utils/endpoint-policy.d.ts +9 -3
- package/dist/utils/endpoint-policy.js +5 -5
- package/dist/utils/markdown-scroll-sync.js +3 -3
- package/dist/utils/notifications.d.ts +1 -1
- package/dist/utils/notifications.js +1 -1
- package/dist/utils/polygon-selection.d.ts +1 -1
- package/dist/utils/polygon-selection.js +1 -1
- package/dist/utils/scroll-tabs-with-wheel.d.ts +4 -1
- package/dist/utils/scroll-tabs-with-wheel.js +1 -1
- package/dist/utils/smooth-scroll-to-top.js +1 -1
- package/dist/utils/sse.d.ts +3 -1
- package/dist/utils/sse.js +1 -1
- package/package.json +105 -16
- package/dist/features/lexical-rich-text-editor/react/test-support/lexical-harness.d.ts +0 -11
- package/dist/features/lexical-rich-text-editor/react/test-support/lexical-harness.js +0 -35
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export interface SelectOption {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function focusableInDomOrder({ exclude }: {
|
|
6
|
+
exclude: HTMLElement | null;
|
|
7
|
+
}): HTMLElement[];
|
|
8
|
+
export interface PanelPosition {
|
|
9
|
+
top?: number | undefined;
|
|
10
|
+
bottom?: number | undefined;
|
|
11
|
+
left: number;
|
|
12
|
+
width: number;
|
|
13
|
+
maxHeight: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Use fixed viewport-relative coordinates to escape scrolling/overflow-hidden ancestors
|
|
17
|
+
* without coupling to their layout. maxHeight uses actual remaining space, not the estimate,
|
|
18
|
+
* so the panel scroll area clips options before the viewport edge does.
|
|
19
|
+
*/
|
|
20
|
+
export declare function computePosition({ trigger }: {
|
|
21
|
+
trigger: HTMLElement;
|
|
22
|
+
}): PanelPosition;
|
|
23
|
+
export declare function buildOptionId({ listboxId, index }: {
|
|
24
|
+
listboxId: string;
|
|
25
|
+
index: number;
|
|
26
|
+
}): string;
|
|
27
|
+
/**
|
|
28
|
+
* Positioning reports a clipped or obscured trigger through onOutOfView; deciding whether
|
|
29
|
+
* to close belongs to the dropdown lifecycle rather than this reusable measurement hook.
|
|
30
|
+
*/
|
|
31
|
+
export declare function usePanelPosition({ open, triggerRef, panelRef, searchInputRef, showSearch, onOutOfView, }: {
|
|
32
|
+
open: boolean;
|
|
33
|
+
triggerRef: React.RefObject<HTMLButtonElement | null>;
|
|
34
|
+
panelRef: React.RefObject<HTMLDivElement | null>;
|
|
35
|
+
searchInputRef: React.RefObject<HTMLInputElement | null>;
|
|
36
|
+
showSearch: boolean;
|
|
37
|
+
onOutOfView: () => void;
|
|
38
|
+
}): {
|
|
39
|
+
position: PanelPosition | null;
|
|
40
|
+
setPosition: import("react").Dispatch<import("react").SetStateAction<PanelPosition | null>>;
|
|
41
|
+
};
|
|
42
|
+
export declare function repositionOrClose({ trigger, onOutOfView, setPosition }: {
|
|
43
|
+
trigger: HTMLElement;
|
|
44
|
+
onOutOfView: () => void;
|
|
45
|
+
setPosition: (position: PanelPosition) => void;
|
|
46
|
+
}): void;
|
|
47
|
+
export declare function resolveTabTarget({ panel, trigger, shiftKey }: {
|
|
48
|
+
panel: HTMLElement | null;
|
|
49
|
+
trigger: HTMLElement | null;
|
|
50
|
+
shiftKey: boolean;
|
|
51
|
+
}): HTMLElement | null;
|
|
52
|
+
export declare function edgeHighlightIndex({ filteredLength, edge }: {
|
|
53
|
+
filteredLength: number;
|
|
54
|
+
edge: "first" | "last";
|
|
55
|
+
}): number;
|
|
56
|
+
export declare function highlightedOptionOrNull({ filtered, highlightedIndex }: {
|
|
57
|
+
filtered: SelectOption[];
|
|
58
|
+
highlightedIndex: number;
|
|
59
|
+
}): SelectOption | null;
|
|
60
|
+
export declare function applyHighlightKey({ key, filtered, highlightedIndex, actions }: {
|
|
61
|
+
key: string;
|
|
62
|
+
filtered: SelectOption[];
|
|
63
|
+
highlightedIndex: number;
|
|
64
|
+
actions: {
|
|
65
|
+
preventDefault: () => void;
|
|
66
|
+
moveHighlight: (delta: 1 | -1) => void;
|
|
67
|
+
setHighlightedIndex: (index: number) => void;
|
|
68
|
+
selectOption: (option: SelectOption) => void;
|
|
69
|
+
};
|
|
70
|
+
}): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Separate hook state from rendering so search visibility, wraparound, placement and
|
|
73
|
+
* out-of-view closing can be asserted directly without coupling every case to portal DOM.
|
|
74
|
+
*/
|
|
75
|
+
export declare function useSelectDropdown({ value, onChange, options, }: {
|
|
76
|
+
value: string;
|
|
77
|
+
onChange: (value: string) => void;
|
|
78
|
+
options: SelectOption[];
|
|
79
|
+
}, { disabled }?: {
|
|
80
|
+
disabled?: boolean | undefined;
|
|
81
|
+
}): {
|
|
82
|
+
open: boolean;
|
|
83
|
+
query: string;
|
|
84
|
+
setQuery: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
85
|
+
highlightedIndex: number;
|
|
86
|
+
setHighlightedIndex: import("react").Dispatch<import("react").SetStateAction<number>>;
|
|
87
|
+
position: PanelPosition | null;
|
|
88
|
+
triggerRef: import("react").RefObject<HTMLButtonElement | null>;
|
|
89
|
+
panelRef: import("react").RefObject<HTMLDivElement | null>;
|
|
90
|
+
searchInputRef: import("react").RefObject<HTMLInputElement | null>;
|
|
91
|
+
optionRefs: import("react").RefObject<Map<number, HTMLLIElement>>;
|
|
92
|
+
listboxId: string;
|
|
93
|
+
showSearch: boolean;
|
|
94
|
+
filtered: SelectOption[];
|
|
95
|
+
selectedOption: SelectOption | null;
|
|
96
|
+
openPanel: () => void;
|
|
97
|
+
closePanel: (opts: {
|
|
98
|
+
refocusTrigger: boolean;
|
|
99
|
+
}) => void;
|
|
100
|
+
selectOption: (option: SelectOption) => void;
|
|
101
|
+
handleTriggerKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => void;
|
|
102
|
+
handlePanelKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
103
|
+
optionId: (index: number) => string;
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=Select.hooks.d.ts.map
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep state/effects and DOM helpers separate from JSX so the dropdown hook can be
|
|
3
|
+
* injected through useDropdown and exercised directly without driving a portaled tree.
|
|
4
|
+
* Compose the smaller hooks unconditionally in their original relative order to obey
|
|
5
|
+
* React hook ordering. Extracting the live DOM key handling from pure highlight decisions
|
|
6
|
+
* reduces the review burden without changing focus or event-order contracts.
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
9
|
+
// Below eight options, a search field adds clutter to a list that is already easy to scan.
|
|
10
|
+
const SEARCH_VISIBILITY_THRESHOLD = 8;
|
|
11
|
+
// 280px is only a placement heuristic; the panel actual maxHeight and scroll limit use
|
|
12
|
+
// the remaining viewport space so a low trigger does not open a mostly off-screen panel.
|
|
13
|
+
const ESTIMATED_PANEL_HEIGHT = 280;
|
|
14
|
+
// The panel is portaled to the end of document.body. Native Tab would follow that DOM
|
|
15
|
+
// position toward browser chrome; walk the trigger real DOM-order neighbours instead.
|
|
16
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
17
|
+
export function focusableInDomOrder({ exclude }) {
|
|
18
|
+
return Array.from(document.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => !exclude || !exclude.contains(el));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Use fixed viewport-relative coordinates to escape scrolling/overflow-hidden ancestors
|
|
22
|
+
* without coupling to their layout. maxHeight uses actual remaining space, not the estimate,
|
|
23
|
+
* so the panel scroll area clips options before the viewport edge does.
|
|
24
|
+
*/
|
|
25
|
+
export function computePosition({ trigger }) {
|
|
26
|
+
const rect = trigger.getBoundingClientRect();
|
|
27
|
+
const gap = 4;
|
|
28
|
+
const viewportHeight = window.innerHeight;
|
|
29
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
30
|
+
const spaceAbove = rect.top;
|
|
31
|
+
const openUpward = spaceBelow < ESTIMATED_PANEL_HEIGHT && spaceAbove > spaceBelow;
|
|
32
|
+
if (openUpward) {
|
|
33
|
+
return { bottom: viewportHeight - rect.top + gap, left: rect.left, width: rect.width, maxHeight: Math.max(120, spaceAbove - gap * 2) };
|
|
34
|
+
}
|
|
35
|
+
return { top: rect.bottom + gap, left: rect.left, width: rect.width, maxHeight: Math.max(120, spaceBelow - gap * 2) };
|
|
36
|
+
}
|
|
37
|
+
export function buildOptionId({ listboxId, index }) {
|
|
38
|
+
return `${listboxId}-option-${index}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Positioning reports a clipped or obscured trigger through onOutOfView; deciding whether
|
|
42
|
+
* to close belongs to the dropdown lifecycle rather than this reusable measurement hook.
|
|
43
|
+
*/
|
|
44
|
+
export function usePanelPosition({ open, triggerRef, panelRef, searchInputRef, showSearch, onOutOfView, }) {
|
|
45
|
+
const [position, setPosition] = useState(null);
|
|
46
|
+
// Two phases, deliberately in one effect rather than two: (1) `position` starts `null` on every
|
|
47
|
+
// open (`closePanel` resets it), so the panel/`createPortal` call below isn't rendered at all yet
|
|
48
|
+
// — `panelRef`/`searchInputRef` are still unattached, so focusing them here would silently no-op.
|
|
49
|
+
// Computing `position` schedules a state update; because this is a `useLayoutEffect`, React
|
|
50
|
+
// re-renders and re-runs layout effects synchronously before the browser paints, rather than
|
|
51
|
+
// waiting a frame. (2) On that second pass `position` is non-null, the portal now exists in the
|
|
52
|
+
// DOM, and the refs are populated — only now is it correct to move focus into it. Found live via
|
|
53
|
+
// this component's own tests: every keyboard-driven test failed until this was split out, because
|
|
54
|
+
// `panelRef.current`/`searchInputRef.current` were both still `null` when focus was attempted in
|
|
55
|
+
// the same pass that first computed the position.
|
|
56
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: two-phase focus effect — see comment above; refs/showSearch intentionally excluded, only open/position redrive it.
|
|
57
|
+
useLayoutEffect(() => {
|
|
58
|
+
if (!open)
|
|
59
|
+
return;
|
|
60
|
+
if (!position) {
|
|
61
|
+
if (triggerRef.current)
|
|
62
|
+
setPosition(computePosition({ trigger: triggerRef.current }));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (showSearch) {
|
|
66
|
+
searchInputRef.current?.focus();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
panelRef.current?.focus();
|
|
70
|
+
}
|
|
71
|
+
}, [open, position]);
|
|
72
|
+
// Keeps the panel anchored to the trigger if the surrounding page/dialog scrolls or resizes while
|
|
73
|
+
// it's open — `.widget-picker-body`'s own `overflow-y: auto` made this a real, live scenario
|
|
74
|
+
// rather than a hypothetical one. `scroll` needs the capture phase: an inner element's own scroll
|
|
75
|
+
// does not bubble to `window` in the bubble phase. The decision logic itself is
|
|
76
|
+
// `repositionOrClose`, above; this effect is only the DOM listener wiring.
|
|
77
|
+
//
|
|
78
|
+
// Found live in a real browser, not by this component's own test suite: scrolling a host's
|
|
79
|
+
// scrolling ancestor far enough that the trigger scrolls out of its own visible clip used to leave
|
|
80
|
+
// this effect happily recomputing a mathematically-correct `position` for it — the panel just
|
|
81
|
+
// ended up following the trigger to an off-screen (or behind-the-header, obscured) spot, floating
|
|
82
|
+
// there indefinitely, nominally still "open" with no visible way to tell.
|
|
83
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: scroll/resize listener wiring — see comment above; triggerRef/onOutOfView intentionally excluded, only `open` re-arms it.
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!open)
|
|
86
|
+
return;
|
|
87
|
+
function reposition() {
|
|
88
|
+
const el = triggerRef.current;
|
|
89
|
+
if (!el)
|
|
90
|
+
return;
|
|
91
|
+
repositionOrClose({ trigger: el, onOutOfView: onOutOfView, setPosition: setPosition });
|
|
92
|
+
}
|
|
93
|
+
window.addEventListener("scroll", reposition, true);
|
|
94
|
+
window.addEventListener("resize", reposition);
|
|
95
|
+
return () => {
|
|
96
|
+
window.removeEventListener("scroll", reposition, true);
|
|
97
|
+
window.removeEventListener("resize", reposition);
|
|
98
|
+
};
|
|
99
|
+
}, [open]);
|
|
100
|
+
return { position, setPosition };
|
|
101
|
+
}
|
|
102
|
+
export function repositionOrClose({ trigger, onOutOfView, setPosition }) {
|
|
103
|
+
const rect = trigger.getBoundingClientRect();
|
|
104
|
+
const outOfViewport = rect.bottom <= 0 || rect.top >= window.innerHeight || rect.right <= 0 || rect.left >= window.innerWidth;
|
|
105
|
+
if (outOfViewport) {
|
|
106
|
+
onOutOfView();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (typeof document.elementFromPoint === "function") {
|
|
110
|
+
const topmostAtCenter = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
111
|
+
const obscured = !topmostAtCenter || !(trigger.contains(topmostAtCenter) || topmostAtCenter.contains(trigger));
|
|
112
|
+
if (obscured) {
|
|
113
|
+
onOutOfView();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
setPosition(computePosition({ trigger: trigger }));
|
|
118
|
+
}
|
|
119
|
+
function useCloseOnOutsideClick({ open, triggerRef, panelRef, onOutside, }) {
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!open)
|
|
122
|
+
return;
|
|
123
|
+
function onDocMouseDown(e) {
|
|
124
|
+
const target = e.target;
|
|
125
|
+
if (triggerRef.current?.contains(target))
|
|
126
|
+
return;
|
|
127
|
+
if (panelRef.current?.contains(target))
|
|
128
|
+
return;
|
|
129
|
+
onOutside();
|
|
130
|
+
}
|
|
131
|
+
document.addEventListener("mousedown", onDocMouseDown);
|
|
132
|
+
return () => document.removeEventListener("mousedown", onDocMouseDown);
|
|
133
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
|
+
}, [open]);
|
|
135
|
+
}
|
|
136
|
+
// A search can remove the previously highlighted option; re-anchor to the first match
|
|
137
|
+
// rather than keep a stale index outside the narrowed list.
|
|
138
|
+
function useResetHighlightOnQueryChange({ open, query, hasMatches, setHighlightedIndex, }) {
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (!open)
|
|
141
|
+
return;
|
|
142
|
+
setHighlightedIndex(hasMatches ? 0 : -1);
|
|
143
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
144
|
+
}, [query]);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Keyboard highlight used to move logically past the visible fold without scrolling its
|
|
148
|
+
* row into view. nearest scrolls only what is needed instead of recentering the list and
|
|
149
|
+
* fighting a user who has already scrolled it manually.
|
|
150
|
+
*/
|
|
151
|
+
function useScrollHighlightedIntoView({ open, highlightedIndex, optionRefs, }) {
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!open || highlightedIndex < 0)
|
|
154
|
+
return;
|
|
155
|
+
const el = optionRefs.current.get(highlightedIndex);
|
|
156
|
+
// Guarded the same way as `document.elementFromPoint` above — this app's own jsdom test
|
|
157
|
+
// harness doesn't implement `scrollIntoView` at all (not even as a no-op), so an unguarded call
|
|
158
|
+
// throws inside the effect on every highlight change.
|
|
159
|
+
if (el && typeof el.scrollIntoView === "function")
|
|
160
|
+
el.scrollIntoView({ block: "nearest" });
|
|
161
|
+
}, [open, highlightedIndex]);
|
|
162
|
+
}
|
|
163
|
+
export function resolveTabTarget({ panel, trigger, shiftKey }) {
|
|
164
|
+
const nodes = focusableInDomOrder({ exclude: panel });
|
|
165
|
+
const triggerIndex = trigger ? nodes.indexOf(trigger) : -1;
|
|
166
|
+
if (triggerIndex < 0)
|
|
167
|
+
return null;
|
|
168
|
+
return nodes[triggerIndex + (shiftKey ? -1 : 1)] ?? null;
|
|
169
|
+
}
|
|
170
|
+
export function edgeHighlightIndex({ filteredLength, edge }) {
|
|
171
|
+
if (filteredLength === 0)
|
|
172
|
+
return -1;
|
|
173
|
+
return edge === "first" ? 0 : filteredLength - 1;
|
|
174
|
+
}
|
|
175
|
+
export function highlightedOptionOrNull({ filtered, highlightedIndex }) {
|
|
176
|
+
return highlightedIndex >= 0 ? (filtered[highlightedIndex] ?? null) : null;
|
|
177
|
+
}
|
|
178
|
+
export function applyHighlightKey({ key, filtered, highlightedIndex, actions }) {
|
|
179
|
+
switch (key) {
|
|
180
|
+
case "ArrowDown":
|
|
181
|
+
actions.preventDefault();
|
|
182
|
+
actions.moveHighlight(1);
|
|
183
|
+
return true;
|
|
184
|
+
case "ArrowUp":
|
|
185
|
+
actions.preventDefault();
|
|
186
|
+
actions.moveHighlight(-1);
|
|
187
|
+
return true;
|
|
188
|
+
case "Home":
|
|
189
|
+
actions.preventDefault();
|
|
190
|
+
actions.setHighlightedIndex(edgeHighlightIndex({ filteredLength: filtered.length, edge: "first" }));
|
|
191
|
+
return true;
|
|
192
|
+
case "End":
|
|
193
|
+
actions.preventDefault();
|
|
194
|
+
actions.setHighlightedIndex(edgeHighlightIndex({ filteredLength: filtered.length, edge: "last" }));
|
|
195
|
+
return true;
|
|
196
|
+
case "Enter": {
|
|
197
|
+
actions.preventDefault();
|
|
198
|
+
const option = highlightedOptionOrNull({ filtered: filtered, highlightedIndex: highlightedIndex });
|
|
199
|
+
if (option)
|
|
200
|
+
actions.selectOption(option);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
default:
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Keep live event/focus effects in the key handler; move the trigger-neighbour calculation
|
|
208
|
+
// out so changing the DOM effects does not bury the independently testable focus decision.
|
|
209
|
+
function useSelectKeyboardHandlers({ disabled, open, openPanel, closePanel, moveHighlight, selectOption, highlightedIndex, setHighlightedIndex, filtered, triggerRef, panelRef, }) {
|
|
210
|
+
function handleTriggerKeyDown(e) {
|
|
211
|
+
if (disabled || open)
|
|
212
|
+
return;
|
|
213
|
+
switch (e.key) {
|
|
214
|
+
case "Enter":
|
|
215
|
+
case " ":
|
|
216
|
+
case "ArrowDown":
|
|
217
|
+
case "ArrowUp":
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
openPanel();
|
|
220
|
+
break;
|
|
221
|
+
default:
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Escape/Tab need live propagation and focus effects. Highlight/selection keys delegate
|
|
226
|
+
// to the pure helper; preventDefault is injected to preserve its order before each action.
|
|
227
|
+
function handlePanelKeyDown(e) {
|
|
228
|
+
if (e.key === "Escape") {
|
|
229
|
+
// Must not bubble: a host dialog (`WidgetPickerDialog`) listens for Escape on `document`
|
|
230
|
+
// to cancel the whole modal. Without stopping propagation here, closing just this dropdown
|
|
231
|
+
// would also close the dialog underneath it — the regression `Select.unit.test.tsx` pins.
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
closePanel({ refocusTrigger: true });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (e.key === "Tab") {
|
|
238
|
+
// See `resolveTabTarget`'s own comment: walk the trigger's real DOM-order neighbours rather
|
|
239
|
+
// than let native Tab handling run, since this event is bubbling from a panel portaled to
|
|
240
|
+
// the end of `document.body`, not sitting next to the trigger in the DOM.
|
|
241
|
+
const target = resolveTabTarget({ panel: panelRef.current, trigger: triggerRef.current, shiftKey: e.shiftKey });
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
closePanel({ refocusTrigger: false });
|
|
244
|
+
target?.focus();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
applyHighlightKey({ key: e.key, filtered: filtered, highlightedIndex: highlightedIndex, actions: {
|
|
248
|
+
preventDefault: () => e.preventDefault(),
|
|
249
|
+
moveHighlight,
|
|
250
|
+
setHighlightedIndex,
|
|
251
|
+
selectOption,
|
|
252
|
+
} });
|
|
253
|
+
}
|
|
254
|
+
return { handleTriggerKeyDown, handlePanelKeyDown };
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Separate hook state from rendering so search visibility, wraparound, placement and
|
|
258
|
+
* out-of-view closing can be asserted directly without coupling every case to portal DOM.
|
|
259
|
+
*/
|
|
260
|
+
export function useSelectDropdown({ value, onChange, options, }, { disabled } = {}) {
|
|
261
|
+
const [open, setOpen] = useState(false);
|
|
262
|
+
const [query, setQuery] = useState("");
|
|
263
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
264
|
+
const triggerRef = useRef(null);
|
|
265
|
+
const panelRef = useRef(null);
|
|
266
|
+
const searchInputRef = useRef(null);
|
|
267
|
+
// Index -> `<li>` node, so the highlight-follow effect below can scroll the right row into view
|
|
268
|
+
// without an id-based `querySelector` (this component's ids come from `useId()`, which can
|
|
269
|
+
// contain characters — `:`, in React's own scheme — that need escaping in a CSS selector; a
|
|
270
|
+
// direct ref avoids that entirely). Populated by each option's own ref callback below.
|
|
271
|
+
const optionRefs = useRef(new Map());
|
|
272
|
+
const listboxId = useId();
|
|
273
|
+
const showSearch = options.length >= SEARCH_VISIBILITY_THRESHOLD;
|
|
274
|
+
const filtered = useMemo(() => {
|
|
275
|
+
if (!showSearch || !query.trim())
|
|
276
|
+
return options;
|
|
277
|
+
const q = query.trim().toLowerCase();
|
|
278
|
+
return options.filter((o) => o.label.toLowerCase().includes(q));
|
|
279
|
+
}, [options, query, showSearch]);
|
|
280
|
+
const selectedOption = options.find((o) => o.value === value) ?? null;
|
|
281
|
+
function openPanel() {
|
|
282
|
+
if (disabled)
|
|
283
|
+
return;
|
|
284
|
+
const initialIndex = options.findIndex((o) => o.value === value);
|
|
285
|
+
setQuery("");
|
|
286
|
+
setOpen(true);
|
|
287
|
+
setHighlightedIndex(initialIndex >= 0 ? initialIndex : options.length ? 0 : -1);
|
|
288
|
+
}
|
|
289
|
+
function closePanel(opts) {
|
|
290
|
+
setOpen(false);
|
|
291
|
+
setPosition(null); // forces the effect below through its "measure, then focus" two-phase sequence again on the next open, rather than focusing at a stale, pre-close position
|
|
292
|
+
if (opts.refocusTrigger)
|
|
293
|
+
triggerRef.current?.focus();
|
|
294
|
+
}
|
|
295
|
+
function selectOption(option) {
|
|
296
|
+
onChange(option.value);
|
|
297
|
+
closePanel({ refocusTrigger: true });
|
|
298
|
+
}
|
|
299
|
+
function moveHighlight(delta) {
|
|
300
|
+
setHighlightedIndex((current) => {
|
|
301
|
+
if (filtered.length === 0)
|
|
302
|
+
return -1;
|
|
303
|
+
return (current + delta + filtered.length) % filtered.length;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
const { position, setPosition } = usePanelPosition({
|
|
307
|
+
open,
|
|
308
|
+
triggerRef,
|
|
309
|
+
panelRef,
|
|
310
|
+
searchInputRef,
|
|
311
|
+
showSearch,
|
|
312
|
+
onOutOfView: () => closePanel({ refocusTrigger: false }),
|
|
313
|
+
});
|
|
314
|
+
useCloseOnOutsideClick({
|
|
315
|
+
open,
|
|
316
|
+
triggerRef,
|
|
317
|
+
panelRef,
|
|
318
|
+
onOutside: () => closePanel({ refocusTrigger: false }),
|
|
319
|
+
});
|
|
320
|
+
useResetHighlightOnQueryChange({ open, query, hasMatches: filtered.length > 0, setHighlightedIndex });
|
|
321
|
+
useScrollHighlightedIntoView({ open, highlightedIndex, optionRefs });
|
|
322
|
+
const { handleTriggerKeyDown, handlePanelKeyDown } = useSelectKeyboardHandlers({
|
|
323
|
+
disabled,
|
|
324
|
+
open,
|
|
325
|
+
openPanel,
|
|
326
|
+
closePanel,
|
|
327
|
+
moveHighlight,
|
|
328
|
+
selectOption,
|
|
329
|
+
highlightedIndex,
|
|
330
|
+
setHighlightedIndex,
|
|
331
|
+
filtered,
|
|
332
|
+
triggerRef,
|
|
333
|
+
panelRef,
|
|
334
|
+
});
|
|
335
|
+
const optionId = (index) => buildOptionId({ listboxId: listboxId, index: index });
|
|
336
|
+
return {
|
|
337
|
+
open,
|
|
338
|
+
query,
|
|
339
|
+
setQuery,
|
|
340
|
+
highlightedIndex,
|
|
341
|
+
setHighlightedIndex,
|
|
342
|
+
position,
|
|
343
|
+
triggerRef,
|
|
344
|
+
panelRef,
|
|
345
|
+
searchInputRef,
|
|
346
|
+
optionRefs,
|
|
347
|
+
listboxId,
|
|
348
|
+
showSearch,
|
|
349
|
+
filtered,
|
|
350
|
+
selectedOption,
|
|
351
|
+
openPanel,
|
|
352
|
+
closePanel,
|
|
353
|
+
selectOption,
|
|
354
|
+
handleTriggerKeyDown,
|
|
355
|
+
handlePanelKeyDown,
|
|
356
|
+
optionId,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=Select.hooks.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
import { agentHandle } from "@jini-ai/agentic";
|
|
4
|
+
import { buildAgentListHandles } from "@jini-ai/agentic";
|
|
5
|
+
import { useSelectDropdown } from "./Select.hooks.js";
|
|
6
|
+
function SelectOptionRow({ option, index, isSelected, isHighlighted, optionId, setOptionRef, onHighlight, onSelect, agentHandle: optionHandle, }) {
|
|
7
|
+
return (_jsxs("li", { ref: (el) => setOptionRef(index, el), id: optionId, role: "option", "aria-selected": isSelected, className: `select-option${isSelected ? " is-selected" : ""}${isHighlighted ? " is-highlighted" : ""}`, onMouseEnter: () => onHighlight(index), onClick: () => onSelect(option), ...(optionHandle ? agentHandle({ handle: optionHandle }, { role: "button", label: option.label }) : {}), children: [_jsx("span", { className: "select-option-label", children: option.label }), isSelected ? (_jsx("svg", { className: "select-option-check", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M3.5 8.5 6.5 11.5 12.5 4.5", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round" }) })) : null] }));
|
|
8
|
+
}
|
|
9
|
+
function SelectPanel({ panelRef, position, showSearch, searchInputRef, query, setQuery, filtered, value, highlightedIndex, listboxId, optionId, setOptionRef, onHighlight, onSelect, onKeyDown, searchHandle, optionHandles, t, }) {
|
|
10
|
+
return (_jsxs("div", { ref: panelRef, className: "select-panel", style: { left: position.left, width: position.width, top: position.top, bottom: position.bottom, maxHeight: position.maxHeight }, tabIndex: -1, onKeyDown: onKeyDown, children: [showSearch ? (_jsx("input", { ref: searchInputRef, type: "text", className: "select-search", "aria-label": t("Search options"), placeholder: t("Search…"), value: query, onChange: (e) => setQuery(e.target.value), ...(searchHandle ? agentHandle({ handle: searchHandle }, { role: "field", label: "Search the option list" }) : {}) })) : null, _jsx("ul", { className: "select-list", role: "listbox", id: listboxId, children: filtered.length === 0 ? (_jsx("li", { className: "select-empty", role: "presentation", children: t("No matches") })) : (filtered.map((option, index) => (_jsx(SelectOptionRow, { option: option, index: index, isSelected: option.value === value, isHighlighted: index === highlightedIndex, optionId: optionId(index), setOptionRef: setOptionRef, onHighlight: onHighlight, onSelect: onSelect, agentHandle: optionHandles?.[index] }, option.value)))) })] }));
|
|
11
|
+
}
|
|
12
|
+
export function resolveSelectTriggerLabel({ selectedOption, placeholder }, { t = (key) => key } = {}) {
|
|
13
|
+
if (selectedOption)
|
|
14
|
+
return { text: selectedOption.label, className: "select-trigger-label" };
|
|
15
|
+
return { text: placeholder ?? t("Select…"), className: "select-trigger-label is-placeholder" };
|
|
16
|
+
}
|
|
17
|
+
function resolveSelectTriggerActiveDescendant(open, highlightedIndex, optionId) {
|
|
18
|
+
return open && highlightedIndex >= 0 ? optionId(highlightedIndex) : undefined;
|
|
19
|
+
}
|
|
20
|
+
function resolveSelectTriggerHandleProps(base, ariaLabel, placeholder) {
|
|
21
|
+
return base ? agentHandle({ handle: base }, { role: "button", label: ariaLabel ?? placeholder ?? "Select an option" }) : {};
|
|
22
|
+
}
|
|
23
|
+
/** Controlled searchable listbox with portal positioning and keyboard navigation. */
|
|
24
|
+
export function Select(props) {
|
|
25
|
+
const { value, onChange, options, placeholder, id, disabled, useDropdown = useSelectDropdown, agentHandle: base, translate, t = translate ?? ((key) => key) } = props;
|
|
26
|
+
const ariaLabel = props["aria-label"];
|
|
27
|
+
const ariaLabelledBy = props["aria-labelledby"];
|
|
28
|
+
const { open, highlightedIndex, setHighlightedIndex, position, triggerRef, panelRef, searchInputRef, optionRefs, listboxId, showSearch, filtered, selectedOption, openPanel, closePanel, selectOption, handleTriggerKeyDown, handlePanelKeyDown, optionId, query, setQuery, } = useDropdown({ value, onChange, options }, { disabled });
|
|
29
|
+
function setOptionRef(index, el) {
|
|
30
|
+
if (el)
|
|
31
|
+
optionRefs.current.set(index, el);
|
|
32
|
+
else
|
|
33
|
+
optionRefs.current.delete(index);
|
|
34
|
+
}
|
|
35
|
+
const triggerLabel = resolveSelectTriggerLabel({ selectedOption: selectedOption, placeholder: placeholder }, { t: t });
|
|
36
|
+
const activeDescendant = resolveSelectTriggerActiveDescendant(open, highlightedIndex, optionId);
|
|
37
|
+
// One handle per FILTERED option, recomputed as the search query narrows the list — an option
|
|
38
|
+
// dropped by the current query has no rendered row to attach a handle to, so it is simply absent
|
|
39
|
+
// this render rather than holding a handle nothing resolves to. `undefined` (not an empty array)
|
|
40
|
+
// when `base` itself is unset, so `SelectPanel` can tell "opted out" apart from "no options match".
|
|
41
|
+
const optionHandles = base ? buildAgentListHandles({ prefix: `${base}-option`, ids: filtered.map((option) => option.value) }) : undefined;
|
|
42
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", ref: triggerRef, id: id, className: "select-trigger", role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": open ? listboxId : undefined, "aria-activedescendant": activeDescendant, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, disabled: disabled, onClick: () => (open ? closePanel({ refocusTrigger: false }) : openPanel()), onKeyDown: handleTriggerKeyDown, ...resolveSelectTriggerHandleProps(base, ariaLabel, placeholder), children: [_jsx("span", { className: triggerLabel.className, children: triggerLabel.text }), _jsx("span", { className: "select-trigger-chevron", "aria-hidden": "true" })] }), open && position
|
|
43
|
+
? createPortal(_jsx(SelectPanel, { panelRef: panelRef, position: position, showSearch: showSearch, searchInputRef: searchInputRef, query: query, setQuery: setQuery, filtered: filtered, value: value, highlightedIndex: highlightedIndex, listboxId: listboxId, optionId: optionId, setOptionRef: setOptionRef, onHighlight: setHighlightedIndex, onSelect: selectOption, onKeyDown: handlePanelKeyDown, searchHandle: base ? `${base}-search` : undefined, optionHandles: optionHandles, t: t }), document.body)
|
|
44
|
+
: null] }));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=Select.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { Select, resolveSelectTriggerLabel } from './components/Select/Select.js';
|
|
2
|
+
export type { SelectProps, SelectOption } from './components/Select/Select.js';
|
|
3
|
+
export { useSelectDropdown, usePanelPosition } from './components/Select/Select.hooks.js';
|
|
4
|
+
export type { PanelPosition } from './components/Select/Select.hooks.js';
|
|
5
|
+
export { InfoTip } from './components/InfoTip.js';
|
|
6
|
+
export type { InfoTipProps } from './components/InfoTip.js';
|
|
7
|
+
export { useInfoTip } from './components/InfoTip.hooks.js';
|
|
8
|
+
export type { InfoTipController } from './components/InfoTip.hooks.js';
|
|
9
|
+
export { SeeMore, resolveSeeMoreView } from './components/SeeMore/SeeMore.js';
|
|
10
|
+
export type { SeeMoreProps, SeeMoreView } from './components/SeeMore/SeeMore.js';
|
|
11
|
+
export { useSeeMoreClamp } from './components/SeeMore/SeeMore.hooks.js';
|
|
12
|
+
export { ImagePreviewModal } from './components/ImagePreviewModal.js';
|
|
13
|
+
export type { ImagePreviewModalProps } from './components/ImagePreviewModal.js';
|
|
14
|
+
export { useImagePreviewModal } from './components/ImagePreviewModal.hooks.js';
|
|
15
|
+
export type { ImagePreviewModalController } from './components/ImagePreviewModal.hooks.js';
|
|
16
|
+
export { ComingSoonPanel } from './components/ComingSoonPanel.js';
|
|
17
|
+
export type { ComingSoonPanelProps } from './components/ComingSoonPanel.js';
|
|
18
|
+
export { ComingSoonNotice } from './components/ComingSoonNotice.js';
|
|
19
|
+
export type { ComingSoonNoticeProps } from './components/ComingSoonNotice.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Select, resolveSelectTriggerLabel } from './components/Select/Select.js';
|
|
2
|
+
export { useSelectDropdown, usePanelPosition } from './components/Select/Select.hooks.js';
|
|
3
|
+
export { InfoTip } from './components/InfoTip.js';
|
|
4
|
+
export { useInfoTip } from './components/InfoTip.hooks.js';
|
|
5
|
+
export { SeeMore, resolveSeeMoreView } from './components/SeeMore/SeeMore.js';
|
|
6
|
+
export { useSeeMoreClamp } from './components/SeeMore/SeeMore.hooks.js';
|
|
7
|
+
export { ImagePreviewModal } from './components/ImagePreviewModal.js';
|
|
8
|
+
export { useImagePreviewModal } from './components/ImagePreviewModal.hooks.js';
|
|
9
|
+
export { ComingSoonPanel } from './components/ComingSoonPanel.js';
|
|
10
|
+
export { ComingSoonNotice } from './components/ComingSoonNotice.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -11,6 +11,6 @@ export interface ThemeOption {
|
|
|
11
11
|
export declare const THEME_OPTIONS: ThemeOption[];
|
|
12
12
|
/** Neutral default accent — hosts are expected to override via their own
|
|
13
13
|
* config/branding; this only guarantees the CSS vars are always set. */
|
|
14
|
-
export declare const DEFAULT_ACCENT_COLOR
|
|
15
|
-
export declare const ACCENT_SWATCHES: readonly [
|
|
14
|
+
export declare const DEFAULT_ACCENT_COLOR: string;
|
|
15
|
+
export declare const ACCENT_SWATCHES: readonly [string, "#c96442", "#7c3aed", "#059669", "#dc2626", "#d97706", "#0891b2", "#db2777"];
|
|
16
16
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defaultAdminTheme } from '../../theme/default.js';
|
|
1
2
|
/** Plain-English defaults for the three segmented-control options — a host
|
|
2
3
|
* wanting localized copy overrides via `AppearanceTabProps.themeLabels` (the
|
|
3
4
|
* component wraps each through `useT()` regardless, so this is only the
|
|
@@ -9,7 +10,7 @@ export const THEME_OPTIONS = [
|
|
|
9
10
|
];
|
|
10
11
|
/** Neutral default accent — hosts are expected to override via their own
|
|
11
12
|
* config/branding; this only guarantees the CSS vars are always set. */
|
|
12
|
-
export const DEFAULT_ACCENT_COLOR =
|
|
13
|
+
export const DEFAULT_ACCENT_COLOR = defaultAdminTheme.light.primary;
|
|
13
14
|
export const ACCENT_SWATCHES = [
|
|
14
15
|
DEFAULT_ACCENT_COLOR,
|
|
15
16
|
'#c96442',
|
|
@@ -13,8 +13,8 @@ import { THEME_OPTIONS } from '../../constants.js';
|
|
|
13
13
|
*/
|
|
14
14
|
export function AppearanceTab({ theme, onThemeChange, accentColor, onAccentColorChange, accentSwatches = ACCENT_SWATCHES, livePreview = true, ariaLabel, accentLabel, defaultAccentAriaLabel, customAccentAriaLabel, agentHandle, }) {
|
|
15
15
|
const t = useT();
|
|
16
|
-
const resolvedAccent = resolveAccentColor(accentColor);
|
|
17
|
-
const swatchHandles = agentHandle ? buildAgentListHandles(agentSubHandle(agentHandle, 'swatch'), accentSwatches) : undefined;
|
|
16
|
+
const resolvedAccent = resolveAccentColor({ value: accentColor });
|
|
17
|
+
const swatchHandles = agentHandle ? buildAgentListHandles({ prefix: agentSubHandle({ base: agentHandle, action: 'swatch' }), ids: accentSwatches }) : undefined;
|
|
18
18
|
const resolvedAriaLabel = ariaLabel ?? t('Appearance');
|
|
19
19
|
const resolvedAccentLabel = accentLabel ?? t('Accent color');
|
|
20
20
|
const resolvedDefaultAccentAriaLabel = defaultAccentAriaLabel ?? t('Default accent color');
|
|
@@ -25,10 +25,10 @@ export function AppearanceTab({ theme, onThemeChange, accentColor, onAccentColor
|
|
|
25
25
|
return;
|
|
26
26
|
applyAppearanceToDocument({ ...(appliedTheme ? { theme: appliedTheme } : {}), accentColor: resolvedAccent });
|
|
27
27
|
}, [livePreview, appliedTheme, resolvedAccent]);
|
|
28
|
-
return (_jsxs("section", { className: "jini-settings-section jini-settings-appearance", children: [_jsx("div", { className: "jini-seg-control", role: "group", "aria-label": resolvedAriaLabel, style: { '--seg-cols': THEME_OPTIONS.length }, children: THEME_OPTIONS.map((option) => (_jsxs("button", { type: "button", className: 'jini-seg-btn' + (theme === option.value ? ' active' : ''), "aria-pressed": theme === option.value, onClick: () => onThemeChange(option.value), ...agentHandleProps(agentHandle, { action: `theme-${option.value}`, role: 'button', label: t(option.label) }), children: [option.icon ? _jsx(Icon, { name: option.icon, size: 14, "aria-hidden": "true" }) : null, _jsx("span", { className: "jini-seg-title", children: t(option.label) })] }, option.value))) }), _jsxs("div", { className: "jini-field", children: [_jsx("span", { className: "jini-field-label", children: resolvedAccentLabel }), _jsxs("div", { className: "jini-accent-swatches", role: "radiogroup", "aria-label": resolvedAccentLabel, children: [accentSwatches.map((color, index) => {
|
|
28
|
+
return (_jsxs("section", { className: "jini-settings-section jini-settings-appearance", children: [_jsx("div", { className: "jini-seg-control", role: "group", "aria-label": resolvedAriaLabel, style: { '--seg-cols': THEME_OPTIONS.length }, children: THEME_OPTIONS.map((option) => (_jsxs("button", { type: "button", className: 'jini-seg-btn' + (theme === option.value ? ' active' : ''), "aria-pressed": theme === option.value, onClick: () => onThemeChange(option.value), ...agentHandleProps({}, { base: agentHandle, ...({ action: `theme-${option.value}`, role: 'button', label: t(option.label) }) }), children: [option.icon ? _jsx(Icon, { name: option.icon, size: 14, "aria-hidden": "true" }) : null, _jsx("span", { className: "jini-seg-title", children: t(option.label) })] }, option.value))) }), _jsxs("div", { className: "jini-field", children: [_jsx("span", { className: "jini-field-label", children: resolvedAccentLabel }), _jsxs("div", { className: "jini-accent-swatches", role: "radiogroup", "aria-label": resolvedAccentLabel, children: [accentSwatches.map((color, index) => {
|
|
29
29
|
const active = resolvedAccent === color;
|
|
30
30
|
const swatchLabel = color === DEFAULT_ACCENT_COLOR ? resolvedDefaultAccentAriaLabel : color;
|
|
31
|
-
return (_jsx("button", { type: "button", className: `jini-accent-swatch${active ? ' active' : ''}`, style: { background: color }, "aria-label": swatchLabel, "aria-checked": active, role: "radio", onClick: () => onAccentColorChange(normalizeAccentColor(color) ?? color), ...agentHandleProps(swatchHandles?.[index], { role: 'button', label: swatchLabel }) }, color));
|
|
32
|
-
}), _jsx("input", { type: "color", "aria-label": resolvedCustomAccentAriaLabel, className: "jini-accent-swatch-picker", value: resolvedAccent, onChange: (event) => onAccentColorChange(event.target.value), ...agentHandleProps(agentHandle, { action: 'custom-accent', role: 'field', label: resolvedCustomAccentAriaLabel }) })] })] })] }));
|
|
31
|
+
return (_jsx("button", { type: "button", className: `jini-accent-swatch${active ? ' active' : ''}`, style: { background: color }, "aria-label": swatchLabel, "aria-checked": active, role: "radio", onClick: () => onAccentColorChange(normalizeAccentColor({ value: color }) ?? color), ...agentHandleProps({}, { base: swatchHandles?.[index], ...({ role: 'button', label: swatchLabel }) }) }, color));
|
|
32
|
+
}), _jsx("input", { type: "color", "aria-label": resolvedCustomAccentAriaLabel, className: "jini-accent-swatch-picker", value: resolvedAccent, onChange: (event) => onAccentColorChange(event.target.value), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'custom-accent', role: 'field', label: resolvedCustomAccentAriaLabel }) }) })] })] })] }));
|
|
33
33
|
}
|
|
34
34
|
//# sourceMappingURL=AppearanceTab.js.map
|
|
@@ -13,10 +13,14 @@ export type AccentCssVars = Record<(typeof ACCENT_VARS)[number], string>;
|
|
|
13
13
|
* string or does not match `#rrggbb`.
|
|
14
14
|
* @complexity O(1).
|
|
15
15
|
*/
|
|
16
|
-
export declare function normalizeAccentColor(value
|
|
17
|
-
|
|
16
|
+
export declare function normalizeAccentColor({ value }: {
|
|
17
|
+
value: unknown;
|
|
18
|
+
}): string | null;
|
|
19
|
+
/** `normalizeAccentColor({ value }) ?? DEFAULT_ACCENT_COLOR` — always returns a
|
|
18
20
|
* usable color. */
|
|
19
|
-
export declare function resolveAccentColor(value
|
|
21
|
+
export declare function resolveAccentColor({ value }: {
|
|
22
|
+
value: unknown;
|
|
23
|
+
}): string;
|
|
20
24
|
/**
|
|
21
25
|
* Pure computation of the five `--accent*` CSS custom property values for a
|
|
22
26
|
* given (already-resolved) accent color, via `color-mix()` formulas. The DOM
|
|
@@ -26,6 +30,8 @@ export declare function resolveAccentColor(value: unknown): string;
|
|
|
26
30
|
*
|
|
27
31
|
* @complexity O(1) — five template-string formulas, no DOM access.
|
|
28
32
|
*/
|
|
29
|
-
export declare function accentVars(accentColor
|
|
33
|
+
export declare function accentVars({ accentColor }: {
|
|
34
|
+
accentColor: string;
|
|
35
|
+
}): AccentCssVars;
|
|
30
36
|
export {};
|
|
31
37
|
//# sourceMappingURL=rules.d.ts.map
|