@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.mjs CHANGED
@@ -8026,17 +8026,22 @@ function createApi$1(ctx) {
8026
8026
  * @example
8027
8027
  * await api.update(["src/islands/board.ts"]);
8028
8028
  */
8029
- update(changes, options = {}) {
8029
+ async update(changes, options = {}) {
8030
8030
  const overrides = devBuildOverrides({
8031
8031
  og: options.og ?? false,
8032
8032
  sitemap: options.sitemap ?? false,
8033
8033
  feeds: options.feeds ?? false
8034
8034
  });
8035
- return ctx.require(buildPlugin).run({
8036
- skipClean: true,
8037
- overrides,
8038
- changed: changes
8039
- });
8035
+ ctx.state.render.setDriven(true);
8036
+ try {
8037
+ return await ctx.require(buildPlugin).run({
8038
+ skipClean: true,
8039
+ overrides,
8040
+ changed: changes
8041
+ });
8042
+ } finally {
8043
+ ctx.state.render.setDriven(false);
8044
+ }
8040
8045
  },
8041
8046
  /**
8042
8047
  * Dev loop: build once, serve `dist/` in-process (live-reload injected), watch
@@ -8329,6 +8334,7 @@ function createPanelRenderer(options = {}) {
8329
8334
  let idle = false;
8330
8335
  let idleStartedAt = 0;
8331
8336
  let serveMode = false;
8337
+ let driven = false;
8332
8338
  let ticker;
8333
8339
  /**
8334
8340
  * Render one phase-tree row: a spinning cyan glyph + dim name while running, or a green
@@ -8490,7 +8496,7 @@ function createPanelRenderer(options = {}) {
8490
8496
  * render.phase({ phase: "pages", status: "done", durationMs: 12 });
8491
8497
  */
8492
8498
  phase(phase) {
8493
- if (rebuilding) return;
8499
+ if (rebuilding || driven) return;
8494
8500
  if (!color) {
8495
8501
  if (phase.status === "done") write(` ${palette.green("✓")} ${phase.phase}${durationSuffix(palette, phase.durationMs)}`);
8496
8502
  return;
@@ -8524,7 +8530,7 @@ function createPanelRenderer(options = {}) {
8524
8530
  * render.built({ outDir: "dist", pageCount: 12, durationMs: 840 });
8525
8531
  */
8526
8532
  built(summary) {
8527
- if (rebuilding) return;
8533
+ if (rebuilding || driven) return;
8528
8534
  if (color && phaseOpen) {
8529
8535
  let frame = cursorUp(phaseDrawn);
8530
8536
  for (const row of phaseRows) frame += `${CLEAR_LINE}${renderPhaseRow(row)}\n`;
@@ -8698,6 +8704,20 @@ function createPanelRenderer(options = {}) {
8698
8704
  if (detail !== void 0) for (const line of detail.split("\n")) write(` ${palette.dim(line)}`);
8699
8705
  },
8700
8706
  /**
8707
+ * Mark the build TUI as externally driven: when `on`, the per-phase build tree and the BUILD
8708
+ * summary are suppressed so an external dev driver (e.g. the worker's `dev({ onChange })` loop,
8709
+ * which calls `update()` and renders its own concise rebuild line) is the sole source of rebuild
8710
+ * output. Off by default; a standalone `build()` / `serve()` renders the full TUI as before.
8711
+ *
8712
+ * @param on - Whether an external driver owns the dev TUI.
8713
+ * @example
8714
+ * render.setDriven(true); // before an externally-driven update()
8715
+ * render.setDriven(false); // after it settles
8716
+ */
8717
+ setDriven(on) {
8718
+ driven = on;
8719
+ },
8720
+ /**
8701
8721
  * Stop every animation and release the interval timer (serve()'s teardown calls this).
8702
8722
  *
8703
8723
  * @example
@@ -9114,15 +9134,15 @@ const cliPlugin = createPlugin$1("cli", {
9114
9134
  function createApi(ctx) {
9115
9135
  return {
9116
9136
  /**
9117
- * Register a component definition (last-registered-wins); warns on collision.
9137
+ * Register a island definition (last-registered-wins); warns on collision.
9118
9138
  *
9119
- * @param component - The component definition created via `createComponent`.
9139
+ * @param island - The island definition created via `createIsland`.
9120
9140
  * @example
9121
9141
  * app.spa.register(counter);
9122
9142
  */
9123
- register(component) {
9124
- if (ctx.state.registeredComponents.has(component.name)) ctx.log.warn("spa:component-collision", { name: component.name });
9125
- ctx.state.kernel?.register(component);
9143
+ register(island) {
9144
+ if (ctx.state.registeredIslands.has(island.name)) ctx.log.warn("spa:island-collision", { name: island.name });
9145
+ ctx.state.kernel?.register(island);
9126
9146
  },
9127
9147
  /**
9128
9148
  * Programmatically navigate to a path (client runtime; no-op without a DOM).
@@ -9148,13 +9168,13 @@ function createApi(ctx) {
9148
9168
  * Resolve a registered island's api by name (the cross-island seam). Returns
9149
9169
  * `undefined` when no provider with that name is currently registered.
9150
9170
  *
9151
- * @param name - The provider island's component name.
9171
+ * @param name - The provider island's island name.
9152
9172
  * @returns The provider's api, or `undefined`.
9153
9173
  * @example
9154
- * app.spa.component("lightbox");
9174
+ * app.spa.island("lightbox");
9155
9175
  */
9156
- component(name) {
9157
- return ctx.state.componentApis.get(name);
9176
+ island(name) {
9177
+ return ctx.state.islandApis.get(name);
9158
9178
  }
9159
9179
  };
9160
9180
  }
@@ -9173,15 +9193,89 @@ function spaEvents(register) {
9173
9193
  return {
9174
9194
  "spa:navigate": register("A navigation has been intercepted and is starting."),
9175
9195
  "spa:navigated": register("The swap completed and the new URL is active."),
9176
- "spa:component-mount": register("A component instance attached to an element."),
9177
- "spa:component-unmount": register("A component instance detached from an element.")
9196
+ "spa:island-mount": register("A island instance attached to an element."),
9197
+ "spa:island-unmount": register("A island instance detached from an element.")
9178
9198
  };
9179
9199
  }
9180
9200
  //#endregion
9201
+ //#region src/plugins/spa/head.ts
9202
+ /** Single-element head selectors synced by replace/append/remove on navigation. */
9203
+ const META_SELECTORS = [
9204
+ "meta[name=\"description\"]",
9205
+ "meta[property=\"og:title\"]",
9206
+ "meta[property=\"og:description\"]",
9207
+ "meta[property=\"og:url\"]",
9208
+ "meta[property=\"og:image\"]",
9209
+ "meta[property=\"og:type\"]",
9210
+ "meta[property=\"og:locale\"]",
9211
+ "meta[name=\"twitter:card\"]",
9212
+ "meta[name=\"twitter:title\"]",
9213
+ "meta[name=\"twitter:description\"]",
9214
+ "meta[name=\"twitter:image\"]",
9215
+ "meta[name=\"twitter:site\"]",
9216
+ "link[rel=\"canonical\"]"
9217
+ ];
9218
+ /** Head element groups fully replaced (remove-all-then-clone) on navigation. */
9219
+ const REPLACE_ALL_SELECTORS = [
9220
+ "script[type=\"application/ld+json\"]",
9221
+ "link[rel=\"alternate\"][hreflang]",
9222
+ "meta[property^=\"article:\"]"
9223
+ ];
9224
+ /**
9225
+ * Sync a single head element by selector between the fetched and live document:
9226
+ * replace when both exist, append when only the new doc has it, remove when only
9227
+ * the live doc has it.
9228
+ *
9229
+ * @param selector - CSS selector for the head element to sync.
9230
+ * @param doc - The fetched document (DOMParser-parsed).
9231
+ * @example
9232
+ * syncElement('link[rel="canonical"]', doc);
9233
+ */
9234
+ function syncElement(selector, doc) {
9235
+ const newElement = doc.querySelector(selector);
9236
+ const oldElement = document.querySelector(selector);
9237
+ if (newElement && oldElement) oldElement.replaceWith(newElement.cloneNode(true));
9238
+ else if (newElement) document.head.append(newElement.cloneNode(true));
9239
+ else if (oldElement) oldElement.remove();
9240
+ }
9241
+ /**
9242
+ * Remove all live matches for a selector and re-clone the fetched document's
9243
+ * matches into the live `<head>`.
9244
+ *
9245
+ * @param selector - CSS selector for the element group to replace wholesale.
9246
+ * @param doc - The fetched document (DOMParser-parsed).
9247
+ * @example
9248
+ * replaceAllBySelector('script[type="application/ld+json"]', doc);
9249
+ */
9250
+ function replaceAllBySelector(selector, doc) {
9251
+ for (const element of document.querySelectorAll(selector)) element.remove();
9252
+ for (const element of doc.querySelectorAll(selector)) document.head.append(element.cloneNode(true));
9253
+ }
9254
+ /**
9255
+ * Syncs the live document `<head>` after a navigation from the fetched document
9256
+ * (whose head was composed by the `head` plugin). Recomputes
9257
+ * title/meta/canonical/JSON-LD/hreflang/`<html lang>` once and applies them.
9258
+ * The `head` API is accepted to bind the structural dependency (spec/09 deps).
9259
+ *
9260
+ * @param _head - The head plugin API (dependency binding; composition reused via the fetched doc).
9261
+ * @param doc - The fetched document parsed from the navigated page's HTML.
9262
+ * @example
9263
+ * syncHead(headApi, parsedDoc);
9264
+ */
9265
+ function syncHead(_head, doc) {
9266
+ if (typeof document === "undefined") return;
9267
+ const newTitle = doc.querySelector("title")?.textContent;
9268
+ if (newTitle) document.title = newTitle;
9269
+ const newLang = doc.documentElement.lang;
9270
+ if (newLang) document.documentElement.lang = newLang;
9271
+ for (const selector of META_SELECTORS) syncElement(selector, doc);
9272
+ for (const selector of REPLACE_ALL_SELECTORS) replaceAllBySelector(selector, doc);
9273
+ }
9274
+ //#endregion
9181
9275
  //#region src/plugins/spa/types.ts
9182
- var types_exports$7 = /* @__PURE__ */ __exportAll({ COMPONENT_HOOK_NAMES: () => COMPONENT_HOOK_NAMES });
9276
+ var types_exports$7 = /* @__PURE__ */ __exportAll({ ISLAND_HOOK_NAMES: () => ISLAND_HOOK_NAMES });
9183
9277
  /** Allowed hook names — single source of truth for fail-fast validation. */
9184
- const COMPONENT_HOOK_NAMES = [
9278
+ const ISLAND_HOOK_NAMES = [
9185
9279
  "onCreate",
9186
9280
  "onMount",
9187
9281
  "onNavStart",
@@ -9190,10 +9284,10 @@ const COMPONENT_HOOK_NAMES = [
9190
9284
  "onDestroy"
9191
9285
  ];
9192
9286
  //#endregion
9193
- //#region src/plugins/spa/components.ts
9287
+ //#region src/plugins/spa/islands.ts
9194
9288
  /**
9195
- * @file spa plugin — component lifecycle, mounting, the plugin-mirror authoring
9196
- * surface (`createComponent` with a typed `{ state, render, events, api }` spec),
9289
+ * @file spa plugin — island lifecycle, mounting, the plugin-mirror authoring
9290
+ * surface (`createIsland` with a typed `{ state, render, events, api }` spec),
9197
9291
  * the per-instance state + microtask-batched render scheduler, declarative
9198
9292
  * delegated events, and the cross-island api registry.
9199
9293
  * @see README.md
@@ -9201,8 +9295,8 @@ const COMPONENT_HOOK_NAMES = [
9201
9295
  /** Error prefix for spa fail-fast failures (spec/11 Part-3). */
9202
9296
  const ERROR_PREFIX$2 = "[web]";
9203
9297
  /** The set of legal hook names, frozen for O(1) membership checks. */
9204
- const HOOK_NAME_SET = new Set(COMPONENT_HOOK_NAMES);
9205
- /** The spec-only keys that select the plugin-mirror form of {@link createComponent}. */
9298
+ const HOOK_NAME_SET = new Set(ISLAND_HOOK_NAMES);
9299
+ /** The spec-only keys that select the plugin-mirror form of {@link createIsland}. */
9206
9300
  const SPEC_KEYS = new Set([
9207
9301
  "state",
9208
9302
  "render",
@@ -9241,7 +9335,7 @@ let renderChunk;
9241
9335
  let commitVNodeFunction;
9242
9336
  /**
9243
9337
  * Load the lazy `./render` chunk (once) and cache its `commitVNode` for synchronous
9244
- * use by later renders. Awaited by a component's `mountPromise` so the test harness's
9338
+ * use by later renders. Awaited by a island's `mountPromise` so the test harness's
9245
9339
  * `settle()` can deterministically flush a VNode render.
9246
9340
  *
9247
9341
  * @returns A promise that resolves once `commitVNode` is available.
@@ -9249,7 +9343,7 @@ let commitVNodeFunction;
9249
9343
  * await loadRenderChunk();
9250
9344
  */
9251
9345
  async function loadRenderChunk() {
9252
- renderChunk ??= import("./render-UO4nimWr.mjs");
9346
+ renderChunk ??= import("./render-yXHc9BWI.mjs");
9253
9347
  commitVNodeFunction = (await renderChunk).commitVNode;
9254
9348
  }
9255
9349
  /**
@@ -9258,7 +9352,7 @@ async function loadRenderChunk() {
9258
9352
  * a Preact `VNode` → committed through the lazy gate (loading it on demand if needed).
9259
9353
  *
9260
9354
  * @param host - The island host element to render into.
9261
- * @param result - The value returned by the component's `render`.
9355
+ * @param result - The value returned by the island's `render`.
9262
9356
  * @example
9263
9357
  * commitResult(host, h(View, { items }));
9264
9358
  */
@@ -9280,7 +9374,7 @@ function commitResult(host, result) {
9280
9374
  loadRenderChunk().then(() => commitVNodeFunction?.(vnode, host)).catch(() => {});
9281
9375
  }
9282
9376
  /**
9283
- * Run a component's `render(state, ctx)` and commit the result now. Guards against
9377
+ * Run a island's `render(state, ctx)` and commit the result now. Guards against
9284
9378
  * synchronous re-entrancy (a render that calls `ctx.flush`) with a depth cap.
9285
9379
  *
9286
9380
  * @param instance - The instance to render.
@@ -9291,7 +9385,7 @@ function commitResult(host, result) {
9291
9385
  function runRender(instance) {
9292
9386
  const render = instance.def.spec?.render;
9293
9387
  if (!render) return;
9294
- 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)`);
9388
+ 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)`);
9295
9389
  instance.renderDepth += 1;
9296
9390
  try {
9297
9391
  commitResult(instance.el, render(instance.state ?? {}, instance.ctx));
@@ -9317,12 +9411,12 @@ function scheduleRender(instance) {
9317
9411
  });
9318
9412
  }
9319
9413
  /**
9320
- * Build the single per-instance {@link ComponentContext} reused by every hook, event
9414
+ * Build the single per-instance {@link IslandContext} reused by every hook, event
9321
9415
  * handler, and render. Route fields (`params`/`meta`/`locale`/`url`) and `data` read
9322
9416
  * through the instance so a navigation update is reflected without rebuilding the ctx;
9323
- * `state`/`set`/`flush`/`cleanup`/`component` are bound to the instance + plugin state.
9417
+ * `state`/`set`/`flush`/`cleanup`/`island` are bound to the instance + plugin state.
9324
9418
  *
9325
- * @param state - The plugin state (for the cross-island `component` resolver).
9419
+ * @param state - The plugin state (for the cross-island `island` resolver).
9326
9420
  * @param instance - The instance the context is bound to.
9327
9421
  * @returns The instance-bound context.
9328
9422
  * @example
@@ -9426,13 +9520,13 @@ function buildContext(state, instance) {
9426
9520
  /**
9427
9521
  * Resolve another island's registered api by name (`undefined` when absent).
9428
9522
  *
9429
- * @param name - The provider island's component name.
9523
+ * @param name - The provider island's island name.
9430
9524
  * @returns The provider's api, or `undefined`.
9431
9525
  * @example
9432
- * ctx.component("lightbox");
9526
+ * ctx.island("lightbox");
9433
9527
  */
9434
- component(name) {
9435
- return state.componentApis.get(name);
9528
+ island(name) {
9529
+ return state.islandApis.get(name);
9436
9530
  }
9437
9531
  };
9438
9532
  }
@@ -9456,7 +9550,7 @@ function matchTarget(host, event, selector) {
9456
9550
  return matched && host.contains(matched) ? matched : void 0;
9457
9551
  }
9458
9552
  /**
9459
- * Attach a component's declarative `events` map: one real listener per event TYPE on
9553
+ * Attach a island's declarative `events` map: one real listener per event TYPE on
9460
9554
  * the host (dispatch walks `closest(selector)` for each registered selector), each
9461
9555
  * removed via the instance's cleanup registry on destroy.
9462
9556
  *
@@ -9473,7 +9567,7 @@ function attachEvents(instance, events) {
9473
9567
  const space = key.indexOf(" ");
9474
9568
  const type = (space === -1 ? key : key.slice(0, space)).trim();
9475
9569
  const selector = space === -1 ? "" : key.slice(space + 1).trim();
9476
- 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]")`);
9570
+ 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]")`);
9477
9571
  const list = byType.get(type) ?? [];
9478
9572
  list.push({
9479
9573
  selector,
@@ -9496,62 +9590,62 @@ function attachEvents(instance, events) {
9496
9590
  * Validate a single hook entry: its key must be a known hook name and its value
9497
9591
  * must be a function. Throws fail-fast on the first violation.
9498
9592
  *
9499
- * @param componentName - The owning component name (for error messages).
9593
+ * @param islandName - The owning island name (for error messages).
9500
9594
  * @param source - The raw authoring object being validated.
9501
9595
  * @param key - The hook key to validate.
9502
- * @throws {Error} If `key` is not in `COMPONENT_HOOK_NAMES`.
9596
+ * @throws {Error} If `key` is not in `ISLAND_HOOK_NAMES`.
9503
9597
  * @throws {TypeError} If the hook value is not a function.
9504
9598
  * @example
9505
9599
  * validateHookEntry("counter", source, "onMount");
9506
9600
  */
9507
- function validateHookEntry(componentName, source, key) {
9508
- 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`);
9509
- 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`);
9601
+ function validateHookEntry(islandName, source, key) {
9602
+ 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`);
9603
+ 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`);
9510
9604
  }
9511
9605
  /**
9512
9606
  * Validate the spec extras (`state`/`render`/`api` must be functions; `events` must be
9513
9607
  * a plain object of functions). Throws fail-fast on the first violation.
9514
9608
  *
9515
- * @param componentName - The owning component name (for error messages).
9609
+ * @param islandName - The owning island name (for error messages).
9516
9610
  * @param extras - The partitioned spec extras to validate.
9517
9611
  * @throws {TypeError} If a present extra has the wrong shape.
9518
9612
  * @example
9519
9613
  * validateSpecExtras("board", { state: () => ({}) });
9520
9614
  */
9521
- function validateSpecExtras(componentName, extras) {
9615
+ function validateSpecExtras(islandName, extras) {
9522
9616
  for (const key of [
9523
9617
  "state",
9524
9618
  "render",
9525
9619
  "api"
9526
- ]) 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`);
9620
+ ]) 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`);
9527
9621
  if (extras.events !== void 0) {
9528
9622
  const events = extras.events;
9529
- if (!(typeof events === "object")) throw new TypeError(`${ERROR_PREFIX$2} component "events" on "${componentName}" must be an object of handlers`);
9530
- 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`);
9623
+ if (!(typeof events === "object")) throw new TypeError(`${ERROR_PREFIX$2} island "events" on "${islandName}" must be an object of handlers`);
9624
+ 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`);
9531
9625
  }
9532
9626
  }
9533
9627
  /**
9534
- * Create a validated component definition. Accepts either the legacy hooks-only form
9535
- * (`createComponent("counter", { onMount() {} })`) or the plugin-mirror spec form
9536
- * (`createComponent("board", { state, render, events, api, ...hooks })`). Spec-only
9628
+ * Create a validated island definition. Accepts either the legacy hooks-only form
9629
+ * (`createIsland("counter", { onMount() {} })`) or the plugin-mirror spec form
9630
+ * (`createIsland("board", { state, render, events, api, ...hooks })`). Spec-only
9537
9631
  * keys (`state`/`render`/`events`/`api`) are partitioned out before hook-name
9538
9632
  * validation, so a real typo (e.g. `onMout`) still throws immediately while the spec
9539
9633
  * keys are accepted.
9540
9634
  *
9541
- * @param name - Unique component name.
9635
+ * @param name - Unique island name.
9542
9636
  * @param spec - Lifecycle hooks, or the `{ state, render, events, api, ...hooks }` spec.
9543
- * @returns A `ComponentDef` ready to `register`.
9637
+ * @returns A `IslandDef` ready to `register`.
9544
9638
  * @throws {Error} If `name` is empty, a hook key is unknown, or an extra/hook value has the wrong shape.
9545
9639
  * @example
9546
- * const counter = createComponent("counter", { onMount({ el }) { el.textContent = "0"; } });
9640
+ * const counter = createIsland("counter", { onMount({ el }) { el.textContent = "0"; } });
9547
9641
  * @example
9548
- * const list = createComponent<{ items: string[] }>("list", {
9642
+ * const list = createIsland<{ items: string[] }>("list", {
9549
9643
  * state: () => ({ items: [] }),
9550
9644
  * render: (s) => h(List, { items: s.items })
9551
9645
  * });
9552
9646
  */
9553
- function createComponent(name, spec) {
9554
- 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)`);
9647
+ function createIsland(name, spec) {
9648
+ 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)`);
9555
9649
  const source = spec;
9556
9650
  const hooks = {};
9557
9651
  const extras = {};
@@ -9628,30 +9722,30 @@ function disposeInstance(state, instance) {
9628
9722
  } catch {}
9629
9723
  instance.cleanups.length = 0;
9630
9724
  instance.renderScheduled = false;
9631
- if (instance.api !== void 0 && state.componentApis.get(instance.def.name) === instance.api) state.componentApis.delete(instance.def.name);
9725
+ if (instance.api !== void 0 && state.islandApis.get(instance.def.name) === instance.api) state.islandApis.delete(instance.def.name);
9632
9726
  }
9633
9727
  /**
9634
- * Mounts a single `data-component` element: classifies persistent vs page-specific,
9728
+ * Mounts a single `data-island` element: classifies persistent vs page-specific,
9635
9729
  * builds the instance + its bound context, initializes per-instance `state`, registers
9636
9730
  * its `api`, attaches declarative `events`, fires `onCreate` then `onMount` (capturing
9637
9731
  * an async `onMount` + render-chunk load as `mountPromise`), schedules the initial
9638
- * render, records it, and emits `spa:component-mount`. No-ops if the element is already
9639
- * mounted, has no component name, or names an unregistered component.
9732
+ * render, records it, and emits `spa:island-mount`. No-ops if the element is already
9733
+ * mounted, has no island name, or names an unregistered island.
9640
9734
  *
9641
- * @param state - The plugin state (registeredComponents + instances + componentApis).
9642
- * @param emit - The event emitter for spa:component-mount.
9735
+ * @param state - The plugin state (registeredIslands + instances + islandApis).
9736
+ * @param emit - The event emitter for spa:island-mount.
9643
9737
  * @param swapArea - The swap-region element, or null when none was found.
9644
9738
  * @param data - The current page data payload.
9645
- * @param element - The candidate element carrying a `data-component` attribute.
9739
+ * @param element - The candidate element carrying a `data-island` attribute.
9646
9740
  * @param route - The matched-route slice for the current URL (params/meta/locale/url).
9647
9741
  * @example
9648
9742
  * mountElement(state, emit, swapArea, data, element, route);
9649
9743
  */
9650
9744
  function mountElement(state, emit, swapArea, data, element, route = EMPTY_ROUTE) {
9651
9745
  if (state.instances.has(element)) return;
9652
- const name = element.dataset.component;
9746
+ const name = element.dataset.island;
9653
9747
  if (!name) return;
9654
- const definition = state.registeredComponents.get(name);
9748
+ const definition = state.registeredIslands.get(name);
9655
9749
  if (!definition) return;
9656
9750
  const instance = {
9657
9751
  def: definition,
@@ -9677,7 +9771,7 @@ function mountElement(state, emit, swapArea, data, element, route = EMPTY_ROUTE)
9677
9771
  if (spec?.state) instance.state = spec.state(instance.ctx);
9678
9772
  if (spec?.api) {
9679
9773
  instance.api = spec.api(instance.ctx);
9680
- state.componentApis.set(definition.name, instance.api);
9774
+ state.islandApis.set(definition.name, instance.api);
9681
9775
  }
9682
9776
  if (spec?.events) attachEvents(instance, spec.events);
9683
9777
  runHook(instance, "onCreate");
@@ -9688,20 +9782,20 @@ function mountElement(state, emit, swapArea, data, element, route = EMPTY_ROUTE)
9688
9782
  if (onMountResult && typeof onMountResult.then === "function") pending.push(onMountResult);
9689
9783
  instance.mountPromise = pending.length > 0 ? Promise.all(pending).then(() => {}) : void 0;
9690
9784
  state.instances.set(element, instance);
9691
- emit("spa:component-mount", {
9785
+ emit("spa:island-mount", {
9692
9786
  name: definition.name,
9693
9787
  el: element
9694
9788
  });
9695
9789
  }
9696
9790
  /**
9697
- * Scans the swap region, mounts components for matching `data-component` elements,
9791
+ * Scans the swap region, mounts islands for matching `data-island` elements,
9698
9792
  * classifies persistent (outside swap area) vs page-specific (inside), runs
9699
- * `onCreate`/`onMount` + initial render, and emits `spa:component-mount` per instance.
9793
+ * `onCreate`/`onMount` + initial render, and emits `spa:island-mount` per instance.
9700
9794
  * Already-mounted elements are skipped.
9701
9795
  *
9702
- * @param state - The plugin state (registeredComponents + instances + componentApis).
9703
- * @param emit - The event emitter for spa:component-mount.
9704
- * @param swapSelector - CSS selector bounding page-specific components.
9796
+ * @param state - The plugin state (registeredIslands + instances + islandApis).
9797
+ * @param emit - The event emitter for spa:island-mount.
9798
+ * @param swapSelector - CSS selector bounding page-specific islands.
9705
9799
  * @param route - The matched-route slice for the current URL (params/meta/locale/url).
9706
9800
  * @example
9707
9801
  * scanAndMount(state, emit, "main > section", route);
@@ -9710,16 +9804,16 @@ function scanAndMount(state, emit, swapSelector, route = EMPTY_ROUTE) {
9710
9804
  if (typeof document === "undefined") return;
9711
9805
  const swapArea = document.querySelector(swapSelector);
9712
9806
  const data = extractPageData(document);
9713
- for (const element of document.querySelectorAll("[data-component]")) mountElement(state, emit, swapArea, data, element, route);
9807
+ for (const element of document.querySelectorAll("[data-island]")) mountElement(state, emit, swapArea, data, element, route);
9714
9808
  }
9715
9809
  /**
9716
9810
  * Unmounts page-specific instances inside the swap region (runs `onUnMount` then
9717
9811
  * `onDestroy`, then their cleanup disposers + api unregister), removes them from state,
9718
- * and emits `spa:component-unmount`. Persistent instances (outside the swap area) are
9812
+ * and emits `spa:island-unmount`. Persistent instances (outside the swap area) are
9719
9813
  * left in place.
9720
9814
  *
9721
9815
  * @param state - The plugin state holding live instances.
9722
- * @param emit - The event emitter for spa:component-unmount.
9816
+ * @param emit - The event emitter for spa:island-unmount.
9723
9817
  * @example
9724
9818
  * unmountPageSpecific(state, emit);
9725
9819
  */
@@ -9732,7 +9826,7 @@ function unmountPageSpecific(state, emit) {
9732
9826
  runHook(instance, "onDestroy");
9733
9827
  disposeInstance(state, instance);
9734
9828
  state.instances.delete(element);
9735
- emit("spa:component-unmount", {
9829
+ emit("spa:island-unmount", {
9736
9830
  name: instance.def.name,
9737
9831
  el: element
9738
9832
  });
@@ -9741,11 +9835,11 @@ function unmountPageSpecific(state, emit) {
9741
9835
  /**
9742
9836
  * Disposes ALL live instances (persistent and page-specific) on teardown: runs
9743
9837
  * `onUnMount` then `onDestroy`, then their cleanup disposers + api unregister, emits
9744
- * `spa:component-unmount`, and clears the instance + api maps. Used by the kernel's
9838
+ * `spa:island-unmount`, and clears the instance + api maps. Used by the kernel's
9745
9839
  * `dispose` on plugin stop.
9746
9840
  *
9747
9841
  * @param state - The plugin state holding live instances.
9748
- * @param emit - The event emitter for spa:component-unmount.
9842
+ * @param emit - The event emitter for spa:island-unmount.
9749
9843
  * @example
9750
9844
  * unmountAll(state, emit);
9751
9845
  */
@@ -9756,13 +9850,13 @@ function unmountAll(state, emit) {
9756
9850
  runHook(instance, "onUnMount");
9757
9851
  runHook(instance, "onDestroy");
9758
9852
  disposeInstance(state, instance);
9759
- emit("spa:component-unmount", {
9853
+ emit("spa:island-unmount", {
9760
9854
  name: instance.def.name,
9761
9855
  el: element
9762
9856
  });
9763
9857
  }
9764
9858
  state.instances.clear();
9765
- state.componentApis.clear();
9859
+ state.islandApis.clear();
9766
9860
  }
9767
9861
  /**
9768
9862
  * Fires `onNavStart` on every currently-mounted instance (persistent instances
@@ -9799,80 +9893,6 @@ function notifyNavEnd(state, route = EMPTY_ROUTE) {
9799
9893
  }
9800
9894
  }
9801
9895
  //#endregion
9802
- //#region src/plugins/spa/head.ts
9803
- /** Single-element head selectors synced by replace/append/remove on navigation. */
9804
- const META_SELECTORS = [
9805
- "meta[name=\"description\"]",
9806
- "meta[property=\"og:title\"]",
9807
- "meta[property=\"og:description\"]",
9808
- "meta[property=\"og:url\"]",
9809
- "meta[property=\"og:image\"]",
9810
- "meta[property=\"og:type\"]",
9811
- "meta[property=\"og:locale\"]",
9812
- "meta[name=\"twitter:card\"]",
9813
- "meta[name=\"twitter:title\"]",
9814
- "meta[name=\"twitter:description\"]",
9815
- "meta[name=\"twitter:image\"]",
9816
- "meta[name=\"twitter:site\"]",
9817
- "link[rel=\"canonical\"]"
9818
- ];
9819
- /** Head element groups fully replaced (remove-all-then-clone) on navigation. */
9820
- const REPLACE_ALL_SELECTORS = [
9821
- "script[type=\"application/ld+json\"]",
9822
- "link[rel=\"alternate\"][hreflang]",
9823
- "meta[property^=\"article:\"]"
9824
- ];
9825
- /**
9826
- * Sync a single head element by selector between the fetched and live document:
9827
- * replace when both exist, append when only the new doc has it, remove when only
9828
- * the live doc has it.
9829
- *
9830
- * @param selector - CSS selector for the head element to sync.
9831
- * @param doc - The fetched document (DOMParser-parsed).
9832
- * @example
9833
- * syncElement('link[rel="canonical"]', doc);
9834
- */
9835
- function syncElement(selector, doc) {
9836
- const newElement = doc.querySelector(selector);
9837
- const oldElement = document.querySelector(selector);
9838
- if (newElement && oldElement) oldElement.replaceWith(newElement.cloneNode(true));
9839
- else if (newElement) document.head.append(newElement.cloneNode(true));
9840
- else if (oldElement) oldElement.remove();
9841
- }
9842
- /**
9843
- * Remove all live matches for a selector and re-clone the fetched document's
9844
- * matches into the live `<head>`.
9845
- *
9846
- * @param selector - CSS selector for the element group to replace wholesale.
9847
- * @param doc - The fetched document (DOMParser-parsed).
9848
- * @example
9849
- * replaceAllBySelector('script[type="application/ld+json"]', doc);
9850
- */
9851
- function replaceAllBySelector(selector, doc) {
9852
- for (const element of document.querySelectorAll(selector)) element.remove();
9853
- for (const element of doc.querySelectorAll(selector)) document.head.append(element.cloneNode(true));
9854
- }
9855
- /**
9856
- * Syncs the live document `<head>` after a navigation from the fetched document
9857
- * (whose head was composed by the `head` plugin). Recomputes
9858
- * title/meta/canonical/JSON-LD/hreflang/`<html lang>` once and applies them.
9859
- * The `head` API is accepted to bind the structural dependency (spec/09 deps).
9860
- *
9861
- * @param _head - The head plugin API (dependency binding; composition reused via the fetched doc).
9862
- * @param doc - The fetched document parsed from the navigated page's HTML.
9863
- * @example
9864
- * syncHead(headApi, parsedDoc);
9865
- */
9866
- function syncHead(_head, doc) {
9867
- if (typeof document === "undefined") return;
9868
- const newTitle = doc.querySelector("title")?.textContent;
9869
- if (newTitle) document.title = newTitle;
9870
- const newLang = doc.documentElement.lang;
9871
- if (newLang) document.documentElement.lang = newLang;
9872
- for (const selector of META_SELECTORS) syncElement(selector, doc);
9873
- for (const selector of REPLACE_ALL_SELECTORS) replaceAllBySelector(selector, doc);
9874
- }
9875
- //#endregion
9876
9896
  //#region src/plugins/spa/progress.ts
9877
9897
  /** Delay before the bar appears, so fast navigations show no indicator. */
9878
9898
  const START_DELAY_MS = 150;
@@ -10279,7 +10299,7 @@ const defaultSpaConfig = {
10279
10299
  swapSelector: "main > section",
10280
10300
  viewTransitions: false,
10281
10301
  progressBar: true,
10282
- components: []
10302
+ islands: []
10283
10303
  };
10284
10304
  /**
10285
10305
  * Whether a selector is syntactically valid (parseable by the DOM). Falls back
@@ -10301,8 +10321,8 @@ function isValidSelector(selector) {
10301
10321
  }
10302
10322
  /**
10303
10323
  * Validates the spa config and applies defaults (Part-3 errors on an empty or
10304
- * syntactically-invalid `swapSelector`). Component-hook validation runs later in
10305
- * `createComponent` when the components are registered.
10324
+ * syntactically-invalid `swapSelector`). Island-hook validation runs later in
10325
+ * `createIsland` when the islands are registered.
10306
10326
  *
10307
10327
  * @param config - The raw spa config to validate.
10308
10328
  * @returns The fully-resolved config with defaults applied.
@@ -10318,7 +10338,7 @@ function resolveSpaConfig(config) {
10318
10338
  swapSelector,
10319
10339
  viewTransitions: config.viewTransitions ?? false,
10320
10340
  progressBar: config.progressBar ?? true,
10321
- components: config.components ?? []
10341
+ islands: config.islands ?? []
10322
10342
  };
10323
10343
  }
10324
10344
  /**
@@ -10335,9 +10355,9 @@ function resolveSpaConfig(config) {
10335
10355
  */
10336
10356
  function createState(_ctx) {
10337
10357
  return {
10338
- registeredComponents: /* @__PURE__ */ new Map(),
10358
+ registeredIslands: /* @__PURE__ */ new Map(),
10339
10359
  instances: /* @__PURE__ */ new Map(),
10340
- componentApis: /* @__PURE__ */ new Map(),
10360
+ islandApis: /* @__PURE__ */ new Map(),
10341
10361
  currentUrl: "",
10342
10362
  destroyRouter: null,
10343
10363
  started: false,
@@ -10357,15 +10377,15 @@ function createState(_ctx) {
10357
10377
  /** Error prefix for spa kernel failures (spec/11 Part-3). */
10358
10378
  const ERROR_PREFIX = "[web]";
10359
10379
  /**
10360
- * Registers a component definition into state (last-registered-wins).
10380
+ * Registers a island definition into state (last-registered-wins).
10361
10381
  *
10362
- * @param state - The plugin state holding registeredComponents.
10363
- * @param component - The component definition to register.
10382
+ * @param state - The plugin state holding registeredIslands.
10383
+ * @param island - The island definition to register.
10364
10384
  * @example
10365
- * registerComponent(state, counter);
10385
+ * registerIsland(state, counter);
10366
10386
  */
10367
- function registerComponent(state, component) {
10368
- state.registeredComponents.set(component.name, component);
10387
+ function registerIsland(state, island) {
10388
+ state.registeredIslands.set(island.name, island);
10369
10389
  }
10370
10390
  /**
10371
10391
  * Resolve the current document URL (pathname + search), or `""` when headless.
@@ -10440,15 +10460,15 @@ function createSpaKernel(state, config, emit, deps) {
10440
10460
  });
10441
10461
  };
10442
10462
  /**
10443
- * Build the matched-route slice (params/meta/locale/url) for the component context at `path`,
10463
+ * Build the matched-route slice (params/meta/locale/url) for the island context at `path`,
10444
10464
  * so islands read their route's params/meta directly. An unmatched path yields an empty slice.
10445
10465
  *
10446
10466
  * @param path - The URL (pathname + search) to match.
10447
10467
  * @returns The route slice for the matched route.
10448
10468
  * @example
10449
- * scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(pathname));
10469
+ * scanAndMount(state, emit, resolved.swapSelector, islandRouteContext(pathname));
10450
10470
  */
10451
- const componentRouteContext = (path) => {
10471
+ const islandRouteContext = (path) => {
10452
10472
  const matchPath = path.split("?")[0] ?? path;
10453
10473
  const hit = deps.router.match(matchPath);
10454
10474
  const locale = hit?.params.lang ?? (typeof document === "undefined" ? "" : document.documentElement.lang) ?? "";
@@ -10477,7 +10497,7 @@ function createSpaKernel(state, config, emit, deps) {
10477
10497
  syncHead(deps.head, doc);
10478
10498
  unmountPageSpecific(state, emit);
10479
10499
  if (!swapRegion(doc, resolved.swapSelector, resolved.viewTransitions, () => {
10480
- const routeSlice = componentRouteContext(pathname);
10500
+ const routeSlice = islandRouteContext(pathname);
10481
10501
  scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10482
10502
  notifyNavEnd(state, routeSlice);
10483
10503
  }, applyPendingScroll)) {
@@ -10490,7 +10510,7 @@ function createSpaKernel(state, config, emit, deps) {
10490
10510
  emit("spa:navigated", { url: pathname });
10491
10511
  };
10492
10512
  /**
10493
- * Begin a navigation: start progress, notify components, emit navigate.
10513
+ * Begin a navigation: start progress, notify islands, emit navigate.
10494
10514
  *
10495
10515
  * @param pathname - The destination pathname.
10496
10516
  * @example
@@ -10576,11 +10596,11 @@ function createSpaKernel(state, config, emit, deps) {
10576
10596
  const commitDataRender = async (pathname, resolvedRender, signal) => {
10577
10597
  if (signal?.aborted) return;
10578
10598
  const { route, vnode, routeContext, region } = resolvedRender;
10579
- const { renderVNode } = await import("./render-UO4nimWr.mjs");
10599
+ const { renderVNode } = await import("./render-yXHc9BWI.mjs");
10580
10600
  if (signal?.aborted) return;
10581
10601
  syncDataHead(deps.head, route, routeContext);
10582
10602
  unmountPageSpecific(state, emit);
10583
- const routeSlice = componentRouteContext(pathname);
10603
+ const routeSlice = islandRouteContext(pathname);
10584
10604
  /**
10585
10605
  * Render the VNode into the region and re-mount its islands in one paint — the
10586
10606
  * swap body handed to `runSwap` (optionally wrapped in a View Transition).
@@ -10638,14 +10658,14 @@ function createSpaKernel(state, config, emit, deps) {
10638
10658
  * await bootRender("/b/abc123");
10639
10659
  */
10640
10660
  const bootRender = async (pathname) => {
10641
- const routeSlice = componentRouteContext(pathname);
10661
+ const routeSlice = islandRouteContext(pathname);
10642
10662
  const resolvedRender = await resolveDataRender(pathname);
10643
10663
  if (resolvedRender === false) {
10644
10664
  scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10645
10665
  return;
10646
10666
  }
10647
10667
  const { vnode, region } = resolvedRender;
10648
- const { renderVNode } = await import("./render-UO4nimWr.mjs");
10668
+ const { renderVNode } = await import("./render-yXHc9BWI.mjs");
10649
10669
  renderVNode(vnode, region);
10650
10670
  scanAndMount(state, emit, resolved.swapSelector, routeSlice);
10651
10671
  };
@@ -10673,13 +10693,13 @@ function createSpaKernel(state, config, emit, deps) {
10673
10693
  };
10674
10694
  return {
10675
10695
  /**
10676
- * Register config components and seed currentUrl from the document.
10696
+ * Register config islands and seed currentUrl from the document.
10677
10697
  *
10678
10698
  * @example
10679
10699
  * kernel.init();
10680
10700
  */
10681
10701
  init() {
10682
- for (const component of resolved.components) registerComponent(state, component);
10702
+ for (const island of resolved.islands) registerIsland(state, island);
10683
10703
  state.currentUrl = currentLocationUrl();
10684
10704
  },
10685
10705
  /**
@@ -10697,18 +10717,18 @@ function createSpaKernel(state, config, emit, deps) {
10697
10717
  const matchPath = state.currentUrl.split("?")[0] ?? state.currentUrl;
10698
10718
  const hit = deps.router.match(matchPath);
10699
10719
  if (hit?.route._handlers.render && isClientOnlyRoute(deps.router.mode(), hit.route)) bootRender(state.currentUrl);
10700
- else scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(state.currentUrl));
10720
+ else scanAndMount(state, emit, resolved.swapSelector, islandRouteContext(state.currentUrl));
10701
10721
  state.started = true;
10702
10722
  },
10703
10723
  /**
10704
- * Register a component definition (last-registered-wins).
10724
+ * Register a island definition (last-registered-wins).
10705
10725
  *
10706
- * @param component - The component definition to register.
10726
+ * @param island - The island definition to register.
10707
10727
  * @example
10708
10728
  * kernel.register(counter);
10709
10729
  */
10710
- register(component) {
10711
- registerComponent(state, component);
10730
+ register(island) {
10731
+ registerIsland(state, island);
10712
10732
  },
10713
10733
  /**
10714
10734
  * Process a navigation to `path` (fetch then swap; full reload on error).
@@ -10722,13 +10742,13 @@ function createSpaKernel(state, config, emit, deps) {
10722
10742
  navigate(path).catch(() => {});
10723
10743
  },
10724
10744
  /**
10725
- * Scan the swap region and mount components for matching elements.
10745
+ * Scan the swap region and mount islands for matching elements.
10726
10746
  *
10727
10747
  * @example
10728
10748
  * kernel.scan();
10729
10749
  */
10730
10750
  scan() {
10731
- scanAndMount(state, emit, resolved.swapSelector, componentRouteContext(state.currentUrl));
10751
+ scanAndMount(state, emit, resolved.swapSelector, islandRouteContext(state.currentUrl));
10732
10752
  },
10733
10753
  /**
10734
10754
  * Tear down router listeners, dispose all instances, reset boot state.
@@ -10747,7 +10767,7 @@ function createSpaKernel(state, config, emit, deps) {
10747
10767
  }
10748
10768
  /**
10749
10769
  * Builds the shared kernel from the plugin context, stores it on `ctx.state`,
10750
- * and runs its init step (validate config, register config.components, seed
10770
+ * and runs its init step (validate config, register config.islands, seed
10751
10771
  * currentUrl). Captures the OPTIONAL `data` reader when the `data` plugin is
10752
10772
  * composed (enabling client DATA navigation) — resolved by instance via
10753
10773
  * `ctx.require(dataPlugin)`, guarded by `ctx.has("data")` so `data` stays optional
@@ -10815,10 +10835,10 @@ function disposeSpa() {
10815
10835
  /**
10816
10836
  * @file `lazyEmbed` island — activates the static embed facades emitted by the
10817
10837
  * content pipeline's `::embed` directive (pipeline/embed.ts). Mounts on every
10818
- * `[data-component="lazy-embed"]` figure; a click on the facade's button swaps
10838
+ * `[data-island="lazy-embed"]` figure; a click on the facade's button swaps
10819
10839
  * it for the real `<iframe loading="lazy">`. Until that click the embedded
10820
10840
  * document costs the page nothing — no request, no third-party JS, no
10821
- * scroll-jacking. Register it in `pluginConfigs.spa.components`; all visual
10841
+ * scroll-jacking. Register it in `pluginConfigs.spa.islands`; all visual
10822
10842
  * chrome (`.lazy-embed*` classes) is consumer CSS.
10823
10843
  */
10824
10844
  /** CSS class on the injected `<iframe>` (consumer CSS sizes it). */
@@ -10871,7 +10891,7 @@ function onFacadeClick(event) {
10871
10891
  * Lazy-embed island: facade button click → real `<iframe loading="lazy">`.
10872
10892
  * The companion of the content pipeline's `::embed` directive.
10873
10893
  */
10874
- const lazyEmbed = createComponent("lazy-embed", {
10894
+ const lazyEmbed = createIsland("lazy-embed", {
10875
10895
  /**
10876
10896
  * Bind the activation click handler when a facade mounts.
10877
10897
  *
@@ -10897,18 +10917,18 @@ const lazyEmbed = createComponent("lazy-embed", {
10897
10917
  //#region src/plugins/spa/index.ts
10898
10918
  /**
10899
10919
  * @file spa — Complex Plugin (WIRING ONLY, ≤30 lines). All logic lives in the
10900
- * domain files (kernel/router/head/progress/components/lifecycle); index wires.
10920
+ * domain files (kernel/router/head/progress/islands/lifecycle); index wires.
10901
10921
  *
10902
10922
  * Depends: router, head.
10903
- * Emits: spa:navigate, spa:navigated, spa:component-mount, spa:component-unmount.
10923
+ * Emits: spa:navigate, spa:navigated, spa:island-mount, spa:island-unmount.
10904
10924
  * @see README.md
10905
10925
  */
10906
10926
  /**
10907
10927
  * SPA plugin — progressive client-side navigation layered over the static site:
10908
10928
  * swaps a page region on navigation, with an optional progress bar and View
10909
- * Transitions. Register interactive islands with {@link createComponent}. Depends
10910
- * on router and head; emits `spa:navigate`, `spa:navigated`, `spa:component-mount`,
10911
- * and `spa:component-unmount`.
10929
+ * Transitions. Register interactive islands with {@link createIsland}. Depends
10930
+ * on router and head; emits `spa:navigate`, `spa:navigated`, `spa:island-mount`,
10931
+ * and `spa:island-unmount`.
10912
10932
  *
10913
10933
  * @example Enable view transitions and a custom swap region
10914
10934
  * ```ts
@@ -11891,8 +11911,8 @@ function EmbedFacadeButton(props) {
11891
11911
  //#region src/plugins/content/pipeline/embed.ts
11892
11912
  /** CSS class on the `<figure>` facade wrapping each embed. */
11893
11913
  const EMBED_FIGURE_CLASS = "lazy-embed";
11894
- /** `data-component` name binding the facade to the `lazyEmbed` SPA island. */
11895
- const EMBED_COMPONENT_NAME = "lazy-embed";
11914
+ /** `data-island` name binding the facade to the `lazyEmbed` SPA island. */
11915
+ const EMBED_ISLAND_NAME = "lazy-embed";
11896
11916
  /**
11897
11917
  * Type guard for an `::embed` leaf directive.
11898
11918
  *
@@ -12002,7 +12022,7 @@ function collectAttributes$1(attributes) {
12002
12022
  * ```
12003
12023
  */
12004
12024
  function embedFacadeHtml(facade, props, dimensions) {
12005
- 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;"` : ""}>${renderToString(h(facade, props))}</figure>`;
12025
+ 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;"` : ""}>${renderToString(h(facade, props))}</figure>`;
12006
12026
  }
12007
12027
  /**
12008
12028
  * Normalize the provider's `embed` config value (`boolean | options`) to a plain
@@ -12115,7 +12135,7 @@ function GalleryTrack(props) {
12115
12135
  *
12116
12136
  * Rewrites `::gallery{src="./images/dir/" caption="…"}` leaf directives into a
12117
12137
  * static swipeable image set at the mdast stage (BEFORE the remark-rehype bridge):
12118
- * a framework-owned `<div class="gallery" data-component="gallery">` carrying the
12138
+ * a framework-owned `<div class="gallery" data-island="gallery">` carrying the
12119
12139
  * island hook, wrapping inner content rendered (at build time, to static markup)
12120
12140
  * by a Preact component — the built-in {@link GalleryTrack} by default, or a
12121
12141
  * consumer component via `gallery.component`.
@@ -12127,12 +12147,12 @@ function GalleryTrack(props) {
12127
12147
  * transform reads `<contentDir>/<slug>/<src>` from disk, sorts its images, and
12128
12148
  * resolves each to its shared `/<slug>/<dir>/<file>` URL (identical from every
12129
12149
  * locale page, mirroring co-located images). The companion gallery SPA island
12130
- * (consumer-provided) wires swipe/keyboard/lightbox on `[data-component="gallery"]`.
12150
+ * (consumer-provided) wires swipe/keyboard/lightbox on `[data-island="gallery"]`.
12131
12151
  */
12132
12152
  /** CSS class on the `<div>` wrapping each gallery. */
12133
12153
  const GALLERY_WRAPPER_CLASS = "gallery";
12134
- /** `data-component` name binding the gallery to its SPA island. */
12135
- const GALLERY_COMPONENT_NAME = "gallery";
12154
+ /** `data-island` name binding the gallery to its SPA island. */
12155
+ const GALLERY_ISLAND_NAME = "gallery";
12136
12156
  /** Image file extensions a gallery folder expands over. */
12137
12157
  const IMAGE_EXTENSIONS = new Set([
12138
12158
  ".webp",
@@ -12225,7 +12245,7 @@ function collectAttributes(attributes) {
12225
12245
  }
12226
12246
  /**
12227
12247
  * Build the static gallery HTML for one directive: the framework-owned `<div>`
12228
- * (island hook in `data-component`) wrapping the component's inner content, SSR'd
12248
+ * (island hook in `data-island`) wrapping the component's inner content, SSR'd
12229
12249
  * to static markup.
12230
12250
  *
12231
12251
  * @param component - The gallery component (default {@link GalleryTrack}).
@@ -12239,7 +12259,7 @@ function collectAttributes(attributes) {
12239
12259
  * ```
12240
12260
  */
12241
12261
  function galleryHtml(component, slides, caption, attributes) {
12242
- return `<div class="${GALLERY_WRAPPER_CLASS}" data-component="${GALLERY_COMPONENT_NAME}">${renderToString(h(component, {
12262
+ return `<div class="${GALLERY_WRAPPER_CLASS}" data-island="${GALLERY_ISLAND_NAME}">${renderToString(h(component, {
12243
12263
  slides,
12244
12264
  caption,
12245
12265
  attributes
@@ -13156,4 +13176,4 @@ const createApp = core.createApp;
13156
13176
  */
13157
13177
  const createPlugin = core.createPlugin;
13158
13178
  //#endregion
13159
- export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as Content, types_exports$3 as Data, types_exports$4 as Deploy, EmbedFacadeButton, GalleryTrack, types_exports$5 as Head, types_exports$6 as Router, types_exports$7 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createComponent, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };
13179
+ export { types_exports as Build, types_exports$1 as Cli, types_exports$2 as Content, types_exports$3 as Data, types_exports$4 as Deploy, EmbedFacadeButton, GalleryTrack, types_exports$5 as Head, types_exports$6 as Router, types_exports$7 as Spa, browserEnv, buildArticleHead, buildPlugin, canonical, cliPlugin, cloudflareBindings, contentPlugin, createApp, createIsland, createPlugin, createUrls, dataPlugin, defineRoutes, deployPlugin, dotenv, envPlugin, feedLink, fileSystemContent, headPlugin, hreflang, i18nPlugin, jsonLd, lazyEmbed, logPlugin, meta, og, processEnv, route, routerPlugin, sitePlugin, spaPlugin, twitter };