216 lines
6.9 KiB
JavaScript
216 lines
6.9 KiB
JavaScript
![]() |
import { HTMLString, markHTMLString } from "../escape.js";
|
||
|
import { serializeListValue } from "../util.js";
|
||
|
const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
|
||
|
const htmlBooleanAttributes = /^(allowfullscreen|async|autofocus|autoplay|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|itemscope)$/i;
|
||
|
const htmlEnumAttributes = /^(contenteditable|draggable|spellcheck|value)$/i;
|
||
|
const svgEnumAttributes = /^(autoReverse|externalResourcesRequired|focusable|preserveAlpha)$/i;
|
||
|
const STATIC_DIRECTIVES = /* @__PURE__ */ new Set(["set:html", "set:text"]);
|
||
|
const toIdent = (k) => k.trim().replace(/(?:(?!^)\b\w|\s+|[^\w]+)/g, (match, index) => {
|
||
|
if (/[^\w]|\s/.test(match))
|
||
|
return "";
|
||
|
return index === 0 ? match : match.toUpperCase();
|
||
|
});
|
||
|
const toAttributeString = (value, shouldEscape = true) => shouldEscape ? String(value).replace(/&/g, "&").replace(/"/g, """) : value;
|
||
|
const kebab = (k) => k.toLowerCase() === k ? k : k.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
||
|
const toStyleString = (obj) => Object.entries(obj).map(([k, v]) => {
|
||
|
if (k[0] !== "-" && k[1] !== "-")
|
||
|
return `${kebab(k)}:${v}`;
|
||
|
if (kebab(k) !== k)
|
||
|
return `${kebab(k)}:var(${k});${k}:${v}`;
|
||
|
return `${k}:${v}`;
|
||
|
}).join(";");
|
||
|
function defineScriptVars(vars) {
|
||
|
var _a;
|
||
|
let output = "";
|
||
|
for (const [key, value] of Object.entries(vars)) {
|
||
|
output += `const ${toIdent(key)} = ${(_a = JSON.stringify(value)) == null ? void 0 : _a.replace(
|
||
|
/<\/script>/g,
|
||
|
"\\x3C/script>"
|
||
|
)};
|
||
|
`;
|
||
|
}
|
||
|
return markHTMLString(output);
|
||
|
}
|
||
|
function formatList(values) {
|
||
|
if (values.length === 1) {
|
||
|
return values[0];
|
||
|
}
|
||
|
return `${values.slice(0, -1).join(", ")} or ${values[values.length - 1]}`;
|
||
|
}
|
||
|
function addAttribute(value, key, shouldEscape = true) {
|
||
|
if (value == null) {
|
||
|
return "";
|
||
|
}
|
||
|
if (value === false) {
|
||
|
if (htmlEnumAttributes.test(key) || svgEnumAttributes.test(key)) {
|
||
|
return markHTMLString(` ${key}="false"`);
|
||
|
}
|
||
|
return "";
|
||
|
}
|
||
|
if (STATIC_DIRECTIVES.has(key)) {
|
||
|
console.warn(`[astro] The "${key}" directive cannot be applied dynamically at runtime. It will not be rendered as an attribute.
|
||
|
|
||
|
Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the dynamic spread syntax (\`{...{ "${key}": value }}\`).`);
|
||
|
return "";
|
||
|
}
|
||
|
if (key === "class:list") {
|
||
|
const listValue = toAttributeString(serializeListValue(value), shouldEscape);
|
||
|
if (listValue === "") {
|
||
|
return "";
|
||
|
}
|
||
|
return markHTMLString(` ${key.slice(0, -5)}="${listValue}"`);
|
||
|
}
|
||
|
if (key === "style" && !(value instanceof HTMLString)) {
|
||
|
if (Array.isArray(value) && value.length === 2) {
|
||
|
return markHTMLString(
|
||
|
` ${key}="${toAttributeString(`${toStyleString(value[0])};${value[1]}`, shouldEscape)}"`
|
||
|
);
|
||
|
}
|
||
|
if (typeof value === "object") {
|
||
|
return markHTMLString(` ${key}="${toAttributeString(toStyleString(value), shouldEscape)}"`);
|
||
|
}
|
||
|
}
|
||
|
if (key === "className") {
|
||
|
return markHTMLString(` class="${toAttributeString(value, shouldEscape)}"`);
|
||
|
}
|
||
|
if (value === true && (key.startsWith("data-") || htmlBooleanAttributes.test(key))) {
|
||
|
return markHTMLString(` ${key}`);
|
||
|
} else {
|
||
|
return markHTMLString(` ${key}="${toAttributeString(value, shouldEscape)}"`);
|
||
|
}
|
||
|
}
|
||
|
function internalSpreadAttributes(values, shouldEscape = true) {
|
||
|
let output = "";
|
||
|
for (const [key, value] of Object.entries(values)) {
|
||
|
output += addAttribute(value, key, shouldEscape);
|
||
|
}
|
||
|
return markHTMLString(output);
|
||
|
}
|
||
|
function renderElement(name, { props: _props, children = "" }, shouldEscape = true) {
|
||
|
const { lang: _, "data-astro-id": astroId, "define:vars": defineVars, ...props } = _props;
|
||
|
if (defineVars) {
|
||
|
if (name === "style") {
|
||
|
delete props["is:global"];
|
||
|
delete props["is:scoped"];
|
||
|
}
|
||
|
if (name === "script") {
|
||
|
delete props.hoist;
|
||
|
children = defineScriptVars(defineVars) + "\n" + children;
|
||
|
}
|
||
|
}
|
||
|
if ((children == null || children == "") && voidElementNames.test(name)) {
|
||
|
return `<${name}${internalSpreadAttributes(props, shouldEscape)} />`;
|
||
|
}
|
||
|
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
|
||
|
}
|
||
|
const iteratorQueue = [];
|
||
|
function queueIteratorBuffers(iterators) {
|
||
|
if (iteratorQueue.length === 0) {
|
||
|
setTimeout(() => {
|
||
|
iteratorQueue.forEach((its) => its.forEach((it) => !it.isStarted() && it.buffer()));
|
||
|
iteratorQueue.length = 0;
|
||
|
});
|
||
|
}
|
||
|
iteratorQueue.push(iterators);
|
||
|
}
|
||
|
function bufferIterators(iterators) {
|
||
|
const eagerIterators = iterators.map((it) => new EagerAsyncIterableIterator(it));
|
||
|
queueIteratorBuffers(eagerIterators);
|
||
|
return eagerIterators;
|
||
|
}
|
||
|
class EagerAsyncIterableIterator {
|
||
|
#iterable;
|
||
|
#queue = new Queue();
|
||
|
#error = void 0;
|
||
|
#next;
|
||
|
/**
|
||
|
* Whether the proxy is running in buffering or pass-through mode
|
||
|
*/
|
||
|
#isBuffering = false;
|
||
|
#gen = void 0;
|
||
|
#isStarted = false;
|
||
|
constructor(iterable) {
|
||
|
this.#iterable = iterable;
|
||
|
}
|
||
|
/**
|
||
|
* Starts to eagerly fetch the inner iterator and cache the results.
|
||
|
* Note: This might not be called after next() has been called once, e.g. the iterator is started
|
||
|
*/
|
||
|
async buffer() {
|
||
|
if (this.#gen) {
|
||
|
throw new Error("Cannot not switch from non-buffer to buffer mode");
|
||
|
}
|
||
|
this.#isBuffering = true;
|
||
|
this.#isStarted = true;
|
||
|
this.#gen = this.#iterable[Symbol.asyncIterator]();
|
||
|
let value = void 0;
|
||
|
do {
|
||
|
this.#next = this.#gen.next();
|
||
|
try {
|
||
|
value = await this.#next;
|
||
|
this.#queue.push(value);
|
||
|
} catch (e) {
|
||
|
this.#error = e;
|
||
|
}
|
||
|
} while (value && !value.done);
|
||
|
}
|
||
|
async next() {
|
||
|
if (this.#error) {
|
||
|
throw this.#error;
|
||
|
}
|
||
|
if (!this.#isBuffering) {
|
||
|
if (!this.#gen) {
|
||
|
this.#isStarted = true;
|
||
|
this.#gen = this.#iterable[Symbol.asyncIterator]();
|
||
|
}
|
||
|
return await this.#gen.next();
|
||
|
}
|
||
|
if (!this.#queue.isEmpty()) {
|
||
|
return this.#queue.shift();
|
||
|
}
|
||
|
await this.#next;
|
||
|
return this.#queue.shift();
|
||
|
}
|
||
|
isStarted() {
|
||
|
return this.#isStarted;
|
||
|
}
|
||
|
[Symbol.asyncIterator]() {
|
||
|
return this;
|
||
|
}
|
||
|
}
|
||
|
class Queue {
|
||
|
constructor() {
|
||
|
this.head = void 0;
|
||
|
this.tail = void 0;
|
||
|
}
|
||
|
push(item) {
|
||
|
if (this.head === void 0) {
|
||
|
this.head = { item };
|
||
|
this.tail = this.head;
|
||
|
} else {
|
||
|
this.tail.next = { item };
|
||
|
this.tail = this.tail.next;
|
||
|
}
|
||
|
}
|
||
|
isEmpty() {
|
||
|
return this.head === void 0;
|
||
|
}
|
||
|
shift() {
|
||
|
var _a, _b;
|
||
|
const val = (_a = this.head) == null ? void 0 : _a.item;
|
||
|
this.head = (_b = this.head) == null ? void 0 : _b.next;
|
||
|
return val;
|
||
|
}
|
||
|
}
|
||
|
export {
|
||
|
EagerAsyncIterableIterator,
|
||
|
addAttribute,
|
||
|
bufferIterators,
|
||
|
defineScriptVars,
|
||
|
formatList,
|
||
|
internalSpreadAttributes,
|
||
|
renderElement,
|
||
|
toAttributeString,
|
||
|
voidElementNames
|
||
|
};
|