@heyramzi/ui 2.0.1 → 2.2.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 (341) hide show
  1. package/package.json +48 -24
  2. package/scripts/sync-shadcn.mjs +3 -4
  3. package/src/agents/agent-avatar.svelte +604 -575
  4. package/src/agents/agent-cast.svelte +179 -183
  5. package/src/agents/demo.svelte +125 -113
  6. package/src/agents/helpers.ts +23 -17
  7. package/src/agents/index.ts +4 -4
  8. package/src/agents/page-state.ts +6 -6
  9. package/src/agents/types.ts +6 -6
  10. package/src/ai/ai-button/AiButton.svelte +4 -12
  11. package/src/ai/generation-progress/GenerationProgress.svelte +10 -3
  12. package/src/ai/prompt-input/PromptInputTextarea.svelte +1 -2
  13. package/src/ai/response/Response.css +2 -3
  14. package/src/animations/avatar-group-hover.css +2 -16
  15. package/src/animations/card-tilt.css +3 -6
  16. package/src/animations/digit-pop-in.css +2 -15
  17. package/src/animations/dropdown.css +4 -17
  18. package/src/animations/input-clear-dissolve.css +5 -18
  19. package/src/animations/input-shake.css +9 -41
  20. package/src/animations/learn-more-hover.css +17 -9
  21. package/src/animations/like-button.css +2 -4
  22. package/src/animations/page-side-by-side.css +2 -10
  23. package/src/animations/plus-menu-morph.css +2 -5
  24. package/src/animations/shimmer-text.css +5 -18
  25. package/src/animations/spinning-counter.css +2 -6
  26. package/src/animations/success-check.css +2 -15
  27. package/src/animations/thinking-states.css +2 -7
  28. package/src/brand/brand.test.ts +21 -5
  29. package/src/brand/index.ts +4 -10
  30. package/src/canvas/README.md +9 -12
  31. package/src/canvas/card/card-frame.svelte +43 -39
  32. package/src/canvas/card/resize-handles.svelte +89 -80
  33. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  34. package/src/canvas/chrome/lock-badge.svelte +8 -8
  35. package/src/canvas/chrome/side-handles.svelte +24 -19
  36. package/src/canvas/chrome/size-chip.svelte +30 -30
  37. package/src/canvas/chrome/snap-guides.svelte +20 -20
  38. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  39. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  40. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  41. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  42. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  43. package/src/canvas/edges/bend-handle.svelte +46 -46
  44. package/src/canvas/edges/edge-gradient.svelte +10 -10
  45. package/src/canvas/edges/gradient-edge.svelte +45 -29
  46. package/src/canvas/editor/lock.test.ts +6 -1
  47. package/src/canvas/editor/style.ts +7 -1
  48. package/src/canvas/headless/svg-figure.test.ts +5 -1
  49. package/src/canvas/lod/lod-switch.svelte +111 -106
  50. package/src/canvas/pen/PenField.svelte +87 -81
  51. package/src/canvas/pen/PenOverlay.svelte +127 -127
  52. package/src/canvas/pen/ShapeNode.svelte +31 -32
  53. package/src/canvas/pen/TextNode.svelte +54 -53
  54. package/src/canvas/pen/host-field.test.svelte +7 -7
  55. package/src/canvas/pen/shapes.ts +7 -1
  56. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  57. package/src/canvas/screen/PartView.svelte +138 -72
  58. package/src/canvas/screen/ScreenBody.svelte +81 -60
  59. package/src/canvas/screen/ScreenCard.svelte +20 -20
  60. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  61. package/src/canvas/ui/FrameLayer.svelte +59 -59
  62. package/src/canvas/ui/PenLayer.svelte +39 -30
  63. package/src/canvas/ui/StylePanel.svelte +119 -102
  64. package/src/canvas/ui/canvas-grid.svelte +137 -129
  65. package/src/charts/animated-value.svelte +1 -1
  66. package/src/charts/area-chart.css +2 -5
  67. package/src/charts/area-chart.svelte +1 -1
  68. package/src/charts/bar-chart.css +2 -3
  69. package/src/charts/bar-chart.svelte +1 -1
  70. package/src/charts/charts.css +5 -17
  71. package/src/charts/crosshair.svelte +1 -1
  72. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  73. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  74. package/src/charts/dashboard/data-panel.svelte +50 -50
  75. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  76. package/src/charts/dashboard/data-table.svelte +138 -138
  77. package/src/charts/dashboard/panel-grid.svelte +30 -30
  78. package/src/charts/dashboard/period-filter.svelte +25 -25
  79. package/src/charts/dashboard/rating-chip.svelte +19 -19
  80. package/src/charts/dashboard/stat-band.svelte +32 -32
  81. package/src/charts/dashboard/stat-tile.svelte +106 -99
  82. package/src/charts/donut-chart.svelte +1 -1
  83. package/src/charts/edge-fade.svelte +1 -1
  84. package/src/charts/figure.svelte +1 -1
  85. package/src/charts/gauge-chart.css +4 -7
  86. package/src/charts/gauge-chart.svelte +1 -1
  87. package/src/charts/hover-focus.svelte +1 -1
  88. package/src/charts/legend.css +1 -3
  89. package/src/charts/legend.svelte +1 -1
  90. package/src/charts/line-chart.svelte +1 -1
  91. package/src/charts/number-chart.css +6 -9
  92. package/src/charts/number-chart.svelte +1 -1
  93. package/src/charts/reference-line.svelte +19 -19
  94. package/src/charts/reveal.svelte +1 -1
  95. package/src/charts/ring-chart.css +2 -4
  96. package/src/charts/ring-chart.svelte +1 -1
  97. package/src/charts/sparkline.svelte +5 -2
  98. package/src/charts/stat-card.svelte +1 -1
  99. package/src/charts/time-series-chart.css +2 -5
  100. package/src/charts/trend-pill.css +2 -4
  101. package/src/charts/trend-pill.svelte +1 -1
  102. package/src/components/copy/copy-button.svelte +93 -98
  103. package/src/components/copy/copy-button.test.ts +10 -2
  104. package/src/components/data-table/infinite-scroll.svelte +42 -42
  105. package/src/components/data-table/pagination.svelte +34 -22
  106. package/src/components/data-table/row-arrow.svelte +19 -16
  107. package/src/components/data-table/row-context-menu.svelte +52 -52
  108. package/src/components/data-table/sortable-head.svelte +25 -25
  109. package/src/components/data-table/table-skeleton.svelte +41 -36
  110. package/src/components/docs/doc-page.svelte +18 -9
  111. package/src/components/docs/on-this-page.svelte +16 -3
  112. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  113. package/src/components/field-row/field-row.svelte +10 -8
  114. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  115. package/src/components/icon/icon.svelte +14 -14
  116. package/src/components/language-flag/language-flag.svelte +3 -1
  117. package/src/components/list-page/detail-empty.svelte +34 -34
  118. package/src/components/list-page/detail-header.svelte +55 -47
  119. package/src/components/list-page/detail-panel.svelte +51 -59
  120. package/src/components/list-page/detail-row.svelte +19 -19
  121. package/src/components/list-page/detail-stack.svelte +21 -21
  122. package/src/components/list-page/empty-state.svelte +30 -30
  123. package/src/components/list-page/filter-item.svelte +20 -20
  124. package/src/components/list-page/filter-popover.svelte +60 -55
  125. package/src/components/list-page/grouped-list.svelte +4 -2
  126. package/src/components/list-page/index.ts +8 -4
  127. package/src/components/list-page/list-page-shell.svelte +2 -5
  128. package/src/components/list-page/master-detail.svelte +87 -88
  129. package/src/components/list-page/notice-bar.svelte +38 -38
  130. package/src/components/list-page/prev-next.svelte +63 -63
  131. package/src/components/list-page/rail-body.svelte +18 -18
  132. package/src/components/list-page/rail-empty.svelte +23 -30
  133. package/src/components/list-page/rail-head.svelte +24 -24
  134. package/src/components/list-page/rail-row.svelte +77 -73
  135. package/src/components/list-page/section-host.ts +2 -3
  136. package/src/components/locale-switch/locale-switch.svelte +41 -41
  137. package/src/components/mark/mark.svelte +34 -38
  138. package/src/components/open-in/index.ts +1 -5
  139. package/src/components/open-in/open-in.svelte +40 -40
  140. package/src/components/picker/mark-picker.svelte +49 -49
  141. package/src/components/picker/picker.svelte +5 -3
  142. package/src/components/section-header/index.ts +1 -0
  143. package/src/components/section-header/section-header.svelte +1 -1
  144. package/src/components/section-header/section-header.test.ts +7 -1
  145. package/src/components/section-header/section-label.svelte +39 -0
  146. package/src/components/segmented/animated-tabs.svelte +31 -2
  147. package/src/components/segmented/collapse.test.ts +2 -1
  148. package/src/components/segmented/index.ts +2 -0
  149. package/src/components/segmented/segment.svelte +52 -49
  150. package/src/components/segmented/segmented-filter.svelte +79 -80
  151. package/src/components/segmented/segmented-nav.svelte +34 -40
  152. package/src/components/segmented/segmented-overflow.svelte +53 -50
  153. package/src/components/segmented/segmented-track.svelte +62 -46
  154. package/src/components/segmented/segmented.ts +2 -3
  155. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  156. package/src/components/svg/svg.svelte +4 -4
  157. package/src/components/table-view/table-view.svelte +17 -2
  158. package/src/components/table-view/table-view.test.ts +7 -1
  159. package/src/components/table-view/types.ts +11 -11
  160. package/src/components/tags-input/index.ts +13 -0
  161. package/src/components/tags-input/tags-input.svelte +305 -0
  162. package/src/components/tags-input/tags.test.ts +64 -0
  163. package/src/components/tags-input/tags.ts +142 -0
  164. package/src/components/theme/index.ts +2 -4
  165. package/src/components/theme/theme-provider.svelte +1 -3
  166. package/src/components/toolbar/index.ts +2 -0
  167. package/src/components/{list-page → toolbar}/toolbar-search.svelte +25 -25
  168. package/src/components/toolbar/toolbar.svelte +13 -9
  169. package/src/components/toolbar/toolbar.test.ts +10 -1
  170. package/src/design/index.css +21 -78
  171. package/src/design/polish.css +26 -35
  172. package/src/editor/editor-toolbar.svelte +104 -99
  173. package/src/editor/editor.svelte +296 -266
  174. package/src/editor/image-toolbar.svelte +20 -22
  175. package/src/editor/index.ts +1 -4
  176. package/src/editor/inline-editor.svelte +280 -238
  177. package/src/editor/markdown.ts +15 -15
  178. package/src/editor/renderer.svelte +71 -71
  179. package/src/editor/slash-menu.svelte +24 -27
  180. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  181. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  182. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  183. package/src/effects/magic-card/MagicCard.css +14 -7
  184. package/src/effects/marquee/Marquee.svelte +3 -5
  185. package/src/effects/reveal/observe.ts +0 -3
  186. package/src/effects/spotlight/Spotlight.svelte +1 -1
  187. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  188. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  189. package/src/gallery.test.ts +14 -4
  190. package/src/inbox/README.md +27 -0
  191. package/src/inbox/inbox.test.ts +214 -0
  192. package/src/inbox/index.ts +31 -0
  193. package/src/inbox/logic.ts +169 -0
  194. package/src/inbox/thread-inbox.svelte +439 -0
  195. package/src/inbox/types.ts +161 -0
  196. package/src/lib/theme-transition.ts +2 -4
  197. package/src/lib/tone.ts +2 -4
  198. package/src/lib/utils.ts +2 -2
  199. package/src/modules.test.ts +105 -0
  200. package/src/onboarding/define-tours.test.ts +55 -40
  201. package/src/onboarding/define-tours.ts +23 -23
  202. package/src/onboarding/demo.svelte +24 -5
  203. package/src/onboarding/index.ts +1 -8
  204. package/src/onboarding/onboarding-provider.svelte +19 -7
  205. package/src/onboarding/onboarding.svelte.ts +16 -16
  206. package/src/onboarding/pick.test.ts +61 -52
  207. package/src/onboarding/pick.ts +16 -13
  208. package/src/onboarding/stores.test.ts +80 -80
  209. package/src/onboarding/stores.ts +41 -39
  210. package/src/onboarding/tour-card.svelte +30 -5
  211. package/src/onboarding/tour-runner.svelte +12 -3
  212. package/src/onboarding/types.ts +20 -12
  213. package/src/onboarding/wait.ts +22 -22
  214. package/src/panel/README.md +5 -0
  215. package/src/panel/index.ts +2 -0
  216. package/src/panel/side-panel.svelte +194 -0
  217. package/src/record/README.md +3 -0
  218. package/src/record/demo-data.ts +352 -0
  219. package/src/record/field-section.svelte +57 -0
  220. package/src/record/fields.ts +194 -0
  221. package/src/record/format.ts +94 -0
  222. package/src/{tasks/record → record}/index.ts +49 -35
  223. package/src/{tasks/record → record}/info-list.svelte +145 -114
  224. package/src/record/kinds.ts +42 -0
  225. package/src/record/logic.ts +326 -0
  226. package/src/record/next-up.svelte +81 -0
  227. package/src/{tasks/record → record}/project-list.svelte +56 -50
  228. package/src/{tasks/record → record}/record-demo.svelte +130 -110
  229. package/src/{tasks/record → record}/record-header.svelte +37 -28
  230. package/src/{tasks/record → record}/record-shell.svelte +54 -54
  231. package/src/record/record-stats.svelte +48 -0
  232. package/src/record/record-timeline.svelte +251 -0
  233. package/src/record/record.test.ts +471 -0
  234. package/src/{tasks/record → record}/steps-rail.svelte +225 -216
  235. package/src/record/types.ts +299 -0
  236. package/src/record/where-we-are.svelte +125 -0
  237. package/src/shadcn/button/button.svelte +1 -1
  238. package/src/shadcn/card/card-title.svelte +1 -5
  239. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  240. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  241. package/src/shadcn/table/table-cell.svelte +2 -3
  242. package/src/shadcn/utilities.css +3 -8
  243. package/src/slides/Controls.svelte +150 -124
  244. package/src/slides/Deck.svelte +243 -238
  245. package/src/slides/Fragment.svelte +64 -57
  246. package/src/slides/Overview.svelte +106 -101
  247. package/src/slides/Presenter.svelte +300 -266
  248. package/src/slides/Slide.svelte +185 -170
  249. package/src/slides/SlideList.svelte +16 -12
  250. package/src/slides/ambient.d.ts +1 -1
  251. package/src/slides/bridges.ts +42 -23
  252. package/src/slides/deck-controller.svelte.ts +47 -35
  253. package/src/slides/demo-slides.svelte +330 -306
  254. package/src/slides/demo.svelte +164 -160
  255. package/src/slides/export/capture.ts +20 -16
  256. package/src/slides/export/ffmpeg.ts +43 -38
  257. package/src/slides/export/index.ts +5 -5
  258. package/src/slides/export/launch.ts +3 -1
  259. package/src/slides/export/pdf.ts +30 -27
  260. package/src/slides/export/types.ts +10 -8
  261. package/src/slides/index.ts +11 -11
  262. package/src/slides/labels.ts +22 -19
  263. package/src/slides/layout.ts +9 -2
  264. package/src/slides/model.ts +4 -4
  265. package/src/slides/navigation.ts +43 -31
  266. package/src/slides/theme.css +5 -7
  267. package/src/slides/types.ts +9 -10
  268. package/src/status/README.md +18 -0
  269. package/src/status/define-statuses.ts +25 -0
  270. package/src/status/index.ts +11 -0
  271. package/src/{tasks → status}/status-badge.svelte +3 -1
  272. package/src/{tasks → status}/status-dot.svelte +1 -3
  273. package/src/{tasks → status}/status-icon.svelte +1 -1
  274. package/src/status/status-types.ts +40 -0
  275. package/src/tasks/README.md +1 -1
  276. package/src/tasks/board-order.ts +2 -4
  277. package/src/tasks/demo.svelte +192 -34
  278. package/src/tasks/detail/detail-demo.svelte +75 -15
  279. package/src/tasks/detail/detail-types.ts +45 -45
  280. package/src/tasks/detail/task-detail.svelte +40 -10
  281. package/src/tasks/detail/task-property.svelte +8 -2
  282. package/src/tasks/detail/task-thread.svelte +53 -12
  283. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  284. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  285. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  286. package/src/tasks/gantt/gantt-header.svelte +9 -3
  287. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  288. package/src/tasks/gantt/gantt.css +2 -1
  289. package/src/tasks/gantt/gantt.test.ts +4 -2
  290. package/src/tasks/gantt/logic.test.ts +110 -20
  291. package/src/tasks/gantt/logic.ts +42 -6
  292. package/src/tasks/inbox/inbox-card.svelte +15 -7
  293. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  294. package/src/tasks/inbox/inbox-logic.ts +6 -40
  295. package/src/tasks/inbox/inbox-statuses.ts +24 -24
  296. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  297. package/src/tasks/inbox/inbox-types.ts +54 -58
  298. package/src/tasks/inbox/index.ts +22 -21
  299. package/src/tasks/index.ts +52 -53
  300. package/src/tasks/kanban-board.svelte +16 -12
  301. package/src/tasks/kanban-types.ts +2 -3
  302. package/src/tasks/presets/sales.ts +7 -7
  303. package/src/tasks/task-gantt.svelte +28 -8
  304. package/src/tasks/task-list.svelte +41 -17
  305. package/src/tasks/time/duration.ts +40 -40
  306. package/src/tasks/time/time-chip.svelte +9 -2
  307. package/src/tasks/time/time-dot.svelte +1 -1
  308. package/src/tasks/time/time-ring.svelte +11 -3
  309. package/src/tasks/time/time-tracker.svelte +57 -15
  310. package/src/tasks/time/time-types.ts +57 -57
  311. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  312. package/src/thread/README.md +31 -0
  313. package/src/thread/index.ts +12 -0
  314. package/src/thread/logic.ts +61 -0
  315. package/src/thread/thread.svelte +432 -0
  316. package/src/thread/thread.test.ts +73 -0
  317. package/src/thread/types.ts +93 -0
  318. package/src/tokens/craft.css +17 -53
  319. package/src/tokens/index.css +3 -13
  320. package/src/tokens/palette.css +13 -11
  321. package/src/tokens/primitives.css +10 -23
  322. package/src/components/list-page/section-label.svelte +0 -39
  323. package/src/tasks/define-statuses.ts +0 -32
  324. package/src/tasks/inbox/inbox.svelte +0 -203
  325. package/src/tasks/record/demo-data.ts +0 -286
  326. package/src/tasks/record/field-section.svelte +0 -53
  327. package/src/tasks/record/fields.ts +0 -142
  328. package/src/tasks/record/format.ts +0 -87
  329. package/src/tasks/record/kinds.ts +0 -42
  330. package/src/tasks/record/logic.ts +0 -324
  331. package/src/tasks/record/next-up.svelte +0 -70
  332. package/src/tasks/record/record-stats.svelte +0 -48
  333. package/src/tasks/record/record-timeline.svelte +0 -231
  334. package/src/tasks/record/record.test.ts +0 -350
  335. package/src/tasks/record/types.ts +0 -280
  336. package/src/tasks/record/where-we-are.svelte +0 -121
  337. package/src/tasks/side-panel.svelte +0 -193
  338. package/src/tasks/status-types.ts +0 -40
  339. /package/src/components/{list-page → toolbar}/toolbar-trigger.ts +0 -0
  340. /package/src/{tasks → panel}/side-panel-types.ts +0 -0
  341. /package/src/{tasks/record → record}/gutter.ts +0 -0
@@ -1,176 +1,180 @@
1
1
  <script lang="ts">
2
- import Deck from './Deck.svelte';
3
- import type { DeckController } from './deck-controller.svelte.js';
4
- import DemoSlides, { demoStyle } from './demo-slides.svelte';
5
- import type { DeckTheme, DeckTransition } from './types.js';
2
+ import Deck from "./Deck.svelte";
3
+ import type { DeckController } from "./deck-controller.svelte.js";
4
+ import DemoSlides, { demoStyle } from "./demo-slides.svelte";
5
+ import type { DeckTheme, DeckTransition } from "./types.js";
6
6
 
7
- interface Props {
8
- /** Where the presenter view is mounted. Leave out to hide the button. */
9
- presenter?: string;
10
- };
7
+ interface Props {
8
+ /** Where the presenter view is mounted. Leave out to hide the button. */
9
+ presenter?: string;
10
+ }
11
11
 
12
- let { presenter }: Props = $props();
12
+ let { presenter }: Props = $props();
13
13
 
14
- const themes: DeckTheme[] = ['ink', 'paper', 'auto'];
15
- const transitions: DeckTransition[] = ['fade', 'slide', 'zoom', 'morph'];
14
+ const themes: DeckTheme[] = ["ink", "paper", "auto"];
15
+ const transitions: DeckTransition[] = ["fade", "slide", "zoom", "morph"];
16
16
 
17
- let theme = $state<DeckTheme>('ink');
18
- let transition = $state<DeckTransition>('fade');
19
- let deck = $state<DeckController>();
17
+ let theme = $state<DeckTheme>("ink");
18
+ let transition = $state<DeckTransition>("fade");
19
+ let deck = $state<DeckController>();
20
20
  </script>
21
21
 
22
22
  <div class="demo">
23
- <div class="demo__bar">
24
- <div class="demo__group" role="radiogroup" aria-label="Theme">
25
- {#each themes as option (option)}
26
- <button
27
- type="button"
28
- role="radio"
29
- aria-checked={theme === option}
30
- class="demo__option"
31
- onclick={() => (theme = option)}>{option}</button
32
- >
33
- {/each}
34
- </div>
35
- <label class="demo__field">
36
- <span>Transition</span>
37
- <select bind:value={transition}>
38
- {#each transitions as option (option)}
39
- <option value={option}>{option}</option>
40
- {/each}
41
- </select>
42
- </label>
43
- <div class="demo__actions">
44
- <button type="button" class="demo__action" onclick={() => deck?.toggleOverview()}>Overview</button>
45
- {#if presenter}
46
- <a class="demo__action" href={presenter} target="heyramzi-slides-demo-presenter">Presenter</a>
47
- {/if}
48
- <button type="button" class="demo__action" onclick={() => window.print()}>Print</button>
49
- </div>
50
- </div>
23
+ <div class="demo__bar">
24
+ <div class="demo__group" role="radiogroup" aria-label="Theme">
25
+ {#each themes as option (option)}
26
+ <button
27
+ type="button"
28
+ role="radio"
29
+ aria-checked={theme === option}
30
+ class="demo__option"
31
+ onclick={() => (theme = option)}>{option}</button
32
+ >
33
+ {/each}
34
+ </div>
35
+ <label class="demo__field">
36
+ <span>Transition</span>
37
+ <select bind:value={transition}>
38
+ {#each transitions as option (option)}
39
+ <option value={option}>{option}</option>
40
+ {/each}
41
+ </select>
42
+ </label>
43
+ <div class="demo__actions">
44
+ <button type="button" class="demo__action" onclick={() => deck?.toggleOverview()}
45
+ >Overview</button
46
+ >
47
+ {#if presenter}
48
+ <a class="demo__action" href={presenter} target="heyramzi-slides-demo-presenter"
49
+ >Presenter</a
50
+ >
51
+ {/if}
52
+ <button type="button" class="demo__action" onclick={() => window.print()}>Print</button>
53
+ </div>
54
+ </div>
51
55
 
52
- <div class="demo__stage">
53
- <Deck
54
- bind:controller={deck}
55
- id="slides-demo"
56
- title="Slides demo"
57
- sync="slides-demo"
58
- urlSync={false}
59
- {theme}
60
- {transition}
61
- {presenter}
62
- style={demoStyle}
63
- >
64
- <DemoSlides />
65
- </Deck>
66
- </div>
56
+ <div class="demo__stage">
57
+ <Deck
58
+ bind:controller={deck}
59
+ id="slides-demo"
60
+ title="Slides demo"
61
+ sync="slides-demo"
62
+ urlSync={false}
63
+ {theme}
64
+ {transition}
65
+ {presenter}
66
+ style={demoStyle}
67
+ >
68
+ <DemoSlides />
69
+ </Deck>
70
+ </div>
67
71
  </div>
68
72
 
69
73
  <style>
70
- .demo {
71
- display: grid;
72
- gap: 14px;
73
- width: 100%;
74
- }
74
+ .demo {
75
+ display: grid;
76
+ gap: 14px;
77
+ width: 100%;
78
+ }
75
79
 
76
- .demo__bar {
77
- display: flex;
78
- flex-wrap: wrap;
79
- align-items: center;
80
- gap: 10px 16px;
81
- font-size: 13px;
82
- }
83
- .demo__group {
84
- display: inline-flex;
85
- padding: 3px;
86
- background: color-mix(in srgb, currentColor 7%, transparent);
87
- border-radius: 10px;
88
- }
89
- .demo__option {
90
- padding: 5px 12px;
91
- font: inherit;
92
- font-weight: 500;
93
- color: inherit;
94
- text-transform: capitalize;
95
- background: transparent;
96
- border: 0;
97
- border-radius: 7px;
98
- opacity: 0.65;
99
- cursor: pointer;
100
- transition:
101
- background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out),
102
- opacity var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
103
- }
104
- @media (hover: hover) and (pointer: fine) {
105
- .demo__option:hover {
106
- opacity: 1;
107
- }
108
- }
109
- .demo__option[aria-checked='true'] {
110
- background: color-mix(in srgb, currentColor 14%, transparent);
111
- opacity: 1;
112
- }
113
- .demo__field {
114
- display: inline-flex;
115
- align-items: center;
116
- gap: 8px;
117
- opacity: 0.85;
118
- }
119
- .demo__field select {
120
- padding: 5px 8px;
121
- font: inherit;
122
- color: inherit;
123
- text-transform: capitalize;
124
- background: color-mix(in srgb, currentColor 7%, transparent);
125
- border: 0;
126
- border-radius: 8px;
127
- }
128
- .demo__actions {
129
- display: inline-flex;
130
- gap: 6px;
131
- margin-left: auto;
132
- }
133
- .demo__action {
134
- padding: 6px 12px;
135
- font: inherit;
136
- font-weight: 500;
137
- color: inherit;
138
- text-decoration: none;
139
- background: transparent;
140
- border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
141
- border-radius: 8px;
142
- cursor: pointer;
143
- transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
144
- }
145
- @media (hover: hover) and (pointer: fine) {
146
- .demo__action:hover {
147
- background: color-mix(in srgb, currentColor 8%, transparent);
148
- }
149
- }
150
- .demo__option:focus-visible,
151
- .demo__action:focus-visible,
152
- .demo__field select:focus-visible {
153
- outline: 2px solid currentColor;
154
- outline-offset: 2px;
155
- }
80
+ .demo__bar {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ align-items: center;
84
+ gap: 10px 16px;
85
+ font-size: 13px;
86
+ }
87
+ .demo__group {
88
+ display: inline-flex;
89
+ padding: 3px;
90
+ background: color-mix(in srgb, currentColor 7%, transparent);
91
+ border-radius: 10px;
92
+ }
93
+ .demo__option {
94
+ padding: 5px 12px;
95
+ font: inherit;
96
+ font-weight: 500;
97
+ color: inherit;
98
+ text-transform: capitalize;
99
+ background: transparent;
100
+ border: 0;
101
+ border-radius: 7px;
102
+ opacity: 0.65;
103
+ cursor: pointer;
104
+ transition:
105
+ background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out),
106
+ opacity var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
107
+ }
108
+ @media (hover: hover) and (pointer: fine) {
109
+ .demo__option:hover {
110
+ opacity: 1;
111
+ }
112
+ }
113
+ .demo__option[aria-checked="true"] {
114
+ background: color-mix(in srgb, currentColor 14%, transparent);
115
+ opacity: 1;
116
+ }
117
+ .demo__field {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: 8px;
121
+ opacity: 0.85;
122
+ }
123
+ .demo__field select {
124
+ padding: 5px 8px;
125
+ font: inherit;
126
+ color: inherit;
127
+ text-transform: capitalize;
128
+ background: color-mix(in srgb, currentColor 7%, transparent);
129
+ border: 0;
130
+ border-radius: 8px;
131
+ }
132
+ .demo__actions {
133
+ display: inline-flex;
134
+ gap: 6px;
135
+ margin-left: auto;
136
+ }
137
+ .demo__action {
138
+ padding: 6px 12px;
139
+ font: inherit;
140
+ font-weight: 500;
141
+ color: inherit;
142
+ text-decoration: none;
143
+ background: transparent;
144
+ border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
145
+ border-radius: 8px;
146
+ cursor: pointer;
147
+ transition: background-color var(--duration-quick, 150ms) var(--ease-smooth-out, ease-out);
148
+ }
149
+ @media (hover: hover) and (pointer: fine) {
150
+ .demo__action:hover {
151
+ background: color-mix(in srgb, currentColor 8%, transparent);
152
+ }
153
+ }
154
+ .demo__option:focus-visible,
155
+ .demo__action:focus-visible,
156
+ .demo__field select:focus-visible {
157
+ outline: 2px solid currentColor;
158
+ outline-offset: 2px;
159
+ }
156
160
 
157
- .demo__stage {
158
- overflow: hidden;
159
- border-radius: 16px;
160
- box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 14%, transparent);
161
- }
161
+ .demo__stage {
162
+ overflow: hidden;
163
+ border-radius: 16px;
164
+ box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 14%, transparent);
165
+ }
162
166
 
163
- @media print {
164
- .demo {
165
- display: block;
166
- }
167
- .demo__bar {
168
- display: none;
169
- }
170
- .demo__stage {
171
- overflow: visible;
172
- border-radius: 0;
173
- box-shadow: none;
174
- }
175
- }
167
+ @media print {
168
+ .demo {
169
+ display: block;
170
+ }
171
+ .demo__bar {
172
+ display: none;
173
+ }
174
+ .demo__stage {
175
+ overflow: visible;
176
+ border-radius: 0;
177
+ box-shadow: none;
178
+ }
179
+ }
176
180
  </style>
@@ -1,16 +1,20 @@
1
1
  // Capture a page, or one element of it, to PNG at a canvas size and device scale.
2
2
 
3
- import { mkdir, writeFile } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import type { BrowserLike, LocatorLike, PageLike, Size, WaitUntil } from './types.js';
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import type {
6
+ BrowserLike,
7
+ LocatorLike,
8
+ PageLike,
9
+ Size,
10
+ WaitUntil,
11
+ } from "./types.js";
6
12
 
7
13
  /**
8
- * `networkidle` makes a screenshot land on a finished page and never fires on a
9
- * page that keeps polling: an analytics beacon, a chat widget, a buffering hero
10
- * video. So the first attempt asks for idle and a retry falls back to `load`,
11
- * which always fires; the caller's `wait` settles whatever is left.
14
+ * `networkidle` never fires on a page that keeps polling, like a chat widget, so a retry
15
+ * falls back to `load`, which always fires.
12
16
  */
13
- export const DEFAULT_WAITS: readonly WaitUntil[] = ['networkidle', 'load'];
17
+ export const DEFAULT_WAITS: readonly WaitUntil[] = ["networkidle", "load"];
14
18
 
15
19
  export interface GotoOptions {
16
20
  /** Tried in order; the first that resolves wins and the last failure is thrown. */
@@ -30,7 +34,7 @@ export async function gotoSettled(page: PageLike, url: string, options: GotoOpti
30
34
  failure = error;
31
35
  }
32
36
  }
33
- throw failure ?? new Error('gotoSettled needs at least one wait condition.');
37
+ throw failure ?? new Error("gotoSettled needs at least one wait condition.");
34
38
  }
35
39
 
36
40
  export type CaptureSource = { url: string } | { html: string };
@@ -64,18 +68,18 @@ export async function capturePage(
64
68
  deviceScaleFactor: options.scale ?? 1,
65
69
  });
66
70
  try {
67
- if ('url' in source) await gotoSettled(page, source.url, options.goto);
68
- else await page.setContent(source.html, { waitUntil: 'load' });
71
+ if ("url" in source) await gotoSettled(page, source.url, options.goto);
72
+ else await page.setContent(source.html, { waitUntil: "load" });
69
73
  if (options.style) await page.addStyleTag({ content: options.style });
70
74
  if (options.wait) await page.waitForTimeout(options.wait);
71
75
  const { selector } = options;
72
76
  let bytes: Uint8Array;
73
77
  if (selector) {
74
78
  const target = page.locator(selector).first();
75
- await target.waitFor({ state: 'visible' });
76
- bytes = await target.screenshot({ type: 'png' });
79
+ await target.waitFor({ state: "visible" });
80
+ bytes = await target.screenshot({ type: "png" });
77
81
  } else {
78
- bytes = await page.screenshot({ type: 'png', fullPage: options.fullPage });
82
+ bytes = await page.screenshot({ type: "png", fullPage: options.fullPage });
79
83
  }
80
84
  if (options.path) {
81
85
  await mkdir(path.dirname(options.path), { recursive: true });
@@ -88,8 +92,8 @@ export async function capturePage(
88
92
  }
89
93
 
90
94
  /** `01.png`, `02.png` ...: the file name for the nth capture, one-based, zero-padded. */
91
- export function frameName(index: number, digits = 2, extension = 'png'): string {
92
- return `${String(index + 1).padStart(digits, '0')}.${extension}`;
95
+ export function frameName(index: number, digits = 2, extension = "png"): string {
96
+ return `${String(index + 1).padStart(digits, "0")}.${extension}`;
93
97
  }
94
98
 
95
99
  /**
@@ -1,23 +1,29 @@
1
1
  // Encode frames to an mp4 with the ffmpeg on the machine. The package ships no
2
2
  // binary; pass `ffmpeg` to name one that is not on PATH.
3
3
 
4
- import { spawn } from 'node:child_process';
5
- import { mkdir, rm } from 'node:fs/promises';
6
- import path from 'node:path';
4
+ import { spawn } from "node:child_process";
5
+ import { mkdir, rm } from "node:fs/promises";
6
+ import path from "node:path";
7
7
 
8
8
  /** Runs ffmpeg and resolves when it exits 0; the error carries the tail of its stderr. */
9
- export function runFfmpeg(args: string[], ffmpeg = 'ffmpeg'): Promise<void> {
9
+ export function runFfmpeg(args: string[], ffmpeg = "ffmpeg"): Promise<void> {
10
10
  return new Promise((resolve, reject) => {
11
- const child = spawn(ffmpeg, args, { stdio: ['ignore', 'ignore', 'pipe'] });
12
- let stderr = '';
13
- child.stderr.on('data', (chunk) => {
11
+ const child = spawn(ffmpeg, args, { stdio: ["ignore", "ignore", "pipe"] });
12
+ let stderr = "";
13
+ child.stderr.on("data", (chunk) => {
14
14
  stderr += chunk;
15
15
  });
16
- child.on('error', (error) =>
17
- reject(new Error(`${ffmpeg} could not be started (${error.message}). Video export needs ffmpeg on PATH, or pass its path.`)),
16
+ child.on("error", (error) =>
17
+ reject(
18
+ new Error(
19
+ `${ffmpeg} could not be started (${error.message}). Video export needs ffmpeg on PATH, or pass its path.`,
20
+ ),
21
+ ),
18
22
  );
19
- child.on('close', (code) =>
20
- code === 0 ? resolve() : reject(new Error(`${ffmpeg} exited ${code}:\n${stderr.slice(-1500)}`)),
23
+ child.on("close", (code) =>
24
+ code === 0
25
+ ? resolve()
26
+ : reject(new Error(`${ffmpeg} exited ${code}:\n${stderr.slice(-1500)}`)),
21
27
  );
22
28
  });
23
29
  }
@@ -36,31 +42,35 @@ export interface EncodeOptions {
36
42
  /** The ffmpeg arguments for a silent yuv420p H.264 mp4 from a numbered image sequence. */
37
43
  export function encodeArgs(dir: string, outFile: string, options: EncodeOptions): string[] {
38
44
  return [
39
- '-y',
40
- '-hide_banner',
41
- '-loglevel',
42
- 'error',
43
- '-framerate',
45
+ "-y",
46
+ "-hide_banner",
47
+ "-loglevel",
48
+ "error",
49
+ "-framerate",
44
50
  String(options.fps),
45
- '-i',
46
- path.join(dir, options.pattern ?? '%04d.png'),
47
- '-an',
48
- '-vf',
49
- 'format=yuv420p',
50
- '-c:v',
51
- 'libx264',
52
- '-preset',
53
- options.preset ?? 'slow',
54
- '-crf',
51
+ "-i",
52
+ path.join(dir, options.pattern ?? "%04d.png"),
53
+ "-an",
54
+ "-vf",
55
+ "format=yuv420p",
56
+ "-c:v",
57
+ "libx264",
58
+ "-preset",
59
+ options.preset ?? "slow",
60
+ "-crf",
55
61
  String(options.crf ?? 23),
56
- '-movflags',
57
- '+faststart',
62
+ "-movflags",
63
+ "+faststart",
58
64
  outFile,
59
65
  ];
60
66
  }
61
67
 
62
68
  /** Encodes a folder of numbered frames. There is no audio stream at all. */
63
- export async function encodeFrames(dir: string, outFile: string, options: EncodeOptions): Promise<void> {
69
+ export async function encodeFrames(
70
+ dir: string,
71
+ outFile: string,
72
+ options: EncodeOptions,
73
+ ): Promise<void> {
64
74
  await mkdir(path.dirname(outFile), { recursive: true });
65
75
  await runFfmpeg(encodeArgs(dir, outFile, options), options.ffmpeg);
66
76
  }
@@ -74,7 +84,7 @@ export interface LoopFrame {
74
84
  file: string;
75
85
  }
76
86
 
77
- export interface LoopOptions extends Omit<EncodeOptions, 'fps' | 'pattern'> {
87
+ export interface LoopOptions extends Omit<EncodeOptions, "fps" | "pattern"> {
78
88
  fps?: number;
79
89
  seconds?: number;
80
90
  /** Photograph the frame at `t`; called once per frame, in order. */
@@ -87,13 +97,8 @@ export function loopFrameCount(seconds: number, fps: number): number {
87
97
  }
88
98
 
89
99
  /**
90
- * Renders a seamless loop: asks `drawFrame` for t = 0, 1/n, ... (n-1)/n and
91
- * encodes the result.
92
- *
93
- * `t` never reaches 1, so the last frame is one step short of the first and
94
- * the cut back to frame zero is the same step as every other. Driving all
95
- * motion off one t is what makes the seam exact by construction. A captured
96
- * CSS animation loops only when its last frame happens to land on its first.
100
+ * Renders a seamless loop from `drawFrame` at t = 0 to (n-1)/n, never 1, so the cut back
101
+ * to frame zero is the same step as every other.
97
102
  */
98
103
  export async function renderLoop(
99
104
  outFile: string,
@@ -109,7 +114,7 @@ export async function renderLoop(
109
114
  await options.drawFrame({
110
115
  index,
111
116
  t: index / frames,
112
- file: path.join(work, `${String(index).padStart(4, '0')}.png`),
117
+ file: path.join(work, `${String(index).padStart(4, "0")}.png`),
113
118
  });
114
119
  }
115
120
  await encodeFrames(work, outFile, { ...options, fps });
@@ -2,8 +2,8 @@
2
2
  // depending on Playwright or bundling ffmpeg. Hand it the Playwright pages and
3
3
  // browsers you already have.
4
4
 
5
- export * from './capture.js';
6
- export * from './ffmpeg.js';
7
- export * from './launch.js';
8
- export * from './pdf.js';
9
- export type * from './types.js';
5
+ export * from "./capture.js";
6
+ export * from "./ffmpeg.js";
7
+ export * from "./launch.js";
8
+ export * from "./pdf.js";
9
+ export type * from "./types.js";
@@ -6,5 +6,7 @@
6
6
  export async function launchChromium<B>(chromium: {
7
7
  launch(options?: { headless?: boolean; channel?: string }): Promise<B>;
8
8
  }): Promise<B> {
9
- return chromium.launch({ headless: true }).catch(() => chromium.launch({ headless: true, channel: 'chrome' }));
9
+ return chromium
10
+ .launch({ headless: true })
11
+ .catch(() => chromium.launch({ headless: true, channel: "chrome" }));
10
12
  }