sdocs 0.0.36 → 0.0.37

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 (32) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/explorer/views/ApiTable.svelte +138 -0
  3. package/dist/explorer/views/ApiTable.svelte.d.ts +14 -0
  4. package/dist/explorer/views/ComponentView.svelte +154 -87
  5. package/dist/explorer/views/CssPropControl.svelte +27 -0
  6. package/dist/explorer/views/CssPropControl.svelte.d.ts +14 -0
  7. package/dist/explorer/views/DataTable.svelte +46 -4
  8. package/dist/explorer/views/PreviewFrame.svelte +11 -1
  9. package/dist/explorer/views/PreviewFrame.svelte.d.ts +4 -1
  10. package/dist/explorer/views/PropControl.svelte +69 -0
  11. package/dist/explorer/views/PropControl.svelte.d.ts +14 -0
  12. package/dist/explorer/views/format.d.ts +6 -0
  13. package/dist/explorer/views/format.js +44 -0
  14. package/dist/server/snippet-compiler.d.ts +3 -2
  15. package/dist/server/snippet-compiler.js +36 -4
  16. package/dist/ui/Control/Checkbox.svelte +6 -3
  17. package/dist/ui/Control/Color.svelte +6 -3
  18. package/dist/ui/Control/Dimension.svelte +8 -4
  19. package/dist/ui/Control/Number.svelte +6 -4
  20. package/dist/ui/Control/Select.svelte +6 -4
  21. package/dist/ui/Control/Text.svelte +6 -3
  22. package/dist/ui/Icon/Icon.svelte +10 -0
  23. package/dist/ui/Icon/icons/database.svg +5 -0
  24. package/dist/ui/Icon/icons/palette.svg +7 -0
  25. package/dist/ui/Icon/icons/sliders-horizontal.svg +11 -0
  26. package/dist/ui/Icon/icons/square-function.svg +5 -0
  27. package/dist/ui/Icon/icons/zap.svg +3 -0
  28. package/dist/ui/styles/sdocs.css +49 -0
  29. package/dist/vite.js +2 -1
  30. package/package.json +1 -1
  31. package/dist/explorer/views/ControlsPanel.svelte +0 -186
  32. package/dist/explorer/views/ControlsPanel.svelte.d.ts +0 -14
package/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.37] - 2026-07-03
11
+
12
+ ### Changed
13
+
14
+ - **The component page is a live API reference.** The separate Controls
15
+ panel is gone — controls sit inside the API tables, on the row of the
16
+ prop or CSS custom property they drive. Props, CSS Props, Events, and
17
+ Snippets share a composite row layout: name (with a red `*` when
18
+ required), type and description stacked in the middle, an explicit
19
+ Default column (`—` when absent), and a fixed control rail. Reset moved
20
+ next to the Props title.
21
+ - **Sections instead of collapsible panels.** The reference sections render
22
+ flat with icon titles; only the preview and its code stay in the bordered
23
+ card (Preview Code still collapses). "State" is now "States", matching
24
+ the plural sibling sections. The page grew to 1120px.
25
+ - **Color-coded types and values.** Type chips are tinted by kind — strings
26
+ green, numbers and dimensions amber, booleans purple, callbacks blue,
27
+ snippets pink, colors cyan, `void` slate — union members render as
28
+ individual chips, and default/state literals are colored plain text.
29
+ Both themes are handled explicitly.
30
+
31
+ ### Added
32
+
33
+ - **Run buttons for methods.** Zero-argument methods can be invoked
34
+ directly from the Methods table and act on the live preview; methods
35
+ with parameters show a disabled button.
36
+ - **Live state values.** The States table shows each exported state's
37
+ current value, streamed from the preview as it changes.
38
+ - Section icons (sliders, palette, zap, function, database) join the UI
39
+ icon set.
40
+
10
41
  ## [0.0.36] - 2026-07-03
11
42
 
12
43
  ### Fixed
@@ -0,0 +1,138 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { typeClass, typeParts, valueClass } from './format.js';
4
+
5
+ interface Row {
6
+ name: string;
7
+ type?: string | null;
8
+ default?: string | null;
9
+ description?: string | null;
10
+ required?: boolean;
11
+ }
12
+
13
+ interface Props {
14
+ rows: Row[];
15
+ /** Renders the per-row control in the fixed right rail */
16
+ control?: Snippet<[Row]>;
17
+ /** Show the Default column (rows without defaults, e.g. events, turn it off) */
18
+ showDefault?: boolean;
19
+ }
20
+
21
+ let { rows, control, showDefault = true }: Props = $props();
22
+
23
+ const gridColumns = $derived(
24
+ ['200px', 'minmax(0, 1fr)', ...(showDefault ? ['120px'] : []), ...(control ? ['200px'] : [])].join(' '),
25
+ );
26
+ </script>
27
+
28
+ {#if rows.length > 0}
29
+ <div class="sdocs-api-list">
30
+ <div class="sdocs-api-header" style:grid-template-columns={gridColumns}>
31
+ <div>Name</div>
32
+ <div>Details</div>
33
+ {#if showDefault}<div>Default</div>{/if}
34
+ {#if control}<div>Control</div>{/if}
35
+ </div>
36
+ {#each rows as row (row.name)}
37
+ <div class="sdocs-api-row" style:grid-template-columns={gridColumns}>
38
+ <div class="sdocs-api-name">
39
+ {row.name}{#if row.required}<span class="sdocs-api-required" title="Required">*</span>{/if}
40
+ </div>
41
+ <div class="sdocs-api-middle">
42
+ {#if row.type}
43
+ <div class="sdocs-api-meta">
44
+ {#each typeParts(row.type) as part, i (i)}
45
+ {#if i > 0}<span class="sdocs-typesep">|</span>{/if}
46
+ <code class="sdocs-type-{typeClass(row.type)}">{part}</code>
47
+ {/each}
48
+ </div>
49
+ {/if}
50
+ {#if row.description}
51
+ <div class="sdocs-api-desc">{row.description}</div>
52
+ {/if}
53
+ </div>
54
+ {#if showDefault}
55
+ <div class="sdocs-api-default">
56
+ {#if row.default != null}
57
+ <span class="sdocs-value sdocs-value-{valueClass(row.default)}">{row.default}</span>
58
+ {:else}
59
+ <span class="sdocs-api-empty">—</span>
60
+ {/if}
61
+ </div>
62
+ {/if}
63
+ {#if control}
64
+ <div class="sdocs-api-control">
65
+ {@render control(row)}
66
+ </div>
67
+ {/if}
68
+ </div>
69
+ {/each}
70
+ </div>
71
+ {:else}
72
+ <p class="sdocs-api-none">None</p>
73
+ {/if}
74
+
75
+ <style>
76
+ .sdocs-api-list {
77
+ display: flex;
78
+ flex-direction: column;
79
+ }
80
+ .sdocs-api-header {
81
+ display: grid;
82
+ grid-template-columns: 200px minmax(0, 1fr) 120px 200px;
83
+ gap: 20px;
84
+ padding: 6px 12px;
85
+ border-bottom: 2px solid var(--color-base-200);
86
+ font-size: 12px;
87
+ font-weight: 600;
88
+ color: var(--color-base-600);
89
+ }
90
+ .sdocs-api-row {
91
+ display: grid;
92
+ grid-template-columns: 200px minmax(0, 1fr) 120px 200px;
93
+ gap: 20px;
94
+ align-items: start;
95
+ padding: 12px 12px;
96
+ border-bottom: 1px solid var(--color-base-100);
97
+ font-size: 13px;
98
+ }
99
+ .sdocs-api-name {
100
+ font-weight: 600;
101
+ color: var(--color-base-900);
102
+ overflow-wrap: break-word;
103
+ }
104
+ .sdocs-api-required {
105
+ color: var(--color-red-500);
106
+ }
107
+ .sdocs-api-middle {
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: 8px;
111
+ min-width: 0;
112
+ }
113
+ .sdocs-api-meta {
114
+ display: flex;
115
+ flex-wrap: wrap;
116
+ align-items: baseline;
117
+ gap: 6px;
118
+ }
119
+ .sdocs-api-desc {
120
+ color: var(--color-base-500);
121
+ }
122
+ .sdocs-api-default {
123
+ overflow-wrap: break-word;
124
+ min-width: 0;
125
+ }
126
+ .sdocs-api-empty {
127
+ color: var(--color-base-300);
128
+ }
129
+ .sdocs-api-control {
130
+ min-width: 0;
131
+ }
132
+ .sdocs-api-none {
133
+ color: var(--color-base-400);
134
+ font-size: 13px;
135
+ font-style: italic;
136
+ margin: 0;
137
+ }
138
+ </style>
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type ApiTableProps = typeof __propDef.props;
10
+ export type ApiTableEvents = typeof __propDef.events;
11
+ export type ApiTableSlots = typeof __propDef.slots;
12
+ export default class ApiTable extends SvelteComponentTyped<ApiTableProps, ApiTableEvents, ApiTableSlots> {
13
+ }
14
+ export {};
@@ -4,8 +4,10 @@
4
4
  import { highlightSvelte } from '../highlighter.js';
5
5
  import CollapsiblePanel from './CollapsiblePanel.svelte';
6
6
  import PreviewFrame from './PreviewFrame.svelte';
7
- import ControlsPanel from './ControlsPanel.svelte';
8
7
  import DataTable from './DataTable.svelte';
8
+ import ApiTable from './ApiTable.svelte';
9
+ import PropControl from './PropControl.svelte';
10
+ import CssPropControl from './CssPropControl.svelte';
9
11
 
10
12
  interface Props {
11
13
  doc: DocEntry;
@@ -32,6 +34,10 @@
32
34
  let propValues = $state<Record<string, unknown>>({});
33
35
  let cssValues = $state<Record<string, string>>({});
34
36
 
37
+ // Live wiring to the default preview: method invocation + exported state values
38
+ let defaultPreview = $state<{ callMethod: (name: string) => void }>();
39
+ let liveStateValues = $state<Record<string, unknown>>({});
40
+
35
41
  // Initialize from meta.args (build new objects to avoid read+write loop)
36
42
  $effect(() => {
37
43
  propValues = { ...(meta.args ?? {}) };
@@ -200,13 +206,17 @@
200
206
  cssValues = newCss;
201
207
  }
202
208
 
209
+ const hasEditableControls = $derived(
210
+ !!cd && (cd.props.some((p) => p.category === 'prop') || cd.cssProps.length > 0),
211
+ );
212
+
203
213
  // Table data builders
204
214
  const propsRows = $derived(
205
215
  (cd?.props ?? []).filter((p) => p.category === 'prop').map((p) => ({
206
216
  name: p.name,
207
217
  type: p.type,
208
218
  default: p.default,
209
- required: p.required ? 'Yes' : '',
219
+ required: p.required,
210
220
  description: p.description,
211
221
  })),
212
222
  );
@@ -247,6 +257,7 @@
247
257
 
248
258
  const stateRows = $derived(
249
259
  (cd?.state ?? []).map((s) => ({
260
+ value: s.name in liveStateValues ? JSON.stringify(liveStateValues[s.name]) : undefined,
250
261
  name: s.name,
251
262
  type: s.type,
252
263
  description: s.description,
@@ -282,10 +293,12 @@
282
293
  {#if defaultSnippet}
283
294
  <div class="sdocs-preview-wrapper">
284
295
  <PreviewFrame
296
+ bind:this={defaultPreview}
285
297
  src={defaultSnippet.previewUrl ?? ''}
286
298
  props={propValues}
287
299
  cssVars={cssValues}
288
300
  {activeStylesheet}
301
+ onStateValues={(values) => (liveStateValues = values)}
289
302
  />
290
303
  </div>
291
304
 
@@ -299,92 +312,101 @@
299
312
  </div>
300
313
  </CollapsiblePanel>
301
314
  {/if}
315
+ </div>
302
316
 
303
- {#if cd && ((cd.props.filter((p) => p.category === 'prop').length > 0) || cd.cssProps.length > 0)}
304
- <CollapsiblePanel title="Controls">
305
- <ControlsPanel
306
- componentProps={cd.props}
307
- cssProps={cd.cssProps}
308
- {propValues}
309
- {cssValues}
310
- onPropChange={handlePropChange}
311
- onCssChange={handleCssChange}
312
- onReset={handleReset}
313
- />
314
- </CollapsiblePanel>
317
+ {#snippet propControl(row: Record<string, unknown>)}
318
+ {@const prop = cd?.props.find((p) => p.name === row.name && p.category === 'prop')}
319
+ {#if prop}
320
+ <PropControl {prop} value={propValues[prop.name]} onchange={(v) => handlePropChange(prop.name, v)} />
315
321
  {/if}
316
-
317
- <CollapsiblePanel title="Props">
318
- <DataTable
319
- columns={[
320
- { key: 'name', label: 'Name' },
321
- { key: 'type', label: 'Type' },
322
- { key: 'default', label: 'Default' },
323
- { key: 'required', label: 'Required' },
324
- { key: 'description', label: 'Description' },
325
- ]}
326
- rows={propsRows}
327
- />
328
- </CollapsiblePanel>
329
-
330
- <CollapsiblePanel title="CSS Props">
331
- <DataTable
332
- columns={[
333
- { key: 'name', label: 'Name' },
334
- { key: 'type', label: 'Type' },
335
- { key: 'default', label: 'Default' },
336
- { key: 'description', label: 'Description' },
337
- ]}
338
- rows={cssPropsRows}
339
- />
340
- </CollapsiblePanel>
341
-
342
- <CollapsiblePanel title="Events">
343
- <DataTable
344
- columns={[
345
- { key: 'name', label: 'Name' },
346
- { key: 'type', label: 'Type' },
347
- { key: 'description', label: 'Description' },
348
- ]}
349
- rows={eventsRows}
350
- />
351
- </CollapsiblePanel>
352
-
353
- <CollapsiblePanel title="Snippets">
354
- <DataTable
355
- columns={[
356
- { key: 'name', label: 'Name' },
357
- { key: 'type', label: 'Type' },
358
- { key: 'description', label: 'Description' },
359
- ]}
360
- rows={snippetsRows}
361
- />
362
- </CollapsiblePanel>
363
-
364
- <CollapsiblePanel title="Methods">
365
- <DataTable
366
- columns={[
367
- { key: 'name', label: 'Name' },
368
- { key: 'params', label: 'Parameters' },
369
- { key: 'returns', label: 'Returns' },
370
- { key: 'description', label: 'Description' },
371
- ]}
372
- rows={methodsRows}
373
- />
374
- </CollapsiblePanel>
375
-
376
- <CollapsiblePanel title="State">
377
- <DataTable
378
- columns={[
379
- { key: 'name', label: 'Name' },
380
- { key: 'type', label: 'Type' },
381
- { key: 'description', label: 'Description' },
382
- ]}
383
- rows={stateRows}
384
- />
385
- </CollapsiblePanel>
386
-
387
- </div>
322
+ {/snippet}
323
+
324
+ {#snippet methodControl(row: Record<string, unknown>)}
325
+ {@const hasParams = String(row.params ?? '').trim().length > 0}
326
+ <button
327
+ class="sdocs-run-btn"
328
+ disabled={hasParams || !defaultPreview}
329
+ title={hasParams ? 'Only methods without parameters can be run here' : `Run ${row.name}() on the preview`}
330
+ onclick={() => defaultPreview?.callMethod(String(row.name))}
331
+ >
332
+ Run
333
+ </button>
334
+ {/snippet}
335
+
336
+ {#snippet cssPropControl(row: Record<string, unknown>)}
337
+ {@const cssProp = cd?.cssProps.find((p) => p.name === row.name)}
338
+ {#if cssProp}
339
+ <CssPropControl {cssProp} value={cssValues[cssProp.name]} onchange={(v) => handleCssChange(cssProp.name, v)} />
340
+ {/if}
341
+ {/snippet}
342
+
343
+ <section class="sdocs-doc-section">
344
+ <h2 class="sdocs-doc-section-title">
345
+ <Icon name="sliders-horizontal" --w="15px" --h="15px" --fill="var(--color-base-500)" />
346
+ Props
347
+ {#if hasEditableControls}
348
+ <button class="sdocs-reset-btn" onclick={handleReset}>Reset</button>
349
+ {/if}
350
+ </h2>
351
+ <ApiTable rows={propsRows} control={propControl} />
352
+ </section>
353
+
354
+ <section class="sdocs-doc-section">
355
+ <h2 class="sdocs-doc-section-title">
356
+ <Icon name="palette" --w="15px" --h="15px" --fill="var(--color-base-500)" />
357
+ CSS Props
358
+ </h2>
359
+ <ApiTable rows={cssPropsRows} control={cssPropControl} />
360
+ </section>
361
+
362
+ <section class="sdocs-doc-section">
363
+ <h2 class="sdocs-doc-section-title">
364
+ <Icon name="zap" --w="15px" --h="15px" --fill="var(--color-base-500)" />
365
+ Events
366
+ </h2>
367
+ <ApiTable rows={eventsRows} showDefault={false} />
368
+ </section>
369
+
370
+ <section class="sdocs-doc-section">
371
+ <h2 class="sdocs-doc-section-title">
372
+ <Icon name="code" --w="15px" --h="15px" --fill="var(--color-base-500)" />
373
+ Snippets
374
+ </h2>
375
+ <ApiTable rows={snippetsRows} showDefault={false} />
376
+ </section>
377
+
378
+ <section class="sdocs-doc-section">
379
+ <h2 class="sdocs-doc-section-title">
380
+ <Icon name="square-function" --w="15px" --h="15px" --fill="var(--color-base-500)" />
381
+ Methods
382
+ </h2>
383
+ <DataTable
384
+ columns={[
385
+ { key: 'name', label: 'Name', kind: 'name' },
386
+ { key: 'params', label: 'Parameters' },
387
+ { key: 'returns', label: 'Returns', kind: 'type' },
388
+ { key: 'description', label: 'Description', kind: 'text' },
389
+ ]}
390
+ rows={methodsRows}
391
+ control={methodsRows.length > 0 ? methodControl : undefined}
392
+ />
393
+ </section>
394
+
395
+ <section class="sdocs-doc-section">
396
+ <h2 class="sdocs-doc-section-title">
397
+ <Icon name="database" --w="15px" --h="15px" --fill="var(--color-base-500)" />
398
+ States
399
+ </h2>
400
+ <DataTable
401
+ columns={[
402
+ { key: 'name', label: 'Name', kind: 'name' },
403
+ { key: 'type', label: 'Type', kind: 'type' },
404
+ { key: 'value', label: 'Current value', kind: 'value' },
405
+ { key: 'description', label: 'Description', kind: 'text' },
406
+ ]}
407
+ rows={stateRows}
408
+ />
409
+ </section>
388
410
 
389
411
  {#if exampleSnippets.length > 0}
390
412
  <hr class="sdocs-divider" />
@@ -421,9 +443,54 @@
421
443
  <style>
422
444
  .sdocs-component-view {
423
445
  padding: 24px 32px;
424
- max-width: 960px;
446
+ max-width: 1120px;
425
447
  font-family: var(--sans);
426
448
  }
449
+ .sdocs-doc-section {
450
+ margin-top: 28px;
451
+ }
452
+ .sdocs-doc-section-title {
453
+ display: flex;
454
+ align-items: center;
455
+ gap: 8px;
456
+ margin: 0 0 10px;
457
+ font-size: 14px;
458
+ font-weight: 600;
459
+ color: var(--color-base-800);
460
+ }
461
+ .sdocs-doc-section-title .sdocs-reset-btn {
462
+ margin-left: auto;
463
+ }
464
+ .sdocs-reset-btn {
465
+ padding: 4px 12px;
466
+ border: 1px solid var(--color-base-200);
467
+ border-radius: 4px;
468
+ background: var(--color-base-0);
469
+ font: inherit;
470
+ font-size: 12px;
471
+ color: var(--color-base-600);
472
+ cursor: pointer;
473
+ }
474
+ .sdocs-reset-btn:hover {
475
+ background: var(--color-base-100);
476
+ }
477
+ .sdocs-run-btn {
478
+ padding: 3px 12px;
479
+ border: 1px solid var(--color-base-200);
480
+ border-radius: 4px;
481
+ background: var(--color-base-0);
482
+ font: inherit;
483
+ font-size: 12px;
484
+ color: var(--color-base-600);
485
+ cursor: pointer;
486
+ }
487
+ .sdocs-run-btn:hover:not(:disabled) {
488
+ background: var(--color-base-100);
489
+ }
490
+ .sdocs-run-btn:disabled {
491
+ opacity: 0.4;
492
+ cursor: default;
493
+ }
427
494
  .sdocs-view-header {
428
495
  margin-bottom: 24px;
429
496
  }
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { ParsedCssProp } from '../../types.js';
3
+ import { Text as TextControl, Color as ColorControl, Dimension as DimensionControl } from '../../ui/Control/index.js';
4
+
5
+ interface Props {
6
+ cssProp: ParsedCssProp;
7
+ value: string | undefined;
8
+ onchange: (value: string) => void;
9
+ }
10
+
11
+ let { cssProp, value, onchange }: Props = $props();
12
+
13
+ const controlType = $derived.by(() => {
14
+ const t = cssProp.type?.toLowerCase() ?? '';
15
+ if (t === 'color') return 'color';
16
+ if (t === 'dimension') return 'dimension';
17
+ return 'text';
18
+ });
19
+ </script>
20
+
21
+ {#if controlType === 'color'}
22
+ <ColorControl value={value ?? cssProp.default ?? '#000000'} {onchange} />
23
+ {:else if controlType === 'dimension'}
24
+ <DimensionControl value={value ?? cssProp.default ?? '0px'} {onchange} />
25
+ {:else}
26
+ <TextControl value={value ?? cssProp.default ?? ''} {onchange} />
27
+ {/if}
@@ -0,0 +1,14 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: Record<string, never>;
4
+ events: {
5
+ [evt: string]: CustomEvent<any>;
6
+ };
7
+ slots: {};
8
+ };
9
+ export type CssPropControlProps = typeof __propDef.props;
10
+ export type CssPropControlEvents = typeof __propDef.events;
11
+ export type CssPropControlSlots = typeof __propDef.slots;
12
+ export default class CssPropControl extends SvelteComponentTyped<CssPropControlProps, CssPropControlEvents, CssPropControlSlots> {
13
+ }
14
+ export {};
@@ -1,15 +1,26 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { typeClass, typeParts, valueClass } from './format.js';
4
+
2
5
  interface Column {
3
6
  key: string;
4
7
  label: string;
8
+ /** 'name' renders as bold plain text, 'text' as plain text,
9
+ * 'type' as a code chip color-coded by the type,
10
+ * 'value' as mono text color-coded by the literal; default is a code chip */
11
+ kind?: 'name' | 'text' | 'type' | 'value';
5
12
  }
6
13
 
7
14
  interface Props {
8
15
  columns: Column[];
9
16
  rows: Record<string, unknown>[];
17
+ /** When set, adds a Control column rendering this snippet per row */
18
+ control?: Snippet<[Record<string, unknown>]>;
10
19
  }
11
20
 
12
- let { columns, rows }: Props = $props();
21
+ let { columns, rows, control }: Props = $props();
22
+
23
+
13
24
  </script>
14
25
 
15
26
  {#if rows.length > 0}
@@ -19,6 +30,9 @@
19
30
  {#each columns as col (col.key)}
20
31
  <th>{col.label}</th>
21
32
  {/each}
33
+ {#if control}
34
+ <th class="sdocs-table-control-col">Control</th>
35
+ {/if}
22
36
  </tr>
23
37
  </thead>
24
38
  <tbody>
@@ -26,13 +40,31 @@
26
40
  <tr>
27
41
  {#each columns as col (col.key)}
28
42
  <td>
29
- {#if row[col.key] != null}
30
- <code>{row[col.key]}</code>
31
- {:else}
43
+ {#if row[col.key] == null}
32
44
  <span class="sdocs-table-empty">—</span>
45
+ {:else if col.kind === 'name'}
46
+ <span class="sdocs-table-name">
47
+ {row[col.key]}{#if row.required === true}<span class="sdocs-table-required" title="Required">*</span>{/if}
48
+ </span>
49
+ {:else if col.kind === 'text'}
50
+ {row[col.key]}
51
+ {:else if col.kind === 'type'}
52
+ {#each typeParts(row[col.key]) as part, i (i)}
53
+ {#if i > 0}<span class="sdocs-typesep">|</span>{/if}
54
+ <code class="sdocs-type-{typeClass(row[col.key])}">{part}</code>
55
+ {/each}
56
+ {:else if col.kind === 'value'}
57
+ <span class="sdocs-value sdocs-value-{valueClass(row[col.key])}">{row[col.key]}</span>
58
+ {:else}
59
+ <code>{row[col.key]}</code>
33
60
  {/if}
34
61
  </td>
35
62
  {/each}
63
+ {#if control}
64
+ <td class="sdocs-table-control-col">
65
+ {@render control(row)}
66
+ </td>
67
+ {/if}
36
68
  </tr>
37
69
  {/each}
38
70
  </tbody>
@@ -67,6 +99,16 @@
67
99
  padding: 1px 4px;
68
100
  border-radius: 3px;
69
101
  }
102
+ .sdocs-table-name {
103
+ font-weight: 600;
104
+ }
105
+ .sdocs-table-required {
106
+ color: var(--color-red-500);
107
+ }
108
+ .sdocs-table-control-col {
109
+ width: 220px;
110
+ min-width: 180px;
111
+ }
70
112
  .sdocs-table-empty {
71
113
  color: var(--color-base-300);
72
114
  }
@@ -7,9 +7,16 @@
7
7
  cssVars?: Record<string, string>;
8
8
  activeStylesheet?: string;
9
9
  fullHeight?: boolean;
10
+ /** Called with the preview component's exported state values whenever they change */
11
+ onStateValues?: (values: Record<string, unknown>) => void;
10
12
  }
11
13
 
12
- let { src, props = {}, cssVars = {}, activeStylesheet, fullHeight = false }: Props = $props();
14
+ let { src, props = {}, cssVars = {}, activeStylesheet, fullHeight = false, onStateValues }: Props = $props();
15
+
16
+ /** Invoke a zero-argument method on the preview's root component */
17
+ export function callMethod(name: string): void {
18
+ iframe?.contentWindow?.postMessage({ type: 'sdocs:call-method', name }, '*');
19
+ }
13
20
 
14
21
  // Preview URLs are root-absolute; hosts serving under a sub-path pass the
15
22
  // prefix via the Explorer's previewBase prop (see Explorer.svelte).
@@ -30,6 +37,9 @@
30
37
  if (e.data?.type === 'sdocs:resize' && e.source === iframe?.contentWindow) {
31
38
  contentHeight = e.data.height;
32
39
  }
40
+ if (e.data?.type === 'sdocs:state-values' && e.source === iframe?.contentWindow) {
41
+ onStateValues?.(e.data.values);
42
+ }
33
43
  }
34
44
  window.addEventListener('message', onMessage);
35
45
  return () => window.removeEventListener('message', onMessage);
@@ -1,6 +1,8 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
- props: Record<string, never>;
3
+ props: {
4
+ callMethod?: (name: string) => void;
5
+ };
4
6
  events: {
5
7
  [evt: string]: CustomEvent<any>;
6
8
  };
@@ -10,5 +12,6 @@ export type PreviewFrameProps = typeof __propDef.props;
10
12
  export type PreviewFrameEvents = typeof __propDef.events;
11
13
  export type PreviewFrameSlots = typeof __propDef.slots;
12
14
  export default class PreviewFrame extends SvelteComponentTyped<PreviewFrameProps, PreviewFrameEvents, PreviewFrameSlots> {
15
+ get callMethod(): (name: string) => void;
13
16
  }
14
17
  export {};