/** * Low-level Effect wrappers for Document APIS and usage from the Context. * @since 8.19.0 */ import * as Context from "@typed/context/Extensions" import type * as Effect from "effect/Effect" import type * as Scope from "effect/Scope" import type { AddEventListenerOptions } from "./EventTarget.js" import { addEventListener } from "./EventTarget.js" /** * @since 8.19.0 * @category models */ export interface Document extends globalThis.Document {} /** * @since 8.19.0 * @category context */ export const Document: Context.Tagged = Context.Tagged("@typed/dom/Document") /** * Retrieve the body element from the current Document * @since 8.19.0 * @category elements */ export const getBody: Effect.Effect = Document.with( (d) => d.body as HTMLBodyElement ) /** * Retrieve the head element from the current Document * @since 8.19.0 * @category elements */ export const getHead: Effect.Effect = Document.with((d) => d.head) /** * Add an event listener to the document * @since 8.19.0 * @category events */ export const addDocumentListener = ( options: AddEventListenerOptions ): Effect.Effect => Document.withEffect((d) => addEventListener(d, options)) /** * Create a new element * @since 8.19.0 * @category elements */ export const createElement: ( tagName: TagName ) => Effect.Effect = < TagName extends keyof HTMLElementTagNameMap >( tagName: TagName ): Effect.Effect => Document.with((d) => d.createElement(tagName)) /** * Create a new element with a namespace * @since 8.19.0 * @category elements */ export const createElementNS = ( namespaceURI: string, tagName: string ): Effect.Effect => Document.with((d) => d.createElementNS(namespaceURI, tagName)) /** * Create a new SVG element * @since 8.19.0 * @category elements */ export const createSvgElement = ( tagName: TagName ): Effect.Effect => createElementNS("http://www.w3.org/2000/svg", tagName) as Effect.Effect< SVGElementTagNameMap[TagName], never, Document > /** * Create a new text node * @since 8.19.0 * @category elements */ export const createTextNode = (data: string): Effect.Effect => Document.with((d) => d.createTextNode(data)) /** * Create a new comment node * @since 8.19.0 * @category elements */ export const createComment = (data: string): Effect.Effect => Document.with((d) => d.createComment(data)) /** * Create a new document fragment * @since 8.19.0 * @category elements */ export const createDocumentFragment: Effect.Effect = Document.with((d) => d.createDocumentFragment() ) /** * Create a new TreeWalker * @since 8.19.0 */ export const createTreeWalker = (root: Node, whatToShow?: number, filter?: NodeFilter | null) => Document.with((d) => d.createTreeWalker(root, whatToShow, filter)) /** * Create a new Range * @since 8.19.0 */ export const createRange: Effect.Effect = Document.with((d) => d.createRange()) /** * Create a new Attr * @since 8.19.0 * @category atrributes */ export const createAttributeNS = ( namespace: string | null, qualifiedName: string ): Effect.Effect => Document.with((d) => d.createAttributeNS(namespace, qualifiedName)) /** * Get the element * @since 8.19.0 */ export const getDocumentElement: Effect.Effect = Document.with( (d) => d.documentElement ) /** * Import a node into the current document * @since 8.19.0 */ export const importNode: ( node: T, deep?: boolean ) => Effect.Effect = (node: T, deep?: boolean) => Document.with((d) => d.importNode(node, deep)) /** * Update the title of the document * @since 8.19.0 */ export const updateTitle: (title: string) => Effect.Effect = ( title: string ) => Document.with((d) => (d.title = title)) /** * Params for updating a meta tag * @since 8.19.0 */ export type MetaParams = { readonly name: string readonly content: string readonly httpEquiv?: string } /** * Update a meta tag * @since 8.19.0 * @category metadata */ export const updateMeta: (params: MetaParams) => Effect.Effect = ( params: MetaParams ) => Document.with((d) => { const meta = d.querySelector(`meta[name="${params.name}"]`) ?? createNewHeadElement(d, "meta") setAttrs(meta, params) return meta }) /** * Update a link tag * @since 8.19.0 * @category metadata */ export type LinkParams = { readonly rel: string readonly href: string readonly crossOrigin?: "anonymous" | "use-credentials" readonly hreflang?: string readonly media?: string readonly referrerPolicy?: | "no-referrer" | "no-referrer-when-downgrade" | "origin" | "origin-when-cross-origin" | "same-origin" | "strict-origin" | "strict-origin-when-cross-origin" | "unsafe-url" readonly sizes?: string readonly type?: string } /** * Update a link tag * @since 8.19.0 * @category metadata */ export const updateLink: (params: LinkParams) => Effect.Effect = ( params: LinkParams ) => Document.with((d) => { const link = d.querySelector(`link[rel="${params.rel}"][href="${params.href}"]`) ?? createNewHeadElement(d, "link") setAttrs(link, params) return link }) function createNewHeadElement( document: Document, tagName: T ) { const newLink = document.createElement(tagName) document.head.appendChild(newLink) return newLink } function setAttrs(element: Element, attrs: Record) { Object.entries(attrs).forEach(([key, value]) => element.setAttribute(key, value)) }