@jini-ai/ui 0.6.17 → 0.6.22
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 +15 -0
- package/dist/features/charts/catalog-manifests.d.ts +25 -0
- package/dist/features/charts/catalog-manifests.js +73 -0
- package/dist/features/charts/index.d.ts +2 -1
- package/dist/features/charts/index.js +2 -1
- package/dist/features/charts/react.d.ts +10 -6
- package/dist/features/charts/react.js +40 -33
- package/dist/features/charts/wire.d.ts +733 -0
- package/dist/features/charts/wire.js +37 -0
- package/dist/features/interactive-ui/index.js +4 -1
- package/dist/features/lexical-rich-text-editor/react/components/RichTextInput.js +2 -1
- package/dist/features/mcp-ui/surfaces/outcome.d.ts +7 -1
- package/dist/features/mcp-ui/surfaces/outcome.js +47 -8
- package/dist/features/panel-kit/ErrorNotice.d.ts +18 -0
- package/dist/features/panel-kit/ErrorNotice.js +17 -0
- package/dist/features/panel-kit/index.d.ts +2 -0
- package/dist/features/panel-kit/index.js +1 -0
- package/dist/features/sketch-editor/react/hooks/useSketchSaveWorkflow.js +2 -1
- package/dist/react/components/TooltipLayer.js +27 -2
- package/package.json +10 -10
|
@@ -25,6 +25,43 @@ export const rechartsPieChartManifest = {
|
|
|
25
25
|
propsSchema: rechartsPieChartPropsSchema, description: 'Pie chart: named, weighted slices. Uses an enabled chart provider or a styled data table.',
|
|
26
26
|
};
|
|
27
27
|
export const chartWireManifests = [rechartsBarChartManifest, rechartsLineChartManifest, rechartsPieChartManifest];
|
|
28
|
+
/** A series point is a finite number, or null for a gap (a missing month on a line). */
|
|
29
|
+
function isSeriesValue(value) {
|
|
30
|
+
return value === null || (typeof value === 'number' && Number.isFinite(value));
|
|
31
|
+
}
|
|
32
|
+
function isCategoryValue(value) {
|
|
33
|
+
return typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));
|
|
34
|
+
}
|
|
35
|
+
/** Report the first bad row per key only, so a large table of strings yields one issue, not thousands. */
|
|
36
|
+
function checkColumn({ rows, key, accepts, expected, ctx }, _options = {}) {
|
|
37
|
+
const index = rows.findIndex((row) => !Object.hasOwn(row, key) || !accepts(row[key]));
|
|
38
|
+
if (index < 0)
|
|
39
|
+
return;
|
|
40
|
+
const problem = Object.hasOwn(rows[index], key) ? `is not ${expected}` : 'is missing';
|
|
41
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['data', index, key], message: `Row ${index} "${key}" ${problem}; every row needs it.` });
|
|
42
|
+
}
|
|
43
|
+
/** Rows are the shared x axis: every row carries every series key, so series lengths always match. */
|
|
44
|
+
function checkSeriesShape(spec, ctx) {
|
|
45
|
+
const keys = spec.series.map(({ key }) => key);
|
|
46
|
+
const duplicate = keys.find((key, index) => keys.indexOf(key) !== index);
|
|
47
|
+
if (duplicate !== undefined) {
|
|
48
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['series'], message: `Series key "${duplicate}" is listed twice.` });
|
|
49
|
+
}
|
|
50
|
+
if (spec.categoryKey !== undefined) {
|
|
51
|
+
checkColumn({ rows: spec.data, key: spec.categoryKey, accepts: isCategoryValue, expected: 'a string or number', ctx });
|
|
52
|
+
}
|
|
53
|
+
for (const key of new Set(keys))
|
|
54
|
+
checkColumn({ rows: spec.data, key, accepts: isSeriesValue, expected: 'a number or null', ctx });
|
|
55
|
+
}
|
|
56
|
+
/** `chart.spec` props: one neutral spec, any kind, one or more series. Stricter than the contract
|
|
57
|
+
* (which also carries converted legacy data) because an agent writes this shape directly.
|
|
58
|
+
*/
|
|
59
|
+
export const chartSpecPropsSchema = z.object({
|
|
60
|
+
spec: chartSpecSchema.extend({
|
|
61
|
+
data: chartSpecSchema.shape.data.min(1),
|
|
62
|
+
series: chartSpecSchema.shape.series.min(1),
|
|
63
|
+
}).superRefine(checkSeriesShape),
|
|
64
|
+
}).passthrough();
|
|
28
65
|
/** Convert validated legacy props to the neutral contract; optional provider overrides stay data.
|
|
29
66
|
* @example legacyChartSpec({ kind: 'pie', props: { data: [{ name: 'A', value: 2 }] } })
|
|
30
67
|
* @throws ZodError for an invalid wire shape or nonserializable row data.
|
|
@@ -8,6 +8,7 @@ import { shadcnDataTableManifest, DataTable as ShadcnDataTable, shadcnButtonMani
|
|
|
8
8
|
import { createChartProviderRegistry } from '../charts/registry.js';
|
|
9
9
|
import { createChartInteractiveRegistry } from '../charts/react.js';
|
|
10
10
|
import { chartWireManifests } from '../charts/wire.js';
|
|
11
|
+
import { CHART_SPEC_COMPONENT_ID } from '../charts/catalog-manifests.js';
|
|
11
12
|
/**
|
|
12
13
|
* Every provider currently registered, shadcn preferred over the dependency-free native fallback
|
|
13
14
|
* for `data-table`/`table` — the same order `manifests.ts`'s `ALL_MANIFESTS` declares, so a
|
|
@@ -35,9 +36,11 @@ const nonChartRegistry = new InteractiveUiRegistry({ entries: [
|
|
|
35
36
|
] });
|
|
36
37
|
// Charts retain their wire IDs and validation while an unconfigured host renders the core table.
|
|
37
38
|
// Runtime providers belong to the host's admitted boot snapshot, never to the default registry.
|
|
39
|
+
// `chart.spec` is here too, so a saved multi-series chart still renders (as a table) in a host that
|
|
40
|
+
// has no provider snapshot. It stays out of `ALL_MANIFESTS`: that list is the legacy wire set.
|
|
38
41
|
const chartHosts = createChartInteractiveRegistry({ registry: nonChartRegistry, providers: createChartProviderRegistry({ providers: [] }) });
|
|
39
42
|
export const DEFAULT_INTERACTIVE_UI_REGISTRY = new InteractiveUiRegistry({ entries: [
|
|
40
43
|
...nonChartRegistry.list(),
|
|
41
|
-
...chartWireManifests.map(({ id }) => chartHosts.resolveById({ id })),
|
|
44
|
+
...[...chartWireManifests.map(({ id }) => id), CHART_SPEC_COMPONENT_ID].map((id) => chartHosts.resolveById({ id })),
|
|
42
45
|
] });
|
|
43
46
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { formatErrorForLog } from '@jini-ai/core';
|
|
3
4
|
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
4
5
|
import { LexicalComposer } from '@lexical/react/LexicalComposer';
|
|
5
6
|
import { PlainTextPlugin } from '@lexical/react/LexicalPlainTextPlugin';
|
|
@@ -145,7 +146,7 @@ export const RichTextInput = forwardRef(function RichTextInput(props, ref) {
|
|
|
145
146
|
theme: EDITOR_THEME,
|
|
146
147
|
onError(err) {
|
|
147
148
|
if (process.env.NODE_ENV !== 'production') {
|
|
148
|
-
console.error('[rich-text-input]', err);
|
|
149
|
+
console.error('[rich-text-input]', formatErrorForLog({ error: err }));
|
|
149
150
|
}
|
|
150
151
|
},
|
|
151
152
|
// editorState intentionally omitted → empty on first paint (SSR-safe).
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* every call site into "sometimes required, sometimes not" — worse than a small, purpose-built
|
|
34
34
|
* sibling that carries only what a result needs.
|
|
35
35
|
*
|
|
36
|
-
* ## The
|
|
36
|
+
* ## The external link and optional cancellation
|
|
37
37
|
*
|
|
38
38
|
* `openLinkUrl` renders a single button wired to the bridge's existing `openLink` (`ui/open-link`) —
|
|
39
39
|
* no new bridge plumbing: a `<a href>` is dead inside this package's sandboxed `srcdoc` frame (no
|
|
@@ -41,9 +41,13 @@
|
|
|
41
41
|
* all (`bridge.ts`'s own header). The URL is ALSO always rendered as a plain detail row regardless —
|
|
42
42
|
* copyable/selectable text that needs no script to be useful, matching this package's general
|
|
43
43
|
* "the document should say something true even if a caller never runs its script" posture.
|
|
44
|
+
* A partial outcome may also describe external work still waiting on the person. Its optional
|
|
45
|
+
* cancel action answers the caller's existing exchange without confirming an operation or
|
|
46
|
+
* navigating. Ordinary completed outcomes retain their read-only contract.
|
|
44
47
|
*/
|
|
45
48
|
import { type UIResource, type UIResourceUri } from '../resource.js';
|
|
46
49
|
import { type SurfaceDetail } from './document.js';
|
|
50
|
+
import type { ConfirmationToolAction } from './confirmation.js';
|
|
47
51
|
import type { BridgeScriptSpec } from './bridge.js';
|
|
48
52
|
import type { SurfaceTokenName } from './tokens.js';
|
|
49
53
|
export interface SurfaceOutcomeSpec {
|
|
@@ -74,6 +78,8 @@ export interface SurfaceOutcomeSpec {
|
|
|
74
78
|
* Only meaningful alongside `state: 'success'` — a caller should not supply this for a failure. */
|
|
75
79
|
readonly openLinkUrl?: string;
|
|
76
80
|
readonly openLinkLabel?: string;
|
|
81
|
+
/** A pending external action may be skipped through its existing surface exchange. */
|
|
82
|
+
readonly cancel?: ConfirmationToolAction;
|
|
77
83
|
/** Identity reported to the Host in `ui/initialize`. Defaults to a generic one. */
|
|
78
84
|
readonly app?: BridgeScriptSpec;
|
|
79
85
|
readonly lang?: string;
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* every call site into "sometimes required, sometimes not" — worse than a small, purpose-built
|
|
34
34
|
* sibling that carries only what a result needs.
|
|
35
35
|
*
|
|
36
|
-
* ## The
|
|
36
|
+
* ## The external link and optional cancellation
|
|
37
37
|
*
|
|
38
38
|
* `openLinkUrl` renders a single button wired to the bridge's existing `openLink` (`ui/open-link`) —
|
|
39
39
|
* no new bridge plumbing: a `<a href>` is dead inside this package's sandboxed `srcdoc` frame (no
|
|
@@ -41,10 +41,13 @@
|
|
|
41
41
|
* all (`bridge.ts`'s own header). The URL is ALSO always rendered as a plain detail row regardless —
|
|
42
42
|
* copyable/selectable text that needs no script to be useful, matching this package's general
|
|
43
43
|
* "the document should say something true even if a caller never runs its script" posture.
|
|
44
|
+
* A partial outcome may also describe external work still waiting on the person. Its optional
|
|
45
|
+
* cancel action answers the caller's existing exchange without confirming an operation or
|
|
46
|
+
* navigating. Ordinary completed outcomes retain their read-only contract.
|
|
44
47
|
*/
|
|
45
48
|
import { createUIResource } from '../resource.js';
|
|
46
49
|
import { escapeHtml, escapeJsValue } from '../escape.js';
|
|
47
|
-
import { renderActions, renderDetailList, renderSurfaceDocument, renderSurfaceHeader, SURFACE_SCRIPT_PRELUDE, SURFACE_STATUS_ELEMENT_ID, } from './document.js';
|
|
50
|
+
import { DEFAULT_SURFACE_STATUS_TEXT, renderActions, renderDetailList, renderSurfaceDocument, renderSurfaceHeader, SURFACE_SCRIPT_PRELUDE, SURFACE_STATUS_ELEMENT_ID, } from './document.js';
|
|
48
51
|
const DEFAULT_APP = { appName: 'jini-mcp-ui-outcome', appVersion: '1' };
|
|
49
52
|
const DEFAULT_OPEN_LINK_LABEL = 'Open site';
|
|
50
53
|
const OPEN_LINK_ACTION_ID = 'open-link';
|
|
@@ -54,11 +57,45 @@ function statusStateOf(state) {
|
|
|
54
57
|
return 'done';
|
|
55
58
|
return state === 'partial' ? 'partial' : 'failed';
|
|
56
59
|
}
|
|
57
|
-
/**
|
|
60
|
+
/** One action list for the frame and its host mirror. @complexity O(n) in the label length. */
|
|
61
|
+
function outcomeActions(spec) {
|
|
62
|
+
const actions = [];
|
|
63
|
+
if (spec.openLinkUrl !== undefined)
|
|
64
|
+
actions.push({ id: OPEN_LINK_ACTION_ID, label: spec.openLinkLabel ?? DEFAULT_OPEN_LINK_LABEL, variant: spec.state === 'success' ? 'primary' : 'neutral' });
|
|
65
|
+
if (spec.cancel)
|
|
66
|
+
actions.push({ id: 'cancel', label: spec.cancel.label, variant: 'neutral' });
|
|
67
|
+
return actions;
|
|
68
|
+
}
|
|
58
69
|
function renderOpenLinkActions(spec) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
const actions = outcomeActions(spec);
|
|
71
|
+
return actions.length === 0 ? '' : renderActions(actions);
|
|
72
|
+
}
|
|
73
|
+
/** Pending external work keeps its outbound link and answers cancellation through the same bridge.
|
|
74
|
+
* Existing read-only outcomes retain their script and need no exchange. @complexity O(n) rendered bytes. */
|
|
75
|
+
function cancellableOutcomeScript(spec) {
|
|
76
|
+
if (!spec.cancel)
|
|
77
|
+
return outcomeScript(spec.openLinkUrl);
|
|
78
|
+
return `(function () {
|
|
79
|
+
"use strict";
|
|
80
|
+
${SURFACE_SCRIPT_PRELUDE}
|
|
81
|
+
var TEXT = ${escapeJsValue(DEFAULT_SURFACE_STATUS_TEXT)};
|
|
82
|
+
var CANCEL = ${escapeJsValue(spec.cancel)};
|
|
83
|
+
var OPEN_URL = ${escapeJsValue(spec.openLinkUrl ?? null)};
|
|
84
|
+
var locked = false;
|
|
85
|
+
function onClick(event) {
|
|
86
|
+
if (event.isTrusted !== true || locked) return;
|
|
87
|
+
if (event.currentTarget.getAttribute("data-mcpui-action") === "open-link") {
|
|
88
|
+
api.openLink(OPEN_URL);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
locked = true;
|
|
92
|
+
setBusy(true);
|
|
93
|
+
api.callTool(CANCEL.toolName, CANCEL.params).then(function () {
|
|
94
|
+
setStatus(TEXT.dismissed, "dismissed");
|
|
95
|
+
}, function (error) { locked = !reportCallFailure(error); });
|
|
96
|
+
}
|
|
97
|
+
for (var i = 0; i < actionButtons.length; i++) actionButtons[i].addEventListener("click", onClick);
|
|
98
|
+
}());`;
|
|
62
99
|
}
|
|
63
100
|
/** The inline script: wires the open-link button, or nothing at all. @complexity O(n) in the URL length. */
|
|
64
101
|
function outcomeScript(openLinkUrl) {
|
|
@@ -99,7 +136,7 @@ export function renderOutcomeDocument(spec) {
|
|
|
99
136
|
return renderSurfaceDocument({
|
|
100
137
|
title: spec.title,
|
|
101
138
|
bodyHtml,
|
|
102
|
-
script:
|
|
139
|
+
script: cancellableOutcomeScript(spec),
|
|
103
140
|
app: spec.app ?? DEFAULT_APP,
|
|
104
141
|
...(spec.lang === undefined ? {} : { lang: spec.lang }),
|
|
105
142
|
...(spec.tokens === undefined ? {} : { tokens: spec.tokens }),
|
|
@@ -120,10 +157,12 @@ export function buildOutcomeSurface(spec) {
|
|
|
120
157
|
uri: spec.uri,
|
|
121
158
|
htmlString: renderOutcomeDocument(spec),
|
|
122
159
|
...(spec.preferredFrameSize === undefined ? {} : { preferredFrameSize: spec.preferredFrameSize }),
|
|
160
|
+
...(spec.cancel ? { actionPlan: { title: spec.title, actions: outcomeActions(spec) } } : {}),
|
|
123
161
|
// No `actionPlan`: unlike a confirmation, this surface has nothing PENDING for
|
|
124
162
|
// `page.find_elements`'s parent-DOM mirror to describe (`McpUiSurfaceCard`'s own
|
|
125
163
|
// `readActionPlan`/`PendingSurfaceMirror` — that mirror exists for a confirmation a human has not
|
|
126
|
-
// yet answered; a result has already been answered).
|
|
164
|
+
// yet answered; a result has already been answered). A cancellable external wait above is
|
|
165
|
+
// pending, so its mirror uses the same actions its frame renders, with no routing params.
|
|
127
166
|
});
|
|
128
167
|
}
|
|
129
168
|
//# sourceMappingURL=outcome.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { UserFacingError } from '@jini-ai/core';
|
|
2
|
+
import { type CopyToClipboardOptions } from '../../utils/copy-to-clipboard.js';
|
|
3
|
+
import type { Translate } from '../i18n/index.js';
|
|
4
|
+
/** A formatted failure, optional host translation, and injectable clipboard ports. */
|
|
5
|
+
export interface ErrorNoticeProps extends CopyToClipboardOptions {
|
|
6
|
+
readonly failure: UserFacingError;
|
|
7
|
+
readonly t?: Translate;
|
|
8
|
+
readonly className?: string;
|
|
9
|
+
/** History surfaces use polite announcements; active form failures keep the alert default. */
|
|
10
|
+
readonly role?: "alert" | "status";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Disclose exactly the formatted copy block in native, selectable details. Copying has no recovery effects.
|
|
14
|
+
* Failed writes leave the details available for manual copying; feedback resets with the incident.
|
|
15
|
+
* @complexity O(copyText length) per render/copy; one clipboard write plus the existing fallback on rejection.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ErrorNotice({ failure, t, className, role, ...ports }: ErrorNoticeProps): import("react").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=ErrorNotice.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { copyToClipboard } from '../../utils/copy-to-clipboard.js';
|
|
4
|
+
/**
|
|
5
|
+
* Disclose exactly the formatted copy block in native, selectable details. Copying has no recovery effects.
|
|
6
|
+
* Failed writes leave the details available for manual copying; feedback resets with the incident.
|
|
7
|
+
* @complexity O(copyText length) per render/copy; one clipboard write plus the existing fallback on rejection.
|
|
8
|
+
*/
|
|
9
|
+
export function ErrorNotice({ failure, t = text => text, className = 'notice error', role = 'alert', ...ports }) {
|
|
10
|
+
const [feedback, setFeedback] = useState(null);
|
|
11
|
+
async function copy() {
|
|
12
|
+
const copied = await copyToClipboard({ text: failure.copyText }, ports);
|
|
13
|
+
setFeedback({ text: failure.copyText, copied });
|
|
14
|
+
}
|
|
15
|
+
return (_jsxs("div", { className: className, role: role, children: [_jsx("p", { children: failure.message }), _jsx("p", { children: failure.action }), _jsxs("details", { children: [_jsx("summary", { children: t('Show details') }), _jsx("pre", { style: { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', userSelect: 'text' }, children: failure.copyText }), _jsx("button", { type: "button", onClick: () => void copy(), children: t('Copy details') }), feedback && feedback.text === failure.copyText ? _jsx("p", { role: "status", children: t(feedback.copied ? 'Copied' : 'Copy failed. Select the details and copy them manually.') }) : null] })] }));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=ErrorNotice.js.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './fetch-query/index.js';
|
|
2
|
+
export { ErrorNotice } from './ErrorNotice.js';
|
|
3
|
+
export type { ErrorNoticeProps } from './ErrorNotice.js';
|
|
2
4
|
export { useFocusTrap } from './hooks/use-focus-trap.hooks.js';
|
|
3
5
|
export { useDirtyGuard } from './hooks/use-dirty-guard.hooks.js';
|
|
4
6
|
export type { DirtyGuard } from './hooks/use-dirty-guard.hooks.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './fetch-query/index.js';
|
|
2
|
+
export { ErrorNotice } from './ErrorNotice.js';
|
|
2
3
|
export { useFocusTrap } from './hooks/use-focus-trap.hooks.js';
|
|
3
4
|
export { useDirtyGuard } from './hooks/use-dirty-guard.hooks.js';
|
|
4
5
|
export { useAsyncAction } from './hooks/use-async-action.hooks.js';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatErrorForLog } from '@jini-ai/core';
|
|
1
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
3
|
import { EXPORTED_IMAGE_MIME_TYPE, SAVED_VISIBLE_MS } from '../../constants.js';
|
|
3
4
|
import { defaultSketchEditorDependencies } from '../../dependencies.js';
|
|
@@ -85,7 +86,7 @@ export function useSketchSaveWorkflow(params) {
|
|
|
85
86
|
});
|
|
86
87
|
}
|
|
87
88
|
catch (err) {
|
|
88
|
-
console.warn('[SketchEditor] export image failed', err);
|
|
89
|
+
console.warn('[SketchEditor] export image failed', formatErrorForLog({ error: err }));
|
|
89
90
|
setToast({ message: tRef.current('Could not export image'), tone: 'error' });
|
|
90
91
|
}
|
|
91
92
|
finally {
|
|
@@ -73,6 +73,12 @@ export function useTooltipLayer() {
|
|
|
73
73
|
const rafRef = useRef(null);
|
|
74
74
|
const lastInputRef = useRef('pointer');
|
|
75
75
|
const suppressedTitleRef = useRef(null);
|
|
76
|
+
// Touch has no hover, and a tap's own pointerdown/click would hide the tip
|
|
77
|
+
// the instant it appeared — so a touch tap on a trigger TOGGLES the tip
|
|
78
|
+
// instead. `touchTapRef` remembers the trigger under the touch pointerdown
|
|
79
|
+
// (and whether its tip was already open) until the matching click.
|
|
80
|
+
const touchTapRef = useRef(null);
|
|
81
|
+
const openTargetRef = useRef(null);
|
|
76
82
|
const [state, setState] = useState(null);
|
|
77
83
|
const restoreNativeTitle = useCallback(() => {
|
|
78
84
|
const suppressed = suppressedTitleRef.current;
|
|
@@ -170,6 +176,7 @@ export function useTooltipLayer() {
|
|
|
170
176
|
});
|
|
171
177
|
}, [updatePosition]);
|
|
172
178
|
useLayoutEffect(() => {
|
|
179
|
+
openTargetRef.current = state?.target ?? null;
|
|
173
180
|
if (!state)
|
|
174
181
|
return;
|
|
175
182
|
updatePosition();
|
|
@@ -209,11 +216,15 @@ export function useTooltipLayer() {
|
|
|
209
216
|
};
|
|
210
217
|
const onPointerOver = (event) => {
|
|
211
218
|
lastInputRef.current = 'pointer';
|
|
219
|
+
if (event.pointerType === 'touch')
|
|
220
|
+
return;
|
|
212
221
|
const target = readTooltipTarget(event.target);
|
|
213
222
|
if (target)
|
|
214
223
|
showTooltip(target);
|
|
215
224
|
};
|
|
216
225
|
const onTargetExit = (event) => {
|
|
226
|
+
if ('pointerType' in event && event.pointerType === 'touch')
|
|
227
|
+
return;
|
|
217
228
|
const target = readTooltipTarget(event.target);
|
|
218
229
|
if (!target)
|
|
219
230
|
return;
|
|
@@ -224,14 +235,28 @@ export function useTooltipLayer() {
|
|
|
224
235
|
};
|
|
225
236
|
const onPointerDown = (event) => {
|
|
226
237
|
lastInputRef.current = 'pointer';
|
|
227
|
-
|
|
238
|
+
const target = readTooltipTarget(event.target);
|
|
239
|
+
touchTapRef.current = null;
|
|
240
|
+
if (event.pointerType === 'touch' && target) {
|
|
241
|
+
touchTapRef.current = { target, wasOpen: openTargetRef.current === target };
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
hideTooltipForActivation(target);
|
|
228
245
|
};
|
|
229
246
|
const onPointerCancel = () => {
|
|
230
247
|
lastInputRef.current = 'pointer';
|
|
248
|
+
touchTapRef.current = null;
|
|
231
249
|
hideTooltip();
|
|
232
250
|
};
|
|
233
251
|
const onClick = (event) => {
|
|
234
|
-
|
|
252
|
+
const target = readTooltipTarget(event.target);
|
|
253
|
+
const touchTap = touchTapRef.current;
|
|
254
|
+
touchTapRef.current = null;
|
|
255
|
+
if (target && touchTap?.target === target && !touchTap.wasOpen) {
|
|
256
|
+
showTooltip(target);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
hideTooltipForActivation(target);
|
|
235
260
|
};
|
|
236
261
|
const onFocusIn = (event) => {
|
|
237
262
|
const target = readTooltipTarget(event.target);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.22",
|
|
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": {
|
|
@@ -263,19 +263,19 @@
|
|
|
263
263
|
"shiki": "^1.24.4",
|
|
264
264
|
"tailwind-merge": "^2.6.0",
|
|
265
265
|
"zod": "^3.25.76",
|
|
266
|
-
"@jini-ai/
|
|
267
|
-
"@jini-ai/
|
|
268
|
-
"@jini-ai/
|
|
269
|
-
"@jini-ai/
|
|
270
|
-
"@jini-ai/
|
|
266
|
+
"@jini-ai/platform": "^0.6.22",
|
|
267
|
+
"@jini-ai/agent-runtime": "^0.6.22",
|
|
268
|
+
"@jini-ai/integrations": "^0.6.19",
|
|
269
|
+
"@jini-ai/agentic": "^0.6.19",
|
|
270
|
+
"@jini-ai/core": "^0.6.22"
|
|
271
271
|
},
|
|
272
272
|
"dependencies": {
|
|
273
273
|
"micromark": "^4.0.0",
|
|
274
274
|
"micromark-extension-gfm": "^3.0.0",
|
|
275
|
-
"@jini-ai/
|
|
276
|
-
"@jini-ai/
|
|
277
|
-
"@jini-ai/
|
|
278
|
-
"@jini-ai/
|
|
275
|
+
"@jini-ai/core": "^0.6.22",
|
|
276
|
+
"@jini-ai/ui-kit": "^0.6.19",
|
|
277
|
+
"@jini-ai/agentic": "^0.6.19",
|
|
278
|
+
"@jini-ai/platform": "^0.6.22"
|
|
279
279
|
},
|
|
280
280
|
"peerDependencies": {
|
|
281
281
|
"@tanstack/react-query": "^5.0.0",
|