react-multi-tab 1.0.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 +190 -0
- package/dist/adapters/react-router.cjs +41 -0
- package/dist/adapters/react-router.cjs.map +1 -0
- package/dist/adapters/react-router.d.cts +33 -0
- package/dist/adapters/react-router.d.ts +33 -0
- package/dist/adapters/react-router.mjs +39 -0
- package/dist/adapters/react-router.mjs.map +1 -0
- package/dist/index.cjs +632 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +276 -0
- package/dist/index.d.ts +276 -0
- package/dist/index.mjs +618 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-Df_5I7eH.d.cts +103 -0
- package/dist/types-Df_5I7eH.d.ts +103 -0
- package/package.json +117 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/components/MultiTabProvider.tsx
|
|
7
|
+
var MultiTabContext = react.createContext(null);
|
|
8
|
+
|
|
9
|
+
// src/store.ts
|
|
10
|
+
function reducer(state, action) {
|
|
11
|
+
switch (action.type) {
|
|
12
|
+
case "OPEN_TAB": {
|
|
13
|
+
const activate = action.activate;
|
|
14
|
+
const newActiveId = activate ? action.tab.instanceId : state.activeTabId;
|
|
15
|
+
const newHistory = activate ? [
|
|
16
|
+
...state.activeTabHistory.filter(
|
|
17
|
+
(id) => id !== action.tab.instanceId
|
|
18
|
+
),
|
|
19
|
+
action.tab.instanceId
|
|
20
|
+
] : state.activeTabHistory;
|
|
21
|
+
return {
|
|
22
|
+
...state,
|
|
23
|
+
tabs: [...state.tabs, action.tab],
|
|
24
|
+
activeTabId: newActiveId,
|
|
25
|
+
activeTabHistory: newHistory
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
case "CLOSE_TAB": {
|
|
29
|
+
const newTabs = state.tabs.filter(
|
|
30
|
+
(t) => t.instanceId !== action.instanceId
|
|
31
|
+
);
|
|
32
|
+
const newHistory = state.activeTabHistory.filter(
|
|
33
|
+
(id) => id !== action.instanceId
|
|
34
|
+
);
|
|
35
|
+
let newActive = state.activeTabId;
|
|
36
|
+
if (state.activeTabId === action.instanceId) {
|
|
37
|
+
const previousValidActive = newHistory.length > 0 ? newHistory[newHistory.length - 1] : null;
|
|
38
|
+
newActive = previousValidActive || (newTabs.length > 0 ? newTabs[newTabs.length - 1].instanceId : null);
|
|
39
|
+
}
|
|
40
|
+
const { [action.instanceId]: _removed, ...restData } = state.tabData;
|
|
41
|
+
return {
|
|
42
|
+
tabs: newTabs,
|
|
43
|
+
activeTabId: newActive,
|
|
44
|
+
tabData: restData,
|
|
45
|
+
activeTabHistory: newHistory
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
case "ACTIVATE_TAB": {
|
|
49
|
+
if (!state.tabs.some((t) => t.instanceId === action.instanceId))
|
|
50
|
+
return state;
|
|
51
|
+
const newHistory = [
|
|
52
|
+
...state.activeTabHistory.filter((id) => id !== action.instanceId),
|
|
53
|
+
action.instanceId
|
|
54
|
+
];
|
|
55
|
+
return {
|
|
56
|
+
...state,
|
|
57
|
+
activeTabId: action.instanceId,
|
|
58
|
+
activeTabHistory: newHistory
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
case "CLOSE_ALL":
|
|
62
|
+
return { tabs: [], activeTabId: null, tabData: {}, activeTabHistory: [] };
|
|
63
|
+
case "CLOSE_OTHERS": {
|
|
64
|
+
const kept = state.tabs.filter((t) => t.instanceId === action.instanceId);
|
|
65
|
+
const keptIds = new Set(kept.map((t) => t.instanceId));
|
|
66
|
+
const keptData = {};
|
|
67
|
+
for (const id of keptIds) {
|
|
68
|
+
if (state.tabData[id]) keptData[id] = state.tabData[id];
|
|
69
|
+
}
|
|
70
|
+
const newHistory = state.activeTabHistory.filter(
|
|
71
|
+
(id) => id === action.instanceId
|
|
72
|
+
);
|
|
73
|
+
return {
|
|
74
|
+
tabs: kept,
|
|
75
|
+
activeTabId: action.instanceId,
|
|
76
|
+
tabData: keptData,
|
|
77
|
+
activeTabHistory: newHistory
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
case "SET_DATA":
|
|
81
|
+
return {
|
|
82
|
+
...state,
|
|
83
|
+
tabData: {
|
|
84
|
+
...state.tabData,
|
|
85
|
+
[action.instanceId]: {
|
|
86
|
+
...state.tabData[action.instanceId] ?? {},
|
|
87
|
+
...action.data
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
case "REMOVE_DATA": {
|
|
92
|
+
const { [action.instanceId]: _removed, ...rest } = state.tabData;
|
|
93
|
+
return { ...state, tabData: rest };
|
|
94
|
+
}
|
|
95
|
+
case "RESTORE": {
|
|
96
|
+
const restoredActive = action.state.activeTabId;
|
|
97
|
+
const newHistory = restoredActive ? [
|
|
98
|
+
...state.activeTabHistory.filter((id) => id !== restoredActive),
|
|
99
|
+
restoredActive
|
|
100
|
+
] : state.activeTabHistory;
|
|
101
|
+
return { ...state, ...action.state, activeTabHistory: newHistory };
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
return state;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function createMultiTabStore(initialState) {
|
|
108
|
+
let state = initialState;
|
|
109
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
110
|
+
const getState = () => state;
|
|
111
|
+
const dispatch = (action) => {
|
|
112
|
+
state = reducer(state, action);
|
|
113
|
+
listeners.forEach((listener) => listener());
|
|
114
|
+
};
|
|
115
|
+
const subscribe = (listener) => {
|
|
116
|
+
listeners.add(listener);
|
|
117
|
+
return () => {
|
|
118
|
+
listeners.delete(listener);
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
getState,
|
|
123
|
+
dispatch,
|
|
124
|
+
subscribe
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
var counter = 0;
|
|
128
|
+
function generateInstanceId(pageId) {
|
|
129
|
+
counter += 1;
|
|
130
|
+
return `${pageId}-${counter}`;
|
|
131
|
+
}
|
|
132
|
+
function MultiTabProvider({
|
|
133
|
+
registry,
|
|
134
|
+
adapter,
|
|
135
|
+
defaultActiveTab,
|
|
136
|
+
children,
|
|
137
|
+
onTabOpen,
|
|
138
|
+
onTabClose,
|
|
139
|
+
onTabChange
|
|
140
|
+
}) {
|
|
141
|
+
const store = react.useRef(
|
|
142
|
+
createMultiTabStore({
|
|
143
|
+
tabs: [],
|
|
144
|
+
activeTabId: defaultActiveTab ?? null,
|
|
145
|
+
tabData: {},
|
|
146
|
+
activeTabHistory: defaultActiveTab ? [defaultActiveTab] : []
|
|
147
|
+
})
|
|
148
|
+
).current;
|
|
149
|
+
const cbRef = react.useRef({ onTabOpen, onTabClose, onTabChange });
|
|
150
|
+
cbRef.current = { onTabOpen, onTabClose, onTabChange };
|
|
151
|
+
const prevActiveRef = react.useRef(defaultActiveTab ?? null);
|
|
152
|
+
react.useEffect(
|
|
153
|
+
() => store.subscribe(() => {
|
|
154
|
+
const state = store.getState();
|
|
155
|
+
if (state.activeTabId !== null && state.activeTabId !== prevActiveRef.current) {
|
|
156
|
+
cbRef.current.onTabChange?.(state.activeTabId);
|
|
157
|
+
}
|
|
158
|
+
prevActiveRef.current = state.activeTabId;
|
|
159
|
+
}),
|
|
160
|
+
[store]
|
|
161
|
+
);
|
|
162
|
+
const initialised = react.useRef(false);
|
|
163
|
+
react.useEffect(() => {
|
|
164
|
+
if (initialised.current || !adapter) return;
|
|
165
|
+
initialised.current = true;
|
|
166
|
+
const saved = adapter.read();
|
|
167
|
+
if (!saved || saved.tabs.length === 0) return;
|
|
168
|
+
const restoredTabs = saved.tabs.map((instanceId) => {
|
|
169
|
+
const parts = instanceId.split("-");
|
|
170
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
171
|
+
const candidateId = parts.slice(0, i).join("-");
|
|
172
|
+
const page = registry.getPage(candidateId);
|
|
173
|
+
if (page) {
|
|
174
|
+
return {
|
|
175
|
+
instanceId,
|
|
176
|
+
pageId: page.id,
|
|
177
|
+
label: page.label,
|
|
178
|
+
closable: page.closable !== false
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}).filter((t) => t !== null);
|
|
184
|
+
if (restoredTabs.length > 0) {
|
|
185
|
+
const activeTab = saved.activeTab && restoredTabs.some((t) => t.instanceId === saved.activeTab) ? saved.activeTab : restoredTabs[restoredTabs.length - 1].instanceId;
|
|
186
|
+
store.dispatch({
|
|
187
|
+
type: "RESTORE",
|
|
188
|
+
state: { tabs: restoredTabs, activeTabId: activeTab }
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}, [adapter, registry, store]);
|
|
192
|
+
react.useEffect(() => {
|
|
193
|
+
if (!adapter) return;
|
|
194
|
+
return store.subscribe(() => {
|
|
195
|
+
const { tabs, activeTabId } = store.getState();
|
|
196
|
+
adapter.write(tabs, activeTabId);
|
|
197
|
+
});
|
|
198
|
+
}, [adapter, store]);
|
|
199
|
+
react.useEffect(() => {
|
|
200
|
+
if (!adapter?.subscribe) return;
|
|
201
|
+
const unsubscribe = adapter.subscribe(() => {
|
|
202
|
+
const saved = adapter.read();
|
|
203
|
+
const currentState = store.getState();
|
|
204
|
+
if (saved?.activeTab && saved.activeTab !== currentState.activeTabId) {
|
|
205
|
+
store.dispatch({ type: "ACTIVATE_TAB", instanceId: saved.activeTab });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
return unsubscribe;
|
|
209
|
+
}, [adapter, store]);
|
|
210
|
+
react.useEffect(() => () => adapter?.destroy?.(), [adapter]);
|
|
211
|
+
const openTab = react.useCallback(
|
|
212
|
+
(pageId, options) => {
|
|
213
|
+
const page = registry.getPage(pageId);
|
|
214
|
+
if (!page) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`react-multi-tab: Page "${pageId}" not found in registry.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
const instanceId = options?.instanceId ?? generateInstanceId(pageId);
|
|
220
|
+
const tab = {
|
|
221
|
+
instanceId,
|
|
222
|
+
pageId,
|
|
223
|
+
label: options?.label ?? page.label,
|
|
224
|
+
closable: page.closable !== false
|
|
225
|
+
};
|
|
226
|
+
store.dispatch({
|
|
227
|
+
type: "OPEN_TAB",
|
|
228
|
+
tab,
|
|
229
|
+
activate: options?.activate !== false
|
|
230
|
+
});
|
|
231
|
+
cbRef.current.onTabOpen?.(tab);
|
|
232
|
+
return instanceId;
|
|
233
|
+
},
|
|
234
|
+
[registry, store]
|
|
235
|
+
);
|
|
236
|
+
const closeTab = react.useCallback(
|
|
237
|
+
(instanceId) => {
|
|
238
|
+
store.dispatch({ type: "CLOSE_TAB", instanceId });
|
|
239
|
+
cbRef.current.onTabClose?.(instanceId);
|
|
240
|
+
},
|
|
241
|
+
[store]
|
|
242
|
+
);
|
|
243
|
+
const activateTab = react.useCallback(
|
|
244
|
+
(instanceId) => {
|
|
245
|
+
store.dispatch({ type: "ACTIVATE_TAB", instanceId });
|
|
246
|
+
},
|
|
247
|
+
[store]
|
|
248
|
+
);
|
|
249
|
+
const closeAllTabs = react.useCallback(() => {
|
|
250
|
+
store.dispatch({ type: "CLOSE_ALL" });
|
|
251
|
+
}, [store]);
|
|
252
|
+
const closeOtherTabs = react.useCallback(
|
|
253
|
+
(instanceId) => {
|
|
254
|
+
store.dispatch({ type: "CLOSE_OTHERS", instanceId });
|
|
255
|
+
},
|
|
256
|
+
[store]
|
|
257
|
+
);
|
|
258
|
+
const setTabData = react.useCallback(
|
|
259
|
+
(instanceId, data) => {
|
|
260
|
+
store.dispatch({ type: "SET_DATA", instanceId, data });
|
|
261
|
+
},
|
|
262
|
+
[store]
|
|
263
|
+
);
|
|
264
|
+
const getTabData = react.useCallback(
|
|
265
|
+
(instanceId) => store.getState().tabData[instanceId] ?? {},
|
|
266
|
+
[store]
|
|
267
|
+
);
|
|
268
|
+
const removeTabData = react.useCallback(
|
|
269
|
+
(instanceId) => {
|
|
270
|
+
store.dispatch({ type: "REMOVE_DATA", instanceId });
|
|
271
|
+
},
|
|
272
|
+
[store]
|
|
273
|
+
);
|
|
274
|
+
const contextValue = react.useMemo(
|
|
275
|
+
() => ({
|
|
276
|
+
store,
|
|
277
|
+
registry,
|
|
278
|
+
openTab,
|
|
279
|
+
closeTab,
|
|
280
|
+
activateTab,
|
|
281
|
+
closeAllTabs,
|
|
282
|
+
closeOtherTabs,
|
|
283
|
+
setTabData,
|
|
284
|
+
getTabData,
|
|
285
|
+
removeTabData
|
|
286
|
+
}),
|
|
287
|
+
[
|
|
288
|
+
store,
|
|
289
|
+
registry,
|
|
290
|
+
openTab,
|
|
291
|
+
closeTab,
|
|
292
|
+
activateTab,
|
|
293
|
+
closeAllTabs,
|
|
294
|
+
closeOtherTabs,
|
|
295
|
+
setTabData,
|
|
296
|
+
getTabData,
|
|
297
|
+
removeTabData
|
|
298
|
+
]
|
|
299
|
+
);
|
|
300
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MultiTabContext.Provider, { value: contextValue, children });
|
|
301
|
+
}
|
|
302
|
+
function TabList({
|
|
303
|
+
children,
|
|
304
|
+
className,
|
|
305
|
+
style,
|
|
306
|
+
...ariaProps
|
|
307
|
+
}) {
|
|
308
|
+
const listRef = react.useRef(null);
|
|
309
|
+
const handleKeyDown = react.useCallback(
|
|
310
|
+
(e) => {
|
|
311
|
+
const tabElements = listRef.current?.querySelectorAll('[role="tab"]');
|
|
312
|
+
if (!tabElements || tabElements.length === 0) return;
|
|
313
|
+
const tabs = Array.from(tabElements);
|
|
314
|
+
const currentIndex = tabs.findIndex(
|
|
315
|
+
(el) => el === document.activeElement
|
|
316
|
+
);
|
|
317
|
+
let nextIndex = null;
|
|
318
|
+
switch (e.key) {
|
|
319
|
+
case "ArrowRight":
|
|
320
|
+
case "ArrowDown":
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
nextIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
|
|
323
|
+
break;
|
|
324
|
+
case "ArrowLeft":
|
|
325
|
+
case "ArrowUp":
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
|
|
328
|
+
break;
|
|
329
|
+
case "Home":
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
nextIndex = 0;
|
|
332
|
+
break;
|
|
333
|
+
case "End":
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
nextIndex = tabs.length - 1;
|
|
336
|
+
break;
|
|
337
|
+
default:
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (nextIndex !== null) {
|
|
341
|
+
tabs[nextIndex].focus();
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
[]
|
|
345
|
+
);
|
|
346
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
ref: listRef,
|
|
350
|
+
role: "tablist",
|
|
351
|
+
className,
|
|
352
|
+
style,
|
|
353
|
+
onKeyDown: handleKeyDown,
|
|
354
|
+
...ariaProps,
|
|
355
|
+
children
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
function useInternalContext() {
|
|
360
|
+
const ctx = react.useContext(MultiTabContext);
|
|
361
|
+
if (!ctx) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
"react-multi-tab: Hooks must be used within a <MultiTabProvider>."
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return ctx;
|
|
367
|
+
}
|
|
368
|
+
function TabTrigger({
|
|
369
|
+
instanceId,
|
|
370
|
+
children,
|
|
371
|
+
className,
|
|
372
|
+
style,
|
|
373
|
+
disabled = false
|
|
374
|
+
}) {
|
|
375
|
+
const { store, activateTab } = useInternalContext();
|
|
376
|
+
const state = react.useSyncExternalStore(store.subscribe, store.getState);
|
|
377
|
+
const isActive = state.activeTabId === instanceId;
|
|
378
|
+
const handleClick = react.useCallback(() => {
|
|
379
|
+
if (!disabled) activateTab(instanceId);
|
|
380
|
+
}, [instanceId, disabled, activateTab]);
|
|
381
|
+
const handleKeyDown = react.useCallback(
|
|
382
|
+
(e) => {
|
|
383
|
+
if ((e.key === "Enter" || e.key === " ") && !disabled) {
|
|
384
|
+
e.preventDefault();
|
|
385
|
+
activateTab(instanceId);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
[instanceId, disabled, activateTab]
|
|
389
|
+
);
|
|
390
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
391
|
+
"div",
|
|
392
|
+
{
|
|
393
|
+
role: "tab",
|
|
394
|
+
id: `rmt-tab-${instanceId}`,
|
|
395
|
+
"aria-selected": isActive,
|
|
396
|
+
"aria-controls": `rmt-tabpanel-${instanceId}`,
|
|
397
|
+
tabIndex: isActive ? 0 : -1,
|
|
398
|
+
className,
|
|
399
|
+
style,
|
|
400
|
+
"aria-disabled": disabled,
|
|
401
|
+
onClick: handleClick,
|
|
402
|
+
onKeyDown: handleKeyDown,
|
|
403
|
+
"data-state": isActive ? "active" : "inactive",
|
|
404
|
+
children
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
function TabPanel({
|
|
409
|
+
instanceId,
|
|
410
|
+
children,
|
|
411
|
+
hidden = false,
|
|
412
|
+
className,
|
|
413
|
+
style
|
|
414
|
+
}) {
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
416
|
+
"div",
|
|
417
|
+
{
|
|
418
|
+
role: "tabpanel",
|
|
419
|
+
id: `rmt-tabpanel-${instanceId}`,
|
|
420
|
+
"aria-labelledby": `rmt-tab-${instanceId}`,
|
|
421
|
+
tabIndex: 0,
|
|
422
|
+
hidden,
|
|
423
|
+
className,
|
|
424
|
+
style,
|
|
425
|
+
children
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
var TabInstanceContext = react.createContext(null);
|
|
430
|
+
function useTabInstanceId() {
|
|
431
|
+
const instanceId = react.useContext(TabInstanceContext);
|
|
432
|
+
if (!instanceId) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
"react-multi-tab: useTabInstanceId must be used within a component rendered inside a Tab."
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
return instanceId;
|
|
438
|
+
}
|
|
439
|
+
function TabContent({ instanceId, children }) {
|
|
440
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TabInstanceContext.Provider, { value: instanceId, children });
|
|
441
|
+
}
|
|
442
|
+
function TabPanels({ className, style }) {
|
|
443
|
+
const { store, registry, setTabData } = useInternalContext();
|
|
444
|
+
const state = react.useSyncExternalStore(store.subscribe, store.getState);
|
|
445
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className, style, children: state.tabs.map((tab) => {
|
|
446
|
+
const page = registry.getPage(tab.pageId);
|
|
447
|
+
if (!page) return null;
|
|
448
|
+
const isActive = state.activeTabId === tab.instanceId;
|
|
449
|
+
const data = state.tabData[tab.instanceId] ?? {};
|
|
450
|
+
const Component = page.component;
|
|
451
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
452
|
+
TabPanel,
|
|
453
|
+
{
|
|
454
|
+
instanceId: tab.instanceId,
|
|
455
|
+
hidden: !isActive,
|
|
456
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(TabContent, { instanceId: tab.instanceId, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
457
|
+
Component,
|
|
458
|
+
{
|
|
459
|
+
instanceId: tab.instanceId,
|
|
460
|
+
data,
|
|
461
|
+
setData: (update) => setTabData(tab.instanceId, update)
|
|
462
|
+
}
|
|
463
|
+
) })
|
|
464
|
+
},
|
|
465
|
+
tab.instanceId
|
|
466
|
+
);
|
|
467
|
+
}) });
|
|
468
|
+
}
|
|
469
|
+
function TabCloseButton({
|
|
470
|
+
instanceId,
|
|
471
|
+
children,
|
|
472
|
+
className,
|
|
473
|
+
style,
|
|
474
|
+
"aria-label": ariaLabel
|
|
475
|
+
}) {
|
|
476
|
+
const { store, closeTab } = useInternalContext();
|
|
477
|
+
const state = react.useSyncExternalStore(store.subscribe, store.getState);
|
|
478
|
+
const tab = state.tabs.find((t) => t.instanceId === instanceId);
|
|
479
|
+
const handleClick = react.useCallback(
|
|
480
|
+
(e) => {
|
|
481
|
+
e.stopPropagation();
|
|
482
|
+
closeTab(instanceId);
|
|
483
|
+
},
|
|
484
|
+
[instanceId, closeTab]
|
|
485
|
+
);
|
|
486
|
+
if (!tab || !tab.closable) return null;
|
|
487
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
488
|
+
"button",
|
|
489
|
+
{
|
|
490
|
+
type: "button",
|
|
491
|
+
"aria-label": ariaLabel ?? `Close ${tab.label}`,
|
|
492
|
+
className,
|
|
493
|
+
style,
|
|
494
|
+
onClick: handleClick,
|
|
495
|
+
tabIndex: -1,
|
|
496
|
+
children: children ?? "\xD7"
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
function useMultiTab() {
|
|
501
|
+
const {
|
|
502
|
+
store,
|
|
503
|
+
openTab,
|
|
504
|
+
closeTab,
|
|
505
|
+
activateTab,
|
|
506
|
+
closeAllTabs,
|
|
507
|
+
closeOtherTabs
|
|
508
|
+
} = useInternalContext();
|
|
509
|
+
const state = react.useSyncExternalStore(store.subscribe, store.getState);
|
|
510
|
+
return {
|
|
511
|
+
tabs: state.tabs,
|
|
512
|
+
activeTabId: state.activeTabId,
|
|
513
|
+
openTab,
|
|
514
|
+
closeTab,
|
|
515
|
+
activateTab,
|
|
516
|
+
closeAllTabs,
|
|
517
|
+
closeOtherTabs
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
var EMPTY_DATA = Object.freeze({});
|
|
521
|
+
function useTabData(instanceId) {
|
|
522
|
+
const { store, setTabData } = useInternalContext();
|
|
523
|
+
const contextId = react.useContext(TabInstanceContext);
|
|
524
|
+
const fallbackId = store.getState().activeTabId;
|
|
525
|
+
const resolvedId = instanceId ?? contextId ?? fallbackId;
|
|
526
|
+
const getSnapshot = react.useCallback(() => {
|
|
527
|
+
if (!resolvedId) return EMPTY_DATA;
|
|
528
|
+
const tabData = store.getState().tabData[resolvedId];
|
|
529
|
+
return tabData ?? EMPTY_DATA;
|
|
530
|
+
}, [resolvedId, store]);
|
|
531
|
+
const data = react.useSyncExternalStore(store.subscribe, getSnapshot);
|
|
532
|
+
const setData = react.useCallback(
|
|
533
|
+
(update) => {
|
|
534
|
+
if (resolvedId) {
|
|
535
|
+
setTabData(resolvedId, update);
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
[resolvedId, setTabData]
|
|
539
|
+
);
|
|
540
|
+
return [data, setData];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/registry.ts
|
|
544
|
+
function createPageRegistry(pages) {
|
|
545
|
+
const ids = /* @__PURE__ */ new Set();
|
|
546
|
+
for (const page of pages) {
|
|
547
|
+
if (!page.id) {
|
|
548
|
+
throw new Error("react-multi-tab: Page definition must have an id.");
|
|
549
|
+
}
|
|
550
|
+
if (!page.label) {
|
|
551
|
+
throw new Error(`react-multi-tab: Page "${page.id}" must have a label.`);
|
|
552
|
+
}
|
|
553
|
+
if (!page.component) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`react-multi-tab: Page "${page.id}" must have a component.`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
if (ids.has(page.id)) {
|
|
559
|
+
throw new Error(`react-multi-tab: Duplicate page id: "${page.id}".`);
|
|
560
|
+
}
|
|
561
|
+
ids.add(page.id);
|
|
562
|
+
}
|
|
563
|
+
const frozen = [...pages];
|
|
564
|
+
return {
|
|
565
|
+
pages: frozen,
|
|
566
|
+
getPage: (id) => frozen.find((p) => p.id === id)
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/adapters/memory.ts
|
|
571
|
+
function memoryAdapter() {
|
|
572
|
+
return {
|
|
573
|
+
read: () => null,
|
|
574
|
+
write: () => {
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/adapters/search-params.ts
|
|
580
|
+
function searchParamsAdapter(options) {
|
|
581
|
+
const tabsKey = options?.tabsParam ?? "tabs";
|
|
582
|
+
const activeKey = options?.activeParam ?? "active";
|
|
583
|
+
return {
|
|
584
|
+
read() {
|
|
585
|
+
if (typeof window === "undefined") return null;
|
|
586
|
+
const params = new URLSearchParams(window.location.search);
|
|
587
|
+
const tabsStr = params.get(tabsKey);
|
|
588
|
+
const activeTab = params.get(activeKey);
|
|
589
|
+
if (!tabsStr) return null;
|
|
590
|
+
const tabs = tabsStr.split(",").filter(Boolean);
|
|
591
|
+
return { tabs, activeTab };
|
|
592
|
+
},
|
|
593
|
+
write(tabs, activeTabId) {
|
|
594
|
+
if (typeof window === "undefined") return;
|
|
595
|
+
const params = new URLSearchParams(window.location.search);
|
|
596
|
+
if (tabs.length > 0) {
|
|
597
|
+
params.set(tabsKey, tabs.map((t) => t.instanceId).join(","));
|
|
598
|
+
if (activeTabId) {
|
|
599
|
+
params.set(activeKey, activeTabId);
|
|
600
|
+
} else {
|
|
601
|
+
params.delete(activeKey);
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
params.delete(tabsKey);
|
|
605
|
+
params.delete(activeKey);
|
|
606
|
+
}
|
|
607
|
+
const qs = params.toString().replace(/%2C/g, ",");
|
|
608
|
+
const newUrl = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
|
|
609
|
+
window.history.replaceState(null, "", newUrl);
|
|
610
|
+
},
|
|
611
|
+
subscribe(callback) {
|
|
612
|
+
window.addEventListener("popstate", callback);
|
|
613
|
+
return () => window.removeEventListener("popstate", callback);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
exports.MultiTabProvider = MultiTabProvider;
|
|
619
|
+
exports.TabCloseButton = TabCloseButton;
|
|
620
|
+
exports.TabContent = TabContent;
|
|
621
|
+
exports.TabList = TabList;
|
|
622
|
+
exports.TabPanel = TabPanel;
|
|
623
|
+
exports.TabPanels = TabPanels;
|
|
624
|
+
exports.TabTrigger = TabTrigger;
|
|
625
|
+
exports.createPageRegistry = createPageRegistry;
|
|
626
|
+
exports.memoryAdapter = memoryAdapter;
|
|
627
|
+
exports.searchParamsAdapter = searchParamsAdapter;
|
|
628
|
+
exports.useMultiTab = useMultiTab;
|
|
629
|
+
exports.useTabData = useTabData;
|
|
630
|
+
exports.useTabInstanceId = useTabInstanceId;
|
|
631
|
+
//# sourceMappingURL=index.cjs.map
|
|
632
|
+
//# sourceMappingURL=index.cjs.map
|