Skip to content

Commit 8309d7b

Browse files
shairezigalklebanovnaorpeled
committed
fix(headless/tabs): render on server
Co-authored-by: Igal Klebanov <[email protected]> Co-authored-by: Naor Peled <[email protected]>
1 parent 487de0e commit 8309d7b

File tree

6 files changed

+83
-67
lines changed

6 files changed

+83
-67
lines changed

apps/website/src/components/keyboard-interaction-table/keyboard-interaction-table.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const KeyboardInteractionTable = component$(
2323
</thead>
2424
<tbody>
2525
{props.keyDescriptors.map((descriptor) => {
26-
console.log('descriptor', descriptor);
2726
return (
2827
<KBInteractionTableRow
2928
key={descriptor.keyTitle}

apps/website/src/routes/docs/headless/(components)/tabs/examples.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const Example01 = component$(() => {
99
return (
1010
<PreviewCodeExample>
1111
<div q:slot="actualComponent" class="tabs-example">
12-
<Tabs behavior="automatic">
12+
<Tabs behavior="automatic" selectedIndex={2}>
1313
<h3 id="tablist-1">Danish Composers</h3>
1414
<TabList>
1515
<Tab>Maria Ahlefeldt</Tab>

packages/kit-headless/src/components/tabs/tab.tsx

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import {
77
useComputed$,
88
useTask$,
99
$,
10+
useSignal,
1011
} from '@builder.io/qwik';
1112
import { tabsContextId } from './tabs-context-id';
1213
import { KeyCode } from '../../utils/key-code.type';
14+
import { isServer } from '@builder.io/qwik/build';
1315

1416
export interface TabProps {
1517
onClick?: PropFunction<() => void>;
@@ -21,30 +23,33 @@ export interface TabProps {
2123
export const Tab = component$((props: TabProps) => {
2224
const contextService = useContext(tabsContextId);
2325

26+
const serverAssignedIndexSig = useSignal<number | undefined>(undefined);
2427
const uniqueId = useId();
2528

26-
useTask$(({ cleanup }) => {
27-
contextService.onTabsChanged$();
28-
29-
cleanup(() => {
30-
contextService.onTabsChanged$();
31-
});
29+
useTask$(() => {
30+
if (isServer) {
31+
serverAssignedIndexSig.value =
32+
contextService.lastAssignedTabIndexSig.value;
33+
contextService.lastAssignedTabIndexSig.value++;
34+
}
3235
});
3336

3437
useTask$(({ track }) => {
3538
track(() => props.disabled);
36-
console.log(
37-
'contextService.tabsMap[uniqueId]',
38-
contextService.tabsMap[uniqueId]
39-
);
39+
4040
if (props.disabled && contextService.tabsMap[uniqueId]) {
4141
contextService.tabsMap[uniqueId].disabled = true;
4242
}
4343
});
4444

4545
const isSelectedSignal = useComputed$(() => {
46+
if (isServer) {
47+
return (
48+
serverAssignedIndexSig.value === contextService.selectedIndexSig.value
49+
);
50+
}
4651
return (
47-
contextService.selectedIndex.value ===
52+
contextService.selectedIndexSig.value ===
4853
contextService.tabsMap[uniqueId]?.index
4954
);
5055
});
@@ -53,22 +58,13 @@ export const Tab = component$((props: TabProps) => {
5358
() => contextService.tabsMap[uniqueId]?.tabPanelId
5459
);
5560

56-
// TODO: Figure out a way to fix this shitty hack :)
57-
useTask$(({ track }) => {
58-
track(() => isSelectedSignal.value);
59-
60-
if (isSelectedSignal.value) {
61-
contextService.showTabs$();
62-
}
63-
});
64-
6561
const selectTab$ = $(() => {
6662
// TODO: try to move this to the Tabs component
6763

6864
if (props.disabled) {
6965
return;
7066
}
71-
contextService.selectedIndex.value =
67+
contextService.selectedIndexSig.value =
7268
contextService.tabsMap[uniqueId]?.index || 0;
7369

7470
contextService.selectTab$(uniqueId);

packages/kit-headless/src/components/tabs/tabs-context.type.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import { TabInfo } from './tabs';
44
import { KeyCode } from '../../utils/key-code.type';
55

66
export interface TabsContext {
7-
selectedIndex: Signal<number>;
8-
selectedTabId: Signal<string>;
97
selectTab$: QRL<(tabId: string) => void>;
108
showTabs$: QRL<() => void>;
119
onTabsChanged$: QRL<() => void>;
10+
onTabKeyDown$: QRL<(key: KeyCode, tabId: string) => void>;
11+
selectedIndexSig: Signal<number>;
12+
selectedTabIdSig: Signal<string>;
1213
tabsMap: { [key: string]: TabInfo };
1314
tabPanelsMap: { [key: string]: TabInfo };
1415
behavior: Behavior;
15-
onTabKeyDown$: QRL<(key: KeyCode, tabId: string) => void>;
16+
17+
lastAssignedTabIndexSig: Signal<number>;
18+
lastAssignedPanelIndexSig: Signal<number>;
1619
}

packages/kit-headless/src/components/tabs/tabs-panel.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,43 @@ import {
55
Slot,
66
useTask$,
77
useComputed$,
8+
useSignal,
89
} from '@builder.io/qwik';
910
import { tabsContextId } from './tabs-context-id';
11+
import { isServer } from '@builder.io/qwik/build';
1012

1113
export interface TabPanelProps {
1214
class?: string;
1315
}
1416

1517
export const TabPanel = component$(({ ...props }: TabPanelProps) => {
1618
const contextService = useContext(tabsContextId);
19+
20+
const serverAssignedIndexSig = useSignal<number | undefined>(undefined);
21+
1722
const panelUID = useId();
1823

1924
const matchedTabId = useComputed$(
2025
() => contextService.tabPanelsMap[panelUID]?.tabId
2126
);
2227

23-
useTask$(({ cleanup }) => {
24-
contextService.onTabsChanged$();
25-
26-
cleanup(() => {
27-
contextService.onTabsChanged$();
28-
});
28+
useTask$(() => {
29+
if (isServer) {
30+
serverAssignedIndexSig.value =
31+
contextService.lastAssignedPanelIndexSig.value;
32+
contextService.lastAssignedPanelIndexSig.value++;
33+
}
2934
});
3035

3136
const isSelectedSignal = useComputed$(() => {
37+
if (isServer) {
38+
return (
39+
serverAssignedIndexSig.value === contextService.selectedIndexSig.value
40+
);
41+
}
42+
3243
return (
33-
contextService.selectedIndex.value ===
44+
contextService.selectedIndexSig.value ===
3445
contextService.tabPanelsMap[panelUID]?.index
3546
);
3647
});

packages/kit-headless/src/components/tabs/tabs.tsx

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -55,32 +55,34 @@ export const Tabs = component$((props: TabsProps) => {
5555
const behavior = props.behavior ?? 'manual';
5656

5757
const ref = useSignal<HTMLElement | undefined>();
58-
const selectedIndex = useSignal(0);
58+
const selectedIndexSig = useSignal(0);
59+
const lastAssignedTabIndexSig = useSignal(0);
60+
const lastAssignedPanelIndexSig = useSignal(0);
5961

6062
useTask$(({ track }) => {
6163
track(() => props.selectedIndex);
62-
selectedIndex.value = props.selectedIndex || 0;
64+
selectedIndexSig.value = props.selectedIndex || 0;
6365
});
6466

65-
const selectedTabId = useSignal<string>('');
66-
const reIndexTabs = useSignal(false);
67-
const showTabsSignal = useSignal(false);
67+
const selectedTabIdSig = useSignal<string>('');
68+
const reIndexTabsSig = useSignal(true);
69+
const showTabsSig = useSignal(false);
6870
const tabPairs = useStore<TabPair[]>([]);
6971

7072
const tabsMap = useStore<{ [key: string]: TabInfo }>({});
7173

7274
const tabPanelsMap = useStore<{ [key: string]: TabInfo }>({});
7375

7476
const onTabsChanged$ = $(() => {
75-
reIndexTabs.value = true;
77+
reIndexTabsSig.value = true;
7678
});
7779

7880
const selectTab$ = $((tabId: string) => {
79-
selectedTabId.value = tabId;
81+
selectedTabIdSig.value = tabId;
8082
});
8183

8284
const showTabs$ = $(() => {
83-
showTabsSignal.value = true;
85+
showTabsSig.value = true;
8486
});
8587

8688
const onTabKeyDown$ = $((key: KeyCode, tabId: string) => {
@@ -131,26 +133,28 @@ export const Tabs = component$((props: TabsProps) => {
131133
});
132134

133135
const contextService: TabsContext = {
134-
tabsMap,
135-
tabPanelsMap,
136-
selectedIndex,
137136
selectTab$,
138137
showTabs$,
139138
onTabsChanged$,
140-
behavior,
141-
selectedTabId,
142139
onTabKeyDown$,
140+
selectedTabIdSig,
141+
selectedIndexSig,
142+
tabsMap,
143+
tabPanelsMap,
144+
behavior,
145+
lastAssignedTabIndexSig,
146+
lastAssignedPanelIndexSig,
143147
};
144148

145149
useContextProvider(tabsContextId, contextService);
146150

147151
useVisibleTask$(({ track }) => {
148-
track(() => reIndexTabs.value);
152+
track(() => reIndexTabsSig.value);
149153

150-
if (!reIndexTabs.value) {
154+
if (!reIndexTabsSig.value) {
151155
return;
152156
}
153-
reIndexTabs.value = false;
157+
reIndexTabsSig.value = false;
154158

155159
if (ref.value) {
156160
const tabsRootElement = ref.value;
@@ -173,13 +177,14 @@ export const Tabs = component$((props: TabsProps) => {
173177
}
174178

175179
// See if the deleted index was the last one
176-
let previousSelectedTabWasLastOne = false;
177-
if (selectedIndex.value === tabPairs.length - 1) {
178-
previousSelectedTabWasLastOne = true;
180+
let lastTabWasSelectedPreviously = false;
181+
if (selectedIndexSig.value === tabPairs.length - 1) {
182+
lastTabWasSelectedPreviously = true;
179183
}
180184

181185
tabPairs.length = 0;
182-
tabsMap;
186+
187+
let deletedTabId: string | undefined = undefined;
183188

184189
tabElements.forEach((tab, index) => {
185190
const tabId = tab.getAttribute('data-tab-id');
@@ -190,12 +195,14 @@ export const Tabs = component$((props: TabsProps) => {
190195
}
191196

192197
// clear all lists and maps
193-
let tabWasDeleted = true;
198+
let thisTabWasDeleted = true;
194199
// TODO: delete object maps, or turn into Map()
195200

196-
if (tabId === selectedTabId.value) {
197-
selectedIndex.value = index;
198-
tabWasDeleted = false;
201+
if (selectedTabIdSig.value === '') {
202+
thisTabWasDeleted = false;
203+
} else if (tabId === selectedTabIdSig.value) {
204+
selectedIndexSig.value = index;
205+
thisTabWasDeleted = false;
199206
}
200207

201208
const tabPanelElement = tabPanelElements[index];
@@ -222,22 +229,22 @@ export const Tabs = component$((props: TabsProps) => {
222229
throw new Error('Missing tab id or tab panel id for tab: ' + index);
223230
}
224231

225-
if (tabPairs.length > 0) {
226-
if (previousSelectedTabWasLastOne && tabWasDeleted) {
227-
selectedIndex.value = tabPairs.length - 1;
228-
}
229-
selectedTabId.value = tabPairs[selectedIndex.value].tabId;
232+
if (thisTabWasDeleted) {
233+
deletedTabId = tabId;
230234
}
231235
});
236+
237+
if (tabPairs.length > 0) {
238+
if (lastTabWasSelectedPreviously && deletedTabId) {
239+
selectedIndexSig.value = tabPairs.length - 1;
240+
}
241+
selectedTabIdSig.value = tabPairs[selectedIndexSig.value].tabId;
242+
}
232243
}
233244
});
234245

235246
return (
236-
<div
237-
ref={ref}
238-
{...props}
239-
style={'visibility:' + (showTabsSignal.value ? 'visible' : 'hidden')}
240-
>
247+
<div ref={ref} {...props}>
241248
<Slot />
242249
</div>
243250
);

0 commit comments

Comments
 (0)