Skip to content

Commit 1bc5b60

Browse files
committed
fix(): InteractiveDemo Script Loading
1 parent 2515fb4 commit 1bc5b60

File tree

2 files changed

+147
-169
lines changed

2 files changed

+147
-169
lines changed

template/components/InteractiveDemo/InteractiveDemo.astro

Lines changed: 1 addition & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -56,172 +56,4 @@ const initialItem = (initialId && items.find((i) => i.id === initialId)) ?? item
5656
</div>
5757
)
5858
}
59-
60-
<script>
61-
const initializedRoots: WeakSet<Element> = new WeakSet();
62-
63-
const initRoot = (root: Element) => {
64-
if (initializedRoots.has(root)) return;
65-
initializedRoots.add(root);
66-
67-
const buttons: NodeListOf<HTMLButtonElement> = root.querySelectorAll(
68-
'[data-image-toggle-button]',
69-
);
70-
const img: HTMLImageElement | null = root.querySelector('[data-image-toggle-target]');
71-
const toggleLight: HTMLButtonElement | null = root.querySelector('[data-image-toggle-light]');
72-
const toggleDark: HTMLButtonElement | null = root.querySelector('[data-image-toggle-dark]');
73-
74-
let cardEl: HTMLElement | null = root.querySelector('[data-image-card]');
75-
if (!cardEl) cardEl = root as HTMLElement; // fallback to root if card not found
76-
77-
const getLocalMode = (): 'dark' | 'light' => {
78-
const attr = cardEl?.getAttribute('data-local-mode');
79-
if (attr === 'dark' || attr === 'light') return attr as 'dark' | 'light';
80-
// Fallback to OS preference if not set locally
81-
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
82-
? 'dark'
83-
: 'light';
84-
};
85-
86-
const setLocalMode = (mode: 'dark' | 'light') => {
87-
cardEl?.setAttribute('data-local-mode', mode);
88-
};
89-
90-
const applyModeToImage = () => {
91-
if (!img) return;
92-
const mode = getLocalMode();
93-
const s = img.getAttribute('src') || '';
94-
const next = s.replace(
95-
/--(Light|Dark)mode--/,
96-
mode === 'dark' ? '--Darkmode--' : '--Lightmode--',
97-
);
98-
if (next !== s) img.setAttribute('src', next);
99-
updateToggleVisibility();
100-
};
101-
102-
const setActiveButton = (activeId: string) => {
103-
buttons.forEach((btn) => {
104-
if (btn.getAttribute('data-image-id') === activeId) {
105-
btn.setAttribute('data-active', '');
106-
} else {
107-
btn.removeAttribute('data-active');
108-
}
109-
});
110-
};
111-
112-
buttons.forEach((button: HTMLButtonElement) => {
113-
button.addEventListener('click', () => {
114-
const srcTemplate = button.getAttribute('data-image-src');
115-
const alt = button.getAttribute('data-image-alt') ?? '';
116-
if (img && srcTemplate) {
117-
const mode = getLocalMode();
118-
// Normalize template to current shell mode by replacing any Light/Dark marker
119-
const next = srcTemplate.replace(
120-
/--(Light|Dark)mode--/,
121-
mode === 'dark' ? '--Darkmode--' : '--Lightmode--',
122-
);
123-
img.setAttribute('src', next);
124-
img.setAttribute('alt', alt);
125-
// update toggle visibility to reflect current local mode after brand change
126-
updateToggleVisibility();
127-
// Scroll image into viewport after change
128-
try {
129-
img.scrollIntoView({ behavior: 'smooth', block: 'center' });
130-
} catch (e) {}
131-
const id = button.getAttribute('data-image-id');
132-
if (id) setActiveButton(id);
133-
}
134-
});
135-
});
136-
137-
// Helper: show/hide correct toggle button based on current img src
138-
const updateToggleVisibility = () => {
139-
const mode = getLocalMode();
140-
const isLight = mode === 'light';
141-
if (toggleLight) toggleLight.style.display = isLight ? '' : 'none';
142-
if (toggleDark) toggleDark.style.display = isLight ? 'none' : '';
143-
};
144-
145-
// Toggle to dark
146-
toggleLight?.addEventListener('click', () => {
147-
if (!img) return;
148-
setLocalMode('dark');
149-
applyModeToImage();
150-
// Scroll image into viewport after toggle
151-
try {
152-
img.scrollIntoView({ behavior: 'smooth', block: 'center' });
153-
} catch (e) {}
154-
});
155-
// Toggle to light
156-
toggleDark?.addEventListener('click', () => {
157-
if (!img) return;
158-
setLocalMode('light');
159-
applyModeToImage();
160-
updateToggleVisibility();
161-
});
162-
163-
// Initial visibility state
164-
applyModeToImage();
165-
// Do not override SSR-marked active button; derive from current image src if needed
166-
const currentSrc = img?.getAttribute('src') || '';
167-
const preMarked = Array.from(buttons).find((b) => b.hasAttribute('data-active'));
168-
if (!preMarked && currentSrc) {
169-
const match = Array.from(buttons).find((b) => {
170-
const tpl = b.getAttribute('data-image-src') || '';
171-
// Normalize mode marker for comparison
172-
const normalizedTpl = tpl.replace(
173-
/--(Light|Dark)mode--/,
174-
currentSrc.includes('--Darkmode--') ? '--Darkmode--' : '--Lightmode--',
175-
);
176-
return normalizedTpl === currentSrc;
177-
});
178-
const id = match?.getAttribute('data-image-id');
179-
if (id) setActiveButton(id);
180-
}
181-
182-
// Local mode is independent; no observation of global shell mode.
183-
};
184-
185-
const scanRoots = () => {
186-
const roots = document.querySelectorAll('[data-image-toggle-root]');
187-
roots.forEach((root) => initRoot(root));
188-
};
189-
190-
const setup = () => {
191-
// check what is already in the DOM
192-
scanRoots();
193-
194-
// observe future changes
195-
const observer = new MutationObserver(() => {
196-
scanRoots();
197-
});
198-
199-
if (document.body) {
200-
observer.observe(document.body, {
201-
childList: true,
202-
subtree: true,
203-
});
204-
}
205-
};
206-
207-
// Debounced setup for dynamic swaps
208-
let setupTimer: number | undefined;
209-
const scheduleSetup = () => {
210-
if (setupTimer) window.clearTimeout(setupTimer);
211-
setupTimer = window.setTimeout(() => {
212-
// wait one frame to allow DOM to settle
213-
requestAnimationFrame(() => setup());
214-
}, 0);
215-
};
216-
217-
// Init on first load
218-
if (document.readyState === 'loading') {
219-
document.addEventListener('DOMContentLoaded', setup, { once: true });
220-
} else {
221-
setup();
222-
}
223-
224-
// Also init on Astro client-side navigations (View Transitions / partial swaps)
225-
document.addEventListener('astro:page-load', scheduleSetup);
226-
document.addEventListener('astro:after-swap', scheduleSetup);
227-
</script>
59+
<script src="./interactiveDemo.client.js"></script>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
(function () {
2+
const initializedRoots = new WeakSet();
3+
4+
const initRoot = (root) => {
5+
if (initializedRoots.has(root)) return;
6+
initializedRoots.add(root);
7+
8+
const buttons = root.querySelectorAll('[data-image-toggle-button]');
9+
const img = root.querySelector('[data-image-toggle-target]');
10+
const toggleLight = root.querySelector('[data-image-toggle-light]');
11+
const toggleDark = root.querySelector('[data-image-toggle-dark]');
12+
13+
let cardEl = root.querySelector('[data-image-card]');
14+
if (!cardEl) cardEl = root; // fallback to root if card not found
15+
16+
const getLocalMode = () => {
17+
const attr = cardEl.getAttribute('data-local-mode');
18+
if (attr === 'dark' || attr === 'light') return attr;
19+
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
20+
? 'dark'
21+
: 'light';
22+
};
23+
24+
const setLocalMode = (mode) => {
25+
cardEl.setAttribute('data-local-mode', mode);
26+
};
27+
28+
const updateToggleVisibility = () => {
29+
const mode = getLocalMode();
30+
const isLight = mode === 'light';
31+
if (toggleLight) toggleLight.style.display = isLight ? '' : 'none';
32+
if (toggleDark) toggleDark.style.display = isLight ? 'none' : '';
33+
};
34+
35+
const applyModeToImage = () => {
36+
if (!img) return;
37+
const mode = getLocalMode();
38+
const s = img.getAttribute('src') || '';
39+
const next = s.replace(
40+
/--(Light|Dark)mode--/,
41+
mode === 'dark' ? '--Darkmode--' : '--Lightmode--',
42+
);
43+
if (next !== s) img.setAttribute('src', next);
44+
updateToggleVisibility();
45+
};
46+
47+
const setActiveButton = (activeId) => {
48+
buttons.forEach((btn) => {
49+
if (btn.getAttribute('data-image-id') === activeId) {
50+
btn.setAttribute('data-active', '');
51+
} else {
52+
btn.removeAttribute('data-active');
53+
}
54+
});
55+
};
56+
57+
buttons.forEach((button) => {
58+
button.addEventListener('click', () => {
59+
const srcTemplate = button.getAttribute('data-image-src');
60+
const alt = button.getAttribute('data-image-alt') ?? '';
61+
if (img && srcTemplate) {
62+
const mode = getLocalMode();
63+
const next = srcTemplate.replace(
64+
/--(Light|Dark)mode--/,
65+
mode === 'dark' ? '--Darkmode--' : '--Lightmode--',
66+
);
67+
img.setAttribute('src', next);
68+
img.setAttribute('alt', alt);
69+
updateToggleVisibility();
70+
try {
71+
img.scrollIntoView({ behavior: 'smooth', block: 'center' });
72+
} catch (e) {}
73+
const id = button.getAttribute('data-image-id');
74+
if (id) setActiveButton(id);
75+
}
76+
});
77+
});
78+
79+
toggleLight &&
80+
toggleLight.addEventListener('click', () => {
81+
if (!img) return;
82+
setLocalMode('dark');
83+
applyModeToImage();
84+
try {
85+
img.scrollIntoView({ behavior: 'smooth', block: 'center' });
86+
} catch (e) {}
87+
});
88+
89+
toggleDark &&
90+
toggleDark.addEventListener('click', () => {
91+
if (!img) return;
92+
setLocalMode('light');
93+
applyModeToImage();
94+
updateToggleVisibility();
95+
});
96+
97+
applyModeToImage();
98+
99+
const currentSrc = img?.getAttribute('src') || '';
100+
const preMarked = Array.from(buttons).find((b) => b.hasAttribute('data-active'));
101+
if (!preMarked && currentSrc) {
102+
const match = Array.from(buttons).find((b) => {
103+
const tpl = b.getAttribute('data-image-src') || '';
104+
const normalizedTpl = tpl.replace(
105+
/--(Light|Dark)mode--/,
106+
currentSrc.includes('--Darkmode--') ? '--Darkmode--' : '--Lightmode--',
107+
);
108+
return normalizedTpl === currentSrc;
109+
});
110+
const id = match?.getAttribute('data-image-id');
111+
if (id) setActiveButton(id);
112+
}
113+
};
114+
115+
const scanRoots = () => {
116+
const roots = document.querySelectorAll('[data-image-toggle-root]');
117+
roots.forEach((root) => initRoot(root));
118+
};
119+
120+
const setup = () => {
121+
scanRoots();
122+
const observer = new MutationObserver(() => {
123+
scanRoots();
124+
});
125+
if (document.body) {
126+
observer.observe(document.body, { childList: true, subtree: true });
127+
}
128+
};
129+
130+
let setupTimer;
131+
const scheduleSetup = () => {
132+
if (setupTimer) window.clearTimeout(setupTimer);
133+
setupTimer = window.setTimeout(() => {
134+
requestAnimationFrame(() => setup());
135+
}, 0);
136+
};
137+
138+
if (document.readyState === 'loading') {
139+
document.addEventListener('DOMContentLoaded', setup, { once: true });
140+
} else {
141+
setup();
142+
}
143+
144+
document.addEventListener('astro:page-load', scheduleSetup);
145+
document.addEventListener('astro:after-swap', scheduleSetup);
146+
})();

0 commit comments

Comments
 (0)