@contractkit/explorer-ui 0.10.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/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # @contractkit/explorer-ui
2
+
3
+ Pure HTML renderer for a ContractKit API explorer. Takes a normalized `PreviewData` snapshot and produces themable HTML strings — no DOM, no framework. The output runs anywhere an HTML string can be inserted: a VS Code webview, a static site, an Electron window, an iframe in a docs site.
4
+
5
+ `@contractkit/vscode-extension` is the first consumer and ships an inline preview panel built on top of this package. A future `@contractkit/plugin-explorer` will generate a self-contained static API explorer from the same renderer.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add @contractkit/explorer-ui
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { renderApp, renderItemPage, type PreviewData } from '@contractkit/explorer-ui';
17
+
18
+ const data: PreviewData = {
19
+ configMeta: { title: 'Payments API', version: '1.0.0' },
20
+ operations: [
21
+ {
22
+ filePath: '/contracts/payments.ck',
23
+ fileGroup: 'payments',
24
+ routePath: '/payments/{id}',
25
+ method: 'get',
26
+ op: { /* OpOperationNode from @contractkit/core */ },
27
+ effectiveModifiers: [],
28
+ },
29
+ ],
30
+ models: [
31
+ {
32
+ filePath: '/contracts/payments.ck',
33
+ model: { /* ModelNode from @contractkit/core */ },
34
+ },
35
+ ],
36
+ warnings: [],
37
+ };
38
+
39
+ // Full sidebar + detail layout (workspace-wide view)
40
+ document.body.innerHTML = renderApp(data);
41
+
42
+ // Or just one item — paired with a host-supplied navigation tree
43
+ document.body.innerHTML = renderItemPage(data, { kind: 'operation', id: 'op-get-payments-id-' });
44
+ ```
45
+
46
+ Pair the HTML with the bundled stylesheet:
47
+
48
+ ```ts
49
+ import '@contractkit/explorer-ui/style.css';
50
+ ```
51
+
52
+ …or copy `dist/assets/style.css` into your project and link it.
53
+
54
+ ## Theming
55
+
56
+ All colors and spacing reference CSS custom properties on `:root`. Override any to retheme:
57
+
58
+ ```css
59
+ :root {
60
+ --ce-fg: var(--vscode-foreground);
61
+ --ce-bg: var(--vscode-editor-background);
62
+ --ce-sidebar-bg: var(--vscode-sideBar-background);
63
+ --ce-link: var(--vscode-textLink-foreground);
64
+ --ce-border: var(--vscode-panel-border);
65
+ --ce-code-bg: var(--vscode-textCodeBlock-background);
66
+ /* …method/status colors, badges, warning palette */
67
+ }
68
+ ```
69
+
70
+ The VS Code extension overrides these to match the active editor theme.
71
+
72
+ ## Data contract
73
+
74
+ The renderer expects **pre-resolved** data — the consumer is responsible for running ContractKit's normalization passes (`applyOptionsDefaults`, `applyVariableSubstitution`, `decomposeCk`, `resolveModifiers`, `resolveSecurity`) and producing the shape in [`src/types.ts`](src/types.ts):
75
+
76
+ | Type | Role |
77
+ | --- | --- |
78
+ | `PreviewData` | Top-level snapshot — `configMeta`, `operations`, `models`, `warnings` |
79
+ | `PreviewConfigMeta` | `title`, `version`, optional `description` and `servers[]` |
80
+ | `ResolvedOperation` | Operation node + file path + effective modifiers/security + grouping key |
81
+ | `ResolvedModel` | Model node + file path |
82
+ | `PreviewWarning` | Non-fatal diagnostics (e.g. unresolved `{{var}}` references) |
83
+
84
+ `@contractkit/core` is a workspace dependency for types only — no runtime imports. An eslint rule (`no-restricted-imports`) enforces this so the bundle stays small in webview consumers.
85
+
86
+ ## Public API
87
+
88
+ | Export | Returns | Purpose |
89
+ | --- | --- | --- |
90
+ | `renderApp(data)` | HTML string | Full layout: sidebar nav + detail pane |
91
+ | `renderItemPage(data, selection, options?)` | HTML string | Single-item detail page (operation / model / overview) |
92
+ | `renderOperation(op, options?)` | HTML string | Operation card with Try-it form when `tryItBaseUrl` is set |
93
+ | `renderModel(resolvedModel, ctx?)` | HTML string | Model card with badges and field table |
94
+ | `renderFieldRows(fields, ctx?)` | HTML string | Just the field table — exported for reuse in inline-object rendering |
95
+ | `renderType(type, ctx?)` | HTML string | Recursive type rendering; `ctx.models` enables inline ref expansion |
96
+ | `renderTryIt(op, baseUrl)` | HTML string | Standalone Try-it form (already included by `renderOperation` when configured) |
97
+ | `renderMarkdown(input)` | HTML string | Tiny safe Markdown renderer (paragraphs, headings, lists, code, bold/italic, http links) |
98
+ | `operationId(op)` / `modelId(name)` | string | Stable anchor ids used by `ItemSelection` |
99
+ | `listSelections(data)` | array | Flat list of every selectable item (useful for building a picker) |
100
+ | `escapeHtml`, `html`, `raw` | helpers | Tagged-template helpers used internally |
101
+
102
+ ## Inline-expanding model refs
103
+
104
+ When `RenderContext.models` is provided, `ref` types in operations and models render as collapsible `<details>` blocks containing the model's fields recursively. Cycles are detected via a `visited` set and render as a `↺` indicator. Past `maxDepth` (default 4) refs collapse back to plain links carrying `data-jump-file` / `data-jump-line` attributes so the host can jump to source.
105
+
106
+ ```ts
107
+ const ctx = { models: new Map(data.models.map(m => [m.model.name, m])) };
108
+ renderType(someTypeWithRefs, ctx);
109
+ ```
110
+
111
+ ## Host integration
112
+
113
+ The webview script (or static-site bootstrapper) attaches event delegation to handle:
114
+
115
+ | Selector | Behavior |
116
+ | --- | --- |
117
+ | `[data-tryit-action="send"]` | Read the form, post `{type:'sendRequest', request}` to the host, render response |
118
+ | `[data-open-model]` | Navigate the detail view to that model's dedicated page |
119
+ | `[data-jump-file] / [data-jump-line]` | Post `{type:'reveal', file, line}` to the host (in VS Code, opens the source) |
120
+ | `a.ce-ref` | Fallback for unresolved refs — host navigates to the dedicated page |
121
+
122
+ See [`apps/vscode-extension/src/webview/main.ts`](../../apps/vscode-extension/src/webview/main.ts) for a complete example.
123
+
124
+ ## Why HTML strings, not DOM/JSX?
125
+
126
+ - **Isomorphic.** Same output in a webview, a static-site generator, a server response, or a test snapshot.
127
+ - **No runtime dependencies in consumers.** The webview bundle stays at ~20 KB; a static-site generator emits `index.html` and is done.
128
+ - **Easy to snapshot-test.** All 54 tests in this package work on plain strings.
@@ -0,0 +1,496 @@
1
+ /* ContractKit Explorer UI — default theme.
2
+ * Override any --ce-* custom property to retheme. */
3
+
4
+ :root {
5
+ --ce-fg: #1f2328;
6
+ --ce-fg-muted: #656d76;
7
+ --ce-bg: #ffffff;
8
+ --ce-sidebar-bg: #f6f8fa;
9
+ --ce-card-bg: #ffffff;
10
+ --ce-link: #0969da;
11
+ --ce-link-active: #0550ae;
12
+ --ce-border: #d0d7de;
13
+ --ce-border-strong: #afb8c1;
14
+ --ce-code-bg: rgba(175, 184, 193, 0.2);
15
+ --ce-badge-fg: #ffffff;
16
+ --ce-badge-bg: #6e7681;
17
+
18
+ --ce-method-get: #0969da;
19
+ --ce-method-post: #1f883d;
20
+ --ce-method-put: #9a6700;
21
+ --ce-method-patch: #8250df;
22
+ --ce-method-delete: #cf222e;
23
+
24
+ --ce-status-2xx: #1f883d;
25
+ --ce-status-3xx: #9a6700;
26
+ --ce-status-4xx: #cf222e;
27
+ --ce-status-5xx: #82071e;
28
+
29
+ --ce-warning-bg: #fff8c5;
30
+ --ce-warning-fg: #59410f;
31
+ --ce-warning-border: #d4a72c;
32
+
33
+ --ce-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
34
+ --ce-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
35
+ }
36
+
37
+ * { box-sizing: border-box; }
38
+ body { margin: 0; }
39
+
40
+ body, .ce-layout {
41
+ color: var(--ce-fg);
42
+ background: var(--ce-bg);
43
+ font-family: var(--ce-font);
44
+ font-size: 14px;
45
+ line-height: 1.5;
46
+ }
47
+
48
+ .ce-layout {
49
+ display: grid;
50
+ grid-template-columns: minmax(220px, 280px) 1fr;
51
+ min-height: 100vh;
52
+ }
53
+
54
+ /* ── Sidebar ───────────────────────────────────────────── */
55
+
56
+ .ce-sidebar {
57
+ background: var(--ce-sidebar-bg);
58
+ border-right: 1px solid var(--ce-border);
59
+ overflow-y: auto;
60
+ padding: 12px 0;
61
+ position: sticky;
62
+ top: 0;
63
+ align-self: start;
64
+ max-height: 100vh;
65
+ }
66
+ .ce-sidebar nav section { margin-bottom: 16px; }
67
+ .ce-sidebar h4 {
68
+ font-size: 11px;
69
+ text-transform: uppercase;
70
+ letter-spacing: 0.05em;
71
+ color: var(--ce-fg-muted);
72
+ margin: 8px 16px 4px;
73
+ }
74
+ .ce-sidebar h4 a { color: inherit; text-decoration: none; }
75
+ .ce-sidebar ul {
76
+ list-style: none;
77
+ margin: 0;
78
+ padding: 0;
79
+ }
80
+ .ce-sidebar li a {
81
+ display: flex;
82
+ align-items: baseline;
83
+ gap: 6px;
84
+ padding: 4px 16px 4px 20px;
85
+ color: var(--ce-fg);
86
+ text-decoration: none;
87
+ border-left: 2px solid transparent;
88
+ }
89
+ .ce-sidebar li a:hover {
90
+ background: var(--ce-code-bg);
91
+ color: var(--ce-link-active);
92
+ }
93
+ .ce-sidebar-group > summary {
94
+ cursor: pointer;
95
+ list-style: none;
96
+ padding: 4px 16px;
97
+ font-weight: 500;
98
+ color: var(--ce-fg-muted);
99
+ }
100
+ .ce-sidebar-group > summary::-webkit-details-marker { display: none; }
101
+ .ce-sidebar-group[open] > summary::before { content: "▾ "; }
102
+ .ce-sidebar-group:not([open]) > summary::before { content: "▸ "; }
103
+ .ce-sidebar-path {
104
+ font-family: var(--ce-mono);
105
+ font-size: 12px;
106
+ overflow: hidden;
107
+ text-overflow: ellipsis;
108
+ white-space: nowrap;
109
+ }
110
+ .ce-model-list li a { font-family: var(--ce-mono); font-size: 13px; }
111
+
112
+ /* ── Detail pane ───────────────────────────────────────── */
113
+
114
+ .ce-detail {
115
+ padding: 24px 32px;
116
+ overflow-x: hidden;
117
+ }
118
+ .ce-detail-single {
119
+ max-width: 900px;
120
+ }
121
+ .ce-stats {
122
+ display: grid;
123
+ grid-template-columns: auto 1fr;
124
+ gap: 4px 16px;
125
+ margin: 12px 0;
126
+ font-size: 13px;
127
+ }
128
+ .ce-stats dt { color: var(--ce-fg-muted); }
129
+ .ce-stats dd { margin: 0; font-family: var(--ce-mono); }
130
+ .ce-hint { color: var(--ce-fg-muted); font-style: italic; }
131
+ .ce-missing { border-color: var(--ce-warning-border); }
132
+ .ce-section { margin-bottom: 32px; }
133
+ .ce-section > h1 {
134
+ border-bottom: 1px solid var(--ce-border);
135
+ padding-bottom: 8px;
136
+ margin-bottom: 16px;
137
+ }
138
+
139
+ .ce-card {
140
+ background: var(--ce-card-bg);
141
+ border: 1px solid var(--ce-border);
142
+ border-radius: 6px;
143
+ padding: 16px 20px;
144
+ margin-bottom: 16px;
145
+ scroll-margin-top: 16px;
146
+ }
147
+ .ce-card-header h2 {
148
+ font-size: 16px;
149
+ margin: 0;
150
+ display: flex;
151
+ align-items: center;
152
+ flex-wrap: wrap;
153
+ gap: 8px;
154
+ }
155
+ .ce-op-name { color: var(--ce-fg-muted); margin: 4px 0 0; font-size: 13px; }
156
+ .ce-meta { margin: 4px 0; font-size: 13px; color: var(--ce-fg-muted); }
157
+ .ce-description { margin: 8px 0; }
158
+ .ce-version { color: var(--ce-fg-muted); margin-top: 0; }
159
+ .ce-extends { font-size: 13px; color: var(--ce-fg-muted); }
160
+
161
+ .ce-subsection { margin-top: 16px; }
162
+ .ce-subsection h3 {
163
+ font-size: 12px;
164
+ text-transform: uppercase;
165
+ letter-spacing: 0.05em;
166
+ color: var(--ce-fg-muted);
167
+ margin: 0 0 8px;
168
+ }
169
+
170
+ /* ── Tables ────────────────────────────────────────────── */
171
+
172
+ .ce-fields {
173
+ width: 100%;
174
+ border-collapse: collapse;
175
+ font-size: 13px;
176
+ }
177
+ .ce-fields td {
178
+ border-top: 1px solid var(--ce-border);
179
+ padding: 8px 10px;
180
+ vertical-align: top;
181
+ }
182
+ .ce-fields tr:first-child td { border-top: none; }
183
+ .ce-field-name {
184
+ width: 35%;
185
+ white-space: nowrap;
186
+ }
187
+ .ce-field-name code {
188
+ font-family: var(--ce-mono);
189
+ font-size: 13px;
190
+ background: transparent;
191
+ padding: 0;
192
+ }
193
+ .ce-field-type { font-family: var(--ce-mono); }
194
+ .ce-field-desc {
195
+ margin-top: 4px;
196
+ font-family: var(--ce-font);
197
+ font-size: 12px;
198
+ color: var(--ce-fg-muted);
199
+ }
200
+ .ce-default {
201
+ font-family: var(--ce-mono);
202
+ color: var(--ce-fg-muted);
203
+ margin-left: 6px;
204
+ }
205
+ .ce-empty { color: var(--ce-fg-muted); font-style: italic; margin: 4px 0; }
206
+
207
+ /* ── Type rendering ────────────────────────────────────── */
208
+
209
+ .ce-type-scalar,
210
+ .ce-type-enum,
211
+ .ce-type-literal {
212
+ color: var(--ce-fg);
213
+ }
214
+ .ce-type-constraint { color: var(--ce-fg-muted); }
215
+ .ce-type-token { color: var(--ce-fg-muted); }
216
+ .ce-ref {
217
+ color: var(--ce-link);
218
+ text-decoration: none;
219
+ }
220
+ .ce-ref:hover { color: var(--ce-link-active); text-decoration: underline; }
221
+ .ce-inline-object {
222
+ display: inline-block;
223
+ margin: 4px 0;
224
+ padding: 0 8px;
225
+ border-left: 2px solid var(--ce-border);
226
+ }
227
+ .ce-inline-object > summary {
228
+ cursor: pointer;
229
+ display: inline;
230
+ }
231
+ .ce-inline-object .ce-fields {
232
+ margin-top: 6px;
233
+ }
234
+
235
+ /* ── Expandable model refs ─────────────────────────────── */
236
+ .ce-ref-expand {
237
+ display: inline-block;
238
+ margin: 2px 0;
239
+ padding: 0;
240
+ border-left: 2px solid var(--ce-link);
241
+ }
242
+ .ce-ref-expand > summary {
243
+ cursor: pointer;
244
+ user-select: none;
245
+ list-style: none;
246
+ padding: 2px 8px;
247
+ display: inline-flex;
248
+ align-items: center;
249
+ gap: 6px;
250
+ }
251
+ .ce-ref-expand > summary::-webkit-details-marker { display: none; }
252
+ .ce-ref-expand > summary::before {
253
+ display: inline-block;
254
+ width: 0.9em;
255
+ color: var(--ce-fg-muted);
256
+ font-size: 0.85em;
257
+ }
258
+ .ce-ref-expand[open] > summary::before { content: "▾"; }
259
+ .ce-ref-expand:not([open]) > summary::before { content: "▸"; }
260
+ .ce-ref-name {
261
+ color: var(--ce-link);
262
+ font-family: var(--ce-mono);
263
+ }
264
+ .ce-ref-expand[open] > summary > .ce-ref-name {
265
+ font-weight: 500;
266
+ }
267
+ .ce-ref-open {
268
+ background: transparent;
269
+ border: none;
270
+ color: var(--ce-fg-muted);
271
+ cursor: pointer;
272
+ padding: 0 2px;
273
+ font-size: 0.85em;
274
+ border-radius: 3px;
275
+ }
276
+ .ce-ref-open:hover {
277
+ color: var(--ce-link);
278
+ background: var(--ce-code-bg);
279
+ }
280
+ .ce-ref-body {
281
+ margin: 4px 0 6px 12px;
282
+ padding: 4px 10px;
283
+ border-left: 1px solid var(--ce-border);
284
+ }
285
+ .ce-ref-cycle {
286
+ color: var(--ce-fg-muted);
287
+ font-style: italic;
288
+ }
289
+
290
+ /* ── Method & status badges ────────────────────────────── */
291
+
292
+ .ce-method {
293
+ display: inline-block;
294
+ font-family: var(--ce-mono);
295
+ font-size: 11px;
296
+ font-weight: 600;
297
+ text-transform: uppercase;
298
+ padding: 2px 6px;
299
+ border-radius: 3px;
300
+ color: #fff;
301
+ background: var(--ce-badge-bg);
302
+ min-width: 50px;
303
+ text-align: center;
304
+ }
305
+ .ce-method-get { background: var(--ce-method-get); }
306
+ .ce-method-post { background: var(--ce-method-post); }
307
+ .ce-method-put { background: var(--ce-method-put); }
308
+ .ce-method-patch { background: var(--ce-method-patch); }
309
+ .ce-method-delete { background: var(--ce-method-delete); }
310
+
311
+ .ce-path {
312
+ font-family: var(--ce-mono);
313
+ font-size: 14px;
314
+ background: var(--ce-code-bg);
315
+ padding: 2px 6px;
316
+ border-radius: 3px;
317
+ }
318
+
319
+ .ce-status {
320
+ display: inline-block;
321
+ font-family: var(--ce-mono);
322
+ font-size: 12px;
323
+ font-weight: 600;
324
+ padding: 2px 8px;
325
+ border-radius: 3px;
326
+ color: #fff;
327
+ background: var(--ce-badge-bg);
328
+ }
329
+ .ce-status-2xx { background: var(--ce-status-2xx); }
330
+ .ce-status-3xx { background: var(--ce-status-3xx); }
331
+ .ce-status-4xx { background: var(--ce-status-4xx); }
332
+ .ce-status-5xx { background: var(--ce-status-5xx); }
333
+
334
+ .ce-badge {
335
+ display: inline-block;
336
+ font-size: 11px;
337
+ padding: 1px 6px;
338
+ border-radius: 10px;
339
+ background: var(--ce-badge-bg);
340
+ color: var(--ce-badge-fg);
341
+ margin-left: 4px;
342
+ vertical-align: middle;
343
+ }
344
+ .ce-badge-deprecated { background: var(--ce-method-delete); }
345
+ .ce-badge-internal { background: var(--ce-method-patch); }
346
+ .ce-badge-public { background: var(--ce-method-post); }
347
+ .ce-badge-readonly,
348
+ .ce-badge-writeonly,
349
+ .ce-badge-optional,
350
+ .ce-badge-nullable,
351
+ .ce-badge-override,
352
+ .ce-badge-mode,
353
+ .ce-badge-format {
354
+ background: var(--ce-border-strong);
355
+ }
356
+ .ce-badge-security-none { background: var(--ce-status-3xx); }
357
+ .ce-badge-security-policy { background: var(--ce-link); }
358
+ .ce-badge-security-bypassed { background: var(--ce-method-delete); }
359
+
360
+ /* ── Response blocks ───────────────────────────────────── */
361
+
362
+ .ce-response-block {
363
+ border-top: 1px solid var(--ce-border);
364
+ padding-top: 12px;
365
+ margin-top: 12px;
366
+ }
367
+ .ce-response-block:first-of-type { border-top: none; padding-top: 0; }
368
+ .ce-response-block h4 { margin: 0 0 8px; }
369
+ .ce-response-block h5 {
370
+ font-size: 12px;
371
+ text-transform: uppercase;
372
+ letter-spacing: 0.05em;
373
+ color: var(--ce-fg-muted);
374
+ margin: 10px 0 4px;
375
+ }
376
+ .ce-body-block { margin: 8px 0; }
377
+ .ce-content-type { margin: 4px 0; font-size: 12px; color: var(--ce-fg-muted); }
378
+ .ce-content-type code { background: transparent; }
379
+
380
+ .ce-code {
381
+ background: var(--ce-code-bg);
382
+ border-radius: 4px;
383
+ padding: 8px 10px;
384
+ font-family: var(--ce-mono);
385
+ font-size: 12px;
386
+ overflow-x: auto;
387
+ white-space: pre;
388
+ }
389
+
390
+ /* ── Jump button ──────────────────────────────────────── */
391
+
392
+ .ce-jump {
393
+ margin-left: auto;
394
+ background: transparent;
395
+ border: 1px solid var(--ce-border);
396
+ color: var(--ce-fg-muted);
397
+ cursor: pointer;
398
+ font-size: 12px;
399
+ padding: 2px 8px;
400
+ border-radius: 4px;
401
+ line-height: 1;
402
+ }
403
+ .ce-jump:hover {
404
+ border-color: var(--ce-link);
405
+ color: var(--ce-link);
406
+ }
407
+
408
+ /* ── Warnings ─────────────────────────────────────────── */
409
+
410
+ .ce-warnings {
411
+ background: var(--ce-warning-bg);
412
+ color: var(--ce-warning-fg);
413
+ border: 1px solid var(--ce-warning-border);
414
+ border-radius: 4px;
415
+ padding: 10px 14px;
416
+ margin-bottom: 16px;
417
+ font-size: 13px;
418
+ }
419
+ .ce-warnings ul { margin: 8px 0 0; padding-left: 18px; }
420
+ .ce-warnings code { font-family: var(--ce-mono); }
421
+
422
+ code {
423
+ font-family: var(--ce-mono);
424
+ background: var(--ce-code-bg);
425
+ padding: 1px 4px;
426
+ border-radius: 3px;
427
+ font-size: 0.95em;
428
+ }
429
+
430
+ /* ── Try-it form ──────────────────────────────────────── */
431
+ .ce-tryit {
432
+ margin-top: 16px;
433
+ border: 1px solid var(--ce-border);
434
+ border-radius: 6px;
435
+ padding: 12px 16px;
436
+ background: var(--ce-sidebar-bg);
437
+ }
438
+ .ce-tryit > summary {
439
+ cursor: pointer;
440
+ font-weight: 500;
441
+ user-select: none;
442
+ list-style: none;
443
+ }
444
+ .ce-tryit > summary::-webkit-details-marker { display: none; }
445
+ .ce-tryit > summary::before {
446
+ display: inline-block;
447
+ width: 1em;
448
+ color: var(--ce-fg-muted);
449
+ }
450
+ .ce-tryit[open] > summary::before { content: "▾"; }
451
+ .ce-tryit:not([open]) > summary::before { content: "▸"; }
452
+ .ce-tryit-form { margin-top: 12px; display: flex; flex-direction: column; gap: 12px; }
453
+ .ce-tryit-row { display: grid; grid-template-columns: 160px 1fr; gap: 8px; align-items: center; }
454
+ .ce-tryit-col { align-items: start; }
455
+ .ce-tryit-label { font-size: 12px; color: var(--ce-fg-muted); }
456
+ .ce-tryit-section { border: 1px solid var(--ce-border); border-radius: 4px; padding: 8px 10px; }
457
+ .ce-tryit-section legend { font-size: 11px; text-transform: uppercase; color: var(--ce-fg-muted); padding: 0 4px; }
458
+ .ce-tryit-section .ce-tryit-row + .ce-tryit-row { margin-top: 6px; }
459
+ .ce-tryit-form input,
460
+ .ce-tryit-form textarea {
461
+ width: 100%;
462
+ background: var(--ce-bg);
463
+ color: var(--ce-fg);
464
+ border: 1px solid var(--ce-border);
465
+ border-radius: 3px;
466
+ padding: 4px 8px;
467
+ font-family: var(--ce-mono);
468
+ font-size: 12px;
469
+ }
470
+ .ce-tryit-form textarea { resize: vertical; }
471
+ .ce-tryit-actions { display: flex; justify-content: flex-start; }
472
+ .ce-tryit-send {
473
+ background: var(--ce-link);
474
+ color: #fff;
475
+ border: none;
476
+ border-radius: 4px;
477
+ padding: 6px 14px;
478
+ cursor: pointer;
479
+ font-weight: 500;
480
+ }
481
+ .ce-tryit-send:hover { background: var(--ce-link-active); }
482
+ .ce-tryit-send:disabled { opacity: 0.6; cursor: progress; }
483
+ .ce-tryit-response { margin-top: 6px; }
484
+ .ce-tryit-response pre { max-height: 320px; overflow: auto; }
485
+ .ce-tryit-response .ce-tryit-status { font-weight: 600; }
486
+
487
+ /* ── Markdown ─────────────────────────────────────────── */
488
+ .ce-markdown p { margin: 6px 0; }
489
+ .ce-markdown ul, .ce-markdown ol { margin: 6px 0; padding-left: 22px; }
490
+ .ce-markdown li { margin: 2px 0; }
491
+ .ce-markdown h1, .ce-markdown h2, .ce-markdown h3, .ce-markdown h4 {
492
+ margin: 12px 0 6px;
493
+ }
494
+ .ce-markdown pre.ce-code { margin: 8px 0; }
495
+ .ce-markdown a { color: var(--ce-link); }
496
+ .ce-markdown a:hover { color: var(--ce-link-active); }
@@ -0,0 +1,4 @@
1
+ import type { ScalarTypeNode } from '@contractkit/core';
2
+ /** Formats the constraint parameters on a scalar type (min/max/len/regex/format) as a parenthesized suffix. */
3
+ export declare function constraintSummary(scalar: ScalarTypeNode): string;
4
+ //# sourceMappingURL=constraints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constraints.d.ts","sourceRoot":"","sources":["../src/constraints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,+GAA+G;AAC/G,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAQhE"}
package/dist/html.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Minimal tagged-template helper for building HTML strings with automatic escaping.
3
+ *
4
+ * html`<p>${userInput}</p>` // escapes userInput
5
+ * html`<ul>${raw(items.join(''))}</ul>` // bypasses escaping
6
+ *
7
+ * Interpolations are coerced to strings and HTML-escaped. Arrays are joined without a separator
8
+ * after escaping each element. `raw(...)` marks a string as pre-escaped so it passes through.
9
+ */
10
+ declare const RAW: unique symbol;
11
+ interface RawHtml {
12
+ [RAW]: true;
13
+ value: string;
14
+ }
15
+ /** Marks a string as already-escaped HTML so the {@link html} tag inserts it verbatim. */
16
+ export declare function raw(value: string): RawHtml;
17
+ /** Escapes the five HTML-significant characters (`& < > " '`) so the input is safe to embed in markup. */
18
+ export declare function escapeHtml(value: string): string;
19
+ /**
20
+ * Tagged template literal that auto-escapes every interpolated value. Use {@link raw} to inject
21
+ * pre-built HTML without double-escaping. `null`, `undefined`, and `false` interpolations render
22
+ * as empty strings; arrays are flattened and each element is escaped (or passed through if `raw`).
23
+ */
24
+ export declare function html(strings: TemplateStringsArray, ...values: unknown[]): string;
25
+ /** Slugify an arbitrary string for use in anchor ids — keeps a-z, 0-9, dashes; collapses everything else. */
26
+ export declare function slug(value: string): string;
27
+ export {};
28
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,QAAA,MAAM,GAAG,eAAgB,CAAC;AAE1B,UAAU,OAAO;IACb,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,0FAA0F;AAC1F,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1C;AAMD,0GAA0G;AAC1G,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD;AASD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAMhF;AAED,6GAA6G;AAC7G,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1C"}
@@ -0,0 +1,12 @@
1
+ export { renderApp } from './render.js';
2
+ export { renderOperation, operationAnchor } from './render-operation.js';
3
+ export { renderTryIt } from './render-tryit.js';
4
+ export { renderModel, renderFieldRows, modelAnchor } from './render-model.js';
5
+ export { renderType } from './render-type.js';
6
+ export { renderItemPage, listSelections, operationId, modelId } from './render-item.js';
7
+ export type { ItemSelection } from './render-item.js';
8
+ export { constraintSummary } from './constraints.js';
9
+ export { renderMarkdown } from './markdown.js';
10
+ export { escapeHtml, html, raw, slug } from './html.js';
11
+ export type { PreviewData, PreviewConfigMeta, PreviewServer, PreviewWarning, ResolvedOperation, ResolvedModel, } from './types.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACxF,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACxD,YAAY,EACR,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,aAAa,GAChB,MAAM,YAAY,CAAC"}