foldkit 0.95.1 → 0.97.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 (52) hide show
  1. package/dist/devTools/overlay-styles.d.ts +1 -1
  2. package/dist/devTools/overlay-styles.d.ts.map +1 -1
  3. package/dist/devTools/overlay-styles.js +30 -0
  4. package/dist/devTools/overlay.d.ts.map +1 -1
  5. package/dist/devTools/overlay.js +60 -8
  6. package/dist/dom/dom.d.ts +53 -3
  7. package/dist/dom/dom.d.ts.map +1 -1
  8. package/dist/dom/dom.js +56 -6
  9. package/dist/dom/index.d.ts +1 -1
  10. package/dist/dom/index.d.ts.map +1 -1
  11. package/dist/dom/index.js +1 -1
  12. package/dist/dom/public.d.ts +1 -1
  13. package/dist/dom/public.d.ts.map +1 -1
  14. package/dist/dom/public.js +1 -1
  15. package/dist/html/index.d.ts +4 -4
  16. package/dist/html/index.d.ts.map +1 -1
  17. package/dist/html/index.js +5 -3
  18. package/dist/runtime/runtime.js +7 -7
  19. package/dist/runtime/subscription.d.ts +7 -7
  20. package/dist/runtime/subscription.d.ts.map +1 -1
  21. package/dist/runtime/subscription.js +2 -2
  22. package/dist/subscription/animationFrame.d.ts +3 -3
  23. package/dist/subscription/animationFrame.js +3 -3
  24. package/dist/test/apps/counter.d.ts +2 -0
  25. package/dist/test/apps/counter.d.ts.map +1 -1
  26. package/dist/test/apps/counter.js +8 -0
  27. package/dist/test/internal.d.ts +6 -0
  28. package/dist/test/internal.d.ts.map +1 -1
  29. package/dist/test/internal.js +14 -0
  30. package/dist/test/scene.d.ts.map +1 -1
  31. package/dist/test/scene.js +2 -1
  32. package/dist/test/story.d.ts.map +1 -1
  33. package/dist/test/story.js +2 -1
  34. package/dist/ui/dragAndDrop/index.d.ts +1 -1
  35. package/dist/ui/dragAndDrop/index.d.ts.map +1 -1
  36. package/dist/ui/dragAndDrop/index.js +2 -2
  37. package/dist/ui/dragAndDrop/public.d.ts +1 -1
  38. package/dist/ui/dragAndDrop/public.d.ts.map +1 -1
  39. package/dist/ui/dragAndDrop/public.js +1 -1
  40. package/dist/ui/slider/index.d.ts +1 -1
  41. package/dist/ui/slider/index.d.ts.map +1 -1
  42. package/dist/ui/slider/index.js +2 -2
  43. package/dist/ui/slider/public.d.ts +1 -1
  44. package/dist/ui/slider/public.d.ts.map +1 -1
  45. package/dist/ui/slider/public.js +1 -1
  46. package/dist/ui/virtualList/index.d.ts +2 -2
  47. package/dist/ui/virtualList/index.d.ts.map +1 -1
  48. package/dist/ui/virtualList/index.js +2 -2
  49. package/dist/ui/virtualList/public.d.ts +1 -1
  50. package/dist/ui/virtualList/public.d.ts.map +1 -1
  51. package/dist/ui/virtualList/public.js +1 -1
  52. package/package.json +1 -1
@@ -1,3 +1,3 @@
1
- declare const overlayStyles = ":host {\n position: relative;\n z-index: 2147483647;\n\n --dt-bg: #1e1e2e;\n --dt-surface-selected: #282839;\n --dt-border: #45475a;\n --dt-text: #cdd6f4;\n --dt-text-muted: #9399b2;\n --dt-accent: #cba6f7;\n --dt-live: #a6e3a1;\n --dt-paused: #fab387;\n --dt-json-string: #a6e3a1;\n --dt-json-number: #89b4fa;\n --dt-json-boolean: #fab387;\n --dt-json-null: #9399b2;\n --dt-json-key: #89dceb;\n --dt-json-tag: #cba6f7;\n --dt-json-preview: #9399b2;\n --dt-json-arrow: #9399b2;\n --dt-tree-hover: #313244;\n --dt-diff-changed: #74c7ec;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\nbutton {\n font: inherit;\n color: inherit;\n}\nul {\n list-style: none;\n}\n\n.fixed {\n position: fixed;\n}\n.flex {\n display: flex;\n}\n.flex-col {\n flex-direction: column;\n}\n.flex-1 {\n flex: 1 1 0%;\n}\n.items-center {\n align-items: center;\n}\n.justify-center {\n justify-content: center;\n}\n.justify-between {\n justify-content: space-between;\n}\n.shrink-0 {\n flex-shrink: 0;\n}\n.inline-block {\n display: inline-block;\n}\n.gap-0\\.5 {\n gap: 2px;\n}\n.gap-1\\.5 {\n gap: 6px;\n}\n.gap-2 {\n gap: 8px;\n}\n.gap-3 {\n gap: 12px;\n}\n.gap-px {\n gap: 1px;\n}\n.px-1 {\n padding-left: 4px;\n padding-right: 4px;\n}\n.px-2 {\n padding-left: 8px;\n padding-right: 8px;\n}\n.px-2\\.5 {\n padding-left: 10px;\n padding-right: 10px;\n}\n.p-3 {\n padding: 12px;\n}\n.px-3 {\n padding-left: 12px;\n padding-right: 12px;\n}\n.py-0\\.5 {\n padding-top: 2px;\n padding-bottom: 2px;\n}\n.pt-1 {\n padding-top: 4px;\n}\n.pl-1 {\n padding-left: 4px;\n}\n.py-1 {\n padding-top: 4px;\n padding-bottom: 4px;\n}\n.py-1\\.5 {\n padding-top: 6px;\n padding-bottom: 6px;\n}\n.py-2 {\n padding-top: 8px;\n padding-bottom: 8px;\n}\n.py-px {\n padding-top: 1px;\n padding-bottom: 1px;\n}\n.w-1\\.5 {\n width: 6px;\n}\n.h-1\\.5 {\n height: 6px;\n}\n.w-3 {\n width: 12px;\n}\n.w-5 {\n width: 20px;\n}\n.h-5 {\n height: 20px;\n}\n.w-14 {\n width: 56px;\n}\n.h-14 {\n height: 56px;\n}\n.min-w-0 {\n min-width: 0;\n}\n.min-w-5 {\n min-width: 20px;\n}\n.min-h-0 {\n min-height: 0;\n}\n/* Badge positions \u2014 flush against side edge */\n.dt-pos-br {\n bottom: 16px;\n right: 0;\n border-radius: 6px 0 0 6px;\n}\n.dt-pos-bl {\n bottom: 16px;\n left: 0;\n border-radius: 0 6px 6px 0;\n}\n.dt-pos-tr {\n top: 16px;\n right: 0;\n border-radius: 6px 0 0 6px;\n}\n.dt-pos-tl {\n top: 16px;\n left: 0;\n border-radius: 0 6px 6px 0;\n}\n.overflow-hidden {\n overflow: hidden;\n}\n.overflow-auto {\n overflow: auto;\n}\n.overflow-y-auto {\n overflow-y: auto;\n}\n.overscroll-none {\n overscroll-behavior: none;\n}\n\n.truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.rounded {\n border-radius: 4px;\n}\n.rounded-lg {\n border-radius: 8px;\n}\n.rounded-full {\n border-radius: 9999px;\n}\n.border {\n border-width: 1px;\n border-style: solid;\n border-color: var(--dt-border);\n}\n.border-b {\n border-bottom: 1px solid var(--dt-border);\n}\n.border-t {\n border-top: 1px solid var(--dt-border);\n}\n.border-r {\n border-right: 1px solid var(--dt-border);\n}\n.border-l {\n border-left: 1px solid var(--dt-border);\n}\n.border-none {\n border: none;\n}\n.selected {\n background-color: var(--dt-surface-selected);\n}\n.dt-row:hover:not(.selected) {\n background-color: var(--dt-tree-hover);\n}\n.dt-header-button:hover {\n color: var(--dt-text);\n}\n.dt-resume-button:hover {\n opacity: 0.7;\n}\n.dt-filter-wrapper {\n position: relative;\n flex-shrink: 0;\n border-bottom: 1px solid var(--dt-border);\n}\n.dt-filter-button {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n background: transparent;\n border: none;\n color: var(--dt-text-muted);\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n font-size: 13px;\n cursor: pointer;\n text-align: left;\n}\n.dt-filter-button:hover {\n color: var(--dt-text);\n background-color: var(--dt-tree-hover);\n}\n.dt-filter-button:focus-visible {\n outline: 1px solid var(--dt-accent);\n outline-offset: -1px;\n}\n.dt-filter-button[data-open] {\n color: var(--dt-text);\n background-color: var(--dt-surface-selected);\n}\n.dt-filter-button[data-open]:hover {\n background-color: var(--dt-tree-hover);\n}\n.dt-filter-button[data-open] .json-arrow {\n transform: rotate(180deg);\n}\n.dt-filter-items {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background-color: var(--dt-bg);\n border-top: 1px solid var(--dt-border);\n border-bottom: 1px solid var(--dt-border);\n z-index: 10;\n max-height: 200px;\n overflow-y: auto;\n outline: none;\n}\n.dt-filter-item {\n padding: 6px 12px;\n color: var(--dt-text-muted);\n cursor: pointer;\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n font-size: 13px;\n border-bottom: 1px solid var(--dt-border);\n}\n.dt-filter-item:last-child {\n border-bottom: none;\n}\n.dt-filter-item[data-active] {\n background-color: var(--dt-tree-hover);\n color: var(--dt-text);\n}\n.dt-filter-item[data-selected] {\n color: var(--dt-accent);\n}\n.dt-filter-check {\n width: 12px;\n height: 12px;\n visibility: hidden;\n}\n.dt-filter-item[data-selected] .dt-filter-check {\n visibility: visible;\n color: var(--dt-accent);\n}\n.dt-filter-backdrop {\n position: fixed;\n inset: 0;\n pointer-events: none;\n}\n.dt-tab-button {\n position: relative;\n background: transparent;\n border: none;\n border-right: 1px solid var(--dt-border);\n outline: none;\n flex: 1;\n}\n.dt-tab-button:last-child {\n border-right: none;\n}\n.dt-tab-active {\n background-color: var(--dt-surface-selected);\n}\n.dt-tab-button:not(.dt-tab-active):hover {\n color: var(--dt-text);\n background-color: rgba(49, 50, 68, 0.3);\n}\n.font-sans {\n font-family:\n system-ui,\n -apple-system,\n BlinkMacSystemFont,\n 'Segoe UI',\n Roboto,\n sans-serif;\n}\n.font-mono {\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n}\n.font-medium {\n font-weight: 500;\n}\n.font-semibold {\n font-weight: 600;\n}\n.text-xs {\n font-size: 12px;\n}\n.text-2xs {\n font-size: 10px;\n}\n.text-sm {\n font-size: 11px;\n}\n.text-base {\n font-size: 13px;\n}\n.text-md {\n font-size: 15px;\n}\n.text-lg {\n font-size: 20px;\n}\n.text-xl {\n font-size: 26px;\n}\n.italic {\n font-style: italic;\n}\n.text-right {\n text-align: right;\n}\n.tracking-wide {\n letter-spacing: 0.025em;\n}\n.tracking-wider {\n letter-spacing: 0.05em;\n}\n.leading-none {\n line-height: 1;\n}\n.leading-snug {\n line-height: 1.35;\n}\n.bg-dt-bg {\n background-color: var(--dt-bg);\n}\n.bg-dt-live {\n background-color: var(--dt-live);\n}\n.bg-transparent {\n background-color: transparent;\n}\n.text-dt {\n color: var(--dt-text);\n}\n.text-dt-bg {\n color: var(--dt-bg);\n}\n.text-dt-muted {\n color: var(--dt-text-muted);\n}\n.text-dt-accent {\n color: var(--dt-accent);\n}\n.text-dt-live {\n color: var(--dt-live);\n}\n.text-dt-paused {\n color: var(--dt-paused);\n}\n.cursor-pointer {\n cursor: pointer;\n}\n.outline-none {\n outline: none;\n}\n.transition-colors {\n transition-property: color, background-color, border-color;\n transition-duration: 100ms;\n transition-timing-function: ease;\n}\n\n/* Panel */\n.dt-panel {\n width: 360px;\n height: 480px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n z-index: 99998;\n}\n/* Panel positions */\n.dt-panel-br {\n bottom: 16px;\n right: 28px;\n}\n.dt-panel-bl {\n bottom: 16px;\n left: 28px;\n}\n.dt-panel-tr {\n top: 16px;\n right: 28px;\n}\n.dt-panel-tl {\n top: 16px;\n left: 28px;\n}\n.dt-panel-wide {\n width: 720px;\n}\n.dt-message-pane {\n width: 320px;\n flex-shrink: 0;\n}\n.dt-badge {\n z-index: 99999;\n box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);\n transition: background-color 150ms ease;\n border: 1px solid var(--dt-border);\n}\n.dt-badge-accent:hover {\n background-color: #252538;\n}\n.dt-badge-paused {\n background-color: var(--dt-paused);\n color: var(--dt-bg);\n border: none;\n}\n.dt-badge-paused:hover {\n background-color: #e0a070;\n}\n.dt-badge.dt-pos-br,\n.dt-badge.dt-pos-tr {\n border-right: none;\n}\n.dt-badge.dt-pos-bl,\n.dt-badge.dt-pos-tl {\n border-left: none;\n}\n\n/* JSON tree */\n.tree-row {\n position: relative;\n white-space: nowrap;\n line-height: 18px;\n padding-right: 8px;\n}\n.tree-row-expandable:hover {\n background-color: var(--dt-tree-hover);\n}\n.inspector-tree {\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n}\n.json-key {\n color: var(--dt-json-key);\n}\n.json-string {\n color: var(--dt-json-string);\n}\n.json-number {\n color: var(--dt-json-number);\n}\n.json-boolean {\n color: var(--dt-json-boolean);\n}\n.json-null {\n color: var(--dt-json-null);\n}\n.json-tag {\n color: var(--dt-json-tag);\n margin-right: 4px;\n}\n.json-preview {\n color: var(--dt-json-preview);\n}\n.json-arrow {\n color: var(--dt-json-arrow);\n width: 10px;\n height: 10px;\n user-select: none;\n}\n\n/* Diff */\n.diff-changed {\n background-color: rgba(116, 199, 236, 0.06);\n}\n.diff-dot {\n position: absolute;\n left: 3px;\n width: 5px;\n height: 5px;\n border-radius: 9999px;\n background-color: var(--dt-diff-changed);\n}\n.diff-dot-inline {\n width: 5px;\n height: 5px;\n border-radius: 9999px;\n background-color: var(--dt-diff-changed);\n flex-shrink: 0;\n}\n.dot-column {\n width: 5px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.pause-column {\n width: 8px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.dt-pause-icon {\n width: 8px;\n height: 8px;\n color: var(--dt-paused);\n}\n\n/* Interaction blocker \u2014 covers the app while time-travelling */\n.dt-interaction-blocker {\n position: fixed;\n inset: 0;\n z-index: 99997;\n cursor: not-allowed;\n}\n\n/* Scrubber */\n.dt-scrubber-row {\n background-color: var(--dt-bg);\n}\n.dt-scrubber-control {\n position: relative;\n height: 16px;\n width: 100%;\n padding: 0 7px;\n display: flex;\n align-items: center;\n}\n.dt-scrubber-track {\n width: 100%;\n height: 4px;\n background-color: var(--dt-border);\n border-radius: 9999px;\n cursor: pointer;\n}\n.dt-scrubber-track[data-disabled] {\n cursor: not-allowed;\n}\n.dt-scrubber-fill {\n height: 100%;\n background-color: var(--dt-accent);\n border-radius: 9999px;\n}\n.dt-scrubber-thumb {\n width: 14px;\n height: 14px;\n border-radius: 9999px;\n background-color: var(--dt-accent);\n border: 2px solid var(--dt-bg);\n cursor: grab;\n outline: none;\n top: 50%;\n margin-top: -7px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n}\n.dt-scrubber-thumb:focus-visible {\n outline: 2px solid var(--dt-text);\n outline-offset: 2px;\n}\n.dt-scrubber-thumb[data-dragging] {\n cursor: grabbing;\n}\n.dt-scrubber-position {\n width: 72px;\n padding-left: 12px;\n border-left: 1px solid var(--dt-border);\n align-self: stretch;\n margin-top: -8px;\n margin-bottom: -8px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Mobile */\n@media (max-width: 767px) {\n .dt-panel {\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n border-radius: 0;\n border: none;\n }\n .dt-panel-wide {\n width: 100%;\n }\n .dt-content {\n flex-direction: column;\n }\n .dt-message-pane {\n width: 100%;\n max-height: 40%;\n border-bottom: 1px solid var(--dt-border);\n }\n .message-list > :last-child {\n border-bottom: none;\n }\n .dt-inspector-pane {\n border-left: none;\n }\n .dt-badge.dt-pos-br,\n .dt-badge.dt-pos-bl {\n bottom: 44px;\n }\n}\n";
1
+ declare const overlayStyles = ":host {\n position: relative;\n z-index: 2147483647;\n\n --dt-bg: #1e1e2e;\n --dt-surface-selected: #282839;\n --dt-border: #45475a;\n --dt-text: #cdd6f4;\n --dt-text-muted: #9399b2;\n --dt-accent: #cba6f7;\n --dt-live: #a6e3a1;\n --dt-paused: #fab387;\n --dt-json-string: #a6e3a1;\n --dt-json-number: #89b4fa;\n --dt-json-boolean: #fab387;\n --dt-json-null: #9399b2;\n --dt-json-key: #89dceb;\n --dt-json-tag: #cba6f7;\n --dt-json-preview: #9399b2;\n --dt-json-arrow: #9399b2;\n --dt-tree-hover: #313244;\n --dt-diff-changed: #74c7ec;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\nbutton {\n font: inherit;\n color: inherit;\n}\nul {\n list-style: none;\n}\n\n.fixed {\n position: fixed;\n}\n.flex {\n display: flex;\n}\n.flex-col {\n flex-direction: column;\n}\n.flex-1 {\n flex: 1 1 0%;\n}\n.items-center {\n align-items: center;\n}\n.justify-center {\n justify-content: center;\n}\n.justify-between {\n justify-content: space-between;\n}\n.shrink-0 {\n flex-shrink: 0;\n}\n.inline-block {\n display: inline-block;\n}\n.gap-0\\.5 {\n gap: 2px;\n}\n.gap-1\\.5 {\n gap: 6px;\n}\n.gap-2 {\n gap: 8px;\n}\n.gap-3 {\n gap: 12px;\n}\n.gap-px {\n gap: 1px;\n}\n.px-1 {\n padding-left: 4px;\n padding-right: 4px;\n}\n.px-2 {\n padding-left: 8px;\n padding-right: 8px;\n}\n.px-2\\.5 {\n padding-left: 10px;\n padding-right: 10px;\n}\n.p-3 {\n padding: 12px;\n}\n.px-3 {\n padding-left: 12px;\n padding-right: 12px;\n}\n.py-0\\.5 {\n padding-top: 2px;\n padding-bottom: 2px;\n}\n.pt-1 {\n padding-top: 4px;\n}\n.pl-1 {\n padding-left: 4px;\n}\n.py-1 {\n padding-top: 4px;\n padding-bottom: 4px;\n}\n.py-1\\.5 {\n padding-top: 6px;\n padding-bottom: 6px;\n}\n.py-2 {\n padding-top: 8px;\n padding-bottom: 8px;\n}\n.py-px {\n padding-top: 1px;\n padding-bottom: 1px;\n}\n.w-1\\.5 {\n width: 6px;\n}\n.h-1\\.5 {\n height: 6px;\n}\n.w-3 {\n width: 12px;\n}\n.w-5 {\n width: 20px;\n}\n.h-5 {\n height: 20px;\n}\n.w-14 {\n width: 56px;\n}\n.h-14 {\n height: 56px;\n}\n.min-w-0 {\n min-width: 0;\n}\n.min-w-5 {\n min-width: 20px;\n}\n.min-h-0 {\n min-height: 0;\n}\n/* Badge positions \u2014 flush against side edge */\n.dt-pos-br {\n bottom: 16px;\n right: 0;\n border-radius: 6px 0 0 6px;\n}\n.dt-pos-bl {\n bottom: 16px;\n left: 0;\n border-radius: 0 6px 6px 0;\n}\n.dt-pos-tr {\n top: 16px;\n right: 0;\n border-radius: 6px 0 0 6px;\n}\n.dt-pos-tl {\n top: 16px;\n left: 0;\n border-radius: 0 6px 6px 0;\n}\n.overflow-hidden {\n overflow: hidden;\n}\n.overflow-auto {\n overflow: auto;\n}\n.overflow-y-auto {\n overflow-y: auto;\n}\n.overscroll-none {\n overscroll-behavior: none;\n}\n\n.truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.rounded {\n border-radius: 4px;\n}\n.rounded-lg {\n border-radius: 8px;\n}\n.rounded-full {\n border-radius: 9999px;\n}\n.border {\n border-width: 1px;\n border-style: solid;\n border-color: var(--dt-border);\n}\n.border-b {\n border-bottom: 1px solid var(--dt-border);\n}\n.border-t {\n border-top: 1px solid var(--dt-border);\n}\n.border-r {\n border-right: 1px solid var(--dt-border);\n}\n.border-l {\n border-left: 1px solid var(--dt-border);\n}\n.border-none {\n border: none;\n}\n.selected {\n background-color: var(--dt-surface-selected);\n}\n.dt-row:hover:not(.selected) {\n background-color: var(--dt-tree-hover);\n}\n.dt-header-button:hover {\n color: var(--dt-text);\n}\n.dt-resume-button:hover {\n opacity: 0.7;\n}\n.dt-scroll-pill {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n flex-shrink: 0;\n padding: 4px 12px;\n background-color: var(--dt-surface-selected);\n border: none;\n border-bottom: 1px solid var(--dt-border);\n color: var(--dt-accent);\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n font-size: 13px;\n cursor: pointer;\n}\n.dt-scroll-pill-icon {\n width: 12px;\n height: 12px;\n}\n.dt-scroll-pill-text {\n margin-top: 1px;\n}\n.dt-scroll-pill:hover {\n background-color: var(--dt-tree-hover);\n}\n.dt-scroll-pill:focus-visible {\n outline: 1px solid var(--dt-accent);\n outline-offset: -1px;\n}\n.dt-filter-wrapper {\n position: relative;\n flex-shrink: 0;\n border-bottom: 1px solid var(--dt-border);\n}\n.dt-filter-button {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n background: transparent;\n border: none;\n color: var(--dt-text-muted);\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n font-size: 13px;\n cursor: pointer;\n text-align: left;\n}\n.dt-filter-button:hover {\n color: var(--dt-text);\n background-color: var(--dt-tree-hover);\n}\n.dt-filter-button:focus-visible {\n outline: 1px solid var(--dt-accent);\n outline-offset: -1px;\n}\n.dt-filter-button[data-open] {\n color: var(--dt-text);\n background-color: var(--dt-surface-selected);\n}\n.dt-filter-button[data-open]:hover {\n background-color: var(--dt-tree-hover);\n}\n.dt-filter-button[data-open] .json-arrow {\n transform: rotate(180deg);\n}\n.dt-filter-items {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n background-color: var(--dt-bg);\n border-top: 1px solid var(--dt-border);\n border-bottom: 1px solid var(--dt-border);\n z-index: 10;\n max-height: 200px;\n overflow-y: auto;\n outline: none;\n}\n.dt-filter-item {\n padding: 6px 12px;\n color: var(--dt-text-muted);\n cursor: pointer;\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n font-size: 13px;\n border-bottom: 1px solid var(--dt-border);\n}\n.dt-filter-item:last-child {\n border-bottom: none;\n}\n.dt-filter-item[data-active] {\n background-color: var(--dt-tree-hover);\n color: var(--dt-text);\n}\n.dt-filter-item[data-selected] {\n color: var(--dt-accent);\n}\n.dt-filter-check {\n width: 12px;\n height: 12px;\n visibility: hidden;\n}\n.dt-filter-item[data-selected] .dt-filter-check {\n visibility: visible;\n color: var(--dt-accent);\n}\n.dt-filter-backdrop {\n position: fixed;\n inset: 0;\n pointer-events: none;\n}\n.dt-tab-button {\n position: relative;\n background: transparent;\n border: none;\n border-right: 1px solid var(--dt-border);\n outline: none;\n flex: 1;\n}\n.dt-tab-button:last-child {\n border-right: none;\n}\n.dt-tab-active {\n background-color: var(--dt-surface-selected);\n}\n.dt-tab-button:not(.dt-tab-active):hover {\n color: var(--dt-text);\n background-color: rgba(49, 50, 68, 0.3);\n}\n.font-sans {\n font-family:\n system-ui,\n -apple-system,\n BlinkMacSystemFont,\n 'Segoe UI',\n Roboto,\n sans-serif;\n}\n.font-mono {\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n}\n.font-medium {\n font-weight: 500;\n}\n.font-semibold {\n font-weight: 600;\n}\n.text-xs {\n font-size: 12px;\n}\n.text-2xs {\n font-size: 10px;\n}\n.text-sm {\n font-size: 11px;\n}\n.text-base {\n font-size: 13px;\n}\n.text-md {\n font-size: 15px;\n}\n.text-lg {\n font-size: 20px;\n}\n.text-xl {\n font-size: 26px;\n}\n.italic {\n font-style: italic;\n}\n.text-right {\n text-align: right;\n}\n.tracking-wide {\n letter-spacing: 0.025em;\n}\n.tracking-wider {\n letter-spacing: 0.05em;\n}\n.leading-none {\n line-height: 1;\n}\n.leading-snug {\n line-height: 1.35;\n}\n.bg-dt-bg {\n background-color: var(--dt-bg);\n}\n.bg-dt-live {\n background-color: var(--dt-live);\n}\n.bg-transparent {\n background-color: transparent;\n}\n.text-dt {\n color: var(--dt-text);\n}\n.text-dt-bg {\n color: var(--dt-bg);\n}\n.text-dt-muted {\n color: var(--dt-text-muted);\n}\n.text-dt-accent {\n color: var(--dt-accent);\n}\n.text-dt-live {\n color: var(--dt-live);\n}\n.text-dt-paused {\n color: var(--dt-paused);\n}\n.cursor-pointer {\n cursor: pointer;\n}\n.outline-none {\n outline: none;\n}\n.transition-colors {\n transition-property: color, background-color, border-color;\n transition-duration: 100ms;\n transition-timing-function: ease;\n}\n\n/* Panel */\n.dt-panel {\n width: 360px;\n height: 480px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n z-index: 99998;\n}\n/* Panel positions */\n.dt-panel-br {\n bottom: 16px;\n right: 28px;\n}\n.dt-panel-bl {\n bottom: 16px;\n left: 28px;\n}\n.dt-panel-tr {\n top: 16px;\n right: 28px;\n}\n.dt-panel-tl {\n top: 16px;\n left: 28px;\n}\n.dt-panel-wide {\n width: 720px;\n}\n.dt-message-pane {\n width: 320px;\n flex-shrink: 0;\n}\n.dt-badge {\n z-index: 99999;\n box-shadow: -2px 0 8px rgba(0, 0, 0, 0.3);\n transition: background-color 150ms ease;\n border: 1px solid var(--dt-border);\n}\n.dt-badge-accent:hover {\n background-color: #252538;\n}\n.dt-badge-paused {\n background-color: var(--dt-paused);\n color: var(--dt-bg);\n border: none;\n}\n.dt-badge-paused:hover {\n background-color: #e0a070;\n}\n.dt-badge.dt-pos-br,\n.dt-badge.dt-pos-tr {\n border-right: none;\n}\n.dt-badge.dt-pos-bl,\n.dt-badge.dt-pos-tl {\n border-left: none;\n}\n\n/* JSON tree */\n.tree-row {\n position: relative;\n white-space: nowrap;\n line-height: 18px;\n padding-right: 8px;\n}\n.tree-row-expandable:hover {\n background-color: var(--dt-tree-hover);\n}\n.inspector-tree {\n font-family:\n ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;\n}\n.json-key {\n color: var(--dt-json-key);\n}\n.json-string {\n color: var(--dt-json-string);\n}\n.json-number {\n color: var(--dt-json-number);\n}\n.json-boolean {\n color: var(--dt-json-boolean);\n}\n.json-null {\n color: var(--dt-json-null);\n}\n.json-tag {\n color: var(--dt-json-tag);\n margin-right: 4px;\n}\n.json-preview {\n color: var(--dt-json-preview);\n}\n.json-arrow {\n color: var(--dt-json-arrow);\n width: 10px;\n height: 10px;\n user-select: none;\n}\n\n/* Diff */\n.diff-changed {\n background-color: rgba(116, 199, 236, 0.06);\n}\n.diff-dot {\n position: absolute;\n left: 3px;\n width: 5px;\n height: 5px;\n border-radius: 9999px;\n background-color: var(--dt-diff-changed);\n}\n.diff-dot-inline {\n width: 5px;\n height: 5px;\n border-radius: 9999px;\n background-color: var(--dt-diff-changed);\n flex-shrink: 0;\n}\n.dot-column {\n width: 5px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.pause-column {\n width: 8px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.dt-pause-icon {\n width: 8px;\n height: 8px;\n color: var(--dt-paused);\n}\n\n/* Interaction blocker \u2014 covers the app while time-travelling */\n.dt-interaction-blocker {\n position: fixed;\n inset: 0;\n z-index: 99997;\n cursor: not-allowed;\n}\n\n/* Scrubber */\n.dt-scrubber-row {\n background-color: var(--dt-bg);\n}\n.dt-scrubber-control {\n position: relative;\n height: 16px;\n width: 100%;\n padding: 0 7px;\n display: flex;\n align-items: center;\n}\n.dt-scrubber-track {\n width: 100%;\n height: 4px;\n background-color: var(--dt-border);\n border-radius: 9999px;\n cursor: pointer;\n}\n.dt-scrubber-track[data-disabled] {\n cursor: not-allowed;\n}\n.dt-scrubber-fill {\n height: 100%;\n background-color: var(--dt-accent);\n border-radius: 9999px;\n}\n.dt-scrubber-thumb {\n width: 14px;\n height: 14px;\n border-radius: 9999px;\n background-color: var(--dt-accent);\n border: 2px solid var(--dt-bg);\n cursor: grab;\n outline: none;\n top: 50%;\n margin-top: -7px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n}\n.dt-scrubber-thumb:focus-visible {\n outline: 2px solid var(--dt-text);\n outline-offset: 2px;\n}\n.dt-scrubber-thumb[data-dragging] {\n cursor: grabbing;\n}\n.dt-scrubber-position {\n width: 72px;\n padding-left: 12px;\n border-left: 1px solid var(--dt-border);\n align-self: stretch;\n margin-top: -8px;\n margin-bottom: -8px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Mobile */\n@media (max-width: 767px) {\n .dt-panel {\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n height: 100%;\n border-radius: 0;\n border: none;\n }\n .dt-panel-wide {\n width: 100%;\n }\n .dt-content {\n flex-direction: column;\n }\n .dt-message-pane {\n width: 100%;\n max-height: 40%;\n border-bottom: 1px solid var(--dt-border);\n }\n .message-list > :last-child {\n border-bottom: none;\n }\n .dt-inspector-pane {\n border-left: none;\n }\n .dt-badge.dt-pos-br,\n .dt-badge.dt-pos-bl {\n bottom: 44px;\n }\n}\n";
2
2
  export { overlayStyles };
3
3
  //# sourceMappingURL=overlay-styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"overlay-styles.d.ts","sourceRoot":"","sources":["../../src/devTools/overlay-styles.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,aAAa,ypXAkqBlB,CAAA;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"overlay-styles.d.ts","sourceRoot":"","sources":["../../src/devTools/overlay-styles.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,aAAa,g1YAgsBlB,CAAA;AAED,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -234,6 +234,36 @@ ul {
234
234
  .dt-resume-button:hover {
235
235
  opacity: 0.7;
236
236
  }
237
+ .dt-scroll-pill {
238
+ display: flex;
239
+ align-items: center;
240
+ justify-content: center;
241
+ gap: 6px;
242
+ flex-shrink: 0;
243
+ padding: 4px 12px;
244
+ background-color: var(--dt-surface-selected);
245
+ border: none;
246
+ border-bottom: 1px solid var(--dt-border);
247
+ color: var(--dt-accent);
248
+ font-family:
249
+ ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
250
+ font-size: 13px;
251
+ cursor: pointer;
252
+ }
253
+ .dt-scroll-pill-icon {
254
+ width: 12px;
255
+ height: 12px;
256
+ }
257
+ .dt-scroll-pill-text {
258
+ margin-top: 1px;
259
+ }
260
+ .dt-scroll-pill:hover {
261
+ background-color: var(--dt-tree-hover);
262
+ }
263
+ .dt-scroll-pill:focus-visible {
264
+ outline: 1px solid var(--dt-accent);
265
+ outline-offset: -1px;
266
+ }
237
267
  .dt-filter-wrapper {
238
268
  position: relative;
239
269
  flex-shrink: 0;
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/devTools/overlay.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,OAAO,EACP,MAAM,EAGN,OAAO,EAGP,MAAM,EAKN,MAAM,IAAI,CAAC,EAGX,eAAe,EAEhB,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAA;AAa9C,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAQ3E,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAElB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAA;;oFA8ZQ,CAAC;;;;;;;;;;;;;;AApI5B,cAAM,YAAa,SAAQ,iBAE1B;CAAG;;AAEJ,cAAM,iBAAkB,SAAQ,sBAGC;CAAG;AAEpC,eAAO,MAAM,UAAU;;iBAGsB,CAAA;AAE7C,eAAO,MAAM,YAAY;;iBAGwB,CAAA;AAWjD,eAAO,MAAM,MAAM;;;;wBAUlB,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;wBAIqB,CAAA;AAE9C,eAAO,MAAM,aAAa;;;;;;wBAYzB,CAAA;AAED,eAAO,MAAM,MAAM;;wBASlB,CAAA;AAED,eAAO,MAAM,KAAK;;wBASjB,CAAA;AAED,eAAO,MAAM,WAAW;;6BAYvB,CAAA;AA0oDD,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,UAAU,gBAAgB,EAC1B,MAAM,YAAY,EAClB,aAAa,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,sCAkEhC,CAAA"}
1
+ {"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/devTools/overlay.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,OAAO,EACP,MAAM,EAGN,OAAO,EAGP,MAAM,EAKN,MAAM,IAAI,CAAC,EAGX,eAAe,EAEhB,MAAM,QAAQ,CAAA;AAEf,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAA;AAa9C,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAQ3E,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAElB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,YAAY,CAAA;;oFA8ZL,CAAC;;;;;;;;;;;;;;AA7Hf,cAAM,YAAa,SAAQ,iBAE1B;CAAG;;AAEJ,cAAM,iBAAkB,SAAQ,sBAGC;CAAG;AAEpC,eAAO,MAAM,UAAU;;iBAGsB,CAAA;AAE7C,eAAO,MAAM,YAAY;;iBAGwB,CAAA;AAWjD,eAAO,MAAM,MAAM;;;;wBAUlB,CAAA;AAED,eAAO,MAAM,YAAY;;;;;;;;wBAIqB,CAAA;AAE9C,eAAO,MAAM,aAAa;;;;;;wBAYzB,CAAA;AAED,eAAO,MAAM,MAAM;;wBASlB,CAAA;AAED,eAAO,MAAM,KAAK;;wBASjB,CAAA;AAED,eAAO,MAAM,WAAW;;6BAYvB,CAAA;AA2sDD,eAAO,MAAM,aAAa,GACxB,OAAO,aAAa,EACpB,UAAU,gBAAgB,EAC1B,MAAM,YAAY,EAClB,aAAa,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,sCAmEhC,CAAA"}
@@ -66,6 +66,7 @@ const Model = S.Struct({
66
66
  pausedAtIndex: S.Number,
67
67
  selectedIndex: S.Number,
68
68
  isFollowingLatest: S.Boolean,
69
+ isFollowingTop: S.Boolean,
69
70
  maybeInspectedModel: S.Option(UnknownByReference),
70
71
  maybeInspectedMessage: S.Option(UnknownByReference),
71
72
  submodelTags: S.Array(S.String),
@@ -102,6 +103,8 @@ const ClickedClear = m('ClickedClear');
102
103
  const CompletedJump = m('CompletedJump');
103
104
  const CompletedResume = m('CompletedResume');
104
105
  const ClickedFollowLatest = m('ClickedFollowLatest');
106
+ const ClickedScrollToTopPill = m('ClickedScrollToTopPill');
107
+ const ScrolledMessageList = m('ScrolledMessageList', { scrollTop: S.Number });
105
108
  const CompletedClear = m('CompletedClear');
106
109
  const LockedScroll = m('LockedScroll');
107
110
  const UnlockedScroll = m('UnlockedScroll');
@@ -143,6 +146,8 @@ const Message = S.Union([
143
146
  ClickedResume,
144
147
  ClickedClear,
145
148
  ClickedFollowLatest,
149
+ ClickedScrollToTopPill,
150
+ ScrolledMessageList,
146
151
  CompletedJump,
147
152
  CompletedResume,
148
153
  CompletedClear,
@@ -177,6 +182,7 @@ const MESSAGE_LIST_SELECTOR = '.message-list';
177
182
  // the boundaries via the helpers below.
178
183
  const hostIndexToSliderValue = (hostIndex, startIndex) => (hostIndex === INIT_INDEX ? 0 : hostIndex - startIndex + 1);
179
184
  const sliderValueToHostIndex = (sliderValue, startIndex) => (sliderValue === 0 ? INIT_INDEX : startIndex + sliderValue - 1);
185
+ const SCROLL_FOLLOW_THRESHOLD_PX = 8;
180
186
  const computeSubmodelTags = (entries) => pipe(entries, Array_.flatMap(({ submodelPath }) => submodelPath), Array_.dedupe, Array_.sort(Order.String));
181
187
  const toDisplayCommand = (command) => ({
182
188
  name: command.name,
@@ -297,22 +303,24 @@ const makeUpdate = (store, shadow, mode) => {
297
303
  ]), M.exhaustive),
298
304
  ClickedResume: () => [
299
305
  evo(model, {
306
+ isFollowingTop: () => true,
300
307
  expandedPaths: () => HashSet.empty(),
301
308
  changedPaths: () => HashSet.empty(),
302
309
  affectedPaths: () => HashSet.empty(),
303
310
  }),
304
- [resume, inspectLatest],
311
+ [resume, inspectLatest, scrollToTop],
305
312
  ],
306
313
  ClickedClear: () => [
307
314
  evo(model, {
308
315
  selectedIndex: () => INIT_INDEX,
309
316
  isFollowingLatest: () => true,
317
+ isFollowingTop: () => true,
310
318
  maybeSubmodelFilter: () => Option.none(),
311
319
  expandedPaths: () => HashSet.empty(),
312
320
  changedPaths: () => HashSet.empty(),
313
321
  affectedPaths: () => HashSet.empty(),
314
322
  }),
315
- [clear, inspectLatest],
323
+ [clear, inspectLatest, scrollToTop],
316
324
  ],
317
325
  ClickedFollowLatest: () => {
318
326
  const latestIndex = Array_.match(model.entries, {
@@ -323,6 +331,7 @@ const makeUpdate = (store, shadow, mode) => {
323
331
  evo(model, {
324
332
  selectedIndex: () => latestIndex,
325
333
  isFollowingLatest: () => true,
334
+ isFollowingTop: () => true,
326
335
  expandedPaths: () => HashSet.empty(),
327
336
  changedPaths: () => HashSet.empty(),
328
337
  affectedPaths: () => HashSet.empty(),
@@ -330,6 +339,18 @@ const makeUpdate = (store, shadow, mode) => {
330
339
  [inspectLatest, scrollToTop],
331
340
  ];
332
341
  },
342
+ ClickedScrollToTopPill: () => [
343
+ evo(model, {
344
+ isFollowingTop: () => true,
345
+ }),
346
+ [scrollToTop],
347
+ ],
348
+ ScrolledMessageList: ({ scrollTop }) => {
349
+ const isAtTop = scrollTop <= SCROLL_FOLLOW_THRESHOLD_PX;
350
+ return isAtTop === model.isFollowingTop
351
+ ? [model, []]
352
+ : [evo(model, { isFollowingTop: () => isAtTop }), []];
353
+ },
333
354
  ReceivedInspectedState: ({ model: inspectedModel, maybeMessage, changedPaths, affectedPaths, }) => [
334
355
  evo(model, {
335
356
  maybeInspectedModel: () => Option.some(inspectedModel),
@@ -359,7 +380,8 @@ const makeUpdate = (store, shadow, mode) => {
359
380
  [],
360
381
  ],
361
382
  ReceivedStoreUpdate: ({ entries, initCommands, initMountStarts, startIndex, isPaused, pausedAtIndex, }) => {
362
- const shouldFollowLatest = M.value(mode).pipe(M.when('TimeTravel', () => !isPaused), M.when('Inspect', () => model.isFollowingLatest), M.exhaustive);
383
+ const shouldFollowSelection = M.value(mode).pipe(M.when('TimeTravel', () => !isPaused), M.when('Inspect', () => model.isFollowingLatest), M.exhaustive);
384
+ const shouldFollowScroll = M.value(mode).pipe(M.when('TimeTravel', () => !isPaused && model.isFollowingTop), M.when('Inspect', () => model.isFollowingTop), M.exhaustive);
363
385
  const latestIndex = Array_.match(entries, {
364
386
  onEmpty: () => INIT_INDEX,
365
387
  onNonEmpty: () => startIndex + entries.length - 1,
@@ -381,7 +403,7 @@ const makeUpdate = (store, shadow, mode) => {
381
403
  maybeSelectedItem: () => Option.some(ALL_MESSAGES_VALUE),
382
404
  })
383
405
  : current,
384
- selectedIndex: current => shouldFollowLatest ? latestIndex : current,
406
+ selectedIndex: current => shouldFollowSelection ? latestIndex : current,
385
407
  scrubberSlider: current => {
386
408
  const sliderMax = entries.length;
387
409
  const targetSliderValue = isPaused
@@ -390,7 +412,10 @@ const makeUpdate = (store, shadow, mode) => {
390
412
  return Slider.setValue(Slider.setRange(current, { min: 0, max: sliderMax }), targetSliderValue);
391
413
  },
392
414
  }),
393
- shouldFollowLatest ? [scrollToTop, inspectLatest] : [],
415
+ [
416
+ ...(shouldFollowSelection ? [inspectLatest] : []),
417
+ ...(shouldFollowScroll ? [scrollToTop] : []),
418
+ ],
394
419
  ];
395
420
  },
396
421
  GotSubmodelFilterMessage: ({ message: listboxMessage }) => {
@@ -433,7 +458,7 @@ const makeUpdate = (store, shadow, mode) => {
433
458
  };
434
459
  // SUBSCRIPTION
435
460
  const ScrubberDragActivity = S.Literals(['Idle', 'Active']);
436
- const SubscriptionDeps = S.Struct({
461
+ const SubscriptionDependencies = S.Struct({
437
462
  storeUpdates: S.Boolean,
438
463
  mobileBreakpoint: S.Null,
439
464
  scrubberPointer: S.Struct({
@@ -448,7 +473,7 @@ const SubscriptionDeps = S.Struct({
448
473
  });
449
474
  const makeOverlaySubscriptions = (store, shadow) => {
450
475
  const sliderSubscriptions = Slider.subscriptionsForRoot(() => shadow);
451
- return makeSubscriptions(SubscriptionDeps)({
476
+ return makeSubscriptions(SubscriptionDependencies)({
452
477
  storeUpdates: {
453
478
  modelToDependencies: () => true,
454
479
  dependenciesToStream: () => Stream.concat(Stream.fromEffect(SubscriptionRef.get(store.stateRef).pipe(Effect.map(state => ReceivedStoreUpdate(toDisplayState(state))))), Stream.map(SubscriptionRef.changes(store.stateRef), state => ReceivedStoreUpdate(toDisplayState(state)))),
@@ -893,6 +918,26 @@ const makeView = (position, mode, shadow, maybeBanner) => {
893
918
  h.path([h.D(CHECK_ICON), h.StrokeLinecap('round'), h.StrokeLinejoin('round')], []),
894
919
  ]);
895
920
  const filterItemLabel = (item) => String_.isNonEmpty(item) ? submodelLabel(item) : 'All Messages';
921
+ const ARROW_UP = 'M4.5 10.5L12 3m0 0l7.5 7.5M12 3v18';
922
+ const arrowUpIconView = h.svg([
923
+ h.AriaHidden(true),
924
+ h.Class('dt-scroll-pill-icon shrink-0'),
925
+ h.Xmlns('http://www.w3.org/2000/h.svg'),
926
+ h.Fill('none'),
927
+ h.ViewBox('0 0 24 24'),
928
+ h.StrokeWidth('2'),
929
+ h.Stroke('currentColor'),
930
+ ], [
931
+ h.path([h.D(ARROW_UP), h.StrokeLinecap('round'), h.StrokeLinejoin('round')], []),
932
+ ]);
933
+ const scrollToTopPillView = h.button([
934
+ h.Key('scroll-pill'),
935
+ h.Class('dt-scroll-pill'),
936
+ h.OnClick(ClickedScrollToTopPill()),
937
+ ], [
938
+ arrowUpIconView,
939
+ h.span([h.Class('dt-scroll-pill-text')], ['Jump to top']),
940
+ ]);
896
941
  const submodelFilterView = (model) => {
897
942
  const buttonLabel = Option.match(model.maybeSubmodelFilter, {
898
943
  onNone: () => 'All Messages',
@@ -928,6 +973,7 @@ const makeView = (position, mode, shadow, maybeBanner) => {
928
973
  buttonClassName: 'dt-filter-button',
929
974
  itemsClassName: 'dt-filter-items',
930
975
  className: 'dt-filter-wrapper',
976
+ attributes: [h.Key('submodel-filter')],
931
977
  backdropClassName: 'dt-filter-backdrop',
932
978
  });
933
979
  };
@@ -1018,7 +1064,11 @@ const makeView = (position, mode, shadow, maybeBanner) => {
1018
1064
  entry.isModelChanged,
1019
1065
  ]);
1020
1066
  }), Array_.reverse);
1021
- return h.ul([h.Class('message-list flex-1 overflow-y-auto min-h-0 overscroll-none')], isFiltered
1067
+ return h.ul([
1068
+ h.Key('message-list'),
1069
+ h.Class('message-list flex-1 overflow-y-auto min-h-0 overscroll-none'),
1070
+ h.OnScroll(scrollTop => ScrolledMessageList({ scrollTop })),
1071
+ ], isFiltered
1022
1072
  ? messageRows
1023
1073
  : [
1024
1074
  ...messageRows,
@@ -1084,6 +1134,7 @@ const makeView = (position, mode, shadow, maybeBanner) => {
1084
1134
  onEmpty: () => [],
1085
1135
  onNonEmpty: () => [submodelFilterView(model)],
1086
1136
  }),
1137
+ ...OptionExt.when(!model.isFollowingTop, scrollToTopPillView).pipe(Option.toArray),
1087
1138
  messageListView(model),
1088
1139
  ]),
1089
1140
  inspectorPaneView(model),
@@ -1146,6 +1197,7 @@ export const createOverlay = (store, position, mode, maybeBanner) => Effect.gen(
1146
1197
  ...flags,
1147
1198
  selectedIndex: INIT_INDEX,
1148
1199
  isFollowingLatest: true,
1200
+ isFollowingTop: true,
1149
1201
  submodelTags: computeSubmodelTags(flags.entries),
1150
1202
  maybeSubmodelFilter: Option.none(),
1151
1203
  submodelFilterListbox: Listbox.init({
package/dist/dom/dom.d.ts CHANGED
@@ -16,14 +16,29 @@ import { ElementNotFound } from './error.js';
16
16
  * effects bound to a VNode existing where the live element handle is
17
17
  * needed (positioning, portaling, observer attachment, library setup).
18
18
  *
19
+ * Section headings, articles, and other non-natively-focusable elements
20
+ * are common URL fragment targets, but `.focus()` is a no-op on them
21
+ * without a `tabindex`. Pass `makeFocusable: true` to inject
22
+ * `tabindex="-1"` on the target if it has none, making programmatic
23
+ * focus actually land. Pass `preventScroll: true` to suppress the
24
+ * browser's default scroll-on-focus, useful when the focus call follows
25
+ * a deliberate scroll that should not be undone. The two options compose
26
+ * with `scrollIntoViewAfterPaint` for URL-fragment-navigation
27
+ * accessibility: scroll the section into view, then focus the same
28
+ * selector so keyboard users start Tab navigation from the target.
29
+ *
19
30
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
20
31
  *
21
32
  * @example
22
33
  * ```typescript
23
34
  * Dom.focus('#email-input').pipe(Effect.ignore, Effect.as(CompletedFocusInput()))
35
+ * Dom.focus('#section', { preventScroll: true, makeFocusable: true })
24
36
  * ```
25
37
  */
26
- export declare const focus: (selector: string) => Effect.Effect<void, ElementNotFound>;
38
+ export declare const focus: (selector: string, options?: Readonly<{
39
+ preventScroll?: boolean;
40
+ makeFocusable?: boolean;
41
+ }>) => Effect.Effect<void, ElementNotFound>;
27
42
  /**
28
43
  * Opens a dialog element using `show()` with high z-index, focus trapping,
29
44
  * and Escape key handling. Uses `show()` instead of `showModal()` so that
@@ -64,15 +79,50 @@ export declare const closeModal: (selector: string) => Effect.Effect<void, Eleme
64
79
  */
65
80
  export declare const clickElement: (selector: string) => Effect.Effect<void, ElementNotFound>;
66
81
  /**
67
- * Scrolls an element into view by selector using `{ block: 'nearest' }`.
82
+ * Scrolls an element into view by selector. Resolves the selector after
83
+ * `Render.afterCommit`. Defaults to `{ block: 'nearest' }`; pass a different
84
+ * `block` for use cases like URL-fragment landing where `'start'` is right.
85
+ * For a target the same Message just brought into the DOM,
86
+ * `scrollIntoViewAfterPaint` is the right choice.
87
+ *
68
88
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
69
89
  *
70
90
  * @example
71
91
  * ```typescript
72
92
  * Dom.scrollIntoView('#active-item').pipe(Effect.ignore, Effect.as(CompletedScrollIntoView()))
93
+ * Dom.scrollIntoView('#section-2', { block: 'start' })
73
94
  * ```
74
95
  */
75
- export declare const scrollIntoView: (selector: string) => Effect.Effect<void, ElementNotFound>;
96
+ export declare const scrollIntoView: (selector: string, options?: Readonly<{
97
+ block?: ScrollLogicalPosition;
98
+ }>) => Effect.Effect<void, ElementNotFound>;
99
+ /**
100
+ * Like `scrollIntoView`, but waits for `Render.afterPaint` instead of
101
+ * `Render.afterCommit` before resolving the selector.
102
+ *
103
+ * Reach for this when the target was just brought into the DOM by the same
104
+ * Message that dispatches the scroll, such as a routing flow landing at a
105
+ * URL fragment. The two-frame wait gives the runtime time to commit the new
106
+ * Model and the browser time to lay it out before the scroll runs. For a
107
+ * target that's already on screen, `scrollIntoView` is the lighter choice.
108
+ *
109
+ * Defaults to `{ block: 'nearest' }`; pass `{ block: 'start' }` for URL
110
+ * fragment landings where the target should sit at the top of the viewport.
111
+ *
112
+ * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * Dom.scrollIntoViewAfterPaint('#overview').pipe(
117
+ * Effect.ignore,
118
+ * Effect.as(CompletedScrollIntoViewAfterPaint()),
119
+ * )
120
+ * Dom.scrollIntoViewAfterPaint(`#${hash}`, { block: 'start' })
121
+ * ```
122
+ */
123
+ export declare const scrollIntoViewAfterPaint: (selector: string, options?: Readonly<{
124
+ block?: ScrollLogicalPosition;
125
+ }>) => Effect.Effect<void, ElementNotFound>;
76
126
  /** Direction for focus advancement: forward or backward in tab order. */
77
127
  export type FocusDirection = 'Next' | 'Previous';
78
128
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAGf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6B5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKxE,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAgDlC,CAAA;AAuBJ;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAclC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAiClC,CAAA"}
1
+ {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../src/dom/dom.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,MAAM,EAMP,MAAM,QAAQ,CAAA;AAGf,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6B5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,KAAK,GAChB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,KACvE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAQlC,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAgDlC,CAAA;AAuBJ;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAclC,CAAA;AAEJ;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,EAChB,UAAU,QAAQ,CAAC;IAAE,KAAK,CAAC,EAAE,qBAAqB,CAAA;CAAE,CAAC,KACpD,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAKlC,CAAA;AAEJ,yEAAyE;AACzE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,UAAU,CAAA;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,WAAW,cAAc,KACxB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAiClC,CAAA"}
package/dist/dom/dom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Array, Effect, Equal, Function, Match as M, Number, Option, } from 'effect';
2
- import { afterCommit } from '../render/render.js';
2
+ import { afterCommit, afterPaint } from '../render/render.js';
3
3
  import { ElementNotFound } from './error.js';
4
4
  const BASE_DIALOG_Z_INDEX = 2147483600;
5
5
  let openDialogCount = 0;
@@ -34,17 +34,32 @@ const queryHTMLElement = (selector) => Effect.suspend(() => {
34
34
  * effects bound to a VNode existing where the live element handle is
35
35
  * needed (positioning, portaling, observer attachment, library setup).
36
36
  *
37
+ * Section headings, articles, and other non-natively-focusable elements
38
+ * are common URL fragment targets, but `.focus()` is a no-op on them
39
+ * without a `tabindex`. Pass `makeFocusable: true` to inject
40
+ * `tabindex="-1"` on the target if it has none, making programmatic
41
+ * focus actually land. Pass `preventScroll: true` to suppress the
42
+ * browser's default scroll-on-focus, useful when the focus call follows
43
+ * a deliberate scroll that should not be undone. The two options compose
44
+ * with `scrollIntoViewAfterPaint` for URL-fragment-navigation
45
+ * accessibility: scroll the section into view, then focus the same
46
+ * selector so keyboard users start Tab navigation from the target.
47
+ *
37
48
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
38
49
  *
39
50
  * @example
40
51
  * ```typescript
41
52
  * Dom.focus('#email-input').pipe(Effect.ignore, Effect.as(CompletedFocusInput()))
53
+ * Dom.focus('#section', { preventScroll: true, makeFocusable: true })
42
54
  * ```
43
55
  */
44
- export const focus = (selector) => Effect.gen(function* () {
56
+ export const focus = (selector, options) => Effect.gen(function* () {
45
57
  yield* afterCommit;
46
58
  const element = yield* queryHTMLElement(selector);
47
- element.focus();
59
+ if (options?.makeFocusable && !element.hasAttribute('tabindex')) {
60
+ element.setAttribute('tabindex', '-1');
61
+ }
62
+ element.focus({ preventScroll: options?.preventScroll ?? false });
48
63
  });
49
64
  /**
50
65
  * Opens a dialog element using `show()` with high z-index, focus trapping,
@@ -149,18 +164,53 @@ export const clickElement = (selector) => Effect.gen(function* () {
149
164
  element.click();
150
165
  });
151
166
  /**
152
- * Scrolls an element into view by selector using `{ block: 'nearest' }`.
167
+ * Scrolls an element into view by selector. Resolves the selector after
168
+ * `Render.afterCommit`. Defaults to `{ block: 'nearest' }`; pass a different
169
+ * `block` for use cases like URL-fragment landing where `'start'` is right.
170
+ * For a target the same Message just brought into the DOM,
171
+ * `scrollIntoViewAfterPaint` is the right choice.
172
+ *
153
173
  * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
154
174
  *
155
175
  * @example
156
176
  * ```typescript
157
177
  * Dom.scrollIntoView('#active-item').pipe(Effect.ignore, Effect.as(CompletedScrollIntoView()))
178
+ * Dom.scrollIntoView('#section-2', { block: 'start' })
158
179
  * ```
159
180
  */
160
- export const scrollIntoView = (selector) => Effect.gen(function* () {
181
+ export const scrollIntoView = (selector, options) => Effect.gen(function* () {
161
182
  yield* afterCommit;
162
183
  const element = yield* queryHTMLElement(selector);
163
- element.scrollIntoView({ block: 'nearest' });
184
+ element.scrollIntoView({ block: options?.block ?? 'nearest' });
185
+ });
186
+ /**
187
+ * Like `scrollIntoView`, but waits for `Render.afterPaint` instead of
188
+ * `Render.afterCommit` before resolving the selector.
189
+ *
190
+ * Reach for this when the target was just brought into the DOM by the same
191
+ * Message that dispatches the scroll, such as a routing flow landing at a
192
+ * URL fragment. The two-frame wait gives the runtime time to commit the new
193
+ * Model and the browser time to lay it out before the scroll runs. For a
194
+ * target that's already on screen, `scrollIntoView` is the lighter choice.
195
+ *
196
+ * Defaults to `{ block: 'nearest' }`; pass `{ block: 'start' }` for URL
197
+ * fragment landings where the target should sit at the top of the viewport.
198
+ *
199
+ * Fails with `ElementNotFound` if the selector does not match an `HTMLElement`.
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * Dom.scrollIntoViewAfterPaint('#overview').pipe(
204
+ * Effect.ignore,
205
+ * Effect.as(CompletedScrollIntoViewAfterPaint()),
206
+ * )
207
+ * Dom.scrollIntoViewAfterPaint(`#${hash}`, { block: 'start' })
208
+ * ```
209
+ */
210
+ export const scrollIntoViewAfterPaint = (selector, options) => Effect.gen(function* () {
211
+ yield* afterPaint;
212
+ const element = yield* queryHTMLElement(selector);
213
+ element.scrollIntoView({ block: options?.block ?? 'nearest' });
164
214
  });
165
215
  /**
166
216
  * Focuses the next or previous focusable element in the document relative to the element matching the given selector.
@@ -1,5 +1,5 @@
1
1
  export { ElementNotFound } from './error.js';
2
- export { advanceFocus, clickElement, closeModal, focus, scrollIntoView, showModal, } from './dom.js';
2
+ export { advanceFocus, clickElement, closeModal, focus, scrollIntoView, scrollIntoViewAfterPaint, showModal, } from './dom.js';
3
3
  export type { FocusDirection } from './dom.js';
4
4
  export { detectElementMovement } from './elementMovement.js';
5
5
  export { inertOthers, restoreInert } from './inert.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dom/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,KAAK,EACL,cAAc,EACd,SAAS,GACV,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dom/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,KAAK,EACL,cAAc,EACd,wBAAwB,EACxB,SAAS,GACV,MAAM,UAAU,CAAA;AACjB,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA"}
package/dist/dom/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { ElementNotFound } from './error.js';
2
- export { advanceFocus, clickElement, closeModal, focus, scrollIntoView, showModal, } from './dom.js';
2
+ export { advanceFocus, clickElement, closeModal, focus, scrollIntoView, scrollIntoViewAfterPaint, showModal, } from './dom.js';
3
3
  export { detectElementMovement } from './elementMovement.js';
4
4
  export { inertOthers, restoreInert } from './inert.js';
5
5
  export { lockScroll, unlockScroll } from './scrollLock.js';
@@ -1,3 +1,3 @@
1
- export { ElementNotFound, advanceFocus, clickElement, closeModal, detectElementMovement, focus, inertOthers, lockScroll, restoreInert, scrollIntoView, showModal, unlockScroll, waitForAnimationSettled, } from './index.js';
1
+ export { ElementNotFound, advanceFocus, clickElement, closeModal, detectElementMovement, focus, inertOthers, lockScroll, restoreInert, scrollIntoView, scrollIntoViewAfterPaint, showModal, unlockScroll, waitForAnimationSettled, } from './index.js';
2
2
  export type { FocusDirection } from './index.js';
3
3
  //# sourceMappingURL=public.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/dom/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,KAAK,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,cAAc,EACd,SAAS,EACT,YAAY,EACZ,uBAAuB,GACxB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/dom/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,KAAK,EACL,WAAW,EACX,UAAU,EACV,YAAY,EACZ,cAAc,EACd,wBAAwB,EACxB,SAAS,EACT,YAAY,EACZ,uBAAuB,GACxB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
@@ -1 +1 @@
1
- export { ElementNotFound, advanceFocus, clickElement, closeModal, detectElementMovement, focus, inertOthers, lockScroll, restoreInert, scrollIntoView, showModal, unlockScroll, waitForAnimationSettled, } from './index.js';
1
+ export { ElementNotFound, advanceFocus, clickElement, closeModal, detectElementMovement, focus, inertOthers, lockScroll, restoreInert, scrollIntoView, scrollIntoViewAfterPaint, showModal, unlockScroll, waitForAnimationSettled, } from './index.js';
@@ -194,7 +194,7 @@ export type Attribute<Message> = Data.TaggedEnum<{
194
194
  readonly message: Message;
195
195
  };
196
196
  OnScroll: {
197
- readonly message: Message;
197
+ readonly f: (scrollTop: number) => Message;
198
198
  };
199
199
  OnWheel: {
200
200
  readonly message: Message;
@@ -972,7 +972,7 @@ export declare const html: <Message = never>() => {
972
972
  readonly message: Message;
973
973
  } | {
974
974
  readonly _tag: "OnScroll";
975
- readonly message: Message;
975
+ readonly f: (scrollTop: number) => Message;
976
976
  } | {
977
977
  readonly _tag: "OnWheel";
978
978
  readonly message: Message;
@@ -1783,9 +1783,9 @@ export declare const html: <Message = never>() => {
1783
1783
  readonly _tag: "OnReset";
1784
1784
  readonly message: Message;
1785
1785
  };
1786
- OnScroll: (message: Message) => {
1786
+ OnScroll: (f: (scrollTop: number) => Message) => {
1787
1787
  readonly _tag: "OnScroll";
1788
- readonly message: Message;
1788
+ readonly f: (scrollTop: number) => Message;
1789
1789
  };
1790
1790
  OnWheel: (message: Message) => {
1791
1791
  readonly _tag: "OnWheel";