@honeydeck/honeydeck 0.1.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.
Files changed (144) hide show
  1. package/AGENTS.md +25 -0
  2. package/DEVELOPMENT.md +522 -0
  3. package/LICENSE +21 -0
  4. package/Readme.md +49 -0
  5. package/SPEC.md +88 -0
  6. package/docs/components.md +63 -0
  7. package/docs/configuration.md +91 -0
  8. package/docs/getting-started.md +116 -0
  9. package/docs/kit-authoring.md +207 -0
  10. package/docs/kits.md +387 -0
  11. package/docs/local-development.md +95 -0
  12. package/docs/mermaid.md +198 -0
  13. package/docs/mobile.md +108 -0
  14. package/docs/navigation.md +93 -0
  15. package/docs/next-steps.md +377 -0
  16. package/docs/pdf-export.md +91 -0
  17. package/docs/presenter-mode.md +104 -0
  18. package/docs/slides.md +130 -0
  19. package/docs/slidev-migration.md +42 -0
  20. package/docs/steps-and-reveals.md +171 -0
  21. package/package.json +134 -0
  22. package/skills/SPEC.md +21 -0
  23. package/skills/honeydeck/SKILL.md +65 -0
  24. package/skills/presentation-writing/SKILL.md +75 -0
  25. package/skills/slidev-migration/SKILL.md +153 -0
  26. package/src/SPEC.md +89 -0
  27. package/src/assets.d.ts +30 -0
  28. package/src/cli/SPEC.md +230 -0
  29. package/src/cli/args.ts +3 -0
  30. package/src/cli/banner.ts +9 -0
  31. package/src/cli/bin.js +5 -0
  32. package/src/cli/build.ts +229 -0
  33. package/src/cli/deck-path.ts +32 -0
  34. package/src/cli/dev.ts +263 -0
  35. package/src/cli/index.ts +126 -0
  36. package/src/cli/init.ts +369 -0
  37. package/src/cli/pdf.ts +923 -0
  38. package/src/cli/skill.ts +75 -0
  39. package/src/cli/templates/SPEC.md +70 -0
  40. package/src/cli/templates/deck-mdx.ts +15 -0
  41. package/src/cli/templates/package-json.ts +36 -0
  42. package/src/cli/templates/sparkle-button.ts +15 -0
  43. package/src/cli/templates/starter/components/SparkleButton.tsx +84 -0
  44. package/src/cli/templates/starter/deck.mdx +153 -0
  45. package/src/cli/templates/starter/styles.css +14 -0
  46. package/src/cli/templates/styles-css.ts +14 -0
  47. package/src/defaults.ts +1 -0
  48. package/src/layouts/ColorModeImage.tsx +55 -0
  49. package/src/layouts/SPEC.md +393 -0
  50. package/src/layouts/SlideFrame.tsx +48 -0
  51. package/src/layouts/bee/Blank.tsx +12 -0
  52. package/src/layouts/bee/Cover.tsx +70 -0
  53. package/src/layouts/bee/Default.tsx +42 -0
  54. package/src/layouts/bee/Image/Image.tsx +151 -0
  55. package/src/layouts/bee/Image/placeholder-dark.webp +0 -0
  56. package/src/layouts/bee/Image/placeholder-vertical-dark.webp +0 -0
  57. package/src/layouts/bee/Image/placeholder-vertical.webp +0 -0
  58. package/src/layouts/bee/Image/placeholder.webp +0 -0
  59. package/src/layouts/bee/ImageLeft.tsx +27 -0
  60. package/src/layouts/bee/ImageRight.tsx +27 -0
  61. package/src/layouts/bee/ImageSide.tsx +107 -0
  62. package/src/layouts/bee/Section.tsx +40 -0
  63. package/src/layouts/bee/TwoCol.tsx +108 -0
  64. package/src/layouts/bee/index.ts +40 -0
  65. package/src/layouts/clean/Blank.tsx +12 -0
  66. package/src/layouts/clean/Cover.tsx +58 -0
  67. package/src/layouts/clean/Default.tsx +33 -0
  68. package/src/layouts/clean/Image/Image.tsx +103 -0
  69. package/src/layouts/clean/ImageLeft.tsx +27 -0
  70. package/src/layouts/clean/ImageRight.tsx +27 -0
  71. package/src/layouts/clean/ImageSide.tsx +113 -0
  72. package/src/layouts/clean/Section.tsx +35 -0
  73. package/src/layouts/clean/TwoCol.tsx +63 -0
  74. package/src/layouts/clean/index.ts +40 -0
  75. package/src/layouts/index.ts +60 -0
  76. package/src/layouts/placeholders.ts +9 -0
  77. package/src/layouts/utils.ts +13 -0
  78. package/src/remark/SPEC.md +49 -0
  79. package/src/remark/h1-extract.ts +124 -0
  80. package/src/remark/index.ts +4 -0
  81. package/src/remark/shiki-code-blocks.ts +325 -0
  82. package/src/remark/step-numbering.ts +412 -0
  83. package/src/runtime/Deck.tsx +533 -0
  84. package/src/runtime/SPEC.md +256 -0
  85. package/src/runtime/SlideCanvas.tsx +95 -0
  86. package/src/runtime/TimelineContext.tsx +122 -0
  87. package/src/runtime/app-shell/index.html +31 -0
  88. package/src/runtime/app-shell/main.tsx +42 -0
  89. package/src/runtime/aspectRatio.ts +34 -0
  90. package/src/runtime/colorMode.ts +23 -0
  91. package/src/runtime/components/BrowserFrame.tsx +233 -0
  92. package/src/runtime/components/Button.tsx +57 -0
  93. package/src/runtime/components/CodeBlock.tsx +210 -0
  94. package/src/runtime/components/ColorModeCycleButton.tsx +59 -0
  95. package/src/runtime/components/ErrorBoundary.tsx +125 -0
  96. package/src/runtime/components/Keyboard.tsx +87 -0
  97. package/src/runtime/components/ListStyle.tsx +203 -0
  98. package/src/runtime/components/NavBar.tsx +223 -0
  99. package/src/runtime/components/NavBarButton.tsx +47 -0
  100. package/src/runtime/components/NavBarDivider.tsx +3 -0
  101. package/src/runtime/components/Notes.tsx +171 -0
  102. package/src/runtime/components/Reveal.tsx +82 -0
  103. package/src/runtime/components/RevealGroup.tsx +193 -0
  104. package/src/runtime/components/SPEC.md +263 -0
  105. package/src/runtime/components/SlideNumberBadge.tsx +11 -0
  106. package/src/runtime/components/TimelineSteps.tsx +115 -0
  107. package/src/runtime/components/index.ts +55 -0
  108. package/src/runtime/index.ts +42 -0
  109. package/src/runtime/inputOwnership.ts +68 -0
  110. package/src/runtime/keyboardTarget.ts +7 -0
  111. package/src/runtime/lastSlideRoute.ts +56 -0
  112. package/src/runtime/navigation.ts +211 -0
  113. package/src/runtime/router.ts +157 -0
  114. package/src/runtime/slideData.ts +137 -0
  115. package/src/runtime/sync.ts +267 -0
  116. package/src/runtime/types.ts +182 -0
  117. package/src/runtime/useKeyboardNav.ts +138 -0
  118. package/src/runtime/useSwipeNav.ts +257 -0
  119. package/src/runtime/views/DocsView.tsx +74 -0
  120. package/src/runtime/views/OverviewView.tsx +386 -0
  121. package/src/runtime/views/PresenterNotesPanel.tsx +76 -0
  122. package/src/runtime/views/PresenterView.tsx +340 -0
  123. package/src/runtime/views/SPEC.md +152 -0
  124. package/src/runtime/views/docs/ComponentsTab.tsx +178 -0
  125. package/src/runtime/views/docs/DocsHeader.tsx +101 -0
  126. package/src/runtime/views/docs/Intro.tsx +20 -0
  127. package/src/runtime/views/docs/LayoutsTab.tsx +324 -0
  128. package/src/runtime/views/docs/ThemeTab.tsx +110 -0
  129. package/src/runtime/views/index.ts +7 -0
  130. package/src/runtime/views/overviewGrid.ts +106 -0
  131. package/src/runtime/views/presenterPreview.ts +27 -0
  132. package/src/runtime/virtual-modules.d.ts +98 -0
  133. package/src/theme/SPEC.md +179 -0
  134. package/src/theme/base.css +623 -0
  135. package/src/theme/bee.css +35 -0
  136. package/src/theme/clean.css +38 -0
  137. package/src/vite-plugin/SPEC.md +114 -0
  138. package/src/vite-plugin/component-doc-crawler.ts +350 -0
  139. package/src/vite-plugin/deck-loader.ts +148 -0
  140. package/src/vite-plugin/index.ts +373 -0
  141. package/src/vite-plugin/layout-demo-crawler.ts +802 -0
  142. package/src/vite-plugin/splitter.ts +353 -0
  143. package/src/vite-plugin/token-manifest.ts +163 -0
  144. package/src/vite-plugin/virtual-modules.ts +587 -0
@@ -0,0 +1,412 @@
1
+ /**
2
+ * Remark plugin: assign `at` props to `<Reveal>` and `<RevealGroup>` elements
3
+ * and count total timeline steps per slide.
4
+ *
5
+ * ### What it does
6
+ * Walks the MDAST (including MDX JSX nodes) in document order and:
7
+ * 1. Assigns `at={n}` to each `<Reveal>` element (starting at 1) and
8
+ * injects `as="div"`/`as="span"` from the MDX flow/text context.
9
+ * 2. Assigns `at={n}` to each `<RevealGroup>` element using the starting
10
+ * index of the group and adds internal per-target step numbers for group
11
+ * children/list items.
12
+ * 3. Assigns `at={n}` to each `<TimelineSteps steps={n}>` element and
13
+ * advances the timeline by its literal static step count.
14
+ * 4. Counts timeline steps from code fence `|`-separated groups after the
15
+ * first baseline group (counted here so `stepCount` is already accurate).
16
+ * 5. Writes the total step count to `vfile.data.stepCount`.
17
+ *
18
+ * ### `at` prop injection
19
+ * Numeric JSX props require an ESTree expression node. We construct a minimal
20
+ * `Program` → `ExpressionStatement` → `Literal` subtree so that `@mdx-js/mdx`
21
+ * generates `at={<number>}` (not `at="<string>"`).
22
+ *
23
+ * ### Existing `at` props
24
+ * If a `<Reveal>` or `<RevealGroup>` already has an explicit `at` prop, this
25
+ * plugin leaves it untouched. Future V2 may expose `<Reveal at={2}>` as a
26
+ * public API for out-of-order reveals. `<Reveal>` still receives a compiler
27
+ * `as` prop when missing, so manually numbered inline reveals remain valid HTML.
28
+ *
29
+ * ### Recursion
30
+ * Nested step producers are flattened into the same slide-local timeline.
31
+ * Parent reveal targets consume their step first, then nested reveals, groups,
32
+ * and code walkthrough steps after the first baseline highlight consume later
33
+ * steps before the next sibling target.
34
+ */
35
+
36
+ import type { Program } from "estree";
37
+ import type { Code, Root } from "mdast";
38
+ import type {
39
+ MdxJsxAttribute,
40
+ MdxJsxAttributeValueExpression,
41
+ MdxJsxFlowElement,
42
+ MdxJsxTextElement,
43
+ } from "mdast-util-mdx-jsx";
44
+ import type { Plugin } from "unified";
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Helper: build an `at={n}` attribute node
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function makeAtAttribute(n: number): MdxJsxAttribute {
51
+ // The ESTree program for the numeric literal `n`.
52
+ const estree: Program = {
53
+ type: "Program",
54
+ sourceType: "module",
55
+ comments: [],
56
+ body: [
57
+ {
58
+ type: "ExpressionStatement",
59
+ expression: {
60
+ type: "Literal",
61
+ value: n,
62
+ raw: String(n),
63
+ },
64
+ },
65
+ ],
66
+ };
67
+
68
+ const valueExpr: MdxJsxAttributeValueExpression = {
69
+ type: "mdxJsxAttributeValueExpression",
70
+ value: String(n),
71
+ data: { estree },
72
+ };
73
+
74
+ return {
75
+ type: "mdxJsxAttribute",
76
+ name: "at",
77
+ value: valueExpr,
78
+ };
79
+ }
80
+
81
+ function makeStringAttribute(name: string, value: string): MdxJsxAttribute {
82
+ return {
83
+ type: "mdxJsxAttribute",
84
+ name,
85
+ value,
86
+ };
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Helper: check if a node is a JSX element with a given name
91
+ // ---------------------------------------------------------------------------
92
+
93
+ type MdxJsxElement = MdxJsxFlowElement | MdxJsxTextElement;
94
+
95
+ function isJsxElement(node: unknown): node is MdxJsxElement {
96
+ const n = node as { type?: string };
97
+ return n.type === "mdxJsxFlowElement" || n.type === "mdxJsxTextElement";
98
+ }
99
+
100
+ function hasAtProp(el: MdxJsxElement): boolean {
101
+ return el.attributes.some(
102
+ (a) => a.type === "mdxJsxAttribute" && a.name === "at",
103
+ );
104
+ }
105
+
106
+ function getAttribute(
107
+ el: MdxJsxElement,
108
+ name: string,
109
+ ): MdxJsxAttribute | undefined {
110
+ return el.attributes.find(
111
+ (a): a is MdxJsxAttribute =>
112
+ a.type === "mdxJsxAttribute" && a.name === name,
113
+ );
114
+ }
115
+
116
+ function injectRevealWrapperElement(el: MdxJsxElement): void {
117
+ if (getAttribute(el, "as")) return;
118
+
119
+ el.attributes.push(
120
+ makeStringAttribute("as", el.type === "mdxJsxTextElement" ? "span" : "div"),
121
+ );
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Helper: count reveal steps produced by a RevealGroup child
126
+ // ---------------------------------------------------------------------------
127
+
128
+ function countListItems(node: unknown): number | null {
129
+ const n = node as { type?: string; children?: unknown[] };
130
+
131
+ if (n.type === "list") {
132
+ return (n.children ?? []).filter((child) => {
133
+ const c = child as { type?: string };
134
+ return c.type === "listItem";
135
+ }).length;
136
+ }
137
+
138
+ if (isJsxElement(node) && (node.name === "ul" || node.name === "ol")) {
139
+ return node.children.filter((child) => {
140
+ const c = child as { type?: string; value?: string };
141
+ if (c.type === "text") {
142
+ return (c.value ?? "").trim().length > 0;
143
+ }
144
+ return true;
145
+ }).length;
146
+ }
147
+
148
+ return null;
149
+ }
150
+
151
+ function getMeaningfulChildren(children: unknown[]): unknown[] {
152
+ return children.filter((child) => {
153
+ const c = child as { type?: string; value?: string };
154
+ if (c.type === "text") {
155
+ return (c.value ?? "").trim().length > 0;
156
+ }
157
+ return true;
158
+ });
159
+ }
160
+
161
+ function getRevealGroupTargets(el: MdxJsxElement): unknown[] {
162
+ const targets: unknown[] = [];
163
+
164
+ for (const child of getMeaningfulChildren(el.children)) {
165
+ const listItemCount = countListItems(child);
166
+ const c = child as { type?: string; children?: unknown[] };
167
+
168
+ if (listItemCount !== null && Array.isArray(c.children)) {
169
+ if (c.type === "list") {
170
+ targets.push(
171
+ ...c.children.filter((item) => {
172
+ const i = item as { type?: string };
173
+ return i.type === "listItem";
174
+ }),
175
+ );
176
+ } else {
177
+ targets.push(...getMeaningfulChildren(c.children));
178
+ }
179
+ continue;
180
+ }
181
+
182
+ targets.push(child);
183
+ }
184
+
185
+ return targets;
186
+ }
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Helper: count groups/steps encoded in a code fence meta string
190
+ //
191
+ // Pattern: `{2|4-5|all}` → 3 groups, 2 timeline steps.
192
+ // The first group is the baseline active highlight. Each later group consumes
193
+ // one timeline step.
194
+ // ---------------------------------------------------------------------------
195
+
196
+ function countCodeFenceGroups(meta: string | null | undefined): number {
197
+ if (!meta) return 0;
198
+ const match = meta.match(/\{([^}]+)\}/);
199
+ if (!match?.[1]) return 0;
200
+ return match[1].split("|").filter(Boolean).length;
201
+ }
202
+
203
+ function countCodeFenceSteps(meta: string | null | undefined): number {
204
+ return Math.max(0, countCodeFenceGroups(meta) - 1);
205
+ }
206
+
207
+ function expressionValue(attr: MdxJsxAttribute): unknown {
208
+ const value = attr.value;
209
+ if (
210
+ !value ||
211
+ typeof value === "string" ||
212
+ value.type !== "mdxJsxAttributeValueExpression"
213
+ ) {
214
+ return undefined;
215
+ }
216
+
217
+ const body = value.data?.estree?.body;
218
+ const statement = body?.[0];
219
+ if (statement?.type !== "ExpressionStatement") return undefined;
220
+
221
+ const expression = statement.expression;
222
+ if (expression.type !== "Literal") return undefined;
223
+ return expression.value;
224
+ }
225
+
226
+ function parsePositiveIntegerLiteral(attr: MdxJsxAttribute): number | null {
227
+ if (typeof attr.value === "string") {
228
+ if (!/^[1-9]\d*$/.test(attr.value)) return null;
229
+ return Number(attr.value);
230
+ }
231
+
232
+ const value = expressionValue(attr);
233
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
234
+ return value;
235
+ }
236
+
237
+ if (typeof value === "string" && /^[1-9]\d*$/.test(value)) {
238
+ return Number(value);
239
+ }
240
+
241
+ return null;
242
+ }
243
+
244
+ function readTimelineSteps(el: MdxJsxElement): number {
245
+ const attr = getAttribute(el, "steps");
246
+ if (!attr || attr.value === null) {
247
+ throw new Error(
248
+ "Honeydeck <TimelineSteps> requires a literal positive integer `steps` prop.",
249
+ );
250
+ }
251
+
252
+ const steps = parsePositiveIntegerLiteral(attr);
253
+ if (steps === null) {
254
+ throw new Error(
255
+ "Honeydeck <TimelineSteps> `steps` must be a literal positive integer, for example steps={3}. Dynamic expressions are not supported because timeline steps are counted at build time.",
256
+ );
257
+ }
258
+
259
+ return steps;
260
+ }
261
+
262
+ function findNestedStepProducer(node: unknown): string | null {
263
+ const n = node as { type?: string; children?: unknown[] };
264
+
265
+ if (n.type === "code") {
266
+ const codeNode = node as Code;
267
+ return countCodeFenceSteps(codeNode.meta) > 0 ? "stepped code fence" : null;
268
+ }
269
+
270
+ if (isJsxElement(node)) {
271
+ if (
272
+ node.name === "Reveal" ||
273
+ node.name === "RevealGroup" ||
274
+ node.name === "TimelineSteps"
275
+ ) {
276
+ return `<${node.name}>`;
277
+ }
278
+ }
279
+
280
+ if (!Array.isArray(n.children)) return null;
281
+
282
+ for (const child of n.children) {
283
+ const producer = findNestedStepProducer(child);
284
+ if (producer) return producer;
285
+ }
286
+
287
+ return null;
288
+ }
289
+
290
+ // ---------------------------------------------------------------------------
291
+ // Plugin
292
+ // ---------------------------------------------------------------------------
293
+
294
+ /**
295
+ * Remark plugin that numbers `<Reveal>` and `<RevealGroup>` elements with
296
+ * sequential `at` props and stores the total step count in `vfile.data`.
297
+ *
298
+ * Usage (in Vite plugin config):
299
+ * ```ts
300
+ * mdx({ remarkPlugins: [remarkFrontmatter, remarkStepNumbering] })
301
+ * ```
302
+ */
303
+ export const remarkStepNumbering: Plugin<[], Root> = () => (tree, vfile) => {
304
+ let counter = 1; // next `at` value to assign; 1-based
305
+
306
+ function visitChildren(node: unknown): void {
307
+ const n = node as { children?: unknown[] };
308
+ if (!Array.isArray(n.children)) return;
309
+
310
+ for (const child of n.children) {
311
+ visitNode(child);
312
+ }
313
+ }
314
+
315
+ function visitNode(node: unknown): void {
316
+ const n = node as { type?: string };
317
+
318
+ // ── Code blocks with step-through meta ─────────────────────────────
319
+ if (n.type === "code") {
320
+ const codeNode = node as Code;
321
+ const groupCount = countCodeFenceGroups(codeNode.meta);
322
+ const steps = Math.max(0, groupCount - 1);
323
+ if (groupCount > 0) {
324
+ // Annotate the code node with the first timeline step that advances
325
+ // beyond the baseline group so that remarkShikiCodeBlocks (which runs
326
+ // after us) can embed it in the <HoneydeckCodeBlock startAt={N}> prop
327
+ // without needing its own counter.
328
+ if (!(codeNode as unknown as Record<string, unknown>).data) {
329
+ (codeNode as unknown as Record<string, unknown>).data = {};
330
+ }
331
+ (
332
+ (codeNode as unknown as Record<string, unknown>).data as Record<
333
+ string,
334
+ unknown
335
+ >
336
+ ).honeydeckStartAt = counter;
337
+ }
338
+ counter += steps;
339
+ return;
340
+ }
341
+
342
+ // ── MDX JSX elements ─────────────────────────────────────────────────
343
+ if (isJsxElement(node)) {
344
+ const el = node as unknown as MdxJsxElement;
345
+
346
+ if (el.name === "TimelineSteps") {
347
+ const steps = readTimelineSteps(el);
348
+ const nestedProducer = findNestedStepProducer({
349
+ type: "honeydeckTimelineStepsChildren",
350
+ children: el.children,
351
+ });
352
+
353
+ if (nestedProducer) {
354
+ throw new Error(
355
+ `Honeydeck <TimelineSteps> cannot contain nested timeline producers (${nestedProducer}). Register custom component steps with the outer <TimelineSteps> and use useTimelineSteps() inside the custom component instead.`,
356
+ );
357
+ }
358
+
359
+ if (!hasAtProp(el)) {
360
+ el.attributes.push(makeAtAttribute(counter));
361
+ }
362
+
363
+ counter += steps;
364
+ return;
365
+ }
366
+
367
+ if (el.name === "Reveal") {
368
+ injectRevealWrapperElement(el);
369
+
370
+ if (!hasAtProp(el)) {
371
+ el.attributes.push(makeAtAttribute(counter));
372
+ counter++;
373
+ visitChildren(el);
374
+ }
375
+ return;
376
+ }
377
+
378
+ if (el.name === "RevealGroup") {
379
+ if (hasAtProp(el)) {
380
+ return;
381
+ }
382
+
383
+ const targets = getRevealGroupTargets(el);
384
+ const targetSteps: number[] = [];
385
+
386
+ el.attributes.push(makeAtAttribute(counter));
387
+ if (targets.length === 0) {
388
+ counter++;
389
+ return;
390
+ }
391
+
392
+ for (const target of targets) {
393
+ targetSteps.push(counter);
394
+ counter++;
395
+ visitNode(target);
396
+ }
397
+
398
+ el.attributes.push(
399
+ makeStringAttribute("targetStepsJson", JSON.stringify(targetSteps)),
400
+ );
401
+ return;
402
+ }
403
+ }
404
+
405
+ visitChildren(node);
406
+ }
407
+
408
+ visitChildren(tree);
409
+
410
+ // Store total step count on the vfile for the virtual modules plugin to read.
411
+ vfile.data.stepCount = counter - 1; // counter started at 1
412
+ };