291 lines
7.0 KiB
JavaScript
291 lines
7.0 KiB
JavaScript
import { defineComponent, inject, onUnmounted, provide, reactive } from "vue";
|
|
import { useHead } from "#app/composables/head";
|
|
const HeadComponentCtxSymbol = Symbol("head-component");
|
|
const TagPositionProps = {
|
|
/**
|
|
* @deprecated Use tagPosition
|
|
*/
|
|
body: { type: Boolean, default: void 0 },
|
|
tagPosition: { type: String }
|
|
};
|
|
const normalizeProps = (_props) => {
|
|
const props = Object.fromEntries(
|
|
Object.entries(_props).filter(([_, value]) => value !== void 0)
|
|
);
|
|
if (typeof props.body !== "undefined") {
|
|
props.tagPosition = props.body ? "bodyClose" : "head";
|
|
}
|
|
if (typeof props.renderPriority !== "undefined") {
|
|
props.tagPriority = props.renderPriority;
|
|
}
|
|
return props;
|
|
};
|
|
function useHeadComponentCtx() {
|
|
return inject(HeadComponentCtxSymbol, createHeadComponentCtx, true);
|
|
}
|
|
function createHeadComponentCtx() {
|
|
const prev = inject(HeadComponentCtxSymbol, null);
|
|
if (prev) {
|
|
return prev;
|
|
}
|
|
const input = reactive({});
|
|
const entry = useHead(input);
|
|
const ctx = { input, entry };
|
|
provide(HeadComponentCtxSymbol, ctx);
|
|
return ctx;
|
|
}
|
|
const globalProps = {
|
|
accesskey: String,
|
|
autocapitalize: String,
|
|
autofocus: {
|
|
type: Boolean,
|
|
default: void 0
|
|
},
|
|
class: { type: [String, Object, Array], default: void 0 },
|
|
contenteditable: {
|
|
type: Boolean,
|
|
default: void 0
|
|
},
|
|
contextmenu: String,
|
|
dir: String,
|
|
draggable: {
|
|
type: Boolean,
|
|
default: void 0
|
|
},
|
|
enterkeyhint: String,
|
|
exportparts: String,
|
|
hidden: {
|
|
type: Boolean,
|
|
default: void 0
|
|
},
|
|
id: String,
|
|
inputmode: String,
|
|
is: String,
|
|
itemid: String,
|
|
itemprop: String,
|
|
itemref: String,
|
|
itemscope: String,
|
|
itemtype: String,
|
|
lang: String,
|
|
nonce: String,
|
|
part: String,
|
|
slot: String,
|
|
spellcheck: {
|
|
type: Boolean,
|
|
default: void 0
|
|
},
|
|
style: { type: [String, Object, Array], default: void 0 },
|
|
tabindex: String,
|
|
title: String,
|
|
translate: String,
|
|
/**
|
|
* @deprecated Use tagPriority
|
|
*/
|
|
renderPriority: [String, Number],
|
|
/**
|
|
* Unhead prop to modify the priority of the tag.
|
|
*/
|
|
tagPriority: { type: [String, Number] }
|
|
};
|
|
export const NoScript = defineComponent({
|
|
name: "NoScript",
|
|
inheritAttrs: false,
|
|
props: {
|
|
...globalProps,
|
|
...TagPositionProps,
|
|
title: String
|
|
},
|
|
setup(props, { slots }) {
|
|
const { input } = useHeadComponentCtx();
|
|
input.noscript ||= [];
|
|
const idx = input.noscript.push({}) - 1;
|
|
onUnmounted(() => input.noscript[idx] = null);
|
|
return () => {
|
|
const noscript = normalizeProps(props);
|
|
const slotVnodes = slots.default?.();
|
|
const textContent = slotVnodes ? slotVnodes.filter(({ children }) => children).map(({ children }) => children).join("") : "";
|
|
if (textContent) {
|
|
noscript.innerHTML = textContent;
|
|
}
|
|
input.noscript[idx] = noscript;
|
|
return null;
|
|
};
|
|
}
|
|
});
|
|
export const Link = defineComponent({
|
|
name: "Link",
|
|
inheritAttrs: false,
|
|
props: {
|
|
...globalProps,
|
|
...TagPositionProps,
|
|
as: String,
|
|
crossorigin: String,
|
|
disabled: Boolean,
|
|
fetchpriority: String,
|
|
href: String,
|
|
hreflang: String,
|
|
imagesizes: String,
|
|
imagesrcset: String,
|
|
integrity: String,
|
|
media: String,
|
|
prefetch: {
|
|
type: Boolean,
|
|
default: void 0
|
|
},
|
|
referrerpolicy: String,
|
|
rel: String,
|
|
sizes: String,
|
|
title: String,
|
|
type: String,
|
|
/** @deprecated **/
|
|
methods: String,
|
|
/** @deprecated **/
|
|
target: String
|
|
},
|
|
setup(props) {
|
|
const { input } = useHeadComponentCtx();
|
|
input.link ||= [];
|
|
const idx = input.link.push({}) - 1;
|
|
onUnmounted(() => input.link[idx] = null);
|
|
return () => {
|
|
input.link[idx] = normalizeProps(props);
|
|
return null;
|
|
};
|
|
}
|
|
});
|
|
export const Base = defineComponent({
|
|
name: "Base",
|
|
inheritAttrs: false,
|
|
props: {
|
|
...globalProps,
|
|
href: String,
|
|
target: String
|
|
},
|
|
setup(props) {
|
|
const { input } = useHeadComponentCtx();
|
|
onUnmounted(() => input.base = null);
|
|
return () => {
|
|
input.base = normalizeProps(props);
|
|
return null;
|
|
};
|
|
}
|
|
});
|
|
export const Title = defineComponent({
|
|
name: "Title",
|
|
inheritAttrs: false,
|
|
setup(_, { slots }) {
|
|
const { input } = useHeadComponentCtx();
|
|
onUnmounted(() => input.title = null);
|
|
return () => {
|
|
const defaultSlot = slots.default?.();
|
|
input.title = defaultSlot?.[0]?.children ? String(defaultSlot?.[0]?.children) : void 0;
|
|
if (import.meta.dev) {
|
|
if (defaultSlot && (defaultSlot.length > 1 || defaultSlot[0] && typeof defaultSlot[0].children !== "string")) {
|
|
console.error("<Title> can take only one string in its default slot.");
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
}
|
|
});
|
|
export const Meta = defineComponent({
|
|
name: "Meta",
|
|
inheritAttrs: false,
|
|
props: {
|
|
...globalProps,
|
|
charset: String,
|
|
content: String,
|
|
httpEquiv: String,
|
|
name: String,
|
|
property: String
|
|
},
|
|
setup(props) {
|
|
const { input } = useHeadComponentCtx();
|
|
input.meta ||= [];
|
|
const idx = input.meta.push({}) - 1;
|
|
onUnmounted(() => input.meta[idx] = null);
|
|
return () => {
|
|
const meta = { "http-equiv": props.httpEquiv, ...normalizeProps(props) };
|
|
if ("httpEquiv" in meta) {
|
|
delete meta.httpEquiv;
|
|
}
|
|
input.meta[idx] = meta;
|
|
return null;
|
|
};
|
|
}
|
|
});
|
|
export const Style = defineComponent({
|
|
name: "Style",
|
|
inheritAttrs: false,
|
|
props: {
|
|
...globalProps,
|
|
...TagPositionProps,
|
|
type: String,
|
|
media: String,
|
|
nonce: String,
|
|
title: String,
|
|
/** @deprecated **/
|
|
scoped: {
|
|
type: Boolean,
|
|
default: void 0
|
|
}
|
|
},
|
|
setup(props, { slots }) {
|
|
const { input } = useHeadComponentCtx();
|
|
input.style ||= [];
|
|
const idx = input.style.push({}) - 1;
|
|
onUnmounted(() => input.style[idx] = null);
|
|
return () => {
|
|
const style = normalizeProps(props);
|
|
const textContent = slots.default?.()?.[0]?.children;
|
|
if (textContent) {
|
|
if (import.meta.dev && typeof textContent !== "string") {
|
|
console.error("<Style> can only take a string in its default slot.");
|
|
}
|
|
input.style[idx] = style;
|
|
style.textContent = textContent;
|
|
}
|
|
return null;
|
|
};
|
|
}
|
|
});
|
|
export const Head = defineComponent({
|
|
name: "Head",
|
|
inheritAttrs: false,
|
|
setup: (_props, ctx) => {
|
|
createHeadComponentCtx();
|
|
return () => ctx.slots.default?.();
|
|
}
|
|
});
|
|
export const Html = defineComponent({
|
|
name: "Html",
|
|
inheritAttrs: false,
|
|
props: {
|
|
...globalProps,
|
|
manifest: String,
|
|
version: String,
|
|
xmlns: String
|
|
},
|
|
setup(_props, ctx) {
|
|
const { input } = useHeadComponentCtx();
|
|
onUnmounted(() => input.htmlAttrs = null);
|
|
return () => {
|
|
input.htmlAttrs = { ..._props, ...ctx.attrs };
|
|
return ctx.slots.default?.();
|
|
};
|
|
}
|
|
});
|
|
export const Body = defineComponent({
|
|
name: "Body",
|
|
inheritAttrs: false,
|
|
props: globalProps,
|
|
setup(_props, ctx) {
|
|
const { input } = useHeadComponentCtx();
|
|
onUnmounted(() => input.bodyAttrs = null);
|
|
return () => {
|
|
input.bodyAttrs = { ..._props, ...ctx.attrs };
|
|
return ctx.slots.default?.();
|
|
};
|
|
}
|
|
});
|