@tai42/studio-sdk 0.12.0 → 0.13.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.
Files changed (75) hide show
  1. package/dist/components/code-block.d.ts.map +1 -1
  2. package/dist/components/code-block.js +26 -13
  3. package/dist/components/code-block.js.map +1 -1
  4. package/dist/components/components.css +20 -0
  5. package/dist/components/copy-field.d.ts.map +1 -1
  6. package/dist/components/copy-field.js +8 -73
  7. package/dist/components/copy-field.js.map +1 -1
  8. package/dist/components/date-range-picker.d.ts +73 -0
  9. package/dist/components/date-range-picker.d.ts.map +1 -0
  10. package/dist/components/date-range-picker.js +146 -0
  11. package/dist/components/date-range-picker.js.map +1 -0
  12. package/dist/components/dialog.d.ts +19 -1
  13. package/dist/components/dialog.d.ts.map +1 -1
  14. package/dist/components/dialog.js +16 -13
  15. package/dist/components/dialog.js.map +1 -1
  16. package/dist/components/dirty-guard.d.ts +28 -0
  17. package/dist/components/dirty-guard.d.ts.map +1 -0
  18. package/dist/components/dirty-guard.js +93 -0
  19. package/dist/components/dirty-guard.js.map +1 -0
  20. package/dist/components/extension-picker.d.ts.map +1 -1
  21. package/dist/components/extension-picker.js +7 -0
  22. package/dist/components/extension-picker.js.map +1 -1
  23. package/dist/components/form-dialog.d.ts +45 -0
  24. package/dist/components/form-dialog.d.ts.map +1 -0
  25. package/dist/components/form-dialog.js +71 -0
  26. package/dist/components/form-dialog.js.map +1 -0
  27. package/dist/components/json-tree.d.ts +7 -1
  28. package/dist/components/json-tree.d.ts.map +1 -1
  29. package/dist/components/json-tree.js +300 -13
  30. package/dist/components/json-tree.js.map +1 -1
  31. package/dist/components/markdown.d.ts +60 -0
  32. package/dist/components/markdown.d.ts.map +1 -0
  33. package/dist/components/markdown.js +435 -0
  34. package/dist/components/markdown.js.map +1 -0
  35. package/dist/components/tabs.d.ts +3 -1
  36. package/dist/components/tabs.d.ts.map +1 -1
  37. package/dist/components/tabs.js +2 -2
  38. package/dist/components/tabs.js.map +1 -1
  39. package/dist/hooks/useApi.d.ts +56 -28
  40. package/dist/hooks/useApi.d.ts.map +1 -1
  41. package/dist/hooks/useClipboardCopy.d.ts +26 -0
  42. package/dist/hooks/useClipboardCopy.d.ts.map +1 -0
  43. package/dist/hooks/useClipboardCopy.js +72 -0
  44. package/dist/hooks/useClipboardCopy.js.map +1 -0
  45. package/dist/index.d.ts +9 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +5 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/navigation/context.d.ts +13 -1
  50. package/dist/navigation/context.d.ts.map +1 -1
  51. package/dist/navigation/context.js +205 -7
  52. package/dist/navigation/context.js.map +1 -1
  53. package/dist/navigation/index.d.ts +2 -2
  54. package/dist/navigation/index.d.ts.map +1 -1
  55. package/dist/navigation/index.js +1 -1
  56. package/dist/navigation/index.js.map +1 -1
  57. package/dist/navigation/types.d.ts +11 -0
  58. package/dist/navigation/types.d.ts.map +1 -1
  59. package/package.json +2 -2
  60. package/src/components/code-block.tsx +72 -9
  61. package/src/components/components.css +20 -0
  62. package/src/components/copy-field.tsx +25 -81
  63. package/src/components/date-range-picker.tsx +256 -0
  64. package/src/components/dialog.tsx +59 -18
  65. package/src/components/dirty-guard.tsx +153 -0
  66. package/src/components/extension-picker.tsx +12 -0
  67. package/src/components/form-dialog.tsx +133 -0
  68. package/src/components/json-tree.tsx +477 -42
  69. package/src/components/markdown.tsx +552 -0
  70. package/src/components/tabs.tsx +10 -1
  71. package/src/hooks/useClipboardCopy.ts +105 -0
  72. package/src/index.ts +18 -0
  73. package/src/navigation/context.tsx +245 -7
  74. package/src/navigation/index.ts +2 -0
  75. package/src/navigation/types.ts +12 -0
@@ -1,13 +1,189 @@
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
- * Shell-provided navigation. The context has no default: a feature rendered
4
- * outside the shell's {@link NavigationProvider} is a wiring bug, so
5
- * {@link useNavigation} raises loudly rather than silently no-op'ing a
6
- * transition.
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
- const NavigationContext = createContext(null);
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
+ function useGuardRegistry() {
62
+ const registry = useContext(GuardRegistryContext);
63
+ if (registry === null) {
64
+ throw new Error('useNavigationGuard must be used within a <NavigationProvider> (the shell provides it).');
65
+ }
66
+ return registry;
67
+ }
9
68
  export function NavigationProvider({ value, children, }) {
10
- return createElement(NavigationContext.Provider, { value }, children);
69
+ const registryRef = useRef(null);
70
+ registryRef.current ??= new NavigationGuardRegistry();
71
+ const registry = registryRef.current;
72
+ // The last committed history entry the SDK observed (URL + `history.state`): what a
73
+ // canceled back/forward restores to. URL changes made outside the SDK's navigate
74
+ // entry points are unobservable by design at this layer.
75
+ const committedRef = useRef(null);
76
+ // Mirror the registry's armed state into React so the browser listeners attach only
77
+ // while at least one guard is armed.
78
+ const [armed, setArmed] = useState(false);
79
+ useEffect(() => {
80
+ setArmed(registry.hasArmedGuards());
81
+ return registry.subscribe(() => {
82
+ setArmed(registry.hasArmedGuards());
83
+ });
84
+ }, [registry]);
85
+ useEffect(() => {
86
+ if (!armed)
87
+ return;
88
+ // The protected entry (URL + state); a back/forward landing elsewhere is first
89
+ // canceled back to here while the guard is consulted.
90
+ committedRef.current = { href: window.location.href, state: window.history.state };
91
+ // Set while replaying a guard-approved back/forward, so its `popstate` passes
92
+ // through instead of being re-intercepted.
93
+ let bypass = false;
94
+ // Latched while a decision is pending, so a second Back mid-decision keeps canceling
95
+ // without starting a concurrent `registry.run()` — one gesture, one dialog.
96
+ let pending = false;
97
+ // Restore pushes stacked for the pending decision, so an approved replay unwinds
98
+ // exactly that far in one `go(-depth)`. Reset per decision.
99
+ let restores = 0;
100
+ const onPopState = () => {
101
+ if (bypass) {
102
+ bypass = false;
103
+ committedRef.current = { href: window.location.href, state: window.history.state };
104
+ return;
105
+ }
106
+ if (!registry.hasArmedGuards()) {
107
+ committedRef.current = { href: window.location.href, state: window.history.state };
108
+ return;
109
+ }
110
+ const committed = committedRef.current;
111
+ if (committed === null)
112
+ return;
113
+ // Cancel the browser's move by pushing the committed entry on top of wherever it
114
+ // landed. popstate exposes no direction/distance and the router owns
115
+ // `history.state`, so restore can only push, not delete. A vetoed forward thus
116
+ // strands a stub entry — an accepted limit the next pushState truncates.
117
+ window.history.pushState(committed.state, '', committed.href);
118
+ restores += 1;
119
+ if (pending)
120
+ return;
121
+ pending = true;
122
+ void registry.run().then((allowed) => {
123
+ pending = false;
124
+ const depth = restores;
125
+ restores = 0;
126
+ if (!allowed)
127
+ return;
128
+ bypass = true;
129
+ window.history.go(-depth);
130
+ });
131
+ };
132
+ const onBeforeUnload = (event) => {
133
+ event.preventDefault();
134
+ // Browsers below the floor (Chrome 108) gate the prompt on a set `returnValue`,
135
+ // not `preventDefault` alone; the string is ignored. Typed deprecated, still required.
136
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- required beforeunload trigger for the supported browser range.
137
+ event.returnValue = '';
138
+ };
139
+ window.addEventListener('popstate', onPopState);
140
+ window.addEventListener('beforeunload', onBeforeUnload);
141
+ return () => {
142
+ committedRef.current = null;
143
+ window.removeEventListener('popstate', onPopState);
144
+ window.removeEventListener('beforeunload', onBeforeUnload);
145
+ };
146
+ }, [armed, registry]);
147
+ // Wrap the shell's transitions so an armed guard is consulted before they commit.
148
+ // With no guard armed the call stays synchronous — identical to the raw shell value.
149
+ const guarded = useMemo(() => {
150
+ const navigate = (token, search) => {
151
+ if (!registry.hasArmedGuards()) {
152
+ value.navigate(token, search);
153
+ return;
154
+ }
155
+ void registry.run().then((allowed) => {
156
+ if (!allowed)
157
+ return;
158
+ value.navigate(token, search);
159
+ // Advance the committed entry to the resolved destination (the router may commit
160
+ // async, so `window.location` would be stale). Its router state is unobservable
161
+ // here, hence `state: null`.
162
+ if (committedRef.current !== null) {
163
+ committedRef.current = { href: value.resolvePath(token, search), state: null };
164
+ }
165
+ });
166
+ };
167
+ const navigatePlugin = (pluginId, pagePath, params, search) => {
168
+ if (!registry.hasArmedGuards()) {
169
+ value.navigatePlugin(pluginId, pagePath, params, search);
170
+ return;
171
+ }
172
+ void registry.run().then((allowed) => {
173
+ if (!allowed)
174
+ return;
175
+ value.navigatePlugin(pluginId, pagePath, params, search);
176
+ if (committedRef.current !== null) {
177
+ committedRef.current = {
178
+ href: value.resolvePluginPath(pluginId, pagePath, params, search),
179
+ state: null,
180
+ };
181
+ }
182
+ });
183
+ };
184
+ return { ...value, navigate, navigatePlugin };
185
+ }, [value, registry]);
186
+ return createElement(GuardRegistryContext.Provider, { value: registry }, createElement(NavigationContext.Provider, { value: guarded }, children));
11
187
  }
12
188
  function useNavigation() {
13
189
  const ctx = useContext(NavigationContext);
@@ -24,6 +200,28 @@ export function useAppNavigate() {
24
200
  export function useResolvePath() {
25
201
  return useNavigation().resolvePath;
26
202
  }
203
+ /**
204
+ * Block navigation away from the current page while `when` is true. `handler` is
205
+ * consulted before any SDK-controlled navigation commits — a route-token navigate,
206
+ * a plugin navigate, or a browser back/forward — and the navigation proceeds only if
207
+ * it resolves `true`; a promise lets the handler drive a confirm dialog. A full-page
208
+ * unload (tab close / refresh) is additionally covered by a native `beforeunload`
209
+ * prompt that fires whenever any guard is armed.
210
+ *
211
+ * Guards compose against the provider's shared registry: a feature and a plugin page
212
+ * may each arm one, and any veto blocks.
213
+ */
214
+ export function useNavigationGuard(when, handler) {
215
+ const registry = useGuardRegistry();
216
+ const entryRef = useRef({ when, handler });
217
+ // Keep the registered entry pointing at the latest inputs without re-registering.
218
+ entryRef.current.when = when;
219
+ entryRef.current.handler = handler;
220
+ useEffect(() => registry.add(entryRef.current), [registry]);
221
+ useEffect(() => {
222
+ registry.touch();
223
+ }, [registry, when]);
224
+ }
27
225
  /**
28
226
  * Plugin-page navigation: `navigatePlugin` drives a client-side transition to a
29
227
  * 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,GAIX,MAAM,OAAO,CAAC;AAIf;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,aAAa,CAAgC,IAAI,CAAC,CAAC;AAE7E,MAAM,UAAU,kBAAkB,CAAC,EACjC,KAAK,EACL,QAAQ,GAIT;IACC,OAAO,aAAa,CAAC,iBAAiB,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,aAAa;IACpB,MAAM,GAAG,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAC1C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,cAAc;IAC5B,OAAO,aAAa,EAAE,CAAC,QAAQ,CAAC;AAClC,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,cAAc;IAC5B,OAAO,aAAa,EAAE,CAAC,WAAW,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB;IAIjC,MAAM,EAAE,cAAc,EAAE,iBAAiB,EAAE,GAAG,aAAa,EAAE,CAAC;IAC9D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC;AAC/C,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAuB,EAC5C,EAAE,EACF,MAAM,EACN,QAAQ,EACR,SAAS,EACT,YAAY,EAAE,SAAS,EACvB,cAAc,EAAE,WAAW,GACX;IAChB,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,aAAa,EAAE,CAAC;IAClD,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,KAAoC,EAAE,EAAE;QACvC,IAAI,KAAK,CAAC,gBAAgB;YAAE,OAAO;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM;YACxF,OAAO;QACT,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACvB,CAAC,EACD,CAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,CACvB,CAAC;IACF,OAAO,aAAa,CAClB,GAAG,EACH,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,EAClF,QAAQ,CACT,CAAC;AACJ,CAAC"}
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, } 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,GACpB,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,GACb,MAAM,SAAS,CAAC"}
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,GACnB,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"}
@@ -1,2 +1,2 @@
1
- export { NavigationProvider, AppLink, useAppNavigate, useResolvePath, usePluginNavigation, } from './context';
1
+ export { NavigationProvider, AppLink, useAppNavigate, useResolvePath, usePluginNavigation, useNavigationGuard, } 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,GACpB,MAAM,WAAW,CAAC"}
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,GACnB,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.12.0",
3
+ "version": "0.13.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.12.0"
92
+ "@tai42/api-client": "0.13.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>`, so it is escaped by React and
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, so it carries the region attributes itself
9
- * rather than sitting inside a `ScrollRegion` wrapper that would add a second
10
- * scroller: a long line makes it a named keyboard target, a short one leaves it
11
- * an ordinary block.
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
- <pre className="tai-code-block" {...region}>
33
- <code>{code}</code>
34
- </pre>
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 key or a
3
- * minted backup key). It renders the value in a monospace, break-all,
4
- * select-all box beside a copy button that writes the value to the clipboard and
5
- * shows a transient "Copied" affordance. It is PRESENTATIONAL: the caller owns
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 widest of them
9
- * sets the width once and the flip never reflows the row. Only the active state
10
- * is exposed to assistive tech, and that state's own words are the button's
11
- * accessible name: a constant `aria-label` would leave a button reading "Copied"
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 write can FAIL — a permissions policy blocks it, or the page is
17
- * not in a secure context, where `navigator.clipboard` does not exist at all.
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: the value and caption render as TEXT (React escapes them) — never an
23
- * HTML sink. The value is never logged, and neither is a failure — the alert
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
- const COPIED_RESET_MS = 2000;
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 [copied, setCopied] = useState(false);
95
- const [copyError, setCopyError] = useState<string | undefined>(undefined);
96
- const resetTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
97
- // The clipboard write is async, so a copy can still be in flight when the
98
- // component goes away — the caller's dialog closes on the same click. Clearing
99
- // the timer alone would not cover that: at unmount there is no timer yet, and
100
- // the resolution that follows would start one nothing is left to clear. This
101
- // flag is what the resolution checks before it touches state at all.
102
- const mounted = useRef(true);
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
- const clipboard = clipboardOf(navigator);
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
- {copied ? COPIED_ANNOUNCEMENT : ''}
110
+ {announcement}
167
111
  </span>
168
112
  </div>
169
113
  {copyError !== undefined ? (