@moldable-ai/ui 0.2.19 → 0.2.21
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.
- package/dist/catalog/catalog-coverage.d.ts +15 -0
- package/dist/catalog/catalog-coverage.d.ts.map +1 -0
- package/dist/catalog/catalog-coverage.js +44 -0
- package/dist/catalog/catalog-manifest.d.ts +6 -0
- package/dist/catalog/catalog-manifest.d.ts.map +1 -0
- package/dist/catalog/catalog-manifest.js +117 -0
- package/dist/catalog/catalog-types.d.ts +64 -0
- package/dist/catalog/catalog-types.d.ts.map +1 -0
- package/dist/catalog/catalog-types.js +20 -0
- package/dist/catalog/component-catalog.d.ts +14 -0
- package/dist/catalog/component-catalog.d.ts.map +1 -0
- package/dist/catalog/component-catalog.js +49 -0
- package/dist/catalog/core-catalog-exclusions.d.ts +8 -0
- package/dist/catalog/core-catalog-exclusions.d.ts.map +1 -0
- package/dist/catalog/core-catalog-exclusions.js +151 -0
- package/dist/catalog/core-catalog.d.ts +2 -0
- package/dist/catalog/core-catalog.d.ts.map +1 -0
- package/dist/catalog/core-catalog.js +19 -0
- package/dist/catalog/core-collection-stories.d.ts +3 -0
- package/dist/catalog/core-collection-stories.d.ts.map +1 -0
- package/dist/catalog/core-collection-stories.js +42 -0
- package/dist/catalog/core-control-stories.d.ts +3 -0
- package/dist/catalog/core-control-stories.d.ts.map +1 -0
- package/dist/catalog/core-control-stories.js +94 -0
- package/dist/catalog/core-feedback-stories.d.ts +3 -0
- package/dist/catalog/core-feedback-stories.d.ts.map +1 -0
- package/dist/catalog/core-feedback-stories.js +50 -0
- package/dist/catalog/core-foundation-stories.d.ts +3 -0
- package/dist/catalog/core-foundation-stories.d.ts.map +1 -0
- package/dist/catalog/core-foundation-stories.js +53 -0
- package/dist/catalog/core-navigation-stories.d.ts +3 -0
- package/dist/catalog/core-navigation-stories.d.ts.map +1 -0
- package/dist/catalog/core-navigation-stories.js +25 -0
- package/dist/catalog/core-overlay-stories.d.ts +3 -0
- package/dist/catalog/core-overlay-stories.d.ts.map +1 -0
- package/dist/catalog/core-overlay-stories.js +20 -0
- package/dist/catalog/index.d.ts +7 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +6 -0
- package/dist/components/app-error-boundary.d.ts +1 -0
- package/dist/components/app-error-boundary.d.ts.map +1 -1
- package/dist/components/app-error-boundary.js +13 -0
- package/dist/components/chat/chat-image-lightbox.d.ts +9 -0
- package/dist/components/chat/chat-image-lightbox.d.ts.map +1 -0
- package/dist/components/chat/chat-image-lightbox.js +9 -0
- package/dist/components/chat/chat-message.d.ts +19 -1
- package/dist/components/chat/chat-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message.js +66 -16
- package/dist/components/chat/chat-messages.d.ts +5 -2
- package/dist/components/chat/chat-messages.d.ts.map +1 -1
- package/dist/components/chat/chat-messages.js +61 -6
- package/dist/components/chat/chat-panel.d.ts +49 -6
- package/dist/components/chat/chat-panel.d.ts.map +1 -1
- package/dist/components/chat/chat-panel.js +196 -163
- package/dist/components/chat/conversation-history.d.ts +39 -5
- package/dist/components/chat/conversation-history.d.ts.map +1 -1
- package/dist/components/chat/conversation-history.js +192 -26
- package/dist/components/chat/index.d.ts +2 -2
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/tool-handlers.d.ts.map +1 -1
- package/dist/components/chat/tool-handlers.js +40 -2
- package/dist/components/chat/vertical-scroll-fade.d.ts +6 -0
- package/dist/components/chat/vertical-scroll-fade.d.ts.map +1 -0
- package/dist/components/chat/vertical-scroll-fade.js +12 -0
- package/dist/components/native-capabilities/bluetooth-panel-helpers.d.ts +9 -0
- package/dist/components/native-capabilities/bluetooth-panel-helpers.d.ts.map +1 -0
- package/dist/components/native-capabilities/bluetooth-panel-helpers.js +37 -0
- package/dist/components/native-capabilities/bluetooth-panel.d.ts +56 -0
- package/dist/components/native-capabilities/bluetooth-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/bluetooth-panel.js +470 -0
- package/dist/components/native-capabilities/camera-preview.d.ts +39 -0
- package/dist/components/native-capabilities/camera-preview.d.ts.map +1 -0
- package/dist/components/native-capabilities/camera-preview.js +228 -0
- package/dist/components/native-capabilities/capability-badge.d.ts +23 -0
- package/dist/components/native-capabilities/capability-badge.d.ts.map +1 -0
- package/dist/components/native-capabilities/capability-badge.js +39 -0
- package/dist/components/native-capabilities/capability-matrix.d.ts +14 -0
- package/dist/components/native-capabilities/capability-matrix.d.ts.map +1 -0
- package/dist/components/native-capabilities/capability-matrix.js +97 -0
- package/dist/components/native-capabilities/clipboard-button.d.ts +26 -0
- package/dist/components/native-capabilities/clipboard-button.d.ts.map +1 -0
- package/dist/components/native-capabilities/clipboard-button.js +99 -0
- package/dist/components/native-capabilities/device-io-controls.d.ts +4 -0
- package/dist/components/native-capabilities/device-io-controls.d.ts.map +1 -0
- package/dist/components/native-capabilities/device-io-controls.js +28 -0
- package/dist/components/native-capabilities/device-list.d.ts +20 -0
- package/dist/components/native-capabilities/device-list.d.ts.map +1 -0
- package/dist/components/native-capabilities/device-list.js +14 -0
- package/dist/components/native-capabilities/display-map.d.ts +23 -0
- package/dist/components/native-capabilities/display-map.d.ts.map +1 -0
- package/dist/components/native-capabilities/display-map.js +114 -0
- package/dist/components/native-capabilities/haptic-button.d.ts +26 -0
- package/dist/components/native-capabilities/haptic-button.d.ts.map +1 -0
- package/dist/components/native-capabilities/haptic-button.js +94 -0
- package/dist/components/native-capabilities/hid-panel.d.ts +40 -0
- package/dist/components/native-capabilities/hid-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/hid-panel.js +373 -0
- package/dist/components/native-capabilities/index.d.ts +27 -0
- package/dist/components/native-capabilities/index.d.ts.map +1 -0
- package/dist/components/native-capabilities/index.js +26 -0
- package/dist/components/native-capabilities/live-indicator.d.ts +15 -0
- package/dist/components/native-capabilities/live-indicator.d.ts.map +1 -0
- package/dist/components/native-capabilities/live-indicator.js +26 -0
- package/dist/components/native-capabilities/local-auth-button.d.ts +33 -0
- package/dist/components/native-capabilities/local-auth-button.d.ts.map +1 -0
- package/dist/components/native-capabilities/local-auth-button.js +115 -0
- package/dist/components/native-capabilities/location-panel.d.ts +29 -0
- package/dist/components/native-capabilities/location-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/location-panel.js +146 -0
- package/dist/components/native-capabilities/microphone-meter.d.ts +35 -0
- package/dist/components/native-capabilities/microphone-meter.d.ts.map +1 -0
- package/dist/components/native-capabilities/microphone-meter.js +244 -0
- package/dist/components/native-capabilities/midi-monitor.d.ts +54 -0
- package/dist/components/native-capabilities/midi-monitor.d.ts.map +1 -0
- package/dist/components/native-capabilities/midi-monitor.js +534 -0
- package/dist/components/native-capabilities/native-capability-panel.d.ts +11 -0
- package/dist/components/native-capabilities/native-capability-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/native-capability-panel.js +28 -0
- package/dist/components/native-capabilities/notification-panel.d.ts +24 -0
- package/dist/components/native-capabilities/notification-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/notification-panel.js +124 -0
- package/dist/components/native-capabilities/power-blocker-controller.d.ts +19 -0
- package/dist/components/native-capabilities/power-blocker-controller.d.ts.map +1 -0
- package/dist/components/native-capabilities/power-blocker-controller.js +83 -0
- package/dist/components/native-capabilities/power-panel.d.ts +67 -0
- package/dist/components/native-capabilities/power-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/power-panel.js +309 -0
- package/dist/components/native-capabilities/readout.d.ts +10 -0
- package/dist/components/native-capabilities/readout.d.ts.map +1 -0
- package/dist/components/native-capabilities/readout.js +11 -0
- package/dist/components/native-capabilities/screen-share-preview.d.ts +36 -0
- package/dist/components/native-capabilities/screen-share-preview.d.ts.map +1 -0
- package/dist/components/native-capabilities/screen-share-preview.js +191 -0
- package/dist/components/native-capabilities/secure-storage-panel.d.ts +36 -0
- package/dist/components/native-capabilities/secure-storage-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/secure-storage-panel.js +193 -0
- package/dist/components/native-capabilities/serial-console.d.ts +64 -0
- package/dist/components/native-capabilities/serial-console.d.ts.map +1 -0
- package/dist/components/native-capabilities/serial-console.js +399 -0
- package/dist/components/native-capabilities/shortcut-recorder.d.ts +35 -0
- package/dist/components/native-capabilities/shortcut-recorder.d.ts.map +1 -0
- package/dist/components/native-capabilities/shortcut-recorder.js +217 -0
- package/dist/components/native-capabilities/stream-log.d.ts +35 -0
- package/dist/components/native-capabilities/stream-log.d.ts.map +1 -0
- package/dist/components/native-capabilities/stream-log.js +65 -0
- package/dist/components/native-capabilities/system-audio-monitor.d.ts +34 -0
- package/dist/components/native-capabilities/system-audio-monitor.d.ts.map +1 -0
- package/dist/components/native-capabilities/system-audio-monitor.js +275 -0
- package/dist/components/native-capabilities/usb-panel.d.ts +54 -0
- package/dist/components/native-capabilities/usb-panel.d.ts.map +1 -0
- package/dist/components/native-capabilities/usb-panel.js +363 -0
- package/dist/components/native-capabilities/use-capability.d.ts +20 -0
- package/dist/components/native-capabilities/use-capability.d.ts.map +1 -0
- package/dist/components/native-capabilities/use-capability.js +61 -0
- package/dist/components/ui/alert-dialog.d.ts +7 -3
- package/dist/components/ui/alert-dialog.d.ts.map +1 -1
- package/dist/components/ui/alert-dialog.js +9 -5
- package/dist/components/ui/alert.d.ts +19 -3
- package/dist/components/ui/alert.d.ts.map +1 -1
- package/dist/components/ui/alert.js +25 -8
- package/dist/components/ui/app-frame.d.ts +28 -0
- package/dist/components/ui/app-frame.d.ts.map +1 -0
- package/dist/components/ui/app-frame.js +95 -0
- package/dist/components/ui/button-group.d.ts +1 -1
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/collapsible.d.ts +9 -1
- package/dist/components/ui/collapsible.d.ts.map +1 -1
- package/dist/components/ui/collapsible.js +13 -2
- package/dist/components/ui/collection-navigation.d.ts +8 -0
- package/dist/components/ui/collection-navigation.d.ts.map +1 -0
- package/dist/components/ui/collection-navigation.js +19 -0
- package/dist/components/ui/color-well.d.ts +13 -0
- package/dist/components/ui/color-well.d.ts.map +1 -0
- package/dist/components/ui/color-well.js +30 -0
- package/dist/components/ui/command.d.ts +5 -1
- package/dist/components/ui/command.d.ts.map +1 -1
- package/dist/components/ui/command.js +13 -1
- package/dist/components/ui/confirm-dialog.d.ts +19 -0
- package/dist/components/ui/confirm-dialog.d.ts.map +1 -0
- package/dist/components/ui/confirm-dialog.js +45 -0
- package/dist/components/ui/date-field.d.ts +19 -0
- package/dist/components/ui/date-field.d.ts.map +1 -0
- package/dist/components/ui/date-field.js +54 -0
- package/dist/components/ui/date-picker.d.ts +24 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +92 -0
- package/dist/components/ui/dialog.d.ts +4 -2
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +8 -5
- package/dist/components/ui/edge-fade.d.ts +11 -0
- package/dist/components/ui/edge-fade.d.ts.map +1 -0
- package/dist/components/ui/edge-fade.js +41 -0
- package/dist/components/ui/empty.d.ts +7 -2
- package/dist/components/ui/empty.d.ts.map +1 -1
- package/dist/components/ui/empty.js +20 -7
- package/dist/components/ui/field.d.ts +1 -1
- package/dist/components/ui/grid.d.ts +24 -0
- package/dist/components/ui/grid.d.ts.map +1 -0
- package/dist/components/ui/grid.js +258 -0
- package/dist/components/ui/icon-button.d.ts +15 -0
- package/dist/components/ui/icon-button.d.ts.map +1 -0
- package/dist/components/ui/icon-button.js +15 -0
- package/dist/components/ui/index.d.ts +23 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +23 -0
- package/dist/components/ui/input-group.d.ts +1 -1
- package/dist/components/ui/inspector.d.ts +15 -0
- package/dist/components/ui/inspector.d.ts.map +1 -0
- package/dist/components/ui/inspector.js +26 -0
- package/dist/components/ui/kbd.d.ts +1 -1
- package/dist/components/ui/list.d.ts +28 -0
- package/dist/components/ui/list.d.ts.map +1 -0
- package/dist/components/ui/list.js +235 -0
- package/dist/components/ui/material.d.ts +34 -0
- package/dist/components/ui/material.d.ts.map +1 -0
- package/dist/components/ui/material.js +34 -0
- package/dist/components/ui/message-scroller-controller.d.ts +162 -0
- package/dist/components/ui/message-scroller-controller.d.ts.map +1 -0
- package/dist/components/ui/message-scroller-controller.js +888 -0
- package/dist/components/ui/message-scroller-layout-stability.d.ts +33 -0
- package/dist/components/ui/message-scroller-layout-stability.d.ts.map +1 -0
- package/dist/components/ui/message-scroller-layout-stability.js +153 -0
- package/dist/components/ui/message-scroller-policy.d.ts +74 -0
- package/dist/components/ui/message-scroller-policy.d.ts.map +1 -0
- package/dist/components/ui/message-scroller-policy.js +129 -0
- package/dist/components/ui/message-scroller-primitive.d.ts +55 -0
- package/dist/components/ui/message-scroller-primitive.d.ts.map +1 -0
- package/dist/components/ui/message-scroller-primitive.js +226 -0
- package/dist/components/ui/message-scroller.d.ts +11 -0
- package/dist/components/ui/message-scroller.d.ts.map +1 -0
- package/dist/components/ui/message-scroller.js +25 -0
- package/dist/components/ui/navigation-button-group.d.ts +16 -0
- package/dist/components/ui/navigation-button-group.d.ts.map +1 -0
- package/dist/components/ui/navigation-button-group.js +9 -0
- package/dist/components/ui/notification-dot.d.ts +4 -0
- package/dist/components/ui/notification-dot.d.ts.map +1 -0
- package/dist/components/ui/notification-dot.js +7 -0
- package/dist/components/ui/number-input.d.ts +16 -0
- package/dist/components/ui/number-input.d.ts.map +1 -0
- package/dist/components/ui/number-input.js +56 -0
- package/dist/components/ui/panel.d.ts +18 -0
- package/dist/components/ui/panel.d.ts.map +1 -0
- package/dist/components/ui/panel.js +40 -0
- package/dist/components/ui/popover.d.ts +9 -2
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +14 -4
- package/dist/components/ui/scroll-area.d.ts +8 -2
- package/dist/components/ui/scroll-area.d.ts.map +1 -1
- package/dist/components/ui/scroll-area.js +6 -3
- package/dist/components/ui/search-field.d.ts +19 -0
- package/dist/components/ui/search-field.d.ts.map +1 -0
- package/dist/components/ui/search-field.js +64 -0
- package/dist/components/ui/segmented-control.d.ts +21 -0
- package/dist/components/ui/segmented-control.d.ts.map +1 -0
- package/dist/components/ui/segmented-control.js +62 -0
- package/dist/components/ui/sheet.d.ts +4 -2
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +12 -9
- package/dist/components/ui/sidebar.js +1 -1
- package/dist/components/ui/sonner.d.ts +4 -3
- package/dist/components/ui/sonner.d.ts.map +1 -1
- package/dist/components/ui/sonner.js +7 -6
- package/dist/components/ui/split-view.d.ts +11 -0
- package/dist/components/ui/split-view.d.ts.map +1 -0
- package/dist/components/ui/split-view.js +13 -0
- package/dist/components/ui/status.d.ts +13 -0
- package/dist/components/ui/status.d.ts.map +1 -0
- package/dist/components/ui/status.js +36 -0
- package/dist/components/ui/table.d.ts +11 -2
- package/dist/components/ui/table.d.ts.map +1 -1
- package/dist/components/ui/table.js +33 -6
- package/dist/components/ui/text.d.ts +17 -0
- package/dist/components/ui/text.d.ts.map +1 -0
- package/dist/components/ui/text.js +55 -0
- package/dist/components/ui/toggle.d.ts +12 -4
- package/dist/components/ui/toggle.d.ts.map +1 -1
- package/dist/components/ui/toggle.js +13 -2
- package/dist/components/ui/toolbar.d.ts +27 -0
- package/dist/components/ui/toolbar.d.ts.map +1 -0
- package/dist/components/ui/toolbar.js +71 -0
- package/dist/components/ui/tooltip.d.ts +8 -3
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip.js +11 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/lib/frame-lifecycle.d.ts +29 -0
- package/dist/lib/frame-lifecycle.d.ts.map +1 -1
- package/dist/lib/frame-lifecycle.js +85 -0
- package/dist/lib/native-capabilities/access.d.ts +13 -0
- package/dist/lib/native-capabilities/access.d.ts.map +1 -0
- package/dist/lib/native-capabilities/access.js +53 -0
- package/dist/lib/native-capabilities/bluetooth-access.d.ts +14 -0
- package/dist/lib/native-capabilities/bluetooth-access.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bluetooth-access.js +43 -0
- package/dist/lib/native-capabilities/bluetooth-connection.d.ts +19 -0
- package/dist/lib/native-capabilities/bluetooth-connection.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bluetooth-connection.js +88 -0
- package/dist/lib/native-capabilities/bluetooth-gatt.d.ts +7 -0
- package/dist/lib/native-capabilities/bluetooth-gatt.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bluetooth-gatt.js +77 -0
- package/dist/lib/native-capabilities/bluetooth-internal.d.ts +10 -0
- package/dist/lib/native-capabilities/bluetooth-internal.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bluetooth-internal.js +105 -0
- package/dist/lib/native-capabilities/bluetooth-native-fallback.d.ts +33 -0
- package/dist/lib/native-capabilities/bluetooth-native-fallback.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bluetooth-native-fallback.js +214 -0
- package/dist/lib/native-capabilities/bluetooth-types.d.ts +85 -0
- package/dist/lib/native-capabilities/bluetooth-types.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bluetooth-types.js +2 -0
- package/dist/lib/native-capabilities/bluetooth.d.ts +7 -0
- package/dist/lib/native-capabilities/bluetooth.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bluetooth.js +4 -0
- package/dist/lib/native-capabilities/bridge.d.ts +23 -0
- package/dist/lib/native-capabilities/bridge.d.ts.map +1 -0
- package/dist/lib/native-capabilities/bridge.js +266 -0
- package/dist/lib/native-capabilities/capabilities.d.ts +7 -0
- package/dist/lib/native-capabilities/capabilities.d.ts.map +1 -0
- package/dist/lib/native-capabilities/capabilities.js +95 -0
- package/dist/lib/native-capabilities/clipboard.d.ts +11 -0
- package/dist/lib/native-capabilities/clipboard.d.ts.map +1 -0
- package/dist/lib/native-capabilities/clipboard.js +58 -0
- package/dist/lib/native-capabilities/device-io-errors.d.ts +11 -0
- package/dist/lib/native-capabilities/device-io-errors.d.ts.map +1 -0
- package/dist/lib/native-capabilities/device-io-errors.js +82 -0
- package/dist/lib/native-capabilities/displays.d.ts +38 -0
- package/dist/lib/native-capabilities/displays.d.ts.map +1 -0
- package/dist/lib/native-capabilities/displays.js +12 -0
- package/dist/lib/native-capabilities/global-shortcuts.d.ts +86 -0
- package/dist/lib/native-capabilities/global-shortcuts.d.ts.map +1 -0
- package/dist/lib/native-capabilities/global-shortcuts.js +351 -0
- package/dist/lib/native-capabilities/haptics.d.ts +25 -0
- package/dist/lib/native-capabilities/haptics.d.ts.map +1 -0
- package/dist/lib/native-capabilities/haptics.js +18 -0
- package/dist/lib/native-capabilities/hid-native.d.ts +26 -0
- package/dist/lib/native-capabilities/hid-native.d.ts.map +1 -0
- package/dist/lib/native-capabilities/hid-native.js +229 -0
- package/dist/lib/native-capabilities/hid.d.ts +52 -0
- package/dist/lib/native-capabilities/hid.d.ts.map +1 -0
- package/dist/lib/native-capabilities/hid.js +163 -0
- package/dist/lib/native-capabilities/index.d.ts +40 -0
- package/dist/lib/native-capabilities/index.d.ts.map +1 -0
- package/dist/lib/native-capabilities/index.js +21 -0
- package/dist/lib/native-capabilities/local-authentication.d.ts +32 -0
- package/dist/lib/native-capabilities/local-authentication.d.ts.map +1 -0
- package/dist/lib/native-capabilities/local-authentication.js +46 -0
- package/dist/lib/native-capabilities/location.d.ts +49 -0
- package/dist/lib/native-capabilities/location.d.ts.map +1 -0
- package/dist/lib/native-capabilities/location.js +114 -0
- package/dist/lib/native-capabilities/media-permissions.d.ts +28 -0
- package/dist/lib/native-capabilities/media-permissions.d.ts.map +1 -0
- package/dist/lib/native-capabilities/media-permissions.js +37 -0
- package/dist/lib/native-capabilities/media.d.ts +17 -0
- package/dist/lib/native-capabilities/media.d.ts.map +1 -0
- package/dist/lib/native-capabilities/media.js +104 -0
- package/dist/lib/native-capabilities/midi-access.d.ts +13 -0
- package/dist/lib/native-capabilities/midi-access.d.ts.map +1 -0
- package/dist/lib/native-capabilities/midi-access.js +68 -0
- package/dist/lib/native-capabilities/midi-internal.d.ts +12 -0
- package/dist/lib/native-capabilities/midi-internal.d.ts.map +1 -0
- package/dist/lib/native-capabilities/midi-internal.js +88 -0
- package/dist/lib/native-capabilities/midi-native-fallback.d.ts +27 -0
- package/dist/lib/native-capabilities/midi-native-fallback.d.ts.map +1 -0
- package/dist/lib/native-capabilities/midi-native-fallback.js +127 -0
- package/dist/lib/native-capabilities/midi-ports.d.ts +6 -0
- package/dist/lib/native-capabilities/midi-ports.d.ts.map +1 -0
- package/dist/lib/native-capabilities/midi-ports.js +115 -0
- package/dist/lib/native-capabilities/midi-types.d.ts +25 -0
- package/dist/lib/native-capabilities/midi-types.d.ts.map +1 -0
- package/dist/lib/native-capabilities/midi-types.js +1 -0
- package/dist/lib/native-capabilities/midi.d.ts +6 -0
- package/dist/lib/native-capabilities/midi.d.ts.map +1 -0
- package/dist/lib/native-capabilities/midi.js +3 -0
- package/dist/lib/native-capabilities/native-device-io.d.ts +28 -0
- package/dist/lib/native-capabilities/native-device-io.d.ts.map +1 -0
- package/dist/lib/native-capabilities/native-device-io.js +91 -0
- package/dist/lib/native-capabilities/native-device-lifecycle.d.ts +13 -0
- package/dist/lib/native-capabilities/native-device-lifecycle.d.ts.map +1 -0
- package/dist/lib/native-capabilities/native-device-lifecycle.js +40 -0
- package/dist/lib/native-capabilities/notifications.d.ts +38 -0
- package/dist/lib/native-capabilities/notifications.d.ts.map +1 -0
- package/dist/lib/native-capabilities/notifications.js +50 -0
- package/dist/lib/native-capabilities/power-session.d.ts +113 -0
- package/dist/lib/native-capabilities/power-session.d.ts.map +1 -0
- package/dist/lib/native-capabilities/power-session.js +264 -0
- package/dist/lib/native-capabilities/secure-storage.d.ts +36 -0
- package/dist/lib/native-capabilities/secure-storage.d.ts.map +1 -0
- package/dist/lib/native-capabilities/secure-storage.js +64 -0
- package/dist/lib/native-capabilities/serial-native.d.ts +23 -0
- package/dist/lib/native-capabilities/serial-native.d.ts.map +1 -0
- package/dist/lib/native-capabilities/serial-native.js +181 -0
- package/dist/lib/native-capabilities/serial.d.ts +76 -0
- package/dist/lib/native-capabilities/serial.d.ts.map +1 -0
- package/dist/lib/native-capabilities/serial.js +215 -0
- package/dist/lib/native-capabilities/system-audio.d.ts +127 -0
- package/dist/lib/native-capabilities/system-audio.d.ts.map +1 -0
- package/dist/lib/native-capabilities/system-audio.js +330 -0
- package/dist/lib/native-capabilities/types.d.ts +62 -0
- package/dist/lib/native-capabilities/types.d.ts.map +1 -0
- package/dist/lib/native-capabilities/types.js +27 -0
- package/dist/lib/native-capabilities/usb-native.d.ts +39 -0
- package/dist/lib/native-capabilities/usb-native.d.ts.map +1 -0
- package/dist/lib/native-capabilities/usb-native.js +217 -0
- package/dist/lib/native-capabilities/usb.d.ts +85 -0
- package/dist/lib/native-capabilities/usb.d.ts.map +1 -0
- package/dist/lib/native-capabilities/usb.js +225 -0
- package/dist/lib/theme.d.ts.map +1 -1
- package/dist/lib/theme.js +2 -1
- package/docs/README.md +39 -0
- package/docs/accessibility.md +32 -0
- package/docs/adaptive-materials.md +79 -0
- package/docs/app-anatomy.md +74 -0
- package/docs/component-catalog.md +82 -0
- package/docs/component-guide-template.md +37 -0
- package/docs/design-tokens.md +185 -0
- package/docs/foundations.md +58 -0
- package/docs/native-integration.md +33 -0
- package/docs/standalone-app-windows.md +136 -0
- package/package.json +8 -3
- package/src/components/ui/alert-dialog.md +31 -0
- package/src/components/ui/alert.md +77 -0
- package/src/components/ui/app-frame.md +38 -0
- package/src/components/ui/badge.md +57 -0
- package/src/components/ui/button.md +76 -0
- package/src/components/ui/collapsible.md +52 -0
- package/src/components/ui/color-well.md +11 -0
- package/src/components/ui/combobox.md +42 -0
- package/src/components/ui/command.md +54 -0
- package/src/components/ui/date-field.md +32 -0
- package/src/components/ui/date-picker.md +55 -0
- package/src/components/ui/dialog.md +40 -0
- package/src/components/ui/edge-fade.md +7 -0
- package/src/components/ui/empty.md +87 -0
- package/src/components/ui/field.md +77 -0
- package/src/components/ui/grid.md +109 -0
- package/src/components/ui/icon-button.md +36 -0
- package/src/components/ui/inspector.md +11 -0
- package/src/components/ui/item.md +87 -0
- package/src/components/ui/list.md +140 -0
- package/src/components/ui/material.md +51 -0
- package/src/components/ui/navigation-button-group.md +46 -0
- package/src/components/ui/notification-dot.md +7 -0
- package/src/components/ui/number-input.md +19 -0
- package/src/components/ui/panel.md +11 -0
- package/src/components/ui/popover.md +31 -0
- package/src/components/ui/resizable.md +66 -0
- package/src/components/ui/scroll-area.md +72 -0
- package/src/components/ui/search-field.md +33 -0
- package/src/components/ui/segmented-control.md +36 -0
- package/src/components/ui/sheet.md +30 -0
- package/src/components/ui/sidebar.md +113 -0
- package/src/components/ui/sonner.md +37 -0
- package/src/components/ui/split-view.md +15 -0
- package/src/components/ui/status.md +12 -0
- package/src/components/ui/table.md +37 -0
- package/src/components/ui/tabs.md +66 -0
- package/src/components/ui/text.md +17 -0
- package/src/components/ui/toggle-button.md +48 -0
- package/src/components/ui/toggle-group.md +80 -0
- package/src/components/ui/toggle.md +73 -0
- package/src/components/ui/toolbar.md +26 -0
- package/src/components/ui/tooltip.md +31 -0
- package/src/styles/index.css +704 -3
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
import { MessageScrollerPolicy } from './message-scroller-policy';
|
|
2
|
+
const READING_LEAF_SELECTOR = '[data-message-scroll-anchor]';
|
|
3
|
+
const POSITION_EPSILON_PX = 0.5;
|
|
4
|
+
const WARMUP_BATCH_SIZE = 3;
|
|
5
|
+
const PROGRAMMATIC_SCROLL_TIMEOUT_MS = 800;
|
|
6
|
+
function now() {
|
|
7
|
+
return typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
8
|
+
}
|
|
9
|
+
const EMPTY_SCROLLABLE = { start: false, end: false };
|
|
10
|
+
const EMPTY_VISIBILITY = {
|
|
11
|
+
currentAnchorId: null,
|
|
12
|
+
visibleMessageIds: [],
|
|
13
|
+
};
|
|
14
|
+
export function createExternalStore(initial, isEqual) {
|
|
15
|
+
let snapshot = initial;
|
|
16
|
+
const listeners = new Set();
|
|
17
|
+
return {
|
|
18
|
+
getSnapshot: () => snapshot,
|
|
19
|
+
hasListeners: () => listeners.size > 0,
|
|
20
|
+
setSnapshot: (next) => {
|
|
21
|
+
if (isEqual(snapshot, next))
|
|
22
|
+
return;
|
|
23
|
+
snapshot = next;
|
|
24
|
+
listeners.forEach((listener) => listener());
|
|
25
|
+
},
|
|
26
|
+
subscribe: (listener, onFirst, onLast) => {
|
|
27
|
+
const wasEmpty = listeners.size === 0;
|
|
28
|
+
listeners.add(listener);
|
|
29
|
+
if (wasEmpty)
|
|
30
|
+
onFirst?.();
|
|
31
|
+
return () => {
|
|
32
|
+
listeners.delete(listener);
|
|
33
|
+
if (listeners.size === 0)
|
|
34
|
+
onLast?.();
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function scrollableEquals(a, b) {
|
|
40
|
+
return a.start === b.start && a.end === b.end;
|
|
41
|
+
}
|
|
42
|
+
function visibilityEquals(a, b) {
|
|
43
|
+
return (a.currentAnchorId === b.currentAnchorId &&
|
|
44
|
+
a.visibleMessageIds.length === b.visibleMessageIds.length &&
|
|
45
|
+
a.visibleMessageIds.every((id, index) => id === b.visibleMessageIds[index]));
|
|
46
|
+
}
|
|
47
|
+
export function isMessageScrollerAtEnd(viewport, threshold) {
|
|
48
|
+
return maxScrollTop(viewport) - viewport.scrollTop <= threshold;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Snapshot of the stable leaves the reader can currently see, preferring
|
|
52
|
+
* fine-grained `[data-message-scroll-anchor]` leaves (their tops do not move
|
|
53
|
+
* when a disclosure collapses inside the same message) over whole items.
|
|
54
|
+
*/
|
|
55
|
+
export function captureReadingLeaves(viewport, content, items) {
|
|
56
|
+
const leaves = [
|
|
57
|
+
...Array.from(content.querySelectorAll(READING_LEAF_SELECTOR)),
|
|
58
|
+
...items,
|
|
59
|
+
];
|
|
60
|
+
const viewportRect = viewport.getBoundingClientRect();
|
|
61
|
+
return leaves.map((element) => {
|
|
62
|
+
const rect = element.getBoundingClientRect();
|
|
63
|
+
return {
|
|
64
|
+
element,
|
|
65
|
+
top: rect.top - viewportRect.top,
|
|
66
|
+
bottom: rect.bottom - viewportRect.top,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Re-pins the first still-visible captured leaf after content above it
|
|
72
|
+
* changed size. Native scroll anchoring may already have compensated; the
|
|
73
|
+
* measured delta is zero then and this is deliberately a no-op.
|
|
74
|
+
*/
|
|
75
|
+
export function restoreReadingLeaves(viewport, leaves) {
|
|
76
|
+
const visible = leaves.find(({ element, top, bottom }) => {
|
|
77
|
+
if (!element.isConnected)
|
|
78
|
+
return false;
|
|
79
|
+
const rect = element.getBoundingClientRect();
|
|
80
|
+
return rect.height > 0 && bottom > 0 && top < viewport.clientHeight;
|
|
81
|
+
});
|
|
82
|
+
if (!visible)
|
|
83
|
+
return false;
|
|
84
|
+
const delta = elementViewportTop(visible.element, viewport) - visible.top;
|
|
85
|
+
if (Math.abs(delta) <= POSITION_EPSILON_PX)
|
|
86
|
+
return false;
|
|
87
|
+
viewport.scrollTop += delta;
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The single DOM-side owner of transcript scrolling. Every behavior that can
|
|
92
|
+
* move the viewport — follow-the-end, turn anchoring, prepend preservation,
|
|
93
|
+
* reading-position stability while rows above collapse — routes through this
|
|
94
|
+
* controller and its {@link MessageScrollerPolicy}, so nothing here can fight
|
|
95
|
+
* the reader (or another controller) for the scroll position.
|
|
96
|
+
*/
|
|
97
|
+
export class MessageScrollerController {
|
|
98
|
+
scrollableStore = createExternalStore(EMPTY_SCROLLABLE, scrollableEquals);
|
|
99
|
+
visibilityStore = createExternalStore(EMPTY_VISIBILITY, visibilityEquals);
|
|
100
|
+
policy;
|
|
101
|
+
options;
|
|
102
|
+
preserveScrollOnPrepend = true;
|
|
103
|
+
root = null;
|
|
104
|
+
viewport = null;
|
|
105
|
+
content = null;
|
|
106
|
+
spacer = null;
|
|
107
|
+
messageElements = new Map();
|
|
108
|
+
handledTurnAnchors = new WeakSet();
|
|
109
|
+
visibleMessageIds = new Set();
|
|
110
|
+
warmedItems = new WeakSet();
|
|
111
|
+
lastScrollTop = 0;
|
|
112
|
+
previousFirstItem = null;
|
|
113
|
+
previousItemCount = 0;
|
|
114
|
+
anchoredElement = null;
|
|
115
|
+
trackedFirstVisible = null;
|
|
116
|
+
readingLeaves = [];
|
|
117
|
+
pendingScrollToMessage = null;
|
|
118
|
+
defaultPositionApplied = false;
|
|
119
|
+
spacerHeight = 0;
|
|
120
|
+
spacerGap = 0;
|
|
121
|
+
programmaticDeadline = 0;
|
|
122
|
+
stateFrame = null;
|
|
123
|
+
visibilityFrame = null;
|
|
124
|
+
warmupHandle = null;
|
|
125
|
+
warmupUsesIdleCallback = false;
|
|
126
|
+
intersectionObserver = null;
|
|
127
|
+
constructor(options) {
|
|
128
|
+
this.options = options;
|
|
129
|
+
this.policy = new MessageScrollerPolicy(options.autoScroll);
|
|
130
|
+
}
|
|
131
|
+
get viewportElement() {
|
|
132
|
+
return this.viewport;
|
|
133
|
+
}
|
|
134
|
+
// MARK: - Element wiring
|
|
135
|
+
setRoot(element) {
|
|
136
|
+
this.root = element;
|
|
137
|
+
if (element)
|
|
138
|
+
this.applyStateAttributes();
|
|
139
|
+
}
|
|
140
|
+
setViewport(element) {
|
|
141
|
+
this.viewport = element;
|
|
142
|
+
if (element) {
|
|
143
|
+
this.lastScrollTop = element.scrollTop;
|
|
144
|
+
this.applyStateAttributes();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
setContent(element) {
|
|
148
|
+
this.content = element;
|
|
149
|
+
}
|
|
150
|
+
setSpacer(element) {
|
|
151
|
+
this.spacer = element;
|
|
152
|
+
this.spacerGap = element ? readRowGap(element.parentElement) : 0;
|
|
153
|
+
}
|
|
154
|
+
registerMessage(messageId, element, previous) {
|
|
155
|
+
if (element) {
|
|
156
|
+
this.messageElements.set(messageId, element);
|
|
157
|
+
this.intersectionObserver?.observe(element);
|
|
158
|
+
this.scheduleVisibilitySync();
|
|
159
|
+
if (this.pendingScrollToMessage?.messageId === messageId) {
|
|
160
|
+
this.flushPendingScrollToMessage();
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (previous && this.messageElements.get(messageId) === previous) {
|
|
165
|
+
this.messageElements.delete(messageId);
|
|
166
|
+
this.visibleMessageIds.delete(messageId);
|
|
167
|
+
this.intersectionObserver?.unobserve(previous);
|
|
168
|
+
this.scheduleVisibilitySync();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// MARK: - Public scroll commands
|
|
172
|
+
scrollToEnd = (options = {}) => {
|
|
173
|
+
const viewport = this.viewport;
|
|
174
|
+
if (!viewport)
|
|
175
|
+
return false;
|
|
176
|
+
this.setSpacerHeight(0);
|
|
177
|
+
this.anchoredElement = null;
|
|
178
|
+
this.readingLeaves = [];
|
|
179
|
+
this.policy.jumpToEnd();
|
|
180
|
+
this.performScroll(maxScrollTop(viewport), options.behavior ?? 'auto');
|
|
181
|
+
this.scheduleVisibilitySync();
|
|
182
|
+
return true;
|
|
183
|
+
};
|
|
184
|
+
scrollToStart = (options = {}) => {
|
|
185
|
+
const viewport = this.viewport;
|
|
186
|
+
if (!viewport)
|
|
187
|
+
return false;
|
|
188
|
+
this.setSpacerHeight(0);
|
|
189
|
+
this.anchoredElement = null;
|
|
190
|
+
this.readingLeaves = [];
|
|
191
|
+
this.policy.jumpToStart();
|
|
192
|
+
this.performScroll(0, options.behavior ?? 'auto');
|
|
193
|
+
this.scheduleVisibilitySync();
|
|
194
|
+
return true;
|
|
195
|
+
};
|
|
196
|
+
scrollToMessage = (messageId, options) => {
|
|
197
|
+
const element = this.messageElements.get(messageId);
|
|
198
|
+
if (element) {
|
|
199
|
+
if (this.scrollToElement(element, options)) {
|
|
200
|
+
this.pendingScrollToMessage = null;
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
this.pendingScrollToMessage = { messageId, options };
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
if (this.previousItemCount === 0) {
|
|
207
|
+
this.pendingScrollToMessage = { messageId, options };
|
|
208
|
+
this.defaultPositionApplied = true;
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
};
|
|
213
|
+
scrollToElement(element, options = {}, { keepPreviousPeek = false } = {}) {
|
|
214
|
+
const viewport = this.viewport;
|
|
215
|
+
const content = this.content;
|
|
216
|
+
if (!viewport || !content || !content.contains(element))
|
|
217
|
+
return false;
|
|
218
|
+
const margin = (options.scrollMargin ?? this.options.scrollMargin) +
|
|
219
|
+
(keepPreviousPeek ? this.options.scrollPreviousItemPeek : 0);
|
|
220
|
+
const padding = readBlockPadding(content);
|
|
221
|
+
const target = alignedScrollTarget({
|
|
222
|
+
align: options.align ?? 'start',
|
|
223
|
+
element,
|
|
224
|
+
margin,
|
|
225
|
+
padding,
|
|
226
|
+
viewport,
|
|
227
|
+
});
|
|
228
|
+
// Growing the spacer first lets a late-transcript anchor actually reach
|
|
229
|
+
// the requested position even when little content follows it. The
|
|
230
|
+
// measurement must exclude the spacer itself: measuring through the
|
|
231
|
+
// content box would yield `required − current` instead of the absolute
|
|
232
|
+
// requirement, and the resulting resize feedback oscillated the spacer
|
|
233
|
+
// between the two values on every ResizeObserver tick.
|
|
234
|
+
const shortfall = target +
|
|
235
|
+
viewport.clientHeight -
|
|
236
|
+
contentBottomWithoutSpacer(viewport, content, this.itemChildren());
|
|
237
|
+
this.setSpacerHeight(Math.max(0, Math.ceil(shortfall)));
|
|
238
|
+
this.readingLeaves = [];
|
|
239
|
+
if (keepPreviousPeek) {
|
|
240
|
+
this.anchoredElement = element;
|
|
241
|
+
this.policy.anchorToTurn();
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.anchoredElement = null;
|
|
245
|
+
this.policy.beginJump();
|
|
246
|
+
}
|
|
247
|
+
this.performScroll(target, options.behavior ?? 'auto');
|
|
248
|
+
this.scheduleVisibilitySync();
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
// MARK: - DOM events
|
|
252
|
+
handleScroll() {
|
|
253
|
+
const viewport = this.viewport;
|
|
254
|
+
if (!viewport)
|
|
255
|
+
return;
|
|
256
|
+
if (this.policy.programmaticTarget !== null &&
|
|
257
|
+
now() > this.programmaticDeadline) {
|
|
258
|
+
this.policy.cancelProgrammaticScroll();
|
|
259
|
+
}
|
|
260
|
+
const scrollTop = viewport.scrollTop;
|
|
261
|
+
const userScrolled = this.policy.handleScrollEvent({
|
|
262
|
+
scrollTop,
|
|
263
|
+
lastScrollTop: this.lastScrollTop,
|
|
264
|
+
atEnd: this.isAtEnd(),
|
|
265
|
+
});
|
|
266
|
+
this.lastScrollTop = scrollTop;
|
|
267
|
+
if (userScrolled) {
|
|
268
|
+
if (this.policy.mode !== 'anchored')
|
|
269
|
+
this.anchoredElement = null;
|
|
270
|
+
// A snapshot captured before this ownership change describes a layout
|
|
271
|
+
// the reader has scrolled away from; restoring against it would
|
|
272
|
+
// teleport the viewport. A fresh capture lands next frame.
|
|
273
|
+
this.readingLeaves = [];
|
|
274
|
+
}
|
|
275
|
+
this.scheduleStateCommit();
|
|
276
|
+
this.scheduleVisibilitySync();
|
|
277
|
+
}
|
|
278
|
+
handleWheel(deltaY) {
|
|
279
|
+
this.policy.handleUserIntent(deltaY < 0 ? 'up' : 'down');
|
|
280
|
+
if (this.policy.mode !== 'anchored')
|
|
281
|
+
this.anchoredElement = null;
|
|
282
|
+
this.readingLeaves = [];
|
|
283
|
+
this.scheduleStateCommit();
|
|
284
|
+
}
|
|
285
|
+
handleTouchMove() {
|
|
286
|
+
this.policy.handleUserIntent('unknown');
|
|
287
|
+
this.anchoredElement = null;
|
|
288
|
+
this.readingLeaves = [];
|
|
289
|
+
this.scheduleStateCommit();
|
|
290
|
+
}
|
|
291
|
+
handleScrollKey() {
|
|
292
|
+
this.policy.handleUserIntent('unknown');
|
|
293
|
+
this.anchoredElement = null;
|
|
294
|
+
this.readingLeaves = [];
|
|
295
|
+
this.scheduleStateCommit();
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Content box resized (streaming growth, images decoding, disclosures
|
|
299
|
+
* collapsing, `content-visibility` reveals). Which way the viewport moves
|
|
300
|
+
* — if at all — is entirely the policy's call.
|
|
301
|
+
*/
|
|
302
|
+
handleResize() {
|
|
303
|
+
const viewport = this.viewport;
|
|
304
|
+
if (!viewport) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
switch (this.policy.resizeAction()) {
|
|
308
|
+
case 'stick-to-end': {
|
|
309
|
+
const target = maxScrollTop(viewport);
|
|
310
|
+
if (Math.abs(viewport.scrollTop - target) > POSITION_EPSILON_PX) {
|
|
311
|
+
this.performScroll(target, 'auto');
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'reanchor': {
|
|
316
|
+
const anchored = this.anchoredElement;
|
|
317
|
+
if (anchored?.isConnected) {
|
|
318
|
+
this.scrollToElement(anchored, { align: 'start' }, { keepPreviousPeek: true });
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
this.anchoredElement = null;
|
|
322
|
+
this.policy.release();
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case 'preserve-reading-position':
|
|
327
|
+
this.restoreReadingPosition();
|
|
328
|
+
break;
|
|
329
|
+
case 'none':
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
this.scheduleStateCommit();
|
|
333
|
+
this.scheduleVisibilitySync();
|
|
334
|
+
}
|
|
335
|
+
/** Children of the content element changed. Runs before the next paint. */
|
|
336
|
+
handleContentMutation() {
|
|
337
|
+
const children = this.itemChildren();
|
|
338
|
+
const previousFirst = this.previousFirstItem;
|
|
339
|
+
const previousCount = this.previousItemCount;
|
|
340
|
+
this.previousFirstItem = children[0] ?? null;
|
|
341
|
+
this.previousItemCount = children.length;
|
|
342
|
+
this.scheduleWarmup();
|
|
343
|
+
if (this.flushPendingScrollToMessage())
|
|
344
|
+
return;
|
|
345
|
+
if (previousCount === 0) {
|
|
346
|
+
// Everything present at mount is history; only anchors appended after
|
|
347
|
+
// this point can represent a new turn. Without this seeding, a later
|
|
348
|
+
// equal-count row replacement would treat the conversation's oldest
|
|
349
|
+
// prompt as "unhandled" and yank the viewport to the top.
|
|
350
|
+
this.markTurnAnchorsHandled(children, 0);
|
|
351
|
+
if (children.length > 0)
|
|
352
|
+
this.applyDefaultPosition(children);
|
|
353
|
+
this.scheduleStateCommit();
|
|
354
|
+
this.scheduleVisibilitySync();
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const previousFirstIndex = previousFirst
|
|
358
|
+
? children.indexOf(previousFirst)
|
|
359
|
+
: -1;
|
|
360
|
+
if (this.preserveScrollOnPrepend && previousFirstIndex > 0) {
|
|
361
|
+
this.markTurnAnchorsHandled(children, 0);
|
|
362
|
+
this.preservePrependPosition();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (children.length > previousCount) {
|
|
366
|
+
const appendedAnchor = firstTurnAnchorAt(children, previousCount);
|
|
367
|
+
if (appendedAnchor) {
|
|
368
|
+
this.markTurnAnchorsHandled(children, previousCount);
|
|
369
|
+
if (this.policy.followsEnd &&
|
|
370
|
+
countTurnAnchorsAt(children, previousCount) > 1) {
|
|
371
|
+
// Several anchors at once is a history restore, not a new turn.
|
|
372
|
+
this.scrollToEnd();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
this.anchorTurn(appendedAnchor);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (children.length === previousCount && this.policy.mode === 'anchored') {
|
|
380
|
+
// A terminal snapshot can replace the live turn's rows one-for-one
|
|
381
|
+
// with their persisted forms; keep that turn anchored. The live turn
|
|
382
|
+
// is the newest anchor, so scan from the end.
|
|
383
|
+
let replacement = null;
|
|
384
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
385
|
+
const child = children[index];
|
|
386
|
+
if (child?.dataset.scrollAnchor === 'true' &&
|
|
387
|
+
!this.handledTurnAnchors.has(child)) {
|
|
388
|
+
replacement = child;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (replacement) {
|
|
393
|
+
this.markTurnAnchorsHandled(children, 0);
|
|
394
|
+
this.anchorTurn(replacement);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (this.policy.followsEnd) {
|
|
399
|
+
this.scrollToEnd();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
this.scheduleStateCommit();
|
|
403
|
+
this.scheduleVisibilitySync();
|
|
404
|
+
}
|
|
405
|
+
observeVisibility() {
|
|
406
|
+
const viewport = this.viewport;
|
|
407
|
+
if (!viewport || !this.visibilityStore.hasListeners())
|
|
408
|
+
return;
|
|
409
|
+
if (typeof IntersectionObserver === 'undefined') {
|
|
410
|
+
this.scheduleVisibilitySync();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (!this.intersectionObserver) {
|
|
414
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
415
|
+
for (const entry of entries) {
|
|
416
|
+
const messageId = entry.target.dataset.messageId;
|
|
417
|
+
if (!messageId)
|
|
418
|
+
continue;
|
|
419
|
+
if (entry.isIntersecting)
|
|
420
|
+
this.visibleMessageIds.add(messageId);
|
|
421
|
+
else
|
|
422
|
+
this.visibleMessageIds.delete(messageId);
|
|
423
|
+
}
|
|
424
|
+
this.scheduleVisibilitySync();
|
|
425
|
+
}, {
|
|
426
|
+
root: viewport,
|
|
427
|
+
rootMargin: `${-(this.options.scrollMargin + this.options.scrollPreviousItemPeek)}px 0px 0px 0px`,
|
|
428
|
+
threshold: [0, 0.01, 0.5, 1],
|
|
429
|
+
});
|
|
430
|
+
this.messageElements.forEach((element) => {
|
|
431
|
+
this.intersectionObserver?.observe(element);
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
this.scheduleVisibilitySync();
|
|
435
|
+
}
|
|
436
|
+
unobserveVisibility() {
|
|
437
|
+
if (this.visibilityFrame !== null) {
|
|
438
|
+
cancelAnimationFrame(this.visibilityFrame);
|
|
439
|
+
this.visibilityFrame = null;
|
|
440
|
+
}
|
|
441
|
+
this.intersectionObserver?.disconnect();
|
|
442
|
+
this.intersectionObserver = null;
|
|
443
|
+
this.visibleMessageIds.clear();
|
|
444
|
+
this.visibilityStore.setSnapshot(EMPTY_VISIBILITY);
|
|
445
|
+
}
|
|
446
|
+
resetDefaultPosition() {
|
|
447
|
+
this.defaultPositionApplied = false;
|
|
448
|
+
}
|
|
449
|
+
updateOptions(options) {
|
|
450
|
+
const autoScrollChanged = options.autoScroll !== this.options.autoScroll;
|
|
451
|
+
this.options = options;
|
|
452
|
+
this.policy.autoScroll = options.autoScroll;
|
|
453
|
+
if (autoScrollChanged && options.autoScroll && this.isAtEnd()) {
|
|
454
|
+
this.policy.jumpToEnd();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
destroy() {
|
|
458
|
+
// Nulling the frame handles matters as much as cancelling them: the
|
|
459
|
+
// controller outlives a StrictMode unmount/remount cycle, and a stale
|
|
460
|
+
// non-null handle would block every future scheduled commit.
|
|
461
|
+
if (this.stateFrame !== null) {
|
|
462
|
+
cancelAnimationFrame(this.stateFrame);
|
|
463
|
+
this.stateFrame = null;
|
|
464
|
+
}
|
|
465
|
+
if (this.visibilityFrame !== null) {
|
|
466
|
+
cancelAnimationFrame(this.visibilityFrame);
|
|
467
|
+
this.visibilityFrame = null;
|
|
468
|
+
}
|
|
469
|
+
this.cancelWarmup();
|
|
470
|
+
this.unobserveVisibility();
|
|
471
|
+
}
|
|
472
|
+
// MARK: - Positioning
|
|
473
|
+
applyDefaultPosition(children) {
|
|
474
|
+
if (this.defaultPositionApplied)
|
|
475
|
+
return;
|
|
476
|
+
this.defaultPositionApplied = true;
|
|
477
|
+
const position = this.options.defaultScrollPosition;
|
|
478
|
+
if (position === 'start') {
|
|
479
|
+
this.scrollToStart();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (position === 'last-anchor') {
|
|
483
|
+
const viewport = this.viewport;
|
|
484
|
+
const content = this.content;
|
|
485
|
+
const lastAnchor = [...children]
|
|
486
|
+
.reverse()
|
|
487
|
+
.find((child) => child.dataset.scrollAnchor === 'true');
|
|
488
|
+
if (viewport && content && lastAnchor) {
|
|
489
|
+
const remaining = contentBottomWithoutSpacer(viewport, content, children) -
|
|
490
|
+
elementTopInContent(lastAnchor, viewport);
|
|
491
|
+
if (remaining > viewport.clientHeight) {
|
|
492
|
+
this.scrollToElement(lastAnchor, { align: 'start' }, { keepPreviousPeek: true });
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
this.scrollToEnd();
|
|
498
|
+
}
|
|
499
|
+
anchorTurn(anchor) {
|
|
500
|
+
this.handledTurnAnchors.add(anchor);
|
|
501
|
+
this.scrollToElement(anchor, { align: 'start' }, { keepPreviousPeek: true });
|
|
502
|
+
}
|
|
503
|
+
markTurnAnchorsHandled(children, fromIndex) {
|
|
504
|
+
for (let index = fromIndex; index < children.length; index += 1) {
|
|
505
|
+
const child = children[index];
|
|
506
|
+
if (child?.dataset.scrollAnchor === 'true') {
|
|
507
|
+
this.handledTurnAnchors.add(child);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Older rows were inserted above the reader. Compensate the scroll offset
|
|
513
|
+
* by the tracked first-visible row's displacement in the same mutation
|
|
514
|
+
* pass, before the shifted layout can paint.
|
|
515
|
+
*/
|
|
516
|
+
preservePrependPosition() {
|
|
517
|
+
const viewport = this.viewport;
|
|
518
|
+
const tracked = this.trackedFirstVisible;
|
|
519
|
+
if (viewport && tracked?.element.isConnected) {
|
|
520
|
+
if (this.policy.followsEnd) {
|
|
521
|
+
this.scrollToEnd();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const delta = elementViewportTop(tracked.element, viewport) - tracked.top;
|
|
525
|
+
if (Math.abs(delta) > POSITION_EPSILON_PX) {
|
|
526
|
+
viewport.scrollTop += delta;
|
|
527
|
+
this.lastScrollTop = viewport.scrollTop;
|
|
528
|
+
}
|
|
529
|
+
this.trackFirstVisibleItem();
|
|
530
|
+
}
|
|
531
|
+
this.scheduleStateCommit();
|
|
532
|
+
this.scheduleVisibilitySync();
|
|
533
|
+
}
|
|
534
|
+
performScroll(target, behavior) {
|
|
535
|
+
const viewport = this.viewport;
|
|
536
|
+
if (!viewport)
|
|
537
|
+
return;
|
|
538
|
+
// Clamp to what the viewport can actually reach: an unreachable target
|
|
539
|
+
// would never register arrival and would park the policy in a state
|
|
540
|
+
// where the next reader scroll is misread as programmatic motion.
|
|
541
|
+
const clamped = Math.min(Math.max(0, target), maxScrollTop(viewport));
|
|
542
|
+
this.policy.beginProgrammaticScroll(clamped);
|
|
543
|
+
const resolveInPlace = behavior === 'auto' ||
|
|
544
|
+
typeof viewport.scrollTo !== 'function' ||
|
|
545
|
+
Math.abs(viewport.scrollTop - clamped) <= POSITION_EPSILON_PX;
|
|
546
|
+
if (resolveInPlace) {
|
|
547
|
+
viewport.scrollTop = clamped;
|
|
548
|
+
this.lastScrollTop = viewport.scrollTop;
|
|
549
|
+
this.policy.handleScrollEvent({
|
|
550
|
+
scrollTop: viewport.scrollTop,
|
|
551
|
+
lastScrollTop: this.lastScrollTop,
|
|
552
|
+
atEnd: this.isAtEnd(),
|
|
553
|
+
});
|
|
554
|
+
this.scheduleStateCommit();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
// A smooth scroll that gets interrupted (content shrank, animation
|
|
558
|
+
// dropped) may never emit an arriving event; past the deadline the
|
|
559
|
+
// policy stops treating movement as its own.
|
|
560
|
+
this.programmaticDeadline = now() + PROGRAMMATIC_SCROLL_TIMEOUT_MS;
|
|
561
|
+
viewport.scrollTo({ top: clamped, behavior });
|
|
562
|
+
this.scheduleStateCommit();
|
|
563
|
+
}
|
|
564
|
+
// MARK: - Reading-position stability
|
|
565
|
+
/**
|
|
566
|
+
* Snapshot of stable leaves the reader can see. When a disclosure above
|
|
567
|
+
* them collapses (or a hidden row re-measures), the first still-visible
|
|
568
|
+
* leaf is re-pinned so the text under the reader's eyes never shifts.
|
|
569
|
+
*/
|
|
570
|
+
snapshotReadingLeaves() {
|
|
571
|
+
const viewport = this.viewport;
|
|
572
|
+
const content = this.content;
|
|
573
|
+
if (!viewport || !content)
|
|
574
|
+
return;
|
|
575
|
+
this.readingLeaves = captureReadingLeaves(viewport, content, this.itemChildren());
|
|
576
|
+
}
|
|
577
|
+
restoreReadingPosition() {
|
|
578
|
+
const viewport = this.viewport;
|
|
579
|
+
if (!viewport)
|
|
580
|
+
return;
|
|
581
|
+
if (restoreReadingLeaves(viewport, this.readingLeaves)) {
|
|
582
|
+
this.lastScrollTop = viewport.scrollTop;
|
|
583
|
+
this.snapshotReadingLeaves();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
trackFirstVisibleItem() {
|
|
587
|
+
const viewport = this.viewport;
|
|
588
|
+
if (!viewport) {
|
|
589
|
+
this.trackedFirstVisible = null;
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const viewportRect = viewport.getBoundingClientRect();
|
|
593
|
+
for (const child of this.itemChildren()) {
|
|
594
|
+
const rect = child.getBoundingClientRect();
|
|
595
|
+
if (rect.bottom > viewportRect.top && rect.top < viewportRect.bottom) {
|
|
596
|
+
this.trackedFirstVisible = {
|
|
597
|
+
element: child,
|
|
598
|
+
top: rect.top - viewportRect.top,
|
|
599
|
+
};
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
this.trackedFirstVisible = null;
|
|
604
|
+
}
|
|
605
|
+
// MARK: - State stores
|
|
606
|
+
scheduleStateCommit() {
|
|
607
|
+
if (this.stateFrame !== null)
|
|
608
|
+
return;
|
|
609
|
+
this.stateFrame = requestAnimationFrame(() => {
|
|
610
|
+
this.stateFrame = null;
|
|
611
|
+
this.commitFrameState();
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Once per frame: publish the scrollable edges and refresh the geometry
|
|
616
|
+
* snapshots (tracked first-visible row, reading leaves) that prepend
|
|
617
|
+
* preservation and reading-position restores compare against. Capturing
|
|
618
|
+
* here — instead of on every scroll event — bounds rect reads to one pass
|
|
619
|
+
* per frame.
|
|
620
|
+
*/
|
|
621
|
+
commitFrameState() {
|
|
622
|
+
const viewport = this.viewport;
|
|
623
|
+
if (!viewport)
|
|
624
|
+
return;
|
|
625
|
+
const threshold = this.options.scrollEdgeThreshold;
|
|
626
|
+
const scrollable = {
|
|
627
|
+
start: viewport.scrollTop > threshold,
|
|
628
|
+
// While following, transient off-end moments (content grew; the
|
|
629
|
+
// follow-up scroll lands next tick) must not flash the jump button.
|
|
630
|
+
end: !this.policy.followsEnd &&
|
|
631
|
+
maxScrollTop(viewport) - viewport.scrollTop > threshold,
|
|
632
|
+
};
|
|
633
|
+
this.scrollableStore.setSnapshot(scrollable);
|
|
634
|
+
this.applyStateAttributes();
|
|
635
|
+
if (!this.policy.followsEnd) {
|
|
636
|
+
this.trackFirstVisibleItem();
|
|
637
|
+
if (this.policy.mode === 'free')
|
|
638
|
+
this.snapshotReadingLeaves();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
applyStateAttributes() {
|
|
642
|
+
const scrollable = this.scrollableStore.getSnapshot();
|
|
643
|
+
const label = [
|
|
644
|
+
scrollable.start ? 'start' : null,
|
|
645
|
+
scrollable.end ? 'end' : null,
|
|
646
|
+
]
|
|
647
|
+
.filter(Boolean)
|
|
648
|
+
.join(' ');
|
|
649
|
+
for (const element of [this.root, this.viewport]) {
|
|
650
|
+
if (!element)
|
|
651
|
+
continue;
|
|
652
|
+
if (label)
|
|
653
|
+
element.setAttribute('data-scrollable', label);
|
|
654
|
+
else
|
|
655
|
+
element.removeAttribute('data-scrollable');
|
|
656
|
+
element.toggleAttribute('data-autoscrolling', this.policy.programmaticTarget !== null);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
scheduleVisibilitySync() {
|
|
660
|
+
if (!this.visibilityStore.hasListeners())
|
|
661
|
+
return;
|
|
662
|
+
if (this.visibilityFrame !== null)
|
|
663
|
+
return;
|
|
664
|
+
this.visibilityFrame = requestAnimationFrame(() => {
|
|
665
|
+
this.visibilityFrame = null;
|
|
666
|
+
if (!this.visibilityStore.hasListeners())
|
|
667
|
+
return;
|
|
668
|
+
this.visibilityStore.setSnapshot(this.computeVisibility());
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
computeVisibility() {
|
|
672
|
+
const viewport = this.viewport;
|
|
673
|
+
const content = this.content;
|
|
674
|
+
if (!viewport || !content)
|
|
675
|
+
return EMPTY_VISIBILITY;
|
|
676
|
+
const viewportRect = viewport.getBoundingClientRect();
|
|
677
|
+
const anchorLine = viewportRect.top +
|
|
678
|
+
this.options.scrollMargin +
|
|
679
|
+
this.options.scrollPreviousItemPeek;
|
|
680
|
+
const useRectFallback = typeof IntersectionObserver === 'undefined';
|
|
681
|
+
const visibleIds = [];
|
|
682
|
+
let currentAnchorId = null;
|
|
683
|
+
for (const child of this.itemChildren()) {
|
|
684
|
+
const messageId = child.dataset.messageId;
|
|
685
|
+
if (!messageId)
|
|
686
|
+
continue;
|
|
687
|
+
const isAnchor = child.dataset.scrollAnchor === 'true';
|
|
688
|
+
const rect = isAnchor || useRectFallback ? child.getBoundingClientRect() : null;
|
|
689
|
+
const isVisible = useRectFallback && rect
|
|
690
|
+
? rect.bottom > anchorLine && rect.top < viewportRect.bottom
|
|
691
|
+
: this.visibleMessageIds.has(messageId);
|
|
692
|
+
if (isVisible)
|
|
693
|
+
visibleIds.push(messageId);
|
|
694
|
+
if (isAnchor && rect && rect.top <= anchorLine + POSITION_EPSILON_PX) {
|
|
695
|
+
currentAnchorId = messageId;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (visibleIds.length === 0 && currentAnchorId === null) {
|
|
699
|
+
return EMPTY_VISIBILITY;
|
|
700
|
+
}
|
|
701
|
+
return { currentAnchorId, visibleMessageIds: visibleIds };
|
|
702
|
+
}
|
|
703
|
+
// MARK: - Content-visibility warm-up
|
|
704
|
+
/**
|
|
705
|
+
* Rows outside the viewport skip rendering via `content-visibility: auto`
|
|
706
|
+
* and a placeholder intrinsic size. Their first reveal during an upward
|
|
707
|
+
* scroll pays first-time layout of complex markdown — felt as hitches on
|
|
708
|
+
* the first pass through history. Rendering each row once at idle records
|
|
709
|
+
* its real size (the `auto` keyword keeps last-remembered sizes), so the
|
|
710
|
+
* first real scroll finds everything measured and warm.
|
|
711
|
+
*/
|
|
712
|
+
scheduleWarmup() {
|
|
713
|
+
if (this.warmupHandle !== null)
|
|
714
|
+
return;
|
|
715
|
+
const run = () => {
|
|
716
|
+
this.warmupHandle = null;
|
|
717
|
+
this.warmupBatch();
|
|
718
|
+
};
|
|
719
|
+
if (typeof requestIdleCallback === 'function') {
|
|
720
|
+
this.warmupUsesIdleCallback = true;
|
|
721
|
+
this.warmupHandle = requestIdleCallback(run, { timeout: 1_000 });
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
this.warmupUsesIdleCallback = false;
|
|
725
|
+
this.warmupHandle = window.setTimeout(run, 150);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
warmupBatch() {
|
|
729
|
+
if (typeof getComputedStyle !== 'function')
|
|
730
|
+
return;
|
|
731
|
+
const batch = [];
|
|
732
|
+
for (const child of this.itemChildren()) {
|
|
733
|
+
if (this.warmedItems.has(child))
|
|
734
|
+
continue;
|
|
735
|
+
this.warmedItems.add(child);
|
|
736
|
+
const style = getComputedStyle(child);
|
|
737
|
+
const contentVisibility = style.getPropertyValue('content-visibility').trim() || 'visible';
|
|
738
|
+
if (contentVisibility === 'auto')
|
|
739
|
+
batch.push(child);
|
|
740
|
+
if (batch.length >= WARMUP_BATCH_SIZE)
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
743
|
+
if (batch.length === 0)
|
|
744
|
+
return;
|
|
745
|
+
for (const element of batch) {
|
|
746
|
+
element.style.contentVisibility = 'visible';
|
|
747
|
+
}
|
|
748
|
+
requestAnimationFrame(() => {
|
|
749
|
+
// Back to the stylesheet's `auto`; the rendered pass left a
|
|
750
|
+
// remembered intrinsic size behind.
|
|
751
|
+
for (const element of batch) {
|
|
752
|
+
element.style.contentVisibility = '';
|
|
753
|
+
}
|
|
754
|
+
this.scheduleWarmup();
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
cancelWarmup() {
|
|
758
|
+
if (this.warmupHandle === null)
|
|
759
|
+
return;
|
|
760
|
+
if (this.warmupUsesIdleCallback &&
|
|
761
|
+
typeof cancelIdleCallback === 'function') {
|
|
762
|
+
cancelIdleCallback(this.warmupHandle);
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
clearTimeout(this.warmupHandle);
|
|
766
|
+
}
|
|
767
|
+
this.warmupHandle = null;
|
|
768
|
+
}
|
|
769
|
+
// MARK: - Helpers
|
|
770
|
+
flushPendingScrollToMessage() {
|
|
771
|
+
const pending = this.pendingScrollToMessage;
|
|
772
|
+
if (!pending)
|
|
773
|
+
return false;
|
|
774
|
+
const element = this.messageElements.get(pending.messageId);
|
|
775
|
+
if (!element || !this.scrollToElement(element, pending.options)) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
this.pendingScrollToMessage = null;
|
|
779
|
+
this.defaultPositionApplied = true;
|
|
780
|
+
return true;
|
|
781
|
+
}
|
|
782
|
+
setSpacerHeight(height) {
|
|
783
|
+
const spacer = this.spacer;
|
|
784
|
+
const next = Math.max(0, Math.ceil(height));
|
|
785
|
+
if (!spacer || this.spacerHeight === next)
|
|
786
|
+
return;
|
|
787
|
+
this.spacerHeight = next;
|
|
788
|
+
spacer.hidden = next === 0;
|
|
789
|
+
spacer.style.height = `${next}px`;
|
|
790
|
+
spacer.style.marginTop = next > 0 ? `${-this.spacerGap}px` : '';
|
|
791
|
+
}
|
|
792
|
+
itemChildren() {
|
|
793
|
+
const content = this.content;
|
|
794
|
+
if (!content)
|
|
795
|
+
return [];
|
|
796
|
+
return Array.from(content.children).filter((child) => child instanceof HTMLElement && child !== this.spacer);
|
|
797
|
+
}
|
|
798
|
+
isAtEnd() {
|
|
799
|
+
const viewport = this.viewport;
|
|
800
|
+
if (!viewport)
|
|
801
|
+
return true;
|
|
802
|
+
return isMessageScrollerAtEnd(viewport, this.options.scrollEdgeThreshold);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function maxScrollTop(viewport) {
|
|
806
|
+
return Math.max(0, viewport.scrollHeight - viewport.clientHeight);
|
|
807
|
+
}
|
|
808
|
+
function elementViewportTop(element, viewport) {
|
|
809
|
+
return (element.getBoundingClientRect().top - viewport.getBoundingClientRect().top);
|
|
810
|
+
}
|
|
811
|
+
function elementTopInContent(element, viewport) {
|
|
812
|
+
return elementViewportTop(element, viewport) + viewport.scrollTop;
|
|
813
|
+
}
|
|
814
|
+
function readBlockPadding(element) {
|
|
815
|
+
if (!element || typeof getComputedStyle !== 'function') {
|
|
816
|
+
return { start: 0, end: 0 };
|
|
817
|
+
}
|
|
818
|
+
const style = getComputedStyle(element);
|
|
819
|
+
return {
|
|
820
|
+
start: parsePx(style.paddingBlockStart || style.paddingTop),
|
|
821
|
+
end: parsePx(style.paddingBlockEnd || style.paddingBottom),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function parsePx(value) {
|
|
825
|
+
const parsed = Number.parseFloat(value || '0');
|
|
826
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* The content-space bottom of the real transcript: the lowest item edge plus
|
|
830
|
+
* the content's own padding. Measured from items instead of the content box
|
|
831
|
+
* so the anchoring spacer never feeds back into its own requirement.
|
|
832
|
+
*/
|
|
833
|
+
function contentBottomWithoutSpacer(viewport, content, items) {
|
|
834
|
+
const viewportRect = viewport.getBoundingClientRect();
|
|
835
|
+
const padding = readBlockPadding(content);
|
|
836
|
+
let bottom = padding.start + padding.end;
|
|
837
|
+
for (const item of items) {
|
|
838
|
+
const rect = item.getBoundingClientRect();
|
|
839
|
+
bottom = Math.max(bottom, rect.bottom - viewportRect.top + viewport.scrollTop + padding.end);
|
|
840
|
+
}
|
|
841
|
+
return bottom;
|
|
842
|
+
}
|
|
843
|
+
function alignedScrollTarget({ align, element, margin, padding, viewport, }) {
|
|
844
|
+
const top = elementTopInContent(element, viewport);
|
|
845
|
+
const height = element.getBoundingClientRect().height;
|
|
846
|
+
if (align === 'center') {
|
|
847
|
+
const available = Math.max(0, viewport.clientHeight - padding.start - padding.end);
|
|
848
|
+
return top - padding.start - (available - height) / 2 - margin;
|
|
849
|
+
}
|
|
850
|
+
if (align === 'end') {
|
|
851
|
+
return top - viewport.clientHeight + height + padding.end + margin;
|
|
852
|
+
}
|
|
853
|
+
if (align === 'nearest') {
|
|
854
|
+
const bottom = top + height;
|
|
855
|
+
const visibleTop = viewport.scrollTop + padding.start;
|
|
856
|
+
const visibleBottom = viewport.scrollTop + viewport.clientHeight - padding.end;
|
|
857
|
+
if (top >= visibleTop && bottom <= visibleBottom)
|
|
858
|
+
return viewport.scrollTop;
|
|
859
|
+
return top < visibleTop
|
|
860
|
+
? top - padding.start - margin
|
|
861
|
+
: bottom - viewport.clientHeight + padding.end + margin;
|
|
862
|
+
}
|
|
863
|
+
return top - padding.start - margin;
|
|
864
|
+
}
|
|
865
|
+
function firstTurnAnchorAt(children, startIndex) {
|
|
866
|
+
for (let index = startIndex; index < children.length; index += 1) {
|
|
867
|
+
if (children[index]?.dataset.scrollAnchor === 'true') {
|
|
868
|
+
return children[index] ?? null;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
function countTurnAnchorsAt(children, startIndex) {
|
|
874
|
+
let count = 0;
|
|
875
|
+
for (let index = startIndex; index < children.length; index += 1) {
|
|
876
|
+
if (children[index]?.dataset.scrollAnchor === 'true')
|
|
877
|
+
count += 1;
|
|
878
|
+
}
|
|
879
|
+
return count;
|
|
880
|
+
}
|
|
881
|
+
function readRowGap(element) {
|
|
882
|
+
if (!element || typeof getComputedStyle !== 'function')
|
|
883
|
+
return 0;
|
|
884
|
+
const style = getComputedStyle(element);
|
|
885
|
+
const gap = style.rowGap === 'normal' ? style.gap : style.rowGap;
|
|
886
|
+
const parsed = Number.parseFloat(gap || '0');
|
|
887
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
888
|
+
}
|