@jini-ai/ui 0.5.0 → 0.5.1
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 +7 -0
- package/dist/features/html-viewer/dependencies.d.ts +1 -1
- package/dist/features/html-viewer/dependencies.js +2 -2
- package/dist/features/html-viewer/react/hooks/useDeckNavigation.d.ts +2 -2
- package/dist/features/html-viewer/react/hooks/useDeckNavigation.js +3 -3
- package/dist/features/i18n/universal.d.ts +3 -0
- package/dist/features/i18n/universal.js +3 -0
- package/dist/features/interactive-ui/providers/shadcn/button.d.ts +1 -1
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +47 -18
- package/dist/features/settings/dialog/styles/settings-dialog.css +19 -3
- package/dist/features/tab-strip/react/components/TabBar.hooks.d.ts +16 -1
- package/dist/features/tab-strip/react/components/TabBar.hooks.js +31 -0
- package/dist/features/tab-strip/react/components/TabBar.js +3 -2
- package/package.json +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @jini-ai/ui
|
|
2
2
|
|
|
3
|
+
## 0.5.1 — 2026-10-13
|
|
4
|
+
|
|
5
|
+
- Phone layout fixes. Media provider cards put one field per line on phones and keep the fields plus Clear on one row on wider screens (was a three-column grid that squeezed inputs to a few characters). The snippet block's Copy button no longer covers the command: phones start the code below it, wider screens reserve its width. `TabBar` scrolls a sideways-scrolling strip so the active tab is fully visible (only the strip scrolls, never the page).
|
|
6
|
+
- HTML viewer imports `renderers/new-tab-preview` and `renderers/sandbox-bridge` directly instead of the renderer barrel (no Markdown renderers or srcdoc assembly in its closure).
|
|
7
|
+
- New universal `./i18n` subpath re-exporting `interpolate` from the existing template-i18n owner.
|
|
8
|
+
- TanStack fetch-query adapter: replacement writes win at TanStack's save boundary, so an older read that settles after `replace()` can no longer overwrite it (hook data, loaders and `peek()` keep their public types).
|
|
9
|
+
|
|
3
10
|
## 0.5.0 — 2026-10-13
|
|
4
11
|
|
|
5
12
|
- Add framework-neutral chart contracts, browser/table and React hosts, provider-defined kinds and legacy chart wire aliases. Extract Recharts implementations and peers into the private browser artifact; chart component export wrappers are removed (no external consumers found).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FullscreenPort, HtmlViewerDependencies, NewTabPreviewPort } from './ports.js';
|
|
2
2
|
/** Real browser Fullscreen API binding. Every method is SSR-guarded (`typeof document === 'undefined'`). */
|
|
3
3
|
export declare function createBrowserFullscreenPort(): FullscreenPort;
|
|
4
|
-
/** Reuses
|
|
4
|
+
/** Reuses `renderers/new-tab-preview.ts`, the owner of sandboxed new-tab previews, without loading the renderer barrel. */
|
|
5
5
|
export declare function createBrowserNewTabPreviewPort(): NewTabPreviewPort;
|
|
6
6
|
export declare function createDefaultHtmlViewerDependencies(): HtmlViewerDependencies;
|
|
7
7
|
export declare const defaultHtmlViewerDependencies: HtmlViewerDependencies;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { openSandboxedPreviewInNewTab } from '../../renderers/
|
|
1
|
+
import { openSandboxedPreviewInNewTab } from '../../renderers/new-tab-preview.js';
|
|
2
2
|
/** Real browser Fullscreen API binding. Every method is SSR-guarded (`typeof document === 'undefined'`). */
|
|
3
3
|
export function createBrowserFullscreenPort() {
|
|
4
4
|
return {
|
|
@@ -28,7 +28,7 @@ export function createBrowserFullscreenPort() {
|
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
/** Reuses
|
|
31
|
+
/** Reuses `renderers/new-tab-preview.ts`, the owner of sandboxed new-tab previews, without loading the renderer barrel. */
|
|
32
32
|
export function createBrowserNewTabPreviewPort() {
|
|
33
33
|
return {
|
|
34
34
|
openInNewTab(html, title) {
|
|
@@ -14,8 +14,8 @@ export interface DeckNavigationController {
|
|
|
14
14
|
goTo(index: number): void;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Drives deck/slide navigation over
|
|
18
|
-
*
|
|
17
|
+
* Drives deck/slide navigation over `renderers/sandbox-bridge.ts`'s
|
|
18
|
+
* `useSandboxBridge`, speaking the neutral `jini:deck-*` protocol from
|
|
19
19
|
* `constants.ts`. The sandboxed content itself must implement the deck
|
|
20
20
|
* framework that responds to these messages — this hook only sends
|
|
21
21
|
* navigation requests and tracks the state the content reports back.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
-
import { useSandboxBridge } from '../../../../renderers/
|
|
2
|
+
import { useSandboxBridge } from '../../../../renderers/sandbox-bridge.js';
|
|
3
3
|
import { DECK_NAVIGATE_MESSAGE_TYPE, DECK_STATE_MESSAGE_TYPE } from '../../constants.js';
|
|
4
4
|
import { canGoNext, canGoPrev, parseDeckStateMessage, slideCounterLabel } from '../../rules.js';
|
|
5
5
|
/**
|
|
6
|
-
* Drives deck/slide navigation over
|
|
7
|
-
*
|
|
6
|
+
* Drives deck/slide navigation over `renderers/sandbox-bridge.ts`'s
|
|
7
|
+
* `useSandboxBridge`, speaking the neutral `jini:deck-*` protocol from
|
|
8
8
|
* `constants.ts`. The sandboxed content itself must implement the deck
|
|
9
9
|
* framework that responds to these messages — this hook only sends
|
|
10
10
|
* navigation requests and tracks the state the content reports back.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import * as React from 'react';
|
|
8
8
|
import { type VariantProps } from 'class-variance-authority';
|
|
9
9
|
declare const buttonVariants: (props?: ({
|
|
10
|
-
variant?: "link" | "default" | "
|
|
10
|
+
variant?: "link" | "default" | "ghost" | "outline" | "destructive" | "secondary" | null | undefined;
|
|
11
11
|
size?: "default" | "icon" | "sm" | "lg" | null | undefined;
|
|
12
12
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
13
13
|
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
/** Optional TanStack translation layer. No library types escape the fetch-query contract. */
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from 'react';
|
|
4
|
-
import { isCancelledError, QueryClient, QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query';
|
|
4
|
+
import { isCancelledError, replaceEqualDeep, QueryClient, QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query';
|
|
5
5
|
import { DEFAULT_STALE_TIME, FetchQueryCache, IDLE_RETENTION, toQueryError } from '@jini-ai/core/fetch';
|
|
6
6
|
import { browserFetchQueryEnvironment } from './environment.browser.js';
|
|
7
7
|
import { resolveFetchQueryError, resolveFetchQueryStatus } from './adapter.react.js';
|
|
@@ -64,8 +64,8 @@ function historyFor({ scope, query }) {
|
|
|
64
64
|
}
|
|
65
65
|
return history;
|
|
66
66
|
}
|
|
67
|
-
/**
|
|
68
|
-
*
|
|
67
|
+
/** Tag transport outcomes; final arbitration belongs to TanStack's synchronous save boundary.
|
|
68
|
+
* Weak query keys follow TanStack's own eviction lifecycle. */
|
|
69
69
|
async function read({ scope, key, fetch }) {
|
|
70
70
|
const query = scope.client.getQueryCache().find({ queryKey: key, exact: true });
|
|
71
71
|
const history = historyFor({ scope, query });
|
|
@@ -77,21 +77,36 @@ async function read({ scope, key, fetch }) {
|
|
|
77
77
|
return scope.client.getQueryData(key);
|
|
78
78
|
if (value === undefined)
|
|
79
79
|
throw new Error(`${JSON.stringify(key)} data is undefined`);
|
|
80
|
-
|
|
81
|
-
history.error = null;
|
|
82
|
-
return value;
|
|
80
|
+
return { generation, request, value };
|
|
83
81
|
}
|
|
84
82
|
catch (error) {
|
|
85
83
|
if (generation !== history.replacement)
|
|
86
84
|
return scope.client.getQueryData(key);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
return { generation, request, error };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Structural sharing runs inside setData, immediately before publication. A check in read()
|
|
89
|
+
* alone leaves a promise-continuation gap, even when the retryer has already resolved.
|
|
90
|
+
* Current failures throw here; stale failures resolve to the newer replacement like stale data.
|
|
91
|
+
* @returns The accepted envelope, reusing TanStack's normal deep structural sharing.
|
|
92
|
+
*/
|
|
93
|
+
function saveResult({ scope, key, previous, incoming }) {
|
|
94
|
+
const query = scope.client.getQueryCache().find({ queryKey: key, exact: true });
|
|
95
|
+
const history = historyFor({ scope, query });
|
|
96
|
+
const result = incoming;
|
|
97
|
+
if (result.generation !== history.replacement)
|
|
98
|
+
return scope.client.getQueryData(key);
|
|
99
|
+
if ('error' in result) {
|
|
100
|
+
// Earlier data stays useful, but a failed refresh is not a fresh cache hit.
|
|
101
|
+
if (result.request === history.request) {
|
|
102
|
+
history.error = toQueryError(result.error);
|
|
91
103
|
query.invalidate();
|
|
92
104
|
}
|
|
93
|
-
throw error;
|
|
105
|
+
throw result.error;
|
|
94
106
|
}
|
|
107
|
+
if (result.request === history.request)
|
|
108
|
+
history.error = null;
|
|
109
|
+
return replaceEqualDeep(previous, result);
|
|
95
110
|
}
|
|
96
111
|
/** Match the built-in cache's idle retention. TanStack's default gcTime (5 min) would
|
|
97
112
|
* otherwise evict an unobserved entry before a longer staleTime says it is stale. */
|
|
@@ -102,7 +117,8 @@ function gcTimeFor({ staleTime }) {
|
|
|
102
117
|
function useFetchQuery({ key, fetch }, { enabled = true, staleTime = DEFAULT_STALE_TIME, refetchOnWindowFocus = false } = {}) {
|
|
103
118
|
const scope = useScope();
|
|
104
119
|
const online = useOnline(scope);
|
|
105
|
-
const query = useQuery({ queryKey: key, queryFn: () => read({ scope, key, fetch }),
|
|
120
|
+
const query = useQuery({ queryKey: key, queryFn: () => read({ scope, key, fetch }),
|
|
121
|
+
structuralSharing: (previous, incoming) => saveResult({ scope, key, previous, incoming }), enabled: enabled && online, staleTime, gcTime: gcTimeFor({ staleTime }) }, scope.client);
|
|
106
122
|
const { refetch: refresh } = query;
|
|
107
123
|
const refetch = useCallback(() => {
|
|
108
124
|
if (scope.environment.isOnline()) {
|
|
@@ -127,9 +143,10 @@ function useFetchQuery({ key, fetch }, { enabled = true, staleTime = DEFAULT_STA
|
|
|
127
143
|
// recovery, so a retry is never mistaken for evidence that the last failure is gone.
|
|
128
144
|
const cached = scope.client.getQueryCache().find({ queryKey: key, exact: true });
|
|
129
145
|
const history = historyFor({ scope, query: cached });
|
|
130
|
-
const
|
|
146
|
+
const data = query.data && 'value' in query.data ? query.data.value : undefined;
|
|
147
|
+
const hasData = data !== undefined;
|
|
131
148
|
return {
|
|
132
|
-
data
|
|
149
|
+
data,
|
|
133
150
|
status: resolveFetchQueryStatus({ disabled: !enabled, hasData, status: query.status }),
|
|
134
151
|
error: resolveFetchQueryError({ error: history.error ?? query.error, disabled: !enabled, hasData }),
|
|
135
152
|
isFetching: online && query.isFetching, refetch,
|
|
@@ -161,11 +178,13 @@ function useFetchMutation({ run }, { invalidates } = {}) {
|
|
|
161
178
|
function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}) {
|
|
162
179
|
const scope = useScope();
|
|
163
180
|
return useMemo(() => {
|
|
164
|
-
const options = { queryKey: key, queryFn: () => read({ scope, key, fetch }),
|
|
181
|
+
const options = { queryKey: key, queryFn: () => read({ scope, key, fetch }),
|
|
182
|
+
structuralSharing: (previous, incoming) => saveResult({ scope, key, previous, incoming }), staleTime, gcTime: gcTimeFor({ staleTime }) };
|
|
165
183
|
return {
|
|
166
|
-
peek: () => scope.client.getQueryData(key),
|
|
184
|
+
peek: () => scope.client.getQueryData(key)?.value,
|
|
167
185
|
load: (required = { key, fetch }) => {
|
|
168
|
-
const selected = { ...options, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch })
|
|
186
|
+
const selected = { ...options, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch }),
|
|
187
|
+
structuralSharing: (previous, incoming) => saveResult({ scope, key: required.key, previous, incoming }) };
|
|
169
188
|
return scope.client.fetchQuery(selected).catch(error => {
|
|
170
189
|
// A forced refresh can supersede a loader's request without aborting its transport.
|
|
171
190
|
// Follow the new request instead of exposing TanStack's cancellation vocabulary.
|
|
@@ -177,6 +196,16 @@ function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}
|
|
|
177
196
|
if (query.state.isInvalidated)
|
|
178
197
|
return scope.client.fetchQuery(selected);
|
|
179
198
|
return (query.promise ?? scope.client.getQueryData(required.key));
|
|
199
|
+
}).then(result => {
|
|
200
|
+
const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
|
|
201
|
+
const history = historyFor({ scope, query });
|
|
202
|
+
// Query.fetch returns the transport envelope, not structuralSharing's accepted value.
|
|
203
|
+
// Unwrap against the current generation so awaiting loaders agree with observers.
|
|
204
|
+
if (result.generation !== history.replacement)
|
|
205
|
+
return scope.client.getQueryData(required.key).value;
|
|
206
|
+
if ('error' in result)
|
|
207
|
+
throw result.error;
|
|
208
|
+
return result.value;
|
|
180
209
|
});
|
|
181
210
|
},
|
|
182
211
|
replace: ({ value }) => {
|
|
@@ -184,7 +213,7 @@ function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}
|
|
|
184
213
|
const history = historyFor({ scope, query });
|
|
185
214
|
history.replacement += 1;
|
|
186
215
|
history.error = null;
|
|
187
|
-
scope.client.setQueryData(key, value);
|
|
216
|
+
scope.client.setQueryData(key, { generation: history.replacement, request: history.request, value });
|
|
188
217
|
},
|
|
189
218
|
};
|
|
190
219
|
}, [scope, key, fetch, staleTime]);
|
|
@@ -1718,9 +1718,11 @@ select.jini-input {
|
|
|
1718
1718
|
position: relative;
|
|
1719
1719
|
}
|
|
1720
1720
|
|
|
1721
|
+
/* The right padding keeps the absolutely positioned copy button off the wrapped code; on a phone
|
|
1722
|
+
that much side padding would halve the line, so the code starts below the button instead. */
|
|
1721
1723
|
.jini-snippet-pre {
|
|
1722
1724
|
margin: 0;
|
|
1723
|
-
padding: 14px 16px;
|
|
1725
|
+
padding: 14px 104px 14px 16px;
|
|
1724
1726
|
overflow-x: auto;
|
|
1725
1727
|
background: var(--jini-text-strong);
|
|
1726
1728
|
border-radius: var(--jini-radius);
|
|
@@ -1748,6 +1750,12 @@ select.jini-input {
|
|
|
1748
1750
|
background: color-mix(in srgb, var(--jini-text-strong) 75%, transparent);
|
|
1749
1751
|
}
|
|
1750
1752
|
|
|
1753
|
+
@media (max-width: 640px) {
|
|
1754
|
+
.jini-snippet-pre {
|
|
1755
|
+
padding: 56px 16px 14px;
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1751
1759
|
/* ============================================================
|
|
1752
1760
|
Connectors tab — PORTED from OD's real source, not authored by eye.
|
|
1753
1761
|
Sources: `foundry/integrations/open-design/reference/od-web-src.orig/
|
|
@@ -3585,13 +3593,21 @@ button.connector-action.is-loading { cursor: wait; }
|
|
|
3585
3593
|
flex-wrap: wrap;
|
|
3586
3594
|
}
|
|
3587
3595
|
|
|
3596
|
+
/* A wrapping row, not a fixed column grid: a card holds two or three fields (the model field only
|
|
3597
|
+
when the provider lists models) plus Clear, and the old three-column grid squeezed each input to
|
|
3598
|
+
a few characters on a phone and pushed Clear onto its own full-column row everywhere. Each field
|
|
3599
|
+
asks for 11rem, so a phone gets one field per line, wider screens fit the whole row, and the cap
|
|
3600
|
+
keeps a desktop field from stretching across the card. */
|
|
3588
3601
|
.jini-media-provider-fields {
|
|
3589
|
-
display:
|
|
3590
|
-
|
|
3602
|
+
display: flex;
|
|
3603
|
+
flex-wrap: wrap;
|
|
3604
|
+
align-items: center;
|
|
3591
3605
|
gap: 6px;
|
|
3592
3606
|
}
|
|
3593
3607
|
|
|
3594
3608
|
.jini-media-provider-field {
|
|
3609
|
+
flex: 1 1 11rem;
|
|
3610
|
+
max-width: 28rem;
|
|
3595
3611
|
min-width: 0;
|
|
3596
3612
|
}
|
|
3597
3613
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type KeyboardEvent as ReactKeyboardEvent, type RefObject } from "react";
|
|
2
2
|
import type { TabBarTab } from "./TabBar.js";
|
|
3
3
|
/**
|
|
4
4
|
* @file TabBar's derived props and WAI-ARIA automatic-activation keyboard behavior.
|
|
@@ -74,4 +74,19 @@ export declare function useTabBarKeyboard({ tabs, activeId, onChange }: {
|
|
|
74
74
|
activeId: string;
|
|
75
75
|
onChange: (id: string) => void;
|
|
76
76
|
}, _optional?: Record<string, never>): TabBarKeyboardHandlers;
|
|
77
|
+
/**
|
|
78
|
+
* Keeps the selected tab visible when the strip scrolls sideways — a host may turn the wrapping
|
|
79
|
+
* row into one swipeable row on phones, and a deep link to a later tab (`?tab=…`) otherwise opens
|
|
80
|
+
* with its own label cut off at the edge. Only the strip's own `scrollLeft` moves: `scrollIntoView`
|
|
81
|
+
* would also scroll the page vertically to the strip, a jump on every tab change below the fold.
|
|
82
|
+
* A strip that wraps (no horizontal overflow) is left alone.
|
|
83
|
+
*
|
|
84
|
+
* @param input.activeId - The currently active tab's id; the strip re-checks when it changes.
|
|
85
|
+
* @returns A ref for the tablist container.
|
|
86
|
+
* @complexity O(n) per active-id change in the strip's tab count (one selector query).
|
|
87
|
+
* @example const stripRef = useActiveTabInView({ activeId });
|
|
88
|
+
*/
|
|
89
|
+
export declare function useActiveTabInView({ activeId }: {
|
|
90
|
+
activeId: string;
|
|
91
|
+
}, _optional?: Record<string, never>): RefObject<HTMLDivElement | null>;
|
|
77
92
|
//# sourceMappingURL=TabBar.hooks.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
1
2
|
import { agentHandle } from "@jini-ai/agentic";
|
|
2
3
|
/**
|
|
3
4
|
* @file TabBar's derived props and WAI-ARIA automatic-activation keyboard behavior.
|
|
@@ -112,4 +113,34 @@ export function useTabBarKeyboard({ tabs, activeId, onChange }, _optional = {})
|
|
|
112
113
|
}
|
|
113
114
|
return { onKeyDown };
|
|
114
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Keeps the selected tab visible when the strip scrolls sideways — a host may turn the wrapping
|
|
118
|
+
* row into one swipeable row on phones, and a deep link to a later tab (`?tab=…`) otherwise opens
|
|
119
|
+
* with its own label cut off at the edge. Only the strip's own `scrollLeft` moves: `scrollIntoView`
|
|
120
|
+
* would also scroll the page vertically to the strip, a jump on every tab change below the fold.
|
|
121
|
+
* A strip that wraps (no horizontal overflow) is left alone.
|
|
122
|
+
*
|
|
123
|
+
* @param input.activeId - The currently active tab's id; the strip re-checks when it changes.
|
|
124
|
+
* @returns A ref for the tablist container.
|
|
125
|
+
* @complexity O(n) per active-id change in the strip's tab count (one selector query).
|
|
126
|
+
* @example const stripRef = useActiveTabInView({ activeId });
|
|
127
|
+
*/
|
|
128
|
+
export function useActiveTabInView({ activeId }, _optional = {}) {
|
|
129
|
+
const stripRef = useRef(null);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const strip = stripRef.current;
|
|
132
|
+
const tab = strip?.querySelector('[role="tab"][aria-selected="true"]');
|
|
133
|
+
if (!strip || !tab || strip.scrollWidth <= strip.clientWidth)
|
|
134
|
+
return;
|
|
135
|
+
const stripBox = strip.getBoundingClientRect();
|
|
136
|
+
const tabBox = tab.getBoundingClientRect();
|
|
137
|
+
// A host's end padding (e.g. room for an edge fade) counts as off-screen.
|
|
138
|
+
const visibleRight = stripBox.right - (parseFloat(getComputedStyle(strip).paddingRight) || 0);
|
|
139
|
+
if (tabBox.left < stripBox.left)
|
|
140
|
+
strip.scrollLeft += tabBox.left - stripBox.left;
|
|
141
|
+
else if (tabBox.right > visibleRight)
|
|
142
|
+
strip.scrollLeft += tabBox.right - visibleRight;
|
|
143
|
+
}, [activeId]);
|
|
144
|
+
return stripRef;
|
|
145
|
+
}
|
|
115
146
|
//# sourceMappingURL=TabBar.hooks.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { TabStripFrame } from "./TabStripFrame.js";
|
|
3
3
|
import { agentHandle } from "@jini-ai/agentic";
|
|
4
|
-
import { resolveTabBarTabIndex, tabHandleProps, useTabBarKeyboard } from "./TabBar.hooks.js";
|
|
4
|
+
import { resolveTabBarTabIndex, tabHandleProps, useActiveTabInView, useTabBarKeyboard } from "./TabBar.hooks.js";
|
|
5
5
|
/** The visually-hidden accessible-name SUFFIX {@link TabBarTab.dot} adds — e.g. "GitHub Pages,
|
|
6
6
|
* Connected", never "Connected GitHub Pages". Deliberately its own function rather than folded into
|
|
7
7
|
* the visual dot span rendered before the label (`TabBarButton` below): the visual dot is
|
|
@@ -24,6 +24,7 @@ function TabBarButton({ tab, active, tabIndex, onChange, }) {
|
|
|
24
24
|
/** Controlled button-based selection strip, including roving focus and optional agent handles. */
|
|
25
25
|
export function TabBar({ tabs, activeId, onChange, ariaLabel, containerHandle }, _optional = {}) {
|
|
26
26
|
const { onKeyDown } = useTabBarKeyboard({ tabs: tabs, activeId: activeId, onChange: onChange });
|
|
27
|
-
|
|
27
|
+
const stripRef = useActiveTabInView({ activeId: activeId });
|
|
28
|
+
return (_jsx(TabStripFrame, { ref: stripRef, className: "tab-bar", role: "tablist", "aria-label": ariaLabel, onKeyDown: onKeyDown, ...(containerHandle ? agentHandle({ handle: containerHandle }, { role: "region", label: ariaLabel }) : {}), children: tabs.map((tab) => (_jsx(TabBarButton, { tab: tab, active: activeId === tab.id, tabIndex: resolveTabBarTabIndex({ tabs: tabs, activeId: activeId, tab: tab }), onChange: onChange }, tab.id))) }));
|
|
28
29
|
}
|
|
29
30
|
//# sourceMappingURL=TabBar.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/ui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Generic, product-neutral React UI primitives and feature domains. Framework-free types, decision rules, catalogs, and host port contracts live behind ./core (no React, no DOM). The Excalidraw and Lexical editors live behind ./sketch-editor and ./lexical-rich-text-editor; the GrapesJS-backed inline HTML editor lives behind ./html-editor.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"entries": {
|
|
16
16
|
".": "browser",
|
|
17
17
|
"./core": "universal",
|
|
18
|
+
"./i18n": "universal",
|
|
18
19
|
"./sketch-editor": "browser",
|
|
19
20
|
"./lexical-rich-text-editor": "browser",
|
|
20
21
|
"./html-editor": "browser",
|
|
@@ -135,6 +136,11 @@
|
|
|
135
136
|
"import": "./dist/features/admin-widgets/index.js",
|
|
136
137
|
"default": "./dist/features/admin-widgets/index.js"
|
|
137
138
|
},
|
|
139
|
+
"./i18n": {
|
|
140
|
+
"types": "./dist/features/i18n/universal.d.ts",
|
|
141
|
+
"import": "./dist/features/i18n/universal.js",
|
|
142
|
+
"default": "./dist/features/i18n/universal.js"
|
|
143
|
+
},
|
|
138
144
|
"./panel-kit": {
|
|
139
145
|
"types": "./dist/features/panel-kit/index.d.ts",
|
|
140
146
|
"import": "./dist/features/panel-kit/index.js",
|
|
@@ -227,13 +233,13 @@
|
|
|
227
233
|
"shiki": "^1.24.4",
|
|
228
234
|
"tailwind-merge": "^2.6.0",
|
|
229
235
|
"zod": "^3.25.76",
|
|
230
|
-
"@jini-ai/integrations": "^0.4.2",
|
|
231
236
|
"@jini-ai/platform": "^0.5.6",
|
|
232
237
|
"@jini-ai/agentic": "^0.4.4",
|
|
233
|
-
"@jini-ai/core": "^0.4.
|
|
238
|
+
"@jini-ai/core": "^0.4.5",
|
|
239
|
+
"@jini-ai/integrations": "^0.4.2"
|
|
234
240
|
},
|
|
235
241
|
"dependencies": {
|
|
236
|
-
"@jini-ai/core": "^0.4.
|
|
242
|
+
"@jini-ai/core": "^0.4.5"
|
|
237
243
|
},
|
|
238
244
|
"peerDependencies": {
|
|
239
245
|
"@jini-ai/integrations": "^0.4.2",
|