@gtkx/navigation 2.0.0-beta.11 → 2.0.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +3 -3
  2. package/dist/drawer/create-drawer-navigator.d.ts +3 -1
  3. package/dist/drawer/create-drawer-navigator.d.ts.map +1 -1
  4. package/dist/drawer/create-drawer-navigator.js +10 -10
  5. package/dist/drawer/create-drawer-navigator.js.map +1 -1
  6. package/dist/drawer/drawer-item-list.d.ts.map +1 -1
  7. package/dist/drawer/drawer-item-list.js +3 -2
  8. package/dist/drawer/drawer-item-list.js.map +1 -1
  9. package/dist/drawer/drawer-router.d.ts.map +1 -1
  10. package/dist/drawer/drawer-router.js.map +1 -1
  11. package/dist/drawer/drawer-status.d.ts.map +1 -1
  12. package/dist/drawer/drawer-view.d.ts.map +1 -1
  13. package/dist/drawer/drawer-view.js +6 -6
  14. package/dist/drawer/drawer-view.js.map +1 -1
  15. package/dist/drawer/types.d.ts +2 -0
  16. package/dist/drawer/types.d.ts.map +1 -1
  17. package/dist/drawer/types.js.map +1 -1
  18. package/dist/index.d.ts +6 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +5 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/shared/header-bar.d.ts.map +1 -1
  23. package/dist/shared/header-bar.js +2 -2
  24. package/dist/shared/header-bar.js.map +1 -1
  25. package/dist/shared/routes.d.ts.map +1 -1
  26. package/dist/shared/scene-page.d.ts +3 -1
  27. package/dist/shared/scene-page.d.ts.map +1 -1
  28. package/dist/shared/scene-page.js +4 -7
  29. package/dist/shared/scene-page.js.map +1 -1
  30. package/dist/shared/use-has-loaded.d.ts +3 -0
  31. package/dist/shared/use-has-loaded.d.ts.map +1 -0
  32. package/dist/shared/use-has-loaded.js +10 -0
  33. package/dist/shared/use-has-loaded.js.map +1 -0
  34. package/dist/shared/use-pop-to-top-on-blur.d.ts.map +1 -1
  35. package/dist/split-view/create-split-view-navigator.d.ts +7 -2
  36. package/dist/split-view/create-split-view-navigator.d.ts.map +1 -1
  37. package/dist/split-view/create-split-view-navigator.js +2 -6
  38. package/dist/split-view/create-split-view-navigator.js.map +1 -1
  39. package/dist/split-view/split-view-collapsed.d.ts +11 -0
  40. package/dist/split-view/split-view-collapsed.d.ts.map +1 -0
  41. package/dist/split-view/split-view-collapsed.js +17 -0
  42. package/dist/split-view/split-view-collapsed.js.map +1 -0
  43. package/dist/split-view/split-view.d.ts.map +1 -1
  44. package/dist/split-view/split-view.js +41 -20
  45. package/dist/split-view/split-view.js.map +1 -1
  46. package/dist/split-view/types.d.ts +9 -2
  47. package/dist/split-view/types.d.ts.map +1 -1
  48. package/dist/split-view/types.js.map +1 -1
  49. package/dist/stack/create-stack-navigator.d.ts +3 -1
  50. package/dist/stack/create-stack-navigator.d.ts.map +1 -1
  51. package/dist/stack/create-stack-navigator.js +0 -1
  52. package/dist/stack/create-stack-navigator.js.map +1 -1
  53. package/dist/stack/stack-pages.d.ts.map +1 -1
  54. package/dist/stack/stack-pages.js +3 -5
  55. package/dist/stack/stack-pages.js.map +1 -1
  56. package/dist/stack/stack-view.d.ts.map +1 -1
  57. package/dist/stack/stack-view.js +6 -14
  58. package/dist/stack/stack-view.js.map +1 -1
  59. package/dist/stack/sync-navigation-stack.d.ts.map +1 -1
  60. package/dist/stack/types.d.ts.map +1 -1
  61. package/dist/stack/types.js.map +1 -1
  62. package/dist/tab-view/create-tab-view-navigator.d.ts +32 -0
  63. package/dist/tab-view/create-tab-view-navigator.d.ts.map +1 -0
  64. package/dist/tab-view/create-tab-view-navigator.js +21 -0
  65. package/dist/tab-view/create-tab-view-navigator.js.map +1 -0
  66. package/dist/tab-view/page-registry.d.ts +34 -0
  67. package/dist/tab-view/page-registry.d.ts.map +1 -0
  68. package/dist/tab-view/page-registry.js +70 -0
  69. package/dist/tab-view/page-registry.js.map +1 -0
  70. package/dist/tab-view/tab-view-host.d.ts +25 -0
  71. package/dist/tab-view/tab-view-host.d.ts.map +1 -0
  72. package/dist/tab-view/tab-view-host.js +284 -0
  73. package/dist/tab-view/tab-view-host.js.map +1 -0
  74. package/dist/tab-view/tab-view-router.d.ts +34 -0
  75. package/dist/tab-view/tab-view-router.d.ts.map +1 -0
  76. package/dist/tab-view/tab-view-router.js +359 -0
  77. package/dist/tab-view/tab-view-router.js.map +1 -0
  78. package/dist/tab-view/types.d.ts +240 -0
  79. package/dist/tab-view/types.d.ts.map +1 -0
  80. package/dist/tab-view/types.js +2 -0
  81. package/dist/tab-view/types.js.map +1 -0
  82. package/dist/tabs/create-tab-navigator.d.ts +3 -1
  83. package/dist/tabs/create-tab-navigator.d.ts.map +1 -1
  84. package/dist/tabs/create-tab-navigator.js +0 -1
  85. package/dist/tabs/create-tab-navigator.js.map +1 -1
  86. package/dist/tabs/tab-view.d.ts.map +1 -1
  87. package/dist/tabs/tab-view.js +5 -7
  88. package/dist/tabs/tab-view.js.map +1 -1
  89. package/dist/tabs/types.d.ts +2 -0
  90. package/dist/tabs/types.d.ts.map +1 -1
  91. package/dist/tabs/types.js.map +1 -1
  92. package/dist/theme.d.ts.map +1 -1
  93. package/dist/theme.js +2 -2
  94. package/dist/theme.js.map +1 -1
  95. package/package.json +9 -19
  96. package/src/drawer/create-drawer-navigator.tsx +23 -11
  97. package/src/drawer/drawer-item-list.tsx +16 -12
  98. package/src/drawer/drawer-router.ts +4 -2
  99. package/src/drawer/drawer-view.tsx +18 -18
  100. package/src/drawer/types.ts +6 -3
  101. package/src/index.ts +31 -5
  102. package/src/shared/header-bar.tsx +2 -2
  103. package/src/shared/scene-page.tsx +7 -8
  104. package/src/shared/use-has-loaded.ts +13 -0
  105. package/src/split-view/create-split-view-navigator.tsx +16 -1
  106. package/src/split-view/split-view-collapsed.ts +21 -0
  107. package/src/split-view/split-view.tsx +102 -49
  108. package/src/split-view/types.ts +13 -5
  109. package/src/stack/create-stack-navigator.tsx +11 -1
  110. package/src/stack/stack-pages.ts +14 -12
  111. package/src/stack/stack-view.tsx +10 -16
  112. package/src/stack/types.ts +4 -3
  113. package/src/tab-view/create-tab-view-navigator.tsx +122 -0
  114. package/src/tab-view/page-registry.ts +139 -0
  115. package/src/tab-view/tab-view-host.tsx +474 -0
  116. package/src/tab-view/tab-view-router.ts +528 -0
  117. package/src/tab-view/types.ts +291 -0
  118. package/src/tabs/create-tab-navigator.tsx +11 -1
  119. package/src/tabs/tab-view.tsx +28 -22
  120. package/src/tabs/types.ts +6 -3
  121. package/src/theme.ts +2 -3
@@ -0,0 +1,474 @@
1
+ import type * as GObject from "@gtkx/gi/gobject";
2
+ import type { ParamListBase, RouteProp } from "@react-navigation/core";
3
+ import type { ReactNode } from "react";
4
+ import * as Adw from "@gtkx/gi/adw";
5
+ import * as Gio from "@gtkx/gi/gio";
6
+ import { AdwBin, AdwTabBar, AdwTabOverview, AdwToolbarView } from "@gtkx/jsx/adw";
7
+ import { createPortal, rootElement, useProperty, useSignal } from "@gtkx/react";
8
+ import { createContext, useContext, useLayoutEffect, useState, useSyncExternalStore } from "react";
9
+ import type { AppliedOptions, PageRecord, PageStore } from "./page-registry.js";
10
+ import type {
11
+ TabViewDescriptor,
12
+ TabViewDescriptorMap,
13
+ TabViewIcon,
14
+ TabViewNavigationConfig,
15
+ TabViewNavigationHelpers,
16
+ TabViewNavigationOptions,
17
+ TabViewNavigationState,
18
+ } from "./types.js";
19
+ import { HeaderBar } from "../shared/header-bar.js";
20
+ import { getFocusedRoute, requireDescriptor } from "../shared/routes.js";
21
+ import { createPageStore, ownerOf, recordOf, registerPage } from "./page-registry.js";
22
+ import { createRouteKey, focusedKey, hasRoute, TabViewActions } from "./tab-view-router.js";
23
+ import { useHasLoaded } from "../shared/use-has-loaded.js";
24
+
25
+ type State = TabViewNavigationState<ParamListBase>;
26
+ type Route = State["routes"][number];
27
+
28
+ type TabViewHostProps = TabViewNavigationConfig & {
29
+ view: Adw.TabView;
30
+ state: State;
31
+ navigation: TabViewNavigationHelpers;
32
+ descriptors: TabViewDescriptorMap;
33
+ };
34
+
35
+ type TabContentProps = {
36
+ descriptor: TabViewDescriptor;
37
+ isFocused: boolean;
38
+ };
39
+
40
+ /** The native view of the surrounding tab view navigator and access to its pages. */
41
+ type TabViewHandle = {
42
+ /** The `Adw.TabView` the navigator renders into. */
43
+ view: Adw.TabView;
44
+ /** Returns the native page of a route, or `null` when it is not in this view. */
45
+ getPage: (key: string) => Adw.TabPage | null;
46
+ /** Moves a route's page into another view, appending to its pinned or unpinned group by default. */
47
+ transferTab: (key: string, target: Adw.TabView, position?: number) => void;
48
+ };
49
+
50
+ const TabViewContext = createContext<TabViewHandle | null>(null);
51
+
52
+ /** Returns the surrounding tab view navigator's native view and page access. */
53
+ const useTabView = (): TabViewHandle => {
54
+ const handle = useContext(TabViewContext);
55
+
56
+ if (handle === null) {
57
+ throw new Error("useTabView must be called inside a tab view navigator.");
58
+ }
59
+
60
+ return handle;
61
+ };
62
+
63
+ const toIcon = (icon: TabViewIcon | null | undefined): Gio.Icon | null =>
64
+ typeof icon === "string" ? Gio.ThemedIcon.new(icon) : (icon ?? null);
65
+
66
+ type PageSync = (page: Adw.TabPage, previous: AppliedOptions, next: AppliedOptions) => void;
67
+
68
+ const sync =
69
+ <K extends keyof AppliedOptions>(key: K, set: (page: Adw.TabPage, value: AppliedOptions[K]) => void): PageSync =>
70
+ (page, previous, next) => {
71
+ if (previous[key] !== next[key]) {
72
+ set(page, next[key]);
73
+ }
74
+ };
75
+
76
+ const pageSyncs: PageSync[] = [
77
+ sync("title", (page, title) => page.setTitle(title ?? "")),
78
+ sync("icon", (page, icon) => page.setIcon(toIcon(icon))),
79
+ sync("tooltip", (page, tooltip) => page.setTooltip(tooltip ?? "")),
80
+ sync("keyword", (page, keyword) => page.setKeyword(keyword ?? "")),
81
+ sync("loading", (page, loading) => page.setLoading(loading ?? false)),
82
+ sync("needsAttention", (page, value) => page.setNeedsAttention(value ?? false)),
83
+ sync("indicatorIcon", (page, icon) => page.setIndicatorIcon(toIcon(icon))),
84
+ sync("indicatorActivatable", (page, value) => page.setIndicatorActivatable(value ?? false)),
85
+ sync("indicatorTooltip", (page, value) => page.setIndicatorTooltip(value ?? "")),
86
+ sync("liveThumbnail", (page, value) => page.setLiveThumbnail(value ?? false)),
87
+ sync("thumbnailXalign", (page, value) => page.setThumbnailXalign(value ?? 0)),
88
+ sync("thumbnailYalign", (page, value) => page.setThumbnailYalign(value ?? 0)),
89
+ ];
90
+
91
+ const applyPageOptions = (record: PageRecord, options: TabViewNavigationOptions): void => {
92
+ const next: AppliedOptions = { ...options, title: options.title ?? record.route.name };
93
+
94
+ for (const apply of pageSyncs) {
95
+ apply(record.page, record.applied, next);
96
+ }
97
+
98
+ record.applied = next;
99
+ };
100
+
101
+ type PagePlacement = {
102
+ position: number;
103
+ pinned: boolean;
104
+ parent?: PageRecord;
105
+ };
106
+
107
+ const createPage = (view: Adw.TabView, store: PageStore, route: Route, placement: PagePlacement): PageRecord => {
108
+ const host = new Adw.Bin();
109
+ const pinnedCount = view.getNPinnedPages();
110
+ const page = placement.pinned
111
+ ? view.insertPinned(host, Math.min(placement.position, pinnedCount))
112
+ : placement.parent !== undefined && ownerOf(placement.parent.page) === view
113
+ ? view.addPage(host, placement.parent.page)
114
+ : view.insert(host, Math.min(Math.max(placement.position, pinnedCount), view.getNPages()));
115
+ const record: PageRecord = { route, page, host, applied: {} };
116
+
117
+ registerPage(record);
118
+ store.set(record);
119
+
120
+ return record;
121
+ };
122
+
123
+ const presentRecord = (view: Adw.TabView, store: PageStore, key: string | undefined): PageRecord | undefined => {
124
+ const record = key === undefined ? undefined : store.get(key);
125
+
126
+ return record !== undefined && ownerOf(record.page) === view ? record : undefined;
127
+ };
128
+
129
+ const adoptPages = (view: Adw.TabView, store: PageStore): void => {
130
+ for (let position = 0; position < view.getNPages(); position += 1) {
131
+ const record = recordOf(view.getNthPage(position));
132
+
133
+ if (record !== undefined && store.get(record.route.key) === undefined) {
134
+ store.set(record);
135
+ }
136
+ }
137
+ };
138
+
139
+ const syncPages = (view: Adw.TabView, store: PageStore, state: State, descriptors: TabViewDescriptorMap): void => {
140
+ adoptPages(view, store);
141
+
142
+ for (const key of store.keys()) {
143
+ if (!hasRoute(state, key)) {
144
+ const record = store.get(key);
145
+ store.delete(key);
146
+
147
+ if (record !== undefined && ownerOf(record.page) === view) {
148
+ view.closePage(record.page);
149
+ }
150
+ }
151
+ }
152
+
153
+ state.routes.forEach((route, position) => {
154
+ if (store.get(route.key) === undefined) {
155
+ const parentKey = state.parents[route.key];
156
+
157
+ createPage(view, store, route, {
158
+ position,
159
+ pinned: state.pinnedKeys.includes(route.key),
160
+ parent: parentKey === undefined ? undefined : store.get(parentKey),
161
+ });
162
+ }
163
+ });
164
+
165
+ const present = state.routes.flatMap((route) => {
166
+ const record = presentRecord(view, store, route.key);
167
+
168
+ return record === undefined ? [] : [{ route, record }];
169
+ });
170
+
171
+ for (const { route, record } of present) {
172
+ const pinned = state.pinnedKeys.includes(route.key);
173
+
174
+ if (record.page.getPinned() !== pinned) {
175
+ view.setPagePinned(record.page, pinned);
176
+ }
177
+ }
178
+
179
+ present.forEach(({ record }, position) => {
180
+ if (view.getPagePosition(record.page) !== position) {
181
+ view.reorderPage(record.page, position);
182
+ }
183
+ });
184
+
185
+ for (const { route, record } of present) {
186
+ record.route = route;
187
+ applyPageOptions(record, descriptors[route.key]?.options ?? {});
188
+ }
189
+
190
+ const focus = focusedKey(state);
191
+ const focused = presentRecord(view, store, focus);
192
+
193
+ if (focus !== store.lastFocus) {
194
+ store.lastFocus = focus;
195
+
196
+ if (focused !== undefined && view.getSelectedPage() !== focused.page) {
197
+ view.setSelectedPage(focused.page);
198
+ }
199
+ }
200
+ };
201
+
202
+ const TabContent = ({ descriptor, isFocused }: TabContentProps): ReactNode => {
203
+ const hasLoaded = useHasLoaded(isFocused || descriptor.options.lazy === false);
204
+
205
+ return hasLoaded ? descriptor.render() : null;
206
+ };
207
+
208
+ const TabViewHost = (props: TabViewHostProps): ReactNode => {
209
+ const { view, state, navigation, descriptors } = props;
210
+ const { tabBarVisible, tabBarAutohide, tabBarExpand, tabBarInverted, overview, menuModel, renderMenu } = props;
211
+ const { extraDropTarget, onExtraDragDrop, onCreateWindow, onSetupMenu, shortcuts, defaultIcon } = props;
212
+ const [store] = useState(createPageStore);
213
+ const [placeholder, setPlaceholder] = useState<Adw.Bin | null>(null);
214
+ const [renderedMenu, setRenderedMenu] = useState<Gio.MenuModel | null>(null);
215
+ const selectedPage = useProperty(view, "selectedPage");
216
+ const focused = getFocusedRoute(state);
217
+ const descriptor = requireDescriptor(descriptors, focused.key);
218
+
219
+ useSyncExternalStore(store.subscribe, store.getVersion, store.getVersion);
220
+
221
+ if (menuModel != null && renderMenu !== undefined) {
222
+ throw new Error("Pass the tab view navigator either menuModel or renderMenu, not both.");
223
+ }
224
+
225
+ const menu = renderMenu === undefined ? (menuModel ?? null) : renderedMenu;
226
+
227
+ const routeOf = (page: Adw.TabPage): RouteProp<ParamListBase> | undefined => {
228
+ const key = recordOf(page)?.route.key;
229
+
230
+ return key === undefined ? undefined : descriptors[key]?.route;
231
+ };
232
+
233
+ const [handle] = useState<TabViewHandle>(() => ({
234
+ view,
235
+ getPage: (key) => presentRecord(view, store, key)?.page ?? null,
236
+ transferTab: (key, target, position) => {
237
+ const record = presentRecord(view, store, key);
238
+
239
+ if (record === undefined) {
240
+ throw new Error(`Route "${key}" has no page in this tab view.`);
241
+ }
242
+
243
+ const pinned = record.page.getPinned();
244
+ const minimum = pinned ? 0 : target.getNPinnedPages();
245
+ const maximum = pinned ? target.getNPinnedPages() : target.getNPages();
246
+ const index = position ?? maximum;
247
+
248
+ if (!Number.isInteger(index) || index < minimum || index > maximum) {
249
+ throw new RangeError("The position is outside the tab's pinned or unpinned group.");
250
+ }
251
+
252
+ view.transferPage(record.page, target, index);
253
+ },
254
+ }));
255
+
256
+ useLayoutEffect(() => {
257
+ if (placeholder === null) {
258
+ return;
259
+ }
260
+
261
+ placeholder.setChild(view);
262
+
263
+ return () => {
264
+ if (placeholder.getChild() === view) {
265
+ placeholder.setChild(null);
266
+ }
267
+ };
268
+ }, [placeholder, view]);
269
+
270
+ useLayoutEffect(() => {
271
+ view.setMenuModel(menu);
272
+ }, [view, menu]);
273
+
274
+ useLayoutEffect(() => {
275
+ if (shortcuts !== undefined) {
276
+ view.setShortcuts(shortcuts);
277
+ }
278
+ }, [view, shortcuts]);
279
+
280
+ useLayoutEffect(() => {
281
+ const icon = toIcon(defaultIcon);
282
+
283
+ if (icon !== null) {
284
+ view.setDefaultIcon(icon);
285
+ }
286
+ }, [view, defaultIcon]);
287
+
288
+ useLayoutEffect(() => {
289
+ syncPages(view, store, state, descriptors);
290
+ }, [view, store, state, descriptors]);
291
+
292
+ useLayoutEffect(() => {
293
+ const selected = view.getSelectedPage();
294
+ const key = selected === null ? undefined : recordOf(selected)?.route.key;
295
+ const current = navigation.getState();
296
+
297
+ if (key === undefined || !hasRoute(current, key) || focusedKey(current) === key) {
298
+ return;
299
+ }
300
+
301
+ const event = navigation.emit({ type: "tabPress", target: key, canPreventDefault: true });
302
+
303
+ if (event.defaultPrevented) {
304
+ const previous = presentRecord(view, store, focusedKey(current));
305
+
306
+ if (previous !== undefined) {
307
+ view.setSelectedPage(previous.page);
308
+ }
309
+
310
+ return;
311
+ }
312
+
313
+ navigation.dispatch(TabViewActions.selectTab(key));
314
+ }, [selectedPage, navigation, view, store]);
315
+
316
+ useSignal(view, "close-page", (page) => {
317
+ const key = recordOf(page)?.route.key;
318
+
319
+ if (key === undefined || !hasRoute(navigation.getState(), key)) {
320
+ view.closePageFinish(page, true);
321
+
322
+ return true;
323
+ }
324
+
325
+ navigation.dispatch(TabViewActions.closeTab(key));
326
+ view.closePageFinish(page, !hasRoute(navigation.getState(), key));
327
+
328
+ return true;
329
+ });
330
+
331
+ useSignal(view, "page-attached", (page, position) => {
332
+ const record = recordOf(page);
333
+
334
+ if (record === undefined || hasRoute(navigation.getState(), record.route.key)) {
335
+ return;
336
+ }
337
+
338
+ store.set(record);
339
+ navigation.dispatch({
340
+ type: "OPEN_TAB",
341
+ payload: { ...record.route, position, pinned: page.getPinned(), select: false },
342
+ });
343
+ });
344
+
345
+ useSignal(view, "page-detached", (page) => {
346
+ const key = recordOf(page)?.route.key;
347
+
348
+ if (key !== undefined && hasRoute(navigation.getState(), key)) {
349
+ navigation.dispatch(TabViewActions.closeTab(key));
350
+ }
351
+ });
352
+
353
+ useSignal(view, "page-reordered", (page, position) => {
354
+ const key = recordOf(page)?.route.key;
355
+
356
+ if (key !== undefined && hasRoute(navigation.getState(), key)) {
357
+ navigation.dispatch(TabViewActions.reorderTab(key, position));
358
+ }
359
+ });
360
+
361
+ useSignal(view, "indicator-activated", (page) => {
362
+ const key = recordOf(page)?.route.key;
363
+
364
+ if (key !== undefined && hasRoute(navigation.getState(), key)) {
365
+ navigation.emit({ type: "indicatorActivated", target: key });
366
+ }
367
+ });
368
+
369
+ useSignal(view, "setup-menu", (page) => {
370
+ onSetupMenu?.(page === null ? null : (routeOf(page) ?? null));
371
+ });
372
+
373
+ useSignal(view, "create-window", () => onCreateWindow?.() ?? undefined);
374
+
375
+ const createTab = (): Adw.TabPage | undefined => {
376
+ const target = overview?.onCreateTab?.();
377
+
378
+ if (target === undefined) {
379
+ return undefined;
380
+ }
381
+
382
+ const route: Route = { key: createRouteKey(target.name), name: target.name, params: target.params };
383
+ const record = createPage(view, store, route, { position: view.getNPages(), pinned: false });
384
+
385
+ navigation.dispatch({ type: "OPEN_TAB", payload: { ...route, position: view.getPagePosition(record.page) } });
386
+
387
+ if (!hasRoute(navigation.getState(), route.key)) {
388
+ store.delete(route.key);
389
+ view.closePage(record.page);
390
+
391
+ return undefined;
392
+ }
393
+
394
+ return record.page;
395
+ };
396
+
397
+ const drop = (page: Adw.TabPage, value: GObject.Value): boolean => {
398
+ const route = routeOf(page);
399
+
400
+ return route === undefined ? false : (onExtraDragDrop?.(route, value) ?? false);
401
+ };
402
+
403
+ const header =
404
+ descriptor.options.headerShown === false ? null : descriptor.options.header === undefined ? (
405
+ <HeaderBar options={descriptor.options} />
406
+ ) : (
407
+ <>
408
+ {descriptor.options.header({
409
+ route: descriptor.route,
410
+ navigation: descriptor.navigation,
411
+ options: descriptor.options,
412
+ })}
413
+ </>
414
+ );
415
+
416
+ const content = (
417
+ <AdwToolbarView
418
+ top={
419
+ <>
420
+ {header}
421
+ <AdwTabBar
422
+ view={view}
423
+ visible={tabBarVisible ?? true}
424
+ autohide={tabBarAutohide ?? true}
425
+ expandTabs={tabBarExpand ?? true}
426
+ inverted={tabBarInverted ?? false}
427
+ extraDropTarget={extraDropTarget ?? null}
428
+ onExtraDragDrop={drop}
429
+ />
430
+ </>
431
+ }
432
+ >
433
+ <AdwBin ref={setPlaceholder} />
434
+ </AdwToolbarView>
435
+ );
436
+
437
+ return (
438
+ <TabViewContext.Provider value={handle}>
439
+ {overview === undefined ? (
440
+ content
441
+ ) : (
442
+ <AdwTabOverview
443
+ view={view}
444
+ open={overview.open}
445
+ enableNewTab={overview.enableNewTab === true && overview.onCreateTab !== undefined}
446
+ enableSearch={overview.enableSearch ?? true}
447
+ extraDropTarget={extraDropTarget ?? null}
448
+ onCreateTab={createTab}
449
+ onExtraDragDrop={drop}
450
+ onNotifyOpen={(open) => overview.onOpenChange?.(open ?? false)}
451
+ >
452
+ {content}
453
+ </AdwTabOverview>
454
+ )}
455
+ {renderMenu === undefined ? null : createPortal(renderMenu(setRenderedMenu), rootElement)}
456
+ {state.routes.map((route) => {
457
+ const record = presentRecord(view, store, route.key);
458
+
459
+ return record === undefined
460
+ ? null
461
+ : createPortal(
462
+ <TabContent
463
+ descriptor={requireDescriptor(descriptors, route.key)}
464
+ isFocused={route.key === focused.key}
465
+ />,
466
+ record.host,
467
+ route.key,
468
+ );
469
+ })}
470
+ </TabViewContext.Provider>
471
+ );
472
+ };
473
+
474
+ export { type TabViewHandle, TabViewHost, useTabView };