@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,7 +1,7 @@
1
1
  // Print a URL or HTML to PDF at a page size, and assemble captured PNGs into one.
2
2
 
3
- import { readFile } from 'node:fs/promises';
4
- import type { PageLike, ResponseLike, Size, WaitUntil } from './types.js';
3
+ import { readFile } from "node:fs/promises";
4
+ import type { PageLike, ResponseLike, Size, WaitUntil } from "./types.js";
5
5
 
6
6
  /** Chromium drops backgrounds in its print pipeline without the color-adjust line. */
7
7
  export const RESET_CSS = `*{margin:0;padding:0;box-sizing:border-box;}
@@ -15,14 +15,14 @@ export function pageRule(size: Size): string {
15
15
  /** The reset, the page size and one full-bleed `.page` box per image. */
16
16
  export function imagePagesCss(size: Size, background?: string): string {
17
17
  return `${RESET_CSS}
18
- ${background ? `body{background:${background};}\n` : ''}${pageRule(size)}
18
+ ${background ? `body{background:${background};}\n` : ""}${pageRule(size)}
19
19
  .page{display:block;width:${size.width}px;height:${size.height}px;break-after:page;}
20
20
  .page:last-child{break-after:auto;}`;
21
21
  }
22
22
 
23
23
  /** One `<img class="page">` per data URL, laid out so each prints on its own page. */
24
24
  export function imagePagesHtml(dataUrls: string[], size: Size, background?: string): string {
25
- const pages = dataUrls.map((dataUrl) => `<img class="page" src="${dataUrl}" alt="">`).join('');
25
+ const pages = dataUrls.map((dataUrl) => `<img class="page" src="${dataUrl}" alt="">`).join("");
26
26
  return `<!doctype html><html><head><meta charset="utf-8"><style>
27
27
  ${imagePagesCss(size, background)}
28
28
  </style></head><body>${pages}</body></html>`;
@@ -30,7 +30,11 @@ ${imagePagesCss(size, background)}
30
30
 
31
31
  /** The page count of a PDF, read off its page objects. */
32
32
  export function countPdfPages(pdf: Uint8Array): number {
33
- return (Buffer.from(pdf).toString('latin1').match(/\/Type\s*\/Page[^s]/g) ?? []).length;
33
+ return (
34
+ Buffer.from(pdf)
35
+ .toString("latin1")
36
+ .match(/\/Type\s*\/Page[^s]/g) ?? []
37
+ ).length;
34
38
  }
35
39
 
36
40
  export interface PrintOptions {
@@ -59,13 +63,12 @@ export interface PrintResult {
59
63
  }
60
64
 
61
65
  /**
62
- * Waits until fonts and images have settled and the caller's own signal has
63
- * fired, then prints the page as it stands. Returns the PDF with its page count
64
- * and the images that never loaded, so a caller can refuse a half-painted sheet.
66
+ * Prints the page once fonts, images and the caller's signal settle, returning the images
67
+ * that never loaded so a caller can refuse a half-painted sheet.
65
68
  */
66
69
  export async function printPage(page: PageLike, options: PrintOptions = {}): Promise<PrintResult> {
67
70
  const { ready } = options;
68
- if (typeof ready === 'string') {
71
+ if (typeof ready === "string") {
69
72
  await page.waitForSelector(ready, { timeout: options.readyTimeout ?? 60_000 });
70
73
  } else if (ready) {
71
74
  await ready(page);
@@ -73,7 +76,9 @@ export async function printPage(page: PageLike, options: PrintOptions = {}): Pro
73
76
  await page.evaluate(() => document.fonts.ready.then(() => undefined));
74
77
  if (options.settle) await page.waitForTimeout(options.settle);
75
78
  const brokenImages = await page.evaluate(() =>
76
- [...document.images].filter((img) => !img.complete || img.naturalWidth === 0).map((img) => img.src),
79
+ [...document.images]
80
+ .filter((img) => !img.complete || img.naturalWidth === 0)
81
+ .map((img) => img.src),
77
82
  );
78
83
  const { size } = options;
79
84
  const pdf = await page.pdf({
@@ -100,7 +105,7 @@ export async function printUrl(
100
105
  url: string,
101
106
  options: PrintNavigation = {},
102
107
  ): Promise<PrintResult> {
103
- const response = await page.goto(url, { waitUntil: options.waitUntil ?? 'load' });
108
+ const response = await page.goto(url, { waitUntil: options.waitUntil ?? "load" });
104
109
  options.check?.(response, page);
105
110
  return printPage(page, options);
106
111
  }
@@ -111,7 +116,7 @@ export async function printHtml(
111
116
  html: string,
112
117
  options: PrintNavigation = {},
113
118
  ): Promise<PrintResult> {
114
- await page.setContent(html, { waitUntil: options.waitUntil ?? 'load' });
119
+ await page.setContent(html, { waitUntil: options.waitUntil ?? "load" });
115
120
  return printPage(page, options);
116
121
  }
117
122
 
@@ -123,30 +128,28 @@ export interface ImagesToPdfOptions {
123
128
  }
124
129
 
125
130
  /**
126
- * Lays PNGs out one per page and prints that, nothing else.
127
- *
128
- * Printing the original document would run it through Chromium's print
129
- * pipeline, which re-rasterises effects instead of compositing them: a blurred
130
- * backdrop lost 47% of its chroma that way. Handing the pipeline flat bitmaps
131
- * removes the whole class of drift, so the PDF shows what the PNG shows.
132
- *
133
- * Each image is a file path or the bytes themselves.
131
+ * Prints PNGs one per page, never the original document, because Chromium's print pipeline
132
+ * re-rasterises effects: a blurred backdrop lost 47% of its chroma.
134
133
  */
135
134
  export async function imagesToPdf(
136
135
  page: PageLike,
137
136
  images: Array<string | Uint8Array>,
138
137
  options: ImagesToPdfOptions,
139
138
  ): Promise<Uint8Array> {
140
- if (images.length === 0) throw new Error('imagesToPdf needs at least one image.');
139
+ if (images.length === 0) throw new Error("imagesToPdf needs at least one image.");
141
140
  const dataUrls = await Promise.all(
142
141
  images.map(async (image) => {
143
- const bytes = typeof image === 'string' ? await readFile(image) : image;
144
- return `data:image/png;base64,${Buffer.from(bytes).toString('base64')}`;
142
+ const bytes = typeof image === "string" ? await readFile(image) : image;
143
+ return `data:image/png;base64,${Buffer.from(bytes).toString("base64")}`;
145
144
  }),
146
145
  );
147
- const { pdf } = await printHtml(page, imagePagesHtml(dataUrls, options.size, options.background), {
148
- size: options.size,
149
- pageRanges: `1-${images.length}`,
150
- });
146
+ const { pdf } = await printHtml(
147
+ page,
148
+ imagePagesHtml(dataUrls, options.size, options.background),
149
+ {
150
+ size: options.size,
151
+ pageRanges: `1-${images.length}`,
152
+ },
153
+ );
151
154
  return pdf;
152
155
  }
@@ -1,13 +1,12 @@
1
- // The slice of a Playwright page the export helpers touch, declared here so the
2
- // package takes no dependency on Playwright. Playwright's own Page, Locator and
3
- // Browser satisfy these as they are, whichever version the caller has installed.
1
+ // The slice of Playwright the export helpers touch, declared here so the package
2
+ // takes no Playwright dependency and any installed version fits.
4
3
 
5
4
  export interface Size {
6
5
  width: number;
7
6
  height: number;
8
7
  }
9
8
 
10
- export type WaitUntil = 'load' | 'domcontentloaded' | 'networkidle';
9
+ export type WaitUntil = "load" | "domcontentloaded" | "networkidle";
11
10
 
12
11
  export interface ResponseLike {
13
12
  ok(): boolean;
@@ -18,19 +17,22 @@ export interface LocatorLike {
18
17
  first(): LocatorLike;
19
18
  nth(index: number): LocatorLike;
20
19
  count(): Promise<number>;
21
- waitFor(options?: { state?: 'visible'; timeout?: number }): Promise<void>;
22
- screenshot(options?: { path?: string; type?: 'png' }): Promise<Uint8Array>;
20
+ waitFor(options?: { state?: "visible"; timeout?: number }): Promise<void>;
21
+ screenshot(options?: { path?: string; type?: "png" }): Promise<Uint8Array>;
23
22
  }
24
23
 
25
24
  export interface PageLike {
26
- goto(url: string, options?: { waitUntil?: WaitUntil; timeout?: number }): Promise<ResponseLike | null>;
25
+ goto(
26
+ url: string,
27
+ options?: { waitUntil?: WaitUntil; timeout?: number },
28
+ ): Promise<ResponseLike | null>;
27
29
  setContent(html: string, options?: { waitUntil?: WaitUntil; timeout?: number }): Promise<void>;
28
30
  addStyleTag(options: { content: string }): Promise<unknown>;
29
31
  evaluate<R>(pageFunction: () => R | Promise<R>): Promise<R>;
30
32
  waitForTimeout(ms: number): Promise<void>;
31
33
  waitForSelector(selector: string, options?: { timeout?: number }): Promise<unknown>;
32
34
  locator(selector: string): LocatorLike;
33
- screenshot(options?: { type?: 'png'; fullPage?: boolean }): Promise<Uint8Array>;
35
+ screenshot(options?: { type?: "png"; fullPage?: boolean }): Promise<Uint8Array>;
34
36
  pdf(options: {
35
37
  path?: string;
36
38
  width?: string;
@@ -1,12 +1,12 @@
1
- export { default as Deck } from './Deck.svelte';
2
- export { default as Slide } from './Slide.svelte';
3
- export { default as Fragment } from './Fragment.svelte';
4
- export { default as Presenter } from './Presenter.svelte';
5
- export { DeckController } from './deck-controller.svelte.js';
6
- export type { SlideEntry, Source } from './deck-controller.svelte.js';
7
- export { defaultLabels } from './labels.js';
8
- export type { DeckLabels } from './labels.js';
9
- export type { Position } from './navigation.js';
1
+ export { default as Deck } from "./Deck.svelte";
2
+ export { default as Slide } from "./Slide.svelte";
3
+ export { default as Fragment } from "./Fragment.svelte";
4
+ export { default as Presenter } from "./Presenter.svelte";
5
+ export { DeckController } from "./deck-controller.svelte.js";
6
+ export type { SlideEntry, Source } from "./deck-controller.svelte.js";
7
+ export { defaultLabels } from "./labels.js";
8
+ export type { DeckLabels } from "./labels.js";
9
+ export type { Position } from "./navigation.js";
10
10
  export type {
11
11
  BridgeOptions,
12
12
  Canvas,
@@ -15,5 +15,5 @@ export type {
15
15
  SlideAlign,
16
16
  SlideNotes,
17
17
  UrlSync,
18
- } from './types.js';
19
- export { DEFAULT_CANVAS } from './types.js';
18
+ } from "./types.js";
19
+ export { DEFAULT_CANVAS } from "./types.js";
@@ -23,26 +23,29 @@ export type DeckLabels = {
23
23
  };
24
24
 
25
25
  export const defaultLabels: DeckLabels = {
26
- deck: 'Slide deck',
27
- deckRole: 'presentation',
28
- slideRole: 'slide',
29
- previous: 'Previous slide',
30
- next: 'Next slide',
31
- fullscreen: 'Toggle fullscreen',
32
- overview: 'Slide overview',
33
- presenter: 'Open presenter view',
34
- progress: 'Deck progress',
26
+ deck: "Slide deck",
27
+ deckRole: "presentation",
28
+ slideRole: "slide",
29
+ previous: "Previous slide",
30
+ next: "Next slide",
31
+ fullscreen: "Toggle fullscreen",
32
+ overview: "Slide overview",
33
+ presenter: "Open presenter view",
34
+ progress: "Deck progress",
35
35
  slide: (n, of) => `Slide ${n} of ${of}`,
36
- upcoming: 'Next',
37
- current: 'Now',
38
- notes: 'Notes',
39
- noNotes: 'No notes for this slide.',
40
- endOfDeck: 'End of deck.',
41
- timer: 'Elapsed time',
42
- startTimer: 'Start timer',
43
- pauseTimer: 'Pause timer',
44
- resetTimer: 'Reset timer',
36
+ upcoming: "Next",
37
+ current: "Now",
38
+ notes: "Notes",
39
+ noNotes: "No notes for this slide.",
40
+ endOfDeck: "End of deck.",
41
+ timer: "Elapsed time",
42
+ startTimer: "Start timer",
43
+ pauseTimer: "Pause timer",
44
+ resetTimer: "Reset timer",
45
45
  unknownType: (type) => `Unknown slide type: ${type}`,
46
46
  };
47
47
 
48
- export const resolveLabels = (labels?: Partial<DeckLabels>): DeckLabels => ({ ...defaultLabels, ...labels });
48
+ export const resolveLabels = (labels?: Partial<DeckLabels>): DeckLabels => ({
49
+ ...defaultLabels,
50
+ ...labels,
51
+ });
@@ -1,6 +1,6 @@
1
1
  /* Where a slide sits inside the stage: centred to fit, or in the overview grid. */
2
2
 
3
- import type { Canvas } from './types.js';
3
+ import type { Canvas } from "./types.js";
4
4
 
5
5
  export type Size = { w: number; h: number };
6
6
 
@@ -15,7 +15,14 @@ export function fitBox(view: Size, canvas: Canvas): Box {
15
15
  }
16
16
 
17
17
  /** The scale at which `cols` columns of `count` canvases fit the view. */
18
- function scaleFor(cols: number, count: number, view: Size, canvas: Canvas, pad: number, gap: number): number {
18
+ function scaleFor(
19
+ cols: number,
20
+ count: number,
21
+ view: Size,
22
+ canvas: Canvas,
23
+ pad: number,
24
+ gap: number,
25
+ ): number {
19
26
  const rows = Math.ceil(count / cols);
20
27
  const byWidth = (view.w - 2 * pad - (cols - 1) * gap) / cols / canvas.w;
21
28
  const byHeight = (view.h - 2 * pad - (rows - 1) * gap) / rows / canvas.h;
@@ -1,7 +1,7 @@
1
1
  /* Data-driven decks: a deck is JSON, and a registry says how each slide type draws itself. */
2
2
 
3
- import type { Component } from 'svelte';
4
- import type { Canvas, DeckTheme } from './types.js';
3
+ import type { Component } from "svelte";
4
+ import type { Canvas, DeckTheme } from "./types.js";
5
5
 
6
6
  export type SlideInstance<Content = unknown> = {
7
7
  id: string;
@@ -33,7 +33,8 @@ export type SlideType<Content = any> = {
33
33
  export type SlideRegistry = Readonly<Record<string, SlideType>>;
34
34
 
35
35
  /** Identity function that fixes `Content` from the layout. */
36
- export const defineSlideType = <Content>(definition: SlideType<Content>): SlideType<Content> => definition;
36
+ export const defineSlideType = <Content>(definition: SlideType<Content>): SlideType<Content> =>
37
+ definition;
37
38
 
38
39
  /** Keys the types by their `type`. Two with the same name is a mistake worth failing on. */
39
40
  export function defineRegistry(types: readonly SlideType[]): SlideRegistry {
@@ -44,4 +45,3 @@ export function defineRegistry(types: readonly SlideType[]): SlideRegistry {
44
45
  }
45
46
  return registry;
46
47
  }
47
-
@@ -6,17 +6,17 @@ export type Position = { index: number; step: number };
6
6
  export type SlideState = { steps: number; hidden: boolean };
7
7
 
8
8
  export type KeyAction =
9
- | 'next'
10
- | 'prev'
11
- | 'up'
12
- | 'down'
13
- | 'first'
14
- | 'last'
15
- | 'select'
16
- | 'overview'
17
- | 'close'
18
- | 'fullscreen'
19
- | 'presenter';
9
+ | "next"
10
+ | "prev"
11
+ | "up"
12
+ | "down"
13
+ | "first"
14
+ | "last"
15
+ | "select"
16
+ | "overview"
17
+ | "close"
18
+ | "fullscreen"
19
+ | "presenter";
20
20
 
21
21
  export const visibleIndexes = (slides: readonly SlideState[]): number[] =>
22
22
  slides.flatMap((slide, index) => (slide.hidden ? [] : [index]));
@@ -32,7 +32,11 @@ export function settle(slides: readonly SlideState[], position: Position): Posit
32
32
  }
33
33
 
34
34
  /** The position after one forward press, or null at the end of a deck that doesn't loop. */
35
- export function nextPosition(slides: readonly SlideState[], from: Position, loop = false): Position | null {
35
+ export function nextPosition(
36
+ slides: readonly SlideState[],
37
+ from: Position,
38
+ loop = false,
39
+ ): Position | null {
36
40
  const visible = visibleIndexes(slides);
37
41
  if (visible.length === 0) return null;
38
42
  if (from.step < slides[from.index].steps) return { index: from.index, step: from.step + 1 };
@@ -42,12 +46,20 @@ export function nextPosition(slides: readonly SlideState[], from: Position, loop
42
46
  }
43
47
 
44
48
  /** The position after one back press. Landing on an earlier slide shows it fully revealed. */
45
- export function prevPosition(slides: readonly SlideState[], from: Position, loop = false): Position | null {
49
+ export function prevPosition(
50
+ slides: readonly SlideState[],
51
+ from: Position,
52
+ loop = false,
53
+ ): Position | null {
46
54
  const visible = visibleIndexes(slides);
47
55
  if (visible.length === 0) return null;
48
56
  if (from.step > 0) return { index: from.index, step: from.step - 1 };
49
57
  const before = visible.filter((i) => i < from.index);
50
- const index = before.length ? before[before.length - 1] : loop ? visible[visible.length - 1] : undefined;
58
+ const index = before.length
59
+ ? before[before.length - 1]
60
+ : loop
61
+ ? visible[visible.length - 1]
62
+ : undefined;
51
63
  return index === undefined ? null : { index, step: slides[index].steps };
52
64
  }
53
65
 
@@ -75,26 +87,26 @@ export function parseSlideParam(raw: string | null | undefined, total: number):
75
87
  }
76
88
 
77
89
  const KEYS: Record<string, KeyAction> = {
78
- ArrowRight: 'next',
79
- PageDown: 'next',
80
- ArrowLeft: 'prev',
81
- PageUp: 'prev',
82
- ArrowUp: 'up',
83
- ArrowDown: 'down',
84
- Home: 'first',
85
- End: 'last',
86
- Enter: 'select',
87
- Escape: 'close',
88
- o: 'overview',
89
- O: 'overview',
90
- f: 'fullscreen',
91
- F: 'fullscreen',
92
- p: 'presenter',
93
- P: 'presenter',
90
+ ArrowRight: "next",
91
+ PageDown: "next",
92
+ ArrowLeft: "prev",
93
+ PageUp: "prev",
94
+ ArrowUp: "up",
95
+ ArrowDown: "down",
96
+ Home: "first",
97
+ End: "last",
98
+ Enter: "select",
99
+ Escape: "close",
100
+ o: "overview",
101
+ O: "overview",
102
+ f: "fullscreen",
103
+ F: "fullscreen",
104
+ p: "presenter",
105
+ P: "presenter",
94
106
  };
95
107
 
96
108
  /** Maps a key to what the deck does with it. Space moves forward, Shift+Space back. */
97
109
  export function keyAction(key: string, shift = false): KeyAction | null {
98
- if (key === ' ') return shift ? 'prev' : 'next';
110
+ if (key === " ") return shift ? "prev" : "next";
99
111
  return KEYS[key] ?? null;
100
112
  }
@@ -1,8 +1,6 @@
1
1
  /*
2
- * Everything global to a deck: tokens, authored type, the enter keyframes and print.
3
- * Components keep their own box mechanics in scoped styles. Sizes are design px,
4
- * because the whole canvas scales, so a 28px heading is 28px on a 960px canvas at any zoom.
5
- * Colours are flat hex: Chromium's PDF pipeline shifts oklch.
2
+ * Everything global to a deck, in design px since the whole canvas scales, and in flat
3
+ * hex because Chromium's PDF pipeline shifts oklch.
6
4
  */
7
5
 
8
6
  .deck,
@@ -119,13 +117,13 @@
119
117
  transform: scale(var(--scale-large, 0.96));
120
118
  }
121
119
  }
122
- .deck[data-transition='fade'] .slide[data-active='true'] .slide__surface {
120
+ .deck[data-transition="fade"] .slide[data-active="true"] .slide__surface {
123
121
  animation-name: deck-fade;
124
122
  }
125
- .deck[data-transition='slide'] .slide[data-active='true'] .slide__surface {
123
+ .deck[data-transition="slide"] .slide[data-active="true"] .slide__surface {
126
124
  animation-name: deck-slide;
127
125
  }
128
- .deck[data-transition='zoom'] .slide[data-active='true'] .slide__surface {
126
+ .deck[data-transition="zoom"] .slide[data-active="true"] .slide__surface {
129
127
  animation-name: deck-zoom;
130
128
  }
131
129
  .deck[data-morphing] .slide__surface {
@@ -1,16 +1,16 @@
1
- import type { Snippet } from 'svelte';
2
- import type { DeckController } from './deck-controller.svelte.js';
3
- import type { DeckLabels } from './labels.js';
4
- import type { SlideInstance, SlideRegistry } from './model.js';
1
+ import type { Snippet } from "svelte";
2
+ import type { DeckController } from "./deck-controller.svelte.js";
3
+ import type { DeckLabels } from "./labels.js";
4
+ import type { SlideInstance, SlideRegistry } from "./model.js";
5
5
 
6
- export const DECK_KEY = Symbol('heyramzi.deck');
7
- export const SLIDE_KEY = Symbol('heyramzi.slide');
6
+ export const DECK_KEY = Symbol("heyramzi.deck");
7
+ export const SLIDE_KEY = Symbol("heyramzi.slide");
8
8
 
9
9
  /** `ink` is the dark default, `paper` a light surface, `auto` follows the system. */
10
- export type DeckTheme = 'ink' | 'paper' | 'auto';
10
+ export type DeckTheme = "ink" | "paper" | "auto";
11
11
 
12
12
  /** How a slide enters. `morph` uses the View Transitions API and falls back to a cut. */
13
- export type DeckTransition = 'fade' | 'slide' | 'zoom' | 'morph' | 'none';
13
+ export type DeckTransition = "fade" | "slide" | "zoom" | "morph" | "none";
14
14
 
15
15
  /** The design size in px. Slides lay out inside it and the whole canvas scales to fit. */
16
16
  export type Canvas = { w: number; h: number };
@@ -21,7 +21,7 @@ export const DEFAULT_CANVAS: Canvas = { w: 960, h: 540 };
21
21
  export type SlideNotes = string | Snippet;
22
22
 
23
23
  /** How a Slide lays out its own content. `bare` leaves the whole canvas to the author. */
24
- export type SlideAlign = 'center' | 'start' | 'bare';
24
+ export type SlideAlign = "center" | "start" | "bare";
25
25
 
26
26
  /** Where a deck keeps `?s=`. `write` replaces the default `history.replaceState`, so SvelteKit apps can use its own. */
27
27
  export type UrlSync = { param?: string; write?: (url: URL) => void };
@@ -83,4 +83,3 @@ export type DeckProps = {
83
83
  onchange?: (index: number, step: number) => void;
84
84
  children?: Snippet;
85
85
  };
86
-
@@ -1,32 +1,25 @@
1
1
  import type { StatusDef, StatusId, StatusMap, Statuses } from "./status-types.js";
2
2
 
3
3
  /**
4
- * Build a status registry from a plain map. The registry keeps the map's keys as
5
- * a union, so `statuses.ids` and lookups stay typed.
6
- *
7
- * ```ts
8
- * const statuses = defineStatuses({
9
- * draft: { label: "Draft", tone: "neutral" },
10
- * sent: { label: "Sent", tone: "info" },
11
- * });
12
- * ```
4
+ * Builds a status registry from a map, keeping its keys as a union so lookups stay typed:
5
+ * `defineStatuses({ draft: { label: "Draft", tone: "neutral" } })`.
13
6
  */
14
7
  export function defineStatuses<const M extends StatusMap>(
15
- map: M,
8
+ map: M,
16
9
  ): Statuses & { ids: StatusId<M>[] } {
17
- const lookup = map as Record<string, StatusDef | undefined>;
18
- const find = (id: string): StatusDef | undefined => lookup[id];
10
+ const lookup = map as Record<string, StatusDef | undefined>;
11
+ const find = (id: string): StatusDef | undefined => lookup[id];
19
12
 
20
- return {
21
- map,
22
- ids: Object.keys(map) as StatusId<M>[],
23
- has: (id) => find(id) !== undefined,
24
- get: find,
25
- label: (id) => find(id)?.label ?? id,
26
- tone: (id) => find(id)?.tone ?? "neutral",
27
- icon: (id) => find(id)?.icon,
28
- variant: (id) => find(id)?.variant ?? "open",
29
- progress: (id) => find(id)?.progress,
30
- color: (id) => find(id)?.color,
31
- };
13
+ return {
14
+ map,
15
+ ids: Object.keys(map) as StatusId<M>[],
16
+ has: (id) => find(id) !== undefined,
17
+ get: find,
18
+ label: (id) => find(id)?.label ?? id,
19
+ tone: (id) => find(id)?.tone ?? "neutral",
20
+ icon: (id) => find(id)?.icon,
21
+ variant: (id) => find(id)?.variant ?? "open",
22
+ progress: (id) => find(id)?.progress,
23
+ color: (id) => find(id)?.color,
24
+ };
32
25
  }
@@ -3,9 +3,9 @@ export { default as StatusDot } from "./status-dot.svelte";
3
3
  export { default as StatusIcon } from "./status-icon.svelte";
4
4
  export { defineStatuses } from "./define-statuses.js";
5
5
  export type {
6
- StatusDef,
7
- StatusDotVariant,
8
- StatusId,
9
- StatusMap,
10
- Statuses,
6
+ StatusDef,
7
+ StatusDotVariant,
8
+ StatusId,
9
+ StatusMap,
10
+ Statuses,
11
11
  } from "./status-types.js";
@@ -41,7 +41,9 @@
41
41
  }: Props = $props();
42
42
 
43
43
  let resolvedTone = $derived(tone ?? (statuses && status ? statuses.tone(status) : "neutral"));
44
- let resolvedLabel = $derived(label ?? (statuses && status ? statuses.label(status) : (status ?? "")));
44
+ let resolvedLabel = $derived(
45
+ label ?? (statuses && status ? statuses.label(status) : (status ?? "")),
46
+ );
45
47
  </script>
46
48
 
47
49
  <Badge tone={resolvedTone} class={cn(capitalize && "capitalize", className)} {...restProps}>
@@ -52,9 +52,7 @@
52
52
  : undefined,
53
53
  );
54
54
  let dasharray = $derived(
55
- fraction != null
56
- ? `${INNER_CIRC * fraction} ${INNER_CIRC * (1 - fraction)}`
57
- : undefined,
55
+ fraction != null ? `${INNER_CIRC * fraction} ${INNER_CIRC * (1 - fraction)}` : undefined,
58
56
  );
59
57
  let dashoffset = $derived(fraction != null ? INNER_CIRC * 0.25 : undefined);
60
58
  </script>
@@ -11,7 +11,7 @@
11
11
  size?: number;
12
12
  animate?: boolean;
13
13
  class?: string;
14
- };
14
+ }
15
15
 
16
16
  let { statuses, status, size = 14, animate = true, class: className }: Props = $props();
17
17
 
@@ -8,16 +8,16 @@ import type { Tone } from "../lib/tone.js";
8
8
  export type StatusDotVariant = "open" | "dashed" | "progress" | "filled" | "completed" | "lost";
9
9
 
10
10
  export type StatusDef = {
11
- label: string;
12
- tone?: Tone;
13
- /** Any Svelte component, for example a Phosphor icon. Renders inside StatusIcon. */
14
- icon?: Component;
15
- variant?: StatusDotVariant;
16
- /** 0 to 1. Only used by the `progress` variant. */
17
- progress?: number;
18
- /** Optional CSS color. Overrides the tone color for the dot and icon. */
19
- color?: string;
20
- description?: string;
11
+ label: string;
12
+ tone?: Tone;
13
+ /** Any Svelte component, for example a Phosphor icon. Renders inside StatusIcon. */
14
+ icon?: Component;
15
+ variant?: StatusDotVariant;
16
+ /** 0 to 1. Only used by the `progress` variant. */
17
+ progress?: number;
18
+ /** Optional CSS color. Overrides the tone color for the dot and icon. */
19
+ color?: string;
20
+ description?: string;
21
21
  };
22
22
 
23
23
  export type StatusMap = Record<string, StatusDef>;
@@ -26,15 +26,15 @@ export type StatusId<M extends StatusMap> = keyof M & string;
26
26
 
27
27
  /** What the components accept. `defineStatuses` returns this plus a typed `ids`. */
28
28
  export type Statuses = {
29
- /** The raw map. Kept loose so a registry with specific keys is assignable here. */
30
- map: StatusMap;
31
- ids: string[];
32
- has(id: string): boolean;
33
- get(id: string): StatusDef | undefined;
34
- label(id: string): string;
35
- tone(id: string): Tone;
36
- icon(id: string): Component | undefined;
37
- variant(id: string): StatusDotVariant;
38
- progress(id: string): number | undefined;
39
- color(id: string): string | undefined;
29
+ /** The raw map. Kept loose so a registry with specific keys is assignable here. */
30
+ map: StatusMap;
31
+ ids: string[];
32
+ has(id: string): boolean;
33
+ get(id: string): StatusDef | undefined;
34
+ label(id: string): string;
35
+ tone(id: string): Tone;
36
+ icon(id: string): Component | undefined;
37
+ variant(id: string): StatusDotVariant;
38
+ progress(id: string): number | undefined;
39
+ color(id: string): string | undefined;
40
40
  };
@@ -1,7 +1,5 @@
1
- // Fractional indexing: an order key that always sorts between its neighbours,
2
- // so a move writes one row instead of renumbering a column. The algorithm is
3
- // the CC0 one from https://observablehq.com/@dgreensp/implementing-fractional-indexing
4
- // (see the README credit). Kept inline so this package carries no runtime deps.
1
+ // Fractional indexing, so a move writes one row instead of renumbering a column; it's the
2
+ // CC0 algorithm from https://observablehq.com/@dgreensp/implementing-fractional-indexing.
5
3
  const DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
6
4
  const INT_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
7
5
  const ZERO = DIGITS.charAt(0);