tab-bridge 0.1.1 → 0.3.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/README.md +263 -10
- package/dist/{chunk-42VOZR6E.js → chunk-4JDWAUYM.js} +216 -93
- package/dist/{chunk-BQCNBNBT.cjs → chunk-TGEXRVAL.cjs} +219 -92
- package/dist/index.cjs +41 -25
- package/dist/index.d.cts +112 -5
- package/dist/index.d.ts +112 -5
- package/dist/index.js +2 -2
- package/dist/instance-hvEUHx6i.d.cts +194 -0
- package/dist/instance-hvEUHx6i.d.ts +194 -0
- package/dist/options-DmHyGTL0.d.cts +93 -0
- package/dist/options-iN7Rnvwj.d.ts +93 -0
- package/dist/react/index.cjs +82 -9
- package/dist/react/index.d.cts +48 -2
- package/dist/react/index.d.ts +48 -2
- package/dist/react/index.js +79 -9
- package/dist/zustand/index.cjs +80 -0
- package/dist/zustand/index.d.cts +87 -0
- package/dist/zustand/index.d.ts +87 -0
- package/dist/zustand/index.js +78 -0
- package/package.json +22 -6
- package/dist/types-BtK4ixKz.d.cts +0 -306
- package/dist/types-BtK4ixKz.d.ts +0 -306
package/dist/react/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkTGEXRVAL_cjs = require('../chunk-TGEXRVAL.cjs');
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ function TabSyncProvider({
|
|
|
11
11
|
}) {
|
|
12
12
|
const instanceRef = react.useRef(null);
|
|
13
13
|
if (!instanceRef.current) {
|
|
14
|
-
instanceRef.current =
|
|
14
|
+
instanceRef.current = chunkTGEXRVAL_cjs.createTabSync(options);
|
|
15
15
|
}
|
|
16
16
|
react.useEffect(() => {
|
|
17
17
|
return () => {
|
|
@@ -26,7 +26,7 @@ function useInstance(options) {
|
|
|
26
26
|
const contextInstance = react.useContext(TabSyncContext);
|
|
27
27
|
const [ownInstance] = react.useState(() => {
|
|
28
28
|
if (!contextInstance && options) {
|
|
29
|
-
return
|
|
29
|
+
return chunkTGEXRVAL_cjs.createTabSync(options);
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
});
|
|
@@ -127,14 +127,14 @@ function useTabSyncSelector(selector, isEqual) {
|
|
|
127
127
|
initializedRef.current = true;
|
|
128
128
|
}
|
|
129
129
|
const subscribe = react.useCallback(
|
|
130
|
-
(onStoreChange) => instance.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (!equal(resultRef.current, next)) {
|
|
130
|
+
(onStoreChange) => instance.select(
|
|
131
|
+
(state) => selectorRef.current(state),
|
|
132
|
+
(next) => {
|
|
134
133
|
resultRef.current = next;
|
|
135
134
|
onStoreChange();
|
|
136
|
-
}
|
|
137
|
-
|
|
135
|
+
},
|
|
136
|
+
{ isEqual: isEqualRef.current }
|
|
137
|
+
),
|
|
138
138
|
[instance]
|
|
139
139
|
);
|
|
140
140
|
const getSnapshot = react.useCallback(() => resultRef.current, []);
|
|
@@ -165,10 +165,83 @@ function useIsLeader() {
|
|
|
165
165
|
const getServerSnapshot = react.useCallback(() => SERVER_SNAPSHOT, []);
|
|
166
166
|
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
167
167
|
}
|
|
168
|
+
var SERVER_SNAPSHOT2 = [];
|
|
169
|
+
function useTabs() {
|
|
170
|
+
const instance = react.useContext(TabSyncContext);
|
|
171
|
+
if (!instance) {
|
|
172
|
+
throw new Error("useTabs must be used within a <TabSyncProvider>");
|
|
173
|
+
}
|
|
174
|
+
const tabsRef = react.useRef(instance.getTabs());
|
|
175
|
+
const subscribe = react.useCallback(
|
|
176
|
+
(onStoreChange) => instance.onTabChange((tabs) => {
|
|
177
|
+
tabsRef.current = tabs;
|
|
178
|
+
onStoreChange();
|
|
179
|
+
}),
|
|
180
|
+
[instance]
|
|
181
|
+
);
|
|
182
|
+
const getSnapshot = react.useCallback(() => tabsRef.current, []);
|
|
183
|
+
const getServerSnapshot = react.useCallback(() => SERVER_SNAPSHOT2, []);
|
|
184
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
185
|
+
}
|
|
186
|
+
function useLeaderInfo() {
|
|
187
|
+
const instance = react.useContext(TabSyncContext);
|
|
188
|
+
if (!instance) {
|
|
189
|
+
throw new Error("useLeaderInfo must be used within a <TabSyncProvider>");
|
|
190
|
+
}
|
|
191
|
+
const leaderRef = react.useRef(instance.getLeader());
|
|
192
|
+
const subscribe = react.useCallback(
|
|
193
|
+
(onStoreChange) => {
|
|
194
|
+
const unsubs = [];
|
|
195
|
+
unsubs.push(
|
|
196
|
+
instance.onTabChange(() => {
|
|
197
|
+
const next = instance.getLeader();
|
|
198
|
+
if (next?.id !== leaderRef.current?.id) {
|
|
199
|
+
leaderRef.current = next;
|
|
200
|
+
onStoreChange();
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
unsubs.push(
|
|
205
|
+
instance.onLeader(() => {
|
|
206
|
+
leaderRef.current = instance.getLeader();
|
|
207
|
+
onStoreChange();
|
|
208
|
+
return () => {
|
|
209
|
+
leaderRef.current = instance.getLeader();
|
|
210
|
+
onStoreChange();
|
|
211
|
+
};
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
return () => {
|
|
215
|
+
for (const u of unsubs) u();
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
[instance]
|
|
219
|
+
);
|
|
220
|
+
const getSnapshot = react.useCallback(() => leaderRef.current, []);
|
|
221
|
+
const getServerSnapshot = react.useCallback(() => null, []);
|
|
222
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
223
|
+
}
|
|
224
|
+
function useTabSyncActions() {
|
|
225
|
+
const instance = react.useContext(TabSyncContext);
|
|
226
|
+
if (!instance) {
|
|
227
|
+
throw new Error("useTabSyncActions must be used within a <TabSyncProvider>");
|
|
228
|
+
}
|
|
229
|
+
return react.useMemo(
|
|
230
|
+
() => ({
|
|
231
|
+
set: instance.set.bind(instance),
|
|
232
|
+
patch: instance.patch.bind(instance),
|
|
233
|
+
transaction: instance.transaction.bind(instance)
|
|
234
|
+
}),
|
|
235
|
+
[instance]
|
|
236
|
+
);
|
|
237
|
+
}
|
|
168
238
|
|
|
169
239
|
exports.TabSyncContext = TabSyncContext;
|
|
170
240
|
exports.TabSyncProvider = TabSyncProvider;
|
|
171
241
|
exports.useIsLeader = useIsLeader;
|
|
242
|
+
exports.useLeaderInfo = useLeaderInfo;
|
|
172
243
|
exports.useTabSync = useTabSync;
|
|
244
|
+
exports.useTabSyncActions = useTabSyncActions;
|
|
173
245
|
exports.useTabSyncSelector = useTabSyncSelector;
|
|
174
246
|
exports.useTabSyncValue = useTabSyncValue;
|
|
247
|
+
exports.useTabs = useTabs;
|
package/dist/react/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { T as TabSyncOptions
|
|
4
|
+
import { T as TabSyncOptions } from '../options-DmHyGTL0.cjs';
|
|
5
|
+
import { T as TabSyncInstance, R as RPCMap, a as TabInfo } from '../instance-hvEUHx6i.cjs';
|
|
5
6
|
|
|
6
7
|
interface TabSyncProviderProps<TState extends Record<string, unknown>> {
|
|
7
8
|
options: TabSyncOptions<TState>;
|
|
@@ -35,6 +36,9 @@ declare function useTabSyncValue<TState extends Record<string, unknown>, K exten
|
|
|
35
36
|
* Subscribe to a **derived value** from the synced state.
|
|
36
37
|
* Only re-renders when the selector's output actually changes.
|
|
37
38
|
*
|
|
39
|
+
* Uses `instance.select()` internally so only changed-key
|
|
40
|
+
* evaluations trigger the selector, reducing unnecessary work.
|
|
41
|
+
*
|
|
38
42
|
* ```tsx
|
|
39
43
|
* const doneCount = useTabSyncSelector(
|
|
40
44
|
* (s) => s.todos.filter(t => t.done).length,
|
|
@@ -58,4 +62,46 @@ declare function useTabSyncSelector<TState extends Record<string, unknown>, TRes
|
|
|
58
62
|
*/
|
|
59
63
|
declare function useIsLeader(): boolean;
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Subscribe to the list of active tabs. Re-renders when tabs join or leave.
|
|
67
|
+
*
|
|
68
|
+
* ```tsx
|
|
69
|
+
* const tabs = useTabs();
|
|
70
|
+
* // tabs: TabInfo[]
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Must be used within a `<TabSyncProvider>`.
|
|
74
|
+
*/
|
|
75
|
+
declare function useTabs(): TabInfo[];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Subscribe to the current leader's full `TabInfo`.
|
|
79
|
+
* Returns `null` if no leader has been elected yet.
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* const leader = useLeaderInfo();
|
|
83
|
+
* // leader: TabInfo | null
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Must be used within a `<TabSyncProvider>`.
|
|
87
|
+
*/
|
|
88
|
+
declare function useLeaderInfo(): TabInfo | null;
|
|
89
|
+
|
|
90
|
+
interface TabSyncActions<TState extends Record<string, unknown>> {
|
|
91
|
+
set: TabSyncInstance<TState>['set'];
|
|
92
|
+
patch: TabSyncInstance<TState>['patch'];
|
|
93
|
+
transaction: TabSyncInstance<TState>['transaction'];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns `set`, `patch`, and `transaction` without subscribing to state.
|
|
97
|
+
* Components using only this hook never re-render due to state changes.
|
|
98
|
+
*
|
|
99
|
+
* ```tsx
|
|
100
|
+
* const { set, patch, transaction } = useTabSyncActions();
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* Must be used within a `<TabSyncProvider>`.
|
|
104
|
+
*/
|
|
105
|
+
declare function useTabSyncActions<TState extends Record<string, unknown> = Record<string, unknown>>(): TabSyncActions<TState>;
|
|
106
|
+
|
|
107
|
+
export { type TabSyncActions, TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { T as TabSyncOptions
|
|
4
|
+
import { T as TabSyncOptions } from '../options-iN7Rnvwj.js';
|
|
5
|
+
import { T as TabSyncInstance, R as RPCMap, a as TabInfo } from '../instance-hvEUHx6i.js';
|
|
5
6
|
|
|
6
7
|
interface TabSyncProviderProps<TState extends Record<string, unknown>> {
|
|
7
8
|
options: TabSyncOptions<TState>;
|
|
@@ -35,6 +36,9 @@ declare function useTabSyncValue<TState extends Record<string, unknown>, K exten
|
|
|
35
36
|
* Subscribe to a **derived value** from the synced state.
|
|
36
37
|
* Only re-renders when the selector's output actually changes.
|
|
37
38
|
*
|
|
39
|
+
* Uses `instance.select()` internally so only changed-key
|
|
40
|
+
* evaluations trigger the selector, reducing unnecessary work.
|
|
41
|
+
*
|
|
38
42
|
* ```tsx
|
|
39
43
|
* const doneCount = useTabSyncSelector(
|
|
40
44
|
* (s) => s.todos.filter(t => t.done).length,
|
|
@@ -58,4 +62,46 @@ declare function useTabSyncSelector<TState extends Record<string, unknown>, TRes
|
|
|
58
62
|
*/
|
|
59
63
|
declare function useIsLeader(): boolean;
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Subscribe to the list of active tabs. Re-renders when tabs join or leave.
|
|
67
|
+
*
|
|
68
|
+
* ```tsx
|
|
69
|
+
* const tabs = useTabs();
|
|
70
|
+
* // tabs: TabInfo[]
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Must be used within a `<TabSyncProvider>`.
|
|
74
|
+
*/
|
|
75
|
+
declare function useTabs(): TabInfo[];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Subscribe to the current leader's full `TabInfo`.
|
|
79
|
+
* Returns `null` if no leader has been elected yet.
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* const leader = useLeaderInfo();
|
|
83
|
+
* // leader: TabInfo | null
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Must be used within a `<TabSyncProvider>`.
|
|
87
|
+
*/
|
|
88
|
+
declare function useLeaderInfo(): TabInfo | null;
|
|
89
|
+
|
|
90
|
+
interface TabSyncActions<TState extends Record<string, unknown>> {
|
|
91
|
+
set: TabSyncInstance<TState>['set'];
|
|
92
|
+
patch: TabSyncInstance<TState>['patch'];
|
|
93
|
+
transaction: TabSyncInstance<TState>['transaction'];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns `set`, `patch`, and `transaction` without subscribing to state.
|
|
97
|
+
* Components using only this hook never re-render due to state changes.
|
|
98
|
+
*
|
|
99
|
+
* ```tsx
|
|
100
|
+
* const { set, patch, transaction } = useTabSyncActions();
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* Must be used within a `<TabSyncProvider>`.
|
|
104
|
+
*/
|
|
105
|
+
declare function useTabSyncActions<TState extends Record<string, unknown> = Record<string, unknown>>(): TabSyncActions<TState>;
|
|
106
|
+
|
|
107
|
+
export { type TabSyncActions, TabSyncContext, TabSyncProvider, type TabSyncProviderProps, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };
|
package/dist/react/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createTabSync } from '../chunk-
|
|
2
|
-
import { createContext, useRef, useEffect, useCallback, useSyncExternalStore, useContext, useState } from 'react';
|
|
1
|
+
import { createTabSync } from '../chunk-4JDWAUYM.js';
|
|
2
|
+
import { createContext, useRef, useEffect, useCallback, useSyncExternalStore, useContext, useMemo, useState } from 'react';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
var TabSyncContext = createContext(null);
|
|
@@ -125,14 +125,14 @@ function useTabSyncSelector(selector, isEqual) {
|
|
|
125
125
|
initializedRef.current = true;
|
|
126
126
|
}
|
|
127
127
|
const subscribe = useCallback(
|
|
128
|
-
(onStoreChange) => instance.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (!equal(resultRef.current, next)) {
|
|
128
|
+
(onStoreChange) => instance.select(
|
|
129
|
+
(state) => selectorRef.current(state),
|
|
130
|
+
(next) => {
|
|
132
131
|
resultRef.current = next;
|
|
133
132
|
onStoreChange();
|
|
134
|
-
}
|
|
135
|
-
|
|
133
|
+
},
|
|
134
|
+
{ isEqual: isEqualRef.current }
|
|
135
|
+
),
|
|
136
136
|
[instance]
|
|
137
137
|
);
|
|
138
138
|
const getSnapshot = useCallback(() => resultRef.current, []);
|
|
@@ -163,5 +163,75 @@ function useIsLeader() {
|
|
|
163
163
|
const getServerSnapshot = useCallback(() => SERVER_SNAPSHOT, []);
|
|
164
164
|
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
165
165
|
}
|
|
166
|
+
var SERVER_SNAPSHOT2 = [];
|
|
167
|
+
function useTabs() {
|
|
168
|
+
const instance = useContext(TabSyncContext);
|
|
169
|
+
if (!instance) {
|
|
170
|
+
throw new Error("useTabs must be used within a <TabSyncProvider>");
|
|
171
|
+
}
|
|
172
|
+
const tabsRef = useRef(instance.getTabs());
|
|
173
|
+
const subscribe = useCallback(
|
|
174
|
+
(onStoreChange) => instance.onTabChange((tabs) => {
|
|
175
|
+
tabsRef.current = tabs;
|
|
176
|
+
onStoreChange();
|
|
177
|
+
}),
|
|
178
|
+
[instance]
|
|
179
|
+
);
|
|
180
|
+
const getSnapshot = useCallback(() => tabsRef.current, []);
|
|
181
|
+
const getServerSnapshot = useCallback(() => SERVER_SNAPSHOT2, []);
|
|
182
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
183
|
+
}
|
|
184
|
+
function useLeaderInfo() {
|
|
185
|
+
const instance = useContext(TabSyncContext);
|
|
186
|
+
if (!instance) {
|
|
187
|
+
throw new Error("useLeaderInfo must be used within a <TabSyncProvider>");
|
|
188
|
+
}
|
|
189
|
+
const leaderRef = useRef(instance.getLeader());
|
|
190
|
+
const subscribe = useCallback(
|
|
191
|
+
(onStoreChange) => {
|
|
192
|
+
const unsubs = [];
|
|
193
|
+
unsubs.push(
|
|
194
|
+
instance.onTabChange(() => {
|
|
195
|
+
const next = instance.getLeader();
|
|
196
|
+
if (next?.id !== leaderRef.current?.id) {
|
|
197
|
+
leaderRef.current = next;
|
|
198
|
+
onStoreChange();
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
unsubs.push(
|
|
203
|
+
instance.onLeader(() => {
|
|
204
|
+
leaderRef.current = instance.getLeader();
|
|
205
|
+
onStoreChange();
|
|
206
|
+
return () => {
|
|
207
|
+
leaderRef.current = instance.getLeader();
|
|
208
|
+
onStoreChange();
|
|
209
|
+
};
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
return () => {
|
|
213
|
+
for (const u of unsubs) u();
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
[instance]
|
|
217
|
+
);
|
|
218
|
+
const getSnapshot = useCallback(() => leaderRef.current, []);
|
|
219
|
+
const getServerSnapshot = useCallback(() => null, []);
|
|
220
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
221
|
+
}
|
|
222
|
+
function useTabSyncActions() {
|
|
223
|
+
const instance = useContext(TabSyncContext);
|
|
224
|
+
if (!instance) {
|
|
225
|
+
throw new Error("useTabSyncActions must be used within a <TabSyncProvider>");
|
|
226
|
+
}
|
|
227
|
+
return useMemo(
|
|
228
|
+
() => ({
|
|
229
|
+
set: instance.set.bind(instance),
|
|
230
|
+
patch: instance.patch.bind(instance),
|
|
231
|
+
transaction: instance.transaction.bind(instance)
|
|
232
|
+
}),
|
|
233
|
+
[instance]
|
|
234
|
+
);
|
|
235
|
+
}
|
|
166
236
|
|
|
167
|
-
export { TabSyncContext, TabSyncProvider, useIsLeader, useTabSync, useTabSyncSelector, useTabSyncValue };
|
|
237
|
+
export { TabSyncContext, TabSyncProvider, useIsLeader, useLeaderInfo, useTabSync, useTabSyncActions, useTabSyncSelector, useTabSyncValue, useTabs };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkTGEXRVAL_cjs = require('../chunk-TGEXRVAL.cjs');
|
|
4
|
+
|
|
5
|
+
// src/zustand/middleware.ts
|
|
6
|
+
function shouldSyncKey(key, value, options) {
|
|
7
|
+
if (typeof value === "function") return false;
|
|
8
|
+
if (options?.include) return options.include.includes(key);
|
|
9
|
+
if (options?.exclude) return !options.exclude.includes(key);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
function extractSyncableState(state, options) {
|
|
13
|
+
const result = {};
|
|
14
|
+
for (const [key, value] of Object.entries(state)) {
|
|
15
|
+
if (shouldSyncKey(key, value, options)) {
|
|
16
|
+
result[key] = value;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
function validateOptions(options) {
|
|
22
|
+
if (options?.include && options?.exclude) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"[tab-bridge/zustand] `include` and `exclude` are mutually exclusive"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var tabSyncImpl = (initializer, options) => (set, get, api) => {
|
|
29
|
+
validateOptions(options);
|
|
30
|
+
let sync_instance = null;
|
|
31
|
+
let is_remote_update = false;
|
|
32
|
+
const initial_state = initializer(set, get, api);
|
|
33
|
+
const syncable_initial = extractSyncableState(initial_state, options);
|
|
34
|
+
sync_instance = chunkTGEXRVAL_cjs.createTabSync({
|
|
35
|
+
initial: syncable_initial,
|
|
36
|
+
channel: options?.channel ?? "tab-sync-zustand",
|
|
37
|
+
debug: options?.debug,
|
|
38
|
+
transport: options?.transport,
|
|
39
|
+
merge: options?.merge,
|
|
40
|
+
onError: options?.onError
|
|
41
|
+
});
|
|
42
|
+
api.subscribe((next_state, prev_state) => {
|
|
43
|
+
if (is_remote_update || !sync_instance) return;
|
|
44
|
+
const next = next_state;
|
|
45
|
+
const prev = prev_state;
|
|
46
|
+
const diff = {};
|
|
47
|
+
let has_diff = false;
|
|
48
|
+
for (const key of Object.keys(next)) {
|
|
49
|
+
if (shouldSyncKey(key, next[key], options) && !Object.is(prev[key], next[key])) {
|
|
50
|
+
diff[key] = next[key];
|
|
51
|
+
has_diff = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (has_diff) {
|
|
55
|
+
sync_instance.patch(diff);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
sync_instance.onChange((remote_state, changed_keys, meta) => {
|
|
59
|
+
if (meta.isLocal) return;
|
|
60
|
+
is_remote_update = true;
|
|
61
|
+
try {
|
|
62
|
+
const patch = {};
|
|
63
|
+
let has_patch = false;
|
|
64
|
+
for (const key of changed_keys) {
|
|
65
|
+
patch[key] = remote_state[key];
|
|
66
|
+
has_patch = true;
|
|
67
|
+
}
|
|
68
|
+
if (has_patch) {
|
|
69
|
+
api.setState(patch);
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
is_remote_update = false;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
options?.onSyncReady?.(sync_instance);
|
|
76
|
+
return initial_state;
|
|
77
|
+
};
|
|
78
|
+
var tabSync = tabSyncImpl;
|
|
79
|
+
|
|
80
|
+
exports.tabSync = tabSync;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { StoreMutatorIdentifier, StateCreator } from 'zustand/vanilla';
|
|
2
|
+
import { T as TabSyncInstance } from '../instance-hvEUHx6i.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the `tabSync` Zustand middleware.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { create } from 'zustand';
|
|
10
|
+
* import { tabSync } from 'tab-bridge/zustand';
|
|
11
|
+
*
|
|
12
|
+
* const useStore = create(
|
|
13
|
+
* tabSync(
|
|
14
|
+
* (set) => ({
|
|
15
|
+
* count: 0,
|
|
16
|
+
* inc: () => set((s) => ({ count: s.count + 1 })),
|
|
17
|
+
* }),
|
|
18
|
+
* { channel: 'my-app', exclude: ['localOnly'] }
|
|
19
|
+
* )
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
interface TabSyncZustandOptions<T = Record<string, unknown>> {
|
|
24
|
+
/** Channel name for cross-tab communication. @default 'tab-sync-zustand' */
|
|
25
|
+
channel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Only sync these keys. Functions are always excluded.
|
|
28
|
+
* Mutually exclusive with `exclude`.
|
|
29
|
+
*/
|
|
30
|
+
include?: readonly (keyof T & string)[];
|
|
31
|
+
/**
|
|
32
|
+
* Exclude these keys from syncing. Functions are always excluded.
|
|
33
|
+
* Mutually exclusive with `include`.
|
|
34
|
+
*/
|
|
35
|
+
exclude?: readonly (keyof T & string)[];
|
|
36
|
+
/**
|
|
37
|
+
* Custom conflict resolution.
|
|
38
|
+
* Called when the same key is updated on two tabs simultaneously.
|
|
39
|
+
* @default Last-write-wins (LWW)
|
|
40
|
+
*/
|
|
41
|
+
merge?: (localValue: unknown, remoteValue: unknown, key: string) => unknown;
|
|
42
|
+
/** Force a specific transport layer. @default auto-detect */
|
|
43
|
+
transport?: 'broadcast-channel' | 'local-storage';
|
|
44
|
+
/** Enable debug logging. @default false */
|
|
45
|
+
debug?: boolean;
|
|
46
|
+
/** Error callback for non-fatal errors (channel failures, etc.). */
|
|
47
|
+
onError?: (error: Error) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Callback invoked when the underlying `TabSyncInstance` is ready.
|
|
50
|
+
* Use this to access advanced features (RPC, leader election, etc.)
|
|
51
|
+
* or to store a reference for manual cleanup via `instance.destroy()`.
|
|
52
|
+
*/
|
|
53
|
+
onSyncReady?: (instance: TabSyncInstance<Record<string, unknown>>) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type TabSyncMiddleware = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, Mps, Mcs>, options?: TabSyncZustandOptions<T>) => StateCreator<T, Mps, Mcs>;
|
|
57
|
+
/**
|
|
58
|
+
* Zustand middleware that synchronizes store state across browser tabs
|
|
59
|
+
* via tab-bridge's BroadcastChannel/localStorage transport.
|
|
60
|
+
*
|
|
61
|
+
* Functions (actions) are automatically excluded from synchronization.
|
|
62
|
+
* Use `include` or `exclude` options to further filter which keys are synced.
|
|
63
|
+
*
|
|
64
|
+
* @param initializer - Zustand StateCreator function.
|
|
65
|
+
* @param options - Synchronization options.
|
|
66
|
+
* @returns A wrapped StateCreator that synchronizes state across tabs.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { create } from 'zustand';
|
|
71
|
+
* import { tabSync } from 'tab-bridge/zustand';
|
|
72
|
+
*
|
|
73
|
+
* const useStore = create(
|
|
74
|
+
* tabSync(
|
|
75
|
+
* (set) => ({
|
|
76
|
+
* count: 0,
|
|
77
|
+
* inc: () => set((s) => ({ count: s.count + 1 })),
|
|
78
|
+
* }),
|
|
79
|
+
* { channel: 'my-app' }
|
|
80
|
+
* )
|
|
81
|
+
* );
|
|
82
|
+
* // All tabs sharing channel 'my-app' will have synchronized state.
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
declare const tabSync: TabSyncMiddleware;
|
|
86
|
+
|
|
87
|
+
export { type TabSyncZustandOptions, tabSync };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { StoreMutatorIdentifier, StateCreator } from 'zustand/vanilla';
|
|
2
|
+
import { T as TabSyncInstance } from '../instance-hvEUHx6i.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the `tabSync` Zustand middleware.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { create } from 'zustand';
|
|
10
|
+
* import { tabSync } from 'tab-bridge/zustand';
|
|
11
|
+
*
|
|
12
|
+
* const useStore = create(
|
|
13
|
+
* tabSync(
|
|
14
|
+
* (set) => ({
|
|
15
|
+
* count: 0,
|
|
16
|
+
* inc: () => set((s) => ({ count: s.count + 1 })),
|
|
17
|
+
* }),
|
|
18
|
+
* { channel: 'my-app', exclude: ['localOnly'] }
|
|
19
|
+
* )
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
interface TabSyncZustandOptions<T = Record<string, unknown>> {
|
|
24
|
+
/** Channel name for cross-tab communication. @default 'tab-sync-zustand' */
|
|
25
|
+
channel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Only sync these keys. Functions are always excluded.
|
|
28
|
+
* Mutually exclusive with `exclude`.
|
|
29
|
+
*/
|
|
30
|
+
include?: readonly (keyof T & string)[];
|
|
31
|
+
/**
|
|
32
|
+
* Exclude these keys from syncing. Functions are always excluded.
|
|
33
|
+
* Mutually exclusive with `include`.
|
|
34
|
+
*/
|
|
35
|
+
exclude?: readonly (keyof T & string)[];
|
|
36
|
+
/**
|
|
37
|
+
* Custom conflict resolution.
|
|
38
|
+
* Called when the same key is updated on two tabs simultaneously.
|
|
39
|
+
* @default Last-write-wins (LWW)
|
|
40
|
+
*/
|
|
41
|
+
merge?: (localValue: unknown, remoteValue: unknown, key: string) => unknown;
|
|
42
|
+
/** Force a specific transport layer. @default auto-detect */
|
|
43
|
+
transport?: 'broadcast-channel' | 'local-storage';
|
|
44
|
+
/** Enable debug logging. @default false */
|
|
45
|
+
debug?: boolean;
|
|
46
|
+
/** Error callback for non-fatal errors (channel failures, etc.). */
|
|
47
|
+
onError?: (error: Error) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Callback invoked when the underlying `TabSyncInstance` is ready.
|
|
50
|
+
* Use this to access advanced features (RPC, leader election, etc.)
|
|
51
|
+
* or to store a reference for manual cleanup via `instance.destroy()`.
|
|
52
|
+
*/
|
|
53
|
+
onSyncReady?: (instance: TabSyncInstance<Record<string, unknown>>) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type TabSyncMiddleware = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, Mps, Mcs>, options?: TabSyncZustandOptions<T>) => StateCreator<T, Mps, Mcs>;
|
|
57
|
+
/**
|
|
58
|
+
* Zustand middleware that synchronizes store state across browser tabs
|
|
59
|
+
* via tab-bridge's BroadcastChannel/localStorage transport.
|
|
60
|
+
*
|
|
61
|
+
* Functions (actions) are automatically excluded from synchronization.
|
|
62
|
+
* Use `include` or `exclude` options to further filter which keys are synced.
|
|
63
|
+
*
|
|
64
|
+
* @param initializer - Zustand StateCreator function.
|
|
65
|
+
* @param options - Synchronization options.
|
|
66
|
+
* @returns A wrapped StateCreator that synchronizes state across tabs.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import { create } from 'zustand';
|
|
71
|
+
* import { tabSync } from 'tab-bridge/zustand';
|
|
72
|
+
*
|
|
73
|
+
* const useStore = create(
|
|
74
|
+
* tabSync(
|
|
75
|
+
* (set) => ({
|
|
76
|
+
* count: 0,
|
|
77
|
+
* inc: () => set((s) => ({ count: s.count + 1 })),
|
|
78
|
+
* }),
|
|
79
|
+
* { channel: 'my-app' }
|
|
80
|
+
* )
|
|
81
|
+
* );
|
|
82
|
+
* // All tabs sharing channel 'my-app' will have synchronized state.
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
declare const tabSync: TabSyncMiddleware;
|
|
86
|
+
|
|
87
|
+
export { type TabSyncZustandOptions, tabSync };
|