apexcharts
Version:
A JavaScript Chart Library
381 lines (337 loc) • 8.07 kB
JavaScript
// @ts-check
/**
* Minimal DOM shim for Server-Side Rendering
* Provides just enough SVG element emulation to create chart structure without full DOM
*/
/**
* Mock SVG element for SSR environment
*/
class SSRElement {
/**
* @param {string} nodeName
* @param {any} namespaceURI
*/
constructor(nodeName, namespaceURI = null) {
this.nodeName = nodeName
this.namespaceURI = namespaceURI
this.attributes = new Map()
/** @type {any[]} */
this.children = []
this.textContent = ''
this.style = {}
this.classList = new SSRClassList()
this.parentNode = /** @type {SSRElement | null} */ (null)
/** @type {number|undefined} */ this._ssrWidth = undefined
/** @type {number|undefined} */ this._ssrHeight = undefined
/** @type {boolean|undefined} */ this._ssrMode = undefined
}
/**
* @param {string} name
* @param {any} value
*/
setAttribute(name, value) {
this.attributes.set(name, value)
}
/**
* @param {string} name
*/
getAttribute(name) {
return this.attributes.get(name)
}
/**
* @param {string} name
*/
removeAttribute(name) {
this.attributes.delete(name)
}
/**
* @param {string} name
*/
hasAttribute(name) {
return this.attributes.has(name)
}
/**
* @param {any} child
*/
appendChild(child) {
if (child && child !== this) {
// Mirror real DOM: re-parenting removes child from previous parent
if (child.parentNode && child.parentNode !== this) {
child.parentNode.removeChild(child)
} else if (child.parentNode === this) {
// Already a child — move to end (matches browser DOM behaviour)
const index = this.children.indexOf(child)
if (index !== -1) this.children.splice(index, 1)
}
child.parentNode = this
this.children.push(child)
}
return child
}
/**
* @param {any} child
*/
removeChild(child) {
const index = this.children.indexOf(child)
if (index !== -1) {
this.children.splice(index, 1)
child.parentNode = null
}
return child
}
/**
* @param {any} newNode
* @param {any} referenceNode
*/
insertBefore(newNode, referenceNode) {
if (!referenceNode) {
return this.appendChild(newNode)
}
// Mirror real DOM: remove from previous parent before inserting
if (newNode.parentNode && newNode.parentNode !== this) {
newNode.parentNode.removeChild(newNode)
} else if (newNode.parentNode === this) {
const existingIndex = this.children.indexOf(newNode)
if (existingIndex !== -1) this.children.splice(existingIndex, 1)
}
const index = this.children.indexOf(referenceNode)
if (index !== -1) {
newNode.parentNode = this
this.children.splice(index, 0, newNode)
}
return newNode
}
cloneNode(deep = false) {
const clone = new SSRElement(this.nodeName, this.namespaceURI)
clone.textContent = this.textContent
// Copy attributes
this.attributes.forEach((value, key) => {
clone.attributes.set(key, value)
})
// Copy styles
Object.assign(clone.style, this.style)
// Deep clone children
if (deep) {
this.children.forEach((child) => {
if (child.cloneNode) {
clone.appendChild(child.cloneNode(true))
}
})
}
return clone
}
getBoundingClientRect() {
// Return default dimensions for SSR
return {
width: this._ssrWidth || 0,
height: this._ssrHeight || 0,
top: 0,
left: 0,
right: this._ssrWidth || 0,
bottom: this._ssrHeight || 0,
x: 0,
y: 0,
}
}
getRootNode() {
/** @type {SSRElement} */
let root = this
while (root.parentNode) {
root = root.parentNode
}
return root
}
querySelector() {
return null
}
querySelectorAll() {
return []
}
getElementsByClassName() {
return []
}
addEventListener() {}
removeEventListener() {}
get childNodes() {
return this.children
}
toString() {
let attrs = ''
this.attributes.forEach((value, key) => {
attrs += ` ${key}="${value}"`
})
if (this.children.length === 0 && !this.textContent) {
return `<${this.nodeName}${attrs}/>`
}
const childrenStr = this.children.map((c) => c.toString()).join('')
return `<${this.nodeName}${attrs}>${this.textContent}${childrenStr}</${this.nodeName}>`
}
// Property getters/setters
get innerHTML() {
return this.children.map((c) => c.toString()).join('')
}
set innerHTML(value) {
this.children = []
this.textContent = value
}
get outerHTML() {
return this.toString()
}
get isConnected() {
return true
}
}
/**
* Mock ClassList for SSR
*/
class SSRClassList {
constructor() {
this.classes = new Set()
}
add(/** @type {any[]} */ ...classNames) {
classNames.forEach((name) => this.classes.add(name))
}
remove(/** @type {any[]} */ ...classNames) {
classNames.forEach((name) => this.classes.delete(name))
}
/**
* @param {string} className
*/
contains(className) {
return this.classes.has(className)
}
/**
* @param {string} className
* @param {any} force
*/
toggle(className, force) {
if (force === true) {
this.classes.add(className)
return true
} else if (force === false) {
this.classes.delete(className)
return false
} else {
if (this.classes.has(className)) {
this.classes.delete(className)
return false
} else {
this.classes.add(className)
return true
}
}
}
toString() {
return Array.from(this.classes).join(' ')
}
}
/**
* Main DOM shim class
*/
export class SSRDOMShim {
constructor() {
this.SVGNS = 'http://www.w3.org/2000/svg'
this.XLINKNS = 'http://www.w3.org/1999/xlink'
}
/**
* Create SVG element with namespace
* @param {string} namespaceURI - Namespace URI
* @param {string} qualifiedName - Element tag name
* @returns {SSRElement} Mock SVG element
*/
createElementNS(namespaceURI, qualifiedName) {
return new SSRElement(qualifiedName, namespaceURI)
}
/**
* Create text node
* @param {string} data - Text content
* @returns {object} Text node mock
*/
createTextNode(data) {
const node = {
nodeName: '#text',
nodeType: 3,
textContent: data,
toString() {
return node.textContent
},
}
return node
}
/**
* Query selector (returns null in SSR)
* @returns {null}
*/
querySelector() {
return null
}
/**
* Query selector all (returns empty array in SSR)
* @returns {any[]}
*/
querySelectorAll() {
return []
}
/**
* Get computed style (returns empty object in SSR)
* @returns {object}
*/
getComputedStyle() {
return {}
}
/**
* Get bounding client rect for element
* @param {SSRElement} element - Element to measure
* @returns {object} Mock dimensions
*/
getBoundingClientRect(element) {
if (element && element.getBoundingClientRect) {
return element.getBoundingClientRect()
}
return {
width: 0,
height: 0,
top: 0,
left: 0,
right: 0,
bottom: 0,
x: 0,
y: 0,
}
}
/**
* Create mock XMLSerializer for SSR
* @returns {object} XMLSerializer mock
*/
createXMLSerializer() {
return {
/**
* @param {Element} element
*/
serializeToString(element) {
return element.toString ? element.toString() : ''
},
}
}
/**
* Create mock DOMParser for SSR
* @returns {object} DOMParser mock
*/
createDOMParser() {
return {
/**
* @param {string} str
* @param {string} _type
*/
parseFromString(str, _type) {
// Basic mock - returns a simple element
const root = new SSRElement('root')
root.innerHTML = str
return {
documentElement: root,
}
},
}
}
}
export { SSRElement, SSRClassList }