@peers-app/peers-ui 0.14.0 → 0.15.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.
- package/.github/workflows/publish.yml +8 -5
- package/babel.config.js +4 -4
- package/biome.json +191 -0
- package/dist/command-palette/command-palette-ui.d.ts +1 -2
- package/dist/command-palette/command-palette-ui.js +175 -244
- package/dist/command-palette/command-palette.js +65 -64
- package/dist/components/chat-overlay.d.ts +2 -2
- package/dist/components/chat-overlay.js +160 -217
- package/dist/components/checkbox.d.ts +5 -4
- package/dist/components/checkbox.js +4 -7
- package/dist/components/group-switcher.d.ts +1 -2
- package/dist/components/group-switcher.js +119 -159
- package/dist/components/input-date.d.ts +3 -3
- package/dist/components/input-date.js +6 -6
- package/dist/components/input-number.d.ts +7 -6
- package/dist/components/input-number.js +25 -20
- package/dist/components/input.d.ts +5 -4
- package/dist/components/input.js +4 -7
- package/dist/components/inverse-lazy-list.d.ts +3 -3
- package/dist/components/inverse-lazy-list.js +13 -47
- package/dist/components/io-schema-values.d.ts +5 -6
- package/dist/components/io-schema-values.js +28 -65
- package/dist/components/io-schema.d.ts +4 -5
- package/dist/components/io-schema.js +42 -79
- package/dist/components/lazy-list.d.ts +3 -3
- package/dist/components/lazy-list.js +38 -58
- package/dist/components/list-screen.d.ts +3 -8
- package/dist/components/list-screen.js +28 -23
- package/dist/components/loading-indicator.d.ts +1 -2
- package/dist/components/loading-indicator.js +2 -6
- package/dist/components/markdown-editor/autolink-plugin.js +5 -8
- package/dist/components/markdown-editor/editor-inline.d.ts +2 -3
- package/dist/components/markdown-editor/editor-inline.js +2 -6
- package/dist/components/markdown-editor/editor.d.ts +6 -6
- package/dist/components/markdown-editor/editor.js +9 -19
- package/dist/components/markdown-editor/markdown-plugin.d.ts +1 -1
- package/dist/components/markdown-editor/markdown-plugin.js +20 -21
- package/dist/components/markdown-editor/mention-node.d.ts +2 -2
- package/dist/components/markdown-editor/mention-node.js +24 -24
- package/dist/components/markdown-editor/mentions-plugin.d.ts +2 -2
- package/dist/components/markdown-editor/mentions-plugin.js +61 -62
- package/dist/components/markdown-editor/theme.js +28 -28
- package/dist/components/markdown-editor/toolbar.d.ts +2 -3
- package/dist/components/markdown-editor/toolbar.js +32 -49
- package/dist/components/markdown-with-mentions.d.ts +1 -1
- package/dist/components/markdown-with-mentions.js +43 -43
- package/dist/components/message-logs/message-logs.d.ts +1 -2
- package/dist/components/message-logs/message-logs.js +91 -116
- package/dist/components/messages/avatar.d.ts +3 -4
- package/dist/components/messages/avatar.js +37 -46
- package/dist/components/messages/channel-message-list.d.ts +5 -6
- package/dist/components/messages/channel-message-list.js +34 -34
- package/dist/components/messages/channel-view.d.ts +1 -2
- package/dist/components/messages/channel-view.js +23 -57
- package/dist/components/messages/message-compose.d.ts +3 -4
- package/dist/components/messages/message-compose.js +27 -38
- package/dist/components/messages/message-display.d.ts +2 -3
- package/dist/components/messages/message-display.js +42 -95
- package/dist/components/messages/thread-message-list.d.ts +4 -5
- package/dist/components/messages/thread-message-list.js +29 -29
- package/dist/components/router.d.ts +1 -2
- package/dist/components/router.js +58 -66
- package/dist/components/save-button.d.ts +3 -3
- package/dist/components/save-button.js +23 -33
- package/dist/components/sortable-list.d.ts +11 -12
- package/dist/components/sortable-list.js +42 -22
- package/dist/components/tabs.d.ts +3 -3
- package/dist/components/tabs.js +16 -47
- package/dist/components/tooltip.d.ts +4 -4
- package/dist/components/tooltip.js +4 -9
- package/dist/components/trust-level-badge.d.ts +2 -3
- package/dist/components/trust-level-badge.js +16 -16
- package/dist/components/trust-level-dropdown.d.ts +3 -4
- package/dist/components/trust-level-dropdown.js +32 -55
- package/dist/components/typeahead.d.ts +3 -3
- package/dist/components/typeahead.js +48 -89
- package/dist/components/voice-indicator.d.ts +2 -2
- package/dist/components/voice-indicator.js +93 -106
- package/dist/components/voice-subscribe-events.d.ts +32 -0
- package/dist/components/voice-subscribe-events.js +2 -0
- package/dist/globals.d.ts +3 -5
- package/dist/globals.js +22 -33
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +22 -12
- package/dist/hooks.test.js +129 -145
- package/dist/index.d.ts +9 -8
- package/dist/index.js +13 -12
- package/dist/mention-configs.d.ts +2 -2
- package/dist/mention-configs.js +55 -42
- package/dist/screens/assistants/assistant-config.d.ts +2 -3
- package/dist/screens/assistants/assistant-config.js +9 -22
- package/dist/screens/assistants/assistant-details.d.ts +1 -2
- package/dist/screens/assistants/assistant-details.js +13 -28
- package/dist/screens/assistants/assistant-info.d.ts +2 -3
- package/dist/screens/assistants/assistant-info.js +3 -17
- package/dist/screens/assistants/assistant-list.d.ts +1 -2
- package/dist/screens/assistants/assistant-list.js +15 -56
- package/dist/screens/assistants/assistant-tools.d.ts +2 -3
- package/dist/screens/assistants/assistant-tools.js +10 -24
- package/dist/screens/console-logs/console-logs-list.d.ts +1 -2
- package/dist/screens/console-logs/console-logs-list.js +130 -134
- package/dist/screens/console-logs/log-display.d.ts +2 -3
- package/dist/screens/console-logs/log-display.js +40 -42
- package/dist/screens/console-logs/log-filters.d.ts +1 -2
- package/dist/screens/console-logs/log-filters.js +2 -24
- package/dist/screens/console-logs/mobile-log-card.d.ts +2 -3
- package/dist/screens/console-logs/mobile-log-card.js +64 -67
- package/dist/screens/console-logs/resizable-table-header.d.ts +1 -2
- package/dist/screens/console-logs/resizable-table-header.js +31 -67
- package/dist/screens/contacts/contact-details.d.ts +1 -2
- package/dist/screens/contacts/contact-details.js +16 -46
- package/dist/screens/contacts/contact-list.d.ts +1 -2
- package/dist/screens/contacts/contact-list.js +44 -103
- package/dist/screens/contacts/index.d.ts +4 -4
- package/dist/screens/contacts/index.js +1 -1
- package/dist/screens/contacts/user-connect.d.ts +1 -2
- package/dist/screens/contacts/user-connect.js +85 -186
- package/dist/screens/data-explorer/data-explorer.d.ts +1 -2
- package/dist/screens/data-explorer/data-explorer.js +61 -181
- package/dist/screens/data-explorer/index.d.ts +2 -2
- package/dist/screens/data-explorer/query-executor.d.ts +1 -2
- package/dist/screens/data-explorer/query-executor.js +56 -166
- package/dist/screens/groups/group-details.d.ts +1 -2
- package/dist/screens/groups/group-details.js +27 -122
- package/dist/screens/groups/group-invite-listener.d.ts +2 -3
- package/dist/screens/groups/group-invite-listener.js +8 -104
- package/dist/screens/groups/group-list.d.ts +1 -2
- package/dist/screens/groups/group-list.js +56 -133
- package/dist/screens/groups/group-members.d.ts +2 -3
- package/dist/screens/groups/group-members.js +62 -132
- package/dist/screens/groups/index.d.ts +4 -4
- package/dist/screens/groups/index.js +1 -1
- package/dist/screens/join-group/index.d.ts +2 -2
- package/dist/screens/join-group/join-group.d.ts +1 -2
- package/dist/screens/join-group/join-group.js +9 -109
- package/dist/screens/network-viewer/connection-troubleshooter.d.ts +2 -3
- package/dist/screens/network-viewer/connection-troubleshooter.js +89 -193
- package/dist/screens/network-viewer/cpu-usage-graph.d.ts +1 -2
- package/dist/screens/network-viewer/cpu-usage-graph.js +60 -99
- package/dist/screens/network-viewer/device-details-modal.d.ts +1 -2
- package/dist/screens/network-viewer/device-details-modal.js +25 -177
- package/dist/screens/network-viewer/group-details-modal.d.ts +1 -2
- package/dist/screens/network-viewer/group-details-modal.js +31 -142
- package/dist/screens/network-viewer/index.d.ts +4 -4
- package/dist/screens/network-viewer/index.js +3 -3
- package/dist/screens/network-viewer/network-viewer-ipc.d.ts +22 -0
- package/dist/screens/network-viewer/network-viewer-ipc.js +6 -0
- package/dist/screens/network-viewer/network-viewer.d.ts +1 -2
- package/dist/screens/network-viewer/network-viewer.js +91 -296
- package/dist/screens/network-viewer/usage-graph.d.ts +1 -2
- package/dist/screens/network-viewer/usage-graph.js +78 -110
- package/dist/screens/packages/package-details.d.ts +1 -2
- package/dist/screens/packages/package-details.js +35 -41
- package/dist/screens/packages/package-info.d.ts +2 -2
- package/dist/screens/packages/package-info.js +33 -86
- package/dist/screens/packages/package-list.d.ts +1 -2
- package/dist/screens/packages/package-list.js +42 -106
- package/dist/screens/packages/package-new-local.d.ts +1 -2
- package/dist/screens/packages/package-new-local.js +13 -19
- package/dist/screens/packages/package-versions.d.ts +2 -3
- package/dist/screens/packages/package-versions.js +29 -96
- package/dist/screens/peer-types/peer-type-details.d.ts +3 -4
- package/dist/screens/peer-types/peer-type-details.js +26 -78
- package/dist/screens/peer-types/peer-type-list.d.ts +1 -2
- package/dist/screens/peer-types/peer-type-list.js +13 -24
- package/dist/screens/search/global-search.d.ts +1 -2
- package/dist/screens/search/global-search.js +104 -182
- package/dist/screens/settings/color-mode-dropdown.d.ts +3 -4
- package/dist/screens/settings/color-mode-dropdown.js +18 -37
- package/dist/screens/settings/settings-page.d.ts +1 -1
- package/dist/screens/settings/settings-page.js +86 -213
- package/dist/screens/settings/voice-settings-agent.d.ts +1 -1
- package/dist/screens/settings/voice-settings-agent.js +7 -44
- package/dist/screens/settings/voice-settings-api-keys.d.ts +2 -2
- package/dist/screens/settings/voice-settings-api-keys.js +2 -29
- package/dist/screens/settings/voice-settings-output.d.ts +2 -2
- package/dist/screens/settings/voice-settings-output.js +2 -40
- package/dist/screens/settings/voice-settings-providers.d.ts +2 -2
- package/dist/screens/settings/voice-settings-providers.js +2 -19
- package/dist/screens/settings/voice-settings-types.d.ts +4 -4
- package/dist/screens/settings/voice-settings-types.js +31 -31
- package/dist/screens/settings/voice-settings-wake-word.d.ts +2 -2
- package/dist/screens/settings/voice-settings-wake-word.js +2 -33
- package/dist/screens/settings/voice-settings.d.ts +1 -1
- package/dist/screens/settings/voice-settings.js +35 -112
- package/dist/screens/setup-user.d.ts +1 -2
- package/dist/screens/setup-user.js +38 -116
- package/dist/screens/tools/tool-code.d.ts +2 -3
- package/dist/screens/tools/tool-code.js +9 -13
- package/dist/screens/tools/tool-details.d.ts +1 -2
- package/dist/screens/tools/tool-details.js +26 -39
- package/dist/screens/tools/tool-info.d.ts +2 -3
- package/dist/screens/tools/tool-info.js +9 -48
- package/dist/screens/tools/tool-list.d.ts +1 -2
- package/dist/screens/tools/tool-list.js +33 -65
- package/dist/screens/tools/tool-schema.d.ts +2 -3
- package/dist/screens/tools/tool-schema.js +2 -13
- package/dist/screens/tools/tool-test-details.d.ts +1 -2
- package/dist/screens/tools/tool-test-details.js +12 -28
- package/dist/screens/tools/tool-test-list.d.ts +1 -2
- package/dist/screens/tools/tool-test-list.js +17 -56
- package/dist/screens/variables/variable-details.d.ts +1 -2
- package/dist/screens/variables/variable-details.js +19 -86
- package/dist/screens/variables/variable-list.d.ts +1 -2
- package/dist/screens/variables/variable-list.js +16 -27
- package/dist/screens/welcome-modal.d.ts +1 -2
- package/dist/screens/welcome-modal.js +44 -111
- package/dist/screens/workflows/workflow-details.d.ts +1 -2
- package/dist/screens/workflows/workflow-details.js +17 -31
- package/dist/screens/workflows/workflow-info.d.ts +2 -3
- package/dist/screens/workflows/workflow-info.js +2 -9
- package/dist/screens/workflows/workflow-instructions.d.ts +2 -3
- package/dist/screens/workflows/workflow-instructions.js +23 -55
- package/dist/screens/workflows/workflow-list.d.ts +1 -2
- package/dist/screens/workflows/workflow-list.js +23 -62
- package/dist/setupTests.d.ts +1 -1
- package/dist/setupTests.js +10 -11
- package/dist/system-apps/assistants.app.d.ts +1 -1
- package/dist/system-apps/assistants.app.js +3 -3
- package/dist/system-apps/console-logs.app.d.ts +1 -1
- package/dist/system-apps/console-logs.app.js +3 -3
- package/dist/system-apps/contacts.app.d.ts +1 -1
- package/dist/system-apps/contacts.app.js +4 -4
- package/dist/system-apps/data-explorer.app.d.ts +1 -1
- package/dist/system-apps/data-explorer.app.js +4 -4
- package/dist/system-apps/groups.app.d.ts +1 -1
- package/dist/system-apps/groups.app.js +4 -4
- package/dist/system-apps/index.d.ts +17 -17
- package/dist/system-apps/index.js +52 -52
- package/dist/system-apps/join-group.app.d.ts +1 -1
- package/dist/system-apps/join-group.app.js +4 -4
- package/dist/system-apps/mobile-settings.app.d.ts +1 -1
- package/dist/system-apps/mobile-settings.app.js +3 -3
- package/dist/system-apps/network-viewer.app.d.ts +1 -1
- package/dist/system-apps/network-viewer.app.js +4 -4
- package/dist/system-apps/packages.app.d.ts +1 -1
- package/dist/system-apps/packages.app.js +3 -3
- package/dist/system-apps/search.app.d.ts +1 -1
- package/dist/system-apps/search.app.js +4 -4
- package/dist/system-apps/settings.app.d.ts +1 -1
- package/dist/system-apps/settings.app.js +3 -3
- package/dist/system-apps/threads.app.d.ts +1 -1
- package/dist/system-apps/threads.app.js +3 -3
- package/dist/system-apps/tools.app.d.ts +1 -1
- package/dist/system-apps/tools.app.js +3 -3
- package/dist/system-apps/types.app.d.ts +1 -1
- package/dist/system-apps/types.app.js +3 -3
- package/dist/system-apps/variables.app.d.ts +1 -1
- package/dist/system-apps/variables.app.js +3 -3
- package/dist/system-apps/workflows.app.d.ts +1 -1
- package/dist/system-apps/workflows.app.js +3 -3
- package/dist/tabs-layout/tabs-layout.d.ts +2 -3
- package/dist/tabs-layout/tabs-layout.js +215 -246
- package/dist/tabs-layout/tabs-state.d.ts +2 -2
- package/dist/tabs-layout/tabs-state.js +73 -61
- package/dist/ui-defaults/index.d.ts +2 -2
- package/dist/ui-defaults/list-screen.d.ts +2 -3
- package/dist/ui-defaults/list-screen.js +33 -37
- package/dist/ui-defaults/markdown-field.js +24 -56
- package/dist/ui-router/routes-loader.d.ts +1 -1
- package/dist/ui-router/routes-loader.js +17 -13
- package/dist/ui-router/ui-loader.d.ts +6 -6
- package/dist/ui-router/ui-loader.js +172 -268
- package/dist/utils.js +49 -39
- package/jest.config.js +16 -16
- package/package.json +16 -14
- package/src/command-palette/command-palette-ui.tsx +261 -237
- package/src/command-palette/command-palette.ts +81 -78
- package/src/components/chat-overlay.tsx +366 -261
- package/src/components/checkbox.tsx +15 -12
- package/src/components/group-switcher.tsx +150 -105
- package/src/components/input-date.tsx +17 -16
- package/src/components/input-number.tsx +47 -31
- package/src/components/input.tsx +15 -12
- package/src/components/inverse-lazy-list.tsx +14 -13
- package/src/components/io-schema-values.tsx +51 -69
- package/src/components/io-schema.tsx +94 -69
- package/src/components/lazy-list.tsx +51 -34
- package/src/components/list-screen.tsx +51 -35
- package/src/components/loading-indicator.tsx +2 -4
- package/src/components/markdown-editor/autolink-plugin.tsx +4 -11
- package/src/components/markdown-editor/editor-inline.tsx +3 -4
- package/src/components/markdown-editor/editor.tsx +53 -51
- package/src/components/markdown-editor/markdown-plugin.tsx +48 -40
- package/src/components/markdown-editor/mention-node.ts +39 -38
- package/src/components/markdown-editor/mentions-plugin.tsx +99 -101
- package/src/components/markdown-editor/theme.ts +28 -29
- package/src/components/markdown-editor/toolbar.tsx +53 -47
- package/src/components/markdown-with-mentions.tsx +56 -46
- package/src/components/message-logs/message-logs.tsx +225 -165
- package/src/components/messages/avatar.tsx +70 -52
- package/src/components/messages/channel-message-list.tsx +80 -68
- package/src/components/messages/channel-view.tsx +34 -33
- package/src/components/messages/message-compose.tsx +84 -67
- package/src/components/messages/message-display.tsx +103 -89
- package/src/components/messages/thread-message-list.tsx +53 -44
- package/src/components/router.tsx +42 -43
- package/src/components/save-button.tsx +43 -39
- package/src/components/sortable-list.tsx +77 -49
- package/src/components/tabs.tsx +31 -31
- package/src/components/tooltip.tsx +21 -28
- package/src/components/trust-level-badge.tsx +15 -11
- package/src/components/trust-level-dropdown.tsx +49 -19
- package/src/components/typeahead.tsx +57 -59
- package/src/components/voice-indicator.tsx +158 -141
- package/src/components/voice-subscribe-events.ts +20 -0
- package/src/globals.tsx +42 -40
- package/src/hooks.test.tsx +141 -134
- package/src/hooks.ts +80 -48
- package/src/index.tsx +17 -10
- package/src/mention-configs.ts +122 -68
- package/src/screens/assistants/assistant-config.tsx +28 -18
- package/src/screens/assistants/assistant-details.tsx +35 -36
- package/src/screens/assistants/assistant-info.tsx +16 -11
- package/src/screens/assistants/assistant-list.tsx +37 -34
- package/src/screens/assistants/assistant-tools.tsx +41 -20
- package/src/screens/console-logs/console-logs-list.tsx +173 -140
- package/src/screens/console-logs/log-display.tsx +65 -38
- package/src/screens/console-logs/log-filters.tsx +4 -3
- package/src/screens/console-logs/mobile-log-card.tsx +78 -71
- package/src/screens/console-logs/resizable-table-header.tsx +29 -21
- package/src/screens/contacts/contact-details.tsx +29 -30
- package/src/screens/contacts/contact-list.tsx +71 -60
- package/src/screens/contacts/index.ts +5 -5
- package/src/screens/contacts/user-connect.tsx +177 -171
- package/src/screens/data-explorer/data-explorer.tsx +134 -98
- package/src/screens/data-explorer/index.ts +2 -3
- package/src/screens/data-explorer/query-executor.tsx +90 -80
- package/src/screens/groups/group-details.tsx +120 -101
- package/src/screens/groups/group-invite-listener.tsx +34 -37
- package/src/screens/groups/group-list.tsx +119 -103
- package/src/screens/groups/group-members.tsx +225 -164
- package/src/screens/groups/index.ts +5 -6
- package/src/screens/join-group/index.ts +2 -2
- package/src/screens/join-group/join-group.tsx +41 -39
- package/src/screens/network-viewer/connection-troubleshooter.tsx +145 -104
- package/src/screens/network-viewer/cpu-usage-graph.tsx +39 -43
- package/src/screens/network-viewer/device-details-modal.tsx +46 -59
- package/src/screens/network-viewer/group-details-modal.tsx +68 -49
- package/src/screens/network-viewer/index.ts +4 -5
- package/src/screens/network-viewer/network-viewer-ipc.ts +23 -0
- package/src/screens/network-viewer/network-viewer.tsx +261 -236
- package/src/screens/network-viewer/usage-graph.tsx +57 -49
- package/src/screens/packages/package-details.tsx +43 -35
- package/src/screens/packages/package-info.tsx +107 -66
- package/src/screens/packages/package-list.tsx +175 -98
- package/src/screens/packages/package-new-local.tsx +28 -26
- package/src/screens/packages/package-versions.tsx +102 -77
- package/src/screens/peer-types/peer-type-details.tsx +60 -50
- package/src/screens/peer-types/peer-type-list.tsx +20 -30
- package/src/screens/search/global-search.tsx +153 -137
- package/src/screens/settings/color-mode-dropdown.tsx +52 -35
- package/src/screens/settings/settings-page.tsx +215 -141
- package/src/screens/settings/voice-settings-agent.tsx +13 -12
- package/src/screens/settings/voice-settings-api-keys.tsx +14 -12
- package/src/screens/settings/voice-settings-output.tsx +12 -11
- package/src/screens/settings/voice-settings-providers.tsx +7 -3
- package/src/screens/settings/voice-settings-types.ts +52 -49
- package/src/screens/settings/voice-settings-wake-word.tsx +25 -9
- package/src/screens/settings/voice-settings.tsx +66 -43
- package/src/screens/setup-user.tsx +88 -41
- package/src/screens/tools/tool-code.tsx +12 -17
- package/src/screens/tools/tool-details.tsx +28 -28
- package/src/screens/tools/tool-info.tsx +14 -19
- package/src/screens/tools/tool-list.tsx +58 -40
- package/src/screens/tools/tool-schema.tsx +16 -9
- package/src/screens/tools/tool-test-details.tsx +11 -22
- package/src/screens/tools/tool-test-list.tsx +29 -30
- package/src/screens/variables/variable-details.tsx +63 -51
- package/src/screens/variables/variable-list.tsx +29 -30
- package/src/screens/welcome-modal.tsx +68 -48
- package/src/screens/workflows/workflow-details.tsx +40 -30
- package/src/screens/workflows/workflow-info.tsx +4 -11
- package/src/screens/workflows/workflow-instructions.tsx +35 -28
- package/src/screens/workflows/workflow-list.tsx +50 -40
- package/src/setupTests.ts +14 -13
- package/src/system-apps/assistants.app.ts +5 -5
- package/src/system-apps/console-logs.app.ts +4 -4
- package/src/system-apps/contacts.app.ts +6 -6
- package/src/system-apps/data-explorer.app.ts +5 -5
- package/src/system-apps/groups.app.ts +6 -6
- package/src/system-apps/index.ts +49 -49
- package/src/system-apps/join-group.app.ts +5 -5
- package/src/system-apps/mobile-settings.app.ts +4 -5
- package/src/system-apps/network-viewer.app.ts +5 -5
- package/src/system-apps/packages.app.ts +5 -5
- package/src/system-apps/search.app.ts +6 -6
- package/src/system-apps/settings.app.ts +5 -5
- package/src/system-apps/threads.app.ts +5 -5
- package/src/system-apps/tools.app.ts +5 -5
- package/src/system-apps/types.app.ts +5 -5
- package/src/system-apps/variables.app.ts +5 -5
- package/src/system-apps/workflows.app.ts +5 -5
- package/src/tabs-layout/tabs-layout.tsx +345 -254
- package/src/tabs-layout/tabs-state.ts +100 -81
- package/src/ui-defaults/index.ts +2 -3
- package/src/ui-defaults/list-screen.tsx +45 -40
- package/src/ui-defaults/markdown-field.tsx +22 -26
- package/src/ui-router/routes-loader.ts +40 -24
- package/src/ui-router/ui-loader.tsx +312 -214
- package/src/utils.ts +68 -81
- package/tsconfig.json +5 -10
- package/dist/components/input-datetime.d.ts +0 -7
- package/dist/components/input-datetime.js +0 -35
- package/dist/components/lazy-sortable-list.d.ts +0 -29
- package/dist/components/lazy-sortable-list.js +0 -12
- package/dist/components/left-bar.d.ts +0 -5
- package/dist/components/left-bar.js +0 -207
- package/dist/components/main-content-container.d.ts +0 -2
- package/dist/components/main-content-container.js +0 -92
- package/dist/components/messages/thread-view.d.ts +0 -6
- package/dist/components/messages/thread-view.js +0 -174
- package/dist/components/off-canvas.d.ts +0 -13
- package/dist/components/off-canvas.js +0 -89
- package/dist/components/text-list-editor.tsx/text-list-editor.d.ts +0 -6
- package/dist/components/text-list-editor.tsx/text-list-editor.js +0 -13
- package/dist/components/top-bar.d.ts +0 -2
- package/dist/components/top-bar.js +0 -51
- package/dist/components/typeahead/mentions-plugin.d.ts +0 -7
- package/dist/components/typeahead/mentions-plugin.js +0 -203
- package/dist/components/typeahead/typeahead-editor.d.ts +0 -15
- package/dist/components/typeahead/typeahead-editor.js +0 -134
- package/dist/components/typeahead/typeahead.d.ts +0 -12
- package/dist/components/typeahead/typeahead.js +0 -94
- package/dist/screens/profile.d.ts +0 -2
- package/dist/screens/profile.js +0 -76
- package/src/components/input-datetime.tsx +0 -41
- package/src/components/lazy-sortable-list.tsx +0 -51
- package/src/components/left-bar.tsx +0 -322
- package/src/components/main-content-container.tsx +0 -79
- package/src/components/messages/thread-view.tsx +0 -214
- package/src/components/off-canvas.tsx +0 -83
- package/src/components/text-list-editor.tsx/text-list-editor.tsx +0 -13
- package/src/components/top-bar.tsx +0 -119
- package/src/components/typeahead/mentions-plugin.tsx +0 -265
- package/src/components/typeahead/typeahead-editor.tsx +0 -140
- package/src/components/typeahead/typeahead.tsx +0 -77
- package/src/screens/profile.tsx +0 -75
package/src/hooks.test.tsx
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { render, screen,
|
|
3
|
-
import {
|
|
4
|
-
import { useObservable, useObservableState } from './hooks';
|
|
1
|
+
import { type Observable, observable } from "@peers-app/peers-sdk";
|
|
2
|
+
import { act, render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import { useObservable, useObservableState } from "./hooks";
|
|
5
4
|
|
|
6
5
|
// Helper component to test useObservable
|
|
7
|
-
function TestComponent<T>({ obs, testId =
|
|
6
|
+
function TestComponent<T>({ obs, testId = "value" }: { obs: Observable<T> | T; testId?: string }) {
|
|
8
7
|
const [value, setValue] = useObservable(obs);
|
|
9
8
|
return (
|
|
10
9
|
<div>
|
|
11
10
|
<span data-testid={testId}>{JSON.stringify(value)}</span>
|
|
12
|
-
<button data-testid="setter" onClick={() => setValue(
|
|
11
|
+
<button type="button" data-testid="setter" onClick={() => setValue("new-value" as T)}>
|
|
12
|
+
Set
|
|
13
|
+
</button>
|
|
13
14
|
</div>
|
|
14
15
|
);
|
|
15
16
|
}
|
|
@@ -32,108 +33,110 @@ function ObservableStateTestComponent({ initialValue }: { initialValue: number }
|
|
|
32
33
|
return (
|
|
33
34
|
<div>
|
|
34
35
|
<span data-testid="state-value">{value}</span>
|
|
35
|
-
<button data-testid="increment" onClick={() => obs(obs() + 1)}>
|
|
36
|
+
<button type="button" data-testid="increment" onClick={() => obs(obs() + 1)}>
|
|
37
|
+
Increment
|
|
38
|
+
</button>
|
|
36
39
|
</div>
|
|
37
40
|
);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
describe(
|
|
41
|
-
describe(
|
|
42
|
-
it(
|
|
43
|
-
const obs = observable(
|
|
43
|
+
describe("useObservable", () => {
|
|
44
|
+
describe("basic functionality", () => {
|
|
45
|
+
it("should return the current value of an observable", () => {
|
|
46
|
+
const obs = observable("hello");
|
|
44
47
|
render(<TestComponent obs={obs} />);
|
|
45
|
-
expect(screen.getByTestId(
|
|
48
|
+
expect(screen.getByTestId("value")).toHaveTextContent('"hello"');
|
|
46
49
|
});
|
|
47
50
|
|
|
48
|
-
it(
|
|
51
|
+
it("should return the value when passed a plain value (non-observable)", () => {
|
|
49
52
|
render(<TestComponent obs="plain-value" />);
|
|
50
|
-
expect(screen.getByTestId(
|
|
53
|
+
expect(screen.getByTestId("value")).toHaveTextContent('"plain-value"');
|
|
51
54
|
});
|
|
52
55
|
|
|
53
|
-
it(
|
|
54
|
-
const obs = observable(
|
|
56
|
+
it("should re-render when observable value changes", async () => {
|
|
57
|
+
const obs = observable("initial");
|
|
55
58
|
render(<TestComponent obs={obs} />);
|
|
56
|
-
|
|
57
|
-
expect(screen.getByTestId(
|
|
58
|
-
|
|
59
|
+
|
|
60
|
+
expect(screen.getByTestId("value")).toHaveTextContent('"initial"');
|
|
61
|
+
|
|
59
62
|
act(() => {
|
|
60
|
-
obs(
|
|
63
|
+
obs("updated");
|
|
61
64
|
});
|
|
62
|
-
|
|
65
|
+
|
|
63
66
|
await waitFor(() => {
|
|
64
|
-
expect(screen.getByTestId(
|
|
67
|
+
expect(screen.getByTestId("value")).toHaveTextContent('"updated"');
|
|
65
68
|
});
|
|
66
69
|
});
|
|
67
70
|
|
|
68
|
-
it(
|
|
69
|
-
const obs = observable(
|
|
71
|
+
it("should update the observable when setter is called", async () => {
|
|
72
|
+
const obs = observable("initial");
|
|
70
73
|
render(<TestComponent obs={obs} />);
|
|
71
|
-
|
|
74
|
+
|
|
72
75
|
act(() => {
|
|
73
|
-
screen.getByTestId(
|
|
76
|
+
screen.getByTestId("setter").click();
|
|
74
77
|
});
|
|
75
|
-
|
|
78
|
+
|
|
76
79
|
await waitFor(() => {
|
|
77
|
-
expect(obs()).toBe(
|
|
78
|
-
expect(screen.getByTestId(
|
|
80
|
+
expect(obs()).toBe("new-value");
|
|
81
|
+
expect(screen.getByTestId("value")).toHaveTextContent('"new-value"');
|
|
79
82
|
});
|
|
80
83
|
});
|
|
81
84
|
|
|
82
|
-
it(
|
|
85
|
+
it("should handle undefined values", () => {
|
|
83
86
|
const obs = observable<string | undefined>(undefined);
|
|
84
87
|
render(<TestComponent obs={obs} />);
|
|
85
88
|
// undefined serializes to empty string in JSON.stringify, but actually shows as nothing
|
|
86
|
-
expect(screen.getByTestId(
|
|
89
|
+
expect(screen.getByTestId("value")).toHaveTextContent("");
|
|
87
90
|
});
|
|
88
91
|
|
|
89
|
-
it(
|
|
90
|
-
const obs = observable({ name:
|
|
92
|
+
it("should handle object values", async () => {
|
|
93
|
+
const obs = observable({ name: "test", count: 5 });
|
|
91
94
|
render(<TestComponent obs={obs} />);
|
|
92
|
-
|
|
93
|
-
expect(screen.getByTestId(
|
|
94
|
-
|
|
95
|
+
|
|
96
|
+
expect(screen.getByTestId("value")).toHaveTextContent('{"name":"test","count":5}');
|
|
97
|
+
|
|
95
98
|
act(() => {
|
|
96
|
-
obs({ name:
|
|
99
|
+
obs({ name: "updated", count: 10 });
|
|
97
100
|
});
|
|
98
|
-
|
|
101
|
+
|
|
99
102
|
await waitFor(() => {
|
|
100
|
-
expect(screen.getByTestId(
|
|
103
|
+
expect(screen.getByTestId("value")).toHaveTextContent('{"name":"updated","count":10}');
|
|
101
104
|
});
|
|
102
105
|
});
|
|
103
106
|
});
|
|
104
107
|
|
|
105
|
-
describe(
|
|
106
|
-
it(
|
|
107
|
-
const obs = observable([
|
|
108
|
+
describe("array handling", () => {
|
|
109
|
+
it("should handle array values", () => {
|
|
110
|
+
const obs = observable(["a", "b", "c"]);
|
|
108
111
|
render(<ArrayTestComponent obs={obs} />);
|
|
109
|
-
|
|
110
|
-
expect(screen.getByTestId(
|
|
111
|
-
expect(screen.getByTestId(
|
|
112
|
+
|
|
113
|
+
expect(screen.getByTestId("array-value")).toHaveTextContent('["a","b","c"]');
|
|
114
|
+
expect(screen.getByTestId("array-length")).toHaveTextContent("3");
|
|
112
115
|
});
|
|
113
116
|
|
|
114
|
-
it(
|
|
115
|
-
const obs = observable([
|
|
117
|
+
it("should re-render when array is replaced with new reference", async () => {
|
|
118
|
+
const obs = observable(["a", "b"]);
|
|
116
119
|
render(<ArrayTestComponent obs={obs} />);
|
|
117
|
-
|
|
118
|
-
expect(screen.getByTestId(
|
|
119
|
-
|
|
120
|
+
|
|
121
|
+
expect(screen.getByTestId("array-length")).toHaveTextContent("2");
|
|
122
|
+
|
|
120
123
|
act(() => {
|
|
121
|
-
obs([
|
|
124
|
+
obs(["a", "b", "c"]); // New array reference
|
|
122
125
|
});
|
|
123
|
-
|
|
126
|
+
|
|
124
127
|
await waitFor(() => {
|
|
125
|
-
expect(screen.getByTestId(
|
|
128
|
+
expect(screen.getByTestId("array-length")).toHaveTextContent("3");
|
|
126
129
|
});
|
|
127
130
|
});
|
|
128
131
|
|
|
129
|
-
it(
|
|
132
|
+
it("should NOT re-render when array is mutated in place (requires immutable updates)", async () => {
|
|
130
133
|
// This tests the case where an array is mutated in place and notifySubscribers is called.
|
|
131
|
-
//
|
|
134
|
+
//
|
|
132
135
|
// IMPORTANT BEHAVIORAL NOTE:
|
|
133
|
-
// The OLD implementation (useState + useEffect) had special handling that would
|
|
134
|
-
// spread the array when it detected same-reference mutation:
|
|
136
|
+
// The OLD implementation (useState + useEffect) had special handling that would
|
|
137
|
+
// spread the array when it detected same-reference mutation:
|
|
135
138
|
// if (isArray(newData) && newData === data) setData([...newData])
|
|
136
|
-
//
|
|
139
|
+
//
|
|
137
140
|
// The NEW implementation (useSyncExternalStore) does NOT do this.
|
|
138
141
|
// useSyncExternalStore uses Object.is() comparison on getSnapshot results.
|
|
139
142
|
// If the reference is the same, React won't re-render.
|
|
@@ -141,132 +144,132 @@ describe('useObservable', () => {
|
|
|
141
144
|
// This is actually the CORRECT behavior - it encourages immutable data patterns
|
|
142
145
|
// which are required for React 18 concurrent rendering to work correctly.
|
|
143
146
|
// Mutating data in place can cause tearing in concurrent mode.
|
|
144
|
-
|
|
145
|
-
const obs = observable([
|
|
147
|
+
|
|
148
|
+
const obs = observable(["a", "b"]);
|
|
146
149
|
render(<ArrayTestComponent obs={obs} />);
|
|
147
|
-
|
|
148
|
-
expect(screen.getByTestId(
|
|
149
|
-
|
|
150
|
+
|
|
151
|
+
expect(screen.getByTestId("array-length")).toHaveTextContent("2");
|
|
152
|
+
|
|
150
153
|
act(() => {
|
|
151
154
|
// Mutate the array in place (BAD PRACTICE - don't do this!)
|
|
152
155
|
const arr = obs();
|
|
153
|
-
arr.push(
|
|
156
|
+
arr.push("c");
|
|
154
157
|
obs.notifySubscribers();
|
|
155
158
|
});
|
|
156
|
-
|
|
159
|
+
|
|
157
160
|
// The observable itself has the mutated value
|
|
158
|
-
expect(obs()).toEqual([
|
|
159
|
-
|
|
161
|
+
expect(obs()).toEqual(["a", "b", "c"]);
|
|
162
|
+
|
|
160
163
|
// But the component did NOT re-render because the reference didn't change
|
|
161
164
|
// This is expected with useSyncExternalStore - it requires immutable updates
|
|
162
|
-
expect(screen.getByTestId(
|
|
165
|
+
expect(screen.getByTestId("array-length")).toHaveTextContent("2");
|
|
163
166
|
});
|
|
164
167
|
|
|
165
|
-
it(
|
|
168
|
+
it("should re-render when array is properly updated with spread", async () => {
|
|
166
169
|
// This is the recommended pattern for updating arrays
|
|
167
|
-
const obs = observable([
|
|
170
|
+
const obs = observable(["a", "b"]);
|
|
168
171
|
render(<ArrayTestComponent obs={obs} />);
|
|
169
|
-
|
|
170
|
-
expect(screen.getByTestId(
|
|
171
|
-
|
|
172
|
+
|
|
173
|
+
expect(screen.getByTestId("array-length")).toHaveTextContent("2");
|
|
174
|
+
|
|
172
175
|
act(() => {
|
|
173
|
-
obs([...obs(),
|
|
176
|
+
obs([...obs(), "c"]); // Proper immutable update
|
|
174
177
|
});
|
|
175
|
-
|
|
178
|
+
|
|
176
179
|
await waitFor(() => {
|
|
177
|
-
expect(screen.getByTestId(
|
|
178
|
-
expect(screen.getByTestId(
|
|
180
|
+
expect(screen.getByTestId("array-length")).toHaveTextContent("3");
|
|
181
|
+
expect(screen.getByTestId("array-value")).toHaveTextContent('["a","b","c"]');
|
|
179
182
|
});
|
|
180
183
|
});
|
|
181
184
|
});
|
|
182
185
|
|
|
183
|
-
describe(
|
|
184
|
-
it(
|
|
185
|
-
const obs = observable(
|
|
186
|
+
describe("subscription management", () => {
|
|
187
|
+
it("should unsubscribe when component unmounts", () => {
|
|
188
|
+
const obs = observable("test");
|
|
186
189
|
const initialSubscriberCount = obs.subscriberCount();
|
|
187
|
-
|
|
190
|
+
|
|
188
191
|
const { unmount } = render(<TestComponent obs={obs} />);
|
|
189
|
-
|
|
192
|
+
|
|
190
193
|
// Should have subscribed
|
|
191
194
|
expect(obs.subscriberCount()).toBeGreaterThan(initialSubscriberCount);
|
|
192
|
-
|
|
195
|
+
|
|
193
196
|
unmount();
|
|
194
|
-
|
|
197
|
+
|
|
195
198
|
// Should have unsubscribed
|
|
196
199
|
expect(obs.subscriberCount()).toBe(initialSubscriberCount);
|
|
197
200
|
});
|
|
198
201
|
|
|
199
|
-
it(
|
|
200
|
-
const obs = observable(
|
|
201
|
-
|
|
202
|
+
it("should handle multiple components subscribing to same observable", async () => {
|
|
203
|
+
const obs = observable("shared");
|
|
204
|
+
|
|
202
205
|
render(
|
|
203
206
|
<div>
|
|
204
207
|
<TestComponent obs={obs} testId="value1" />
|
|
205
208
|
<TestComponent obs={obs} testId="value2" />
|
|
206
|
-
</div
|
|
209
|
+
</div>,
|
|
207
210
|
);
|
|
208
|
-
|
|
209
|
-
expect(screen.getByTestId(
|
|
210
|
-
expect(screen.getByTestId(
|
|
211
|
-
|
|
211
|
+
|
|
212
|
+
expect(screen.getByTestId("value1")).toHaveTextContent('"shared"');
|
|
213
|
+
expect(screen.getByTestId("value2")).toHaveTextContent('"shared"');
|
|
214
|
+
|
|
212
215
|
act(() => {
|
|
213
|
-
obs(
|
|
216
|
+
obs("updated");
|
|
214
217
|
});
|
|
215
|
-
|
|
218
|
+
|
|
216
219
|
await waitFor(() => {
|
|
217
|
-
expect(screen.getByTestId(
|
|
218
|
-
expect(screen.getByTestId(
|
|
220
|
+
expect(screen.getByTestId("value1")).toHaveTextContent('"updated"');
|
|
221
|
+
expect(screen.getByTestId("value2")).toHaveTextContent('"updated"');
|
|
219
222
|
});
|
|
220
223
|
});
|
|
221
224
|
});
|
|
222
225
|
|
|
223
|
-
describe(
|
|
224
|
-
it(
|
|
226
|
+
describe("rapid updates", () => {
|
|
227
|
+
it("should handle rapid sequential updates correctly", async () => {
|
|
225
228
|
const obs = observable(0);
|
|
226
|
-
|
|
229
|
+
|
|
227
230
|
function CounterComponent() {
|
|
228
231
|
const [value] = useObservable(obs);
|
|
229
232
|
return <span data-testid="counter">{value}</span>;
|
|
230
233
|
}
|
|
231
|
-
|
|
234
|
+
|
|
232
235
|
render(<CounterComponent />);
|
|
233
|
-
|
|
236
|
+
|
|
234
237
|
act(() => {
|
|
235
238
|
for (let i = 1; i <= 10; i++) {
|
|
236
239
|
obs(i);
|
|
237
240
|
}
|
|
238
241
|
});
|
|
239
|
-
|
|
242
|
+
|
|
240
243
|
await waitFor(() => {
|
|
241
|
-
expect(screen.getByTestId(
|
|
244
|
+
expect(screen.getByTestId("counter")).toHaveTextContent("10");
|
|
242
245
|
});
|
|
243
246
|
});
|
|
244
247
|
});
|
|
245
248
|
});
|
|
246
249
|
|
|
247
|
-
describe(
|
|
248
|
-
it(
|
|
250
|
+
describe("useObservableState", () => {
|
|
251
|
+
it("should create an observable with the initial value", () => {
|
|
249
252
|
render(<ObservableStateTestComponent initialValue={42} />);
|
|
250
|
-
expect(screen.getByTestId(
|
|
253
|
+
expect(screen.getByTestId("state-value")).toHaveTextContent("42");
|
|
251
254
|
});
|
|
252
255
|
|
|
253
|
-
it(
|
|
256
|
+
it("should re-render when the observable is updated", async () => {
|
|
254
257
|
render(<ObservableStateTestComponent initialValue={0} />);
|
|
255
|
-
|
|
256
|
-
expect(screen.getByTestId(
|
|
257
|
-
|
|
258
|
+
|
|
259
|
+
expect(screen.getByTestId("state-value")).toHaveTextContent("0");
|
|
260
|
+
|
|
258
261
|
act(() => {
|
|
259
|
-
screen.getByTestId(
|
|
262
|
+
screen.getByTestId("increment").click();
|
|
260
263
|
});
|
|
261
|
-
|
|
264
|
+
|
|
262
265
|
await waitFor(() => {
|
|
263
|
-
expect(screen.getByTestId(
|
|
266
|
+
expect(screen.getByTestId("state-value")).toHaveTextContent("1");
|
|
264
267
|
});
|
|
265
268
|
});
|
|
266
269
|
|
|
267
|
-
it(
|
|
270
|
+
it("should maintain the same observable instance across re-renders", async () => {
|
|
268
271
|
let capturedObs: Observable<number> | null = null;
|
|
269
|
-
|
|
272
|
+
|
|
270
273
|
function CaptureObsComponent() {
|
|
271
274
|
const obs = useObservableState(0);
|
|
272
275
|
if (!capturedObs) {
|
|
@@ -276,49 +279,53 @@ describe('useObservableState', () => {
|
|
|
276
279
|
return (
|
|
277
280
|
<div>
|
|
278
281
|
<span data-testid="value">{value}</span>
|
|
279
|
-
<span data-testid="same-obs">{obs === capturedObs ?
|
|
280
|
-
<button data-testid="update" onClick={() => obs(obs() + 1)}>
|
|
282
|
+
<span data-testid="same-obs">{obs === capturedObs ? "same" : "different"}</span>
|
|
283
|
+
<button type="button" data-testid="update" onClick={() => obs(obs() + 1)}>
|
|
284
|
+
Update
|
|
285
|
+
</button>
|
|
281
286
|
</div>
|
|
282
287
|
);
|
|
283
288
|
}
|
|
284
|
-
|
|
289
|
+
|
|
285
290
|
render(<CaptureObsComponent />);
|
|
286
|
-
|
|
287
|
-
expect(screen.getByTestId(
|
|
288
|
-
|
|
291
|
+
|
|
292
|
+
expect(screen.getByTestId("same-obs")).toHaveTextContent("same");
|
|
293
|
+
|
|
289
294
|
// Trigger a re-render by updating the observable
|
|
290
295
|
act(() => {
|
|
291
|
-
screen.getByTestId(
|
|
296
|
+
screen.getByTestId("update").click();
|
|
292
297
|
});
|
|
293
|
-
|
|
298
|
+
|
|
294
299
|
await waitFor(() => {
|
|
295
|
-
expect(screen.getByTestId(
|
|
296
|
-
expect(screen.getByTestId(
|
|
300
|
+
expect(screen.getByTestId("value")).toHaveTextContent("1");
|
|
301
|
+
expect(screen.getByTestId("same-obs")).toHaveTextContent("same");
|
|
297
302
|
});
|
|
298
303
|
});
|
|
299
304
|
|
|
300
|
-
it(
|
|
305
|
+
it("should not subscribe when doNotSubscribe is true", () => {
|
|
301
306
|
function NoSubscribeComponent() {
|
|
302
307
|
const obs = useObservableState(0, true); // doNotSubscribe = true
|
|
303
308
|
return (
|
|
304
309
|
<div>
|
|
305
310
|
<span data-testid="value">{obs()}</span>
|
|
306
|
-
<button data-testid="update" onClick={() => obs(obs() + 1)}>
|
|
311
|
+
<button type="button" data-testid="update" onClick={() => obs(obs() + 1)}>
|
|
312
|
+
Update
|
|
313
|
+
</button>
|
|
307
314
|
</div>
|
|
308
315
|
);
|
|
309
316
|
}
|
|
310
|
-
|
|
317
|
+
|
|
311
318
|
render(<NoSubscribeComponent />);
|
|
312
|
-
|
|
313
|
-
expect(screen.getByTestId(
|
|
314
|
-
|
|
319
|
+
|
|
320
|
+
expect(screen.getByTestId("value")).toHaveTextContent("0");
|
|
321
|
+
|
|
315
322
|
// Update won't cause re-render since we're not subscribed
|
|
316
323
|
act(() => {
|
|
317
|
-
screen.getByTestId(
|
|
324
|
+
screen.getByTestId("update").click();
|
|
318
325
|
});
|
|
319
|
-
|
|
326
|
+
|
|
320
327
|
// Value in DOM should still show 0 since component didn't re-render
|
|
321
328
|
// But the observable itself has been updated
|
|
322
|
-
expect(screen.getByTestId(
|
|
329
|
+
expect(screen.getByTestId("value")).toHaveTextContent("0");
|
|
323
330
|
});
|
|
324
331
|
});
|
package/src/hooks.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
isSubscribable,
|
|
3
|
+
type Observable,
|
|
4
|
+
observable,
|
|
5
|
+
unwrapObservable,
|
|
6
|
+
} from "@peers-app/peers-sdk";
|
|
7
|
+
import type React from "react";
|
|
8
|
+
import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Use this to subscribe to an observable or computed in a functional component.
|
|
@@ -11,16 +14,22 @@ import React, { useCallback, useEffect, useState, useSyncExternalStore } from 'r
|
|
|
11
14
|
* @param deps an array of dependencies that will cause re-subscription when changed
|
|
12
15
|
* @returns the current value of the observable or computed and a function to set the value
|
|
13
16
|
*/
|
|
14
|
-
export function useObservable<T>(
|
|
17
|
+
export function useObservable<T>(
|
|
18
|
+
sub: Observable<T> | T,
|
|
19
|
+
deps: React.DependencyList = [],
|
|
20
|
+
): [T, (value: T) => void] {
|
|
15
21
|
// Create memoized subscribe function that adapts Observable's subscribe API to useSyncExternalStore's expected signature
|
|
16
|
-
const subscribe = useCallback(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const subscribe = useCallback(
|
|
23
|
+
(onStoreChange: () => void) => {
|
|
24
|
+
if (!isSubscribable(sub)) {
|
|
25
|
+
// If not subscribable, return a no-op unsubscribe
|
|
26
|
+
return () => {};
|
|
27
|
+
}
|
|
28
|
+
const subscription = sub.subscribe(onStoreChange);
|
|
29
|
+
return () => subscription.dispose();
|
|
30
|
+
},
|
|
31
|
+
[sub, ...deps],
|
|
32
|
+
);
|
|
24
33
|
|
|
25
34
|
// getSnapshot returns the current value from the observable
|
|
26
35
|
const getSnapshot = useCallback(() => {
|
|
@@ -31,11 +40,14 @@ export function useObservable<T>(sub: Observable<T> | T, deps: React.DependencyL
|
|
|
31
40
|
const data = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
32
41
|
|
|
33
42
|
// Create setter that updates both local understanding and the observable
|
|
34
|
-
const setter = useCallback(
|
|
35
|
-
|
|
36
|
-
sub
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
const setter = useCallback(
|
|
44
|
+
(newData: T) => {
|
|
45
|
+
if (isSubscribable(sub)) {
|
|
46
|
+
sub(newData);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
[sub],
|
|
50
|
+
);
|
|
39
51
|
|
|
40
52
|
return [data, setter];
|
|
41
53
|
}
|
|
@@ -47,23 +59,28 @@ export function useObservable<T>(sub: Observable<T> | T, deps: React.DependencyL
|
|
|
47
59
|
* @param deps the dependencies to pass to useEffect which will trigger a re-render when any of the dependencies change
|
|
48
60
|
* @returns the initial value and the resolved value of the promise
|
|
49
61
|
*/
|
|
50
|
-
export function usePromise<T>(
|
|
62
|
+
export function usePromise<T>(
|
|
63
|
+
p: Promise<T> | (() => Promise<T>),
|
|
64
|
+
initialValue?: T,
|
|
65
|
+
deps: React.DependencyList = [],
|
|
66
|
+
): T | undefined {
|
|
51
67
|
const [data, setData] = useState(initialValue);
|
|
68
|
+
// `deps` drives when the effect re-runs; callers include `p` when it changes. Omitting `p` avoids duplicate tracking with `[...deps]`.
|
|
52
69
|
useEffect(() => {
|
|
53
70
|
let disposed = false;
|
|
54
|
-
if (typeof p ===
|
|
71
|
+
if (typeof p === "function") {
|
|
55
72
|
p = p();
|
|
56
73
|
}
|
|
57
|
-
p.then(newData => {
|
|
74
|
+
p.then((newData) => {
|
|
58
75
|
// if (!_.isEqual(newData, data) && !disposed) {
|
|
59
76
|
if (!disposed) {
|
|
60
|
-
setData(newData)
|
|
77
|
+
setData(newData);
|
|
61
78
|
}
|
|
62
|
-
})
|
|
63
|
-
return () => {
|
|
64
|
-
disposed = true;
|
|
65
|
-
}
|
|
66
|
-
}, deps);
|
|
79
|
+
});
|
|
80
|
+
return () => {
|
|
81
|
+
disposed = true;
|
|
82
|
+
};
|
|
83
|
+
}, [...deps]);
|
|
67
84
|
return data;
|
|
68
85
|
}
|
|
69
86
|
|
|
@@ -76,10 +93,16 @@ export function usePromise<T>(p: Promise<T> | (() => Promise<T>), initialValue?:
|
|
|
76
93
|
* @returns
|
|
77
94
|
*/
|
|
78
95
|
export function useObservableState<T>(initialValue: T, doNotSubscribe?: boolean): Observable<T>;
|
|
79
|
-
export function useObservableState<T = undefined>(
|
|
96
|
+
export function useObservableState<T = undefined>(
|
|
97
|
+
initialValue?: T,
|
|
98
|
+
doNotSubscribe?: boolean,
|
|
99
|
+
): Observable<T | undefined>;
|
|
80
100
|
export function useObservableState<T>(initialValue?: T, doNotSubscribe?: boolean) {
|
|
81
|
-
const [obs] = useState(() =>
|
|
101
|
+
const [obs] = useState(() =>
|
|
102
|
+
initialValue !== undefined ? observable<T>(initialValue) : observable<T | undefined>(),
|
|
103
|
+
);
|
|
82
104
|
if (!doNotSubscribe) {
|
|
105
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: when doNotSubscribe, caller keeps a stable observable without React-driven subscription (see docstring).
|
|
83
106
|
useObservable(obs);
|
|
84
107
|
}
|
|
85
108
|
return obs;
|
|
@@ -87,32 +110,41 @@ export function useObservableState<T>(initialValue?: T, doNotSubscribe?: boolean
|
|
|
87
110
|
|
|
88
111
|
/**
|
|
89
112
|
* Use this to register a handler for a knockout subscribable in a functional component.
|
|
90
|
-
* The handler will be called immediately and whenever the subscribable changes.
|
|
113
|
+
* The handler will be called immediately and whenever the subscribable changes.
|
|
91
114
|
* @param subscribable The observable to subscribe to
|
|
92
115
|
* @param onChange The function to call with the new value when the observable changes
|
|
93
|
-
|
|
94
|
-
export function useSubscription<T>(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
*/
|
|
117
|
+
export function useSubscription<T>(
|
|
118
|
+
subscribable: Observable<T>,
|
|
119
|
+
onChange: (value: T) => void,
|
|
120
|
+
doNotCallOnChangeDuringSetup?: boolean,
|
|
121
|
+
): void {
|
|
98
122
|
useEffect(() => {
|
|
123
|
+
if (!doNotCallOnChangeDuringSetup) {
|
|
124
|
+
onChange(subscribable());
|
|
125
|
+
}
|
|
99
126
|
const subscription = subscribable.subscribe(() => onChange(subscribable()));
|
|
100
127
|
return () => subscription.dispose();
|
|
101
|
-
}, [subscribable, onChange]);
|
|
128
|
+
}, [subscribable, onChange, doNotCallOnChangeDuringSetup]);
|
|
102
129
|
}
|
|
103
130
|
|
|
104
|
-
export function useOnScreen(ref: React.RefObject<
|
|
105
|
-
const [isIntersecting, setIntersecting] = useState(false)
|
|
106
|
-
|
|
107
|
-
const observer = new IntersectionObserver(
|
|
108
|
-
([entry]) => setIntersecting(entry.isIntersecting)
|
|
109
|
-
)
|
|
131
|
+
export function useOnScreen(ref: React.RefObject<Element | null>) {
|
|
132
|
+
const [isIntersecting, setIntersecting] = useState(false);
|
|
110
133
|
|
|
134
|
+
// Read ref.current once on mount (ref object identity is stable).
|
|
111
135
|
useEffect(() => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
const element = ref.current;
|
|
137
|
+
if (!element) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
141
|
+
setIntersecting(entry.isIntersecting);
|
|
142
|
+
});
|
|
143
|
+
observer.observe(element);
|
|
144
|
+
return () => {
|
|
145
|
+
observer.disconnect();
|
|
146
|
+
};
|
|
147
|
+
}, []);
|
|
116
148
|
|
|
117
|
-
return isIntersecting
|
|
149
|
+
return isIntersecting;
|
|
118
150
|
}
|