hono-decks 1.3.0 → 1.4.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.ja.md CHANGED
@@ -294,7 +294,7 @@ export default defineConfig({
294
294
  });
295
295
  ```
296
296
 
297
- pluginはVite起動前にcompileし、deck rootと`hono-decks.config.ts`をVite watcherへ登録します。再compileに成功すると、安全にislandを維持できる場合はスライドの内容だけを更新し、現在のスライド・ステップとislandの状態を保ちます。islandのコード、props、Markdownのコンテキスト、構造などが変わり、安全に引き継げない場合は表示ページを再読み込みし、スライドとステップを復元します。どちらの構成でも通常の`bun run dev`だけでauthoringできます。
297
+ pluginはVite起動前にcompileし、deck rootと`hono-decks.config.ts`をVite watcherへ登録します。再compileに成功すると、既存のislandを安全に識別できる場合は表示ページを再読み込みせずに内容を更新します。対応するprops・Markdownコンテキストの変更、別々のislandの並べ替え・追加・削除でも状態を維持し、現在のスライド・ステップを保ちます。クライアントコードの変更や同一islandの区別がつかない場合など、安全に更新できないときは表示ページを再読み込みしてスライドとステップを復元します。このときislandの状態はリセットされます。どちらの構成でも通常の`bun run dev`だけでauthoringできます。
298
298
 
299
299
  ## Runtime configuration
300
300
 
package/README.md CHANGED
@@ -295,7 +295,7 @@ export default defineConfig({
295
295
  });
296
296
  ~~~
297
297
 
298
- The plugin compiles before Vite starts and adds the deck root and <code>hono-decks.config.ts</code> to the Vite watcher. After a successful recompile, open presentation pages update their deck content without reloading when it is safe to retain the existing islands. The current slide, step, and island state are preserved. If the update cannot safely retain islands (for example, their code, props, Markdown context, or structure changed), the presentation page reloads instead and restores the slide and step. In both setups, the application's normal <code>dev</code> command is enough for authoring.
298
+ The plugin compiles before Vite starts and adds the deck root and <code>hono-decks.config.ts</code> to the Vite watcher. After a successful recompile, presentation pages update without reloading when existing islands can be identified safely. Their state survives supported prop and Markdown context changes, reordering, and the addition or removal of distinct islands. The current slide and step are retained. When an update cannot be applied safely, such as a changed client bundle or ambiguous duplicate islands, the presentation page reloads and restores the slide and step; island state is then reset. In both setups, the application's normal <code>dev</code> command is enough for authoring.
299
299
 
300
300
  ## Runtime configuration
301
301
 
@@ -31,8 +31,37 @@ function createMarkdownAdapter({ cwd, options }) {
31
31
  function emitMarkdownClient(islands, runtime) {
32
32
  if (!islands.length) return "";
33
33
  const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
34
+ const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
35
+ const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
36
+ const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
37
+ const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
34
38
  return `import { hydrateMarkdownIslands } from ${literal(runtime)};
39
+ import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
40
+ import { MarkdownContext } from ${literal(contextModule)};
41
+ import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
42
+ import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
43
+ function hydrateDeckMarkdownIslands() {
35
44
  hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
45
+ schedule(element, loader, props, strategy, mount) {
46
+ return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
47
+ const live = window.__honoDecksLive;
48
+ if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
49
+ const Component = module.default;
50
+ function LiveMarkdownIsland(componentProps) {
51
+ const [current, setCurrent] = useLiveState({
52
+ props: componentProps,
53
+ context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
54
+ });
55
+ const [active, setActive] = useLiveState(true);
56
+ live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
57
+ return active ? liveJsx(MarkdownContext.Provider, {
58
+ value: current.context,
59
+ children: liveJsx(Component, current.props),
60
+ }) : null;
61
+ }
62
+ return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
63
+ });
64
+ },
36
65
  ready(element, start) {
37
66
  const slide = element.closest('section[data-slide-state]');
38
67
  if (!slide || slide.dataset.slideState === 'active') { start(); return; }
@@ -41,7 +70,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
41
70
  });
42
71
  observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
43
72
  }
44
- });`;
73
+ });
74
+ }
75
+ hydrateDeckMarkdownIslands();
76
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
45
77
  }
46
78
  //#endregion
47
79
  export { createHonoMarkdownAdapter, createMarkdownAdapter };
package/dist/advanced.js CHANGED
@@ -805,9 +805,16 @@ function renderLiveReloadScript(eventsPath, nonce) {
805
805
  return `<script${documentNonceAttribute(nonce)}>
806
806
  (() => {
807
807
  const events = new EventSource(${JSON.stringify(eventsPath)});
808
+ const scriptNonce = document.currentScript?.nonce;
808
809
  let running = false;
809
810
  let pending = false;
810
811
  const selector = "[data-hono-decks-island],[data-hono-md-island]";
812
+ const islandUpdaters = new WeakMap();
813
+ const hydrators = new Set();
814
+ window.__honoDecksLive = {
815
+ register: (element, update, dispose) => islandUpdaters.set(element, { update, dispose }),
816
+ registerHydrator: (hydrate) => hydrators.add(hydrate),
817
+ };
811
818
  const clientSelector = "script[data-hono-decks-client-entry]";
812
819
  const clientText = async (url) => {
813
820
  if (!url) return "";
@@ -820,12 +827,12 @@ function renderLiveReloadScript(eventsPath, nonce) {
820
827
  : Promise.resolve();
821
828
  let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
822
829
 
823
- function signature(element) {
830
+ function signature(element, includeContext = true, includeProps = true) {
824
831
  const attributes = element.hasAttribute("data-hono-md-island")
825
832
  ? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
826
833
  : ["data-hono-decks-island", "data-hono-decks-props"];
827
- return JSON.stringify([attributes.map((name) => element.getAttribute(name)),
828
- element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null]);
834
+ return JSON.stringify([attributes.map((name) => !includeProps && name.endsWith("-props") ? null : element.getAttribute(name)),
835
+ includeContext ? element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null : null]);
829
836
  }
830
837
 
831
838
  async function update() {
@@ -843,34 +850,142 @@ function renderLiveReloadScript(eventsPath, nonce) {
843
850
  if (!newSlides.length) throw new Error("Empty deck");
844
851
  // Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
845
852
  if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
846
- const oldIslands = oldSlides.flatMap((slide) => Array.from(slide.querySelectorAll(selector)));
853
+ const collect = (slides) => slides.flatMap((slide, slideIndex) =>
854
+ Array.from(slide.querySelectorAll(selector)).map((element, position) => ({ element, slideIndex, position })));
855
+ const oldItems = collect(oldSlides);
856
+ const newItems = collect(newSlides);
847
857
  const nextScript = next.querySelector(clientSelector);
848
- const nextClient = await clientText(nextScript?.getAttribute("src"));
858
+ const fetchedClient = await clientText(nextScript?.getAttribute("src"));
849
859
  const previousClient = await clientSnapshot;
850
860
  if (previousClient === null) throw new Error("Initial client entry fetch failed");
851
- // Changed client code cannot safely replace a running component while retaining its state.
852
- if (oldIslands.length && previousClient !== nextClient) throw new Error("Client code changed");
861
+ // Keep the loaded registry when the last island disappears; it can dispose
862
+ // that island and initialize it again if it is reintroduced later.
863
+ const nextClient = !nextScript && !newItems.length ? previousClient : fetchedClient;
864
+ // A changed client bundle could contain new implementations for running islands.
865
+ if (oldItems.length && previousClient !== nextClient) throw new Error("Client code changed");
866
+ const needsEntry = !document.querySelector(clientSelector) && Boolean(nextScript);
867
+ if (newItems.length && !nextScript) throw new Error("New islands require a client entry");
868
+ if (!needsEntry && previousClient !== nextClient) throw new Error("Client code changed");
869
+
870
+ const duplicateKeys = new Set([...oldItems, ...newItems].map((item) => signature(item.element, false)));
871
+ for (const key of duplicateKeys) {
872
+ const before = oldItems.filter((item) => signature(item.element, false) === key);
873
+ const after = newItems.filter((item) => signature(item.element, false) === key);
874
+ if (before.length <= 1 && after.length <= 1) continue;
875
+ if (before.length !== after.length || before.some((item, index) =>
876
+ item.slideIndex !== after[index].slideIndex || item.position !== after[index].position))
877
+ throw new Error("Ambiguous duplicate islands");
878
+ }
853
879
  const pairs = [];
854
- if (oldIslands.length && oldSlides.length !== newSlides.length) throw new Error("Slide structure changed");
855
- for (let i = 0; i < oldSlides.length && i < newSlides.length; i++) {
856
- const before = Array.from(oldSlides[i].querySelectorAll(selector));
857
- const after = Array.from(newSlides[i].querySelectorAll(selector));
858
- if (before.length !== after.length) throw new Error("Island structure changed");
859
- for (let j = 0; j < before.length; j++) {
860
- if (signature(before[j]) !== signature(after[j])) throw new Error("Island properties changed");
861
- // Deferred Markdown islands observe their original slide. Reload rather than strand the observer.
862
- if (before[j].hasAttribute("data-hono-md-island") && !before[j].hasAttribute("data-hono-md-mounted"))
863
- throw new Error("Island is pending hydration");
864
- pairs.push([before[j], after[j]]);
880
+ const remainingOld = new Set(oldItems);
881
+ const remainingNew = new Set(newItems);
882
+ function pair(oldItem, newItem) {
883
+ remainingOld.delete(oldItem);
884
+ remainingNew.delete(newItem);
885
+ pairs.push([oldItem, newItem]);
886
+ }
887
+ // Exact matches can move between slides. Match only unique instances so
888
+ // duplicate islands cannot accidentally inherit each other's state.
889
+ for (const oldItem of oldItems) {
890
+ const key = signature(oldItem.element, false);
891
+ const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false) === key);
892
+ const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false) === key);
893
+ if (oldMatches.length === 1 && newMatches.length === 1) pair(oldItem, newMatches[0]);
894
+ }
895
+ // Changed props can be reconciled if there is only one island of that type.
896
+ for (const oldItem of Array.from(remainingOld)) {
897
+ const key = signature(oldItem.element, false, false);
898
+ const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false, false) === key);
899
+ const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false, false) === key);
900
+ if (oldMatches.length === 1 && newMatches.length === 1 &&
901
+ oldItem.slideIndex === newMatches[0].slideIndex && oldItem.position === newMatches[0].position)
902
+ pair(oldItem, newMatches[0]);
903
+ }
904
+ // Existing duplicate instances remain safe only when their positions and
905
+ // identity attributes are unchanged; ambiguous structural edits reload.
906
+ for (const oldItem of Array.from(remainingOld)) {
907
+ const newItem = Array.from(remainingNew).find((item) => item.slideIndex === oldItem.slideIndex && item.position === oldItem.position);
908
+ if (newItem && signature(oldItem.element, false) === signature(newItem.element, false)) pair(oldItem, newItem);
909
+ }
910
+ for (const oldItem of remainingOld) {
911
+ const key = signature(oldItem.element, false, false);
912
+ if (Array.from(remainingNew).some((item) => signature(item.element, false, false) === key))
913
+ throw new Error("Ambiguous island identity");
914
+ }
915
+ const removals = [];
916
+ for (const oldItem of remainingOld) {
917
+ const entry = islandUpdaters.get(oldItem.element);
918
+ if (!entry?.dispose) throw new Error("Cannot safely dispose island");
919
+ removals.push(entry.dispose);
920
+ }
921
+ const updates = [];
922
+ let preserveSlides = false;
923
+ for (const [oldItem, newItem] of pairs) {
924
+ const before = oldItem.element;
925
+ const after = newItem.element;
926
+ const pendingMarkdown = before.hasAttribute("data-hono-md-island") && !before.hasAttribute("data-hono-md-mounted");
927
+ if (pendingMarkdown) {
928
+ if (oldSlides.length !== newSlides.length || oldItem.slideIndex !== newItem.slideIndex)
929
+ throw new Error("Pending island moved between slides");
930
+ preserveSlides = true;
931
+ }
932
+ if (signature(before, !pendingMarkdown) !== signature(after, !pendingMarkdown)) {
933
+ const entry = islandUpdaters.get(before);
934
+ if (!entry?.update || signature(before, false, false) !== signature(after, false, false))
935
+ throw new Error("Island properties changed");
936
+ const markdown = before.hasAttribute("data-hono-md-island");
937
+ const propsAttribute = markdown ? "data-hono-md-props" : "data-hono-decks-props";
938
+ const rawProps = after.getAttribute(propsAttribute) || "{}";
939
+ const props = JSON.parse(rawProps);
940
+ const context = markdown ? JSON.parse(after.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") || "{}") : undefined;
941
+ updates.push([before, entry.update, propsAttribute, rawProps, markdown ? { props, context } : props]);
942
+ }
943
+ }
944
+ for (const dispose of removals) dispose();
945
+ for (const [oldItem, newItem] of pairs) newItem.element.replaceWith(oldItem.element);
946
+ if (preserveSlides) {
947
+ for (let i = 0; i < oldSlides.length; i++) {
948
+ const oldSlide = oldSlides[i];
949
+ const newSlide = newSlides[i];
950
+ for (const attribute of Array.from(oldSlide.attributes)) oldSlide.removeAttribute(attribute.name);
951
+ for (const attribute of Array.from(newSlide.attributes)) oldSlide.setAttribute(attribute.name, attribute.value);
952
+ oldSlide.replaceChildren(...newSlide.childNodes);
953
+ newSlide.replaceWith(oldSlide);
865
954
  }
866
955
  }
867
- if (!oldIslands.length && nextDeck.querySelector(selector)) throw new Error("New islands require initialization");
868
- for (const [oldIsland, newIsland] of pairs) newIsland.replaceWith(oldIsland);
869
956
  currentDeck.replaceChildren(...nextDeck.childNodes);
957
+ for (const [island, update, propsAttribute, rawProps, value] of updates) {
958
+ island.setAttribute(propsAttribute, rawProps);
959
+ update(value);
960
+ }
870
961
  style.textContent = nextStyle.textContent;
871
962
  document.title = next.title;
872
963
  clientSnapshot = Promise.resolve(nextClient);
873
964
  runtime.refresh();
965
+ if (remainingNew.size) {
966
+ if (needsEntry) {
967
+ const url = new URL(nextScript.getAttribute("src"), location.href);
968
+ if (url.origin !== location.origin) throw new Error("Cross-origin client entry");
969
+ url.searchParams.set("hono-decks-update", String(Date.now()));
970
+ const script = document.createElement("script");
971
+ script.type = "module";
972
+ script.setAttribute("data-hono-decks-client-entry", "");
973
+ if (scriptNonce) script.nonce = scriptNonce;
974
+ script.src = url.href;
975
+ await new Promise((resolve, reject) => {
976
+ script.onload = resolve;
977
+ script.onerror = () => reject(new Error("Client entry failed"));
978
+ document.body.appendChild(script);
979
+ });
980
+ } else {
981
+ for (const hydrate of hydrators) hydrate();
982
+ }
983
+ for (const item of remainingNew) {
984
+ const element = item.element;
985
+ if (!element.hasAttribute("data-hono-decks-live-mounted") && !element.hasAttribute("data-hono-md-scheduled"))
986
+ throw new Error("New island did not initialize");
987
+ }
988
+ }
874
989
  }
875
990
 
876
991
  async function onUpdate() {
@@ -1272,12 +1387,15 @@ function renderPresentationScript(nonce) {
1272
1387
  await document.documentElement.requestFullscreen?.();
1273
1388
  }
1274
1389
 
1390
+ const boundSlides = new WeakSet();
1275
1391
  function bindSlideClicks() {
1276
- slides.forEach((slide, slideIndex) => {
1392
+ slides.forEach((slide) => {
1393
+ if (boundSlides.has(slide)) return;
1394
+ boundSlides.add(slide);
1277
1395
  slide.addEventListener("click", () => {
1278
1396
  if (!document.body.hasAttribute("data-overview-mode")) return;
1279
1397
  document.body.removeAttribute("data-overview-mode");
1280
- show(slideIndex);
1398
+ show(slides.indexOf(slide));
1281
1399
  });
1282
1400
  });
1283
1401
  }
package/dist/bin.js CHANGED
@@ -1660,8 +1660,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
1660
1660
  function emitMarkdownClient(islands, runtime) {
1661
1661
  if (!islands.length) return "";
1662
1662
  const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
1663
+ const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
1664
+ const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
1665
+ const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
1666
+ const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
1663
1667
  return `import { hydrateMarkdownIslands } from ${literal(runtime)};
1668
+ import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
1669
+ import { MarkdownContext } from ${literal(contextModule)};
1670
+ import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
1671
+ import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
1672
+ function hydrateDeckMarkdownIslands() {
1664
1673
  hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
1674
+ schedule(element, loader, props, strategy, mount) {
1675
+ return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
1676
+ const live = window.__honoDecksLive;
1677
+ if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
1678
+ const Component = module.default;
1679
+ function LiveMarkdownIsland(componentProps) {
1680
+ const [current, setCurrent] = useLiveState({
1681
+ props: componentProps,
1682
+ context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
1683
+ });
1684
+ const [active, setActive] = useLiveState(true);
1685
+ live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
1686
+ return active ? liveJsx(MarkdownContext.Provider, {
1687
+ value: current.context,
1688
+ children: liveJsx(Component, current.props),
1689
+ }) : null;
1690
+ }
1691
+ return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
1692
+ });
1693
+ },
1665
1694
  ready(element, start) {
1666
1695
  const slide = element.closest('section[data-slide-state]');
1667
1696
  if (!slide || slide.dataset.slideState === 'active') { start(); return; }
@@ -1670,7 +1699,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
1670
1699
  });
1671
1700
  observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
1672
1701
  }
1673
- });`;
1702
+ });
1703
+ }
1704
+ hydrateDeckMarkdownIslands();
1705
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
1674
1706
  }
1675
1707
  //#endregion
1676
1708
  //#region src/node/markdown-adapter.ts
@@ -1719,11 +1751,15 @@ async function emitClientEntryModule(input) {
1719
1751
  import { hydrateSlideIslands } from "hono-decks/client";
1720
1752
  ${imports.join("\n")}
1721
1753
 
1722
- hydrateSlideIslands({
1723
- components: {
1724
- ${registrations.join(",\n ")}
1725
- }
1726
- });
1754
+ function hydrateDeckIslands() {
1755
+ hydrateSlideIslands({
1756
+ components: {
1757
+ ${registrations.join(",\n ")}
1758
+ }
1759
+ });
1760
+ }
1761
+ hydrateDeckIslands();
1762
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
1727
1763
  `,
1728
1764
  resolveDir: input.cwd,
1729
1765
  sourcefile: "hono-decks-client-entry.tsx",
package/dist/cli.js CHANGED
@@ -1659,8 +1659,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
1659
1659
  function emitMarkdownClient(islands, runtime) {
1660
1660
  if (!islands.length) return "";
1661
1661
  const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
1662
+ const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
1663
+ const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
1664
+ const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
1665
+ const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
1662
1666
  return `import { hydrateMarkdownIslands } from ${literal(runtime)};
1667
+ import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
1668
+ import { MarkdownContext } from ${literal(contextModule)};
1669
+ import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
1670
+ import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
1671
+ function hydrateDeckMarkdownIslands() {
1663
1672
  hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
1673
+ schedule(element, loader, props, strategy, mount) {
1674
+ return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
1675
+ const live = window.__honoDecksLive;
1676
+ if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
1677
+ const Component = module.default;
1678
+ function LiveMarkdownIsland(componentProps) {
1679
+ const [current, setCurrent] = useLiveState({
1680
+ props: componentProps,
1681
+ context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
1682
+ });
1683
+ const [active, setActive] = useLiveState(true);
1684
+ live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
1685
+ return active ? liveJsx(MarkdownContext.Provider, {
1686
+ value: current.context,
1687
+ children: liveJsx(Component, current.props),
1688
+ }) : null;
1689
+ }
1690
+ return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
1691
+ });
1692
+ },
1664
1693
  ready(element, start) {
1665
1694
  const slide = element.closest('section[data-slide-state]');
1666
1695
  if (!slide || slide.dataset.slideState === 'active') { start(); return; }
@@ -1669,7 +1698,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
1669
1698
  });
1670
1699
  observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
1671
1700
  }
1672
- });`;
1701
+ });
1702
+ }
1703
+ hydrateDeckMarkdownIslands();
1704
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
1673
1705
  }
1674
1706
  //#endregion
1675
1707
  //#region src/node/markdown-adapter.ts
@@ -1718,11 +1750,15 @@ async function emitClientEntryModule(input) {
1718
1750
  import { hydrateSlideIslands } from "hono-decks/client";
1719
1751
  ${imports.join("\n")}
1720
1752
 
1721
- hydrateSlideIslands({
1722
- components: {
1723
- ${registrations.join(",\n ")}
1724
- }
1725
- });
1753
+ function hydrateDeckIslands() {
1754
+ hydrateSlideIslands({
1755
+ components: {
1756
+ ${registrations.join(",\n ")}
1757
+ }
1758
+ });
1759
+ }
1760
+ hydrateDeckIslands();
1761
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
1726
1762
  `,
1727
1763
  resolveDir: input.cwd,
1728
1764
  sourcefile: "hono-decks-client-entry.tsx",
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { render } from "hono/jsx/dom";
1
+ import { flushSync, render, useState } from "hono/jsx/dom";
2
2
  import { jsx } from "hono/jsx/dom/jsx-runtime";
3
3
  //#region src/client/islands.ts
4
4
  function hydrateSlideIslands(input) {
@@ -8,7 +8,19 @@ function hydrateSlideIslands(input) {
8
8
  if (!name) continue;
9
9
  const component = input.components[name];
10
10
  if (!component) continue;
11
- render(jsx(component, parseIslandProps(island.dataset.honoDecksProps)), island);
11
+ const props = parseIslandProps(island.dataset.honoDecksProps);
12
+ const live = window.__honoDecksLive;
13
+ if (live) {
14
+ if (island.dataset.honoDecksLiveMounted) continue;
15
+ function LiveIsland() {
16
+ const [currentProps, setCurrentProps] = useState(props);
17
+ const [active, setActive] = useState(true);
18
+ live.register(island, setCurrentProps, () => flushSync(() => setActive(false)));
19
+ return active ? jsx(component, currentProps) : null;
20
+ }
21
+ render(jsx(LiveIsland, {}), island);
22
+ island.dataset.honoDecksLiveMounted = "true";
23
+ } else render(jsx(component, props), island);
12
24
  }
13
25
  }
14
26
  function parseIslandProps(value) {
package/dist/node.js CHANGED
@@ -1852,8 +1852,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
1852
1852
  function emitMarkdownClient(islands, runtime) {
1853
1853
  if (!islands.length) return "";
1854
1854
  const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
1855
+ const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
1856
+ const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
1857
+ const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
1858
+ const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
1855
1859
  return `import { hydrateMarkdownIslands } from ${literal(runtime)};
1860
+ import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
1861
+ import { MarkdownContext } from ${literal(contextModule)};
1862
+ import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
1863
+ import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
1864
+ function hydrateDeckMarkdownIslands() {
1856
1865
  hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
1866
+ schedule(element, loader, props, strategy, mount) {
1867
+ return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
1868
+ const live = window.__honoDecksLive;
1869
+ if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
1870
+ const Component = module.default;
1871
+ function LiveMarkdownIsland(componentProps) {
1872
+ const [current, setCurrent] = useLiveState({
1873
+ props: componentProps,
1874
+ context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
1875
+ });
1876
+ const [active, setActive] = useLiveState(true);
1877
+ live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
1878
+ return active ? liveJsx(MarkdownContext.Provider, {
1879
+ value: current.context,
1880
+ children: liveJsx(Component, current.props),
1881
+ }) : null;
1882
+ }
1883
+ return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
1884
+ });
1885
+ },
1857
1886
  ready(element, start) {
1858
1887
  const slide = element.closest('section[data-slide-state]');
1859
1888
  if (!slide || slide.dataset.slideState === 'active') { start(); return; }
@@ -1862,7 +1891,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
1862
1891
  });
1863
1892
  observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
1864
1893
  }
1865
- });`;
1894
+ });
1895
+ }
1896
+ hydrateDeckMarkdownIslands();
1897
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
1866
1898
  }
1867
1899
  //#endregion
1868
1900
  //#region src/node/markdown-adapter.ts
@@ -2001,11 +2033,15 @@ async function emitClientEntryModule(input) {
2001
2033
  import { hydrateSlideIslands } from "hono-decks/client";
2002
2034
  ${imports.join("\n")}
2003
2035
 
2004
- hydrateSlideIslands({
2005
- components: {
2006
- ${registrations.join(",\n ")}
2007
- }
2008
- });
2036
+ function hydrateDeckIslands() {
2037
+ hydrateSlideIslands({
2038
+ components: {
2039
+ ${registrations.join(",\n ")}
2040
+ }
2041
+ });
2042
+ }
2043
+ hydrateDeckIslands();
2044
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
2009
2045
  `,
2010
2046
  resolveDir: input.cwd,
2011
2047
  sourcefile: "hono-decks-client-entry.tsx",
@@ -3524,9 +3560,16 @@ function renderLiveReloadScript(eventsPath, nonce) {
3524
3560
  return `<script${documentNonceAttribute(nonce)}>
3525
3561
  (() => {
3526
3562
  const events = new EventSource(${JSON.stringify(eventsPath)});
3563
+ const scriptNonce = document.currentScript?.nonce;
3527
3564
  let running = false;
3528
3565
  let pending = false;
3529
3566
  const selector = "[data-hono-decks-island],[data-hono-md-island]";
3567
+ const islandUpdaters = new WeakMap();
3568
+ const hydrators = new Set();
3569
+ window.__honoDecksLive = {
3570
+ register: (element, update, dispose) => islandUpdaters.set(element, { update, dispose }),
3571
+ registerHydrator: (hydrate) => hydrators.add(hydrate),
3572
+ };
3530
3573
  const clientSelector = "script[data-hono-decks-client-entry]";
3531
3574
  const clientText = async (url) => {
3532
3575
  if (!url) return "";
@@ -3539,12 +3582,12 @@ function renderLiveReloadScript(eventsPath, nonce) {
3539
3582
  : Promise.resolve();
3540
3583
  let clientSnapshot = domReady.then(() => clientText(document.querySelector(clientSelector)?.src)).catch(() => null);
3541
3584
 
3542
- function signature(element) {
3585
+ function signature(element, includeContext = true, includeProps = true) {
3543
3586
  const attributes = element.hasAttribute("data-hono-md-island")
3544
3587
  ? ["data-hono-md-island", "data-hono-md-hydrate", "data-hono-md-props"]
3545
3588
  : ["data-hono-decks-island", "data-hono-decks-props"];
3546
- return JSON.stringify([attributes.map((name) => element.getAttribute(name)),
3547
- element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null]);
3589
+ return JSON.stringify([attributes.map((name) => !includeProps && name.endsWith("-props") ? null : element.getAttribute(name)),
3590
+ includeContext ? element.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") ?? null : null]);
3548
3591
  }
3549
3592
 
3550
3593
  async function update() {
@@ -3562,34 +3605,142 @@ function renderLiveReloadScript(eventsPath, nonce) {
3562
3605
  if (!newSlides.length) throw new Error("Empty deck");
3563
3606
  // Scripts parsed from fetched HTML are inert; reload so their behavior is not lost.
3564
3607
  if (currentDeck.querySelector("script") || nextDeck.querySelector("script")) throw new Error("Deck contains scripts");
3565
- const oldIslands = oldSlides.flatMap((slide) => Array.from(slide.querySelectorAll(selector)));
3608
+ const collect = (slides) => slides.flatMap((slide, slideIndex) =>
3609
+ Array.from(slide.querySelectorAll(selector)).map((element, position) => ({ element, slideIndex, position })));
3610
+ const oldItems = collect(oldSlides);
3611
+ const newItems = collect(newSlides);
3566
3612
  const nextScript = next.querySelector(clientSelector);
3567
- const nextClient = await clientText(nextScript?.getAttribute("src"));
3613
+ const fetchedClient = await clientText(nextScript?.getAttribute("src"));
3568
3614
  const previousClient = await clientSnapshot;
3569
3615
  if (previousClient === null) throw new Error("Initial client entry fetch failed");
3570
- // Changed client code cannot safely replace a running component while retaining its state.
3571
- if (oldIslands.length && previousClient !== nextClient) throw new Error("Client code changed");
3616
+ // Keep the loaded registry when the last island disappears; it can dispose
3617
+ // that island and initialize it again if it is reintroduced later.
3618
+ const nextClient = !nextScript && !newItems.length ? previousClient : fetchedClient;
3619
+ // A changed client bundle could contain new implementations for running islands.
3620
+ if (oldItems.length && previousClient !== nextClient) throw new Error("Client code changed");
3621
+ const needsEntry = !document.querySelector(clientSelector) && Boolean(nextScript);
3622
+ if (newItems.length && !nextScript) throw new Error("New islands require a client entry");
3623
+ if (!needsEntry && previousClient !== nextClient) throw new Error("Client code changed");
3624
+
3625
+ const duplicateKeys = new Set([...oldItems, ...newItems].map((item) => signature(item.element, false)));
3626
+ for (const key of duplicateKeys) {
3627
+ const before = oldItems.filter((item) => signature(item.element, false) === key);
3628
+ const after = newItems.filter((item) => signature(item.element, false) === key);
3629
+ if (before.length <= 1 && after.length <= 1) continue;
3630
+ if (before.length !== after.length || before.some((item, index) =>
3631
+ item.slideIndex !== after[index].slideIndex || item.position !== after[index].position))
3632
+ throw new Error("Ambiguous duplicate islands");
3633
+ }
3572
3634
  const pairs = [];
3573
- if (oldIslands.length && oldSlides.length !== newSlides.length) throw new Error("Slide structure changed");
3574
- for (let i = 0; i < oldSlides.length && i < newSlides.length; i++) {
3575
- const before = Array.from(oldSlides[i].querySelectorAll(selector));
3576
- const after = Array.from(newSlides[i].querySelectorAll(selector));
3577
- if (before.length !== after.length) throw new Error("Island structure changed");
3578
- for (let j = 0; j < before.length; j++) {
3579
- if (signature(before[j]) !== signature(after[j])) throw new Error("Island properties changed");
3580
- // Deferred Markdown islands observe their original slide. Reload rather than strand the observer.
3581
- if (before[j].hasAttribute("data-hono-md-island") && !before[j].hasAttribute("data-hono-md-mounted"))
3582
- throw new Error("Island is pending hydration");
3583
- pairs.push([before[j], after[j]]);
3635
+ const remainingOld = new Set(oldItems);
3636
+ const remainingNew = new Set(newItems);
3637
+ function pair(oldItem, newItem) {
3638
+ remainingOld.delete(oldItem);
3639
+ remainingNew.delete(newItem);
3640
+ pairs.push([oldItem, newItem]);
3641
+ }
3642
+ // Exact matches can move between slides. Match only unique instances so
3643
+ // duplicate islands cannot accidentally inherit each other's state.
3644
+ for (const oldItem of oldItems) {
3645
+ const key = signature(oldItem.element, false);
3646
+ const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false) === key);
3647
+ const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false) === key);
3648
+ if (oldMatches.length === 1 && newMatches.length === 1) pair(oldItem, newMatches[0]);
3649
+ }
3650
+ // Changed props can be reconciled if there is only one island of that type.
3651
+ for (const oldItem of Array.from(remainingOld)) {
3652
+ const key = signature(oldItem.element, false, false);
3653
+ const oldMatches = Array.from(remainingOld).filter((item) => signature(item.element, false, false) === key);
3654
+ const newMatches = Array.from(remainingNew).filter((item) => signature(item.element, false, false) === key);
3655
+ if (oldMatches.length === 1 && newMatches.length === 1 &&
3656
+ oldItem.slideIndex === newMatches[0].slideIndex && oldItem.position === newMatches[0].position)
3657
+ pair(oldItem, newMatches[0]);
3658
+ }
3659
+ // Existing duplicate instances remain safe only when their positions and
3660
+ // identity attributes are unchanged; ambiguous structural edits reload.
3661
+ for (const oldItem of Array.from(remainingOld)) {
3662
+ const newItem = Array.from(remainingNew).find((item) => item.slideIndex === oldItem.slideIndex && item.position === oldItem.position);
3663
+ if (newItem && signature(oldItem.element, false) === signature(newItem.element, false)) pair(oldItem, newItem);
3664
+ }
3665
+ for (const oldItem of remainingOld) {
3666
+ const key = signature(oldItem.element, false, false);
3667
+ if (Array.from(remainingNew).some((item) => signature(item.element, false, false) === key))
3668
+ throw new Error("Ambiguous island identity");
3669
+ }
3670
+ const removals = [];
3671
+ for (const oldItem of remainingOld) {
3672
+ const entry = islandUpdaters.get(oldItem.element);
3673
+ if (!entry?.dispose) throw new Error("Cannot safely dispose island");
3674
+ removals.push(entry.dispose);
3675
+ }
3676
+ const updates = [];
3677
+ let preserveSlides = false;
3678
+ for (const [oldItem, newItem] of pairs) {
3679
+ const before = oldItem.element;
3680
+ const after = newItem.element;
3681
+ const pendingMarkdown = before.hasAttribute("data-hono-md-island") && !before.hasAttribute("data-hono-md-mounted");
3682
+ if (pendingMarkdown) {
3683
+ if (oldSlides.length !== newSlides.length || oldItem.slideIndex !== newItem.slideIndex)
3684
+ throw new Error("Pending island moved between slides");
3685
+ preserveSlides = true;
3686
+ }
3687
+ if (signature(before, !pendingMarkdown) !== signature(after, !pendingMarkdown)) {
3688
+ const entry = islandUpdaters.get(before);
3689
+ if (!entry?.update || signature(before, false, false) !== signature(after, false, false))
3690
+ throw new Error("Island properties changed");
3691
+ const markdown = before.hasAttribute("data-hono-md-island");
3692
+ const propsAttribute = markdown ? "data-hono-md-props" : "data-hono-decks-props";
3693
+ const rawProps = after.getAttribute(propsAttribute) || "{}";
3694
+ const props = JSON.parse(rawProps);
3695
+ const context = markdown ? JSON.parse(after.closest("[data-hono-md-context]")?.getAttribute("data-hono-md-context") || "{}") : undefined;
3696
+ updates.push([before, entry.update, propsAttribute, rawProps, markdown ? { props, context } : props]);
3697
+ }
3698
+ }
3699
+ for (const dispose of removals) dispose();
3700
+ for (const [oldItem, newItem] of pairs) newItem.element.replaceWith(oldItem.element);
3701
+ if (preserveSlides) {
3702
+ for (let i = 0; i < oldSlides.length; i++) {
3703
+ const oldSlide = oldSlides[i];
3704
+ const newSlide = newSlides[i];
3705
+ for (const attribute of Array.from(oldSlide.attributes)) oldSlide.removeAttribute(attribute.name);
3706
+ for (const attribute of Array.from(newSlide.attributes)) oldSlide.setAttribute(attribute.name, attribute.value);
3707
+ oldSlide.replaceChildren(...newSlide.childNodes);
3708
+ newSlide.replaceWith(oldSlide);
3584
3709
  }
3585
3710
  }
3586
- if (!oldIslands.length && nextDeck.querySelector(selector)) throw new Error("New islands require initialization");
3587
- for (const [oldIsland, newIsland] of pairs) newIsland.replaceWith(oldIsland);
3588
3711
  currentDeck.replaceChildren(...nextDeck.childNodes);
3712
+ for (const [island, update, propsAttribute, rawProps, value] of updates) {
3713
+ island.setAttribute(propsAttribute, rawProps);
3714
+ update(value);
3715
+ }
3589
3716
  style.textContent = nextStyle.textContent;
3590
3717
  document.title = next.title;
3591
3718
  clientSnapshot = Promise.resolve(nextClient);
3592
3719
  runtime.refresh();
3720
+ if (remainingNew.size) {
3721
+ if (needsEntry) {
3722
+ const url = new URL(nextScript.getAttribute("src"), location.href);
3723
+ if (url.origin !== location.origin) throw new Error("Cross-origin client entry");
3724
+ url.searchParams.set("hono-decks-update", String(Date.now()));
3725
+ const script = document.createElement("script");
3726
+ script.type = "module";
3727
+ script.setAttribute("data-hono-decks-client-entry", "");
3728
+ if (scriptNonce) script.nonce = scriptNonce;
3729
+ script.src = url.href;
3730
+ await new Promise((resolve, reject) => {
3731
+ script.onload = resolve;
3732
+ script.onerror = () => reject(new Error("Client entry failed"));
3733
+ document.body.appendChild(script);
3734
+ });
3735
+ } else {
3736
+ for (const hydrate of hydrators) hydrate();
3737
+ }
3738
+ for (const item of remainingNew) {
3739
+ const element = item.element;
3740
+ if (!element.hasAttribute("data-hono-decks-live-mounted") && !element.hasAttribute("data-hono-md-scheduled"))
3741
+ throw new Error("New island did not initialize");
3742
+ }
3743
+ }
3593
3744
  }
3594
3745
 
3595
3746
  async function onUpdate() {
@@ -3991,12 +4142,15 @@ function renderPresentationScript(nonce) {
3991
4142
  await document.documentElement.requestFullscreen?.();
3992
4143
  }
3993
4144
 
4145
+ const boundSlides = new WeakSet();
3994
4146
  function bindSlideClicks() {
3995
- slides.forEach((slide, slideIndex) => {
4147
+ slides.forEach((slide) => {
4148
+ if (boundSlides.has(slide)) return;
4149
+ boundSlides.add(slide);
3996
4150
  slide.addEventListener("click", () => {
3997
4151
  if (!document.body.hasAttribute("data-overview-mode")) return;
3998
4152
  document.body.removeAttribute("data-overview-mode");
3999
- show(slideIndex);
4153
+ show(slides.indexOf(slide));
4000
4154
  });
4001
4155
  });
4002
4156
  }
package/dist/vite.js CHANGED
@@ -1659,8 +1659,37 @@ async function createHonoMarkdownAdapter(cwd, legacy = false, options) {
1659
1659
  function emitMarkdownClient(islands, runtime) {
1660
1660
  if (!islands.length) return "";
1661
1661
  const literal = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
1662
+ const isVoid = runtime.startsWith("void-plugin-hono-jsx/");
1663
+ const contextModule = isVoid ? "void-plugin-hono-jsx/markdown" : "@ts-76/hono-markdown";
1664
+ const scheduler = isVoid ? "void/pages-client" : "@ts-76/hono-markdown/client";
1665
+ const schedulerName = isVoid ? "scheduleHydration" : "scheduleMarkdownIsland";
1662
1666
  return `import { hydrateMarkdownIslands } from ${literal(runtime)};
1667
+ import { ${schedulerName} as scheduleIsland } from ${literal(scheduler)};
1668
+ import { MarkdownContext } from ${literal(contextModule)};
1669
+ import { jsx as liveJsx } from "hono/jsx/dom/jsx-runtime";
1670
+ import { flushSync as flushLiveSync, useState as useLiveState } from "hono/jsx/dom";
1671
+ function hydrateDeckMarkdownIslands() {
1663
1672
  hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () => import(${literal(island.modulePath)})`).join(",")} }, {
1673
+ schedule(element, loader, props, strategy, mount) {
1674
+ return scheduleIsland(element, loader, props, strategy, (target, module, initialProps) => {
1675
+ const live = window.__honoDecksLive;
1676
+ if (!live || !module || typeof module.default !== 'function') return mount(target, module, initialProps);
1677
+ const Component = module.default;
1678
+ function LiveMarkdownIsland(componentProps) {
1679
+ const [current, setCurrent] = useLiveState({
1680
+ props: componentProps,
1681
+ context: JSON.parse(element.closest('[data-hono-md-context]')?.getAttribute('data-hono-md-context') || '{}'),
1682
+ });
1683
+ const [active, setActive] = useLiveState(true);
1684
+ live.register(element, setCurrent, () => flushLiveSync(() => setActive(false)));
1685
+ return active ? liveJsx(MarkdownContext.Provider, {
1686
+ value: current.context,
1687
+ children: liveJsx(Component, current.props),
1688
+ }) : null;
1689
+ }
1690
+ return mount(target, { ...module, default: LiveMarkdownIsland }, initialProps);
1691
+ });
1692
+ },
1664
1693
  ready(element, start) {
1665
1694
  const slide = element.closest('section[data-slide-state]');
1666
1695
  if (!slide || slide.dataset.slideState === 'active') { start(); return; }
@@ -1669,7 +1698,10 @@ hydrateMarkdownIslands({ ${islands.map((island) => `${literal(island.id)}: () =>
1669
1698
  });
1670
1699
  observer.observe(slide, { attributes: true, attributeFilter: ['data-slide-state'] });
1671
1700
  }
1672
- });`;
1701
+ });
1702
+ }
1703
+ hydrateDeckMarkdownIslands();
1704
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckMarkdownIslands);`;
1673
1705
  }
1674
1706
  //#endregion
1675
1707
  //#region src/node/markdown-adapter.ts
@@ -1718,11 +1750,15 @@ async function emitClientEntryModule(input) {
1718
1750
  import { hydrateSlideIslands } from "hono-decks/client";
1719
1751
  ${imports.join("\n")}
1720
1752
 
1721
- hydrateSlideIslands({
1722
- components: {
1723
- ${registrations.join(",\n ")}
1724
- }
1725
- });
1753
+ function hydrateDeckIslands() {
1754
+ hydrateSlideIslands({
1755
+ components: {
1756
+ ${registrations.join(",\n ")}
1757
+ }
1758
+ });
1759
+ }
1760
+ hydrateDeckIslands();
1761
+ if (window.__honoDecksLive) window.__honoDecksLive.registerHydrator(hydrateDeckIslands);
1726
1762
  `,
1727
1763
  resolveDir: input.cwd,
1728
1764
  sourcefile: "hono-decks-client-entry.tsx",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hono-decks",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Slide decks for Hono and Cloudflare Workers",
5
5
  "keywords": [
6
6
  "cloudflare-workers",