@jini-ai/ui 0.5.2 → 0.5.4

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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @jini-ai/ui
2
2
 
3
+ ## 0.5.4 — 2026-10-15
4
+
5
+ - `TabBar` re-scrolls the active tab into view once web fonts are ready (06931dfe): fonts that widen tabs after the first measurement no longer leave a deep-linked tab cut off at the strip edge.
6
+
7
+ ## 0.5.3 — 2026-10-14
8
+
9
+ - New `./preview-modal-shell` subpath exporting `PreviewModalShell` without the whole `./renderers` barrel (Markdown, artifact views, annotation canvas); it still includes its sandboxed HTML renderer and parser (ec060014).
10
+
3
11
  ## 0.5.2 — 2026-10-13
4
12
 
5
13
  - MCP-UI surfaces follow the host's live theme (da019dde): `McpUiHost` reads the host's resolved `--jini-*` theme where a card mounts and writes it into Jini-built surface documents as a `:root` rule after the token block; a MutationObserver re-themes cards already on screen when the theme or accent changes. Third-party Views are not rewritten. Builder token overrides render as `:root:root`, so a card that sets its own tokens keeps them. New `--jini-mcpui-accent-ink` token (host `--jini-primary-ink`) for primary button text.
package/README.md CHANGED
@@ -89,6 +89,10 @@ package's own React layer. See the chat-core/chat-react split discussion in
89
89
  i18n/observability/utils porting task (2026-07-16); see
90
90
  the archived provenance ledger.
91
91
 
92
+ `PreviewModalShell` has its own entry, `@jini-ai/ui/preview-modal-shell`, so a modal does not load
93
+ the whole `./renderers` barrel (Markdown, artifact views, annotation canvas). It still includes its
94
+ sandboxed HTML renderer and parser. Framework-free execution/provider rules come from `./core`.
95
+
92
96
  ## `./core` — the framework-free half, importable without React
93
97
 
94
98
  **`@jini-ai/ui/core` is not a separate concern, just a separate entry point.** It re-exports
@@ -79,7 +79,8 @@ export declare function useTabBarKeyboard({ tabs, activeId, onChange }: {
79
79
  * row into one swipeable row on phones, and a deep link to a later tab (`?tab=…`) otherwise opens
80
80
  * with its own label cut off at the edge. Only the strip's own `scrollLeft` moves: `scrollIntoView`
81
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.
82
+ * A strip that wraps (no horizontal overflow) is left alone. Re-checks once fonts are ready,
83
+ * since web fonts can change tab widths after the initial measurement.
83
84
  *
84
85
  * @param input.activeId - The currently active tab's id; the strip re-checks when it changes.
85
86
  * @returns A ref for the tablist container.
@@ -118,7 +118,8 @@ export function useTabBarKeyboard({ tabs, activeId, onChange }, _optional = {})
118
118
  * row into one swipeable row on phones, and a deep link to a later tab (`?tab=…`) otherwise opens
119
119
  * with its own label cut off at the edge. Only the strip's own `scrollLeft` moves: `scrollIntoView`
120
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.
121
+ * A strip that wraps (no horizontal overflow) is left alone. Re-checks once fonts are ready,
122
+ * since web fonts can change tab widths after the initial measurement.
122
123
  *
123
124
  * @param input.activeId - The currently active tab's id; the strip re-checks when it changes.
124
125
  * @returns A ref for the tablist container.
@@ -128,18 +129,25 @@ export function useTabBarKeyboard({ tabs, activeId, onChange }, _optional = {})
128
129
  export function useActiveTabInView({ activeId }, _optional = {}) {
129
130
  const stripRef = useRef(null);
130
131
  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;
132
+ function scrollActiveTabIntoView() {
133
+ const strip = stripRef.current;
134
+ const tab = strip?.querySelector('[role="tab"][aria-selected="true"]');
135
+ if (!strip || !tab || strip.scrollWidth <= strip.clientWidth)
136
+ return;
137
+ const stripBox = strip.getBoundingClientRect();
138
+ const tabBox = tab.getBoundingClientRect();
139
+ // A host's end padding (e.g. room for an edge fade) counts as off-screen.
140
+ const visibleRight = stripBox.right - (parseFloat(getComputedStyle(strip).paddingRight) || 0);
141
+ if (tabBox.left < stripBox.left)
142
+ strip.scrollLeft += tabBox.left - stripBox.left;
143
+ else if (tabBox.right > visibleRight)
144
+ strip.scrollLeft += tabBox.right - visibleRight;
145
+ }
146
+ scrollActiveTabIntoView();
147
+ let cancelled = false;
148
+ void document.fonts?.ready.then(() => { if (!cancelled)
149
+ scrollActiveTabIntoView(); });
150
+ return () => { cancelled = true; };
143
151
  }, [activeId]);
144
152
  return stripRef;
145
153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/ui",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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": {
@@ -26,6 +26,7 @@
26
26
  "./interactive-ui/manifests": "universal",
27
27
  "./a2ui": "browser",
28
28
  "./renderers": "browser",
29
+ "./preview-modal-shell": "browser",
29
30
  "./interactive-ui.css": "browser",
30
31
  "./settings-dialog.css": "browser",
31
32
  "./tabbed-dialog.css": "browser",
@@ -45,7 +46,8 @@
45
46
  "isolation": {
46
47
  "entries": {
47
48
  "./fetch-query": "panel-kit",
48
- "./fetch-query/tanstack": "panel-kit"
49
+ "./fetch-query/tanstack": "panel-kit",
50
+ "./preview-modal-shell": "renderers"
49
51
  },
50
52
  "shared": [
51
53
  "src/features/charts",
@@ -130,6 +132,11 @@
130
132
  "import": "./dist/renderers/index.js",
131
133
  "default": "./dist/renderers/index.js"
132
134
  },
135
+ "./preview-modal-shell": {
136
+ "types": "./dist/renderers/preview-modal-shell/index.d.ts",
137
+ "import": "./dist/renderers/preview-modal-shell/index.js",
138
+ "default": "./dist/renderers/preview-modal-shell/index.js"
139
+ },
133
140
  "./interactive-ui.css": "./dist/features/interactive-ui/interactive-ui.css",
134
141
  "./settings-dialog.css": "./dist/features/settings/dialog/styles/settings-dialog.css",
135
142
  "./tabbed-dialog.css": "./dist/features/tabbed-dialog/styles/tabbed-dialog.css",
@@ -236,13 +243,13 @@
236
243
  "shiki": "^1.24.4",
237
244
  "tailwind-merge": "^2.6.0",
238
245
  "zod": "^3.25.76",
239
- "@jini-ai/core": "^0.4.5",
240
- "@jini-ai/platform": "^0.5.6",
241
246
  "@jini-ai/integrations": "^0.4.2",
247
+ "@jini-ai/platform": "^0.5.8",
248
+ "@jini-ai/core": "^0.4.6",
242
249
  "@jini-ai/agentic": "^0.4.4"
243
250
  },
244
251
  "dependencies": {
245
- "@jini-ai/core": "^0.4.5"
252
+ "@jini-ai/core": "^0.4.6"
246
253
  },
247
254
  "peerDependencies": {
248
255
  "@jini-ai/integrations": "^0.4.2",