@tai42/studio-sdk 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/code-block.d.ts.map +1 -1
- package/dist/components/code-block.js +26 -13
- package/dist/components/code-block.js.map +1 -1
- package/dist/components/components.css +20 -0
- package/dist/components/copy-field.d.ts.map +1 -1
- package/dist/components/copy-field.js +8 -73
- package/dist/components/copy-field.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +73 -0
- package/dist/components/date-range-picker.d.ts.map +1 -0
- package/dist/components/date-range-picker.js +146 -0
- package/dist/components/date-range-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +19 -1
- package/dist/components/dialog.d.ts.map +1 -1
- package/dist/components/dialog.js +16 -13
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/dirty-guard.d.ts +28 -0
- package/dist/components/dirty-guard.d.ts.map +1 -0
- package/dist/components/dirty-guard.js +93 -0
- package/dist/components/dirty-guard.js.map +1 -0
- package/dist/components/extension-picker.d.ts.map +1 -1
- package/dist/components/extension-picker.js +7 -0
- package/dist/components/extension-picker.js.map +1 -1
- package/dist/components/form-dialog.d.ts +45 -0
- package/dist/components/form-dialog.d.ts.map +1 -0
- package/dist/components/form-dialog.js +71 -0
- package/dist/components/form-dialog.js.map +1 -0
- package/dist/components/json-tree.d.ts +7 -1
- package/dist/components/json-tree.d.ts.map +1 -1
- package/dist/components/json-tree.js +300 -13
- package/dist/components/json-tree.js.map +1 -1
- package/dist/components/markdown.d.ts +60 -0
- package/dist/components/markdown.d.ts.map +1 -0
- package/dist/components/markdown.js +435 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/tabs.d.ts +3 -1
- package/dist/components/tabs.d.ts.map +1 -1
- package/dist/components/tabs.js +2 -2
- package/dist/components/tabs.js.map +1 -1
- package/dist/hooks/useApi.d.ts +56 -28
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useClipboardCopy.d.ts +26 -0
- package/dist/hooks/useClipboardCopy.d.ts.map +1 -0
- package/dist/hooks/useClipboardCopy.js +72 -0
- package/dist/hooks/useClipboardCopy.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/navigation/context.d.ts +21 -1
- package/dist/navigation/context.d.ts.map +1 -1
- package/dist/navigation/context.js +229 -7
- package/dist/navigation/context.js.map +1 -1
- package/dist/navigation/index.d.ts +2 -2
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/types.d.ts +11 -0
- package/dist/navigation/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/code-block.tsx +72 -9
- package/src/components/components.css +20 -0
- package/src/components/copy-field.tsx +25 -81
- package/src/components/date-range-picker.tsx +256 -0
- package/src/components/dialog.tsx +59 -18
- package/src/components/dirty-guard.tsx +153 -0
- package/src/components/extension-picker.tsx +12 -0
- package/src/components/form-dialog.tsx +133 -0
- package/src/components/json-tree.tsx +477 -42
- package/src/components/markdown.tsx +552 -0
- package/src/components/tabs.tsx +10 -1
- package/src/hooks/useClipboardCopy.ts +105 -0
- package/src/index.ts +19 -0
- package/src/navigation/context.tsx +277 -7
- package/src/navigation/index.ts +3 -0
- package/src/navigation/types.ts +12 -0
|
@@ -1,13 +1,196 @@
|
|
|
1
|
-
import { createContext, createElement, useCallback, useContext, } from 'react';
|
|
1
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
2
|
+
/** Shell-provided navigation; no default, so use outside {@link NavigationProvider} throws. */
|
|
3
|
+
const NavigationContext = createContext(null);
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The guards active under one {@link NavigationProvider} — the single registry
|
|
6
|
+
* every SDK-controlled navigation consults, so feature and plugin-page guards
|
|
7
|
+
* compose against the same set. `run` stops at the first veto (at most one confirm
|
|
8
|
+
* dialog); `subscribe` re-evaluates browser-level listeners when the armed set changes.
|
|
7
9
|
*/
|
|
8
|
-
|
|
10
|
+
class NavigationGuardRegistry {
|
|
11
|
+
guards = new Set();
|
|
12
|
+
listeners = new Set();
|
|
13
|
+
/** Register a guard; returns the unregister function. */
|
|
14
|
+
add(entry) {
|
|
15
|
+
this.guards.add(entry);
|
|
16
|
+
this.emit();
|
|
17
|
+
return () => {
|
|
18
|
+
this.guards.delete(entry);
|
|
19
|
+
this.emit();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Notify subscribers that a guard's `when` may have changed. */
|
|
23
|
+
touch() {
|
|
24
|
+
this.emit();
|
|
25
|
+
}
|
|
26
|
+
subscribe(listener) {
|
|
27
|
+
this.listeners.add(listener);
|
|
28
|
+
return () => {
|
|
29
|
+
this.listeners.delete(listener);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
hasArmedGuards() {
|
|
33
|
+
for (const entry of this.guards) {
|
|
34
|
+
if (entry.when)
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
/** Resolve `true` only if every armed guard allows the navigation. */
|
|
40
|
+
async run() {
|
|
41
|
+
for (const entry of this.guards) {
|
|
42
|
+
if (!entry.when)
|
|
43
|
+
continue;
|
|
44
|
+
const allowed = await entry.handler();
|
|
45
|
+
if (!allowed)
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
emit() {
|
|
51
|
+
for (const listener of this.listeners)
|
|
52
|
+
listener();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Carries the provider's guard registry to {@link useNavigationGuard}. Separate from
|
|
57
|
+
* {@link NavigationContext} so the shell-supplied value stays exactly the shell's
|
|
58
|
+
* contract — the registry is SDK-owned.
|
|
59
|
+
*/
|
|
60
|
+
const GuardRegistryContext = createContext(null);
|
|
61
|
+
/** Records an approved raw-path transition as the committed history entry, so a later
|
|
62
|
+
* back/forward restores to it exactly as it does after a token navigation. */
|
|
63
|
+
const CommitHrefContext = createContext(null);
|
|
64
|
+
function useGuardRegistry() {
|
|
65
|
+
const registry = useContext(GuardRegistryContext);
|
|
66
|
+
if (registry === null) {
|
|
67
|
+
throw new Error('useNavigationGuard must be used within a <NavigationProvider> (the shell provides it).');
|
|
68
|
+
}
|
|
69
|
+
return registry;
|
|
70
|
+
}
|
|
9
71
|
export function NavigationProvider({ value, children, }) {
|
|
10
|
-
|
|
72
|
+
const registryRef = useRef(null);
|
|
73
|
+
registryRef.current ??= new NavigationGuardRegistry();
|
|
74
|
+
const registry = registryRef.current;
|
|
75
|
+
// The last committed history entry the SDK observed (URL + `history.state`): what a
|
|
76
|
+
// canceled back/forward restores to. URL changes made outside the SDK's navigate
|
|
77
|
+
// entry points are unobservable by design at this layer.
|
|
78
|
+
const committedRef = useRef(null);
|
|
79
|
+
// Mirror the registry's armed state into React so the browser listeners attach only
|
|
80
|
+
// while at least one guard is armed.
|
|
81
|
+
const [armed, setArmed] = useState(false);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
setArmed(registry.hasArmedGuards());
|
|
84
|
+
return registry.subscribe(() => {
|
|
85
|
+
setArmed(registry.hasArmedGuards());
|
|
86
|
+
});
|
|
87
|
+
}, [registry]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!armed)
|
|
90
|
+
return;
|
|
91
|
+
// The protected entry (URL + state); a back/forward landing elsewhere is first
|
|
92
|
+
// canceled back to here while the guard is consulted.
|
|
93
|
+
committedRef.current = { href: window.location.href, state: window.history.state };
|
|
94
|
+
// Set while replaying a guard-approved back/forward, so its `popstate` passes
|
|
95
|
+
// through instead of being re-intercepted.
|
|
96
|
+
let bypass = false;
|
|
97
|
+
// Latched while a decision is pending, so a second Back mid-decision keeps canceling
|
|
98
|
+
// without starting a concurrent `registry.run()` — one gesture, one dialog.
|
|
99
|
+
let pending = false;
|
|
100
|
+
// Restore pushes stacked for the pending decision, so an approved replay unwinds
|
|
101
|
+
// exactly that far in one `go(-depth)`. Reset per decision.
|
|
102
|
+
let restores = 0;
|
|
103
|
+
const onPopState = () => {
|
|
104
|
+
if (bypass) {
|
|
105
|
+
bypass = false;
|
|
106
|
+
committedRef.current = { href: window.location.href, state: window.history.state };
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!registry.hasArmedGuards()) {
|
|
110
|
+
committedRef.current = { href: window.location.href, state: window.history.state };
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const committed = committedRef.current;
|
|
114
|
+
if (committed === null)
|
|
115
|
+
return;
|
|
116
|
+
// Cancel the browser's move by pushing the committed entry on top of wherever it
|
|
117
|
+
// landed. popstate exposes no direction/distance and the router owns
|
|
118
|
+
// `history.state`, so restore can only push, not delete. A vetoed forward thus
|
|
119
|
+
// strands a stub entry — an accepted limit the next pushState truncates.
|
|
120
|
+
window.history.pushState(committed.state, '', committed.href);
|
|
121
|
+
restores += 1;
|
|
122
|
+
if (pending)
|
|
123
|
+
return;
|
|
124
|
+
pending = true;
|
|
125
|
+
void registry.run().then((allowed) => {
|
|
126
|
+
pending = false;
|
|
127
|
+
const depth = restores;
|
|
128
|
+
restores = 0;
|
|
129
|
+
if (!allowed)
|
|
130
|
+
return;
|
|
131
|
+
bypass = true;
|
|
132
|
+
window.history.go(-depth);
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
const onBeforeUnload = (event) => {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
// Browsers below the floor (Chrome 108) gate the prompt on a set `returnValue`,
|
|
138
|
+
// not `preventDefault` alone; the string is ignored. Typed deprecated, still required.
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- required beforeunload trigger for the supported browser range.
|
|
140
|
+
event.returnValue = '';
|
|
141
|
+
};
|
|
142
|
+
window.addEventListener('popstate', onPopState);
|
|
143
|
+
window.addEventListener('beforeunload', onBeforeUnload);
|
|
144
|
+
return () => {
|
|
145
|
+
committedRef.current = null;
|
|
146
|
+
window.removeEventListener('popstate', onPopState);
|
|
147
|
+
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
148
|
+
};
|
|
149
|
+
}, [armed, registry]);
|
|
150
|
+
// Wrap the shell's transitions so an armed guard is consulted before they commit.
|
|
151
|
+
// With no guard armed the call stays synchronous — identical to the raw shell value.
|
|
152
|
+
const guarded = useMemo(() => {
|
|
153
|
+
const navigate = (token, search) => {
|
|
154
|
+
if (!registry.hasArmedGuards()) {
|
|
155
|
+
value.navigate(token, search);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
void registry.run().then((allowed) => {
|
|
159
|
+
if (!allowed)
|
|
160
|
+
return;
|
|
161
|
+
value.navigate(token, search);
|
|
162
|
+
// Advance the committed entry to the resolved destination (the router may commit
|
|
163
|
+
// async, so `window.location` would be stale). Its router state is unobservable
|
|
164
|
+
// here, hence `state: null`.
|
|
165
|
+
if (committedRef.current !== null) {
|
|
166
|
+
committedRef.current = { href: value.resolvePath(token, search), state: null };
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
const navigatePlugin = (pluginId, pagePath, params, search) => {
|
|
171
|
+
if (!registry.hasArmedGuards()) {
|
|
172
|
+
value.navigatePlugin(pluginId, pagePath, params, search);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
void registry.run().then((allowed) => {
|
|
176
|
+
if (!allowed)
|
|
177
|
+
return;
|
|
178
|
+
value.navigatePlugin(pluginId, pagePath, params, search);
|
|
179
|
+
if (committedRef.current !== null) {
|
|
180
|
+
committedRef.current = {
|
|
181
|
+
href: value.resolvePluginPath(pluginId, pagePath, params, search),
|
|
182
|
+
state: null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
return { ...value, navigate, navigatePlugin };
|
|
188
|
+
}, [value, registry]);
|
|
189
|
+
const commitHref = useCallback((href) => {
|
|
190
|
+
if (committedRef.current !== null)
|
|
191
|
+
committedRef.current = { href, state: null };
|
|
192
|
+
}, []);
|
|
193
|
+
return createElement(GuardRegistryContext.Provider, { value: registry }, createElement(CommitHrefContext.Provider, { value: commitHref }, createElement(NavigationContext.Provider, { value: guarded }, children)));
|
|
11
194
|
}
|
|
12
195
|
function useNavigation() {
|
|
13
196
|
const ctx = useContext(NavigationContext);
|
|
@@ -24,6 +207,45 @@ export function useAppNavigate() {
|
|
|
24
207
|
export function useResolvePath() {
|
|
25
208
|
return useNavigation().resolvePath;
|
|
26
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Block navigation away from the current page while `when` is true. `handler` is
|
|
212
|
+
* consulted before any SDK-controlled navigation commits — a route-token navigate,
|
|
213
|
+
* a plugin navigate, or a browser back/forward — and the navigation proceeds only if
|
|
214
|
+
* it resolves `true`; a promise lets the handler drive a confirm dialog. A full-page
|
|
215
|
+
* unload (tab close / refresh) is additionally covered by a native `beforeunload`
|
|
216
|
+
* prompt that fires whenever any guard is armed.
|
|
217
|
+
*
|
|
218
|
+
* Guards compose against the provider's shared registry: a feature and a plugin page
|
|
219
|
+
* may each arm one, and any veto blocks.
|
|
220
|
+
*/
|
|
221
|
+
export function useNavigationGuard(when, handler) {
|
|
222
|
+
const registry = useGuardRegistry();
|
|
223
|
+
const entryRef = useRef({ when, handler });
|
|
224
|
+
// Keep the registered entry pointing at the latest inputs without re-registering.
|
|
225
|
+
entryRef.current.when = when;
|
|
226
|
+
entryRef.current.handler = handler;
|
|
227
|
+
useEffect(() => registry.add(entryRef.current), [registry]);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
registry.touch();
|
|
230
|
+
}, [registry, when]);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Consult the armed navigation guards before a transition the shell drives itself
|
|
234
|
+
* (a chrome link whose target is outside the route-token map). Resolves `true` when
|
|
235
|
+
* the navigation may proceed. Token and plugin navigations already run this gate
|
|
236
|
+
* inside {@link NavigationProvider}; this is the escape hatch for the shell's own
|
|
237
|
+
* raw-path links, without which such a link would bypass every armed guard.
|
|
238
|
+
*/
|
|
239
|
+
export function useNavigationGate() {
|
|
240
|
+
const registry = useGuardRegistry();
|
|
241
|
+
const commitHref = useContext(CommitHrefContext);
|
|
242
|
+
return useCallback(async (href) => {
|
|
243
|
+
if (registry.hasArmedGuards() && !(await registry.run()))
|
|
244
|
+
return false;
|
|
245
|
+
commitHref?.(href);
|
|
246
|
+
return true;
|
|
247
|
+
}, [registry, commitHref]);
|
|
248
|
+
}
|
|
27
249
|
/**
|
|
28
250
|
* Plugin-page navigation: `navigatePlugin` drives a client-side transition to a
|
|
29
251
|
* runtime plugin path, `resolvePluginPath` yields its href. These are the ONLY
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/navigation/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/navigation/context.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { NavigationProvider, AppLink, useAppNavigate, useResolvePath, usePluginNavigation, } from './context';
|
|
1
|
+
export { NavigationProvider, AppLink, useAppNavigate, useResolvePath, usePluginNavigation, useNavigationGuard, useNavigationGate, } from './context';
|
|
2
2
|
export type { AppLinkProps } from './context';
|
|
3
|
-
export type { RouteToken, RouteSearch, RouteSearchByToken, PageProps, NavigationContextValue, PluginSearch, } from './types';
|
|
3
|
+
export type { RouteToken, RouteSearch, RouteSearchByToken, PageProps, NavigationContextValue, PluginSearch, NavigationGuardHandler, } from './types';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,OAAO,EACP,cAAc,EACd,cAAc,EACd,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,OAAO,EACP,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EACV,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,sBAAsB,EACtB,YAAY,EACZ,sBAAsB,GACvB,MAAM,SAAS,CAAC"}
|
package/dist/navigation/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { NavigationProvider, AppLink, useAppNavigate, useResolvePath, usePluginNavigation, } from './context';
|
|
1
|
+
export { NavigationProvider, AppLink, useAppNavigate, useResolvePath, usePluginNavigation, useNavigationGuard, useNavigationGate, } from './context';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,OAAO,EACP,cAAc,EACd,cAAc,EACd,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navigation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,OAAO,EACP,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,WAAW,CAAC"}
|
|
@@ -108,4 +108,15 @@ export interface NavigationContextValue {
|
|
|
108
108
|
navigatePlugin: (pluginId: string, pagePath: string, params?: string, search?: PluginSearch) => void;
|
|
109
109
|
resolvePluginPath: (pluginId: string, pagePath: string, params?: string, search?: PluginSearch) => string;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* A navigation guard's decision function, run while armed before any SDK-controlled
|
|
113
|
+
* navigation commits ({@link NavigationContextValue.navigate},
|
|
114
|
+
* {@link NavigationContextValue.navigatePlugin}, or a browser back/forward). Returns
|
|
115
|
+
* `true` to proceed, `false` to veto; a promise lets it await a confirm dialog.
|
|
116
|
+
*
|
|
117
|
+
* Guards compose — navigation proceeds only if every armed guard allows, and the first
|
|
118
|
+
* veto blocks (at most one dialog). A full-page unload can't await, so it is covered by
|
|
119
|
+
* a `beforeunload` prompt independent of this handler.
|
|
120
|
+
*/
|
|
121
|
+
export type NavigationGuardHandler = () => boolean | Promise<boolean>;
|
|
111
122
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigation/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/navigation/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tai42/studio-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "The Studio plugin contract, shared UI components, and hooks that every feature builds on.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "tai42 <oss@tai42.ai>",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@radix-ui/react-select": "^2.3.2",
|
|
90
90
|
"@radix-ui/react-tabs": "^1.1.16",
|
|
91
91
|
"@radix-ui/react-tooltip": "^1.2.11",
|
|
92
|
-
"@tai42/api-client": "0.
|
|
92
|
+
"@tai42/api-client": "0.14.0"
|
|
93
93
|
},
|
|
94
94
|
"scripts": {
|
|
95
95
|
"build": "tsc -b tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `CodeBlock` — escaped, monospaced preformatted text for tool results and string
|
|
3
3
|
* payloads, on the design system's terminal ground (`tai-code-block`). The content
|
|
4
|
-
* is a React text child inside `<pre><code
|
|
5
|
-
* rendered verbatim; this component is NEVER an HTML sink (no
|
|
4
|
+
* is a React text child inside `<pre><code>`; NEVER an HTML sink (no
|
|
6
5
|
* `dangerouslySetInnerHTML`).
|
|
7
6
|
*
|
|
8
|
-
* The `<pre>` IS the scrolling box,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* The `<pre>` IS the scrolling box, carrying the region attributes itself rather
|
|
8
|
+
* than nesting a `ScrollRegion` that would add a second scroller.
|
|
9
|
+
*
|
|
10
|
+
* A top-right copy button sits OUTSIDE the `<pre>`, pinned to a relatively-positioned
|
|
11
|
+
* frame, so horizontal scroll never carries it off-screen. Its accessible name is
|
|
12
|
+
* the state it shows ("Copy code" / "Copied"), so it is never named by words it has
|
|
13
|
+
* stopped saying (WCAG 2.5.3). A clipboard failure renders as a visible `role="alert"`
|
|
14
|
+
* naming the reason, never the payload — see {@link useClipboardCopy}.
|
|
12
15
|
*/
|
|
16
|
+
import type { CSSProperties } from 'react';
|
|
17
|
+
|
|
18
|
+
import { CheckIcon, CopyIcon, XCircleIcon } from './icons';
|
|
13
19
|
import { useOverflowRegion } from './scroll-region';
|
|
20
|
+
import { COPIED_LABEL, useClipboardCopy } from '../hooks/useClipboardCopy';
|
|
14
21
|
|
|
15
22
|
export interface CodeBlockProps {
|
|
16
23
|
readonly code: string;
|
|
@@ -21,17 +28,73 @@ export interface CodeBlockProps {
|
|
|
21
28
|
/** The region's name when no language caption supplies one. */
|
|
22
29
|
const DEFAULT_LABEL = 'Code';
|
|
23
30
|
|
|
31
|
+
/** The button's resting accessible name; whichever face shows is also its name. */
|
|
32
|
+
const COPY_LABEL = 'Copy code';
|
|
33
|
+
|
|
34
|
+
/** Shown when the browser offers no clipboard at all (any non-secure context). */
|
|
35
|
+
const NO_CLIPBOARD =
|
|
36
|
+
'This browser will not write to the clipboard here. Select the code and copy it.';
|
|
37
|
+
|
|
38
|
+
/** Shown when the write is offered and refused. */
|
|
39
|
+
function writeFailed(reason: unknown): string {
|
|
40
|
+
const detail = reason instanceof Error ? reason.message : String(reason);
|
|
41
|
+
return `Copy failed: ${detail}. Select the code and copy it.`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The frame the copy button is pinned to, over the top-right of the code. */
|
|
45
|
+
const frameStyle: CSSProperties = { position: 'relative' };
|
|
46
|
+
|
|
47
|
+
const copyButtonStyle: CSSProperties = {
|
|
48
|
+
position: 'absolute',
|
|
49
|
+
top: 'var(--tai-space-2)',
|
|
50
|
+
right: 'var(--tai-space-2)',
|
|
51
|
+
};
|
|
52
|
+
|
|
24
53
|
export function CodeBlock({ code, language }: CodeBlockProps) {
|
|
25
54
|
// No consumer ref: the measurement's own callback ref is the only thing that
|
|
26
55
|
// needs the `<pre>`, and this component publishes no `ref` prop.
|
|
27
56
|
const region = useOverflowRegion(undefined, language ?? DEFAULT_LABEL);
|
|
28
57
|
|
|
58
|
+
const {
|
|
59
|
+
copied,
|
|
60
|
+
error: copyError,
|
|
61
|
+
announcement,
|
|
62
|
+
copy,
|
|
63
|
+
} = useClipboardCopy({
|
|
64
|
+
noClipboard: NO_CLIPBOARD,
|
|
65
|
+
writeFailed,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const handleCopy = (): void => {
|
|
69
|
+
void copy(() => code);
|
|
70
|
+
};
|
|
71
|
+
|
|
29
72
|
return (
|
|
30
73
|
<div className="tai-stack tai-stack-2">
|
|
31
74
|
{language !== undefined ? <span className="tai-label">{language}</span> : null}
|
|
32
|
-
<
|
|
33
|
-
<code
|
|
34
|
-
|
|
75
|
+
<div style={frameStyle}>
|
|
76
|
+
<pre className="tai-code-block" {...region}>
|
|
77
|
+
<code>{code}</code>
|
|
78
|
+
</pre>
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className="tai-icon-btn"
|
|
82
|
+
style={copyButtonStyle}
|
|
83
|
+
aria-label={copied ? COPIED_LABEL : COPY_LABEL}
|
|
84
|
+
onClick={handleCopy}
|
|
85
|
+
>
|
|
86
|
+
{copied ? <CheckIcon /> : <CopyIcon />}
|
|
87
|
+
</button>
|
|
88
|
+
<span aria-live="polite" className="tai-visually-hidden">
|
|
89
|
+
{announcement}
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
{copyError !== undefined ? (
|
|
93
|
+
<span role="alert" className="tai-field-error">
|
|
94
|
+
<XCircleIcon />
|
|
95
|
+
{copyError}
|
|
96
|
+
</span>
|
|
97
|
+
) : null}
|
|
35
98
|
</div>
|
|
36
99
|
);
|
|
37
100
|
}
|
|
@@ -1316,6 +1316,26 @@
|
|
|
1316
1316
|
color: var(--tai-color-text);
|
|
1317
1317
|
}
|
|
1318
1318
|
|
|
1319
|
+
/* Fullscreen variant: fills the viewport instead of centring at a fixed size.
|
|
1320
|
+
Composes with `.tai-dialog`, overriding only geometry and edge chrome. */
|
|
1321
|
+
.tai-dialog-fullscreen {
|
|
1322
|
+
top: 0;
|
|
1323
|
+
left: 0;
|
|
1324
|
+
transform: none;
|
|
1325
|
+
/* `border-box` so `100vw` is the outer edge and never overflows; declared here,
|
|
1326
|
+
not inherited from `.tai-dialog`. */
|
|
1327
|
+
box-sizing: border-box;
|
|
1328
|
+
width: 100vw;
|
|
1329
|
+
max-width: none;
|
|
1330
|
+
/* `dvh` is the live viewport with a `vh` fallback for browsers below the `dvh` floor. */
|
|
1331
|
+
height: 100vh;
|
|
1332
|
+
height: 100dvh;
|
|
1333
|
+
max-height: none;
|
|
1334
|
+
padding: 0;
|
|
1335
|
+
border: 0;
|
|
1336
|
+
border-radius: 0;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1319
1339
|
.tai-dialog-title {
|
|
1320
1340
|
margin: 0 0 var(--tai-space-2);
|
|
1321
1341
|
font-size: var(--tai-text-section);
|
|
@@ -1,32 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `CopyField` — a show-once box for a freshly-minted secret (an API
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* the value; this component fetches nothing and never re-reads the secret.
|
|
2
|
+
* `CopyField` — a show-once box for a freshly-minted secret (an API or backup key)
|
|
3
|
+
* beside a copy button that writes it to the clipboard and shows a transient "Copied"
|
|
4
|
+
* affordance. PRESENTATIONAL: the caller owns the value; this fetches nothing and
|
|
5
|
+
* never re-reads the secret.
|
|
7
6
|
*
|
|
8
|
-
* The button holds BOTH states stacked in one grid cell, so the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* named "Copy", which fails WCAG 2.5.3 (Label in Name) and leaves a voice-control
|
|
13
|
-
* user naming a control they cannot see. The polite live region beside the button
|
|
14
|
-
* is what actually announces the flip.
|
|
7
|
+
* The button holds BOTH states stacked in one grid cell, so the wider one sets the
|
|
8
|
+
* width once and the flip never reflows. Only the active state is exposed to
|
|
9
|
+
* assistive tech, and its own words are the button's accessible name — a constant
|
|
10
|
+
* `aria-label` would leave a "Copied" button named "Copy" (WCAG 2.5.3).
|
|
15
11
|
*
|
|
16
|
-
* A clipboard
|
|
17
|
-
*
|
|
18
|
-
* Either way the value is a secret shown once, so the failure is rendered as a
|
|
19
|
-
* visible `role="alert"` telling the reader to copy it by hand: throwing it at
|
|
20
|
-
* the console would leave the button looking inert and the secret unrecoverable.
|
|
12
|
+
* A clipboard failure renders as a visible `role="alert"` telling the reader to copy
|
|
13
|
+
* by hand — the secret is shown once, so a swallowed failure leaves it unrecoverable.
|
|
21
14
|
*
|
|
22
|
-
* SAFETY:
|
|
23
|
-
*
|
|
24
|
-
* names the reason, never the value. Pinned by a test.
|
|
15
|
+
* SAFETY: value and caption render as TEXT — never an HTML sink. The value is never
|
|
16
|
+
* logged, and the alert names the reason, never the value. Pinned by a test.
|
|
25
17
|
*/
|
|
26
|
-
import { useEffect, useRef, useState } from 'react';
|
|
27
18
|
import type { CSSProperties } from 'react';
|
|
28
19
|
|
|
29
20
|
import { CheckIcon, CopyIcon, XCircleIcon } from './icons';
|
|
21
|
+
import { COPIED_LABEL, useClipboardCopy } from '../hooks/useClipboardCopy';
|
|
30
22
|
|
|
31
23
|
export interface CopyFieldProps {
|
|
32
24
|
readonly value: string;
|
|
@@ -35,29 +27,8 @@ export interface CopyFieldProps {
|
|
|
35
27
|
readonly label?: string;
|
|
36
28
|
}
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/** The button's two faces. Whichever is exposed is also its accessible name. */
|
|
30
|
+
/** The button's resting face; whichever face is exposed is also its accessible name. */
|
|
41
31
|
const COPY_LABEL = 'Copy';
|
|
42
|
-
const COPIED_LABEL = 'Copied';
|
|
43
|
-
|
|
44
|
-
/** Announced once, and worded so it is never mistaken for the visible label. */
|
|
45
|
-
const COPIED_ANNOUNCEMENT = 'Copied to clipboard';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* `lib.dom` declares `navigator.clipboard` as always present, but the platform
|
|
49
|
-
* exposes it only in a SECURE CONTEXT — over plain http it is not there at all.
|
|
50
|
-
* Reading the navigator through this shape admits the absence the DOM types deny,
|
|
51
|
-
* so the guard below is a real check rather than a cast around one.
|
|
52
|
-
*/
|
|
53
|
-
interface MaybeClipboard {
|
|
54
|
-
readonly clipboard?: Clipboard;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** The clipboard this browser actually offers, or `undefined` when it offers none. */
|
|
58
|
-
function clipboardOf(host: MaybeClipboard): Clipboard | undefined {
|
|
59
|
-
return host.clipboard;
|
|
60
|
-
}
|
|
61
32
|
|
|
62
33
|
/** Shown when the browser offers no clipboard at all (any non-secure context). */
|
|
63
34
|
const NO_CLIPBOARD =
|
|
@@ -91,45 +62,18 @@ const stateStyle: CSSProperties = {
|
|
|
91
62
|
};
|
|
92
63
|
|
|
93
64
|
export function CopyField({ value, caption, idPrefix = 'copy-field', label }: CopyFieldProps) {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
mounted.current = true;
|
|
106
|
-
return () => {
|
|
107
|
-
mounted.current = false;
|
|
108
|
-
if (resetTimer.current !== null) clearTimeout(resetTimer.current);
|
|
109
|
-
};
|
|
110
|
-
}, []);
|
|
65
|
+
const {
|
|
66
|
+
copied,
|
|
67
|
+
error: copyError,
|
|
68
|
+
announcement,
|
|
69
|
+
copy,
|
|
70
|
+
} = useClipboardCopy({
|
|
71
|
+
noClipboard: NO_CLIPBOARD,
|
|
72
|
+
writeFailed,
|
|
73
|
+
});
|
|
111
74
|
|
|
112
75
|
const handleCopy = (): void => {
|
|
113
|
-
|
|
114
|
-
if (clipboard === undefined) {
|
|
115
|
-
setCopyError(NO_CLIPBOARD);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
setCopyError(undefined);
|
|
119
|
-
void clipboard.writeText(value).then(
|
|
120
|
-
() => {
|
|
121
|
-
if (!mounted.current) return;
|
|
122
|
-
setCopied(true);
|
|
123
|
-
if (resetTimer.current !== null) clearTimeout(resetTimer.current);
|
|
124
|
-
resetTimer.current = setTimeout(() => {
|
|
125
|
-
setCopied(false);
|
|
126
|
-
}, COPIED_RESET_MS);
|
|
127
|
-
},
|
|
128
|
-
(error: unknown) => {
|
|
129
|
-
if (!mounted.current) return;
|
|
130
|
-
setCopyError(writeFailed(error));
|
|
131
|
-
},
|
|
132
|
-
);
|
|
76
|
+
void copy(() => value);
|
|
133
77
|
};
|
|
134
78
|
|
|
135
79
|
return (
|
|
@@ -163,7 +107,7 @@ export function CopyField({ value, caption, idPrefix = 'copy-field', label }: Co
|
|
|
163
107
|
</span>
|
|
164
108
|
</button>
|
|
165
109
|
<span aria-live="polite" className="tai-visually-hidden">
|
|
166
|
-
{
|
|
110
|
+
{announcement}
|
|
167
111
|
</span>
|
|
168
112
|
</div>
|
|
169
113
|
{copyError !== undefined ? (
|