@brett_lamy/docstream 1.2.1 → 1.2.3
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 +36 -5
- package/package.json +1 -1
- package/src/demo/DemoViewer.tsx +52 -9
- package/src/demo/index.ts +1 -0
- package/src/demo/markdown.ts +17 -3
- package/src/demo/surface.ts +67 -0
- package/src/gitbook/ast.ts +43 -2
- package/src/gitbook/attrs.ts +39 -0
- package/src/gitbook/flatten.ts +1 -1
- package/src/gitbook/index.ts +2 -1
- package/src/gitbook/inline.ts +502 -163
- package/src/gitbook/parse.ts +43 -22
- package/src/gitbook/serialize.ts +52 -43
- package/src/index.ts +2 -0
- package/src/styles.css +29 -19
package/src/gitbook/parse.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Block,
|
|
3
|
+
CodeBlockNode,
|
|
3
4
|
ColumnNode,
|
|
4
5
|
CommandNode,
|
|
5
6
|
DemoInlineFile,
|
|
@@ -15,6 +16,7 @@ import type {
|
|
|
15
16
|
TabNode,
|
|
16
17
|
UpdateNode,
|
|
17
18
|
} from "./ast"
|
|
19
|
+
import { TAG_ATTRS, parseAttrs } from "./attrs"
|
|
18
20
|
import { footnoteDefinitions, parseInline, plainText, refDefinitions } from "./inline"
|
|
19
21
|
|
|
20
22
|
// Minimal HTML-inline → markdown-inline bridge for HTML table cells.
|
|
@@ -37,20 +39,31 @@ function splitTableRow(row: string): string[] {
|
|
|
37
39
|
const trimmed = row.trim().replace(/^\|/, "").replace(/\|$/, "")
|
|
38
40
|
const cells: string[] = []
|
|
39
41
|
let cur = ""
|
|
40
|
-
let inCode = false
|
|
41
42
|
for (let k = 0; k < trimmed.length; k++) {
|
|
42
43
|
const ch = trimmed[k]
|
|
43
|
-
if (ch === "\\"
|
|
44
|
-
|
|
44
|
+
if (ch === "\\") {
|
|
45
|
+
// `\|` is a literal pipe (in code spans too); any other escape passes through for the inline parser.
|
|
46
|
+
cur += trimmed[k + 1] === "|" ? "|" : trimmed.slice(k, k + 2)
|
|
45
47
|
k++
|
|
46
48
|
continue
|
|
47
49
|
}
|
|
48
50
|
if (ch === "`") {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
// A code span (a backtick run closed by one of the same length) keeps its pipes; a lone run is literal.
|
|
52
|
+
let n = 1
|
|
53
|
+
while (trimmed[k + n] === "`") n++
|
|
54
|
+
const fence = "`".repeat(n)
|
|
55
|
+
let end = trimmed.indexOf(fence, k + n)
|
|
56
|
+
while (end !== -1 && (trimmed[end + n] === "`" || trimmed[end - 1] === "`")) {
|
|
57
|
+
let e = end
|
|
58
|
+
while (trimmed[e] === "`") e++
|
|
59
|
+
end = trimmed.indexOf(fence, e)
|
|
60
|
+
}
|
|
61
|
+
const stop = end === -1 ? k + n : end + n
|
|
62
|
+
cur += trimmed.slice(k, stop).replace(/\\\|/g, "|")
|
|
63
|
+
k = stop - 1
|
|
51
64
|
continue
|
|
52
65
|
}
|
|
53
|
-
if (ch === "|"
|
|
66
|
+
if (ch === "|") {
|
|
54
67
|
cells.push(cur)
|
|
55
68
|
cur = ""
|
|
56
69
|
continue
|
|
@@ -61,22 +74,23 @@ function splitTableRow(row: string): string[] {
|
|
|
61
74
|
return cells
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
const TEMPLATE_RE =
|
|
65
|
-
|
|
66
|
-
function parseAttrs(raw: string | undefined): Record<string, string> {
|
|
67
|
-
const attrs: Record<string, string> = {}
|
|
68
|
-
if (!raw) return attrs
|
|
69
|
-
for (const m of raw.matchAll(/([\w-]+)="([^"]*)"/g)) {
|
|
70
|
-
attrs[m[1]] = m[2]
|
|
71
|
-
}
|
|
72
|
-
return attrs
|
|
73
|
-
}
|
|
77
|
+
const TEMPLATE_RE = new RegExp(String.raw`^\s*\{%\s*(\S+?)(\s+${TAG_ATTRS})?\s*%\}\s*$`)
|
|
74
78
|
|
|
75
79
|
function booleanAttr(attrs: Record<string, string>, key: string): boolean | undefined {
|
|
76
80
|
if (!(key in attrs)) return undefined
|
|
77
81
|
return attrs[key] !== "false"
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
/**
|
|
85
|
+
* A fence's line numbers: `lineNumbers` / `showLineNumbers` when written (remembered as
|
|
86
|
+
* explicit so the attribute round-trips even when it equals the default), otherwise on for
|
|
87
|
+
* titled (file) fences and off for untitled ones.
|
|
88
|
+
*/
|
|
89
|
+
function lineNumbersFrom(attrs: Record<string, string>, titled: boolean): Pick<CodeBlockNode, "lineNumbers" | "lineNumbersExplicit"> {
|
|
90
|
+
const written = booleanAttr(attrs, "lineNumbers") ?? booleanAttr(attrs, "showLineNumbers")
|
|
91
|
+
return written === undefined ? { lineNumbers: titled } : { lineNumbers: written, lineNumbersExplicit: true }
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
function positiveNumberAttr(attrs: Record<string, string>, key: string): number | undefined {
|
|
81
95
|
if (!(key in attrs)) return undefined
|
|
82
96
|
const value = Number(attrs[key])
|
|
@@ -224,7 +238,7 @@ export function parseDemoVariants(raw: string): DemoVariantOption[] {
|
|
|
224
238
|
}
|
|
225
239
|
|
|
226
240
|
/** `{% command %}npm install x{% endcommand %}` on one line. */
|
|
227
|
-
const COMMAND_ONE_LINE_RE =
|
|
241
|
+
const COMMAND_ONE_LINE_RE = new RegExp(String.raw`^\s*\{%\s*command(\s${TAG_ATTRS})?\s*%\}(.*?)\{%\s*endcommand\s*%\}\s*$`)
|
|
228
242
|
|
|
229
243
|
function commandNode(attrs: Record<string, string>, raw: string): CommandNode {
|
|
230
244
|
const overrides: NonNullable<CommandNode["overrides"]> = {}
|
|
@@ -326,6 +340,11 @@ function parseDemoTag(attrs: Record<string, string>): DemoNode {
|
|
|
326
340
|
...(variants.length ? { variants } : {}),
|
|
327
341
|
...((DEMO_VIEWPORTS as string[]).includes(attrs.viewport) ? { viewport: attrs.viewport as DemoViewport } : {}),
|
|
328
342
|
...(attrs.entry ? { entry: attrs.entry } : {}),
|
|
343
|
+
...(attrs.status ? { status: attrs.status } : {}),
|
|
344
|
+
...(booleanAttr(attrs, "bleed") === undefined ? {} : { bleed: booleanAttr(attrs, "bleed") }),
|
|
345
|
+
...(attrs.className ? { className: attrs.className } : {}),
|
|
346
|
+
...(attrs.surface?.trim() ? { surface: attrs.surface } : {}),
|
|
347
|
+
...(attrs.variantsWidth ? { variantsWidth: attrs.variantsWidth } : {}),
|
|
329
348
|
}
|
|
330
349
|
}
|
|
331
350
|
|
|
@@ -435,7 +454,7 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
435
454
|
const code = inner.find((b) => b.type === "code")
|
|
436
455
|
if (code && code.type === "code") {
|
|
437
456
|
code.title = tag.attrs.title ?? null
|
|
438
|
-
|
|
457
|
+
Object.assign(code, lineNumbersFrom(tag.attrs, code.title !== null))
|
|
439
458
|
code.live = tag.attrs.live === "true"
|
|
440
459
|
code.entry = tag.attrs.entry ?? null
|
|
441
460
|
blocks.push(code)
|
|
@@ -655,7 +674,9 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
655
674
|
const fence = trimmed.match(/^(`{3,}|~{3,})([^\s`]*)?(?:\s+(.*?))?\s*$/)
|
|
656
675
|
if (fence) {
|
|
657
676
|
flushParagraph()
|
|
658
|
-
|
|
677
|
+
// ```` ```title="x" ```` (attributes, no language): the first word is not a language.
|
|
678
|
+
const bareAttrs = !!fence[2]?.includes("=")
|
|
679
|
+
const attrs = parseAttrs(bareAttrs ? [fence[2], fence[3]].filter(Boolean).join(" ") : fence[3])
|
|
659
680
|
const code: string[] = []
|
|
660
681
|
i++
|
|
661
682
|
while (i < lines.length && !lines[i].trim().startsWith(fence[1])) {
|
|
@@ -665,9 +686,9 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
665
686
|
i++
|
|
666
687
|
blocks.push({
|
|
667
688
|
type: "code",
|
|
668
|
-
language: fence[2] || null,
|
|
689
|
+
language: (!bareAttrs && fence[2]) || null,
|
|
669
690
|
title: attrs.title ?? null,
|
|
670
|
-
|
|
691
|
+
...lineNumbersFrom(attrs, attrs.title !== undefined),
|
|
671
692
|
code: code.join("\n"),
|
|
672
693
|
...(booleanAttr(attrs, "live") === undefined ? {} : { live: booleanAttr(attrs, "live") }),
|
|
673
694
|
...(attrs.entry ? { entry: attrs.entry } : {}),
|
|
@@ -691,7 +712,7 @@ export function parseBlocks(lines: string[]): Block[] {
|
|
|
691
712
|
code.push(lines[i].slice(4))
|
|
692
713
|
i++
|
|
693
714
|
}
|
|
694
|
-
blocks.push({ type: "code", language: null, title: null, lineNumbers:
|
|
715
|
+
blocks.push({ type: "code", language: null, title: null, lineNumbers: false, code: code.join("\n") })
|
|
695
716
|
continue
|
|
696
717
|
}
|
|
697
718
|
|
package/src/gitbook/serialize.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Block, DemoInlineFile, DemoNode, DocumentNode, Inline, ListItemNode } from "./ast"
|
|
2
|
+
import { attr } from "./attrs"
|
|
2
3
|
import { serializeInline, serializeReference } from "./inline"
|
|
3
4
|
|
|
4
5
|
export function serializeMarkdown(doc: DocumentNode): string {
|
|
@@ -34,36 +35,38 @@ function serializeBlock(b: Block): string {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
case "code": {
|
|
38
|
+
// Line numbers default to on for titled fences, off for untitled ones.
|
|
39
|
+
const numbersByDefault = b.title !== null && b.title !== undefined && b.title !== ""
|
|
37
40
|
const attrs = [
|
|
38
|
-
b.title ?
|
|
39
|
-
|
|
40
|
-
b.live ?
|
|
41
|
-
b.entry ?
|
|
42
|
-
b.collapsedCodeLines ?
|
|
43
|
-
b.expandedCodeLines ?
|
|
44
|
-
].
|
|
45
|
-
const info =
|
|
41
|
+
b.title ? attr("title", b.title) : "",
|
|
42
|
+
b.lineNumbers !== numbersByDefault || b.lineNumbersExplicit ? attr("lineNumbers", b.lineNumbers) : "",
|
|
43
|
+
b.live ? attr("live", true) : "",
|
|
44
|
+
b.entry ? attr("entry", b.entry) : "",
|
|
45
|
+
b.collapsedCodeLines ? attr("collapsedCodeLines", b.collapsedCodeLines) : "",
|
|
46
|
+
b.expandedCodeLines ? attr("expandedCodeLines", b.expandedCodeLines) : "",
|
|
47
|
+
].join("")
|
|
48
|
+
const info = `${b.language ?? ""}${b.language ? attrs : attrs.trimStart()}`
|
|
46
49
|
return `\`\`\`${info}\n${b.code}\n\`\`\``
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
case "hint":
|
|
50
|
-
return `{% hint
|
|
53
|
+
return `{% hint${attr("style", b.style)} %}\n${serializeBlocks(b.children)}\n{% endhint %}`
|
|
51
54
|
|
|
52
55
|
case "tabs": {
|
|
53
56
|
const attrs = [
|
|
54
|
-
b.title ?
|
|
55
|
-
b.title && b.level && b.level !== 2 ?
|
|
56
|
-
b.sync ?
|
|
57
|
+
b.title ? attr("title", b.title) : "",
|
|
58
|
+
b.title && b.level && b.level !== 2 ? attr("level", b.level) : "",
|
|
59
|
+
b.sync ? attr("sync", b.sync) : "",
|
|
57
60
|
].join("")
|
|
58
61
|
return `{% tabs${attrs} %}\n${b.tabs
|
|
59
|
-
.map((t) => `{% tab
|
|
62
|
+
.map((t) => `{% tab${attr("title", t.title)} %}\n${serializeBlocks(t.children)}\n{% endtab %}`)
|
|
60
63
|
.join("\n\n")}\n{% endtabs %}`
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
case "command": {
|
|
64
67
|
const attrs = [
|
|
65
|
-
...(["pnpm", "yarn", "bun"] as const).map((pm) => (b.overrides?.[pm] ?
|
|
66
|
-
b.sync && b.sync !== "pm" ?
|
|
68
|
+
...(["pnpm", "yarn", "bun"] as const).map((pm) => (b.overrides?.[pm] ? attr(pm, b.overrides[pm]) : "")),
|
|
69
|
+
b.sync && b.sync !== "pm" ? attr("sync", b.sync) : "",
|
|
67
70
|
].join("")
|
|
68
71
|
return b.command.includes("\n")
|
|
69
72
|
? `{% command${attrs} %}\n${b.command}\n{% endcommand %}`
|
|
@@ -83,43 +86,48 @@ function serializeBlock(b: Block): string {
|
|
|
83
86
|
|
|
84
87
|
case "embed": {
|
|
85
88
|
const attrs = [
|
|
86
|
-
|
|
87
|
-
b.title ?
|
|
88
|
-
b.poster ?
|
|
89
|
-
b.autoplay === undefined ? "" :
|
|
90
|
-
b.loop === undefined ? "" :
|
|
91
|
-
b.muted === undefined ? "" :
|
|
92
|
-
b.controls === undefined ? "" :
|
|
89
|
+
attr("url", b.url),
|
|
90
|
+
b.title ? attr("title", b.title) : "",
|
|
91
|
+
b.poster ? attr("poster", b.poster) : "",
|
|
92
|
+
b.autoplay === undefined ? "" : attr("autoplay", b.autoplay),
|
|
93
|
+
b.loop === undefined ? "" : attr("loop", b.loop),
|
|
94
|
+
b.muted === undefined ? "" : attr("muted", b.muted),
|
|
95
|
+
b.controls === undefined ? "" : attr("controls", b.controls),
|
|
93
96
|
].join("")
|
|
94
97
|
return `{% embed${attrs} %}`
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
case "content-ref":
|
|
98
|
-
return `{% content-ref
|
|
101
|
+
return `{% content-ref${attr("url", b.url)} %}\n${serializeInline(b.children)}\n{% endcontent-ref %}`
|
|
99
102
|
|
|
100
103
|
case "source-ref": {
|
|
101
104
|
const attrs = [
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
b.title ?
|
|
105
|
+
attr("mount", b.mount),
|
|
106
|
+
attr("path", b.path),
|
|
107
|
+
attr("export", b.exportName),
|
|
108
|
+
attr("kind", b.kind),
|
|
109
|
+
b.title ? attr("title", b.title) : "",
|
|
107
110
|
].join("")
|
|
108
111
|
return `{% source-ref${attrs} %}`
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
case "demo": {
|
|
112
115
|
const attrs = [
|
|
113
|
-
|
|
114
|
-
b.title ?
|
|
115
|
-
b.description ?
|
|
116
|
-
b.height ?
|
|
117
|
-
b.layout ?
|
|
116
|
+
attr("src", b.src),
|
|
117
|
+
b.title ? attr("title", b.title) : "",
|
|
118
|
+
b.description ? attr("description", b.description) : "",
|
|
119
|
+
b.height ? attr("height", b.height) : "",
|
|
120
|
+
b.layout ? attr("layout", b.layout) : "",
|
|
118
121
|
b.variants?.length
|
|
119
|
-
?
|
|
122
|
+
? attr("variants", b.variants.map((v) => (v.label === v.id ? v.id : `${v.id}:${v.label}`)).join(","))
|
|
120
123
|
: "",
|
|
121
|
-
b.viewport ?
|
|
122
|
-
b.entry ?
|
|
124
|
+
b.viewport ? attr("viewport", b.viewport) : "",
|
|
125
|
+
b.entry ? attr("entry", b.entry) : "",
|
|
126
|
+
b.status ? attr("status", b.status) : "",
|
|
127
|
+
b.bleed === undefined ? "" : attr("bleed", b.bleed),
|
|
128
|
+
b.className ? attr("className", b.className) : "",
|
|
129
|
+
b.surface ? attr("surface", b.surface) : "",
|
|
130
|
+
b.variantsWidth ? attr("variantsWidth", b.variantsWidth) : "",
|
|
123
131
|
].join("")
|
|
124
132
|
return `{% demo${attrs} %}${serializeDemoFiles(b)}`
|
|
125
133
|
}
|
|
@@ -158,7 +166,8 @@ function serializeBlock(b: Block): string {
|
|
|
158
166
|
}
|
|
159
167
|
// Escape pipes inside cell text so they don't break the GFM row.
|
|
160
168
|
const cell = (c: Inline[]) => serializeInline(c).replace(/\|/g, "\\|")
|
|
161
|
-
|
|
169
|
+
// An empty cell is written `| |`, as authors do.
|
|
170
|
+
const row = (cells: Inline[][]) => `|${cells.map((c) => ` ${cell(c)} `.replace(/^ $/, " ")).join("|")}|`
|
|
162
171
|
const sep = `| ${b.header.map(() => "---").join(" | ")} |`
|
|
163
172
|
return [row(b.header), sep, ...b.rows.map(row)].join("\n")
|
|
164
173
|
}
|
|
@@ -167,18 +176,18 @@ function serializeBlock(b: Block): string {
|
|
|
167
176
|
return `$$\n${b.formula}\n$$`
|
|
168
177
|
|
|
169
178
|
case "updates":
|
|
170
|
-
return `{% updates${b.format ?
|
|
179
|
+
return `{% updates${b.format ? attr("format", b.format) : ""} %}\n${b.updates
|
|
171
180
|
.map(
|
|
172
181
|
(u) =>
|
|
173
|
-
`{% update
|
|
182
|
+
`{% update${attr("date", u.date)} %}\n${serializeBlocks(u.children)}\n{% endupdate %}`
|
|
174
183
|
)
|
|
175
184
|
.join("\n\n")}\n{% endupdates %}`
|
|
176
185
|
|
|
177
186
|
case "openapi-operation": {
|
|
178
187
|
const attrs = [
|
|
179
|
-
b.spec ?
|
|
180
|
-
b.path ?
|
|
181
|
-
b.method ?
|
|
188
|
+
b.spec ? attr("spec", b.spec) : "",
|
|
189
|
+
b.path ? attr("path", b.path) : "",
|
|
190
|
+
b.method ? attr("method", b.method) : "",
|
|
182
191
|
].join("")
|
|
183
192
|
const inner = b.specUrl ? `\n[${b.label || b.spec || "OpenAPI"}](${b.specUrl})` : ""
|
|
184
193
|
return `{% openapi-operation${attrs} %}${inner}\n{% endopenapi-operation %}`
|
|
@@ -225,7 +234,7 @@ export function fenceFor(content: string): string {
|
|
|
225
234
|
/** One inline demo file as a titled fence. */
|
|
226
235
|
export function serializeDemoFile(file: DemoInlineFile): string {
|
|
227
236
|
const fence = fenceFor(file.content)
|
|
228
|
-
return `${fence}${file.language ?? ""}
|
|
237
|
+
return `${fence}${file.language ?? ""}${attr("title", file.path)}\n${file.content}\n${fence}`
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
/** The body of a block-form `{% demo %}`: files as titled fences, then `{% enddemo %}`. Empty without files. */
|
package/src/index.ts
CHANGED
|
@@ -99,7 +99,9 @@ export type {
|
|
|
99
99
|
export { parseDemoVariants, parseMarkdown, parseBlocks, trimPartialInlineToken } from "./gitbook/parse"
|
|
100
100
|
export { fenceFor, serializeBlocks, serializeDemoFile, serializeMarkdown } from "./gitbook/serialize"
|
|
101
101
|
export {
|
|
102
|
+
defaultInlineDelims,
|
|
102
103
|
footnoteDefinitions,
|
|
104
|
+
inlineDelims,
|
|
103
105
|
parseInline,
|
|
104
106
|
plainText,
|
|
105
107
|
refDefinitions,
|
package/src/styles.css
CHANGED
|
@@ -1530,9 +1530,13 @@
|
|
|
1530
1530
|
margin-bottom: 12px;
|
|
1531
1531
|
}
|
|
1532
1532
|
|
|
1533
|
-
:
|
|
1533
|
+
/* The heading sits centred on the switch's row: no block margin, padding or rule of its own
|
|
1534
|
+
(page heading rules — docstream's and a host's `h2 { padding-top }` — would push it off
|
|
1535
|
+
centre; 0,4,0 outranks them). */
|
|
1536
|
+
:is([data-docstream], .docs-article) .docs-tabs-section > .docs-tabs-section-head > .docs-tabs-section-title {
|
|
1537
|
+
align-self: center;
|
|
1534
1538
|
margin: 0;
|
|
1535
|
-
padding-
|
|
1539
|
+
padding-block: 0;
|
|
1536
1540
|
border-bottom: 0;
|
|
1537
1541
|
scroll-margin-top: 80px;
|
|
1538
1542
|
}
|
|
@@ -1543,15 +1547,18 @@
|
|
|
1543
1547
|
gap: 10px;
|
|
1544
1548
|
}
|
|
1545
1549
|
|
|
1546
|
-
.
|
|
1550
|
+
/* One 10px rhythm inside a section. These must outrank the generic block spacing
|
|
1551
|
+
(`:is([data-docstream], .docs-article) [data-docstream-blocks] > :is(.docs-code, …)`,
|
|
1552
|
+
headings and demos, specificity 0,3,0), hence the extra `.docs-tabs-section >` (0,4,0). */
|
|
1553
|
+
:is([data-docstream], .docs-article) .docs-tabs-section > .docs-tabs-section-body > [data-docstream-blocks] > * {
|
|
1547
1554
|
margin-block: 0 10px;
|
|
1548
1555
|
}
|
|
1549
1556
|
|
|
1550
|
-
.docs-tabs-section-body > [data-docstream-blocks] > :last-child {
|
|
1557
|
+
:is([data-docstream], .docs-article) .docs-tabs-section > .docs-tabs-section-body > [data-docstream-blocks] > :last-child {
|
|
1551
1558
|
margin-bottom: 0;
|
|
1552
1559
|
}
|
|
1553
1560
|
|
|
1554
|
-
.docs-tabs-section-body > [data-docstream-blocks] > p {
|
|
1561
|
+
:is([data-docstream], .docs-article) .docs-tabs-section > .docs-tabs-section-body > [data-docstream-blocks] > p {
|
|
1555
1562
|
color: var(--gb-muted-foreground);
|
|
1556
1563
|
font-size: 0.93em;
|
|
1557
1564
|
}
|
|
@@ -1945,35 +1952,36 @@
|
|
|
1945
1952
|
opacity: 1;
|
|
1946
1953
|
}
|
|
1947
1954
|
|
|
1948
|
-
/*
|
|
1949
|
-
|
|
1955
|
+
/* The canvas is block flow, so a block-level demo root lays out exactly as it would on a
|
|
1956
|
+
page: it fills the width, and `max-width: 480px; margin: 0 auto` centres it at 480px (a
|
|
1957
|
+
grid or flex canvas would shrink it to its content). Intrinsically sized roots — inline /
|
|
1958
|
+
inline-block / inline-flex elements such as a lone button, badge or image — sit on the
|
|
1959
|
+
canvas's centred line box. Content is centred vertically (`align-content` on a block
|
|
1960
|
+
container). The canvas's `text-align: center` stops at its children: the reset below has
|
|
1961
|
+
zero specificity, so any rule of the demo's own wins. */
|
|
1950
1962
|
.docs-demo-canvas {
|
|
1951
|
-
display:
|
|
1963
|
+
display: block;
|
|
1952
1964
|
align-content: center;
|
|
1953
|
-
justify-items: stretch;
|
|
1954
1965
|
box-sizing: border-box;
|
|
1955
1966
|
min-height: 100%;
|
|
1956
1967
|
padding: 28px 24px;
|
|
1957
1968
|
color: var(--foreground, var(--gb-panel-foreground));
|
|
1969
|
+
text-align: center;
|
|
1958
1970
|
}
|
|
1959
1971
|
|
|
1960
|
-
.docs-demo-canvas > * {
|
|
1961
|
-
min-width: 0;
|
|
1972
|
+
:where(.docs-demo-canvas) > * {
|
|
1962
1973
|
max-width: 100%;
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
.docs-demo-canvas > :where(button, a, img, svg, video, canvas, input, select, textarea, label, span, code) {
|
|
1966
|
-
justify-self: center;
|
|
1974
|
+
text-align: start;
|
|
1967
1975
|
}
|
|
1968
1976
|
|
|
1969
1977
|
.docs-demo-canvas-bleed {
|
|
1970
|
-
|
|
1971
|
-
place-items: normal;
|
|
1978
|
+
align-content: normal;
|
|
1972
1979
|
height: 100%;
|
|
1973
1980
|
padding: 0;
|
|
1981
|
+
text-align: start;
|
|
1974
1982
|
}
|
|
1975
1983
|
|
|
1976
|
-
.docs-demo-canvas-bleed > * {
|
|
1984
|
+
:where(.docs-demo-canvas-bleed) > * {
|
|
1977
1985
|
max-width: none;
|
|
1978
1986
|
}
|
|
1979
1987
|
|
|
@@ -2241,9 +2249,11 @@
|
|
|
2241
2249
|
background: var(--gb-hover);
|
|
2242
2250
|
}
|
|
2243
2251
|
|
|
2252
|
+
/* Sizes to its content; a `height` in meta or on the tag sets a minimum (inline style), and
|
|
2253
|
+
hosts may set a floor for every single-file preview with --docs-demo-preview-min-height. */
|
|
2244
2254
|
.docs-demo-single-preview {
|
|
2245
2255
|
display: grid;
|
|
2246
|
-
min-height:
|
|
2256
|
+
min-height: var(--docs-demo-preview-min-height, 0px);
|
|
2247
2257
|
background: var(--background, var(--gb-panel));
|
|
2248
2258
|
}
|
|
2249
2259
|
|