@heyramzi/ui 2.1.0 → 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 (312) hide show
  1. package/package.json +7 -8
  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/card/card-frame.svelte +43 -39
  31. package/src/canvas/card/resize-handles.svelte +89 -80
  32. package/src/canvas/chrome/chrome-zoom.svelte +101 -100
  33. package/src/canvas/chrome/lock-badge.svelte +8 -8
  34. package/src/canvas/chrome/side-handles.svelte +24 -19
  35. package/src/canvas/chrome/size-chip.svelte +30 -30
  36. package/src/canvas/chrome/snap-guides.svelte +20 -20
  37. package/src/canvas/components/icons/ToolIcon.svelte +8 -8
  38. package/src/canvas/components/shared/svg/PreviewSvg.svelte +19 -19
  39. package/src/canvas/components/shared/svg/ShapeOutline.svelte +71 -48
  40. package/src/canvas/components/shared/svg/StrokeNode.svelte +21 -27
  41. package/src/canvas/components/shared/svg/TextRule.svelte +25 -17
  42. package/src/canvas/edges/bend-handle.svelte +46 -46
  43. package/src/canvas/edges/edge-gradient.svelte +10 -10
  44. package/src/canvas/edges/gradient-edge.svelte +45 -29
  45. package/src/canvas/editor/lock.test.ts +6 -1
  46. package/src/canvas/editor/style.ts +7 -1
  47. package/src/canvas/headless/svg-figure.test.ts +5 -1
  48. package/src/canvas/lod/lod-switch.svelte +111 -106
  49. package/src/canvas/pen/PenField.svelte +87 -81
  50. package/src/canvas/pen/PenOverlay.svelte +127 -127
  51. package/src/canvas/pen/ShapeNode.svelte +31 -32
  52. package/src/canvas/pen/TextNode.svelte +54 -53
  53. package/src/canvas/pen/host-field.test.svelte +7 -7
  54. package/src/canvas/pen/shapes.ts +7 -1
  55. package/src/canvas/screen/GlyphIcon.svelte +22 -17
  56. package/src/canvas/screen/PartView.svelte +138 -72
  57. package/src/canvas/screen/ScreenBody.svelte +81 -60
  58. package/src/canvas/screen/ScreenCard.svelte +20 -20
  59. package/src/canvas/ui/CanvasToolbar.svelte +173 -85
  60. package/src/canvas/ui/FrameLayer.svelte +59 -59
  61. package/src/canvas/ui/PenLayer.svelte +39 -30
  62. package/src/canvas/ui/StylePanel.svelte +119 -102
  63. package/src/canvas/ui/canvas-grid.svelte +137 -129
  64. package/src/charts/animated-value.svelte +1 -1
  65. package/src/charts/area-chart.css +2 -5
  66. package/src/charts/area-chart.svelte +1 -1
  67. package/src/charts/bar-chart.css +2 -3
  68. package/src/charts/bar-chart.svelte +1 -1
  69. package/src/charts/charts.css +5 -17
  70. package/src/charts/crosshair.svelte +1 -1
  71. package/src/charts/dashboard/dashboard-empty.svelte +25 -25
  72. package/src/charts/dashboard/dashboard-section.svelte +24 -24
  73. package/src/charts/dashboard/data-panel.svelte +50 -50
  74. package/src/charts/dashboard/data-sort-head.svelte +56 -56
  75. package/src/charts/dashboard/data-table.svelte +138 -138
  76. package/src/charts/dashboard/panel-grid.svelte +30 -30
  77. package/src/charts/dashboard/period-filter.svelte +25 -25
  78. package/src/charts/dashboard/rating-chip.svelte +19 -19
  79. package/src/charts/dashboard/stat-band.svelte +32 -32
  80. package/src/charts/dashboard/stat-tile.svelte +106 -99
  81. package/src/charts/donut-chart.svelte +1 -1
  82. package/src/charts/edge-fade.svelte +1 -1
  83. package/src/charts/figure.svelte +1 -1
  84. package/src/charts/gauge-chart.css +4 -7
  85. package/src/charts/gauge-chart.svelte +1 -1
  86. package/src/charts/hover-focus.svelte +1 -1
  87. package/src/charts/legend.css +1 -3
  88. package/src/charts/legend.svelte +1 -1
  89. package/src/charts/line-chart.svelte +1 -1
  90. package/src/charts/number-chart.css +6 -9
  91. package/src/charts/number-chart.svelte +1 -1
  92. package/src/charts/reference-line.svelte +19 -19
  93. package/src/charts/reveal.svelte +1 -1
  94. package/src/charts/ring-chart.css +2 -4
  95. package/src/charts/ring-chart.svelte +1 -1
  96. package/src/charts/sparkline.svelte +5 -2
  97. package/src/charts/stat-card.svelte +1 -1
  98. package/src/charts/time-series-chart.css +2 -5
  99. package/src/charts/trend-pill.css +2 -4
  100. package/src/charts/trend-pill.svelte +1 -1
  101. package/src/components/copy/copy-button.svelte +93 -98
  102. package/src/components/copy/copy-button.test.ts +10 -2
  103. package/src/components/data-table/infinite-scroll.svelte +42 -42
  104. package/src/components/data-table/pagination.svelte +34 -22
  105. package/src/components/data-table/row-arrow.svelte +19 -16
  106. package/src/components/data-table/row-context-menu.svelte +52 -52
  107. package/src/components/data-table/sortable-head.svelte +25 -25
  108. package/src/components/data-table/table-skeleton.svelte +41 -36
  109. package/src/components/docs/doc-page.svelte +18 -9
  110. package/src/components/docs/on-this-page.svelte +16 -3
  111. package/src/components/docs/scroll-spy.svelte.ts +2 -3
  112. package/src/components/field-row/field-row.svelte +10 -8
  113. package/src/components/hold-to-confirm/hold-to-confirm.svelte +5 -1
  114. package/src/components/icon/icon.svelte +14 -14
  115. package/src/components/language-flag/language-flag.svelte +3 -1
  116. package/src/components/list-page/detail-empty.svelte +34 -34
  117. package/src/components/list-page/detail-header.svelte +55 -47
  118. package/src/components/list-page/detail-panel.svelte +51 -59
  119. package/src/components/list-page/detail-row.svelte +19 -19
  120. package/src/components/list-page/detail-stack.svelte +21 -21
  121. package/src/components/list-page/empty-state.svelte +30 -30
  122. package/src/components/list-page/filter-item.svelte +20 -20
  123. package/src/components/list-page/filter-popover.svelte +60 -55
  124. package/src/components/list-page/grouped-list.svelte +4 -2
  125. package/src/components/list-page/index.ts +5 -1
  126. package/src/components/list-page/list-page-shell.svelte +2 -5
  127. package/src/components/list-page/master-detail.svelte +87 -88
  128. package/src/components/list-page/notice-bar.svelte +38 -38
  129. package/src/components/list-page/prev-next.svelte +63 -63
  130. package/src/components/list-page/rail-body.svelte +18 -18
  131. package/src/components/list-page/rail-empty.svelte +23 -30
  132. package/src/components/list-page/rail-head.svelte +24 -24
  133. package/src/components/list-page/rail-row.svelte +77 -73
  134. package/src/components/list-page/section-host.ts +2 -3
  135. package/src/components/locale-switch/locale-switch.svelte +41 -41
  136. package/src/components/mark/mark.svelte +34 -38
  137. package/src/components/open-in/index.ts +1 -5
  138. package/src/components/open-in/open-in.svelte +40 -40
  139. package/src/components/picker/mark-picker.svelte +49 -49
  140. package/src/components/picker/picker.svelte +5 -3
  141. package/src/components/section-header/section-header.test.ts +7 -1
  142. package/src/components/section-header/section-label.svelte +20 -20
  143. package/src/components/segmented/animated-tabs.svelte +31 -2
  144. package/src/components/segmented/collapse.test.ts +2 -1
  145. package/src/components/segmented/index.ts +2 -0
  146. package/src/components/segmented/segment.svelte +52 -49
  147. package/src/components/segmented/segmented-filter.svelte +79 -80
  148. package/src/components/segmented/segmented-nav.svelte +34 -40
  149. package/src/components/segmented/segmented-overflow.svelte +53 -50
  150. package/src/components/segmented/segmented-track.svelte +62 -46
  151. package/src/components/segmented/segmented.ts +2 -3
  152. package/src/components/star-rating-input/star-rating-input.svelte +103 -103
  153. package/src/components/svg/svg.svelte +4 -4
  154. package/src/components/table-view/table-view.svelte +17 -2
  155. package/src/components/table-view/table-view.test.ts +7 -1
  156. package/src/components/table-view/types.ts +11 -11
  157. package/src/components/tags-input/index.ts +13 -0
  158. package/src/components/tags-input/tags-input.svelte +305 -0
  159. package/src/components/tags-input/tags.test.ts +64 -0
  160. package/src/components/tags-input/tags.ts +142 -0
  161. package/src/components/theme/index.ts +2 -4
  162. package/src/components/theme/theme-provider.svelte +1 -3
  163. package/src/components/toolbar/toolbar-search.svelte +25 -25
  164. package/src/components/toolbar/toolbar.svelte +11 -7
  165. package/src/components/toolbar/toolbar.test.ts +10 -1
  166. package/src/design/index.css +21 -78
  167. package/src/design/polish.css +26 -35
  168. package/src/editor/editor-toolbar.svelte +104 -99
  169. package/src/editor/editor.svelte +296 -266
  170. package/src/editor/image-toolbar.svelte +20 -22
  171. package/src/editor/index.ts +1 -4
  172. package/src/editor/inline-editor.svelte +280 -238
  173. package/src/editor/markdown.ts +15 -15
  174. package/src/editor/renderer.svelte +71 -71
  175. package/src/editor/slash-menu.svelte +24 -27
  176. package/src/effects/animated-beam/diagrams/BeamSvg.svelte +1 -1
  177. package/src/effects/image-comparison/ImageComparison.svelte +11 -1
  178. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +59 -46
  179. package/src/effects/magic-card/MagicCard.css +14 -7
  180. package/src/effects/marquee/Marquee.svelte +3 -5
  181. package/src/effects/reveal/observe.ts +0 -3
  182. package/src/effects/spotlight/Spotlight.svelte +1 -1
  183. package/src/effects/theme-toggle/ThemeToggle.css +25 -22
  184. package/src/effects/theme-toggle/ThemeToggle.svelte +6 -7
  185. package/src/gallery.test.ts +14 -4
  186. package/src/inbox/README.md +1 -3
  187. package/src/inbox/inbox.test.ts +181 -138
  188. package/src/inbox/index.ts +20 -20
  189. package/src/inbox/logic.ts +114 -111
  190. package/src/inbox/thread-inbox.svelte +227 -151
  191. package/src/inbox/types.ts +130 -108
  192. package/src/lib/theme-transition.ts +2 -4
  193. package/src/lib/tone.ts +2 -4
  194. package/src/lib/utils.ts +2 -2
  195. package/src/modules.test.ts +79 -69
  196. package/src/onboarding/define-tours.test.ts +55 -40
  197. package/src/onboarding/define-tours.ts +23 -23
  198. package/src/onboarding/demo.svelte +24 -5
  199. package/src/onboarding/index.ts +1 -8
  200. package/src/onboarding/onboarding-provider.svelte +19 -7
  201. package/src/onboarding/onboarding.svelte.ts +16 -16
  202. package/src/onboarding/pick.test.ts +61 -52
  203. package/src/onboarding/pick.ts +16 -13
  204. package/src/onboarding/stores.test.ts +80 -80
  205. package/src/onboarding/stores.ts +41 -39
  206. package/src/onboarding/tour-card.svelte +30 -5
  207. package/src/onboarding/tour-runner.svelte +12 -3
  208. package/src/onboarding/types.ts +20 -12
  209. package/src/onboarding/wait.ts +22 -22
  210. package/src/panel/side-panel.svelte +94 -93
  211. package/src/record/demo-data.ts +291 -225
  212. package/src/record/field-section.svelte +38 -34
  213. package/src/record/fields.ts +148 -96
  214. package/src/record/format.ts +63 -56
  215. package/src/record/index.ts +49 -35
  216. package/src/record/info-list.svelte +145 -114
  217. package/src/record/kinds.ts +31 -31
  218. package/src/record/logic.ts +216 -214
  219. package/src/record/next-up.svelte +45 -34
  220. package/src/record/project-list.svelte +56 -50
  221. package/src/record/record-demo.svelte +130 -110
  222. package/src/record/record-header.svelte +37 -28
  223. package/src/record/record-shell.svelte +54 -54
  224. package/src/record/record-stats.svelte +25 -25
  225. package/src/record/record-timeline.svelte +134 -114
  226. package/src/record/record.test.ts +430 -309
  227. package/src/record/steps-rail.svelte +225 -216
  228. package/src/record/types.ts +234 -215
  229. package/src/record/where-we-are.svelte +62 -58
  230. package/src/shadcn/button/button.svelte +1 -1
  231. package/src/shadcn/card/card-title.svelte +1 -5
  232. package/src/shadcn/hooks/use-highlighted-code.svelte.ts +2 -13
  233. package/src/shadcn/sheet/sheet-content.svelte +2 -4
  234. package/src/shadcn/table/table-cell.svelte +2 -3
  235. package/src/shadcn/utilities.css +3 -8
  236. package/src/slides/Controls.svelte +150 -124
  237. package/src/slides/Deck.svelte +243 -238
  238. package/src/slides/Fragment.svelte +64 -57
  239. package/src/slides/Overview.svelte +106 -101
  240. package/src/slides/Presenter.svelte +300 -266
  241. package/src/slides/Slide.svelte +185 -170
  242. package/src/slides/SlideList.svelte +16 -12
  243. package/src/slides/ambient.d.ts +1 -1
  244. package/src/slides/bridges.ts +42 -23
  245. package/src/slides/deck-controller.svelte.ts +47 -35
  246. package/src/slides/demo-slides.svelte +330 -306
  247. package/src/slides/demo.svelte +164 -160
  248. package/src/slides/export/capture.ts +20 -16
  249. package/src/slides/export/ffmpeg.ts +43 -38
  250. package/src/slides/export/index.ts +5 -5
  251. package/src/slides/export/launch.ts +3 -1
  252. package/src/slides/export/pdf.ts +30 -27
  253. package/src/slides/export/types.ts +10 -8
  254. package/src/slides/index.ts +11 -11
  255. package/src/slides/labels.ts +22 -19
  256. package/src/slides/layout.ts +9 -2
  257. package/src/slides/model.ts +4 -4
  258. package/src/slides/navigation.ts +43 -31
  259. package/src/slides/theme.css +5 -7
  260. package/src/slides/types.ts +9 -10
  261. package/src/status/define-statuses.ts +17 -24
  262. package/src/status/index.ts +5 -5
  263. package/src/status/status-badge.svelte +3 -1
  264. package/src/status/status-dot.svelte +1 -3
  265. package/src/status/status-icon.svelte +1 -1
  266. package/src/status/status-types.ts +21 -21
  267. package/src/tasks/board-order.ts +2 -4
  268. package/src/tasks/demo.svelte +189 -31
  269. package/src/tasks/detail/detail-demo.svelte +73 -13
  270. package/src/tasks/detail/detail-types.ts +45 -45
  271. package/src/tasks/detail/task-detail.svelte +40 -10
  272. package/src/tasks/detail/task-property.svelte +8 -2
  273. package/src/tasks/detail/task-thread.svelte +53 -12
  274. package/src/tasks/gantt/gantt-bar.svelte +17 -6
  275. package/src/tasks/gantt/gantt-grid.svelte +40 -9
  276. package/src/tasks/gantt/gantt-group-row.svelte +8 -2
  277. package/src/tasks/gantt/gantt-header.svelte +9 -3
  278. package/src/tasks/gantt/gantt-task-row.svelte +26 -6
  279. package/src/tasks/gantt/gantt.css +2 -1
  280. package/src/tasks/gantt/gantt.test.ts +3 -1
  281. package/src/tasks/gantt/logic.test.ts +110 -20
  282. package/src/tasks/gantt/logic.ts +42 -6
  283. package/src/tasks/inbox/inbox-card.svelte +13 -5
  284. package/src/tasks/inbox/inbox-draft.svelte +1 -1
  285. package/src/tasks/inbox/inbox-logic.ts +6 -40
  286. package/src/tasks/inbox/inbox-statuses.ts +23 -23
  287. package/src/tasks/inbox/inbox-trace.svelte +9 -3
  288. package/src/tasks/inbox/inbox-types.ts +54 -58
  289. package/src/tasks/inbox/index.ts +22 -21
  290. package/src/tasks/index.ts +49 -52
  291. package/src/tasks/kanban-board.svelte +16 -12
  292. package/src/tasks/kanban-types.ts +2 -3
  293. package/src/tasks/presets/sales.ts +6 -6
  294. package/src/tasks/task-gantt.svelte +28 -8
  295. package/src/tasks/task-list.svelte +41 -17
  296. package/src/tasks/time/duration.ts +40 -40
  297. package/src/tasks/time/time-chip.svelte +9 -2
  298. package/src/tasks/time/time-dot.svelte +1 -1
  299. package/src/tasks/time/time-ring.svelte +11 -3
  300. package/src/tasks/time/time-tracker.svelte +57 -15
  301. package/src/tasks/time/time-types.ts +57 -57
  302. package/src/tasks/use-kanban-dnd.svelte.ts +2 -3
  303. package/src/thread/index.ts +7 -7
  304. package/src/thread/logic.ts +36 -33
  305. package/src/thread/thread.svelte +82 -16
  306. package/src/thread/thread.test.ts +58 -47
  307. package/src/thread/types.ts +68 -68
  308. package/src/tokens/craft.css +17 -53
  309. package/src/tokens/index.css +3 -13
  310. package/src/tokens/palette.css +13 -11
  311. package/src/tokens/primitives.css +10 -23
  312. package/src/tasks/inbox/inbox.svelte +0 -203
@@ -1,179 +1,194 @@
1
1
  <script lang="ts">
2
- import { getContext, setContext, type Snippet } from 'svelte';
3
- import type { DeckController, SlideEntry } from './deck-controller.svelte.js';
4
- import { DECK_KEY, SLIDE_KEY, type SlideAlign, type SlideContext, type SlideNotes } from './types.js';
5
-
6
- interface Props {
7
- /** Accessible name, read after "Slide 3 of 12". */
8
- label?: string;
9
- /** Speaker notes for the presenter view. */
10
- notes?: SlideNotes;
11
- /** Skipped by paging and left out of the overview and print. */
12
- hidden?: boolean;
13
- /** `center` (default), `start`, or `bare` to lay the canvas out yourself. */
14
- align?: SlideAlign;
15
- class?: string;
16
- style?: string;
17
- children?: Snippet;
18
- };
19
-
20
- let { label, notes, hidden = false, align = 'center', class: className, style, children }: Props = $props();
21
-
22
- const deck = getContext<DeckController | undefined>(DECK_KEY);
23
- if (!deck) throw new Error('<Slide> must be rendered inside <Deck>.');
24
-
25
- let el = $state<HTMLElement | null>(null);
26
- let steps = $state(0);
27
- /* Fragments per step, so one leaving doesn't drop a step another still asks for. */
28
- const counts = new Map<number, number>();
29
-
30
- const entry: SlideEntry = {
31
- id: Symbol('slide'),
32
- get steps() {
33
- return steps;
34
- },
35
- get hidden() {
36
- return hidden;
37
- },
38
- get label() {
39
- return label;
40
- },
41
- get notes() {
42
- return notes;
43
- },
44
- get el() {
45
- return el;
46
- },
47
- };
48
-
49
- $effect(() => deck.register(entry));
50
-
51
- const index = $derived(deck.entries.indexOf(entry));
52
- const active = $derived(index >= 0 && deck.position.index === index);
53
- const box = $derived(deck.boxOf(index));
54
- const order = $derived(Math.max(0, deck.visible.indexOf(index)));
55
-
56
- /* A slide that is leaving keeps the step it left at, so its fragments don't vanish mid-fade. */
57
- let held = $state(0);
58
- $effect.pre(() => {
59
- if (active) held = deck.position.step;
60
- });
61
-
62
- const recount = () => (steps = counts.size ? Math.max(...counts.keys()) : 0);
63
-
64
- setContext<SlideContext>(SLIDE_KEY, {
65
- shown: (step) => deck.revealAll || step <= (active ? deck.position.step : held),
66
- join(step) {
67
- const order = counts.get(step) ?? 0;
68
- counts.set(step, order + 1);
69
- recount();
70
- return {
71
- order,
72
- leave() {
73
- const left = (counts.get(step) ?? 1) - 1;
74
- if (left <= 0) counts.delete(step);
75
- else counts.set(step, left);
76
- recount();
77
- },
78
- };
79
- },
80
- });
81
-
82
- /* A View Transition needs one element per name, so only the active slide holds its morph names. */
83
- $effect(() => {
84
- const names = el?.querySelectorAll<HTMLElement>('[data-morph]') ?? [];
85
- for (const node of names) node.style.viewTransitionName = active ? (node.dataset.morph ?? '') : '';
86
- });
87
-
88
- const where = $derived(deck.labels.slide(order + 1, deck.visible.length));
89
- const name = $derived(label ? `${where}: ${label}` : where);
2
+ import { getContext, setContext, type Snippet } from "svelte";
3
+ import type { DeckController, SlideEntry } from "./deck-controller.svelte.js";
4
+ import {
5
+ DECK_KEY,
6
+ SLIDE_KEY,
7
+ type SlideAlign,
8
+ type SlideContext,
9
+ type SlideNotes,
10
+ } from "./types.js";
11
+
12
+ interface Props {
13
+ /** Accessible name, read after "Slide 3 of 12". */
14
+ label?: string;
15
+ /** Speaker notes for the presenter view. */
16
+ notes?: SlideNotes;
17
+ /** Skipped by paging and left out of the overview and print. */
18
+ hidden?: boolean;
19
+ /** `center` (default), `start`, or `bare` to lay the canvas out yourself. */
20
+ align?: SlideAlign;
21
+ class?: string;
22
+ style?: string;
23
+ children?: Snippet;
24
+ }
25
+
26
+ let {
27
+ label,
28
+ notes,
29
+ hidden = false,
30
+ align = "center",
31
+ class: className,
32
+ style,
33
+ children,
34
+ }: Props = $props();
35
+
36
+ const deck = getContext<DeckController | undefined>(DECK_KEY);
37
+ if (!deck) throw new Error("<Slide> must be rendered inside <Deck>.");
38
+
39
+ let el = $state<HTMLElement | null>(null);
40
+ let steps = $state(0);
41
+ /* Fragments per step, so one leaving doesn't drop a step another still asks for. */
42
+ const counts = new Map<number, number>();
43
+
44
+ const entry: SlideEntry = {
45
+ id: Symbol("slide"),
46
+ get steps() {
47
+ return steps;
48
+ },
49
+ get hidden() {
50
+ return hidden;
51
+ },
52
+ get label() {
53
+ return label;
54
+ },
55
+ get notes() {
56
+ return notes;
57
+ },
58
+ get el() {
59
+ return el;
60
+ },
61
+ };
62
+
63
+ $effect(() => deck.register(entry));
64
+
65
+ const index = $derived(deck.entries.indexOf(entry));
66
+ const active = $derived(index >= 0 && deck.position.index === index);
67
+ const box = $derived(deck.boxOf(index));
68
+ const order = $derived(Math.max(0, deck.visible.indexOf(index)));
69
+
70
+ /* A slide that is leaving keeps the step it left at, so its fragments don't vanish mid-fade. */
71
+ let held = $state(0);
72
+ $effect.pre(() => {
73
+ if (active) held = deck.position.step;
74
+ });
75
+
76
+ const recount = () => (steps = counts.size ? Math.max(...counts.keys()) : 0);
77
+
78
+ setContext<SlideContext>(SLIDE_KEY, {
79
+ shown: (step) => deck.revealAll || step <= (active ? deck.position.step : held),
80
+ join(step) {
81
+ const order = counts.get(step) ?? 0;
82
+ counts.set(step, order + 1);
83
+ recount();
84
+ return {
85
+ order,
86
+ leave() {
87
+ const left = (counts.get(step) ?? 1) - 1;
88
+ if (left <= 0) counts.delete(step);
89
+ else counts.set(step, left);
90
+ recount();
91
+ },
92
+ };
93
+ },
94
+ });
95
+
96
+ /* A View Transition needs one element per name, so only the active slide holds its morph names. */
97
+ $effect(() => {
98
+ const names = el?.querySelectorAll<HTMLElement>("[data-morph]") ?? [];
99
+ for (const node of names)
100
+ node.style.viewTransitionName = active ? (node.dataset.morph ?? "") : "";
101
+ });
102
+
103
+ const where = $derived(deck.labels.slide(order + 1, deck.visible.length));
104
+ const name = $derived(label ? `${where}: ${label}` : where);
90
105
  </script>
91
106
 
92
107
  <section
93
- bind:this={el}
94
- class={['slide', className]}
95
- style:--slide-w="{deck.canvas.w}px"
96
- style:--slide-h="{deck.canvas.h}px"
97
- style:--slide-scale={box?.scale}
98
- style:--i={order}
99
- style:transform={box ? `translate3d(${box.x}px, ${box.y}px, 0) scale(${box.scale})` : undefined}
100
- {style}
101
- {hidden}
102
- data-active={active}
103
- role="group"
104
- aria-roledescription={deck.labels.slideRole}
105
- aria-label={name}
106
- aria-hidden={!active}
107
- inert={!active || deck.overview}
108
+ bind:this={el}
109
+ class={["slide", className]}
110
+ style:--slide-w="{deck.canvas.w}px"
111
+ style:--slide-h="{deck.canvas.h}px"
112
+ style:--slide-scale={box?.scale}
113
+ style:--i={order}
114
+ style:transform={box ? `translate3d(${box.x}px, ${box.y}px, 0) scale(${box.scale})` : undefined}
115
+ {style}
116
+ {hidden}
117
+ data-active={active}
118
+ role="group"
119
+ aria-roledescription={deck.labels.slideRole}
120
+ aria-label={name}
121
+ aria-hidden={!active}
122
+ inert={!active || deck.overview}
108
123
  >
109
- <div class="slide__surface" data-align={align}>
110
- {@render children?.()}
111
- </div>
124
+ <div class="slide__surface" data-align={align}>
125
+ {@render children?.()}
126
+ </div>
112
127
  </section>
113
128
 
114
129
  <style>
115
- /* The box is a full canvas, moved and scaled from its top-left. */
116
- .slide {
117
- position: absolute;
118
- top: 0;
119
- left: 0;
120
- z-index: 1;
121
- width: var(--slide-w);
122
- height: var(--slide-h);
123
- transform-origin: 0 0;
124
- visibility: hidden;
125
- /* The old slide stays drawn for the length of the enter animation. */
126
- transition: visibility 0s linear var(--deck-t-slide);
127
- }
128
- .slide[hidden] {
129
- display: none;
130
- }
131
- .slide[data-active='true'] {
132
- z-index: 2;
133
- visibility: visible;
134
- transition-delay: 0s;
135
- }
136
-
137
- /* The overview shows every slide. They all leave the stack at once and settle in turn, later ones slower. */
138
- :global(.deck[data-view='overview']) .slide {
139
- visibility: visible;
140
- }
141
- :global(.deck[data-morphing]) .slide {
142
- transition:
143
- visibility 0s linear calc(var(--deck-t-morph) + 10 * var(--deck-stagger)),
144
- transform calc(var(--deck-t-morph) + min(var(--i), 10) * var(--deck-stagger)) var(--deck-ease);
145
- }
146
- /* Opening shows the slides at once, so only closing waits before hiding them. */
147
- :global(.deck[data-morphing][data-view='overview']) .slide {
148
- transition-delay: 0s;
149
- }
150
-
151
- .slide__surface {
152
- position: relative;
153
- box-sizing: border-box;
154
- width: 100%;
155
- height: 100%;
156
- overflow: hidden;
157
- color: var(--deck-fg);
158
- background: var(--deck-bg);
159
- font-family: var(--deck-font);
160
- animation: var(--deck-t-slide) var(--deck-ease) backwards;
161
- }
162
- :global(.deck[data-view='overview']) .slide__surface {
163
- /* Design px, so divide by the scale to get the same corner on every thumbnail. */
164
- border-radius: calc(10px / var(--slide-scale, 1));
165
- box-shadow: 0 0 0 calc(1px / var(--slide-scale, 1)) var(--deck-line);
166
- }
167
-
168
- .slide__surface[data-align='center'],
169
- .slide__surface[data-align='start'] {
170
- display: flex;
171
- flex-direction: column;
172
- justify-content: center;
173
- padding: var(--deck-pad);
174
- }
175
- .slide__surface[data-align='center'] {
176
- align-items: center;
177
- text-align: center;
178
- }
130
+ /* The box is a full canvas, moved and scaled from its top-left. */
131
+ .slide {
132
+ position: absolute;
133
+ top: 0;
134
+ left: 0;
135
+ z-index: 1;
136
+ width: var(--slide-w);
137
+ height: var(--slide-h);
138
+ transform-origin: 0 0;
139
+ visibility: hidden;
140
+ /* The old slide stays drawn for the length of the enter animation. */
141
+ transition: visibility 0s linear var(--deck-t-slide);
142
+ }
143
+ .slide[hidden] {
144
+ display: none;
145
+ }
146
+ .slide[data-active="true"] {
147
+ z-index: 2;
148
+ visibility: visible;
149
+ transition-delay: 0s;
150
+ }
151
+
152
+ /* The overview shows every slide. They all leave the stack at once and settle in turn, later ones slower. */
153
+ :global(.deck[data-view="overview"]) .slide {
154
+ visibility: visible;
155
+ }
156
+ :global(.deck[data-morphing]) .slide {
157
+ transition:
158
+ visibility 0s linear calc(var(--deck-t-morph) + 10 * var(--deck-stagger)),
159
+ transform calc(var(--deck-t-morph) + min(var(--i), 10) * var(--deck-stagger)) var(--deck-ease);
160
+ }
161
+ /* Opening shows the slides at once, so only closing waits before hiding them. */
162
+ :global(.deck[data-morphing][data-view="overview"]) .slide {
163
+ transition-delay: 0s;
164
+ }
165
+
166
+ .slide__surface {
167
+ position: relative;
168
+ box-sizing: border-box;
169
+ width: 100%;
170
+ height: 100%;
171
+ overflow: hidden;
172
+ color: var(--deck-fg);
173
+ background: var(--deck-bg);
174
+ font-family: var(--deck-font);
175
+ animation: var(--deck-t-slide) var(--deck-ease) backwards;
176
+ }
177
+ :global(.deck[data-view="overview"]) .slide__surface {
178
+ /* Design px, so divide by the scale to get the same corner on every thumbnail. */
179
+ border-radius: calc(10px / var(--slide-scale, 1));
180
+ box-shadow: 0 0 0 calc(1px / var(--slide-scale, 1)) var(--deck-line);
181
+ }
182
+
183
+ .slide__surface[data-align="center"],
184
+ .slide__surface[data-align="start"] {
185
+ display: flex;
186
+ flex-direction: column;
187
+ justify-content: center;
188
+ padding: var(--deck-pad);
189
+ }
190
+ .slide__surface[data-align="center"] {
191
+ align-items: center;
192
+ text-align: center;
193
+ }
179
194
  </style>
@@ -1,19 +1,23 @@
1
1
  <script lang="ts">
2
- import Slide from './Slide.svelte';
3
- import type { SlideInstance, SlideRegistry } from './model.js';
2
+ import Slide from "./Slide.svelte";
3
+ import type { SlideInstance, SlideRegistry } from "./model.js";
4
4
 
5
- interface Props { slides: SlideInstance[]; registry: SlideRegistry; unknownLabel: (type: string) => string };
5
+ interface Props {
6
+ slides: SlideInstance[];
7
+ registry: SlideRegistry;
8
+ unknownLabel: (type: string) => string;
9
+ }
6
10
 
7
- let { slides, registry, unknownLabel }: Props = $props();
11
+ let { slides, registry, unknownLabel }: Props = $props();
8
12
  </script>
9
13
 
10
14
  {#each slides as slide (slide.id)}
11
- {@const definition = registry[slide.type]}
12
- <Slide label={definition?.label} notes={slide.notes} hidden={slide.hidden} align="bare">
13
- {#if definition}
14
- <definition.layout content={slide.content} {slide} />
15
- {:else}
16
- <p class="deck-body" style:padding="var(--deck-pad)">{unknownLabel(slide.type)}</p>
17
- {/if}
18
- </Slide>
15
+ {@const definition = registry[slide.type]}
16
+ <Slide label={definition?.label} notes={slide.notes} hidden={slide.hidden} align="bare">
17
+ {#if definition}
18
+ <definition.layout content={slide.content} {slide} />
19
+ {:else}
20
+ <p class="deck-body" style:padding="var(--deck-pad)">{unknownLabel(slide.type)}</p>
21
+ {/if}
22
+ </Slide>
19
23
  {/each}
@@ -1 +1 @@
1
- declare module '*.css';
1
+ declare module "*.css";
@@ -1,18 +1,24 @@
1
1
  /* The ways a deck talks to the outside: its URL, a parent frame, another window. Each returns a cleanup. */
2
2
 
3
- import type { DeckController } from './deck-controller.svelte.js';
4
- import { parseSlideParam } from './navigation.js';
5
- import type { BridgeOptions, UrlSync } from './types.js';
3
+ import type { DeckController } from "./deck-controller.svelte.js";
4
+ import { parseSlideParam } from "./navigation.js";
5
+ import type { BridgeOptions, UrlSync } from "./types.js";
6
6
 
7
7
  /** Reads `?s=` once, then keeps it at the current slide. The write is a replace, so Back skips it. */
8
- export function attachUrlSync(deck: DeckController, { param = 's', write }: UrlSync = {}): () => void {
9
- const initial = parseSlideParam(new URL(location.href).searchParams.get(param), deck.entries.length);
10
- if (initial !== null) deck.go(initial, 0, 'remote');
8
+ export function attachUrlSync(
9
+ deck: DeckController,
10
+ { param = "s", write }: UrlSync = {},
11
+ ): () => void {
12
+ const initial = parseSlideParam(
13
+ new URL(location.href).searchParams.get(param),
14
+ deck.entries.length,
15
+ );
16
+ if (initial !== null) deck.go(initial, 0, "remote");
11
17
  return deck.listen(({ index }) => {
12
18
  const url = new URL(location.href);
13
19
  url.searchParams.set(param, String(index + 1));
14
20
  if (write) write(url);
15
- else history.replaceState(history.state, '', url);
21
+ else history.replaceState(history.state, "", url);
16
22
  });
17
23
  }
18
24
 
@@ -20,48 +26,61 @@ export function attachUrlSync(deck: DeckController, { param = 's', write }: UrlS
20
26
  * Lets a host page drive a deck in an iframe. In: `{type:'deck:nav', delta}` and
21
27
  * `{type:'deck:go', index, step?}`. Out to the parent: `{type:'deck:change', index, step, total}`.
22
28
  */
23
- export function attachBridge(deck: DeckController, { origin = location.origin }: BridgeOptions = {}): () => void {
29
+ export function attachBridge(
30
+ deck: DeckController,
31
+ { origin = location.origin }: BridgeOptions = {},
32
+ ): () => void {
24
33
  const onMessage = (event: MessageEvent) => {
25
- if (origin !== '*' && event.origin !== origin) return;
34
+ if (origin !== "*" && event.origin !== origin) return;
26
35
  const data = event.data;
27
- if (data?.type === 'deck:nav') {
36
+ if (data?.type === "deck:nav") {
28
37
  if (data.delta === -1) deck.prev();
29
38
  else if (data.delta === 1) deck.next();
30
- } else if (data?.type === 'deck:go' && Number.isInteger(data.index)) {
31
- deck.go(data.index, Number.isInteger(data.step) ? data.step : 0, 'remote');
39
+ } else if (data?.type === "deck:go" && Number.isInteger(data.index)) {
40
+ deck.go(data.index, Number.isInteger(data.step) ? data.step : 0, "remote");
32
41
  }
33
42
  };
34
- window.addEventListener('message', onMessage);
43
+ window.addEventListener("message", onMessage);
35
44
  const stop = deck.listen(({ index, step }, source) => {
36
- if (source === 'local' && window.parent !== window) {
37
- window.parent.postMessage({ type: 'deck:change', index, step, total: deck.visible.length }, origin);
45
+ if (source === "local" && window.parent !== window) {
46
+ window.parent.postMessage(
47
+ { type: "deck:change", index, step, total: deck.visible.length },
48
+ origin,
49
+ );
38
50
  }
39
51
  });
40
52
  return () => {
41
- window.removeEventListener('message', onMessage);
53
+ window.removeEventListener("message", onMessage);
42
54
  stop();
43
55
  };
44
56
  }
45
57
 
46
- type ChannelMessage = { type: 'state'; from: string; index: number; step: number } | { type: 'hello'; from: string };
58
+ type ChannelMessage =
59
+ | { type: "state"; from: string; index: number; step: number }
60
+ | { type: "hello"; from: string };
47
61
 
48
62
  /** Mirrors the deck across windows of one origin: the audience screen and a presenter view. */
49
63
  export function attachChannel(deck: DeckController, name: string): () => void {
50
- if (typeof BroadcastChannel === 'undefined') return () => {};
64
+ if (typeof BroadcastChannel === "undefined") return () => {};
51
65
  const channel = new BroadcastChannel(`heyramzi-slides:${name}`);
52
66
  const self = crypto.randomUUID();
53
67
  const send = (message: ChannelMessage) => channel.postMessage(message);
54
- const state = ({ index, step } = deck.position): ChannelMessage => ({ type: 'state', from: self, index, step });
68
+ const state = ({ index, step } = deck.position): ChannelMessage => ({
69
+ type: "state",
70
+ from: self,
71
+ index,
72
+ step,
73
+ });
55
74
 
56
75
  channel.onmessage = ({ data }: MessageEvent<ChannelMessage>) => {
57
76
  if (data.from === self) return;
58
- if (data.type === 'hello') send(state());
59
- else deck.go(data.index, data.step, 'remote');
77
+ if (data.type === "hello") send(state());
78
+ else deck.go(data.index, data.step, "remote");
60
79
  };
61
80
  const stop = deck.listen((position, source) => {
62
- if (source === 'local') send(state(position));
81
+ if (source === "local") send(state(position));
63
82
  });
64
- send({ type: 'hello', from: self });
83
+ send({ type: "hello", from: self });
65
84
  return () => {
66
85
  stop();
67
86
  channel.close();