122 lines
3.6 KiB
JavaScript
122 lines
3.6 KiB
JavaScript
import { HTMLString, markHTMLString, unescapeHTML } from "../escape.js";
|
|
import { renderChild } from "./any.js";
|
|
import { renderTemplate } from "./astro/render-template.js";
|
|
import { chunkToString } from "./common.js";
|
|
import {
|
|
isScriptInstruction
|
|
} from "./instruction.js";
|
|
const slotString = /* @__PURE__ */ Symbol.for("astro:slot-string");
|
|
class SlotString extends HTMLString {
|
|
instructions;
|
|
/**
|
|
* The slot's content as an ordered stream. Unlike `instructions` (which holds
|
|
* position-independent instructions like head/hydration content), scripts are
|
|
* kept inline here so they render at their original position instead of being
|
|
* hoisted to the start of the slot output.
|
|
*/
|
|
chunks;
|
|
[slotString];
|
|
constructor(content, instructions, chunks = []) {
|
|
super(content);
|
|
this.instructions = instructions;
|
|
this.chunks = chunks;
|
|
this[slotString] = true;
|
|
}
|
|
}
|
|
function isSlotString(str) {
|
|
return !!str[slotString];
|
|
}
|
|
function mergeSlotInstructions(target, source) {
|
|
if (source.instructions?.length) {
|
|
target ??= [];
|
|
target.push(...source.instructions);
|
|
}
|
|
return target;
|
|
}
|
|
function renderSlot(result, slotted, fallback) {
|
|
if (!slotted && fallback) {
|
|
return renderSlot(result, fallback);
|
|
}
|
|
return {
|
|
async render(destination) {
|
|
await renderChild(destination, typeof slotted === "function" ? slotted(result) : slotted);
|
|
}
|
|
};
|
|
}
|
|
async function renderSlotToString(result, slotted, fallback) {
|
|
let content = "";
|
|
let instructions = null;
|
|
const chunks = [];
|
|
const temporaryDestination = {
|
|
write(chunk) {
|
|
if (chunk instanceof SlotString) {
|
|
content += chunk;
|
|
if (chunk.chunks.length) {
|
|
chunks.push(...chunk.chunks);
|
|
}
|
|
instructions = mergeSlotInstructions(instructions, chunk);
|
|
} else if (chunk instanceof Response) return;
|
|
else if (typeof chunk === "object" && "type" in chunk && typeof chunk.type === "string") {
|
|
if (isScriptInstruction(chunk)) {
|
|
chunks.push(chunk);
|
|
} else {
|
|
if (instructions === null) {
|
|
instructions = [];
|
|
}
|
|
instructions.push(chunk);
|
|
}
|
|
} else {
|
|
const str = chunkToString(result, chunk);
|
|
content += str;
|
|
chunks.push(str);
|
|
}
|
|
}
|
|
};
|
|
const renderInstance = renderSlot(result, slotted, fallback);
|
|
await renderInstance.render(temporaryDestination);
|
|
return markHTMLString(new SlotString(content, instructions, chunks));
|
|
}
|
|
async function renderSlots(result, slots = {}) {
|
|
let slotInstructions = null;
|
|
let children = {};
|
|
if (slots) {
|
|
await Promise.all(
|
|
Object.entries(slots).map(
|
|
([key, value]) => renderSlotToString(result, value).then((output) => {
|
|
if (output.instructions) {
|
|
if (slotInstructions === null) {
|
|
slotInstructions = [];
|
|
}
|
|
slotInstructions.push(...output.instructions);
|
|
}
|
|
if (output.chunks) {
|
|
for (const part of output.chunks) {
|
|
if (typeof part !== "string") {
|
|
if (slotInstructions === null) {
|
|
slotInstructions = [];
|
|
}
|
|
slotInstructions.push(part);
|
|
}
|
|
}
|
|
}
|
|
children[key] = output;
|
|
})
|
|
)
|
|
);
|
|
}
|
|
return { slotInstructions, children };
|
|
}
|
|
function createSlotValueFromString(content) {
|
|
return function() {
|
|
return renderTemplate`${unescapeHTML(content)}`;
|
|
};
|
|
}
|
|
export {
|
|
SlotString,
|
|
createSlotValueFromString,
|
|
isSlotString,
|
|
mergeSlotInstructions,
|
|
renderSlot,
|
|
renderSlotToString,
|
|
renderSlots
|
|
};
|