@tangle-network/ui 11.36.0 → 11.38.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 (37) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/code-presentation.d.ts +2 -0
  3. package/dist/code-presentation.js +2 -0
  4. package/dist/index.d.ts +5 -2
  5. package/dist/index.js +5 -2
  6. package/dist/lib/code-presentation.d.ts +92 -0
  7. package/dist/lib/code-presentation.js +437 -0
  8. package/dist/lib/surface-motion.js +34 -13
  9. package/dist/markdown/api-schema.d.ts +9 -0
  10. package/dist/markdown/api-schema.js +20 -0
  11. package/dist/markdown/code-block.d.ts +15 -7
  12. package/dist/markdown/code-block.js +120 -268
  13. package/dist/markdown.d.ts +4 -2
  14. package/dist/markdown.js +4 -2
  15. package/dist/primitives/button.js +2 -1
  16. package/dist/primitives/code-block.d.ts +1 -1
  17. package/dist/primitives/popover.js +11 -3
  18. package/dist/primitives/wizard.d.ts +30 -0
  19. package/dist/primitives/wizard.js +158 -0
  20. package/dist/primitives.d.ts +2 -1
  21. package/dist/primitives.js +2 -1
  22. package/package.json +7 -2
  23. package/src/code-presentation.ts +2 -0
  24. package/src/lib/code-presentation.ts +257 -0
  25. package/src/lib/surface-motion.tsx +22 -15
  26. package/src/markdown/api-presentation.test.tsx +63 -0
  27. package/src/markdown/api-schema.tsx +9 -0
  28. package/src/markdown/code-block.tsx +54 -185
  29. package/src/markdown/index.ts +2 -1
  30. package/src/primitives/button.tsx +2 -1
  31. package/src/primitives/code-block.tsx +1 -1
  32. package/src/primitives/index.ts +3 -0
  33. package/src/primitives/popover.tsx +6 -8
  34. package/src/primitives/wizard.stories.tsx +51 -0
  35. package/src/primitives/wizard.test.tsx +106 -0
  36. package/src/primitives/wizard.tsx +212 -0
  37. package/src/styles/presentation.css +8 -0
@@ -0,0 +1,437 @@
1
+ import { tabsPresentation } from "../presentation.js";
2
+ //#region src/lib/code-presentation.ts
3
+ const SYNTAX = {
4
+ comment: "var(--syntax-comment, currentColor)",
5
+ keyword: "var(--syntax-keyword, currentColor)",
6
+ string: "var(--syntax-string, currentColor)",
7
+ function: "var(--syntax-function, currentColor)",
8
+ number: "var(--syntax-number, currentColor)",
9
+ variable: "var(--syntax-variable, currentColor)",
10
+ foreground: "var(--syntax-foreground, currentColor)"
11
+ };
12
+ const SHIKI_THEME = "tangle-syntax";
13
+ const BRAND_COLOR_FOR_SHIKI = {
14
+ "var(--shiki-foreground)": SYNTAX.foreground,
15
+ "var(--shiki-token-comment)": SYNTAX.comment,
16
+ "var(--shiki-token-keyword)": SYNTAX.keyword,
17
+ "var(--shiki-token-string)": SYNTAX.string,
18
+ "var(--shiki-token-string-expression)": SYNTAX.string,
19
+ "var(--shiki-token-function)": SYNTAX.function,
20
+ "var(--shiki-token-constant)": SYNTAX.number,
21
+ "var(--shiki-token-link)": SYNTAX.number,
22
+ "var(--shiki-token-parameter)": SYNTAX.variable,
23
+ "var(--shiki-token-punctuation)": SYNTAX.foreground
24
+ };
25
+ const GRAMMARS = {
26
+ typescript: () => import("shiki/langs/typescript.mjs"),
27
+ tsx: () => import("shiki/langs/tsx.mjs"),
28
+ javascript: () => import("shiki/langs/javascript.mjs"),
29
+ jsx: () => import("shiki/langs/jsx.mjs"),
30
+ json: () => import("shiki/langs/json.mjs"),
31
+ jsonc: () => import("shiki/langs/jsonc.mjs"),
32
+ bash: () => import("shiki/langs/bash.mjs"),
33
+ python: () => import("shiki/langs/python.mjs"),
34
+ html: () => import("shiki/langs/html.mjs"),
35
+ css: () => import("shiki/langs/css.mjs"),
36
+ scss: () => import("shiki/langs/scss.mjs"),
37
+ markdown: () => import("shiki/langs/markdown.mjs"),
38
+ mdx: () => import("shiki/langs/mdx.mjs"),
39
+ yaml: () => import("shiki/langs/yaml.mjs"),
40
+ toml: () => import("shiki/langs/toml.mjs"),
41
+ sql: () => import("shiki/langs/sql.mjs"),
42
+ diff: () => import("shiki/langs/diff.mjs"),
43
+ go: () => import("shiki/langs/go.mjs"),
44
+ rust: () => import("shiki/langs/rust.mjs"),
45
+ solidity: () => import("shiki/langs/solidity.mjs"),
46
+ ruby: () => import("shiki/langs/ruby.mjs"),
47
+ java: () => import("shiki/langs/java.mjs"),
48
+ kotlin: () => import("shiki/langs/kotlin.mjs"),
49
+ swift: () => import("shiki/langs/swift.mjs"),
50
+ php: () => import("shiki/langs/php.mjs"),
51
+ c: () => import("shiki/langs/c.mjs"),
52
+ cpp: () => import("shiki/langs/cpp.mjs"),
53
+ csharp: () => import("shiki/langs/csharp.mjs"),
54
+ graphql: () => import("shiki/langs/graphql.mjs"),
55
+ xml: () => import("shiki/langs/xml.mjs"),
56
+ dockerfile: () => import("shiki/langs/dockerfile.mjs")
57
+ };
58
+ const LANGUAGE_ALIASES = {
59
+ ts: "typescript",
60
+ mts: "typescript",
61
+ cts: "typescript",
62
+ js: "javascript",
63
+ mjs: "javascript",
64
+ cjs: "javascript",
65
+ node: "javascript",
66
+ sh: "bash",
67
+ shell: "bash",
68
+ zsh: "bash",
69
+ console: "bash",
70
+ shellscript: "bash",
71
+ py: "python",
72
+ python3: "python",
73
+ yml: "yaml",
74
+ md: "markdown",
75
+ rs: "rust",
76
+ sol: "solidity",
77
+ rb: "ruby",
78
+ kt: "kotlin",
79
+ cs: "csharp",
80
+ "c#": "csharp",
81
+ "c++": "cpp",
82
+ gql: "graphql",
83
+ svg: "xml",
84
+ docker: "dockerfile",
85
+ patch: "diff",
86
+ htm: "html"
87
+ };
88
+ const LANGUAGE_NAMES = {
89
+ typescript: "TypeScript",
90
+ tsx: "TSX",
91
+ javascript: "JavaScript",
92
+ jsx: "JSX",
93
+ json: "JSON",
94
+ jsonc: "JSONC",
95
+ bash: "Bash",
96
+ python: "Python",
97
+ html: "HTML",
98
+ css: "CSS",
99
+ scss: "SCSS",
100
+ markdown: "Markdown",
101
+ mdx: "MDX",
102
+ yaml: "YAML",
103
+ toml: "TOML",
104
+ sql: "SQL",
105
+ diff: "Diff",
106
+ go: "Go",
107
+ rust: "Rust",
108
+ solidity: "Solidity",
109
+ ruby: "Ruby",
110
+ java: "Java",
111
+ kotlin: "Kotlin",
112
+ swift: "Swift",
113
+ php: "PHP",
114
+ c: "C",
115
+ cpp: "C++",
116
+ csharp: "C#",
117
+ graphql: "GraphQL",
118
+ xml: "XML",
119
+ dockerfile: "Dockerfile"
120
+ };
121
+ /** The Shiki grammar for a fence language, or undefined for plain text. */
122
+ const PLAIN_TEXT = /* @__PURE__ */ new Set([
123
+ "text",
124
+ "txt",
125
+ "plain",
126
+ "plaintext"
127
+ ]);
128
+ function codeBlockGrammar(language) {
129
+ if (!language) return void 0;
130
+ const id = language.toLowerCase();
131
+ const resolved = LANGUAGE_ALIASES[id] ?? id;
132
+ return GRAMMARS[resolved] ? resolved : void 0;
133
+ }
134
+ let highlighterPromise;
135
+ const grammarLoads = /* @__PURE__ */ new Map();
136
+ function loadHighlighter() {
137
+ return highlighterPromise ??= Promise.all([import("shiki/core"), import("shiki/engine/javascript")]).then(([core, engine]) => core.createHighlighterCore({
138
+ themes: [core.createCssVariablesTheme({
139
+ name: SHIKI_THEME,
140
+ variablePrefix: "--shiki-",
141
+ fontStyle: true
142
+ })],
143
+ langs: [],
144
+ engine: engine.createJavaScriptRegexEngine()
145
+ })).catch((error) => {
146
+ highlighterPromise = void 0;
147
+ throw error;
148
+ });
149
+ }
150
+ async function tokenize(code, grammar) {
151
+ const highlighter = await loadHighlighter();
152
+ let load = grammarLoads.get(grammar);
153
+ if (!load) {
154
+ load = GRAMMARS[grammar]().then((module) => highlighter.loadLanguage(module.default));
155
+ load.catch(() => grammarLoads.delete(grammar));
156
+ grammarLoads.set(grammar, load);
157
+ }
158
+ await load;
159
+ return highlighter.codeToTokensBase(code, {
160
+ lang: grammar,
161
+ theme: SHIKI_THEME
162
+ });
163
+ }
164
+ function tokenStyle(token) {
165
+ const color = token.color ? BRAND_COLOR_FOR_SHIKI[token.color] ?? SYNTAX.foreground : void 0;
166
+ const style = token.fontStyle ?? 0;
167
+ if (!color && !style) return void 0;
168
+ return {
169
+ ...color ? { color } : {},
170
+ ...style & 1 ? { fontStyle: "italic" } : {},
171
+ ...style & 2 ? { fontWeight: "bold" } : {},
172
+ ...style & 4 ? { textDecoration: "underline" } : {}
173
+ };
174
+ }
175
+ const codePreStyle = {
176
+ margin: 0,
177
+ padding: "var(--code-padding-y, 0.625rem) var(--code-padding-x, 0.75rem)",
178
+ background: "transparent",
179
+ color: SYNTAX.foreground,
180
+ fontSize: "var(--code-font-size, 0.8125rem)",
181
+ lineHeight: "var(--code-line-height, 1.5)",
182
+ overflowX: "auto"
183
+ };
184
+ const codeTextStyle = {
185
+ fontFamily: "var(--font-mono, 'JetBrains Mono', ui-monospace, monospace)",
186
+ whiteSpace: "pre"
187
+ };
188
+ const codePresentation = {
189
+ tabs: `tangle-code-tabs ${tabsPresentation.list.underline}`,
190
+ tab: tabsPresentation.trigger.underline,
191
+ root: "group relative min-w-0 overflow-hidden rounded-lg border font-mono bg-card border-border",
192
+ header: "flex flex-wrap items-center justify-between gap-3 border-b border-border px-3 py-1.5 bg-[var(--code-header-bg,hsl(var(--background)))]",
193
+ copy: "flex items-center justify-center min-w-8 min-h-8 pointer-coarse:min-w-11 pointer-coarse:min-h-11 rounded-md bg-muted border border-border hover:border-[var(--border-strong)] transition-colors data-[copy-loading=true]:after:content-['…'] data-[copy-loading=true]:gap-1",
194
+ copyStatus: "sr-only data-[copy-long-work=true]:not-sr-only data-[copy-long-work=true]:text-xs data-[copy-long-work=true]:text-muted-foreground"
195
+ };
196
+ const apiMetadataText = (meta) => [
197
+ meta.kind,
198
+ meta.method,
199
+ meta.status,
200
+ meta.contentType
201
+ ].filter((v) => v !== void 0).join(" · ");
202
+ /** Same lazy grammar/token model for React, Hono SSR and static renderers. */
203
+ async function highlightCode(code, language) {
204
+ const grammar = codeBlockGrammar(language);
205
+ if (!grammar) return code.split("\n").map((content) => [{
206
+ content,
207
+ style: void 0
208
+ }]);
209
+ return (await tokenize(code, grammar)).map((line) => line.map((token) => ({
210
+ content: token.content,
211
+ style: tokenStyle(token)
212
+ })));
213
+ }
214
+ /** Legacy fallback is used only when the modern clipboard API is absent. */
215
+ async function writeClipboard(text) {
216
+ if (navigator.clipboard?.writeText) {
217
+ await navigator.clipboard.writeText(text);
218
+ return;
219
+ }
220
+ const active = document.activeElement, selection = document.getSelection();
221
+ const ranges = selection ? Array.from({ length: selection.rangeCount }, (_, i) => selection.getRangeAt(i).cloneRange()) : [];
222
+ const field = document.createElement("textarea");
223
+ field.value = text;
224
+ field.readOnly = true;
225
+ field.tabIndex = -1;
226
+ field.setAttribute("aria-hidden", "true");
227
+ field.style.position = "fixed";
228
+ field.style.left = "-9999px";
229
+ document.body.append(field);
230
+ try {
231
+ field.select();
232
+ if (!document.execCommand?.("copy")) throw Error("Clipboard unavailable");
233
+ } finally {
234
+ field.remove();
235
+ active?.focus({ preventScroll: true });
236
+ if (selection) {
237
+ selection.removeAllRanges();
238
+ for (const range of ranges) selection.addRange(range);
239
+ }
240
+ }
241
+ }
242
+ const COPY_FEEDBACK_MS = 2e3;
243
+ /** Resolve scoped Brand times, including calc(), without duplicating token values. */
244
+ function interactionBudgets(scope) {
245
+ const view = scope.ownerDocument.defaultView;
246
+ if (!view) return {};
247
+ const probe = scope.ownerDocument.createElement("i");
248
+ probe.hidden = true;
249
+ scope.append(probe);
250
+ const read = (role) => {
251
+ const name = `--interaction-${role}`;
252
+ if (!view.getComputedStyle(scope).getPropertyValue(name).trim()) return void 0;
253
+ probe.style.animationDuration = `var(${name})`;
254
+ const time = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(view.getComputedStyle(probe).animationDuration);
255
+ return time ? Number(time[1]) * (time[2] === "s" ? 1e3 : 1) : void 0;
256
+ };
257
+ try {
258
+ return {
259
+ feedback: read("feedback-budget"),
260
+ pending: read("pending-delay"),
261
+ longWork: read("long-work-delay"),
262
+ mutation: read("mutation-budget")
263
+ };
264
+ } finally {
265
+ probe.remove();
266
+ }
267
+ }
268
+ /** Owns one accepted write, scoped delays and cleanup for both renderers. */
269
+ function createCopyFeedback(change, write = writeClipboard, scope) {
270
+ let disposed = false, pending = false, timer;
271
+ let delays = [], elapsed;
272
+ const clearPending = () => {
273
+ for (const delay of delays) clearTimeout(delay);
274
+ delays = [];
275
+ if (elapsed) clearInterval(elapsed);
276
+ elapsed = void 0;
277
+ };
278
+ return {
279
+ async copy(text) {
280
+ if (disposed || pending) return;
281
+ const started = performance.now(), budgets = scope ? interactionBudgets(scope) : {};
282
+ pending = true;
283
+ if (timer) clearTimeout(timer);
284
+ let loading = budgets.pending === void 0, longRunning = false;
285
+ const announce = () => {
286
+ const elapsedMs = performance.now() - started;
287
+ change({
288
+ copied: false,
289
+ pending: true,
290
+ loading,
291
+ longRunning,
292
+ elapsedMs,
293
+ status: longRunning ? `Copying · ${Math.floor(elapsedMs / 1e3)}s elapsed. Time remaining unknown. Cannot cancel this clipboard write; waiting for confirmation.` : "Copying to clipboard"
294
+ });
295
+ };
296
+ announce();
297
+ const feedbackMs = performance.now() - started;
298
+ if (budgets.pending !== void 0) delays.push(setTimeout(() => {
299
+ loading = true;
300
+ announce();
301
+ }, budgets.pending));
302
+ if (budgets.longWork !== void 0) delays.push(setTimeout(() => {
303
+ longRunning = true;
304
+ announce();
305
+ elapsed = setInterval(announce, 1e3);
306
+ }, budgets.longWork));
307
+ let copied;
308
+ try {
309
+ await write(text);
310
+ copied = true;
311
+ } catch {
312
+ copied = false;
313
+ }
314
+ pending = false;
315
+ clearPending();
316
+ if (disposed) return;
317
+ const elapsedMs = performance.now() - started;
318
+ change({
319
+ copied,
320
+ status: copied ? "Copied to clipboard" : "Copy failed",
321
+ pending: false,
322
+ measurement: {
323
+ elapsedMs,
324
+ feedbackMs,
325
+ budgets,
326
+ mutationBudgetExceeded: budgets.mutation === void 0 ? void 0 : elapsedMs > budgets.mutation
327
+ }
328
+ });
329
+ timer = setTimeout(() => {
330
+ if (!disposed) change({
331
+ copied: false,
332
+ status: "",
333
+ pending: false
334
+ });
335
+ }, COPY_FEEDBACK_MS);
336
+ },
337
+ dispose() {
338
+ disposed = true;
339
+ clearPending();
340
+ if (timer) clearTimeout(timer);
341
+ }
342
+ };
343
+ }
344
+ function bindCopyButton(button, status, text) {
345
+ const previous = {
346
+ label: button.getAttribute("aria-label"),
347
+ status: status.textContent,
348
+ disabled: button.getAttribute("aria-disabled"),
349
+ busy: button.getAttribute("aria-busy"),
350
+ loading: button.getAttribute("data-copy-loading"),
351
+ long: status.getAttribute("data-copy-long-work")
352
+ };
353
+ const restore = (element, name, value) => {
354
+ if (value === null) element.removeAttribute(name);
355
+ else element.setAttribute(name, value);
356
+ };
357
+ const feedback = createCopyFeedback((state) => {
358
+ status.textContent = state.status;
359
+ status.setAttribute("data-copy-long-work", String(!!state.longRunning));
360
+ button.setAttribute("data-copy-loading", String(!!state.loading));
361
+ button.setAttribute("aria-label", state.pending ? "Copying to clipboard" : state.copied ? "Copied" : "Copy to clipboard");
362
+ if (state.pending) {
363
+ button.setAttribute("aria-disabled", "true");
364
+ button.setAttribute("aria-busy", "true");
365
+ } else {
366
+ restore(button, "aria-disabled", previous.disabled);
367
+ restore(button, "aria-busy", previous.busy);
368
+ }
369
+ }, writeClipboard, button);
370
+ const click = () => {
371
+ if (!button.disabled && button.getAttribute("aria-disabled") !== "true") feedback.copy(text());
372
+ };
373
+ button.addEventListener("click", click);
374
+ return () => {
375
+ button.removeEventListener("click", click);
376
+ feedback.dispose();
377
+ restore(button, "aria-label", previous.label);
378
+ status.textContent = previous.status;
379
+ restore(button, "aria-disabled", previous.disabled);
380
+ restore(button, "aria-busy", previous.busy);
381
+ restore(button, "data-copy-loading", previous.loading);
382
+ restore(status, "data-copy-long-work", previous.long);
383
+ };
384
+ }
385
+ /** Native tabs adapter. The consumer owns labels, IDs and escaped panel content. */
386
+ function bindCodeTabs(list, onSelect) {
387
+ const buttons = () => [...list.querySelectorAll("[role=\"tab\"]")];
388
+ const select = (index, focus) => {
389
+ const tabs = buttons(), tab = tabs[index];
390
+ if (!tab) return;
391
+ for (const panel of new Set(tabs.map((t) => document.getElementById(t.getAttribute("aria-controls") ?? "")))) if (panel) panel.hidden = true;
392
+ tabs.forEach((t, i) => {
393
+ t.setAttribute("aria-selected", String(i === index));
394
+ t.dataset.state = i === index ? "active" : "inactive";
395
+ t.tabIndex = i === index ? 0 : -1;
396
+ });
397
+ const panel = document.getElementById(tab.getAttribute("aria-controls") ?? "");
398
+ if (panel) {
399
+ panel.hidden = false;
400
+ panel.setAttribute("aria-labelledby", tab.id);
401
+ }
402
+ if (focus) tab.focus();
403
+ onSelect?.(index);
404
+ };
405
+ const click = (event) => {
406
+ const tab = event.target.closest("[role=\"tab\"]");
407
+ const index = buttons().findIndex((t) => t === tab);
408
+ if (index >= 0) select(index, false);
409
+ };
410
+ const key = (event) => {
411
+ const tabs = buttons(), current = tabs.findIndex((t) => t === document.activeElement);
412
+ if (current < 0) return;
413
+ const next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : event.key === "ArrowRight" ? (current + 1) % tabs.length : event.key === "ArrowLeft" ? (current + tabs.length - 1) % tabs.length : void 0;
414
+ if (next !== void 0) {
415
+ event.preventDefault();
416
+ select(next, true);
417
+ }
418
+ };
419
+ list.addEventListener("click", click);
420
+ list.addEventListener("keydown", key);
421
+ return () => {
422
+ list.removeEventListener("click", click);
423
+ list.removeEventListener("keydown", key);
424
+ };
425
+ }
426
+ function schemaRows(fields, parent = "", depth = 0) {
427
+ return fields.flatMap((field) => {
428
+ const path = parent ? `${parent}.${field.name}` : field.name;
429
+ return [{
430
+ ...field,
431
+ path,
432
+ depth
433
+ }, ...schemaRows(field.children ?? [], path, depth + 1)];
434
+ });
435
+ }
436
+ //#endregion
437
+ export { LANGUAGE_NAMES, PLAIN_TEXT, SYNTAX, apiMetadataText, bindCodeTabs, bindCopyButton, codeBlockGrammar, codePreStyle, codePresentation, codeTextStyle, createCopyFeedback, highlightCode, interactionBudgets, schemaRows, tokenStyle, tokenize, writeClipboard };
@@ -2,13 +2,13 @@
2
2
  import { Slot } from "@radix-ui/react-slot";
3
3
  import * as React from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
- import { animate } from "motion";
5
+ import { animate, motionValue, styleEffect } from "motion";
6
6
  //#region src/lib/surface-motion.tsx
7
7
  /** Radix owns state, focus and presence; Motion only retargets the visible object. */
8
8
  const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-state": state, ...props }, forwardedRef) => {
9
9
  const element = React.useRef(null);
10
- const initialized = React.useRef(false);
11
- const controls = React.useRef(void 0);
10
+ const values = React.useRef(null);
11
+ const controls = React.useRef([]);
12
12
  const ref = React.useCallback((node) => {
13
13
  element.current = node;
14
14
  if (typeof forwardedRef === "function") {
@@ -23,12 +23,13 @@ const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-s
23
23
  const node = element.current;
24
24
  if (!node) return;
25
25
  const preference = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : void 0;
26
+ let cancelRender;
26
27
  const run = () => {
27
28
  const closed = state === "closed";
28
29
  const bounds = node.getBoundingClientRect();
29
30
  const hidden = {
30
31
  opacity: kind === "sheet" ? 1 : 0,
31
- scale: kind === "dialog" || kind === "tooltip" ? .98 : 1,
32
+ scale: kind === "dialog" || kind === "tooltip" || kind === "popover" ? .98 : 1,
32
33
  x: kind === "sheet" ? side === "left" ? -bounds.width : side === "right" ? bounds.width : 0 : 0,
33
34
  y: kind === "sheet" ? side === "top" ? -bounds.height : side === "bottom" ? bounds.height : 0 : 0
34
35
  };
@@ -46,33 +47,53 @@ const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-s
46
47
  node.style.transform = `translateX(${value.x}px) translateY(${value.y}px) scale(${value.scale})`;
47
48
  };
48
49
  if (preference?.matches || seconds <= 0 || style.animationName === "none") {
49
- controls.current?.stop();
50
+ controls.current.forEach((control) => control.stop());
51
+ if (values.current) for (const key of [
52
+ "opacity",
53
+ "scale",
54
+ "x",
55
+ "y"
56
+ ]) values.current[key].jump(target[key]);
50
57
  place(target);
51
- if (initialized.current) controls.current = animate(node, target, { duration: 0 });
52
58
  return;
53
59
  }
54
- if (!initialized.current) {
60
+ if (!values.current) {
55
61
  place(hidden);
56
- initialized.current = true;
62
+ values.current = {
63
+ opacity: motionValue(hidden.opacity),
64
+ scale: motionValue(hidden.scale),
65
+ x: motionValue(hidden.x),
66
+ y: motionValue(hidden.y)
67
+ };
57
68
  }
69
+ cancelRender ??= styleEffect(node, values.current);
58
70
  const frequency = 12 / seconds;
59
- controls.current = animate(node, target, {
71
+ controls.current = [
72
+ "opacity",
73
+ "scale",
74
+ "x",
75
+ "y"
76
+ ].map((key) => animate(values.current[key], target[key], {
60
77
  type: "spring",
61
78
  stiffness: frequency * frequency,
62
79
  damping: 2 * frequency,
63
80
  restDelta: .001,
64
81
  restSpeed: .01
65
- });
82
+ }));
66
83
  };
67
84
  run();
68
85
  preference?.addEventListener("change", run);
69
- return () => preference?.removeEventListener("change", run);
86
+ return () => {
87
+ preference?.removeEventListener("change", run);
88
+ cancelRender?.();
89
+ };
70
90
  }, [
71
91
  state,
72
92
  kind,
73
- side
93
+ side,
94
+ props.style?.animation
74
95
  ]);
75
- React.useEffect(() => () => controls.current?.stop(), []);
96
+ React.useEffect(() => () => controls.current.forEach((control) => control.stop()), []);
76
97
  return /* @__PURE__ */ jsx(asChild ? Slot : "div", {
77
98
  ...props,
78
99
  "data-state": state,
@@ -0,0 +1,9 @@
1
+ import { ApiField } from "../lib/code-presentation.js";
2
+ //#region src/markdown/api-schema.d.ts
3
+ export interface ApiSchemaProps {
4
+ fields: readonly ApiField[];
5
+ label?: string;
6
+ }
7
+ /** Presentation only: the consuming API owns all fields and constraints. */
8
+ export declare function ApiSchema({ fields, label }: ApiSchemaProps): import("react").JSX.Element;
9
+ //#endregion
@@ -0,0 +1,20 @@
1
+ import { schemaRows } from "../lib/code-presentation.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/markdown/api-schema.tsx
4
+ /** Presentation only: the consuming API owns all fields and constraints. */
5
+ function ApiSchema({ fields, label = "Schema" }) {
6
+ return /* @__PURE__ */ jsx("dl", {
7
+ "aria-label": label,
8
+ className: "tangle-api-schema",
9
+ children: schemaRows(fields).map((field, index) => /* @__PURE__ */ jsxs("div", {
10
+ style: { paddingInlineStart: `${field.depth}rem` },
11
+ children: [/* @__PURE__ */ jsxs("dt", { children: [
12
+ /* @__PURE__ */ jsx("code", { children: field.path }),
13
+ " ",
14
+ /* @__PURE__ */ jsxs("span", { children: [field.type, field.required ? " · required" : ""] })
15
+ ] }), field.description && /* @__PURE__ */ jsx("dd", { children: field.description })]
16
+ }, `${field.path}-${index}`))
17
+ });
18
+ }
19
+ //#endregion
20
+ export { ApiSchema };
@@ -1,8 +1,10 @@
1
- import { HTMLAttributes, ReactNode } from "react";
1
+ import { ApiMetadata, CodeExample, codeBlockGrammar } from "../lib/code-presentation.js";
2
+ import { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
2
3
  //#region src/markdown/code-block.d.ts
3
- export declare function codeBlockGrammar(language: string | undefined): string | undefined;
4
4
  export interface CodeBlockProps extends HTMLAttributes<HTMLDivElement> {
5
5
  code: string;
6
+ examples?: readonly CodeExample[];
7
+ metadata?: ApiMetadata;
6
8
  language?: string;
7
9
  /**
8
10
  * Header text. Defaults to the language's name. Set this when the display
@@ -20,9 +22,15 @@ export interface CodeBlockProps extends HTMLAttributes<HTMLDivElement> {
20
22
  * Code with Shiki highlighting in Brand's syntax colors. It renders readable
21
23
  * plain code first (and on the server), then highlights once its grammar loads.
22
24
  */
23
- export declare const CodeBlock: import("react").MemoExoticComponent<({ code, language, label, filename, showLineNumbers, light, className, children, ...props }: CodeBlockProps) => import("react").JSX.Element>;
24
- /** Copy-to-clipboard button for use inside CodeBlock. */
25
- export declare const CopyButton: import("react").MemoExoticComponent<({ text }: {
25
+ export declare const CodeBlock: import("react").MemoExoticComponent<({ code: defaultCode, language: defaultLanguage, label, filename: defaultFilename, examples, metadata, showLineNumbers, light, className, children, ...props }: CodeBlockProps) => import("react").JSX.Element>;
26
+ export interface CopyButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
26
27
  text: string;
27
- }) => import("react").JSX.Element>;
28
- //#endregion
28
+ /** Supply for a visible action label; omitted preserves the icon-only control. */
29
+ label?: string;
30
+ copiedLabel?: string;
31
+ failedLabel?: string;
32
+ }
33
+ /** Copy-to-clipboard button for use inside CodeBlock or other exact-text actions. */
34
+ export declare const CopyButton: import("react").MemoExoticComponent<({ text, label: actionLabel, copiedLabel, failedLabel, className, onClick, ...props }: CopyButtonProps) => import("react").JSX.Element>;
35
+ //#endregion
36
+ export { codeBlockGrammar };