refactor: ♻️ type infer

This commit is contained in:
bubkoo
2021-04-06 17:39:14 +08:00
parent 5856d71855
commit 23acfd903f
9 changed files with 439 additions and 134 deletions

View File

@ -1,63 +1,88 @@
import { DomUtil } from '../util/dom'
import { Global } from '../global'
import type { Svg } from './container/svg'
import type { Dom } from './dom'
import { Registry } from './registry'
import { Base } from './base'
import { ElementMapping, HTMLTagMapping, SVGTagMapping } from './types'
export namespace Adopter {
const cache: WeakMap<Node, Base> = new WeakMap()
const store: WeakMap<Node, Dom> = new WeakMap()
export function ref(node: Node, instance?: Base | null) {
export function cache(node: Node, instance?: Dom | null) {
if (instance == null) {
cache.delete(node)
store.delete(node)
} else {
cache.set(node, instance)
store.set(node, instance)
}
}
export function adopt<TVector extends Base>(node: Node): TVector
export function adopt<TVector extends Base>(
node?: Node | null,
): TVector | null
export function adopt<TVector extends Base>(
node?: Node | null,
): TVector | null {
export function adopt(node: null): null
export function adopt<T extends Node>(node: T): ElementMapping<T>
export function adopt<T extends Dom>(node: Node | ChildNode): T
export function adopt<T extends Dom>(node: Node | ChildNode | null): T | null
export function adopt<T extends Node>(
node?: T | null,
): ElementMapping<T> | null
export function adopt<T extends Node>(
node?: T | null,
): ElementMapping<T> | null {
if (node == null) {
return null
}
// make sure a node isn't already adopted
const instance = cache.get(node)
if (instance != null && instance instanceof Base) {
return instance as TVector
const instance = store.get(node)
if (instance != null) {
return instance as ElementMapping<T>
}
const cls = Registry.getClass(node)
return new cls(node) // eslint-disable-line new-cap
const Type = Registry.getClass(node)
return new Type(node) as ElementMapping<T>
}
let adopter = adopt
export type Target<TVector extends Base = Base> = TVector | Node | string
export type Target<T extends Dom = Dom> = T | Node | string
export function makeInstance<TVector extends Base>(
node?: TVector | Node | string,
export function makeInstance(): Svg
export function makeInstance(node: undefined | null): Svg
export function makeInstance<T extends Dom>(instance: T): T
export function makeInstance<T extends Dom>(target: Target<T>): T
export function makeInstance<T extends Node>(node: T): ElementMapping<T>
export function makeInstance<T extends keyof SVGTagMapping>(
tagName: T,
): SVGTagMapping[T]
export function makeInstance<T extends keyof SVGTagMapping>(
tagName: T,
isHTML: false,
): SVGTagMapping[T]
export function makeInstance<T extends keyof HTMLTagMapping>(
tagName: T,
isHTML: true,
): HTMLTagMapping[T]
export function makeInstance<T extends Dom>(
tagName: string,
isHTML: boolean,
): T
export function makeInstance<T extends Dom>(
node?: T | Node | string,
isHTML = false,
): TVector {
): T {
if (node == null) {
const root = Registry.getRoot()
return new root() // eslint-disable-line new-cap
const Root = Registry.getRoot()
return new Root() as T
}
if (node instanceof Base) {
return node
if (node instanceof Node) {
return adopter(node) as T
}
if (typeof node === 'object') {
return adopter(node)
return node
}
if (typeof node === 'string' && node.charAt(0) !== '<') {
return adopter(Global.document.querySelector(node)) as TVector
return adopter(Global.document.querySelector(node)) as T
}
// Make sure, that HTML elements are created with the correct namespace
@ -73,7 +98,7 @@ export namespace Adopter {
// make sure, that element doesnt have its wrapper attached
wrapper.firstChild!.remove()
return result as TVector
return result as T
}
export function mock(instance = adopt) {

View File

@ -4,16 +4,14 @@ import { Registry } from './registry'
export abstract class Base {}
export namespace Base {
type Class = { new (...arguments_: any[]): Base }
export function register(name: string, asRoot?: boolean) {
return <TClass extends Class>(ctor: TClass) => {
return (ctor: Registry.Definition) => {
Registry.register(ctor, name, asRoot)
}
}
export function mixin(...source: any[]) {
return <TClass extends Class>(ctor: TClass) => {
return (ctor: Registry.Definition) => {
Obj.applyMixins(ctor, ...source)
}
}

View File

@ -9,7 +9,7 @@ export namespace Decorator {
const raw = descriptor.value
descriptor.value = function (
this: VectorElement<TSVGElement>,
...arguments_: any[]
...args: any[]
) {
const defs = this.defs()
if (defs == null) {
@ -18,7 +18,7 @@ export namespace Decorator {
'Please ensure that the current element is attached into any SVG context.',
)
}
return raw.call(this, ...arguments_)
return raw.call(this, ...args)
}
}
}

View File

@ -1,10 +1,10 @@
import { Attrs, Class } from '../../types'
import type { Attrs } from '../../types'
import { Global } from '../../global'
import { DomUtil } from '../../util/dom'
import { Attr } from '../../util/attr'
import { Svg } from '../container/svg'
import { DomUtil } from '../../util/dom'
import { Adopter } from '../adopter'
import { Data } from './data'
import { Affix } from './affix'
import { Style } from './style'
import { Primer } from './primer'
import { Memory } from './memory'
@ -12,30 +12,70 @@ import { Listener } from './listener'
import { ClassName } from './classname'
import { Transform } from './transform'
import { EventEmitter } from './event-emitter'
import { ElementMapping, HTMLTagMapping, SVGTagMapping } from '../types'
import { Registry } from '../registry'
@Dom.register('Dom')
@Dom.mixin(EventEmitter, ClassName, Style, Data, Memory, Listener, Transform)
@Dom.mixin(
ClassName,
Style,
Transform,
Data,
Affix,
Memory,
EventEmitter,
Listener,
)
export class Dom<TNode extends Node = Node> extends Primer<TNode> {
constructor()
constructor(node: Node)
constructor(attrs: Attrs | null)
constructor(node?: TNode | string | null, attrs?: Attrs | null)
constructor(node?: TNode | string | Attrs | null, attrs?: Attrs | null)
constructor(node?: TNode | string | Attrs | null, attrs?: Attrs | null) {
super(node, attrs)
this.restoreAffix()
Adopter.cache(this.node, this)
}
/**
* Returns the first child of the element.
*/
first<T extends Dom = Dom>(): T | null {
return Dom.adopt<T>(this.node.firstChild)
}
/**
* Returns the last child of the element.
*/
last<T extends Dom = Dom>(): T | null {
return Dom.adopt<T>(this.node.lastChild)
}
/**
* Returns an element on a given position in the element's children array.
*/
get<T extends Dom = Dom>(index: number): T | null {
return Dom.adopt<T>(this.node.childNodes[index])
}
find<T extends Dom = Dom>(selectors: string): T[] {
return Dom.find<T>(selectors, DomUtil.toElement(this.node))
/**
* Returns an array of elements matching the given selector.
*/
find<T extends Dom = Dom>(selector: string): T[] {
return Dom.find<T>(selector, DomUtil.toElement(this.node))
}
findOne<T extends Dom = Dom>(selectors: string): T | null {
return Dom.findOne<T>(selectors, DomUtil.toElement(this.node))
/**
* Returns the first element matching the given selector.
*/
findOne<T extends Dom = Dom>(selector: string): T | null {
return Dom.findOne<T>(selector, DomUtil.toElement(this.node))
}
/**
* Returns `true` if the element matching the given selector.
*/
matches(selector: string): boolean {
const elem = DomUtil.toElement(this.node)
const node = this.node as any
@ -51,6 +91,9 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return matcher ? matcher.call(elem, selector) : false
}
/**
* Returns an array of children elements.
*/
children<T extends Dom>(): T[] {
const elems: T[] = []
this.node.childNodes.forEach((node) => {
@ -59,6 +102,9 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return elems
}
/**
* Removes all elements from the element.
*/
clear() {
while (this.node.lastChild) {
this.node.lastChild.remove()
@ -66,15 +112,22 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
clone<T extends Dom>(deep = true): T {
/**
* Returns an exact copy of the element.
*/
clone(deep = true) {
// write dom data to the dom so the clone can pickup the data
this.storeAssets()
this.storeAffix(deep)
// clone element and assign new id
const ctor = this.constructor as new (node: Node) => T
// eslint-disable-next-line new-cap
return new ctor(DomUtil.assignNewId(this.node.cloneNode(deep)))
const Ctor = this.constructor as new (node: Node) => ElementMapping<TNode>
return new Ctor(DomUtil.assignNewId(this.node.cloneNode(deep)))
}
/**
* Iterates over all the children of the element.
* Deep traversing is also possible by passing `true` as the second argument.
* @returns
*/
eachChild<T extends Dom>(
iterator: (this: T, child: T, index: number, children: T[]) => void,
deep?: boolean,
@ -91,21 +144,58 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
indexOf(element: Dom): number {
/**
* Returns the index of given node.
* Returns `-1` when it is not a child.
*/
indexOf(node: Node): number
/**
* Returns the index of given element.
* Returns `-1` when it is not a child.
*/
indexOf(element: Dom): number
/**
* Returns the index of given element or node.
* Returns `-1` when it is not a child.
*/
indexOf(element: Dom | Node): number
indexOf(element: Dom | Node): number {
const children = Array.prototype.slice.call(this.node.childNodes) as Node[]
return children.indexOf(element.node)
return children.indexOf(element instanceof Node ? element : element.node)
}
/**
* Returns `true` when the given node is a child of the element.
*/
has(node: Node): boolean
/**
* Returns `true` when the given element is a child of the element.
*/
has(element: Dom): boolean
/**
* Returns `true` when the given element or node is a child of the element.
*/
has(element: Dom | Node): boolean
has(element: Dom | Node): boolean {
return this.indexOf(element) !== -1
}
/**
* Returns the index of the element in it's parent.
* Returns `-1` when the element do not have a parent.
*/
index(): number {
const parent: Dom | null = this.parent()
return parent ? parent.indexOf(this) : -1
}
has(element: Dom): boolean {
return this.indexOf(element) !== -1
}
/**
* Returns the element's id, generate new id if no id set.
*/
id(): string
/**
* Set id of the element.
*/
id(id: string | null): this
id(id?: string | null) {
const elem = DomUtil.toElement(this.node)
@ -119,23 +209,38 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return typeof id === 'undefined' ? this.attr('id') : this.attr('id', id)
}
parent<T extends Dom = Dom>(selectors?: string | Class): T | null {
/**
* Returns the parent element if exist.
*/
parent<T extends Dom = Dom>(): T | null
/**
* Iterates over the ancestors and returns the ancestor mathcing the selector.
*/
parent<T extends Dom = Dom>(selector: string): T | null
/**
* Iterates over the ancestors and returns the ancestor mathcing the type.
*/
parent<T extends Dom = Dom>(parentType: Registry.Definition): T | null
parent<T extends Dom = Dom>(selector?: string | Registry.Definition): T | null
parent<T extends Dom = Dom>(
selector?: string | Registry.Definition,
): T | null {
if (this.node.parentNode == null) {
return null
}
let parent: T | null = Dom.adopt<T>(this.node.parentNode)
if (selectors == null) {
if (selector == null) {
return parent
}
// loop trough ancestors if type is given
do {
if (
typeof selectors === 'string'
? parent.matches(selectors)
: parent instanceof selectors
typeof selector === 'string'
? parent.matches(selector)
: parent instanceof selector
) {
return parent
}
@ -144,12 +249,36 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return null
}
parents<T extends Dom = Dom>(): T[]
parents<T extends Dom = Dom>(until: null): T[]
parents<T extends Dom = Dom>(untilElement: Dom): T[]
parents<T extends Dom = Dom>(untilNode: Node): T[]
parents<T extends Dom = Dom>(untilSelector: string): T[]
parents<T extends Dom = Dom>(until: Adopter.Target<Dom> | null): T[]
parents<T extends Dom = Dom>(until?: Adopter.Target<Dom> | null) {
const stop = until ? Adopter.makeInstance<Dom>(until) : null
const parents: T[] = []
let parent = this.parent()
while (parent && !parent.isDocument() && !parent.isDocumentFragment()) {
parents.push(parent as T)
if (stop && parent.node === stop.node) {
break
}
parent = parent.parent()
}
return parents
}
add<T extends Dom>(element: T, index?: number): this
add<T extends Node>(node: T, index?: number): this
add(selector: string, index?: number): this
add<T extends Dom>(element: Adopter.Target<T>, index?: number): this
add<T extends Dom>(element: Adopter.Target<T>, index?: number): this {
const instance = Adopter.makeInstance<T>(element)
// If non-root svg nodes are added we have to remove their namespaces
if (instance.isSVGSVGElement()) {
const svg = Dom.adopt<Svg>(instance.node)
const svg = Dom.adopt(instance.node as SVGSVGElement)
svg.removeNamespace()
}
@ -162,40 +291,72 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
append<T extends Dom>(element: T): this
append<T extends Node>(node: T): this
append(selector: string): this
append<T extends Dom>(element: Adopter.Target<T>): this
append<T extends Dom>(element: Adopter.Target<T>): this {
return this.add(element)
}
prepend<T extends Dom>(element: T): this
prepend<T extends Node>(node: T): this
prepend(selector: string): this
prepend<T extends Dom>(element: Adopter.Target<T>): this
prepend<T extends Dom>(element: Adopter.Target<T>): this {
return this.add(element, 0)
}
appendTo<T extends Dom>(parent: Adopter.Target<T>): this {
return this.addTo(parent)
}
addTo<T extends Dom>(parentElement: T, index?: number): this
addTo<T extends Node>(parentNode: T, index?: number): this
addTo(selector: string, index?: number): this
addTo<T extends Dom>(parent: Adopter.Target<T>, index?: number): this
addTo<T extends Dom>(parent: Adopter.Target<T>, index?: number): this {
return Adopter.makeInstance<T>(parent).put(this, index)
}
appendTo<T extends Dom>(parentElement: T): this
appendTo<T extends Node>(parentNode: T): this
appendTo(selector: string): this
appendTo<T extends Dom>(parent: Adopter.Target<T>): this
appendTo<T extends Dom>(parent: Adopter.Target<T>): this {
return this.addTo(parent)
}
/**
* Adds the given element to the end fo child list or the optional child
* position and returns the added element.
*/
put<T extends Dom>(element: T, index?: number): T
/**
* Adds the given node to the end fo child list or the optional child position
* and returns the added element.
*/
put<T extends Node>(node: T, index?: number): ElementMapping<T>
/**
* Adds the node matching the selector to end fo child list or the optional
* child position and returns the added element.
*/
put<T extends Dom>(selector: string, index?: number): T
put<T extends Dom>(element: Adopter.Target<T>, index?: number): T
put<T extends Dom>(element: Adopter.Target<T>, index?: number): T {
const instance = Adopter.makeInstance<T>(element)
this.add(instance, index)
return instance
}
putIn<T extends Dom>(parentElement: T, index?: number): T
putIn<T extends Node>(parentNode: T, index?: number): ElementMapping<T>
putIn<T extends Dom>(selector: string, index?: number): T
putIn<T extends Dom>(parent: Adopter.Target<T>, index?: number): T
putIn<T extends Dom>(parent: Adopter.Target<T>, index?: number): T {
return Adopter.makeInstance<T>(parent).add(this, index)
}
element<T extends Dom>(nodeName: string, attrs?: Attrs | null): T {
const elem = Adopter.makeInstance<T>(nodeName)
if (attrs) {
elem.attr(attrs)
}
return this.put(elem)
}
replace<T extends Dom>(element: T, index?: number): T
replace<T extends Node>(node: T, index?: number): ElementMapping<T>
replace<T extends Dom>(selector: string, index?: number): T
replace<T extends Dom>(element: Adopter.Target<T>, index?: number): T
replace<T extends Dom>(element: Adopter.Target<T>): T {
const instance = Adopter.makeInstance<T>(element)
@ -206,6 +367,22 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return instance
}
element<T extends keyof SVGElementTagNameMap>(
nodeName: T,
attrs?: Attrs | null,
): SVGTagMapping[T]
element<T extends keyof HTMLElementTagNameMap>(
nodeName: T,
attrs?: Attrs | null,
): HTMLTagMapping[T]
element<T extends Dom>(nodeName: string, attrs?: Attrs | null): T {
const elem = Adopter.makeInstance<T>(nodeName)
if (attrs) {
elem.attr(attrs)
}
return this.put(elem)
}
remove() {
const parent = this.parent()
if (parent) {
@ -215,11 +392,17 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
removeElement(element: Dom) {
this.node.removeChild(element.node)
removeElement(node: Node): this
removeElement(element: Dom): this
removeElement(element: Dom | Node) {
this.node.removeChild(element instanceof Node ? element : element.node)
return this
}
before<T extends Dom>(element: T): this
before<T extends Node>(node: T): this
before(selector: string): this
before<T extends Dom>(element: Adopter.Target<T>): this
before<T extends Dom>(element: Adopter.Target<T>) {
const parent = this.parent()
if (parent) {
@ -232,6 +415,10 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
after<T extends Dom>(element: T): this
after<T extends Node>(node: T): this
after(selector: string): this
after<T extends Dom>(element: Adopter.Target<T>): this
after<T extends Dom>(element: Adopter.Target<T>) {
const parent = this.parent()
if (parent) {
@ -243,11 +430,19 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
insertBefore<T extends Dom>(element: T): this
insertBefore<T extends Node>(node: T): this
insertBefore(selector: string): this
insertBefore<T extends Dom>(element: Adopter.Target<T>): this
insertBefore<T extends Dom>(element: Adopter.Target<T>) {
Adopter.makeInstance(element).before(this)
return this
}
insertAfter<T extends Dom>(element: T): this
insertAfter<T extends Node>(node: T): this
insertAfter(selector: string): this
insertAfter<T extends Dom>(element: Adopter.Target<T>): this
insertAfter<T extends Dom>(element: Adopter.Target<T>) {
Adopter.makeInstance(element).after(this)
return this
@ -255,32 +450,34 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
siblings<T extends Dom>(): T[]
siblings<T extends Dom>(selfInclued?: boolean): T[]
siblings<T extends Dom>(selectors: string, selfInclued?: boolean): T[]
siblings(selectors?: string | boolean, selfInclued?: boolean) {
siblings<T extends Dom>(selector: string, selfInclued?: boolean): T[]
siblings(selector?: string | boolean, selfInclued?: boolean) {
const parent = this.parent()
const children = parent ? parent.children() : []
if (selectors == null) {
if (selector == null) {
return children.filter((child) => child !== this)
}
if (typeof selectors === 'boolean') {
return selectors ? children : children.filter((child) => child !== this)
if (typeof selector === 'boolean') {
return selector ? children : children.filter((child) => child !== this)
}
return children.filter(
(child) => child.matches(selectors) && (selfInclued || child !== this),
(child) => child.matches(selector) && (selfInclued || child !== this),
)
}
next<T extends Dom>(selectors?: string): T | null {
next<T extends Dom>(): T | null
next<T extends Dom>(selector: string): T | null
next<T extends Dom>(selector?: string): T | null {
const parent = this.parent()
if (parent) {
const index = this.index()
const children = parent.children<T>()
for (let i = index + 1, l = children.length; i < l; i += 1) {
const next = children[i]
if (selectors == null || next.matches(selectors)) {
if (selector == null || next.matches(selector)) {
return next
}
}
@ -288,7 +485,9 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return null
}
nextAll<T extends Dom>(selectors?: string): T[] {
nextAll<T extends Dom>(): T[]
nextAll<T extends Dom>(selector: string): T[]
nextAll<T extends Dom>(selector?: string): T[] {
const result: T[] = []
const parent = this.parent()
if (parent) {
@ -296,7 +495,7 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
const children = parent.children<T>()
for (let i = index + 1, l = children.length; i < l; i += 1) {
const next = children[i]
if (selectors == null || next.matches(selectors)) {
if (selector == null || next.matches(selector)) {
result.push(next)
}
}
@ -304,14 +503,16 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return result
}
prev<T extends Dom>(selectors?: string): T | null {
prev<T extends Dom>(): T | null
prev<T extends Dom>(selector: string): T | null
prev<T extends Dom>(selector?: string): T | null {
const parent = this.parent()
if (parent) {
const index = this.index()
const children = parent.children<T>()
for (let i = index - 1; i >= 0; i -= 1) {
const previous = children[i]
if (selectors == null || previous.matches(selectors)) {
if (selector == null || previous.matches(selector)) {
return previous
}
}
@ -320,7 +521,9 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return null
}
prevAll<T extends Dom>(selectors?: string): T[] {
prevAll<T extends Dom>(): T[]
prevAll<T extends Dom>(selector: string): T[]
prevAll<T extends Dom>(selector?: string): T[] {
const result: T[] = []
const parent = this.parent()
if (parent) {
@ -328,7 +531,7 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
const children = parent.children<T>()
for (let i = index - 1; i >= 0; i -= 1) {
const previous = children[i]
if (selectors == null || previous.matches(selectors)) {
if (selector == null || previous.matches(selector)) {
result.push(previous)
}
}
@ -370,6 +573,10 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
wrap<T extends Dom>(element: T): this
wrap<T extends Node>(node: T): this
wrap(selector: string): this
wrap<T extends Dom>(element: Adopter.Target<T>): this
wrap<T extends Dom>(node: Adopter.Target<T>): this {
const parent = this.parent()
if (!parent) {
@ -385,11 +592,6 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
return this
}
storeAssets() {
this.eachChild(() => this.storeAssets())
return this
}
toString() {
return this.id()
}
@ -441,7 +643,7 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
// The default for exports is, that the outerNode is included
isOuterXML = isOuterXML == null ? true : isOuterXML
this.storeAssets()
this.storeAffix(true)
let current: Dom = this // eslint-disable-line
@ -507,19 +709,16 @@ export class Dom<TNode extends Node = Node> extends Primer<TNode> {
export interface Dom<TNode extends Node = Node>
extends ClassName<TNode>,
EventEmitter<TNode>,
Style<TNode>,
Transform<TNode>,
Data<TNode>,
Affix<TNode>,
Memory<TNode>,
Listener<TNode>,
Transform<TNode> {}
EventEmitter<TNode>,
Listener<TNode> {}
export namespace Dom {
export function adopt<T extends Dom>(node: Node): T
export function adopt<T extends Dom>(node?: Node | null): T | null
export function adopt<T extends Dom>(node?: Node | null) {
return Adopter.adopt<T>(node)
}
export const adopt = Adopter.adopt
export function find<T extends Dom = Dom>(
selectors: string,

View File

@ -1,6 +1,6 @@
import { Attr } from '../../util/attr'
import { DomUtil } from '../../util/dom'
import { Attrs, Class } from '../../types'
import { Attrs } from '../../types'
import { Color } from '../../struct/color'
import { NumberArray } from '../../struct/number-array'
import { Registry } from '../registry'
@ -13,34 +13,29 @@ export abstract class Primer<TNode extends Node> extends Base {
return this.node.nodeName
}
constructor()
constructor(node: Node)
constructor(attrs: Attrs | null)
constructor(node?: TNode | string | null, attrs?: Attrs | null)
constructor(node?: TNode | string | Attrs | null, attrs?: Attrs | null)
constructor(node?: TNode | string | Attrs | null, attrs?: Attrs | null) {
super()
let attributes: Attrs | null | undefined
if (DomUtil.isNode(node)) {
this.node = node
if (attrs) {
this.attr(attrs)
}
attributes = attrs
} else {
const ctor = this.constructor as Class
const ctor = this.constructor as Registry.Definition
const name = typeof node === 'string' ? node : Registry.getTagName(ctor)
if (name) {
this.node = DomUtil.createNode<any>(name)
const ats = node != null && typeof node !== 'string' ? node : attrs
if (ats) {
this.attr(ats)
}
attributes = node != null && typeof node !== 'string' ? node : attrs
} else {
throw new Error(
`Can not initialize "${ctor.name}" with unknown node name`,
)
}
}
if (attributes) {
this.attr(attributes)
}
}
attr(): Attrs

View File

@ -98,20 +98,28 @@ export class Transform<TNode extends Node>
)
}
rotate(angle: number): this
rotate(angle: number, cx: number, cy: number): this
rotate(angle: number, cx?: number, cy?: number) {
return this.transform({ rotate: angle, ox: cx, oy: cy }, true)
}
skew(x: number, y: number): this
skew(x: number, y: number, cx: number, cy: number): this
skew(x: number, y: number, cx?: number, cy?: number) {
return arguments.length === 1 || arguments.length === 3
? this.transform({ skew: x, ox: y, oy: cx }, true)
: this.transform({ skew: [x, y], ox: cx, oy: cy }, true)
}
shear(lam: number): this
shear(lam: number, cx: number, cy: number): this
shear(lam: number, cx?: number, cy?: number) {
return this.transform({ shear: lam, ox: cx, oy: cy }, true)
}
scale(x: number, y: number): this
scale(x: number, y: number, cx: number, cy: number): this
scale(x: number, y: number, cx?: number, cy?: number) {
return arguments.length === 1 || arguments.length === 3
? this.transform({ scale: x, ox: y, oy: cx }, true)

View File

@ -10,7 +10,7 @@ import { Matrix } from '../struct/matrix'
import { UNumber } from '../struct/unumber'
import { Vector } from './vector'
@VectorElement.register('Element')
@VectorElement.register('Vector')
export class VectorElement<
TSVGElement extends SVGElement = SVGElement
> extends Vector<TSVGElement> {
@ -258,7 +258,7 @@ export class VectorElement<
const retry = (node: SVGGraphicsElement) =>
DomUtil.withSvgContext((svg) => {
try {
const cloned = this.clone<VectorElement>().addTo(svg).show()
const cloned = this.clone().addTo(svg).show()
const box = DomUtil.toElement<SVGGraphicsElement>(
cloned.node,
).getBBox()

View File

@ -1,11 +1,17 @@
import { Class } from '../types'
import { Str } from '../util/str'
import { Base } from './base'
export namespace Registry {
let root: Class
const cache: Record<string, Class> = {}
export type Definition = { new (...args: any[]): Base }
export function register(ctor: Class, name = ctor.name, asRoot?: boolean) {
let root: Definition
const cache: Record<string, Definition> = {}
export function register(
ctor: Definition,
name = ctor.name,
asRoot?: boolean,
) {
cache[name] = ctor
if (asRoot) {
@ -15,28 +21,34 @@ export namespace Registry {
return ctor
}
export function getClass<TClass extends Class = Class>(node: Node): TClass
export function getClass<TClass extends Class = Class>(name: string): TClass
export function getClass<TClass extends Class = Class>(node: string | Node) {
export function getClass<TDefinition extends Definition = Definition>(
node: Node,
): TDefinition
export function getClass<TDefinition extends Definition = Definition>(
name: string,
): TDefinition
export function getClass<TDefinition extends Definition = Definition>(
node: string | Node,
) {
if (typeof node === 'string') {
return cache[node] as TClass
return cache[node] as TDefinition
}
const nodeName = node.nodeName || 'Dom'
const nodeName = node.nodeName
let className = Str.ucfirst(nodeName)
if (nodeName === '#document-fragment') {
className = 'Fragment'
} else if (
className === 'LinearGradient' ||
className === 'RadialGradient'
if (
node instanceof SVGElement &&
(className === 'LinearGradient' || className === 'RadialGradient')
) {
className = 'Gradient'
} else if (nodeName === '#document-fragment') {
className = 'Fragment'
} else if (!Registry.hasClass(className)) {
className = 'Dom'
className = node instanceof SVGElement ? 'Vector' : 'Dom'
}
return cache[className] as TClass
return cache[className] as TDefinition
}
export function hasClass(name: string) {
@ -47,7 +59,7 @@ export namespace Registry {
return root
}
export function getTagName(cls: Class) {
export function getTagName(cls: Definition) {
const keys = Object.keys(cache)
for (let i = 0, l = keys.length; i < l; i += 1) {
const key = keys[i]

View File

@ -0,0 +1,68 @@
import { A } from './container/a'
import { ClipPath } from './container/clippath'
import { Defs } from './container/defs'
import { G } from './container/g'
import { Gradient } from './container/gradient'
import { Marker } from './container/marker'
import { Mask } from './container/mask'
import { Pattern } from './container/pattern'
import { Svg } from './container/svg'
import { Symbol } from './container/symbol'
import { Dom } from './dom'
import { VectorElement } from './element'
import { Circle } from './shape/circle'
import { Ellipse } from './shape/ellipse'
import { ForeignObject } from './shape/foreignobject'
import { Fragment } from './shape/fragment'
import { Image } from './shape/image'
import { Line } from './shape/line'
import { Path } from './shape/path'
import { Polygon } from './shape/polygon'
import { Polyline } from './shape/polyline'
import { Rect } from './shape/rect'
import { Style } from './shape/style'
import { Text } from './shape/text'
import { TSpan } from './shape/tspan'
import { Use } from './shape/use'
// prettier-ignore
export type ElementMapping<T> =
T extends SVGAElement ? A :
T extends SVGClipPathElement ? ClipPath :
T extends SVGDefsElement ? Defs :
T extends SVGGElement ? G :
T extends SVGLinearGradientElement ? Gradient :
T extends SVGRadialGradientElement ? Gradient :
T extends SVGMarkerElement ? Marker :
T extends SVGMaskElement ? Mask :
T extends SVGPatternElement ? Pattern :
T extends SVGSVGElement ? Svg :
T extends SVGSymbolElement ? Symbol : // eslint-disable-line
T extends SVGCircleElement ? Circle :
T extends SVGEllipseElement ? Ellipse :
T extends SVGForeignObjectElement ? ForeignObject :
T extends DocumentFragment ? Fragment :
T extends SVGImageElement ? Image :
T extends SVGLineElement ? Line :
T extends SVGPathElement ? Path :
T extends SVGPolygonElement ? Polygon :
T extends SVGPolylineElement ? Polyline :
T extends SVGRectElement ? Rect :
T extends SVGStyleElement ? Style :
T extends SVGTextElement ? Text :
T extends SVGTSpanElement ? TSpan :
T extends SVGUseElement ? Use :
T extends SVGElement ? VectorElement<T>:
T extends HTMLElement ? Dom<T> : Dom<Node>
export type HTMLElementMapping<T extends HTMLElement> = Dom<T>
export type SVGTagMapping = {
[K in keyof SVGElementTagNameMap]: ElementMapping<SVGElementTagNameMap[K]>
}
export type HTMLTagMapping = {
[K in keyof HTMLElementTagNameMap]: Dom<HTMLElementTagNameMap[K]>
}