aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md4
-rw-r--r--spec/spec/elements/Dom.js322
-rw-r--r--spec/spec/elements/Fragment.js61
-rw-r--r--spec/spec/utils/adopter.js8
-rw-r--r--src/elements/Dom.js24
-rw-r--r--src/elements/Fragment.js34
-rw-r--r--src/main.js5
-rw-r--r--src/utils/adopter.js4
-rw-r--r--svg.js.d.ts2
9 files changed, 443 insertions, 21 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1152156..83af8ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,9 +19,10 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
- fixed tons of typings in the svg.d.ts file and relaxed type requirements for `put()` and `parent()`
- fixed adopter when adopting an svg/html string. It had still its wrapper as parentNode attached
- fixed `put()` which correctly creates an svgjs object from the passed element now before returning
- - fixed `parent()` which correctly returns null if direct parent is the document or a document-fragment
+ - fixed `parent()` which correctly returns a Dom instance when parent is the document or document-fragment
- fixed `add()` which correctly removes namespaces of non-root svg elements now when added to another svg element (#1086)
- fixed `isRoot()` which correctly returns false, if the element is in a document-fragment
+ - fixed `replace()` which works without a parent now, too
### Added
- added second Parameter to `SVG(el, isHTML)` which allows to explicitely create elements in the HTML namespace (#1058)
@@ -31,6 +32,7 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
- added `options` parameter to `dispatch()` and `fire()` to allow for more special needs
- added `newLine()` constructor to `Text` to create a tspan marked as new line (#1088)
- added lots of tests in es6 format
+ - added `Fragment` as a wrapper for document-fragment
## [3.0.16] - 2019-11-12
diff --git a/spec/spec/elements/Dom.js b/spec/spec/elements/Dom.js
index a1e2a6d..6689822 100644
--- a/spec/spec/elements/Dom.js
+++ b/spec/spec/elements/Dom.js
@@ -1,6 +1,6 @@
/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */
-import { SVG, G, Rect, Svg, Dom, List } from '../../../src/main.js'
+import { SVG, G, Rect, Svg, Dom, List, Fragment, Circle, Tspan } from '../../../src/main.js'
import { getWindow } from '../../../src/utils/window.js'
const { any, createSpy, objectContaining } = jasmine
@@ -63,6 +63,16 @@ describe('Dom.js', function () {
expect(g.children().length).toBe(1)
expect(g.get(0)).toBe(rect)
})
+
+ it('handles a node', () => {
+ const g = new G()
+ const rect = new Rect()
+ const node = rect.node
+ delete rect.instance
+ g.add(node)
+ expect(g.children().length).toBe(1)
+ expect(g.get(0)).toEqual(any(Rect))
+ })
})
describe('addTo()', () => {
@@ -249,6 +259,47 @@ describe('Dom.js', function () {
})
})
+ describe('id()', () => {
+ it('returns current element when called as setter', () => {
+ const g = new G()
+ expect(g.id('asd')).toBe(g)
+ })
+
+ it('sets the id with argument given', () => {
+ expect(new G().id('foo').node.id).toBe('foo')
+ })
+
+ it('gets the id when no argument given', () => {
+ const g = new G({ id: 'foo' })
+ expect(g.id()).toBe('foo')
+ })
+
+ it('generates an id on getting if none is set', () => {
+ const g = new G()
+ expect(g.node.id).toBe('')
+ g.id()
+ expect(g.node.id).not.toBe('')
+ })
+ })
+
+ describe('index()', () => {
+ it('gets the position of the passed child', () => {
+ const g = new G()
+ g.rect(100, 100)
+ const rect = g.rect(100, 100)
+ expect(g.index(rect)).toBe(1)
+ })
+ })
+
+ describe('last()', () => {
+ it('gets the last child of the element', () => {
+ const g = new G()
+ g.rect(100, 100)
+ const rect = g.rect(100, 100)
+ expect(g.last()).toBe(rect)
+ })
+ })
+
describe('parent()', () => {
var canvas, rect, group1, group2
@@ -282,14 +333,10 @@ describe('Dom.js', function () {
expect(rect.parent('.not-there')).toBe(null)
})
- it('returns null if parent is #document', () => {
- // cant test that here
- })
-
- it('returns null if parent is #document-fragment', () => {
+ it('returns Dom if parent is #document-fragment', () => {
const fragment = getWindow().document.createDocumentFragment()
const svg = new Svg().addTo(fragment)
- expect(svg.parent()).toBe(null)
+ expect(svg.parent()).toEqual(any(Dom))
})
it('returns html parents, too', () => {
@@ -297,6 +344,263 @@ describe('Dom.js', function () {
})
})
+ describe('put()', () => {
+ it('calls add() but returns the added element instead', () => {
+ const g = new G()
+ const rect = new Rect()
+ const spy = spyOn(g, 'add').and.callThrough()
+ expect(g.put(rect, 0)).toBe(rect)
+ expect(spy).toHaveBeenCalledWith(rect, 0)
+ })
+
+ it('creates object from svg string', () => {
+ const g = new G()
+ const rect = '<rect>'
+ const spy = spyOn(g, 'add').and.callThrough()
+ const ret = g.put(rect, 0)
+ expect(ret).toEqual(any(Rect))
+ expect(spy).toHaveBeenCalledWith(ret, 0)
+ })
+
+ it('works with a query selector', () => {
+ const canvas = SVG().addTo(container)
+ const rect = canvas.rect().addClass('test')
+ const g = canvas.group()
+ const spy = spyOn(g, 'add').and.callThrough()
+ const ret = g.put('.test', 0)
+ expect(ret).toEqual(rect)
+ expect(spy).toHaveBeenCalledWith(rect, 0)
+ })
+ })
+
+ describe('putIn()', () => {
+ it('calls add on the given parent', () => {
+ const g = new G()
+ const rect = new Rect()
+ const spy = spyOn(g, 'add')
+ rect.putIn(g, 0)
+ expect(spy).toHaveBeenCalledWith(rect, 0)
+ })
+
+ it('returns the passed element', () => {
+ const g = new G()
+ const rect = new Rect()
+ expect(rect.putIn(g, 0)).toBe(g)
+ })
+
+ it('returns an instance when svg string given', () => {
+ const g = '<g>'
+ const rect = new Rect()
+ const ret = rect.putIn(g)
+ expect(ret).toEqual(any(G))
+ expect(ret.children()).toEqual([ rect ])
+ })
+
+ it('works with a query selector', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group().addClass('test')
+ const rect = canvas.rect(100, 100)
+ const ret = rect.putIn('.test')
+ expect(ret).toBe(g)
+ expect(g.children()).toEqual([ rect ])
+ })
+ })
+
+ describe('remove()', () => {
+ it('returns the removed element', () => {
+ const canvas = SVG().addTo(container)
+ const rect = canvas.rect(100, 100)
+ expect(rect.remove()).toBe(rect)
+ })
+
+ it('removes the element from the parent', () => {
+ const canvas = SVG().addTo(container)
+ const rect = canvas.rect(100, 100)
+ expect(canvas.children()).toEqual([ rect ])
+ rect.remove()
+ expect(canvas.children()).toEqual([])
+ })
+
+ it('is a noop when element is not attached to the dom', () => {
+ const rect = new Rect()
+ expect(rect.remove()).toBe(rect)
+ })
+
+ it('also works when direct child of document-fragment', () => {
+ const fragment = new Fragment()
+ const rect = fragment.rect(100, 100)
+ expect(fragment.children()).toEqual([ rect ])
+ expect(rect.remove()).toBe(rect)
+ expect(fragment.children()).toEqual([])
+ })
+ })
+
+ describe('removeElement()', () => {
+ it('returns itself', () => {
+ const g = new G()
+ const rect = g.rect(100, 100)
+ expect(g.removeElement(rect)).toBe(g)
+ })
+
+ it('removes the given child', () => {
+ const g = new G()
+ const rect = g.rect(100, 100)
+ expect(g.removeElement(rect).children()).toEqual([])
+ })
+
+ it('throws if the given element is not a child', () => {
+ const g = new G()
+ const rect = new Rect()
+ try {
+ g.removeElement(rect)
+ } catch (e) {
+ expect(e).toEqual(objectContaining({ code: 8 }))
+ }
+ })
+ })
+
+ describe('replace()', () => {
+ it('returns the new element', () => {
+ const g = new G()
+ const rect = g.rect(100, 100)
+ const circle = new Circle()
+ expect(rect.replace(circle)).toBe(circle)
+ })
+
+ it('replaces the child at the correct position', () => {
+ const g = new G()
+ const rect1 = g.rect(100, 100)
+ const rect2 = g.rect(100, 100)
+ const rect3 = g.rect(100, 100)
+ const circle = new Circle()
+ rect2.replace(circle)
+ expect(g.children()).toEqual([ rect1, circle, rect3 ])
+ })
+
+ it('also works without a parent', () => {
+ const rect = new Rect()
+ const circle = new Circle()
+ expect(rect.replace(circle)).toBe(circle)
+ })
+ })
+
+ describe('round()', () => {
+ it('rounds all attributes whose values are numbers to two decimals by default', () => {
+ const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 })
+ expect(rect.round().attr()).toEqual({ id: 'foo', x: 10.68, y: 3, width: 123.46 })
+ })
+
+ it('rounds all attributes whose values are numbers to the passed precision', () => {
+ const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 })
+ expect(rect.round(1).attr()).toEqual({ id: 'foo', x: 10.7, y: 3, width: 123.5 })
+ })
+
+ it('rounds the given attribues whose values are numbers to the passed precision', () => {
+ const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 })
+ expect(rect.round(1, [ 'id', 'x' ]).attr()).toEqual({ id: 'foo', x: 10.7, y: 3, width: 123.456 })
+ })
+ })
+
+ describe('svg()', () => {
+ describe('as setter', () => {
+ it('returns itself', () => {
+ const g = new G()
+ expect(g.svg('<rect>')).toBe(g)
+ })
+
+ it('imports a single element', () => {
+ const g = new G().svg('<rect>')
+ expect(g.children()).toEqual([ any(Rect) ])
+ })
+
+ it('imports multiple elements', () => {
+ const g = new G().svg('<rect /><circle />')
+ expect(g.children()).toEqual([ any(Rect), any(Circle) ])
+ })
+
+ it('replaces the current element with the imported elements with outerHtml = true', () => {
+ const canvas = new Svg()
+ const g = canvas.group()
+ g.svg('<rect /><circle />', true)
+ expect(canvas.children()).toEqual([ any(Rect), any(Circle) ])
+ })
+
+ it('returns the parent when outerHtml = true', () => {
+ const canvas = new Svg()
+ const g = canvas.group()
+ expect(g.svg('<rect><circle>', true)).toBe(canvas)
+ })
+ })
+
+ describe('as getter', () => {
+ let canvas, group, rect
+
+ beforeEach(() => {
+ canvas = new Svg().removeNamespace()
+ group = canvas.group()
+ rect = group.rect(123.456, 234.567)
+ })
+
+ it('returns the svg string of the element by default', () => {
+ expect(rect.svg()).toBe('<rect width="123.456" height="234.567"></rect>')
+ expect(canvas.svg()).toBe('<svg><g><rect width="123.456" height="234.567"></rect></g></svg>')
+ })
+
+ it('returns the innerHtml when outerHtml = false', () => {
+ expect(rect.svg(false)).toBe('')
+ expect(canvas.svg(false)).toBe('<g><rect width="123.456" height="234.567"></rect></g>')
+ })
+
+ it('runs a function on every exported node', () => {
+ expect(rect.svg((el) => el.round(1))).toBe('<rect width="123.5" height="234.6"></rect>')
+ })
+
+ it('runs a function on every exported node and replaces node with returned node if return value is not falsy', () => {
+ expect(rect.svg((el) => new Circle())).toBe('<circle></circle>')
+ expect(canvas.svg((el) => new G())).toBe('<g></g>') // outer <svg> was replaced by an empty g
+ expect(canvas.svg((el) => {
+ if (el instanceof Rect) return new Circle()
+ if (el instanceof Svg) el.removeNamespace()
+ })).toBe('<svg><g><circle></circle></g></svg>')
+ })
+
+ it('runs a function on every exported node and removes node if return value is false', () => {
+ expect(group.svg(() => false)).toBe('')
+ expect(canvas.svg(() => false)).toBe('')
+ expect(canvas.svg((el) => {
+ if (el instanceof Svg) {
+ el.removeNamespace()
+ } else {
+ return false
+ }
+ })).toBe('<svg></svg>')
+ })
+
+ it('runs a function on every inner node and exports it when outerHtml = false', () => {
+ expect(canvas.svg(() => false), false).toBe('')
+ expect(canvas.svg(() => undefined, false)).toBe('<g><rect width="123.456" height="234.567"></rect></g>')
+ })
+
+ })
+
+ })
+
+ describe('toString()', () => {
+ it('calls id() and returns its result', () => {
+ const rect = new Rect({ id: 'foo' })
+ const spy = spyOn(rect, 'id').and.callThrough()
+ expect(rect.toString()).toBe('foo')
+ expect(spy).toHaveBeenCalled()
+ })
+ })
+
+ describe('words', () => {
+ it('sets the nodes textContent to the given value', () => {
+ const tspan = new Tspan().words('Hello World')
+ expect(tspan.text()).toBe('Hello World')
+ })
+ })
+
describe('wrap()', function () {
var canvas
var rect
@@ -361,4 +665,8 @@ describe('Dom.js', function () {
expect(rect.parent().parent()).toBe(canvas)
})
})
+
+ describe('writeDataToDom()', () => {
+ // not really testable
+ })
})
diff --git a/spec/spec/elements/Fragment.js b/spec/spec/elements/Fragment.js
new file mode 100644
index 0000000..c0f5f0f
--- /dev/null
+++ b/spec/spec/elements/Fragment.js
@@ -0,0 +1,61 @@
+/* globals describe, expect, it, spyOn, jasmine */
+
+import { Fragment, Dom } from '../../../src/main.js'
+import { getWindow } from '../../../src/utils/window.js'
+
+const { any } = jasmine
+
+describe('Fragment.js', () => {
+
+ describe('()', () => {
+ it('creates a new object of type Fragment', () => {
+ expect(new Fragment()).toEqual(any(Fragment))
+ })
+
+ it('uses passed node instead of creating', () => {
+ const fragment = getWindow().document.createDocumentFragment()
+ expect(new Fragment(fragment).node).toBe(fragment)
+ })
+
+ it('has all Container methods available', () => {
+ const frag = new Fragment()
+ const rect = frag.rect(100, 100)
+
+ expect(frag.children()).toEqual([ rect ])
+ })
+ })
+
+ describe('svg()', () => {
+ describe('as setter', () => {
+ it('calls parent method with outerHtml = false', () => {
+ const frag = new Fragment()
+ const spy = spyOn(Dom.prototype, 'svg').and.callThrough()
+ frag.svg('<rect>', true)
+ expect(spy).toHaveBeenCalledWith('<rect>', false)
+ })
+ })
+
+ describe('as getter', () => {
+ it('calls parent method with outerHtml = false - 1', () => {
+ const frag = new Fragment()
+ const group = frag.group()
+ group.rect(123.456, 234.567)
+ const spy = spyOn(Dom.prototype, 'svg').and.callThrough()
+
+ expect(frag.svg(false)).toBe('<g><rect width="123.456" height="234.567"></rect></g>')
+ expect(spy).toHaveBeenCalledWith(null, false)
+ })
+
+ it('calls parent method with outerHtml = false - 2', () => {
+ const frag = new Fragment()
+ const group = frag.group()
+ group.rect(123.456, 234.567)
+ const spy = spyOn(Dom.prototype, 'svg').and.callThrough()
+
+ expect(frag.svg(null, true)).toBe('<g><rect width="123.456" height="234.567"></rect></g>')
+ expect(spy).toHaveBeenCalledWith(null, false)
+ })
+ })
+
+ })
+})
diff --git a/spec/spec/utils/adopter.js b/spec/spec/utils/adopter.js
index c209980..e544752 100644
--- a/spec/spec/utils/adopter.js
+++ b/spec/spec/utils/adopter.js
@@ -15,7 +15,8 @@ import {
G,
Gradient,
Dom,
- Path
+ Path,
+ Fragment
} from '../../../src/main.js'
import { mockAdopt, assignNewId, adopt } from '../../../src/utils/adopter.js'
@@ -120,6 +121,11 @@ describe('adopter.js', () => {
expect(adopt(rect.node)).toBe(rect)
})
+ it('creates Fragment when document fragment is passed', () => {
+ const frag = getWindow().document.createDocumentFragment()
+ expect(adopt(frag)).toEqual(any(Fragment))
+ })
+
it('creates instance when node without instance is passed', () => {
const rect = new Rect()
const node = rect.node
diff --git a/src/elements/Dom.js b/src/elements/Dom.js
index 0180c78..c9e6953 100644
--- a/src/elements/Dom.js
+++ b/src/elements/Dom.js
@@ -142,7 +142,8 @@ export default class Dom extends EventTarget {
// matches the element vs a css selector
matches (selector) {
const el = this.node
- return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector)
+ const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null
+ return matcher && matcher.call(el, selector)
}
// Returns the parent element instance
@@ -151,7 +152,6 @@ export default class Dom extends EventTarget {
// check for parent
if (!parent.node.parentNode) return null
- if (parent.node.parentNode.nodeName === '#document' || parent.node.parentNode.nodeName === '#document-fragment') return null
// get parent element
parent = adopt(parent.node.parentNode)
@@ -159,11 +159,11 @@ export default class Dom extends EventTarget {
if (!type) return parent
// loop trough ancestors if type is given
- while (parent) {
+ do {
if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
- if (!parent.node.parentNode || parent.node.parentNode.nodeName === '#document' || parent.node.parentNode.nodeName === '#document-fragment') return null // #759, #720
- parent = adopt(parent.node.parentNode)
- }
+ } while ((parent = adopt(parent.node.parentNode)))
+
+ return parent
}
// Basically does the same as `add()` but returns the added element instead
@@ -197,7 +197,11 @@ export default class Dom extends EventTarget {
// Replace this with element
replace (element) {
element = makeInstance(element)
- this.node.parentNode.replaceChild(element.node, this.node)
+
+ if (this.node.parentNode) {
+ this.node.parentNode.replaceChild(element.node, this.node)
+ }
+
return element
}
@@ -206,14 +210,16 @@ export default class Dom extends EventTarget {
const attrs = this.attr(map)
for (const i in attrs) {
- attrs[i] = Math.round(attrs[i] * factor) / factor
+ if (typeof attrs[i] === 'number') {
+ attrs[i] = Math.round(attrs[i] * factor) / factor
+ }
}
this.attr(attrs)
return this
}
- // Import raw svg
+ // Import / Export raw svg
svg (svgOrFn, outerHTML) {
var well, len, fragment
diff --git a/src/elements/Fragment.js b/src/elements/Fragment.js
new file mode 100644
index 0000000..228e93f
--- /dev/null
+++ b/src/elements/Fragment.js
@@ -0,0 +1,34 @@
+import Dom from './Dom.js'
+import { globals } from '../utils/window.js'
+import { register } from '../utils/adopter.js'
+import Svg from './Svg.js'
+
+class Fragment extends Dom {
+ constructor (node = globals.document.createDocumentFragment()) {
+ super(node)
+ }
+
+ // Import / Export raw svg
+ svg (svgOrFn, outerHTML) {
+ if (svgOrFn === false) {
+ outerHTML = false
+ svgOrFn = null
+ }
+
+ // act as getter if no svg string is given
+ if (svgOrFn == null || typeof svgOrFn === 'function') {
+ const wrapper = new Svg()
+ wrapper.add(this.node.cloneNode(true))
+
+ return wrapper.svg(svgOrFn, false)
+ }
+
+ // Act as setter if we got a string
+ return super.svg(svgOrFn, false)
+ }
+
+}
+
+register(Fragment, 'Fragment')
+
+export default Fragment
diff --git a/src/main.js b/src/main.js
index 3162752..12ebc7b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -18,6 +18,7 @@ import Dom from './elements/Dom.js'
import Element from './elements/Element.js'
import Ellipse from './elements/Ellipse.js'
import EventTarget from './types/EventTarget.js'
+import Fragment from './elements/Fragment.js'
import Gradient from './elements/Gradient.js'
import Image from './elements/Image.js'
import Line from './elements/Line.js'
@@ -96,6 +97,7 @@ export { default as Dom } from './elements/Dom.js'
export { default as Element } from './elements/Element.js'
export { default as Ellipse } from './elements/Ellipse.js'
export { default as ForeignObject } from './elements/ForeignObject.js'
+export { default as Fragment } from './elements/Fragment.js'
export { default as Gradient } from './elements/Gradient.js'
export { default as G } from './elements/G.js'
export { default as A } from './elements/A.js'
@@ -154,8 +156,7 @@ extend(EventTarget, getMethodsFor('EventTarget'))
extend(Dom, getMethodsFor('Dom'))
extend(Element, getMethodsFor('Element'))
extend(Shape, getMethodsFor('Shape'))
-// extend(Element, getConstructor('Memory'))
-extend(Container, getMethodsFor('Container'))
+extend([ Container, Fragment ], getMethodsFor('Container'))
extend(Runner, getMethodsFor('Runner'))
diff --git a/src/utils/adopter.js b/src/utils/adopter.js
index b016837..217aafb 100644
--- a/src/utils/adopter.js
+++ b/src/utils/adopter.js
@@ -53,6 +53,10 @@ export function adopt (node) {
// make sure a node isn't already adopted
if (node.instance instanceof Base) return node.instance
+ if (node.nodeName === '#document-fragment') {
+ return new elements.Fragment(node)
+ }
+
// initialize variables
var className = capitalize(node.nodeName || 'Dom')
diff --git a/svg.js.d.ts b/svg.js.d.ts
index 72c6d7d..8dd99e2 100644
--- a/svg.js.d.ts
+++ b/svg.js.d.ts
@@ -970,7 +970,7 @@ declare module "@svgdotjs/svg.js" {
attr(name: string, value: any, namespace?: string): this;
attr(name: string): any;
attr(obj: object): this;
- attr(obj: object[]): object;
+ attr(obj: string[]): object;
// prototype extend Selector in selector.js
find(query: string): List<Element>