@moku-labs/web 1.16.1 → 2.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/dist/index.cjs CHANGED
@@ -8039,17 +8039,22 @@ function createApi$1(ctx) {
8039
8039
  * @example
8040
8040
  * await api.update(["src/islands/board.ts"]);
8041
8041
  */
8042
- update(changes, options = {}) {
8042
+ async update(changes, options = {}) {
8043
8043
  const overrides = devBuildOverrides({
8044
8044
  og: options.og ?? false,
8045
8045
  sitemap: options.sitemap ?? false,
8046
8046
  feeds: options.feeds ?? false
8047
8047
  });
8048
- return ctx.require(buildPlugin).run({
8049
- skipClean: true,
8050
- overrides,
8051
- changed: changes
8052
- });
8048
+ ctx.state.render.setDriven(true);
8049
+ try {
8050
+ return await ctx.require(buildPlugin).run({
8051
+ skipClean: true,
8052
+ overrides,
8053
+ changed: changes
8054
+ });
8055
+ } finally {
8056
+ ctx.state.render.setDriven(false);
8057
+ }
8053
8058
  },
8054
8059
  /**
8055
8060
  * Dev loop: build once, serve `dist/` in-process (live-reload injected), watch
@@ -8342,6 +8347,7 @@ function createPanelRenderer(options = {}) {
8342
8347
  let idle = false;
8343
8348
  let idleStartedAt = 0;
8344
8349
  let serveMode = false;
8350
+ let driven = false;
8345
8351
  let ticker;
8346
8352
  /**
8347
8353
  * Render one phase-tree row: a spinning cyan glyph + dim name while running, or a green
@@ -8503,7 +8509,7 @@ function createPanelRenderer(options = {}) {
8503
8509
  * render.phase({ phase: "pages", status: "done", durationMs: 12 });
8504
8510
  */
8505
8511
  phase(phase) {
8506
- if (rebuilding) return;
8512
+ if (rebuilding || driven) return;
8507
8513
  if (!color) {
8508
8514
  if (phase.status === "done") write(` ${palette.green("✓")} ${phase.phase}${durationSuffix(palette, phase.durationMs)}`);
8509
8515
  return;
@@ -8537,7 +8543,7 @@ function createPanelRenderer(options = {}) {
8537
8543
  * render.built({ outDir: "dist", pageCount: 12, durationMs: 840 });
8538
8544
  */
8539
8545
  built(summary) {
8540
- if (rebuilding) return;
8546
+ if (rebuilding || driven) return;
8541
8547
  if (color && phaseOpen) {
8542
8548
  let frame = (0, _moku_labs_common_cli.cursorUp)(phaseDrawn);
8543
8549
  for (const row of phaseRows) frame += `${_moku_labs_common_cli.CLEAR_LINE}${renderPhaseRow(row)}\n`;
@@ -8711,6 +8717,20 @@ function createPanelRenderer(options = {}) {
8711
8717
  if (detail !== void 0) for (const line of detail.split("\n")) write(` ${palette.dim(line)}`);
8712
8718
  },
8713
8719
  /**
8720
+ * Mark the build TUI as externally driven: when `on`, the per-phase build tree and the BUILD
8721
+ * summary are suppressed so an external dev driver (e.g. the worker's `dev({ onChange })` loop,
8722
+ * which calls `update()` and renders its own concise rebuild line) is the sole source of rebuild
8723
+ * output. Off by default; a standalone `build()` / `serve()` renders the full TUI as before.
8724
+ *
8725
+ * @param on - Whether an external driver owns the dev TUI.
8726
+ * @example
8727
+ * render.setDriven(true); // before an externally-driven update()
8728
+ * render.setDriven(false); // after it settles
8729
+ */
8730
+ setDriven(on) {
8731
+ driven = on;
8732
+ },
8733
+ /**
8714
8734
  * Stop every animation and release the interval timer (serve()'s teardown calls this).
8715
8735
  *
8716
8736
  * @example
@@ -9127,15 +9147,15 @@ const cliPlugin = createPlugin$1("cli", {
9127
9147
  function createApi(ctx) {
9128
9148
  return {
9129
9149
  /**
9130
- * Register a component definition (last-registered-wins); warns on collision.
9150
+ * Register a island definition (last-registered-wins); warns on collision.
9131
9151
  *
9132
- * @param component - The component definition created via `createComponent`.
9152
+ * @param island - The island definition created via `createIsland`.
9133
9153
  * @example
9134
9154
  * app.spa.register(counter);
9135
9155
  */
9136
- register(component) {
9137
- if (ctx.state.registeredComponents.has(component.name)) ctx.log.warn("spa:component-collision", { name: component.name });
9138
- ctx.state.kernel?.register(component);
9156
+ register(island) {
9157
+ if (ctx.state.registeredIslands.has(island.name)) ctx.log.warn("spa:island-collision", { name: island.name });
9158
+ ctx.state.kernel?.register(island);
9139
9159
  },
9140
9160
  /**
9141
9161
  * Programmatically navigate to a path (client runtime; no-op without a DOM).
@@ -9161,13 +9181,13 @@ function createApi(ctx) {
9161
9181
  * Resolve a registered island's api by name (the cross-island seam). Returns
9162
9182
  * `undefined` when no provider with that name is currently registered.
9163
9183
  *
9164
- * @param name - The provider island's component name.
9184
+ * @param name - The provider island's island name.
9165
9185
  * @returns The provider's api, or `undefined`.
9166
9186
  * @example
9167
- * app.spa.component("lightbox");
9187
+ * app.spa.island("lightbox");
9168
9188
  */
9169
- component(name) {
9170
- return ctx.state.componentApis.get(name);
9189
+ island(name) {
9190
+ return ctx.state.islandApis.get(name);
9171
9191
  }
9172
9192
  };
9173
9193
  }
@@ -9186,15 +9206,89 @@ function spaEvents(register) {
9186
9206
  return {
9187
9207
  "spa:navigate": register("A navigation has been intercepted and is starting."),
9188
9208
  "spa:navigated": register("The swap completed and the new URL is active."),
9189
- "spa:component-mount": register("A component instance attached to an element."),
9190
- "spa:component-unmount": register("A component instance detached from an element.")
9209
+ "spa:island-mount": register("A island instance attached to an element."),
9210
+ "spa:island-unmount": register("A island instance detached from an element.")
9191
9211
  };
9192
9212
  }
9193
9213
  //#endregion
9214
+ //#region src/plugins/spa/head.ts
9215
+ /** Single-element head selectors synced by replace/append/remove on navigation. */
9216
+ const META_SELECTORS = [
9217
+ "meta[name=\"description\"]",
9218
+ "meta[property=\"og:title\"]",
9219
+ "meta[property=\"og:description\"]",
9220
+ "meta[property=\"og:url\"]",
9221
+ "meta[property=\"og:image\"]",
9222
+ "meta[property=\"og:type\"]",
9223
+ "meta[property=\"og:locale\"]",
9224
+ "meta[name=\"twitter:card\"]",
9225
+ "meta[name=\"twitter:title\"]",
9226
+ "meta[name=\"twitter:description\"]",
9227
+ "meta[name=\"twitter:image\"]",
9228
+ "meta[name=\"twitter:site\"]",
9229
+ "link[rel=\"canonical\"]"
9230
+ ];
9231
+ /** Head element groups fully replaced (remove-all-then-clone) on navigation. */
9232
+ const REPLACE_ALL_SELECTORS = [
9233
+ "script[type=\"application/ld+json\"]",
9234
+ "link[rel=\"alternate\"][hreflang]",
9235
+ "meta[property^=\"article:\"]"
9236
+ ];
9237
+ /**
9238
+ * Sync a single head element by selector between the fetched and live document:
9239
+ * replace when both exist, append when only the new doc has it, remove when only
9240
+ * the live doc has it.
9241
+ *
9242
+ * @param selector - CSS selector for the head element to sync.
9243
+ * @param doc - The fetched document (DOMParser-parsed).
9244
+ * @example
9245
+ * syncElement('link[rel="canonical"]', doc);
9246
+ */
9247
+ function syncElement(selector, doc) {
9248
+ const newElement = doc.querySelector(selector);
9249
+ const oldElement = document.querySelector(selector);
9250
+ if (newElement && oldElement) oldElement.replaceWith(newElement.cloneNode(true));
9251
+ else if (newElement) document.head.append(newElement.cloneNode(true));
9252
+ else if (oldElement) oldElement.remove();
9253
+ }
9254
+ /**
9255
+ * Remove all live matches for a selector and re-clone the fetched document's
9256
+ * matches into the live `<head>`.
9257
+ *
9258
+ * @param selector - CSS selector for the element group to replace wholesale.
9259
+ * @param doc - The fetched document (DOMParser-parsed).
9260
+ * @example
9261
+ * replaceAllBySelector('script[type="application/ld+json"]', doc);
9262
+ */
9263
+ function replaceAllBySelector(selector, doc) {
9264
+ for (const element of document.querySelectorAll(selector)) element.remove();
9265
+ for (const element of doc.querySelectorAll(selector)) document.head.append(element.cloneNode(true));
9266
+ }
9267
+ /**
9268
+ * Syncs the live document `<head>` after a navigation from the fetched document
9269
+ * (whose head was composed by the `head` plugin). Recomputes
9270
+ * title/meta/canonical/JSON-LD/hreflang/`<html lang>` once and applies them.
9271
+ * The `head` API is accepted to bind the structural dependency (spec/09 deps).
9272
+ *
9273
+ * @param _head - The head plugin API (dependency binding; composition reused via the fetched doc).
9274
+ * @param doc - The fetched document parsed from the navigated page's HTML.
9275
+ * @example
9276
+ * syncHead(headApi, parsedDoc);
9277
+ */
9278
+ function syncHead(_head, doc) {
9279
+ if (typeof document === "undefined") return;
9280
+ const newTitle = doc.querySelector("title")?.textContent;
9281
+ if (newTitle) document.title = newTitle;
9282
+ const newLang = doc.documentElement.lang;
9283
+ if (newLang) document.documentElement.lang = newLang;
9284
+ for (const selector of META_SELECTORS) syncElement(selector, doc);
9285
+ for (const selector of REPLACE_ALL_SELECTORS) replaceAllBySelector(selector, doc);
9286
+ }
9287
+ //#endregion
9194
9288
  //#region src/plugins/spa/types.ts
9195
- var types_exports$7 = /* @__PURE__ */ require_convention.__exportAll({ COMPONENT_HOOK_NAMES: () => COMPONENT_HOOK_NAMES });
9289
+ var types_exports$7 = /* @__PURE__ */ require_convention.__exportAll({ ISLAND_HOOK_NAMES: () => ISLAND_HOOK_NAMES });
9196
9290
  /** Allowed hook names — single source of truth for fail-fast validation. */
9197
- const COMPONENT_HOOK_NAMES = [
9291
+ const ISLAND_HOOK_NAMES = [
9198
9292
  "onCreate",
9199
9293
  "onMount",
9200
9294
  "onNavStart",
@@ -9203,10 +9297,10 @@ const COMPONENT_HOOK_NAMES = [
9203
9297
  "onDestroy"
9204
9298
  ];
9205
9299
  //#endregion
9206
- //#region src/plugins/spa/components.ts
9300
+ //#region src/plugins/spa/islands.ts
9207
9301
  /**
9208
- * @file spa plugin — component lifecycle, mounting, the plugin-mirror authoring
9209
- * surface (`createComponent` with a typed `{ state, render, events, api }` spec),
9302
+ * @file spa plugin — island lifecycle, mounting, the plugin-mirror authoring
9303
+ * surface (`createIsland` with a typed `{ state, render, events, api }` spec),
9210
9304
  * the per-instance state + microtask-batched render scheduler, declarative
9211
9305
  * delegated events, and the cross-island api registry.
9212
9306
  * @see README.md
@@ -9214,8 +9308,8 @@ const COMPONENT_HOOK_NAMES = [
9214
9308
  /** Error prefix for spa fail-fast failures (spec/11 Part-3). */
9215
9309
  const ERROR_PREFIX$2 = "[web]";
9216
9310
  /** The set of legal hook names, frozen for O(1) membership checks. */
9217
- const HOOK_NAME_SET = new Set(COMPONENT_HOOK_NAMES);
9218
- /** The spec-only keys that select the plugin-mirror form of {@link createComponent}. */
9311
+ const HOOK_NAME_SET = new Set(ISLAND_HOOK_NAMES);
9312
+ /** The spec-only keys that select the plugin-mirror form of {@link createIsland}. */
9219
9313
  const SPEC_KEYS = new Set([
9220
9314
  "state",
9221
9315
  "render",
@@ -9254,7 +9348,7 @@ let renderChunk;
9254
9348
  let commitVNodeFunction;
9255
9349
  /**
9256
9350
  * Load the lazy `./render` chunk (once) and cache its `commitVNode` for synchronous
9257
- * use by later renders. Awaited by a component's `mountPromise` so the test harness's
9351
+ * use by later renders. Awaited by a island's `mountPromise` so the test harness's
9258
9352
  * `settle()` can deterministically flush a VNode render.
9259
9353
  *
9260
9354
  * @returns A promise that resolves once `commitVNode` is available.
@@ -9262,7 +9356,7 @@ let commitVNodeFunction;
9262
9356
  * await loadRenderChunk();
9263
9357
  */
9264
9358
  async function loadRenderChunk() {
9265
- renderChunk ??= Promise.resolve().then(() => require("./render-KdufA3_b.cjs"));
9359
+ renderChunk ??= Promise.resolve().then(() => require("./render-DHUcHCYs.cjs"));
9266
9360
  commitVNodeFunction = (await renderChunk).commitVNode;
9267
9361
  }
9268
9362
  /**
@@ -9271,7 +9365,7 @@ async function loadRenderChunk() {
9271
9365
  * a Preact `VNode` → committed through the lazy gate (loading it on demand if needed).
9272
9366
  *
9273
9367
  * @param host - The island host element to render into.
9274
- * @param result - The value returned by the component's `render`.
9368
+ * @param result - The value returned by the island's `render`.
9275
9369
  * @example
9276
9370
  * commitResult(host, h(View, { items }));
9277
9371
  */
@@ -9293,7 +9387,7 @@ function commitResult(host, result) {
9293
9387
  loadRenderChunk().then(() => commitVNodeFunction?.(vnode, host)).catch(() => {});
9294
9388
  }
9295
9389
  /**
9296
- * Run a component's `render(state, ctx)` and commit the result now. Guards against
9390
+ * Run a island's `render(state, ctx)` and commit the result now. Guards against
9297
9391
  * synchronous re-entrancy (a render that calls `ctx.flush`) with a depth cap.
9298
9392
  *
9299
9393
  * @param instance - The instance to render.
@@ -9304,7 +9398,7 @@ function commitResult(host, result) {
9304
9398
  function runRender(instance) {
9305
9399
  const render = instance.def.spec?.render;
9306
9400
  if (!render) return;
9307
- if (instance.renderDepth > MAX_RENDER_DEPTH) throw new Error(`${ERROR_PREFIX$2} component "${instance.def.name}" render re-entered ${MAX_RENDER_DEPTH}+ times\n → a render must not synchronously trigger its own render (avoid ctx.flush() inside render)`);
9401
+ if (instance.renderDepth > MAX_RENDER_DEPTH) throw new Error(`${ERROR_PREFIX$2} island "${instance.def.name}" render re-entered ${MAX_RENDER_DEPTH}+ times\n → a render must not synchronously trigger its own render (avoid ctx.flush() inside render)`);
9308
9402
  instance.renderDepth += 1;
9309
9403
  try {
9310
9404
  commitResult(instance.el, render(instance.state ?? {}, instance.ctx));
@@ -9330,12 +9424,12 @@ function scheduleRender(instance) {
9330
9424
  });
9331
9425
  }
9332
9426
  /**
9333
- * Build the single per-instance {@link ComponentContext} reused by every hook, event
9427
+ * Build the single per-instance {@link IslandContext} reused by every hook, event
9334
9428
  * handler, and render. Route fields (`params`/`meta`/`locale`/`url`) and `data` read
9335
9429
  * through the instance so a navigation update is reflected without rebuilding the ctx;
9336
- * `state`/`set`/`flush`/`cleanup`/`component` are bound to the instance + plugin state.
9430
+ * `state`/`set`/`flush`/`cleanup`/`island` are bound to the instance + plugin state.
9337
9431
  *
9338
- * @param state - The plugin state (for the cross-island `component` resolver).
9432
+ * @param state - The plugin state (for the cross-island `island` resolver).
9339
9433
  * @param instance - The instance the context is bound to.
9340
9434
  * @returns The instance-bound context.
9341
9435
  * @example
@@ -9439,13 +9533,13 @@ function buildContext(state, instance) {
9439
9533
  /**
9440
9534
  * Resolve another island's registered api by name (`undefined` when absent).
9441
9535
  *
9442
- * @param name - The provider island's component name.
9536
+ * @param name - The provider island's island name.
9443
9537
  * @returns The provider's api, or `undefined`.
9444
9538
  * @example
9445
- * ctx.component("lightbox");
9539
+ * ctx.island("lightbox");
9446
9540
  */
9447
- component(name) {
9448
- return state.componentApis.get(name);
9541
+ island(name) {
9542
+ return state.islandApis.get(name);
9449
9543
  }
9450
9544
  };
9451
9545
  }
@@ -9469,7 +9563,7 @@ function matchTarget(host, event, selector) {
9469
9563
  return matched && host.contains(matched) ? matched : void 0;
9470
9564
  }
9471
9565
  /**
9472
- * Attach a component's declarative `events` map: one real listener per event TYPE on
9566
+ * Attach a island's declarative `events` map: one real listener per event TYPE on
9473
9567
  * the host (dispatch walks `closest(selector)` for each registered selector), each
9474
9568
  * removed via the instance's cleanup registry on destroy.
9475
9569
  *
@@ -9486,7 +9580,7 @@ function attachEvents(instance, events) {
9486
9580
  const space = key.indexOf(" ");
9487
9581
  const type = (space === -1 ? key : key.slice(0, space)).trim();
9488
9582
  const selector = space === -1 ? "" : key.slice(space + 1).trim();
9489
- if (type === "") throw new Error(`${ERROR_PREFIX$2} component "${instance.def.name}" event key must start with an event type: "${key}"\n → use "<type>" or "<type> <selector>" (e.g. "click [data-action]")`);
9583
+ if (type === "") throw new Error(`${ERROR_PREFIX$2} island "${instance.def.name}" event key must start with an event type: "${key}"\n → use "<type>" or "<type> <selector>" (e.g. "click [data-action]")`);
9490
9584
  const list = byType.get(type) ?? [];
9491
9585
  list.push({
9492
9586
  selector,
@@ -9509,62 +9603,62 @@ function attachEvents(instance, events) {
9509
9603
  * Validate a single hook entry: its key must be a known hook name and its value
9510
9604
  * must be a function. Throws fail-fast on the first violation.
9511
9605
  *
9512
- * @param componentName - The owning component name (for error messages).
9606
+ * @param islandName - The owning island name (for error messages).
9513
9607
  * @param source - The raw authoring object being validated.
9514
9608
  * @param key - The hook key to validate.
9515
- * @throws {Error} If `key` is not in `COMPONENT_HOOK_NAMES`.
9609
+ * @throws {Error} If `key` is not in `ISLAND_HOOK_NAMES`.
9516
9610
  * @throws {TypeError} If the hook value is not a function.
9517
9611
  * @example
9518
9612
  * validateHookEntry("counter", source, "onMount");
9519
9613
  */
9520
- function validateHookEntry(componentName, source, key) {
9521
- if (!HOOK_NAME_SET.has(key)) throw new Error(`${ERROR_PREFIX$2} unknown component hook "${key}" on "${componentName}"\n → valid hooks: ${COMPONENT_HOOK_NAMES.join(", ")}\n → spec keys: state, render, events, api`);
9522
- if (typeof source[key] !== "function") throw new TypeError(`${ERROR_PREFIX$2} component hook "${key}" on "${componentName}" must be a function\n → provide a function or omit the hook`);
9614
+ function validateHookEntry(islandName, source, key) {
9615
+ if (!HOOK_NAME_SET.has(key)) throw new Error(`${ERROR_PREFIX$2} unknown island hook "${key}" on "${islandName}"\n → valid hooks: ${ISLAND_HOOK_NAMES.join(", ")}\n → spec keys: state, render, events, api`);
9616
+ if (typeof source[key] !== "function") throw new TypeError(`${ERROR_PREFIX$2} island hook "${key}" on "${islandName}" must be a function\n → provide a function or omit the hook`);
9523
9617
  }
9524
9618
  /**
9525
9619
  * Validate the spec extras (`state`/`render`/`api` must be functions; `events` must be
9526
9620
  * a plain object of functions). Throws fail-fast on the first violation.
9527
9621
  *
9528
- * @param componentName - The owning component name (for error messages).
9622
+ * @param islandName - The owning island name (for error messages).
9529
9623
  * @param extras - The partitioned spec extras to validate.
9530
9624
  * @throws {TypeError} If a present extra has the wrong shape.
9531
9625
  * @example
9532
9626
  * validateSpecExtras("board", { state: () => ({}) });
9533
9627
  */
9534
- function validateSpecExtras(componentName, extras) {
9628
+ function validateSpecExtras(islandName, extras) {
9535
9629
  for (const key of [
9536
9630
  "state",
9537
9631
  "render",
9538
9632
  "api"
9539
- ]) if (extras[key] !== void 0 && typeof extras[key] !== "function") throw new TypeError(`${ERROR_PREFIX$2} component "${key}" on "${componentName}" must be a function\n → provide a function or omit it`);
9633
+ ]) if (extras[key] !== void 0 && typeof extras[key] !== "function") throw new TypeError(`${ERROR_PREFIX$2} island "${key}" on "${islandName}" must be a function\n → provide a function or omit it`);
9540
9634
  if (extras.events !== void 0) {
9541
9635
  const events = extras.events;
9542
- if (!(typeof events === "object")) throw new TypeError(`${ERROR_PREFIX$2} component "events" on "${componentName}" must be an object of handlers`);
9543
- for (const [key, handler] of Object.entries(events)) if (typeof handler !== "function") throw new TypeError(`${ERROR_PREFIX$2} component event "${key}" on "${componentName}" must be a function`);
9636
+ if (!(typeof events === "object")) throw new TypeError(`${ERROR_PREFIX$2} island "events" on "${islandName}" must be an object of handlers`);
9637
+ for (const [key, handler] of Object.entries(events)) if (typeof handler !== "function") throw new TypeError(`${ERROR_PREFIX$2} island event "${key}" on "${islandName}" must be a function`);
9544
9638
  }
9545
9639
  }
9546
9640
  /**
9547
- * Create a validated component definition. Accepts either the legacy hooks-only form
9548
- * (`createComponent("counter", { onMount() {} })`) or the plugin-mirror spec form
9549
- * (`createComponent("board", { state, render, events, api, ...hooks })`). Spec-only
9641
+ * Create a validated island definition. Accepts either the legacy hooks-only form
9642
+ * (`createIsland("counter", { onMount() {} })`) or the plugin-mirror spec form
9643
+ * (`createIsland("board", { state, render, events, api, ...hooks })`). Spec-only
9550
9644
  * keys (`state`/`render`/`events`/`api`) are partitioned out before hook-name
9551
9645
  * validation, so a real typo (e.g. `onMout`) still throws immediately while the spec
9552
9646
  * keys are accepted.
9553
9647
  *
9554
- * @param name - Unique component name.
9648
+ * @param name - Unique island name.
9555
9649
  * @param spec - Lifecycle hooks, or the `{ state, render, events, api, ...hooks }` spec.
9556
- * @returns A `ComponentDef` ready to `register`.
9650
+ * @returns A `IslandDef` ready to `register`.
9557
9651
  * @throws {Error} If `name` is empty, a hook key is unknown, or an extra/hook value has the wrong shape.
9558
9652
  * @example
9559
- * const counter = createComponent("counter", { onMount({ el }) { el.textContent = "0"; } });
9653
+ * const counter = createIsland("counter", { onMount({ el }) { el.textContent = "0"; } });
9560
9654
  * @example
9561
- * const list = createComponent<{ items: string[] }>("list", {
9655
+ * const list = createIsland<{ items: string[] }>("list", {
9562
9656
  * state: () => ({ items: [] }),
9563
9657
  * render: (s) => h(List, { items: s.items })
9564
9658
  * });
9565
9659
  */
9566
- function createComponent(name, spec) {
9567
- if (name.trim() === "") throw new Error(`${ERROR_PREFIX$2} component name must be a non-empty string\n → pass a unique name to createComponent("name", hooks)`);
9660
+ function createIsland(name, spec) {
9661
+ if (name.trim() === "") throw new Error(`${ERROR_PREFIX$2} island name must be a non-empty string\n → pass a unique name to createIsland("name", hooks)`);
9568
9662
  const source = spec;
9569
9663
  const hooks = {};
9570
9664
  const extras = {};
@@ -9641,30 +9735,30 @@ function disposeInstance(state, instance) {
9641
9735
  } catch {}
9642
9736
  instance.cleanups.length = 0;
9643
9737
  instance.renderScheduled = false;
9644
- if (instance.api !== void 0 && state.componentApis.get(instance.def.name) === instance.api) state.componentApis.delete(instance.def.name);
9738
+ if (instance.api !== void 0 && state.islandApis.get(instance.def.name) === instance.api) state.islandApis.delete(instance.def.name);
9645
9739
  }
9646
9740
  /**
9647
- * Mounts a single `data-component` element: classifies persistent vs page-specific,
9741
+ * Mounts a single `data-island` element: classifies persistent vs page-specific,
9648
9742
  * builds the instance + its bound context, initializes per-instance `state`, registers
9649
9743
  * its `api`, attaches declarative `events`, fires `onCreate` then `onMount` (capturing
9650
9744
  * an async `onMount` + render-chunk load as `mountPromise`), schedules the initial
9651
- * render, records it, and emits `spa:component-mount`. No-ops if the element is already
9652
- * mounted, has no component name, or names an unregistered component.
9745
+ * render, records it, and emits `spa:island-mount`. No-ops if the element is already
9746
+ * mounted, has no island name, or names an unregistered island.
9653
9747
  *
9654
- * @param state - The plugin state (registeredComponents + instances + componentApis).
9655
- * @param emit - The event emitter for spa:component-mount.
9748
+ * @param state - The plugin state (registeredIslands + instances + islandApis).
9749
+ * @param emit - The event emitter for spa:island-mount.
9656
9750
  * @param swapArea - The swap-region element, or null when none was found.
9657
9751
  * @param data - The current page data payload.
9658
- * @param element - The candidate element carrying a `data-component` attribute.
9752
+ * @param element - The candidate element carrying a `data-island` attribute.
9659
9753
  * @param route - The matched-route slice for the current URL (params/meta/locale/url).
9660
9754
  * @example
9661
9755
  * mountElement(state, emit, swapArea, data, element, route);
9662
9756
  */
9663
9757
  function mountElement(state, emit, swapArea, data, element, route = EMPTY_ROUTE) {
9664
9758
  if (state.instances.has(element)) return;
9665
- const name = element.dataset.component;
9759
+ const name = element.dataset.island;
9666
9760
  if (!name) return;
9667
- const definition = state.registeredComponents.get(name);
9761
+ const definition = state.registeredIslands.get(name);
9668
9762
  if (!definition) return;
9669
9763
  const instance = {
9670
9764
  def: definition,
@@ -9690,7 +9784,7 @@ function mountElement(state, emit, swapArea, data, element, route = EMPTY_ROUTE)
9690
9784
  if (spec?.state) instance.state = spec.state(instance.ctx);
9691
9785
  if (spec?.api) {
9692
9786
  instance.api = spec.api(instance.ctx);
9693
- state.componentApis.set(definition.name, instance.api);
9787
+ state.islandApis.set(definition.name, instance.api);
9694
9788
  }
9695
9789
  if (spec?.events) attachEvents(instance, spec.events);
9696
9790
  runHook(instance, "onCreate");
@@ -9701,20 +9795,20 @@ function mountElement(state, emit, swapArea, data, element, route = EMPTY_ROUTE)
9701
9795
  if (onMountResult && typeof onMountResult.then === "function") pending.push(onMountResult);
9702
9796
  instance.mountPromise = pending.length > 0 ? Promise.all(pending).then(() => {}) : void 0;
9703
9797
  state.instances.set(element, instance);
9704
- emit("spa:component-mount", {
9798
+ emit("spa:island-mount", {
9705
9799
  name: definition.name,
9706
9800
  el: element
9707
9801
  });
9708
9802
  }
9709
9803
  /**
9710
- * Scans the swap region, mounts components for matching `data-component` elements,
9804
+ * Scans the swap region, mounts islands for matching `data-island` elements,
9711
9805
  * classifies persistent (outside swap area) vs page-specific (inside), runs
9712
- * `onCreate`/`onMount` + initial render, and emits `spa:component-mount` per instance.
9806
+ * `onCreate`/`onMount` + initial render, and emits `spa:island-mount` per instance.
9713
9807
  * Already-mounted elements are skipped.
9714
9808
  *
9715
- * @param state - The plugin state (registeredComponents + instances + componentApis).
9716
- * @param emit - The event emitter for spa:component-mount.
9717
- * @param swapSelector - CSS selector bounding page-specific components.
9809
+ * @param state - The plugin state (registeredIslands + instances + islandApis).
9810
+ * @param emit - The event emitter for spa:island-mount.
9811
+ * @param swapSelector - CSS selector bounding page-specific islands.
9718
9812
  * @param route - The matched-route slice for the current URL (params/meta/locale/url).
9719
9813
  * @example
9720
9814
  * scanAndMount(state, emit, "main > section", route);
@@ -9723,16 +9817,16 @@ function scanAndMount(state, emit, swapSelector, route = EMPTY_ROUTE) {
9723
9817
  if (typeof document === "undefined") return;
9724
9818
  const swapArea = document.querySelector(swapSelector);
9725
9819
  const data = extractPageData(document);
9726
- for (const element of document.querySelectorAll("[data-component]")) mountElement(state, emit, swapArea, data, element, route);
9820
+ for (const element of document.querySelectorAll("[data-island]")) mountElement(state, emit, swapArea, data, element, route);
9727
9821
  }
9728
9822
  /**
9729
9823
  * Unmounts page-specific instances inside the swap region (runs `onUnMount` then
9730
9824
  * `onDestroy`, then their cleanup disposers + api unregister), removes them from state,
9731
- * and emits `spa:component-unmount`. Persistent instances (outside the swap area) are
9825
+ * and emits `spa:island-unmount`. Persistent instances (outside the swap area) are
9732
9826
  * left in place.
9733
9827
  *
9734
9828
  * @param state - The plugin state holding live instances.
9735
- * @param emit - The event emitter for spa:component-unmount.
9829
+ * @param emit - The event emitter for spa:island-unmount.
9736
9830
  * @example
9737
9831
  * unmountPageSpecific(state, emit);
9738
9832
  */
@@ -9745,7 +9839,7 @@ function unmountPageSpecific(state, emit) {
9745
9839
  runHook(instance, "onDestroy");
9746
9840
  disposeInstance(state, instance);
9747
9841
  state.instances.delete(element);
9748
- emit("spa:component-unmount", {
9842
+ emit("spa:island-unmount", {
9749
9843
  name: instance.def.name,
9750
9844
  el: element
9751
9845
  });
@@ -9754,11 +9848,11 @@ function unmountPageSpecific(state, emit) {
9754
9848
  /**
9755
9849
  * Disposes ALL live instances (persistent and page-specific) on teardown: runs
9756
9850
  * `onUnMount` then `onDestroy`, then their cleanup disposers + api unregister, emits
9757
- * `spa:component-unmount`, and clears the instance + api maps. Used by the kernel's
9851
+ * `spa:island-unmount`, and clears the instance + api maps. Used by the kernel's
9758
9852
  * `dispose` on plugin stop.
9759
9853
  *
9760
9854
  * @param state - The plugin state holding live instances.
9761
- * @param emit - The event emitter for spa:component-unmount.
9855
+ * @param emit - The event emitter for spa:island-unmount.
9762
9856
  * @example
9763
9857
  * unmountAll(state, emit);
9764
9858
  */
@@ -9769,13 +9863,13 @@ function unmountAll(state, emit) {
9769
9863
  runHook(instance, "onUnMount");
9770
9864
  runHook(instance, "onDestroy");
9771
9865
  disposeInstance(state, instance);
9772
- emit("spa:component-unmount", {
9866
+ emit("spa:island-unmount", {
9773
9867
  name: instance.def.name,
9774
9868
  el: element
9775
9869
  });
9776
9870
  }
9777
9871
  state.instances.clear();
9778
- state.componentApis.clear();
9872
+ state.islandApis.clear();
9779
9873
  }
9780
9874
  /**
9781
9875
  * Fires `onNavStart` on every currently-mounted instance (persistent instances
@@ -9812,80 +9906,6 @@ function notifyNavEnd(state, route = EMPTY_ROUTE) {
9812
9906
  }
9813
9907
  }
9814
9908
  //#endregion
9815
- //#region src/plugins/spa/head.ts
9816
- /** Single-element head selectors synced by replace/append/remove on navigation. */
9817
- const META_SELECTORS = [
9818
- "meta[name=\"description\"]",
9819
- "meta[property=\"og:title\"]",
9820
- "meta[property=\"og:description\"]",
9821
- "meta[property=\"og:url\"]",
9822
- "meta[property=\"og:image\"]",
9823
- "meta[property=\"og:type\"]",
9824
- "meta[property=\"og:locale\"]",
9825
- "meta[name=\"twitter:card\"]",
9826
- "meta[name=\"twitter:title\"]",
9827
- "meta[name=\"twitter:description\"]",
9828
- "meta[name=\"twitter:image\"]",
9829
- "meta[name=\"twitter:site\"]",
9830
- "link[rel=\"canonical\"]"
9831
- ];
9832
- /** Head element groups fully replaced (remove-all-then-clone) on navigation. */
9833
- const REPLACE_ALL_SELECTORS = [
9834
- "script[type=\"application/ld+json\"]",
9835
- "link[rel=\"alternate\"][hreflang]",
9836
- "meta[property^=\"article:\"]"
9837
- ];
9838
- /**
9839
- * Sync a single head element by selector between the fetched and live document:
9840
- * replace when both exist, append when only the new doc has it, remove when only
9841
- * the live doc has it.
9842
- *
9843
- * @param selector - CSS selector for the head element to sync.
9844
- * @param doc - The fetched document (DOMParser-parsed).
9845
- * @example
9846
- * syncElement('link[rel="canonical"]', doc);
9847
- */
9848
- function syncElement(selector, doc) {
9849
- const newElement = doc.querySelector(selector);
9850
- const oldElement = document.querySelector(selector);
9851
- if (newElement && oldElement) oldElement.replaceWith(newElement.cloneNode(true));
9852
- else if (newElement) document.head.append(newElement.cloneNode(true));
9853
- else if (oldElement) oldElement.remove();
9854
- }
9855
- /**
9856
- * Remove all live matches for a selector and re-clone the fetched document's
9857
- * matches into the live `<head>`.
9858
- *
9859
- * @param selector - CSS selector for the element group to replace wholesale.
9860
- * @param doc - The fetched document (DOMParser-parsed).
9861
- * @example
9862
- * replaceAllBySelector('script[type="application/ld+json"]', doc);
9863
- */
9864
- function replaceAllBySelector(selector, doc) {
9865
- for (const element of document.querySelectorAll(selector)) element.remove();
9866
- for (const element of doc.querySelectorAll(selector)) document.head.append(element.cloneNode(true));
9867
- }
9868
- /**
9869
- * Syncs the live document `<head>` after a navigation from the fetched document
9870
- * (whose head was composed by the `head` plugin). Recomputes
9871
- * title/meta/canonical/JSON-LD/hreflang/`<html lang>` once and applies them.
9872
- * The `head` API is accepted to bind the structural dependency (spec/09 deps).
9873
- *
9874
- * @param _head - The head plugin API (dependency binding; composition reused via the fetched doc).
9875
- * @param doc - The fetched document parsed from the navigated page's HTML.
9876
- * @example
9877
- * syncHead(headApi, parsedDoc);
9878
- */
9879
- function syncHead(_head, doc) {
9880
- if (typeof document === "undefined") return;
9881
- const newTitle = doc.querySelector("title")?.textContent;
9882
- if (newTitle) document.title = newTitle;
9883
- const newLang = doc.documentElement.lang;
9884
- if (newLang) document.documentElement.lang = newLang;
9885
- for (const selector of META_SELECTORS) syncElement(selector, doc);
9886
- for (const selector of REPLACE_ALL_SELECTORS) replaceAllBySelector(selector, doc);
9887
- }
9888
- //#endregion
9889
9909
  //#region src/plugins/spa/progress.ts
9890
9910
  /** Delay before the bar appears, so fast navigations show no indicator. */
9891
9911
  const START_DELAY_MS = 150;
@@ -10292,7 +10312,7 @@ const defaultSpaConfig = {
10292
10312
  swapSelector: "main > section",
10293
10313
  viewTransitions: false,
10294
10314
  progressBar: true,
10295
- components: []
10315
+ islands: []
10296
10316
  };
10297
10317
  /**
10298
10318
  * Whether a selector is syntactically valid (parseable by the DOM). Falls back
@@ -10314,8 +10334,8 @@ function isValidSelector(selector) {
10314
10334
  }
10315
10335
  /**
10316
10336
  * Validates the spa config and applies defaults (Part-3 errors on an empty or
10317
- * syntactically-invalid `swapSelector`). Component-hook validation runs later in
10318
- * `createComponent` when the components are registered.
10337
+ * syntactically-invalid `swapSelector`). Island-hook validation runs later in
10338
+ * `createIsland` when the islands are registered.
10319
10339
  *
10320
10340
  * @param config - The raw spa config to validate.
10321
10341
  * @returns The fully-resolved config with defaults applied.
@@ -10331,7 +10351,7 @@ function resolveSpaConfig(config) {
10331
10351
  swapSelector,
10332
10352
  viewTransitions: config.viewTransitions ?? false,
10333
10353
  progressBar: config.progressBar ?? true,
10334
- components: config.components ?? []
10354
+ islands: config.islands ?? []
10335
10355
  };
10336
10356
  }
10337
10357
  /**
@@ -10348,9 +10368,9 @@ function resolveSpaConfig(config) {
10348
10368
  */
10349
10369
  function createState(_ctx) {
10350
10370
  return {
10351
- registeredComponents: /* @__PURE__ */ new Map(),
10371
+ registeredIslands: /* @__PURE__ */ new Map(),
10352
10372
  instances: /* @__PURE__ */ new Map(),
10353
- componentApis: /* @__PURE__ */ new Map(),
10373
+ islandApis: /* @__PURE__ */ new Map(),
10354
10374
  currentUrl: "",
10355
10375
  destroyRouter: null,
10356
10376
  started: false,
@@ -10370,15 +10390,15 @@ function createState(_ctx) {
10370
10390
  /** Error prefix for spa kernel failures (spec/11 Part-3). */
10371
10391
  const ERROR_PREFIX = "[web]";
10372
10392
  /**
10373
- * Registers a component definition into state (last-registered-wins).
10393
+ * Registers a island definition into state (last-registered-wins).
10374
10394
  *
10375
- * @param state - The plugin state holding registeredComponents.
10376
- * @param component - The component definition to register.
10395
+ * @param state - The plugin state holding registeredIslands.
10396
+ * @param island - The island definition to register.
10377
10397
  * @example
10378
- * registerComponent(state, counter);
10398
+ * registerIsland(state, counter);
10379
10399
  */
10380
- function registerComponent(state, component) {
10381
- state.registeredComponents.set(component.name, component);
10400
+ function registerIsland(state, island) {
10401
+ state.registeredIslands.set(island.name, island);
10382
10402
  }
10383
10403
  /**
10384
10404
  * Resolve the current document URL (pathname + search), or `""` when headless.
@@ -10453,15 +10473,15 @@ function createSpaKernel(state, config, emit, deps) {
10453
10473
  });
10454
10474
  };
10455
10475
  /**
10456
- * Build the matched-route slice (params/meta/locale/url) for the component context at `path`,
10476
+ * Build the matched-route slice (params/meta/locale/url) for the island context at `path`,
10457
10477
  * so islands read their route's params/meta directly. An unmatched path yields an empty slice.
10458
10478
  *
10459
10479
  * @param path - The URL (pathname + search) to match.
10460
10480
  * @returns The route slice for the matched route.
10461
10481
  * @example
10462
- * scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(pathname));
10482
+ * scanAndMount(state, emit, resolved.swapSelector, islandRouteContext(pathname));
10463
10483
  */
10464
- const componentRouteContext = (path) => {
10484
+ const islandRouteContext = (path) => {
10465
10485
  const matchPath = path.split("?")[0] ?? path;
10466
10486
  const hit = deps.router.match(matchPath);
10467
10487
  const locale = hit?.params.lang ?? (typeof document === "undefined" ? "" : document.documentElement.lang) ?? "";
@@ -10490,7 +10510,7 @@ function createSpaKernel(state, config, emit, deps) {
10490
10510
  syncHead(deps.head, doc);
10491
10511
  unmountPageSpecific(state, emit);
10492
10512
  if (!swapRegion(doc, resolved.swapSelector, resolved.viewTransitions, () => {
10493
- const routeSlice = componentRouteContext(pathname);
10513
+ const routeSlice = islandRouteContext(pathname);
10494
10514
  scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10495
10515
  notifyNavEnd(state, routeSlice);
10496
10516
  }, applyPendingScroll)) {
@@ -10503,7 +10523,7 @@ function createSpaKernel(state, config, emit, deps) {
10503
10523
  emit("spa:navigated", { url: pathname });
10504
10524
  };
10505
10525
  /**
10506
- * Begin a navigation: start progress, notify components, emit navigate.
10526
+ * Begin a navigation: start progress, notify islands, emit navigate.
10507
10527
  *
10508
10528
  * @param pathname - The destination pathname.
10509
10529
  * @example
@@ -10589,11 +10609,11 @@ function createSpaKernel(state, config, emit, deps) {
10589
10609
  const commitDataRender = async (pathname, resolvedRender, signal) => {
10590
10610
  if (signal?.aborted) return;
10591
10611
  const { route, vnode, routeContext, region } = resolvedRender;
10592
- const { renderVNode } = await Promise.resolve().then(() => require("./render-KdufA3_b.cjs"));
10612
+ const { renderVNode } = await Promise.resolve().then(() => require("./render-DHUcHCYs.cjs"));
10593
10613
  if (signal?.aborted) return;
10594
10614
  syncDataHead(deps.head, route, routeContext);
10595
10615
  unmountPageSpecific(state, emit);
10596
- const routeSlice = componentRouteContext(pathname);
10616
+ const routeSlice = islandRouteContext(pathname);
10597
10617
  /**
10598
10618
  * Render the VNode into the region and re-mount its islands in one paint — the
10599
10619
  * swap body handed to `runSwap` (optionally wrapped in a View Transition).
@@ -10651,14 +10671,14 @@ function createSpaKernel(state, config, emit, deps) {
10651
10671
  * await bootRender("/b/abc123");
10652
10672
  */
10653
10673
  const bootRender = async (pathname) => {
10654
- const routeSlice = componentRouteContext(pathname);
10674
+ const routeSlice = islandRouteContext(pathname);
10655
10675
  const resolvedRender = await resolveDataRender(pathname);
10656
10676
  if (resolvedRender === false) {
10657
10677
  scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10658
10678
  return;
10659
10679
  }
10660
10680
  const { vnode, region } = resolvedRender;
10661
- const { renderVNode } = await Promise.resolve().then(() => require("./render-KdufA3_b.cjs"));
10681
+ const { renderVNode } = await Promise.resolve().then(() => require("./render-DHUcHCYs.cjs"));
10662
10682
  renderVNode(vnode, region);
10663
10683
  scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10664
10684
  };
@@ -10686,13 +10706,13 @@ function createSpaKernel(state, config, emit, deps) {
10686
10706
  };
10687
10707
  return {
10688
10708
  /**
10689
- * Register config components and seed currentUrl from the document.
10709
+ * Register config islands and seed currentUrl from the document.
10690
10710
  *
10691
10711
  * @example
10692
10712
  * kernel.init();
10693
10713
  */
10694
10714
  init() {
10695
- for (const component of resolved.components) registerComponent(state, component);
10715
+ for (const island of resolved.islands) registerIsland(state, island);
10696
10716
  state.currentUrl = currentLocationUrl();
10697
10717
  },
10698
10718
  /**
@@ -10710,18 +10730,18 @@ function createSpaKernel(state, config, emit, deps) {
10710
10730
  const matchPath = state.currentUrl.split("?")[0] ?? state.currentUrl;
10711
10731
  const hit = deps.router.match(matchPath);
10712
10732
  if (hit?.route._handlers.render && isClientOnlyRoute(deps.router.mode(), hit.route)) bootRender(state.currentUrl);
10713
- else scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(state.currentUrl));
10733
+ else scanAndMount(state, emit, resolved.swapSelector, islandRouteContext(state.currentUrl));
10714
10734
  state.started = true;
10715
10735
  },
10716
10736
  /**
10717
- * Register a component definition (last-registered-wins).
10737
+ * Register a island definition (last-registered-wins).
10718
10738
  *
10719
- * @param component - The component definition to register.
10739
+ * @param island - The island definition to register.
10720
10740
  * @example
10721
10741
  * kernel.register(counter);
10722
10742
  */
10723
- register(component) {
10724
- registerComponent(state, component);
10743
+ register(island) {
10744
+ registerIsland(state, island);
10725
10745
  },
10726
10746
  /**
10727
10747
  * Process a navigation to `path` (fetch then swap; full reload on error).
@@ -10735,13 +10755,13 @@ function createSpaKernel(state, config, emit, deps) {
10735
10755
  navigate(path).catch(() => {});
10736
10756
  },
10737
10757
  /**
10738
- * Scan the swap region and mount components for matching elements.
10758
+ * Scan the swap region and mount islands for matching elements.
10739
10759
  *
10740
10760
  * @example
10741
10761
  * kernel.scan();
10742
10762
  */
10743
10763
  scan() {
10744
- scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(state.currentUrl));
10764
+ scanAndMount(state, emit, resolved.swapSelector, islandRouteContext(state.currentUrl));
10745
10765
  },
10746
10766
  /**
10747
10767
  * Tear down router listeners, dispose all instances, reset boot state.
@@ -10760,7 +10780,7 @@ function createSpaKernel(state, config, emit, deps) {
10760
10780
  }
10761
10781
  /**
10762
10782
  * Builds the shared kernel from the plugin context, stores it on `ctx.state`,
10763
- * and runs its init step (validate config, register config.components, seed
10783
+ * and runs its init step (validate config, register config.islands, seed
10764
10784
  * currentUrl). Captures the OPTIONAL `data` reader when the `data` plugin is
10765
10785
  * composed (enabling client DATA navigation) — resolved by instance via
10766
10786
  * `ctx.require(dataPlugin)`, guarded by `ctx.has("data")` so `data` stays optional
@@ -10828,10 +10848,10 @@ function disposeSpa() {
10828
10848
  /**
10829
10849
  * @file `lazyEmbed` island — activates the static embed facades emitted by the
10830
10850
  * content pipeline's `::embed` directive (pipeline/embed.ts). Mounts on every
10831
- * `[data-component="lazy-embed"]` figure; a click on the facade's button swaps
10851
+ * `[data-island="lazy-embed"]` figure; a click on the facade's button swaps
10832
10852
  * it for the real `<iframe loading="lazy">`. Until that click the embedded
10833
10853
  * document costs the page nothing — no request, no third-party JS, no
10834
- * scroll-jacking. Register it in `pluginConfigs.spa.components`; all visual
10854
+ * scroll-jacking. Register it in `pluginConfigs.spa.islands`; all visual
10835
10855
  * chrome (`.lazy-embed*` classes) is consumer CSS.
10836
10856
  */
10837
10857
  /** CSS class on the injected `<iframe>` (consumer CSS sizes it). */
@@ -10884,7 +10904,7 @@ function onFacadeClick(event) {
10884
10904
  * Lazy-embed island: facade button click → real `<iframe loading="lazy">`.
10885
10905
  * The companion of the content pipeline's `::embed` directive.
10886
10906
  */
10887
- const lazyEmbed = createComponent("lazy-embed", {
10907
+ const lazyEmbed = createIsland("lazy-embed", {
10888
10908
  /**
10889
10909
  * Bind the activation click handler when a facade mounts.
10890
10910
  *
@@ -10910,18 +10930,18 @@ const lazyEmbed = createComponent("lazy-embed", {
10910
10930
  //#region src/plugins/spa/index.ts
10911
10931
  /**
10912
10932
  * @file spa — Complex Plugin (WIRING ONLY, ≤30 lines). All logic lives in the
10913
- * domain files (kernel/router/head/progress/components/lifecycle); index wires.
10933
+ * domain files (kernel/router/head/progress/islands/lifecycle); index wires.
10914
10934
  *
10915
10935
  * Depends: router, head.
10916
- * Emits: spa:navigate, spa:navigated, spa:component-mount, spa:component-unmount.
10936
+ * Emits: spa:navigate, spa:navigated, spa:island-mount, spa:island-unmount.
10917
10937
  * @see README.md
10918
10938
  */
10919
10939
  /**
10920
10940
  * SPA plugin — progressive client-side navigation layered over the static site:
10921
10941
  * swaps a page region on navigation, with an optional progress bar and View
10922
- * Transitions. Register interactive islands with {@link createComponent}. Depends
10923
- * on router and head; emits `spa:navigate`, `spa:navigated`, `spa:component-mount`,
10924
- * and `spa:component-unmount`.
10942
+ * Transitions. Register interactive islands with {@link createIsland}. Depends
10943
+ * on router and head; emits `spa:navigate`, `spa:navigated`, `spa:island-mount`,
10944
+ * and `spa:island-unmount`.
10925
10945
  *
10926
10946
  * @example Enable view transitions and a custom swap region
10927
10947
  * ```ts
@@ -11904,8 +11924,8 @@ function EmbedFacadeButton(props) {
11904
11924
  //#region src/plugins/content/pipeline/embed.ts
11905
11925
  /** CSS class on the `<figure>` facade wrapping each embed. */
11906
11926
  const EMBED_FIGURE_CLASS = "lazy-embed";
11907
- /** `data-component` name binding the facade to the `lazyEmbed` SPA island. */
11908
- const EMBED_COMPONENT_NAME = "lazy-embed";
11927
+ /** `data-island` name binding the facade to the `lazyEmbed` SPA island. */
11928
+ const EMBED_ISLAND_NAME = "lazy-embed";
11909
11929
  /**
11910
11930
  * Type guard for an `::embed` leaf directive.
11911
11931
  *
@@ -12015,7 +12035,7 @@ function collectAttributes$1(attributes) {
12015
12035
  * ```
12016
12036
  */
12017
12037
  function embedFacadeHtml(facade, props, dimensions) {
12018
- return `<figure class="${EMBED_FIGURE_CLASS}" data-component="${EMBED_COMPONENT_NAME}" data-embed-src="${escapeAttribute(props.src)}" data-embed-title="${escapeAttribute(props.title)}"${dimensions ? ` data-embed-width="${dimensions.width}" data-embed-height="${dimensions.height}" style="aspect-ratio: ${dimensions.width} / ${dimensions.height}; max-width: ${dimensions.width}px;"` : ""}>${(0, preact_render_to_string.renderToString)((0, preact.h)(facade, props))}</figure>`;
12038
+ return `<figure class="${EMBED_FIGURE_CLASS}" data-island="${EMBED_ISLAND_NAME}" data-embed-src="${escapeAttribute(props.src)}" data-embed-title="${escapeAttribute(props.title)}"${dimensions ? ` data-embed-width="${dimensions.width}" data-embed-height="${dimensions.height}" style="aspect-ratio: ${dimensions.width} / ${dimensions.height}; max-width: ${dimensions.width}px;"` : ""}>${(0, preact_render_to_string.renderToString)((0, preact.h)(facade, props))}</figure>`;
12019
12039
  }
12020
12040
  /**
12021
12041
  * Normalize the provider's `embed` config value (`boolean | options`) to a plain
@@ -12128,7 +12148,7 @@ function GalleryTrack(props) {
12128
12148
  *
12129
12149
  * Rewrites `::gallery{src="./images/dir/" caption="…"}` leaf directives into a
12130
12150
  * static swipeable image set at the mdast stage (BEFORE the remark-rehype bridge):
12131
- * a framework-owned `<div class="gallery" data-component="gallery">` carrying the
12151
+ * a framework-owned `<div class="gallery" data-island="gallery">` carrying the
12132
12152
  * island hook, wrapping inner content rendered (at build time, to static markup)
12133
12153
  * by a Preact component — the built-in {@link GalleryTrack} by default, or a
12134
12154
  * consumer component via `gallery.component`.
@@ -12140,12 +12160,12 @@ function GalleryTrack(props) {
12140
12160
  * transform reads `<contentDir>/<slug>/<src>` from disk, sorts its images, and
12141
12161
  * resolves each to its shared `/<slug>/<dir>/<file>` URL (identical from every
12142
12162
  * locale page, mirroring co-located images). The companion gallery SPA island
12143
- * (consumer-provided) wires swipe/keyboard/lightbox on `[data-component="gallery"]`.
12163
+ * (consumer-provided) wires swipe/keyboard/lightbox on `[data-island="gallery"]`.
12144
12164
  */
12145
12165
  /** CSS class on the `<div>` wrapping each gallery. */
12146
12166
  const GALLERY_WRAPPER_CLASS = "gallery";
12147
- /** `data-component` name binding the gallery to its SPA island. */
12148
- const GALLERY_COMPONENT_NAME = "gallery";
12167
+ /** `data-island` name binding the gallery to its SPA island. */
12168
+ const GALLERY_ISLAND_NAME = "gallery";
12149
12169
  /** Image file extensions a gallery folder expands over. */
12150
12170
  const IMAGE_EXTENSIONS = new Set([
12151
12171
  ".webp",
@@ -12238,7 +12258,7 @@ function collectAttributes(attributes) {
12238
12258
  }
12239
12259
  /**
12240
12260
  * Build the static gallery HTML for one directive: the framework-owned `<div>`
12241
- * (island hook in `data-component`) wrapping the component's inner content, SSR'd
12261
+ * (island hook in `data-island`) wrapping the component's inner content, SSR'd
12242
12262
  * to static markup.
12243
12263
  *
12244
12264
  * @param component - The gallery component (default {@link GalleryTrack}).
@@ -12252,7 +12272,7 @@ function collectAttributes(attributes) {
12252
12272
  * ```
12253
12273
  */
12254
12274
  function galleryHtml(component, slides, caption, attributes) {
12255
- return `<div class="${GALLERY_WRAPPER_CLASS}" data-component="${GALLERY_COMPONENT_NAME}">${(0, preact_render_to_string.renderToString)((0, preact.h)(component, {
12275
+ return `<div class="${GALLERY_WRAPPER_CLASS}" data-island="${GALLERY_ISLAND_NAME}">${(0, preact_render_to_string.renderToString)((0, preact.h)(component, {
12256
12276
  slides,
12257
12277
  caption,
12258
12278
  attributes
@@ -13237,7 +13257,7 @@ Object.defineProperty(exports, "cloudflareBindings", {
13237
13257
  });
13238
13258
  exports.contentPlugin = contentPlugin;
13239
13259
  exports.createApp = createApp;
13240
- exports.createComponent = createComponent;
13260
+ exports.createIsland = createIsland;
13241
13261
  exports.createPlugin = createPlugin;
13242
13262
  exports.createUrls = createUrls;
13243
13263
  exports.dataPlugin = dataPlugin;