@jini-ai/ui 0.6.12 → 0.6.15-rc.2
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 +28 -0
- package/README.md +14 -0
- package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.d.ts +17 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +64 -14
- package/dist/features/admin-widgets/components/InfoTip.js +2 -2
- package/dist/features/admin-widgets/index.d.ts +1 -1
- package/dist/features/admin-widgets/index.js +1 -1
- package/dist/features/execution/react/components/AgentCliEnvFields.js +27 -8
- package/dist/features/execution/react/components/ByokProviderForm.js +16 -19
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/shadcn/button.d.ts +8 -6
- package/dist/features/interactive-ui/providers/shadcn/button.js +5 -6
- package/dist/features/interactive-ui/providers/shadcn/card.d.ts +3 -3
- package/dist/features/interactive-ui/providers/shadcn/card.js +3 -4
- package/dist/features/interactive-ui/providers/shadcn/select.d.ts +3 -5
- package/dist/features/interactive-ui/providers/shadcn/select.js +3 -7
- package/dist/features/interactive-ui/providers/shadcn/table.d.ts +4 -5
- package/dist/features/interactive-ui/providers/shadcn/table.js +4 -9
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +28 -0
- package/dist/features/mcp-ui/surfaces/fields.d.ts +28 -1
- package/dist/features/mcp-ui/surfaces/fields.js +5 -1
- package/dist/features/mcp-ui/surfaces/form.js +86 -16
- package/dist/features/mcp-ui/surfaces/text-input.d.ts +3 -3
- package/dist/features/mcp-ui/surfaces/text-input.js +26 -11
- package/dist/features/media-providers/catalog.d.ts +3 -0
- package/dist/features/media-providers/catalog.js +3 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.js +5 -4
- package/dist/features/media-providers/react/components/media-providers-tab-labels.js +2 -1
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +10 -5
- package/dist/features/panel-kit/fetch-query/adapter.react.js +9 -3
- package/dist/features/panel-kit/fetch-query/binding.react.d.ts +4 -0
- package/dist/features/panel-kit/fetch-query/binding.react.js +29 -10
- package/dist/features/panel-kit/fetch-query/index.d.ts +1 -1
- package/dist/features/panel-kit/fetch-query/index.js +1 -1
- package/dist/features/panel-kit/fetch-query/tanstack.d.ts +1 -1
- package/dist/features/panel-kit/fetch-query/tanstack.js +1 -1
- package/dist/features/privacy/react/components/PrivacyTab.d.ts +1 -3
- package/dist/features/privacy/react/components/PrivacyTab.js +5 -8
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +0 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +0 -6
- package/dist/features/settings/dialog/styles/settings-dialog.css +5 -70
- package/dist/features/source-config-list/react/components/SourceConfigField.js +27 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/components/SecretInput.d.ts +47 -0
- package/dist/react/components/SecretInput.hooks.d.ts +26 -0
- package/dist/react/components/SecretInput.hooks.js +25 -0
- package/dist/react/components/SecretInput.js +30 -0
- package/dist/styles/lightbox.css +1 -1
- package/dist/styles/variables.css +1 -1
- package/package.json +9 -9
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type
|
|
2
|
+
import { type FetchQueryCacheOptions } from '@jini-ai/core/fetch';
|
|
3
3
|
import type { CachedLoader, CachedLoaderDefaults, CachedLoaderOptions, FetchMutationOptions, FetchQueryOptions, MutationResult, QueryKey, QueryResult } from '@jini-ai/core/fetch';
|
|
4
|
-
/** Isolated cache scope; no external provider or query-library singleton is needed.
|
|
5
|
-
|
|
4
|
+
/** Isolated cache scope; no external provider or query-library singleton is needed.
|
|
5
|
+
* Omitted timing ports use core's real clock and native timers. Keep port identities stable:
|
|
6
|
+
* replacing a port creates a new cache and disposes the previous scope's timers/subscriptions.
|
|
7
|
+
* @param props Children and optional host environment, clock and scheduler ports.
|
|
8
|
+
* @returns The provider-scoped query binding; activates on mount and disposes on unmount.
|
|
9
|
+
* @example <FetchQueryProvider clock={clock} scheduler={scheduler}><Records /></FetchQueryProvider>
|
|
10
|
+
* @complexity O(1) setup time and space; disposal scales with cached entries. */
|
|
11
|
+
export declare function FetchQueryProvider({ children, environment, ...timing }: {
|
|
6
12
|
children: ReactNode;
|
|
7
|
-
|
|
8
|
-
}): import("react").JSX.Element;
|
|
13
|
+
} & Pick<FetchQueryCacheOptions, 'environment' | 'clock' | 'scheduler'>): import("react").JSX.Element;
|
|
9
14
|
/**
|
|
10
15
|
* Without prior successful data, a disabled read hides cached errors so gesture-gated
|
|
11
16
|
* content cannot display a failure the user has not asked to retry. With prior data, retain
|
|
@@ -4,11 +4,17 @@ import { DEFAULT_STALE_TIME, FetchQueryCache, toQueryError, withCachedLoaderFetc
|
|
|
4
4
|
import { browserFetchQueryEnvironment } from './environment.browser.js';
|
|
5
5
|
import { FetchQueryBinding } from './binding.react.js';
|
|
6
6
|
const CacheContext = createContext(null);
|
|
7
|
-
/** Isolated cache scope; no external provider or query-library singleton is needed.
|
|
8
|
-
|
|
7
|
+
/** Isolated cache scope; no external provider or query-library singleton is needed.
|
|
8
|
+
* Omitted timing ports use core's real clock and native timers. Keep port identities stable:
|
|
9
|
+
* replacing a port creates a new cache and disposes the previous scope's timers/subscriptions.
|
|
10
|
+
* @param props Children and optional host environment, clock and scheduler ports.
|
|
11
|
+
* @returns The provider-scoped query binding; activates on mount and disposes on unmount.
|
|
12
|
+
* @example <FetchQueryProvider clock={clock} scheduler={scheduler}><Records /></FetchQueryProvider>
|
|
13
|
+
* @complexity O(1) setup time and space; disposal scales with cached entries. */
|
|
14
|
+
export function FetchQueryProvider({ children, environment = browserFetchQueryEnvironment, ...timing }) {
|
|
9
15
|
// A module singleton would leak cache values between independent providers/tests,
|
|
10
16
|
// producing cases that pass alone but fail together. Scope ownership to this provider.
|
|
11
|
-
const cache = useMemo(() => new FetchQueryCache({}, { environment }), [environment]);
|
|
17
|
+
const cache = useMemo(() => new FetchQueryCache({}, { environment, ...timing }), [environment, timing.clock, timing.scheduler]);
|
|
12
18
|
useEffect(() => {
|
|
13
19
|
cache.activate();
|
|
14
20
|
return () => cache.dispose();
|
|
@@ -14,4 +14,8 @@ export declare const useFetchMutation: FetchQueryAdapter['useFetchMutation'];
|
|
|
14
14
|
export declare const useInvalidate: FetchQueryAdapter['useInvalidate'];
|
|
15
15
|
/** Promise loader using the same cache as reactive reads. */
|
|
16
16
|
export declare const useCachedLoader: FetchQueryAdapter['useCachedLoader'];
|
|
17
|
+
/** Stable API identity shared by observers of the same provider, expressed as a primitive key. */
|
|
18
|
+
export declare function useFetchQueryScope(required: {
|
|
19
|
+
owner: object;
|
|
20
|
+
}, _options?: Record<string, never>): number;
|
|
17
21
|
//# sourceMappingURL=binding.react.d.ts.map
|
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext } from 'react';
|
|
2
|
+
import { createContext, useContext, useMemo, useState } from 'react';
|
|
3
|
+
/** Allocate primitive namespace keys within one cache provider; weak keys do not retain APIs. */
|
|
4
|
+
function createQueryScopes(_required, _options = {}) {
|
|
5
|
+
const scopes = new WeakMap();
|
|
6
|
+
let nextScope = 0;
|
|
7
|
+
return ({ owner }, _optional = {}) => {
|
|
8
|
+
let scope = scopes.get(owner);
|
|
9
|
+
if (scope === undefined) {
|
|
10
|
+
scope = ++nextScope;
|
|
11
|
+
scopes.set(owner, scope);
|
|
12
|
+
}
|
|
13
|
+
return scope;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
3
16
|
const AdapterContext = createContext(null);
|
|
4
17
|
/** Internal composition seam: hooks and imperative loaders always use the same provider.
|
|
5
18
|
* Keep the adapter fixed for this provider's lifetime to preserve React hook ordering. */
|
|
6
19
|
export function FetchQueryBinding({ adapter, children }) {
|
|
7
|
-
|
|
20
|
+
const [scopeFor] = useState(() => createQueryScopes({}));
|
|
21
|
+
const binding = useMemo(() => ({ adapter, scopeFor }), [adapter, scopeFor]);
|
|
22
|
+
return _jsx(AdapterContext.Provider, { value: binding, children: children });
|
|
8
23
|
}
|
|
9
24
|
/** Resolve the nearest provider; fail before attempting an unscoped read or write. */
|
|
10
|
-
function
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
25
|
+
function useBinding() {
|
|
26
|
+
const binding = useContext(AdapterContext);
|
|
27
|
+
if (!binding)
|
|
13
28
|
throw new Error('Fetch-query hooks require FetchQueryProvider');
|
|
14
|
-
return
|
|
29
|
+
return binding;
|
|
15
30
|
}
|
|
16
31
|
/** Library-free read facade; the provider selects its implementation. */
|
|
17
|
-
export const useFetchQuery = (required, optional = {}) =>
|
|
32
|
+
export const useFetchQuery = (required, optional = {}) => useBinding().adapter.useFetchQuery(required, optional);
|
|
18
33
|
/** Library-free write facade, including safe discarded promises. */
|
|
19
|
-
export const useFetchMutation = (required, optional = {}) =>
|
|
34
|
+
export const useFetchMutation = (required, optional = {}) => useBinding().adapter.useFetchMutation(required, optional);
|
|
20
35
|
/** Prefix invalidator for the nearest provider's cache. */
|
|
21
|
-
export const useInvalidate = () =>
|
|
36
|
+
export const useInvalidate = () => useBinding().adapter.useInvalidate();
|
|
22
37
|
/** Promise loader using the same cache as reactive reads. */
|
|
23
|
-
export const useCachedLoader = (required, optional = {}) =>
|
|
38
|
+
export const useCachedLoader = (required, optional = {}) => useBinding().adapter.useCachedLoader(required, optional);
|
|
39
|
+
/** Stable API identity shared by observers of the same provider, expressed as a primitive key. */
|
|
40
|
+
export function useFetchQueryScope(required, _options = {}) {
|
|
41
|
+
return useBinding().scopeFor(required);
|
|
42
|
+
}
|
|
24
43
|
//# sourceMappingURL=binding.react.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { FetchQueryProvider } from './adapter.react.js';
|
|
2
|
-
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate } from './binding.react.js';
|
|
2
|
+
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate, useFetchQueryScope } from './binding.react.js';
|
|
3
3
|
/** @deprecated Import the framework-neutral environment port from @jini-ai/core/fetch. */
|
|
4
4
|
export type { FetchQueryEnvironmentPort } from '@jini-ai/core/fetch';
|
|
5
5
|
/** @deprecated Import framework-neutral query contracts from @jini-ai/core/fetch. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { FetchQueryProvider } from './adapter.react.js';
|
|
2
|
-
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate } from './binding.react.js';
|
|
2
|
+
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate, useFetchQueryScope } from './binding.react.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Optional adapter entry. Importing the default entry never loads TanStack. */
|
|
2
2
|
export { FetchQueryProvider } from './adapter.tanstack.js';
|
|
3
|
-
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate } from './binding.react.js';
|
|
3
|
+
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate, useFetchQueryScope } from './binding.react.js';
|
|
4
4
|
/** @deprecated Import the framework-neutral environment port from @jini-ai/core/fetch. */
|
|
5
5
|
export type { FetchQueryEnvironmentPort } from '@jini-ai/core/fetch';
|
|
6
6
|
/** @deprecated Import framework-neutral query contracts from @jini-ai/core/fetch. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Optional adapter entry. Importing the default entry never loads TanStack. */
|
|
2
2
|
export { FetchQueryProvider } from './adapter.tanstack.js';
|
|
3
|
-
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate } from './binding.react.js';
|
|
3
|
+
export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate, useFetchQueryScope } from './binding.react.js';
|
|
4
4
|
//# sourceMappingURL=tanstack.js.map
|
|
@@ -3,8 +3,6 @@ export interface PrivacyTabLabels {
|
|
|
3
3
|
consentKicker?: string;
|
|
4
4
|
consentLead?: string;
|
|
5
5
|
consentFooter?: string;
|
|
6
|
-
declineLabel?: string;
|
|
7
|
-
shareLabel?: string;
|
|
8
6
|
metricsLabel?: string;
|
|
9
7
|
metricsHint?: string;
|
|
10
8
|
contentLabel?: string;
|
|
@@ -30,7 +28,7 @@ export interface PrivacyTabProps {
|
|
|
30
28
|
agentHandle?: string;
|
|
31
29
|
}
|
|
32
30
|
/**
|
|
33
|
-
* Telemetry consent card (
|
|
31
|
+
* Telemetry consent card (two per-category toggles) over a
|
|
34
32
|
* generic `{ metrics, content }` shape, plus an installation-id
|
|
35
33
|
* generate/rotate ("Delete my data") flow. Origin: `PrivacySection.tsx`,
|
|
36
34
|
* mounted by `SettingsDialog.tsx`'s `privacy` tab — flagged "likely
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { agentHandleProps, agentSubHandle } from '@jini-ai/agentic';
|
|
3
3
|
import { useT } from '../../../i18n/index.js';
|
|
4
4
|
import { Icon } from '../../../../react/components/Icon.js';
|
|
5
|
-
import { hasMadeConsentDecision,
|
|
5
|
+
import { hasMadeConsentDecision, nextStateForDeleteMyData, nextStateForTelemetryPatch, } from '../../rules.js';
|
|
6
6
|
function ToggleRow({ label, hint, checked, onChange, agentHandle }) {
|
|
7
7
|
return (_jsxs("button", { type: "button", className: `jini-toggle-row${checked ? ' on' : ''}`, onClick: () => onChange(!checked), "aria-pressed": checked, ...agentHandleProps({}, { base: agentHandle, ...({ role: 'checkbox', label }) }), children: [_jsxs("div", { className: "jini-toggle-row-text", children: [_jsx("span", { className: "jini-toggle-row-label", children: label }), _jsx("span", { className: "jini-toggle-row-hint", children: hint })] }), _jsx("span", { className: "jini-toggle-row-switch", "aria-hidden": true })] }));
|
|
8
8
|
}
|
|
@@ -11,8 +11,6 @@ function resolveConsentLabels({ labels }, { t }) {
|
|
|
11
11
|
consentKicker: labels.consentKicker ?? t('Help improve this product'),
|
|
12
12
|
consentLead: labels.consentLead ?? t('Choose what you share. You can change this anytime.'),
|
|
13
13
|
consentFooter: labels.consentFooter ?? t('Nothing is shared unless you opt in.'),
|
|
14
|
-
declineLabel: labels.declineLabel ?? t("Don't share"),
|
|
15
|
-
shareLabel: labels.shareLabel ?? t('Share usage'),
|
|
16
14
|
metricsLabel: labels.metricsLabel ?? t('Anonymous metrics'),
|
|
17
15
|
metricsHint: labels.metricsHint ?? t('Feature usage and performance data, with no personal content.'),
|
|
18
16
|
contentLabel: labels.contentLabel ?? t('Conversation and tool content'),
|
|
@@ -28,7 +26,7 @@ function resolveInstallationLabels({ labels }, { t }) {
|
|
|
28
26
|
};
|
|
29
27
|
}
|
|
30
28
|
/**
|
|
31
|
-
* Telemetry consent card (
|
|
29
|
+
* Telemetry consent card (two per-category toggles) over a
|
|
32
30
|
* generic `{ metrics, content }` shape, plus an installation-id
|
|
33
31
|
* generate/rotate ("Delete my data") flow. Origin: `PrivacySection.tsx`,
|
|
34
32
|
* mounted by `SettingsDialog.tsx`'s `privacy` tab — flagged "likely
|
|
@@ -39,10 +37,9 @@ function resolveInstallationLabels({ labels }, { t }) {
|
|
|
39
37
|
export function PrivacyTab({ state, onChange, labels, now = Date.now, agentHandle }) {
|
|
40
38
|
const t = useT();
|
|
41
39
|
const decided = hasMadeConsentDecision(state);
|
|
42
|
-
const sharing = decided ? isSharingEnabled(state.telemetry) : undefined;
|
|
43
40
|
const labelOverrides = labels ?? {};
|
|
44
|
-
const { consentKicker, consentLead, consentFooter,
|
|
41
|
+
const { consentKicker, consentLead, consentFooter, metricsLabel, metricsHint, contentLabel, contentHint, } = resolveConsentLabels({ labels: labelOverrides }, { t });
|
|
45
42
|
const { installationIdLabel, installationIdHint, optedOutLabel, deleteMyDataLabel, } = resolveInstallationLabels({ labels: labelOverrides }, { t });
|
|
46
|
-
return (_jsxs("section", { className: "jini-settings-section jini-settings-privacy", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: consentKicker }), _jsx("p", { className: "jini-hint", children: consentLead })] }) }), _jsxs("
|
|
43
|
+
return (_jsxs("section", { className: "jini-settings-section jini-settings-privacy", children: [_jsxs("div", { className: "jini-settings-subsection", children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: consentKicker }), _jsx("p", { className: "jini-hint", children: consentLead })] }) }), _jsxs("div", { className: "jini-settings-privacy-toggles", children: [_jsx(ToggleRow, { label: metricsLabel, hint: metricsHint, checked: state.telemetry.metrics === true, onChange: (v) => onChange(nextStateForTelemetryPatch(state, { metrics: v }, now())), ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'metrics-toggle' }) } : {}) }), _jsx(ToggleRow, { label: contentLabel, hint: contentHint, checked: state.telemetry.content === true, onChange: (v) => onChange(nextStateForTelemetryPatch(state, { content: v }, now())), ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'content-toggle' }) } : {}) })] }), _jsx("p", { className: "jini-hint", children: consentFooter })] }), decided ? (_jsxs("div", { className: "jini-settings-subsection", children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { children: installationIdLabel }), _jsx("p", { className: "jini-hint", children: installationIdHint })] }) }), _jsx("div", { className: "jini-settings-field", children: _jsx("input", { type: "text", readOnly: true, value: state.installationId ?? optedOutLabel, "aria-label": installationIdLabel, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'installation-id', role: 'field', label: installationIdLabel }) }) }) }), _jsxs("button", { type: "button", className: "jini-button jini-button-ghost", onClick: () => onChange(nextStateForDeleteMyData(state, now())), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'delete-my-data', role: 'button', label: deleteMyDataLabel }) }), children: [_jsx(Icon, { name: "trash", size: 13 }), _jsx("span", { children: deleteMyDataLabel })] })] })) : null] }));
|
|
47
44
|
}
|
|
48
45
|
//# sourceMappingURL=PrivacyTab.js.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ResourceBoardAlerts, ResourceBoardContent
|
|
2
|
+
import { ResourceBoardAlerts, ResourceBoardContent } from './ResourceBoardViewParts.js';
|
|
3
|
+
import { ResourceBoardToolbar } from './ResourceBoardToolbar.js';
|
|
3
4
|
/**
|
|
4
5
|
* Pure composition (props in, JSX out): the toolbar, then either the empty
|
|
5
6
|
* state, the card grid, or the kanban board — DesignsTab's own
|
|
6
7
|
* `filtered.length === 0 ? empty : view === "grid" ? grid : kanban` branch.
|
|
7
8
|
*/
|
|
8
9
|
export function ResourceBoardView(props) {
|
|
9
|
-
return (_jsxs("div", { className: `resource-board${props.viewMode === 'kanban' ? ' is-kanban' : ''}`, children: [_jsx(
|
|
10
|
+
return (_jsxs("div", { className: `resource-board${props.viewMode === 'kanban' ? ' is-kanban' : ''}`, children: [_jsx(ResourceBoardToolbar, { ...props, selectedCount: props.selected.size }), _jsx(ResourceBoardAlerts, { ...props }), _jsx(ResourceBoardContent, { ...props })] }));
|
|
10
11
|
}
|
|
11
12
|
//# sourceMappingURL=ResourceBoardView.js.map
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { ResourceBoardViewProps } from './ResourceBoardView.js';
|
|
2
|
-
/** Maps the view's props onto `ResourceBoardToolbar` (sort, search, view toggle, select-mode/bulk-delete, create CTA). */
|
|
3
|
-
export declare function ResourceBoardViewToolbar<TBody>(props: ResourceBoardViewProps<TBody>): import("react").JSX.Element;
|
|
4
2
|
/** The LOAD-failure banner and, independently, the single-item action-failure banner (shown ABOVE the still-visible item list). */
|
|
5
3
|
export declare function ResourceBoardAlerts<TBody>({ error, errorLabel, actionError, actionErrorLabel, }: Pick<ResourceBoardViewProps<TBody>, 'error' | 'errorLabel' | 'actionError' | 'actionErrorLabel'>): import("react").JSX.Element;
|
|
6
4
|
/** The card grid: one `ResourceCard` per item, with the item's id bound into each per-card callback. */
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ResourceBoardToolbar } from './ResourceBoardToolbar.js';
|
|
3
2
|
import { ResourceCard } from './ResourceCard.js';
|
|
4
3
|
import { ResourceKanbanBoard } from './ResourceKanbanBoard.js';
|
|
5
|
-
/** Maps the view's props onto `ResourceBoardToolbar` (sort, search, view toggle, select-mode/bulk-delete, create CTA). */
|
|
6
|
-
export function ResourceBoardViewToolbar(props) {
|
|
7
|
-
const { activeSort, createLabel, onCreate } = props;
|
|
8
|
-
return (_jsx(ResourceBoardToolbar, { sortOptions: props.sortOptions, ...(activeSort !== undefined ? { activeSort } : {}), onSortChange: props.onSortChange, sortAriaLabel: props.sortAriaLabel, query: props.query, onQueryChange: props.onQueryChange, searchPlaceholder: props.searchPlaceholder, viewMode: props.viewMode, onViewModeChange: props.onViewModeChange, viewToggleAriaLabel: props.viewToggleAriaLabel, gridViewLabel: props.gridViewLabel, kanbanViewLabel: props.kanbanViewLabel, enableBulkDelete: props.enableBulkDelete, selectMode: props.selectMode, selectedCount: props.selected.size, bulkDeleteBusy: props.bulkDeleteBusy, onEnterSelectMode: props.onEnterSelectMode, onExitSelectMode: props.onExitSelectMode, onBulkDelete: props.onBulkDelete, selectModeLabel: props.selectModeLabel, selectedCountLabel: props.selectedCountLabel, deleteSelectedLabel: props.deleteSelectedLabel, cancelSelectLabel: props.cancelSelectLabel, ...(createLabel !== undefined ? { createLabel } : {}), ...(onCreate !== undefined ? { onCreate } : {}) }));
|
|
9
|
-
}
|
|
10
4
|
/** The LOAD-failure banner and, independently, the single-item action-failure banner (shown ABOVE the still-visible item list). */
|
|
11
5
|
export function ResourceBoardAlerts({ error, errorLabel, actionError, actionErrorLabel, }) {
|
|
12
6
|
return (_jsxs(_Fragment, { children: [error ? (_jsx("div", { className: "resource-board-error", role: "alert", children: errorLabel ?? error })) : null, actionError ? (_jsx("div", { className: "resource-board-action-error", role: "alert", children: actionErrorLabel ?? actionError })) : null] }));
|
|
@@ -1274,16 +1274,11 @@ select.jini-input {
|
|
|
1274
1274
|
/* ============================================================
|
|
1275
1275
|
Privacy tab
|
|
1276
1276
|
============================================================
|
|
1277
|
-
`PrivacyTab.tsx`'s
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
nothing before this. Reference: `od-settings-privacy.png` — the two
|
|
1283
|
-
disclosure items read as plain title+description pairs, the two consent
|
|
1284
|
-
buttons sit side by side with the primary one filled, and the granular
|
|
1285
|
-
toggles are real switches once a consent decision exists
|
|
1286
|
-
(`hasMadeConsentDecision`). */
|
|
1277
|
+
`PrivacyTab.tsx`'s toggle switches (`jini-toggle-row*`) had NO rules
|
|
1278
|
+
anywhere in this file — not even a partial match; `grep -i
|
|
1279
|
+
"toggle|switch"` against the file returned nothing before this. The two
|
|
1280
|
+
choices show once, as these switches (the disclosure list and
|
|
1281
|
+
share/decline buttons that repeated them were removed 2026-10-26). */
|
|
1287
1282
|
|
|
1288
1283
|
/* `PrivacyTab.tsx` puts its two `jini-section-head` headings ("Help improve
|
|
1289
1284
|
this product", "Installation ID") in a bare `<h4>` with no className of
|
|
@@ -1301,66 +1296,6 @@ select.jini-input {
|
|
|
1301
1296
|
color: var(--jini-text-strong);
|
|
1302
1297
|
}
|
|
1303
1298
|
|
|
1304
|
-
.jini-settings-privacy-disclosure {
|
|
1305
|
-
margin: 0;
|
|
1306
|
-
display: flex;
|
|
1307
|
-
flex-direction: column;
|
|
1308
|
-
gap: 10px;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
.jini-settings-privacy-disclosure > div {
|
|
1312
|
-
display: flex;
|
|
1313
|
-
flex-direction: column;
|
|
1314
|
-
gap: 2px;
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
.jini-settings-privacy-disclosure dt {
|
|
1318
|
-
margin: 0;
|
|
1319
|
-
font-size: 13px;
|
|
1320
|
-
font-weight: 600;
|
|
1321
|
-
color: var(--jini-text-strong);
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
.jini-settings-privacy-disclosure dd {
|
|
1325
|
-
margin: 0;
|
|
1326
|
-
font-size: 12px;
|
|
1327
|
-
color: var(--jini-text-muted);
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
.jini-privacy-consent-actions {
|
|
1331
|
-
display: flex;
|
|
1332
|
-
gap: 10px;
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
.jini-privacy-consent-action {
|
|
1336
|
-
flex: 1 1 0;
|
|
1337
|
-
padding: 9px 16px;
|
|
1338
|
-
font: inherit;
|
|
1339
|
-
font-size: 13px;
|
|
1340
|
-
font-weight: 600;
|
|
1341
|
-
color: var(--jini-text);
|
|
1342
|
-
background: var(--jini-bg-panel);
|
|
1343
|
-
border: 1px solid var(--jini-border-strong);
|
|
1344
|
-
border-radius: var(--jini-radius-sm);
|
|
1345
|
-
cursor: pointer;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
.jini-privacy-consent-action:hover {
|
|
1349
|
-
border-color: var(--jini-text-muted);
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
.jini-privacy-consent-action--primary {
|
|
1353
|
-
color: var(--jini-accent-ink);
|
|
1354
|
-
background: var(--jini-accent);
|
|
1355
|
-
border-color: var(--jini-accent);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
.jini-privacy-consent-action--primary:hover,
|
|
1359
|
-
.jini-privacy-consent-action--primary.is-active {
|
|
1360
|
-
background: var(--jini-accent-hover);
|
|
1361
|
-
border-color: var(--jini-accent-hover);
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
1299
|
.jini-settings-privacy-toggles {
|
|
1365
1300
|
display: flex;
|
|
1366
1301
|
flex-direction: column;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
3
|
import { useT } from '../../../i18n/index.js';
|
|
4
|
+
import { SecretInput, SecretTextarea } from '../../../../react/components/SecretInput.js';
|
|
5
|
+
import { useSecretReveal as useSecretInputReveal } from '../../../../react/components/SecretInput.hooks.js';
|
|
4
6
|
import { sourceConfigAgentProps } from '../../agent-handles.js';
|
|
5
7
|
import { maskFieldValue } from '../../rules.js';
|
|
6
8
|
/**
|
|
@@ -36,10 +38,10 @@ import { maskFieldValue } from '../../rules.js';
|
|
|
36
38
|
*/
|
|
37
39
|
export function SourceConfigField({ spec, value, error, disabled = false, idPrefix = 'source-config-field', agentHandle, onChange }) {
|
|
38
40
|
const t = useT();
|
|
39
|
-
const { revealed, toggleRevealed, isLockedSecret } = useSecretReveal({ kind: spec.kind, value });
|
|
40
41
|
const inputId = `${idPrefix}-${spec.key}`;
|
|
41
42
|
const errorId = error ? `${inputId}-error` : undefined;
|
|
42
43
|
const fieldLabel = t(spec.label);
|
|
44
|
+
const { revealed, toggleRevealed, isLockedSecret } = useSecretReveal({ kind: spec.kind, value, label: fieldLabel });
|
|
43
45
|
const shared = {
|
|
44
46
|
disabled,
|
|
45
47
|
'aria-invalid': Boolean(error) || undefined,
|
|
@@ -55,15 +57,16 @@ export function SourceConfigField({ spec, value, error, disabled = false, idPref
|
|
|
55
57
|
action: 'reveal',
|
|
56
58
|
});
|
|
57
59
|
const reveal = { revealed, disabled, agentProps: revealAgentProps, onToggle: toggleRevealed };
|
|
58
|
-
return (_jsxs("
|
|
60
|
+
return (_jsxs("div", { className: "source-config-field", children: [_jsxs("label", { className: "source-config-field-label", htmlFor: inputId, children: [fieldLabel, spec.required ? (_jsx("span", { className: "source-config-field-required", "aria-label": t('required'), children: "*" })) : null] }), _jsx(SourceConfigFieldControl, { spec: spec, value: value, inputId: inputId, shared: shared, reveal: reveal, isLockedSecret: isLockedSecret }), error ? (_jsx("span", { id: errorId, className: "source-config-field-error", role: "alert", children: error })) : null] }));
|
|
59
61
|
}
|
|
60
62
|
/**
|
|
61
63
|
* The `password`/`secret-textarea` show-hide state. The toggle is small local
|
|
62
64
|
* disclosure state (per the vertical-slice guardrail allowing that in a leaf
|
|
63
65
|
* component).
|
|
64
66
|
*/
|
|
65
|
-
function useSecretReveal({ kind, value }) {
|
|
66
|
-
const
|
|
67
|
+
function useSecretReveal({ kind, value, label }, _options = {}) {
|
|
68
|
+
const t = useT();
|
|
69
|
+
const { revealed, toggle } = useSecretInputReveal({ value, labels: { show: `${t('Show')} ${label}`, hide: `${t('Hide')} ${label}` } });
|
|
67
70
|
// Whether THIS mount ever had a pre-existing secret to protect, captured
|
|
68
71
|
// once rather than recomputed from `value` on every render. A plain
|
|
69
72
|
// `Boolean(value)` gate would look right at first glance but re-locks the
|
|
@@ -87,7 +90,6 @@ function useSecretReveal({ kind, value }) {
|
|
|
87
90
|
// or from the user clearing an existing secret mid-edit to type a new one.
|
|
88
91
|
useEffect(() => {
|
|
89
92
|
if (!value) {
|
|
90
|
-
setRevealed(false);
|
|
91
93
|
hadStoredSecretRef.current = false;
|
|
92
94
|
}
|
|
93
95
|
}, [value]);
|
|
@@ -95,8 +97,8 @@ function useSecretReveal({ kind, value }) {
|
|
|
95
97
|
// secret still un-revealed. A field with nothing to protect — first-time
|
|
96
98
|
// entry, or one already cleared out — is immediately editable, no
|
|
97
99
|
// "Show"/reveal click required.
|
|
98
|
-
const isLockedSecret = kind === 'secret-textarea' && hadStoredSecretRef.current && !revealed;
|
|
99
|
-
return { revealed, toggleRevealed:
|
|
100
|
+
const isLockedSecret = kind === 'secret-textarea' && Boolean(value) && hadStoredSecretRef.current && !revealed;
|
|
101
|
+
return { revealed, toggleRevealed: toggle, isLockedSecret };
|
|
100
102
|
}
|
|
101
103
|
/** Picks the control for `spec.kind`: a select, a (secret-)textarea, a password input, or a plain text/url input. */
|
|
102
104
|
function SourceConfigFieldControl(props) {
|
|
@@ -116,25 +118,24 @@ function SelectControl({ spec, value, inputId, shared, placeholder, }) {
|
|
|
116
118
|
return (_jsxs("select", { id: inputId, value: value, ...shared, children: [_jsx("option", { value: "", disabled: true, hidden: true, children: placeholder ?? t('Select…') }), (spec.options ?? []).map((option) => (_jsx("option", { value: option.value, children: t(option.label) }, option.value)))] }));
|
|
117
119
|
}
|
|
118
120
|
function TextareaControl({ spec, value, inputId, shared, reveal, isLockedSecret, placeholder }) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// login into the first password input it finds (and the text input above it as the
|
|
131
|
-
// username). A source's credential is never the operator's own login.
|
|
132
|
-
autoComplete: "new-password", value: value, placeholder: placeholder, ...shared }), _jsx(RevealToggle, { ...reveal })] }));
|
|
121
|
+
const t = useT();
|
|
122
|
+
if (spec.kind !== 'secret-textarea') {
|
|
123
|
+
return _jsx("span", { className: "source-config-field-row", children: _jsx("textarea", { id: inputId, value: value, placeholder: placeholder, ...shared }) });
|
|
124
|
+
}
|
|
125
|
+
return (_jsx("span", { className: "source-config-field-row", children: _jsx(SecretTextarea, { id: inputId, label: t(spec.label), revealed: reveal.revealed, onRevealedChange: reveal.onToggle, toggleAttrs: reveal.agentProps, value: value, maskedValue: isLockedSecret ? maskFieldValue(spec.kind, value) : undefined, placeholder: placeholder,
|
|
126
|
+
// A masked textarea must not be typed into — the visible text is not
|
|
127
|
+
// the real value, so an edit would commit the mask characters.
|
|
128
|
+
// Revealing is the way in, exactly like the password field. Only
|
|
129
|
+
// applies when there is a stored secret to protect — see
|
|
130
|
+
// `isLockedSecret`.
|
|
131
|
+
readOnly: isLockedSecret, autoComplete: "off", ...shared }) }));
|
|
133
132
|
}
|
|
134
|
-
|
|
135
|
-
function RevealToggle({ revealed, disabled, agentProps, onToggle }) {
|
|
133
|
+
function PasswordControl({ spec, value, inputId, shared, reveal, placeholder }) {
|
|
136
134
|
const t = useT();
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
return (_jsx("span", { className: "source-config-field-row", children: _jsx(SecretInput, { id: inputId, label: t(spec.label), "aria-label": t(spec.label), revealed: reveal.revealed, onRevealedChange: reveal.onToggle, toggleAttrs: reveal.agentProps,
|
|
136
|
+
// `new-password`, NOT `off`: Chrome deliberately ignores `off` and fills a saved site
|
|
137
|
+
// login into the first password input it finds (and the text input above it as the
|
|
138
|
+
// username). A source's credential is never the operator's own login.
|
|
139
|
+
autoComplete: "new-password", value: value, placeholder: placeholder, ...shared }) }));
|
|
139
140
|
}
|
|
140
141
|
//# sourceMappingURL=SourceConfigField.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,7 @@ export * from './browser/useModalWindowDragGuard.js';
|
|
|
105
105
|
export * from './browser/index.js';
|
|
106
106
|
export { Icon, ICON_PATH_DATA, type IconName } from './react/components/Icon.js';
|
|
107
107
|
export * from './react/components/RemixIcon.js';
|
|
108
|
+
export { SecretInput, SecretTextarea, type SecretInputProps, type SecretTextareaProps } from './react/components/SecretInput.js';
|
|
108
109
|
export * from './react/components/AgentIcon.js';
|
|
109
110
|
export * from './react/components/Toast.js';
|
|
110
111
|
export * from './react/components/Loading.js';
|
package/dist/index.js
CHANGED
|
@@ -112,6 +112,7 @@ export * from './browser/index.js';
|
|
|
112
112
|
// not reach this published package's public surface.
|
|
113
113
|
export { Icon, ICON_PATH_DATA } from './react/components/Icon.js';
|
|
114
114
|
export * from './react/components/RemixIcon.js';
|
|
115
|
+
export { SecretInput, SecretTextarea } from './react/components/SecretInput.js';
|
|
115
116
|
export * from './react/components/AgentIcon.js';
|
|
116
117
|
export * from './react/components/Toast.js';
|
|
117
118
|
export * from './react/components/Loading.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, TextareaHTMLAttributes } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @file The shared masked input: a password/token/key `<input>` with an eye toggle inside its right
|
|
4
|
+
* edge. Drop-in for `<input type="password">` — every other input prop passes straight through.
|
|
5
|
+
*
|
|
6
|
+
* Same markup and classes as `@jini-ai/ui-kit`'s password `TextField` and the mcp-ui secret fields
|
|
7
|
+
* (`.jini-secret` / `.jini-secret-toggle`), so one host stylesheet styles all of them; hosts own
|
|
8
|
+
* the visual rules.
|
|
9
|
+
*
|
|
10
|
+
* Keep it OUT of a wrapping `<label>`: the toggle's "Show <label>" name would join the input's own
|
|
11
|
+
* accessible name. Pair it with a sibling `<label htmlFor>` (or `aria-label`) instead.
|
|
12
|
+
*
|
|
13
|
+
* `SecretTextarea` is the multi-line twin (a `<textarea>` cannot be `type="password"`, so it masks
|
|
14
|
+
* with `-webkit-text-security` while hidden); its wrapper adds `.jini-secret--multiline` so a host
|
|
15
|
+
* can pin the toggle to the top corner instead of the vertical middle.
|
|
16
|
+
*/
|
|
17
|
+
/** The props both secret fields share: how the eye toggle is named and whether it starts shown. */
|
|
18
|
+
interface SecretToggleProps {
|
|
19
|
+
/** The field's visible label, translated; names the toggle ("Show API key"). */
|
|
20
|
+
readonly label: string;
|
|
21
|
+
/** The toggle's full names, for a host with its own translator (chat, user-management). Default:
|
|
22
|
+
* `t('Show')`/`t('Hide')` + `label`, from this package's `I18nProvider`. Same contract as
|
|
23
|
+
* ui-kit `TextField`'s `secretToggleLabels`. */
|
|
24
|
+
readonly toggleLabels?: {
|
|
25
|
+
readonly show: string;
|
|
26
|
+
readonly hide: string;
|
|
27
|
+
};
|
|
28
|
+
/** Extra attributes for the toggle button, e.g. an agent handle. */
|
|
29
|
+
readonly toggleAttrs?: Readonly<Record<string, string | boolean | undefined>>;
|
|
30
|
+
/** Controlled mode: the host owns the shown/hidden state. */
|
|
31
|
+
readonly revealed?: boolean;
|
|
32
|
+
readonly onRevealedChange?: (revealed: boolean) => void;
|
|
33
|
+
}
|
|
34
|
+
export type SecretInputProps = SecretToggleProps & Omit<InputHTMLAttributes<HTMLInputElement>, 'type'>;
|
|
35
|
+
export type SecretTextareaProps = SecretToggleProps & TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
36
|
+
/** Safe replacement text while hidden (e.g. env names with masked values). Read-only while
|
|
37
|
+
* displayed: editing replacement characters would overwrite the actual secret. */
|
|
38
|
+
readonly maskedValue?: string | undefined;
|
|
39
|
+
};
|
|
40
|
+
export declare const SecretInput: import("react").ForwardRefExoticComponent<SecretToggleProps & Omit<InputHTMLAttributes<HTMLInputElement>, "type"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
41
|
+
export declare const SecretTextarea: import("react").ForwardRefExoticComponent<SecretToggleProps & TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
42
|
+
/** Safe replacement text while hidden (e.g. env names with masked values). Read-only while
|
|
43
|
+
* displayed: editing replacement characters would overwrite the actual secret. */
|
|
44
|
+
readonly maskedValue?: string | undefined;
|
|
45
|
+
} & import("react").RefAttributes<HTMLTextAreaElement>>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=SecretInput.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface SecretRevealRequired {
|
|
2
|
+
/** The input's current value. A transition to `''` re-hides (the usual sign a save succeeded). */
|
|
3
|
+
readonly value: unknown;
|
|
4
|
+
/** The toggle's full accessible names, translated ("Show API key" / "Hide API key"). */
|
|
5
|
+
readonly labels: {
|
|
6
|
+
readonly show: string;
|
|
7
|
+
readonly hide: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface SecretRevealOptional {
|
|
11
|
+
/** Controlled mode: the host owns the shown/hidden state (e.g. to reset it per provider). */
|
|
12
|
+
readonly revealed?: boolean;
|
|
13
|
+
readonly onRevealedChange?: (revealed: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The show/hide state behind `SecretInput`'s eye toggle. Starts hidden. Uncontrolled, it re-hides on
|
|
17
|
+
* the value's transition to empty only — an empty field may still be revealed before pasting.
|
|
18
|
+
* @complexity O(1).
|
|
19
|
+
*/
|
|
20
|
+
export declare function useSecretReveal(required: SecretRevealRequired, optional?: SecretRevealOptional): {
|
|
21
|
+
readonly revealed: boolean;
|
|
22
|
+
readonly inputType: "text" | "password";
|
|
23
|
+
readonly toggleLabel: string;
|
|
24
|
+
readonly toggle: () => void;
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=SecretInput.hooks.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The show/hide state behind `SecretInput`'s eye toggle. Starts hidden. Uncontrolled, it re-hides on
|
|
4
|
+
* the value's transition to empty only — an empty field may still be revealed before pasting.
|
|
5
|
+
* @complexity O(1).
|
|
6
|
+
*/
|
|
7
|
+
export function useSecretReveal(required, optional = {}) {
|
|
8
|
+
const [ownRevealed, setOwnRevealed] = useState(false);
|
|
9
|
+
const [lastValue, setLastValue] = useState(required.value);
|
|
10
|
+
if (required.value !== lastValue) {
|
|
11
|
+
setLastValue(required.value);
|
|
12
|
+
if (required.value === '')
|
|
13
|
+
setOwnRevealed(false);
|
|
14
|
+
}
|
|
15
|
+
const revealed = optional.revealed ?? ownRevealed;
|
|
16
|
+
const toggleLabel = revealed ? required.labels.hide : required.labels.show;
|
|
17
|
+
const toggle = () => {
|
|
18
|
+
if (optional.onRevealedChange)
|
|
19
|
+
optional.onRevealedChange(!revealed);
|
|
20
|
+
else
|
|
21
|
+
setOwnRevealed(!revealed);
|
|
22
|
+
};
|
|
23
|
+
return { revealed, inputType: revealed ? 'text' : 'password', toggleLabel, toggle };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=SecretInput.hooks.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { useT } from '../../features/i18n/index.js';
|
|
4
|
+
import { Icon } from './Icon.js';
|
|
5
|
+
import { useSecretReveal } from './SecretInput.hooks.js';
|
|
6
|
+
/** Reveal state + the eye button, shared by `SecretInput` and `SecretTextarea`. */
|
|
7
|
+
function useSecretToggle({ label, toggleLabels, toggleAttrs, revealed, onRevealedChange }, { value, disabled } = {}) {
|
|
8
|
+
const t = useT();
|
|
9
|
+
const reveal = useSecretReveal({ value, labels: toggleLabels ?? { show: `${t('Show')} ${label}`, hide: `${t('Hide')} ${label}` } }, { ...(revealed === undefined ? {} : { revealed }), ...(onRevealedChange ? { onRevealedChange } : {}) });
|
|
10
|
+
// One signal only: the name flips Show/Hide, so no `aria-pressed` — both together made screen
|
|
11
|
+
// readers announce "Hide API key, toggle button, pressed" (Opus UI review 2026-10-26).
|
|
12
|
+
const button = (_jsx("button", { ...toggleAttrs, type: "button", className: "jini-secret-toggle", "aria-label": reveal.toggleLabel, title: reveal.toggleLabel, disabled: disabled, onClick: reveal.toggle, children: _jsx(Icon, { name: reveal.revealed ? 'eye-off' : 'eye', size: 18 }) }));
|
|
13
|
+
return { reveal, button };
|
|
14
|
+
}
|
|
15
|
+
export const SecretInput = forwardRef(function SecretInput(props, ref) {
|
|
16
|
+
// The toggle props are pulled off so they never reach the DOM `<input>`.
|
|
17
|
+
const { label, toggleLabels, toggleAttrs, revealed, onRevealedChange, ...inputProps } = props;
|
|
18
|
+
const { reveal, button } = useSecretToggle(props, { value: inputProps.value, disabled: inputProps.disabled });
|
|
19
|
+
return (_jsxs("span", { className: "jini-secret", children: [_jsx("input", { ...inputProps, spellCheck: false, ref: ref, type: reveal.inputType }), button] }));
|
|
20
|
+
});
|
|
21
|
+
export const SecretTextarea = forwardRef(function SecretTextarea(props, ref) {
|
|
22
|
+
const { label, toggleLabels, toggleAttrs, revealed, onRevealedChange, style, maskedValue, ...textareaProps } = props;
|
|
23
|
+
const { reveal, button } = useSecretToggle(props, { value: textareaProps.value, disabled: textareaProps.disabled });
|
|
24
|
+
const replacementShown = !reveal.revealed && maskedValue !== undefined;
|
|
25
|
+
/* Not a standard CSS property, so it is set here rather than left to a host stylesheet: a host
|
|
26
|
+
that forgot it would show the secret in clear text. pre-wrap keeps line breaks while masked. */
|
|
27
|
+
const masked = { ...style, ...{ WebkitTextSecurity: reveal.revealed || replacementShown ? 'none' : 'disc' }, whiteSpace: 'pre-wrap' };
|
|
28
|
+
return (_jsxs("span", { className: "jini-secret jini-secret--multiline", children: [_jsx("textarea", { ...textareaProps, ...(replacementShown ? { value: maskedValue, readOnly: true } : {}), spellCheck: false, ref: ref, style: masked }), button] }));
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=SecretInput.js.map
|
package/dist/styles/lightbox.css
CHANGED
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
}
|
|
127
127
|
.jini-lightbox-button:hover:not(:disabled) { background: var(--jini-lightbox-button-bg-hover); border-color: var(--jini-lightbox-button-border-hover); }
|
|
128
128
|
.jini-lightbox-button:focus-visible { outline: 2px solid var(--jini-lightbox-ink); outline-offset: 2px; }
|
|
129
|
-
/* Same chip, only fainter: a host `button:disabled` fill (the
|
|
129
|
+
/* Same chip, only fainter: a host `button:disabled` fill (the host admin's light `--surface-3`) would
|
|
130
130
|
otherwise turn the end-of-set arrow into a bright pressed-looking disc. */
|
|
131
131
|
.jini-lightbox-button:disabled {
|
|
132
132
|
opacity: 0.3;
|