@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.
- package/CHANGELOG.md +20 -0
- package/dist/code-presentation.d.ts +2 -0
- package/dist/code-presentation.js +2 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -2
- package/dist/lib/code-presentation.d.ts +92 -0
- package/dist/lib/code-presentation.js +437 -0
- package/dist/lib/surface-motion.js +34 -13
- package/dist/markdown/api-schema.d.ts +9 -0
- package/dist/markdown/api-schema.js +20 -0
- package/dist/markdown/code-block.d.ts +15 -7
- package/dist/markdown/code-block.js +120 -268
- package/dist/markdown.d.ts +4 -2
- package/dist/markdown.js +4 -2
- package/dist/primitives/button.js +2 -1
- package/dist/primitives/code-block.d.ts +1 -1
- package/dist/primitives/popover.js +11 -3
- package/dist/primitives/wizard.d.ts +30 -0
- package/dist/primitives/wizard.js +158 -0
- package/dist/primitives.d.ts +2 -1
- package/dist/primitives.js +2 -1
- package/package.json +7 -2
- package/src/code-presentation.ts +2 -0
- package/src/lib/code-presentation.ts +257 -0
- package/src/lib/surface-motion.tsx +22 -15
- package/src/markdown/api-presentation.test.tsx +63 -0
- package/src/markdown/api-schema.tsx +9 -0
- package/src/markdown/code-block.tsx +54 -185
- package/src/markdown/index.ts +2 -1
- package/src/primitives/button.tsx +2 -1
- package/src/primitives/code-block.tsx +1 -1
- package/src/primitives/index.ts +3 -0
- package/src/primitives/popover.tsx +6 -8
- package/src/primitives/wizard.stories.tsx +51 -0
- package/src/primitives/wizard.test.tsx +106 -0
- package/src/primitives/wizard.tsx +212 -0
- 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
|
|
11
|
-
const controls = React.useRef(
|
|
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
|
|
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 (!
|
|
60
|
+
if (!values.current) {
|
|
55
61
|
place(hidden);
|
|
56
|
-
|
|
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 =
|
|
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 () =>
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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 };
|