sdocs 0.0.52 → 0.0.54
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 +31 -0
- package/dist/explorer/views/ComponentView.svelte +3 -3
- package/dist/explorer/views/PageView.svelte +2 -2
- package/dist/language/index.d.ts +1 -1
- package/dist/language/index.js +1 -1
- package/dist/language/parser.d.ts +31 -1
- package/dist/language/parser.js +42 -5
- package/dist/server/config.js +17 -0
- package/dist/server/snippet-compiler.d.ts +8 -1
- package/dist/server/snippet-compiler.js +49 -2
- package/dist/types.d.ts +58 -2
- package/dist/vite.js +33 -3
- package/package.json +1 -1
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.54] - 2026-07-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Stage alignment on previews and examples.** Two direction-aware
|
|
15
|
+
attributes (also settable per-kind in `content.docs` and on `[DOCS]`):
|
|
16
|
+
`align` — horizontal (`left`/`center`/`right`/`justify`) — and `alignY` —
|
|
17
|
+
vertical (`top`/`middle`/`bottom`/`justify`). sdocs maps each to the right
|
|
18
|
+
flex property for the current `direction`, so `align="center"` centers
|
|
19
|
+
horizontally in a row or a column; `justify` spreads items along the flow.
|
|
20
|
+
- **`attributeRules(kind)` in `sdocs/language`** — the allowed attributes and
|
|
21
|
+
value shapes for a block kind, the single source of truth behind both
|
|
22
|
+
diagnostics and the VS Code extension's attribute completions.
|
|
23
|
+
|
|
24
|
+
## [0.0.53] - 2026-07-05
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Content layout is configurable — globally and in place.** The config's
|
|
29
|
+
`content` option sets per-kind defaults, and the same names work as entity
|
|
30
|
+
and block attributes (block beats entity beats config):
|
|
31
|
+
- `maxWidth` — the content column for `[DOCS]`/`[PAGE]` (default `1200px`),
|
|
32
|
+
the stage for `[LAYOUT]` (default `100%`) and for `[preview]`/`[example]`
|
|
33
|
+
(default `100%`; narrower stages center).
|
|
34
|
+
- `padding` — page content `32px`, preview/example stages `16px`
|
|
35
|
+
(settable on `[DOCS]` as the entity default), layout stages `0px`.
|
|
36
|
+
- `direction` / `gap` — preview/example stages are flex containers:
|
|
37
|
+
`flex-direction` (default `row`) and `gap` (default `16px`).
|
|
38
|
+
- `toc` — `[PAGE]` table-of-contents visibility (default `true`;
|
|
39
|
+
`toc="false"` hides it).
|
|
40
|
+
|
|
10
41
|
## [0.0.52] - 2026-07-05
|
|
11
42
|
|
|
12
43
|
### Added
|
|
@@ -288,7 +288,7 @@
|
|
|
288
288
|
);
|
|
289
289
|
</script>
|
|
290
290
|
|
|
291
|
-
<div class="sdocs-component-view">
|
|
291
|
+
<div class="sdocs-component-view" style:max-width={doc.maxWidth}>
|
|
292
292
|
{#if focusedSnippet}
|
|
293
293
|
<!-- Example full-page view -->
|
|
294
294
|
<div class="sdocs-view-header">
|
|
@@ -482,7 +482,7 @@
|
|
|
482
482
|
<style>
|
|
483
483
|
.sdocs-component-view {
|
|
484
484
|
padding: 24px 32px;
|
|
485
|
-
max-width
|
|
485
|
+
/* max-width comes from the doc entry (config/entity cascade) */
|
|
486
486
|
font-family: var(--sans);
|
|
487
487
|
}
|
|
488
488
|
.sdocs-doc-section {
|
|
@@ -579,8 +579,8 @@
|
|
|
579
579
|
overflow: hidden;
|
|
580
580
|
}
|
|
581
581
|
.sdocs-preview-wrapper {
|
|
582
|
+
/* stage padding lives inside the iframe (config/entity/block cascade) */
|
|
582
583
|
background: var(--color-base-0);
|
|
583
|
-
padding: 16px;
|
|
584
584
|
}
|
|
585
585
|
.sdocs-divider {
|
|
586
586
|
border: none;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
25
|
<div class="sdocs-page-view">
|
|
26
|
-
<div class="sdocs-page-main">
|
|
26
|
+
<div class="sdocs-page-main" style:max-width={doc.maxWidth}>
|
|
27
27
|
<!-- Header -->
|
|
28
28
|
<div class="sdocs-view-header">
|
|
29
29
|
<h1 class="sdocs-view-title">{displayTitle(meta.title)}</h1>
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
</div>
|
|
42
42
|
|
|
43
43
|
<!-- Table of Contents -->
|
|
44
|
-
{#if toc.length > 0}
|
|
44
|
+
{#if toc.length > 0 && doc.showToc !== false}
|
|
45
45
|
<aside class="sdocs-toc">
|
|
46
46
|
<h3 class="sdocs-toc-title">On this page</h3>
|
|
47
47
|
<nav>
|
package/dist/language/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { scanSdoc, offsetToPosition, ENTITY_KINDS, SUB_BLOCK_KINDS, type Span, type EntityKind, type SubBlockKind, type AttrValue, type Attrs, type SubBlock, type Entity, type TagBlock, type ScanError, type SdocFile, } from './scanner.js';
|
|
2
2
|
export { projectSdoc, type SdocProjection, type ProjectedLineKind, } from './projection.js';
|
|
3
3
|
export { segmentPageBody, type PageSegment, } from './page-islands.js';
|
|
4
|
-
export { parseSdoc, parseArgsLiteral, slugifyTitle, normalizeBody, type ArgValue, type PreviewBlock, type ExampleBlock, type DocsEntity, type PageEntity, type LayoutEntity, type SdocEntity, type SdocDocument, } from './parser.js';
|
|
4
|
+
export { parseSdoc, parseArgsLiteral, slugifyTitle, normalizeBody, attributeRules, type AttrRule, type ArgValue, type Sizing, type PreviewBlock, type ExampleBlock, type DocsEntity, type PageEntity, type LayoutEntity, type SdocEntity, type SdocDocument, } from './parser.js';
|
package/dist/language/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { scanSdoc, offsetToPosition, ENTITY_KINDS, SUB_BLOCK_KINDS, } from './scanner.js';
|
|
2
2
|
export { projectSdoc, } from './projection.js';
|
|
3
3
|
export { segmentPageBody, } from './page-islands.js';
|
|
4
|
-
export { parseSdoc, parseArgsLiteral, slugifyTitle, normalizeBody, } from './parser.js';
|
|
4
|
+
export { parseSdoc, parseArgsLiteral, slugifyTitle, normalizeBody, attributeRules, } from './parser.js';
|
|
@@ -5,6 +5,21 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type ScanError, type Span, type TagBlock } from './scanner.js';
|
|
7
7
|
export type ArgValue = string | number | boolean;
|
|
8
|
+
/** Explicit presentation overrides from stage attributes */
|
|
9
|
+
export interface Sizing {
|
|
10
|
+
maxWidth: string | null;
|
|
11
|
+
padding: string | null;
|
|
12
|
+
/** flex-direction of preview/example stages */
|
|
13
|
+
direction: string | null;
|
|
14
|
+
/** gap of preview/example stages */
|
|
15
|
+
gap: string | null;
|
|
16
|
+
/** horizontal alignment of preview/example stage contents */
|
|
17
|
+
align: string | null;
|
|
18
|
+
/** vertical alignment of preview/example stage contents */
|
|
19
|
+
alignY: string | null;
|
|
20
|
+
/** table-of-contents visibility (PAGE) */
|
|
21
|
+
toc: boolean | null;
|
|
22
|
+
}
|
|
8
23
|
export interface PreviewBlock {
|
|
9
24
|
/** The identifier from component={X}; null when invalid/missing (already reported) */
|
|
10
25
|
componentName: string | null;
|
|
@@ -16,12 +31,14 @@ export interface PreviewBlock {
|
|
|
16
31
|
title: string | null;
|
|
17
32
|
/** Tab label: the title override or the component name */
|
|
18
33
|
label: string;
|
|
34
|
+
sizing: Sizing;
|
|
19
35
|
body: string;
|
|
20
36
|
bodySpan: Span;
|
|
21
37
|
span: Span;
|
|
22
38
|
}
|
|
23
39
|
export interface ExampleBlock {
|
|
24
40
|
title: string;
|
|
41
|
+
sizing: Sizing;
|
|
25
42
|
body: string;
|
|
26
43
|
bodySpan: Span;
|
|
27
44
|
span: Span;
|
|
@@ -31,6 +48,7 @@ export interface DocsEntity {
|
|
|
31
48
|
title: string;
|
|
32
49
|
slug: string;
|
|
33
50
|
description: string | null;
|
|
51
|
+
sizing: Sizing;
|
|
34
52
|
previews: PreviewBlock[];
|
|
35
53
|
examples: ExampleBlock[];
|
|
36
54
|
openerSpan: Span;
|
|
@@ -40,6 +58,7 @@ export interface PageEntity {
|
|
|
40
58
|
kind: 'PAGE';
|
|
41
59
|
title: string;
|
|
42
60
|
slug: string;
|
|
61
|
+
sizing: Sizing;
|
|
43
62
|
body: string;
|
|
44
63
|
bodySpan: Span;
|
|
45
64
|
openerSpan: Span;
|
|
@@ -49,7 +68,7 @@ export interface LayoutEntity {
|
|
|
49
68
|
kind: 'LAYOUT';
|
|
50
69
|
title: string;
|
|
51
70
|
slug: string;
|
|
52
|
-
|
|
71
|
+
sizing: Sizing;
|
|
53
72
|
body: string;
|
|
54
73
|
bodySpan: Span;
|
|
55
74
|
openerSpan: Span;
|
|
@@ -73,6 +92,17 @@ export interface SdocDocument {
|
|
|
73
92
|
export declare function normalizeBody(raw: string): string;
|
|
74
93
|
/** Slug used for entity addressing: relPath + '#' + slug. */
|
|
75
94
|
export declare function slugifyTitle(title: string): string;
|
|
95
|
+
export interface AttrRule {
|
|
96
|
+
/** required | optional */
|
|
97
|
+
required: boolean;
|
|
98
|
+
/** expected value kind */
|
|
99
|
+
kind: 'string' | 'expression';
|
|
100
|
+
hint: string;
|
|
101
|
+
}
|
|
102
|
+
/** Allowed attributes and their value shapes for a block, keyed by kind
|
|
103
|
+
* ('DOCS'|'PAGE'|'LAYOUT'|'preview'|'example'). Single source of truth for
|
|
104
|
+
* both diagnostics and editor attribute completions. */
|
|
105
|
+
export declare function attributeRules(kind: string): Record<string, AttrRule>;
|
|
76
106
|
/**
|
|
77
107
|
* Parse a preview args expression: a flat object literal whose values are
|
|
78
108
|
* plain literals (strings, numbers, booleans). Anything richer belongs in
|
package/dist/language/parser.js
CHANGED
|
@@ -43,17 +43,43 @@ export function slugifyTitle(title) {
|
|
|
43
43
|
.replace(/^-+|-+$/g, '') || 'untitled');
|
|
44
44
|
}
|
|
45
45
|
const IDENTIFIER_RE = /^[A-Z][A-Za-z0-9_]*$/;
|
|
46
|
+
const SIZING_ATTR_RULES = {
|
|
47
|
+
maxWidth: { required: false, kind: 'string', hint: 'maxWidth="1200px"' },
|
|
48
|
+
padding: { required: false, kind: 'string', hint: 'padding="32px"' },
|
|
49
|
+
};
|
|
50
|
+
const STAGE_LAYOUT_ATTR_RULES = {
|
|
51
|
+
direction: { required: false, kind: 'string', hint: 'direction="column"' },
|
|
52
|
+
gap: { required: false, kind: 'string', hint: 'gap="16px"' },
|
|
53
|
+
align: { required: false, kind: 'string', hint: 'align="center"' },
|
|
54
|
+
alignY: { required: false, kind: 'string', hint: 'alignY="middle"' },
|
|
55
|
+
};
|
|
56
|
+
function sizingOf(attrs) {
|
|
57
|
+
const toc = stringAttr(attrs, 'toc');
|
|
58
|
+
return {
|
|
59
|
+
maxWidth: stringAttr(attrs, 'maxWidth'),
|
|
60
|
+
padding: stringAttr(attrs, 'padding'),
|
|
61
|
+
direction: stringAttr(attrs, 'direction'),
|
|
62
|
+
gap: stringAttr(attrs, 'gap'),
|
|
63
|
+
align: stringAttr(attrs, 'align'),
|
|
64
|
+
alignY: stringAttr(attrs, 'alignY'),
|
|
65
|
+
toc: toc === null ? null : toc === 'true',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
46
68
|
const ENTITY_ATTR_RULES = {
|
|
47
69
|
DOCS: {
|
|
48
70
|
title: { required: true, kind: 'string', hint: 'title="Group / Name"' },
|
|
49
71
|
description: { required: false, kind: 'string', hint: 'description="…"' },
|
|
72
|
+
...SIZING_ATTR_RULES,
|
|
73
|
+
...STAGE_LAYOUT_ATTR_RULES,
|
|
50
74
|
},
|
|
51
75
|
PAGE: {
|
|
52
76
|
title: { required: true, kind: 'string', hint: 'title="Group / Name"' },
|
|
77
|
+
...SIZING_ATTR_RULES,
|
|
78
|
+
toc: { required: false, kind: 'string', hint: 'toc="false"' },
|
|
53
79
|
},
|
|
54
80
|
LAYOUT: {
|
|
55
81
|
title: { required: true, kind: 'string', hint: 'title="Group / Name"' },
|
|
56
|
-
|
|
82
|
+
...SIZING_ATTR_RULES,
|
|
57
83
|
},
|
|
58
84
|
};
|
|
59
85
|
const SUB_BLOCK_ATTR_RULES = {
|
|
@@ -61,11 +87,21 @@ const SUB_BLOCK_ATTR_RULES = {
|
|
|
61
87
|
component: { required: true, kind: 'expression', hint: 'component={Button}' },
|
|
62
88
|
args: { required: false, kind: 'expression', hint: 'args={{ label: "Hi" }}' },
|
|
63
89
|
title: { required: false, kind: 'string', hint: 'title="…"' },
|
|
90
|
+
...SIZING_ATTR_RULES,
|
|
91
|
+
...STAGE_LAYOUT_ATTR_RULES,
|
|
64
92
|
},
|
|
65
93
|
example: {
|
|
66
94
|
title: { required: true, kind: 'string', hint: 'title="…"' },
|
|
95
|
+
...SIZING_ATTR_RULES,
|
|
96
|
+
...STAGE_LAYOUT_ATTR_RULES,
|
|
67
97
|
},
|
|
68
98
|
};
|
|
99
|
+
/** Allowed attributes and their value shapes for a block, keyed by kind
|
|
100
|
+
* ('DOCS'|'PAGE'|'LAYOUT'|'preview'|'example'). Single source of truth for
|
|
101
|
+
* both diagnostics and editor attribute completions. */
|
|
102
|
+
export function attributeRules(kind) {
|
|
103
|
+
return ENTITY_ATTR_RULES[kind] ?? SUB_BLOCK_ATTR_RULES[kind] ?? {};
|
|
104
|
+
}
|
|
69
105
|
function checkAttrs(owner, attrs, rules, ownerSpan, diagnostics) {
|
|
70
106
|
for (const [name, value] of Object.entries(attrs)) {
|
|
71
107
|
const rule = rules[name];
|
|
@@ -192,6 +228,7 @@ function parsePreview(block, diagnostics) {
|
|
|
192
228
|
argsRaw,
|
|
193
229
|
title,
|
|
194
230
|
label: title ?? componentName ?? 'Preview',
|
|
231
|
+
sizing: sizingOf(block.attrs),
|
|
195
232
|
body: normalizeBody(block.body),
|
|
196
233
|
bodySpan: block.bodySpan,
|
|
197
234
|
span: block.span,
|
|
@@ -229,6 +266,7 @@ function parseDocs(entity, diagnostics) {
|
|
|
229
266
|
exampleTitles.add(title);
|
|
230
267
|
examples.push({
|
|
231
268
|
title,
|
|
269
|
+
sizing: sizingOf(block.attrs),
|
|
232
270
|
body: normalizeBody(block.body),
|
|
233
271
|
bodySpan: block.bodySpan,
|
|
234
272
|
span: block.span,
|
|
@@ -241,6 +279,7 @@ function parseDocs(entity, diagnostics) {
|
|
|
241
279
|
title,
|
|
242
280
|
slug: slugifyTitle(title),
|
|
243
281
|
description: stringAttr(entity.attrs, 'description'),
|
|
282
|
+
sizing: sizingOf(entity.attrs),
|
|
244
283
|
previews,
|
|
245
284
|
examples,
|
|
246
285
|
openerSpan: entity.openerSpan,
|
|
@@ -263,15 +302,13 @@ export function parseSdoc(source) {
|
|
|
263
302
|
const base = {
|
|
264
303
|
title,
|
|
265
304
|
slug: slugifyTitle(title),
|
|
305
|
+
sizing: sizingOf(entity.attrs),
|
|
266
306
|
body: normalizeBody(entity.body),
|
|
267
307
|
bodySpan: entity.bodySpan,
|
|
268
308
|
openerSpan: entity.openerSpan,
|
|
269
309
|
span: entity.span,
|
|
270
310
|
};
|
|
271
|
-
typed =
|
|
272
|
-
entity.kind === 'PAGE'
|
|
273
|
-
? { kind: 'PAGE', ...base }
|
|
274
|
-
: { kind: 'LAYOUT', padding: stringAttr(entity.attrs, 'padding'), ...base };
|
|
311
|
+
typed = entity.kind === 'PAGE' ? { kind: 'PAGE', ...base } : { kind: 'LAYOUT', ...base };
|
|
275
312
|
}
|
|
276
313
|
if (slugs.has(typed.slug)) {
|
|
277
314
|
diagnostics.push({
|
package/dist/server/config.js
CHANGED
|
@@ -13,6 +13,18 @@ const DEFAULTS = {
|
|
|
13
13
|
order: {},
|
|
14
14
|
open: [],
|
|
15
15
|
},
|
|
16
|
+
content: {
|
|
17
|
+
page: { maxWidth: '1200px', padding: '32px', toc: true },
|
|
18
|
+
docs: {
|
|
19
|
+
maxWidth: '1200px',
|
|
20
|
+
padding: '16px',
|
|
21
|
+
direction: 'row',
|
|
22
|
+
gap: '16px',
|
|
23
|
+
align: 'left',
|
|
24
|
+
alignY: 'top',
|
|
25
|
+
},
|
|
26
|
+
layout: { maxWidth: '100%', padding: '0px' },
|
|
27
|
+
},
|
|
16
28
|
};
|
|
17
29
|
/** Find the config file path in the given directory */
|
|
18
30
|
export function findConfigFile(root) {
|
|
@@ -83,5 +95,10 @@ export function resolveConfig(userConfig) {
|
|
|
83
95
|
order: userConfig.sidebar?.order ?? DEFAULTS.sidebar.order,
|
|
84
96
|
open: userConfig.sidebar?.open ?? DEFAULTS.sidebar.open,
|
|
85
97
|
},
|
|
98
|
+
content: {
|
|
99
|
+
page: { ...DEFAULTS.content.page, ...userConfig.content?.page },
|
|
100
|
+
docs: { ...DEFAULTS.content.docs, ...userConfig.content?.docs },
|
|
101
|
+
layout: { ...DEFAULTS.content.layout, ...userConfig.content?.layout },
|
|
102
|
+
},
|
|
86
103
|
};
|
|
87
104
|
}
|
|
@@ -23,7 +23,14 @@ export declare function resolveScriptImports(script: string, docFilePath: string
|
|
|
23
23
|
/** Generate a virtual Svelte iframe wrapper component for a snippet.
|
|
24
24
|
* Includes $state for reactive prop updates via postMessage, invokes
|
|
25
25
|
* component methods on request, and broadcasts exported state values. */
|
|
26
|
-
export declare function generateIframeComponent(scriptPrelude: string, snippetBody: string, stateNames?: string[], componentName?: string
|
|
26
|
+
export declare function generateIframeComponent(scriptPrelude: string, snippetBody: string, stateNames?: string[], componentName?: string, stage?: {
|
|
27
|
+
maxWidth: string;
|
|
28
|
+
padding: string;
|
|
29
|
+
direction?: string;
|
|
30
|
+
gap?: string;
|
|
31
|
+
align?: string;
|
|
32
|
+
alignY?: string;
|
|
33
|
+
}): string;
|
|
27
34
|
/** The JS that boots a preview page: mount the wrapper + parent-frame messaging. */
|
|
28
35
|
export declare function generateMountScript(iframeComponentId: string): string;
|
|
29
36
|
/** Generate the HTML page served inside the iframe (dev / CLI-build inputs) */
|
|
@@ -81,7 +81,54 @@ function injectRootRef(snippetBody, componentName) {
|
|
|
81
81
|
/** Generate a virtual Svelte iframe wrapper component for a snippet.
|
|
82
82
|
* Includes $state for reactive prop updates via postMessage, invokes
|
|
83
83
|
* component methods on request, and broadcasts exported state values. */
|
|
84
|
-
export function generateIframeComponent(scriptPrelude, snippetBody, stateNames = [], componentName) {
|
|
84
|
+
export function generateIframeComponent(scriptPrelude, snippetBody, stateNames = [], componentName, stage) {
|
|
85
|
+
// The stage layout (config -> entity -> block cascade) applies here, inside
|
|
86
|
+
// the iframe, so every consumer of the preview page gets it. Preview and
|
|
87
|
+
// example stages are flex containers (direction + gap + alignment); page and
|
|
88
|
+
// layout stages are flow-root blocks. Both contain child margins, so the
|
|
89
|
+
// height reported for iframe auto-sizing is exact.
|
|
90
|
+
//
|
|
91
|
+
// align/alignY are *physical* (horizontal/vertical); which flex property
|
|
92
|
+
// each drives depends on direction. The flow axis (main) takes
|
|
93
|
+
// justify-content, the other (cross) takes align-items — and align-items
|
|
94
|
+
// has no distribution value, so a 'justify' that lands on the cross axis
|
|
95
|
+
// falls back to stretch.
|
|
96
|
+
const H = {
|
|
97
|
+
left: 'flex-start',
|
|
98
|
+
center: 'center',
|
|
99
|
+
right: 'flex-end',
|
|
100
|
+
justify: 'space-between',
|
|
101
|
+
};
|
|
102
|
+
const V = {
|
|
103
|
+
top: 'flex-start',
|
|
104
|
+
middle: 'center',
|
|
105
|
+
bottom: 'flex-end',
|
|
106
|
+
justify: 'space-between',
|
|
107
|
+
};
|
|
108
|
+
const flexStage = () => {
|
|
109
|
+
const h = H[stage.align ?? 'left'] ?? 'flex-start';
|
|
110
|
+
const v = V[stage.alignY ?? 'top'] ?? 'flex-start';
|
|
111
|
+
const isColumn = String(stage.direction).startsWith('column');
|
|
112
|
+
const justify = isColumn ? v : h;
|
|
113
|
+
let alignItems = isColumn ? h : v;
|
|
114
|
+
if (alignItems === 'space-between')
|
|
115
|
+
alignItems = 'stretch';
|
|
116
|
+
return [
|
|
117
|
+
'display: flex',
|
|
118
|
+
`flex-direction: ${stage.direction}`,
|
|
119
|
+
'flex-wrap: wrap',
|
|
120
|
+
`justify-content: ${justify}`,
|
|
121
|
+
`align-items: ${alignItems}`,
|
|
122
|
+
`gap: ${stage.gap}`,
|
|
123
|
+
];
|
|
124
|
+
};
|
|
125
|
+
const stageStyle = [
|
|
126
|
+
...(stage?.direction ? flexStage() : ['display: flow-root']),
|
|
127
|
+
...(stage ? [`padding: ${stage.padding}`] : []),
|
|
128
|
+
...(stage && stage.maxWidth !== '100%'
|
|
129
|
+
? [`max-width: ${stage.maxWidth}`, 'margin-inline: auto']
|
|
130
|
+
: []),
|
|
131
|
+
].join('; ');
|
|
85
132
|
// The file <script> (imports + shared values), lifted verbatim so previews
|
|
86
133
|
// and examples see everything the entity's siblings do.
|
|
87
134
|
const importBlock = scriptPrelude.trim() ? scriptPrelude.trim() + '\n' : '';
|
|
@@ -151,7 +198,7 @@ ${stateBroadcast}
|
|
|
151
198
|
});
|
|
152
199
|
</script>
|
|
153
200
|
|
|
154
|
-
<div id="sdocs-preview" style="
|
|
201
|
+
<div id="sdocs-preview" style="${stageStyle}">
|
|
155
202
|
{#snippet SdocsPreview(args)}
|
|
156
203
|
${injectRootRef(snippetBody, componentName)}
|
|
157
204
|
{/snippet}
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,46 @@ export interface SdocsConfig {
|
|
|
19
19
|
/** Folders expanded by default on load. */
|
|
20
20
|
open?: string[];
|
|
21
21
|
};
|
|
22
|
+
/** Content presentation per entity kind; entity/block attributes override these. */
|
|
23
|
+
content?: {
|
|
24
|
+
/** [PAGE] content. Defaults: maxWidth '1200px', padding '32px', toc true. */
|
|
25
|
+
page?: ContentSizing & {
|
|
26
|
+
/** Show the table of contents. Default: true */
|
|
27
|
+
toc?: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** [DOCS] pages: maxWidth is the content column (default '1200px');
|
|
30
|
+
* padding/direction/gap are the default preview/example stage layout
|
|
31
|
+
* (defaults '16px', 'row', '16px'). */
|
|
32
|
+
docs?: ContentSizing & {
|
|
33
|
+
/** Stage flex-direction. Default: 'row' */
|
|
34
|
+
direction?: string;
|
|
35
|
+
/** Stage gap. Default: '16px' */
|
|
36
|
+
gap?: string;
|
|
37
|
+
/** Horizontal alignment of stage contents: 'left'|'center'|'right'|'justify'. Default: 'left' */
|
|
38
|
+
align?: string;
|
|
39
|
+
/** Vertical alignment of stage contents: 'top'|'middle'|'bottom'. Default: 'top' */
|
|
40
|
+
alignY?: string;
|
|
41
|
+
};
|
|
42
|
+
/** [LAYOUT] stages. Defaults: maxWidth '100%', padding '0px'. */
|
|
43
|
+
layout?: ContentSizing;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Content sizing knobs (any CSS length; padding takes CSS shorthand) */
|
|
47
|
+
export interface ContentSizing {
|
|
48
|
+
maxWidth?: string;
|
|
49
|
+
padding?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Resolved stage layout applied inside a preview iframe */
|
|
52
|
+
export interface StageLayout {
|
|
53
|
+
maxWidth: string;
|
|
54
|
+
padding: string;
|
|
55
|
+
/** flex-direction + gap + align; set for preview/example stages only */
|
|
56
|
+
direction?: string;
|
|
57
|
+
gap?: string;
|
|
58
|
+
/** horizontal ('left'|'center'|'right'|'justify') — mapped by direction */
|
|
59
|
+
align?: string;
|
|
60
|
+
/** vertical ('top'|'middle'|'bottom') — mapped by direction */
|
|
61
|
+
alignY?: string;
|
|
22
62
|
}
|
|
23
63
|
/** Resolved config with all defaults applied */
|
|
24
64
|
export interface ResolvedSdocsConfig {
|
|
@@ -32,6 +72,18 @@ export interface ResolvedSdocsConfig {
|
|
|
32
72
|
order: Record<string, string[]>;
|
|
33
73
|
open: string[];
|
|
34
74
|
};
|
|
75
|
+
content: {
|
|
76
|
+
page: Required<ContentSizing> & {
|
|
77
|
+
toc: boolean;
|
|
78
|
+
};
|
|
79
|
+
docs: Required<ContentSizing> & {
|
|
80
|
+
direction: string;
|
|
81
|
+
gap: string;
|
|
82
|
+
align: string;
|
|
83
|
+
alignY: string;
|
|
84
|
+
};
|
|
85
|
+
layout: Required<ContentSizing>;
|
|
86
|
+
};
|
|
35
87
|
}
|
|
36
88
|
/** Entity metadata from a [DOCS]/[PAGE]/[LAYOUT] opener */
|
|
37
89
|
export interface SdocMeta {
|
|
@@ -87,6 +139,8 @@ export interface ExtractedSnippet {
|
|
|
87
139
|
highlightedHtml?: string;
|
|
88
140
|
/** Preview URL for iframe (added by virtual module) */
|
|
89
141
|
previewUrl?: string;
|
|
142
|
+
/** Resolved stage layout applied inside the iframe (config → entity → block) */
|
|
143
|
+
stage?: StageLayout;
|
|
90
144
|
}
|
|
91
145
|
/** One [preview] of a DOCS entity: a live showcase of one component */
|
|
92
146
|
export interface PreviewEntry {
|
|
@@ -128,6 +182,8 @@ export interface DocEntry {
|
|
|
128
182
|
content: ExtractedSnippet | null;
|
|
129
183
|
/** Table of contents headings (pages only) */
|
|
130
184
|
toc?: TocHeading[];
|
|
131
|
-
/**
|
|
132
|
-
|
|
185
|
+
/** Resolved content-column max width (component and page kinds) */
|
|
186
|
+
maxWidth?: string;
|
|
187
|
+
/** Resolved table-of-contents visibility (pages only) */
|
|
188
|
+
showToc?: boolean;
|
|
133
189
|
}
|
package/dist/vite.js
CHANGED
|
@@ -228,7 +228,7 @@ export function sdocsPlugin(userConfig) {
|
|
|
228
228
|
// example iframes fall back to the first preview's component.
|
|
229
229
|
const preview = entry.previews.find((p) => p.snippet.slug === snippet.slug) ?? entry.previews[0];
|
|
230
230
|
const stateNames = (preview?.componentData?.state ?? []).map((s) => s.name);
|
|
231
|
-
return generateIframeComponent(scriptPrelude, snippet.body, stateNames, preview?.componentName ?? undefined);
|
|
231
|
+
return generateIframeComponent(scriptPrelude, snippet.body, stateNames, preview?.componentName ?? undefined, snippet.stage);
|
|
232
232
|
}
|
|
233
233
|
// Virtual mount script for an emitted preview page
|
|
234
234
|
if (id.startsWith('\0' + MOUNT_PREFIX)) {
|
|
@@ -299,6 +299,29 @@ export function sdocsPlugin(userConfig) {
|
|
|
299
299
|
examples: [],
|
|
300
300
|
content: null,
|
|
301
301
|
};
|
|
302
|
+
// Sizing cascade: block attribute -> entity attribute -> config default.
|
|
303
|
+
const kindKey = entity.kind === 'DOCS' ? 'docs' : entity.kind === 'PAGE' ? 'page' : 'layout';
|
|
304
|
+
const kindDefaults = config.content[kindKey];
|
|
305
|
+
const stageOf = (block) => ({
|
|
306
|
+
// Entity-level maxWidth on DOCS/PAGE is the content column, not the
|
|
307
|
+
// stage; stages inside them span their panel unless the block says so.
|
|
308
|
+
maxWidth: block?.maxWidth ??
|
|
309
|
+
(entity.kind === 'LAYOUT' ? (entity.sizing.maxWidth ?? kindDefaults.maxWidth) : '100%'),
|
|
310
|
+
padding: block?.padding ?? entity.sizing.padding ?? kindDefaults.padding,
|
|
311
|
+
// direction/gap/align flex the preview/example stages only
|
|
312
|
+
...(entity.kind === 'DOCS' && block
|
|
313
|
+
? {
|
|
314
|
+
direction: block.direction ?? entity.sizing.direction ?? config.content.docs.direction,
|
|
315
|
+
gap: block.gap ?? entity.sizing.gap ?? config.content.docs.gap,
|
|
316
|
+
align: block.align ?? entity.sizing.align ?? config.content.docs.align,
|
|
317
|
+
alignY: block.alignY ?? entity.sizing.alignY ?? config.content.docs.alignY,
|
|
318
|
+
}
|
|
319
|
+
: {}),
|
|
320
|
+
});
|
|
321
|
+
entry.maxWidth = entity.sizing.maxWidth ?? kindDefaults.maxWidth;
|
|
322
|
+
if (entity.kind === 'PAGE') {
|
|
323
|
+
entry.showToc = entity.sizing.toc ?? config.content.page.toc;
|
|
324
|
+
}
|
|
302
325
|
if (entity.kind === 'DOCS') {
|
|
303
326
|
if (entity.description)
|
|
304
327
|
entry.meta.description = entity.description;
|
|
@@ -318,6 +341,7 @@ export function sdocsPlugin(userConfig) {
|
|
|
318
341
|
console.warn(`[sdocs] ${filePath}: component {${preview.componentName}} is not imported in the file's <script>`);
|
|
319
342
|
}
|
|
320
343
|
}
|
|
344
|
+
snippet.stage = stageOf(preview.sizing);
|
|
321
345
|
snippet.highlightedHtml = await highlight(snippet.body);
|
|
322
346
|
entry.previews.push({
|
|
323
347
|
label: preview.label,
|
|
@@ -330,6 +354,10 @@ export function sdocsPlugin(userConfig) {
|
|
|
330
354
|
});
|
|
331
355
|
}
|
|
332
356
|
entry.examples = snippets.filter((s) => s.role === 'example');
|
|
357
|
+
entity.examples.forEach((example, i) => {
|
|
358
|
+
if (entry.examples[i])
|
|
359
|
+
entry.examples[i].stage = stageOf(example.sizing);
|
|
360
|
+
});
|
|
333
361
|
for (const example of entry.examples) {
|
|
334
362
|
example.highlightedHtml = await highlight(example.body);
|
|
335
363
|
}
|
|
@@ -337,12 +365,13 @@ export function sdocsPlugin(userConfig) {
|
|
|
337
365
|
else if (entity.kind === 'PAGE') {
|
|
338
366
|
const rendered = await renderPageMarkdown(entity.body);
|
|
339
367
|
snippets[0].body = rendered.html;
|
|
368
|
+
snippets[0].stage = stageOf();
|
|
340
369
|
entry.content = snippets[0];
|
|
341
370
|
entry.toc = rendered.toc;
|
|
342
371
|
}
|
|
343
372
|
else {
|
|
373
|
+
snippets[0].stage = stageOf();
|
|
344
374
|
entry.content = snippets[0];
|
|
345
|
-
entry.padding = entity.padding;
|
|
346
375
|
}
|
|
347
376
|
docEntries.set(entityKey(filePath, entity.slug), entry);
|
|
348
377
|
}
|
|
@@ -369,7 +398,8 @@ export function sdocsPlugin(userConfig) {
|
|
|
369
398
|
examples: e.examples.map((s) => withUrl(e, s)),
|
|
370
399
|
content: e.content ? withUrl(e, e.content) : null,
|
|
371
400
|
toc: e.toc,
|
|
372
|
-
|
|
401
|
+
maxWidth: e.maxWidth,
|
|
402
|
+
showToc: e.showToc,
|
|
373
403
|
}));
|
|
374
404
|
// Extract named CSS stylesheet names (empty array if single string or null)
|
|
375
405
|
const cssNames = config.css && typeof config.css === 'object'
|