├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist └── index.d.ts ├── karma.conf.cjs ├── lib ├── append.js ├── appendTo.js ├── attr.js ├── classes.js ├── clear.js ├── clone.js ├── create.js ├── events.js ├── geometry.js ├── index.js ├── innerSVG.js ├── prepend.js ├── prependTo.js ├── query.js ├── remove.js ├── replace.js ├── transform.js └── util │ ├── ensureImported.js │ ├── ns.js │ ├── parse.js │ └── serialize.js ├── package-lock.json ├── package.json ├── rollup.config.js └── test ├── .eslintrc ├── helper.js └── spec ├── append.js ├── appendTo.js ├── attr.js ├── classes.js ├── clear.js ├── clone.js ├── create.js ├── geometry.js ├── innerSVG.js ├── prepend.js ├── prependTo.js ├── query.js ├── remove.js ├── transform.js └── util └── parse.js /.eslintignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plugin:bpmn-io/browser" 3 | } -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [ push, pull_request ] 3 | jobs: 4 | Build: 5 | runs-on: 'ubuntu-20.04' 6 | 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v4 10 | - name: Use Node.js 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: 20 14 | cache: 'npm' 15 | - name: Install dependencies 16 | run: npm ci 17 | - name: Build 18 | env: 19 | TEST_BROWSERS: Firefox,ChromeHeadless 20 | run: xvfb-run npm run all -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to [tiny-svg](https://github.com/bpmn-io/tiny-svg) are documented here. We use [semantic versioning](http://semver.org/) for releases. 4 | 5 | ## Unreleased 6 | 7 | ___Note:__ Yet to be released changes appear here._ 8 | 9 | ## 4.1.3 10 | 11 | * `FIX`: escape entities in attributes ([#16](https://github.com/bpmn-io/tiny-svg/issues/16)) 12 | 13 | ## 3.1.3 14 | 15 | * `FIX`: escape entities in attributes ([#16](https://github.com/bpmn-io/tiny-svg/issues/16)) 16 | 17 | ## 4.1.2 18 | 19 | * `CHORE`: make `clear` work standalone 20 | 21 | ## 3.1.2 22 | 23 | * `CHORE`: standalone `clear` implementation 24 | 25 | ## 4.1.1 26 | 27 | * `CHORE`: revert to basic `clear` 28 | 29 | ## 3.1.1 30 | 31 | * `CHORE`: rever to basic `clear` 32 | 33 | ## 4.1.0 34 | 35 | * `FEAT`: use optimized `clear` 36 | * `FEAT`: trim whitespace around `create` 37 | 38 | ## 3.1.0 39 | 40 | * `FEAT`: use optimized `clear` 41 | * `FEAT`: trim whitespace around `create` 42 | 43 | ## 4.0.0 44 | 45 | * `CHORE`: turn into ES module 46 | * `CHORE`: require Node >= 16 47 | * `CHORE`: drop UMD distribution 48 | 49 | ### Breaking Change 50 | 51 | * This library is now an ES only module, and can consumed as such in modern JavaScript environments. 52 | 53 | ## 3.0.1 54 | 55 | * `FIX`: correct `create` type definition ([#13](https://github.com/bpmn-io/tiny-svg/pull/13)) 56 | 57 | ## 3.0.0 58 | 59 | * `FEAT`: change library target to `ES2018` 60 | * `FEAT`: drop polyfills for browser not supporting `ES2018` 61 | 62 | ### Breaking Changes 63 | 64 | * Target syntax is `ES2018`. Transpile the code base to target `< ES2018`. 65 | * Polyfills for browsers not supporting `ES2018` are dropped (e.g. Element.classList). 66 | 67 | ## 2.2.4 68 | 69 | * `FIX`: lazily create utility elements ([#10](https://github.com/bpmn-io/tiny-svg/issues/10)) 70 | 71 | ## 2.2.3 72 | 73 | * `CHORE`: add type definitions for prepend and prependTo 74 | 75 | ## 2.2.2 76 | 77 | * `FIX`: correct type definitions for select and selectAll 78 | 79 | ## 2.2.1 80 | 81 | * `FIX`: work around IE / MS Edge transform issue ([`980e9d6f`](https://github.com/bpmn-io/tiny-svg/commit/980e9d6f69a79ae500c6a4172d046b2420e4ca25)) 82 | 83 | ## 2.2.0 84 | 85 | * `FEAT`: add ability to `create` any SVG element via markup 86 | * `FEAT`: add ability to set `innerSVG` to fragment 87 | * `FEAT`: add `prependTo` and `prepend` utils 88 | 89 | ## 2.1.2 90 | 91 | * `FIX`: correct `{}` TypeScript definition 92 | 93 | ## 2.1.1 94 | 95 | * `FIX`: correct TypeScript definitions 96 | 97 | ## 2.1.0 98 | 99 | * `FEAT`: add TypeScript definitions 100 | 101 | ## 2.0.0 102 | 103 | * `FIX`: drop `browser` field for better interoperability with module bundlers 104 | 105 | ## 1.1.0 106 | 107 | * `CHORE`: mark utils as side-effect free via `sideEffects: false` 108 | 109 | ## 1.0.0 110 | 111 | * `CHORE`: migrate code base to ES6 112 | * `FEAT`: generate bundles for CJS, ES6 and UMD 113 | 114 | ## ... 115 | 116 | Check `git log` for earlier history. 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nico Rehwaldt 4 | Copyright (c) 2015-present camunda Services GmbH 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tiny-svg 2 | 3 | [![CI](https://github.com/bpmn-io/tiny-svg/actions/workflows/CI.yml/badge.svg)](https://github.com/bpmn-io/tiny-svg/actions/workflows/CI.yml) 4 | 5 | __tiny-svg__ is a minimal toolbelt for creating clean SVG applications. 6 | 7 | 8 | ## Features 9 | 10 | * no wrapping magic, using native DOM elements instead 11 | * modular, just use what you need 12 | * `2kB` minified + gzipped 13 | * `innerSVG` support 14 | * simplified attribute handling 15 | * geometry helpers 16 | 17 | Checkout [provided utilities](./lib). 18 | 19 | 20 | ## Usage 21 | 22 | ```javascript 23 | import { 24 | appendTo, 25 | classes, 26 | create, 27 | innerSVG 28 | } from 'tiny-svg'; 29 | 30 | var container = document.createElement('div'); 31 | var element = appendTo(create('svg'), container); 32 | 33 | var g = appendTo(create('g'), element); 34 | 35 | // add classes, SVG style! 36 | classes(g).add('foo'); 37 | 38 | var text = ` 39 | 40 | 41 | 42 | `; 43 | 44 | // set innerSVG 45 | innerSVG(g, text); 46 | ``` 47 | 48 | Your favourite module bundler should apply tree-shaking to only include the components your application requires. If you're using CommonJS modules give [common-shake](https://github.com/indutny/common-shake) a try. 49 | 50 | 51 | ## Related 52 | 53 | * [min-dom](https://github.com/bpmn-io/min-dom) - minimal DOM utility toolbelt 54 | * [min-dash](https://github.com/bpmn-io/min-dash) - minimal lodash inspired utility toolbelt 55 | 56 | 57 | ## License 58 | 59 | MIT 60 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Append a node to an element 3 | * 4 | * @param {SVGElement} element 5 | * @param {SVGElement} node 6 | * 7 | * @return {SVGElement} the element 8 | */ 9 | export function append(element: Element, node: SVGElement): typeof element; 10 | 11 | /** 12 | * Append a node to a target element and return the appended node. 13 | * 14 | * @param {SVGElement} element 15 | * @param {SVGElement} node 16 | * 17 | * @return {SVGElement} the appended node 18 | */ 19 | export function appendTo(element: Element, target: SVGElement): typeof element; 20 | 21 | /** 22 | * Prepend a node to an element 23 | * 24 | * @param {SVGElement} element 25 | * @param {SVGElement} node 26 | * 27 | * @return {SVGElement} the element 28 | */ 29 | export function prepend(element: Element, node: SVGElement): typeof element; 30 | 31 | /** 32 | * Prepend a node to a target element and return the prepended node. 33 | * 34 | * @param {SVGElement} element 35 | * @param {SVGElement} node 36 | * 37 | * @return {SVGElement} the prepended node 38 | */ 39 | export function prependTo(element: Element, target: SVGElement): typeof element; 40 | 41 | export interface KeyValue { 42 | [key: string]: any; 43 | } 44 | 45 | export function attr(node: SVGElement, name: string): string; 46 | export function attr(node: SVGElement, name: string, value: number | string): typeof node; 47 | export function attr(node: SVGElement, attrs: KeyValue): typeof node; 48 | export function attr(node: SVGElement, name: string, value: number | string): typeof node | string; 49 | 50 | /** 51 | * Wrap `el` in a `ClassList`. 52 | * 53 | * @param {Element} el 54 | * @return {ClassList} 55 | * @api public 56 | */ 57 | export function classes(el: T): ClassList; 58 | 59 | export class ClassList { 60 | public list: T["classList"]; 61 | public el: T; 62 | constructor(el: T); 63 | 64 | add(name: string): this; 65 | remove(name: string | RegExp): this; 66 | 67 | removeMatching(re: RegExp): this; 68 | 69 | toggle(name: string, force?: boolean): this; 70 | 71 | array(): string[]; 72 | 73 | has(name: string): boolean; 74 | contains(name: string): boolean; 75 | } 76 | 77 | /** 78 | * Removes all children from the given element 79 | * 80 | * @param {DOMElement} element 81 | * @return {DOMElement} the element (for chaining) 82 | */ 83 | export function clear(element: T): T; 84 | 85 | export function clone(element: T): T; 86 | 87 | /** 88 | * Create a specific type from name or SVG markup. 89 | * 90 | * @param name the name or markup of the element 91 | * @param attrs attributes to set on the element 92 | * 93 | * @return 94 | */ 95 | export function create(name: "a", attrs?: KeyValue): SVGAElement; 96 | export function create(name: "circle", attrs?: KeyValue): SVGCircleElement; 97 | export function create(name: "clipPath", attrs?: KeyValue): SVGClipPathElement; 98 | export function create(name: "componentTransferFunction", attrs?: KeyValue): SVGComponentTransferFunctionElement; 99 | export function create(name: "defs", attrs?: KeyValue): SVGDefsElement; 100 | export function create(name: "desc", attrs?: KeyValue): SVGDescElement; 101 | export function create(name: "ellipse", attrs?: KeyValue): SVGEllipseElement; 102 | export function create(name: "feBlend", attrs?: KeyValue): SVGFEBlendElement; 103 | export function create(name: "feColorMatrix", attrs?: KeyValue): SVGFEColorMatrixElement; 104 | export function create(name: "feComponentTransfer", attrs?: KeyValue): SVGFEComponentTransferElement; 105 | export function create(name: "feComposite", attrs?: KeyValue): SVGFECompositeElement; 106 | export function create(name: "feConvolveMatrix", attrs?: KeyValue): SVGFEConvolveMatrixElement; 107 | export function create(name: "feDiffuseLighting", attrs?: KeyValue): SVGFEDiffuseLightingElement; 108 | export function create(name: "feDisplacementMap", attrs?: KeyValue): SVGFEDisplacementMapElement; 109 | export function create(name: "feDistantLight", attrs?: KeyValue): SVGFEDistantLightElement; 110 | export function create(name: "feFlood", attrs?: KeyValue): SVGFEFloodElement; 111 | export function create(name: "feFuncA", attrs?: KeyValue): SVGFEFuncAElement; 112 | export function create(name: "feFuncB", attrs?: KeyValue): SVGFEFuncBElement; 113 | export function create(name: "feFuncG", attrs?: KeyValue): SVGFEFuncGElement; 114 | export function create(name: "feFuncR", attrs?: KeyValue): SVGFEFuncRElement; 115 | export function create(name: "feGaussianBlur", attrs?: KeyValue): SVGFEGaussianBlurElement; 116 | export function create(name: "feImage", attrs?: KeyValue): SVGFEImageElement; 117 | export function create(name: "feMerge", attrs?: KeyValue): SVGFEMergeElement; 118 | export function create(name: "feMergeNode", attrs?: KeyValue): SVGFEMergeNodeElement; 119 | export function create(name: "feMorphology", attrs?: KeyValue): SVGFEMorphologyElement; 120 | export function create(name: "feOffset", attrs?: KeyValue): SVGFEOffsetElement; 121 | export function create(name: "fePointLight", attrs?: KeyValue): SVGFEPointLightElement; 122 | export function create(name: "feSpecularLighting", attrs?: KeyValue): SVGFESpecularLightingElement; 123 | export function create(name: "feSpotLight", attrs?: KeyValue): SVGFESpotLightElement; 124 | export function create(name: "feTile", attrs?: KeyValue): SVGFETileElement; 125 | export function create(name: "feTurbulence", attrs?: KeyValue): SVGFETurbulenceElement; 126 | export function create(name: "filter", attrs?: KeyValue): SVGFilterElement; 127 | export function create(name: "foreignObject", attrs?: KeyValue): SVGForeignObjectElement; 128 | export function create(name: "g", attrs?: KeyValue): SVGGElement; 129 | export function create(name: "image", attrs?: KeyValue): SVGImageElement; 130 | export function create(name: "gradient", attrs?: KeyValue): SVGGradientElement; 131 | export function create(name: "line", attrs?: KeyValue): SVGLineElement; 132 | export function create(name: "linearGradient", attrs?: KeyValue): SVGLinearGradientElement; 133 | export function create(name: "marker", attrs?: KeyValue): SVGMarkerElement; 134 | export function create(name: "mask", attrs?: KeyValue): SVGMaskElement; 135 | export function create(name: "path", attrs?: KeyValue): SVGPathElement; 136 | export function create(name: "metadata", attrs?: KeyValue): SVGMetadataElement; 137 | export function create(name: "pattern", attrs?: KeyValue): SVGPatternElement; 138 | export function create(name: "polygon", attrs?: KeyValue): SVGPolygonElement; 139 | export function create(name: "polyline", attrs?: KeyValue): SVGPolylineElement; 140 | export function create(name: "radialGradient", attrs?: KeyValue): SVGRadialGradientElement; 141 | export function create(name: "rect", attrs?: KeyValue): SVGRectElement; 142 | export function create(name: "svg", attrs?: KeyValue): SVGSVGElement; 143 | export function create(name: "script", attrs?: KeyValue): SVGScriptElement; 144 | export function create(name: "stop", attrs?: KeyValue): SVGStopElement; 145 | export function create(name: "style", attrs?: KeyValue): SVGStyleElement; 146 | export function create(name: "switch", attrs?: KeyValue): SVGSwitchElement; 147 | export function create(name: "symbol", attrs?: KeyValue): SVGSymbolElement; 148 | export function create(name: "tspan", attrs?: KeyValue): SVGTSpanElement; 149 | export function create(name: "textContent", attrs?: KeyValue): SVGTextContentElement; 150 | export function create(name: "text", attrs?: KeyValue): SVGTextElement; 151 | export function create(name: "textPath", attrs?: KeyValue): SVGTextPathElement; 152 | export function create(name: "textPositioning", attrs?: KeyValue): SVGTextPositioningElement; 153 | export function create(name: "title", attrs?: KeyValue): SVGTitleElement; 154 | export function create(name: "use", attrs?: KeyValue): SVGUseElement; 155 | export function create(name: "view", attrs?: KeyValue): SVGViewElement; 156 | export function create(name: string, attrs?: KeyValue): SVGElement; 157 | 158 | 159 | export function on(node: Node, event: string, listener: Function, useCapture?: boolean): void; 160 | export function off(node: Node, event: string, listener: Function, useCapture?: boolean): void; 161 | 162 | export function createPoint(): SVGPoint; 163 | export function createPoint(x: number, y: number): SVGPoint; 164 | 165 | export function createMatrix(): SVGMatrix; 166 | export function createMatrix(a: number, b: number, c: number, d: number, e: number, f: number): SVGMatrix; 167 | 168 | export function createTransform(matrix?: SVGMatrix): SVGTransform; 169 | 170 | export function innerSVG(element: Element, svg: string): typeof element; 171 | export function innerSVG(element: Element): string; 172 | export function innerSVG(element: Element, svg?: string): typeof element | string; 173 | 174 | export function select(node: Node, selector: string): Node | null; 175 | 176 | export function select(node: K, selector: string): HTMLElementTagNameMap[K] | null; 177 | export function select(node: K, selector: string): SVGElementTagNameMap[K] | null; 178 | export function select(node: E, selector: string): E | null; 179 | 180 | export function selectAll(node: K, selector: string): HTMLElementTagNameMap[K][]; 181 | export function selectAll(node: K, selector: string): SVGElementTagNameMap[K][]; 182 | export function selectAll(node: E, selector: string): E[]; 183 | 184 | export function remove(el: Node): void; 185 | 186 | export function replace(element: Node, replacement: Node): typeof replacement; 187 | 188 | export function transform(node: Node): SVGTransform; 189 | export function transform(node: Node, transforms?: SVGTransform | SVGTransform[]): SVGTransform | void; 190 | -------------------------------------------------------------------------------- /karma.conf.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | // configures browsers to run test against 4 | // any of [ 'ChromeHeadless', 'Chrome', 'Firefox', 'IE', 'PhantomJS' ] 5 | process.env.CHROME_BIN = require('puppeteer').executablePath(); 6 | 7 | const browsers = (process.env.TEST_BROWSERS || 'ChromeHeadless').split(','); 8 | 9 | module.exports = function(karma) { 10 | karma.set({ 11 | 12 | frameworks: [ 13 | 'webpack', 14 | 'mocha', 15 | 'chai' 16 | ], 17 | 18 | files: [ 19 | 'test/spec/**/*.js' 20 | ], 21 | 22 | preprocessors: { 23 | 'test/spec/**/*.js': [ 'webpack' ] 24 | }, 25 | 26 | reporters: [ 'progress' ], 27 | 28 | browsers: browsers, 29 | 30 | singleRun: true, 31 | autoWatch: false, 32 | webpack: {} 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /lib/append.js: -------------------------------------------------------------------------------- 1 | /** 2 | * append utility 3 | */ 4 | 5 | import appendTo from './appendTo.js'; 6 | 7 | /** 8 | * Append a node to an element 9 | * 10 | * @param {SVGElement} element 11 | * @param {SVGElement} node 12 | * 13 | * @return {SVGElement} the element 14 | */ 15 | export default function append(target, node) { 16 | appendTo(node, target); 17 | return target; 18 | } -------------------------------------------------------------------------------- /lib/appendTo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * appendTo utility 3 | */ 4 | 5 | import ensureImported from './util/ensureImported.js'; 6 | 7 | /** 8 | * Append a node to a target element and return the appended node. 9 | * 10 | * @param {SVGElement} element 11 | * @param {SVGElement} target 12 | * 13 | * @return {SVGElement} the appended node 14 | */ 15 | export default function appendTo(element, target) { 16 | return target.appendChild(ensureImported(element, target)); 17 | } -------------------------------------------------------------------------------- /lib/attr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * attribute accessor utility 3 | */ 4 | 5 | var LENGTH_ATTR = 2; 6 | 7 | var CSS_PROPERTIES = { 8 | 'alignment-baseline': 1, 9 | 'baseline-shift': 1, 10 | 'clip': 1, 11 | 'clip-path': 1, 12 | 'clip-rule': 1, 13 | 'color': 1, 14 | 'color-interpolation': 1, 15 | 'color-interpolation-filters': 1, 16 | 'color-profile': 1, 17 | 'color-rendering': 1, 18 | 'cursor': 1, 19 | 'direction': 1, 20 | 'display': 1, 21 | 'dominant-baseline': 1, 22 | 'enable-background': 1, 23 | 'fill': 1, 24 | 'fill-opacity': 1, 25 | 'fill-rule': 1, 26 | 'filter': 1, 27 | 'flood-color': 1, 28 | 'flood-opacity': 1, 29 | 'font': 1, 30 | 'font-family': 1, 31 | 'font-size': LENGTH_ATTR, 32 | 'font-size-adjust': 1, 33 | 'font-stretch': 1, 34 | 'font-style': 1, 35 | 'font-variant': 1, 36 | 'font-weight': 1, 37 | 'glyph-orientation-horizontal': 1, 38 | 'glyph-orientation-vertical': 1, 39 | 'image-rendering': 1, 40 | 'kerning': 1, 41 | 'letter-spacing': 1, 42 | 'lighting-color': 1, 43 | 'marker': 1, 44 | 'marker-end': 1, 45 | 'marker-mid': 1, 46 | 'marker-start': 1, 47 | 'mask': 1, 48 | 'opacity': 1, 49 | 'overflow': 1, 50 | 'pointer-events': 1, 51 | 'shape-rendering': 1, 52 | 'stop-color': 1, 53 | 'stop-opacity': 1, 54 | 'stroke': 1, 55 | 'stroke-dasharray': 1, 56 | 'stroke-dashoffset': 1, 57 | 'stroke-linecap': 1, 58 | 'stroke-linejoin': 1, 59 | 'stroke-miterlimit': 1, 60 | 'stroke-opacity': 1, 61 | 'stroke-width': LENGTH_ATTR, 62 | 'text-anchor': 1, 63 | 'text-decoration': 1, 64 | 'text-rendering': 1, 65 | 'unicode-bidi': 1, 66 | 'visibility': 1, 67 | 'word-spacing': 1, 68 | 'writing-mode': 1 69 | }; 70 | 71 | 72 | function getAttribute(node, name) { 73 | if (CSS_PROPERTIES[name]) { 74 | return node.style[name]; 75 | } else { 76 | return node.getAttributeNS(null, name); 77 | } 78 | } 79 | 80 | function setAttribute(node, name, value) { 81 | var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); 82 | 83 | var type = CSS_PROPERTIES[hyphenated]; 84 | 85 | if (type) { 86 | 87 | // append pixel unit, unless present 88 | if (type === LENGTH_ATTR && typeof value === 'number') { 89 | value = String(value) + 'px'; 90 | } 91 | 92 | node.style[hyphenated] = value; 93 | } else { 94 | node.setAttributeNS(null, name, value); 95 | } 96 | } 97 | 98 | function setAttributes(node, attrs) { 99 | 100 | var names = Object.keys(attrs), i, name; 101 | 102 | for (i = 0, name; (name = names[i]); i++) { 103 | setAttribute(node, name, attrs[name]); 104 | } 105 | } 106 | 107 | /** 108 | * Gets or sets raw attributes on a node. 109 | * 110 | * @param {SVGElement} node 111 | * @param {Object} [attrs] 112 | * @param {String} [name] 113 | * @param {String} [value] 114 | * 115 | * @return {String} 116 | */ 117 | export default function attr(node, name, value) { 118 | if (typeof name === 'string') { 119 | if (value !== undefined) { 120 | setAttribute(node, name, value); 121 | } else { 122 | return getAttribute(node, name); 123 | } 124 | } else { 125 | setAttributes(node, name); 126 | } 127 | 128 | return node; 129 | } 130 | -------------------------------------------------------------------------------- /lib/classes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Taken from https://github.com/component/classes 3 | * 4 | * Without the component bits. 5 | */ 6 | 7 | /** 8 | * toString reference. 9 | */ 10 | 11 | const toString = Object.prototype.toString; 12 | 13 | /** 14 | * Wrap `el` in a `ClassList`. 15 | * 16 | * @param {Element} el 17 | * @return {ClassList} 18 | * @api public 19 | */ 20 | 21 | export default function classes(el) { 22 | return new ClassList(el); 23 | } 24 | 25 | function ClassList(el) { 26 | if (!el || !el.nodeType) { 27 | throw new Error('A DOM element reference is required'); 28 | } 29 | this.el = el; 30 | this.list = el.classList; 31 | } 32 | 33 | /** 34 | * Add class `name` if not already present. 35 | * 36 | * @param {String} name 37 | * @return {ClassList} 38 | * @api public 39 | */ 40 | 41 | ClassList.prototype.add = function(name) { 42 | this.list.add(name); 43 | return this; 44 | }; 45 | 46 | /** 47 | * Remove class `name` when present, or 48 | * pass a regular expression to remove 49 | * any which match. 50 | * 51 | * @param {String|RegExp} name 52 | * @return {ClassList} 53 | * @api public 54 | */ 55 | 56 | ClassList.prototype.remove = function(name) { 57 | if ('[object RegExp]' == toString.call(name)) { 58 | return this.removeMatching(name); 59 | } 60 | 61 | this.list.remove(name); 62 | return this; 63 | }; 64 | 65 | /** 66 | * Remove all classes matching `re`. 67 | * 68 | * @param {RegExp} re 69 | * @return {ClassList} 70 | * @api private 71 | */ 72 | 73 | ClassList.prototype.removeMatching = function(re) { 74 | const arr = this.array(); 75 | for (let i = 0; i < arr.length; i++) { 76 | if (re.test(arr[i])) { 77 | this.remove(arr[i]); 78 | } 79 | } 80 | return this; 81 | }; 82 | 83 | /** 84 | * Toggle class `name`, can force state via `force`. 85 | * 86 | * For browsers that support classList, but do not support `force` yet, 87 | * the mistake will be detected and corrected. 88 | * 89 | * @param {String} name 90 | * @param {Boolean} force 91 | * @return {ClassList} 92 | * @api public 93 | */ 94 | 95 | ClassList.prototype.toggle = function(name, force) { 96 | if ('undefined' !== typeof force) { 97 | if (force !== this.list.toggle(name, force)) { 98 | this.list.toggle(name); // toggle again to correct 99 | } 100 | } else { 101 | this.list.toggle(name); 102 | } 103 | return this; 104 | }; 105 | 106 | /** 107 | * Return an array of classes. 108 | * 109 | * @return {Array} 110 | * @api public 111 | */ 112 | 113 | ClassList.prototype.array = function() { 114 | return Array.from(this.list); 115 | }; 116 | 117 | /** 118 | * Check if class `name` is present. 119 | * 120 | * @param {String} name 121 | * @return {ClassList} 122 | * @api public 123 | */ 124 | 125 | ClassList.prototype.has = 126 | ClassList.prototype.contains = function(name) { 127 | return this.list.contains(name); 128 | }; 129 | -------------------------------------------------------------------------------- /lib/clear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clear utility 3 | */ 4 | 5 | /** 6 | * Removes all children from the given element 7 | * 8 | * @param {SVGElement} element 9 | * @return {Element} the element (for chaining) 10 | */ 11 | export default function clear(element) { 12 | var child; 13 | 14 | while ((child = element.firstChild)) { 15 | element.removeChild(child); 16 | } 17 | 18 | return element; 19 | } -------------------------------------------------------------------------------- /lib/clone.js: -------------------------------------------------------------------------------- 1 | export default function clone(element) { 2 | return element.cloneNode(true); 3 | } -------------------------------------------------------------------------------- /lib/create.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create utility for SVG elements 3 | */ 4 | 5 | import attr from './attr.js'; 6 | import parse from './util/parse.js'; 7 | import ns from './util/ns.js'; 8 | 9 | 10 | /** 11 | * Create a specific type from name or SVG markup. 12 | * 13 | * @param {String} name the name or markup of the element 14 | * @param {Object} [attrs] attributes to set on the element 15 | * 16 | * @returns {SVGElement} 17 | */ 18 | export default function create(name, attrs) { 19 | var element; 20 | 21 | name = name.trim(); 22 | 23 | if (name.charAt(0) === '<') { 24 | element = parse(name).firstChild; 25 | element = document.importNode(element, true); 26 | } else { 27 | element = document.createElementNS(ns.svg, name); 28 | } 29 | 30 | if (attrs) { 31 | attr(element, attrs); 32 | } 33 | 34 | return element; 35 | } -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Events handling utility 3 | */ 4 | 5 | export function on(node, event, listener, useCapture) { 6 | node.addEventListener(event, listener, useCapture); 7 | } 8 | 9 | export function off(node, event, listener, useCapture) { 10 | node.removeEventListener(event, listener, useCapture); 11 | } -------------------------------------------------------------------------------- /lib/geometry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Geometry helpers 3 | */ 4 | 5 | import create from './create.js'; 6 | 7 | // fake node used to instantiate svg geometry elements 8 | var node = null; 9 | 10 | function getNode() { 11 | if (node === null) { 12 | node = create('svg'); 13 | } 14 | 15 | return node; 16 | } 17 | 18 | function extend(object, props) { 19 | var i, k, keys = Object.keys(props); 20 | 21 | for (i = 0; (k = keys[i]); i++) { 22 | object[k] = props[k]; 23 | } 24 | 25 | return object; 26 | } 27 | 28 | 29 | export function createPoint(x, y) { 30 | var point = getNode().createSVGPoint(); 31 | 32 | switch (arguments.length) { 33 | case 0: 34 | return point; 35 | case 2: 36 | x = { 37 | x: x, 38 | y: y 39 | }; 40 | break; 41 | } 42 | 43 | return extend(point, x); 44 | } 45 | 46 | /** 47 | * Create matrix via args. 48 | * 49 | * @example 50 | * 51 | * createMatrix({ a: 1, b: 1 }); 52 | * createMatrix(); 53 | * createMatrix(1, 2, 0, 0, 30, 20); 54 | * 55 | * @return {SVGMatrix} 56 | */ 57 | export function createMatrix(a, b, c, d, e, f) { 58 | var matrix = getNode().createSVGMatrix(); 59 | 60 | switch (arguments.length) { 61 | case 0: 62 | return matrix; 63 | case 1: 64 | return extend(matrix, a); 65 | case 6: 66 | return extend(matrix, { 67 | a: a, 68 | b: b, 69 | c: c, 70 | d: d, 71 | e: e, 72 | f: f 73 | }); 74 | } 75 | } 76 | 77 | export function createTransform(matrix) { 78 | if (matrix) { 79 | return getNode().createSVGTransformFromMatrix(matrix); 80 | } else { 81 | return getNode().createSVGTransform(); 82 | } 83 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | export { default as append } from './append.js'; 2 | export { default as appendTo } from './appendTo.js'; 3 | export { default as attr } from './attr.js'; 4 | export { default as classes } from './classes.js'; 5 | export { default as clear } from './clear.js'; 6 | export { default as clone } from './clone.js'; 7 | export { default as create } from './create.js'; 8 | export * from './events.js'; 9 | export * from './geometry.js'; 10 | export { default as innerSVG } from './innerSVG.js'; 11 | export * from './query.js'; 12 | export { default as prepend } from './prepend.js'; 13 | export { default as prependTo } from './prependTo.js'; 14 | export { default as remove } from './remove.js'; 15 | export { default as replace } from './replace.js'; 16 | export { default as transform } from './transform.js'; -------------------------------------------------------------------------------- /lib/innerSVG.js: -------------------------------------------------------------------------------- 1 | /** 2 | * innerHTML like functionality for SVG elements. 3 | * based on innerSVG (https://code.google.com/p/innersvg) 4 | */ 5 | 6 | import clear from './clear.js'; 7 | import appendTo from './appendTo.js'; 8 | import parse from './util/parse.js'; 9 | import serialize from './util/serialize.js'; 10 | 11 | 12 | function set(element, svg) { 13 | 14 | var parsed = parse(svg); 15 | 16 | // clear element contents 17 | clear(element); 18 | 19 | if (!svg) { 20 | return; 21 | } 22 | 23 | if (!isFragment(parsed)) { 24 | 25 | // extract from parsed document 26 | parsed = parsed.documentElement; 27 | } 28 | 29 | var nodes = slice(parsed.childNodes); 30 | 31 | // import + append each node 32 | for (var i = 0; i < nodes.length; i++) { 33 | appendTo(nodes[i], element); 34 | } 35 | 36 | } 37 | 38 | function get(element) { 39 | var child = element.firstChild, 40 | output = []; 41 | 42 | while (child) { 43 | serialize(child, output); 44 | child = child.nextSibling; 45 | } 46 | 47 | return output.join(''); 48 | } 49 | 50 | function isFragment(node) { 51 | return node.nodeName === '#document-fragment'; 52 | } 53 | 54 | export default function innerSVG(element, svg) { 55 | 56 | if (svg !== undefined) { 57 | 58 | try { 59 | set(element, svg); 60 | } catch (e) { 61 | throw new Error('error parsing SVG: ' + e.message); 62 | } 63 | 64 | return element; 65 | } else { 66 | return get(element); 67 | } 68 | } 69 | 70 | 71 | function slice(arr) { 72 | return Array.prototype.slice.call(arr); 73 | } -------------------------------------------------------------------------------- /lib/prepend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * prepend utility 3 | */ 4 | 5 | import prependTo from './prependTo.js'; 6 | 7 | /** 8 | * Prepend a node to a target element 9 | * 10 | * @param {SVGElement} target 11 | * @param {SVGElement} node 12 | * 13 | * @return {SVGElement} the target element 14 | */ 15 | export default function prepend(target, node) { 16 | prependTo(node, target); 17 | return target; 18 | } -------------------------------------------------------------------------------- /lib/prependTo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * prependTo utility 3 | */ 4 | 5 | import ensureImported from './util/ensureImported.js'; 6 | 7 | /** 8 | * Prepend a node to a target element and return the prepended node. 9 | * 10 | * @param {SVGElement} node 11 | * @param {SVGElement} target 12 | * 13 | * @return {SVGElement} the prepended node 14 | */ 15 | export default function prependTo(node, target) { 16 | return target.insertBefore(ensureImported(node, target), target.firstChild || null); 17 | } -------------------------------------------------------------------------------- /lib/query.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Selection utilities 3 | */ 4 | 5 | export function select(node, selector) { 6 | return node.querySelector(selector); 7 | } 8 | 9 | export function selectAll(node, selector) { 10 | var nodes = node.querySelectorAll(selector); 11 | 12 | return [].map.call(nodes, function(element) { 13 | return element; 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/remove.js: -------------------------------------------------------------------------------- 1 | export default function remove(element) { 2 | var parent = element.parentNode; 3 | 4 | if (parent) { 5 | parent.removeChild(element); 6 | } 7 | 8 | return element; 9 | } -------------------------------------------------------------------------------- /lib/replace.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replace utility 3 | */ 4 | 5 | import ensureImported from './util/ensureImported.js'; 6 | 7 | export default function replace(element, replacement) { 8 | element.parentNode.replaceChild(ensureImported(replacement, element), element); 9 | return replacement; 10 | } -------------------------------------------------------------------------------- /lib/transform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * transform accessor utility 3 | */ 4 | 5 | function wrapMatrix(transformList, transform) { 6 | if (transform instanceof SVGMatrix) { 7 | return transformList.createSVGTransformFromMatrix(transform); 8 | } 9 | 10 | return transform; 11 | } 12 | 13 | 14 | function setTransforms(transformList, transforms) { 15 | var i, t; 16 | 17 | transformList.clear(); 18 | 19 | for (i = 0; (t = transforms[i]); i++) { 20 | transformList.appendItem(wrapMatrix(transformList, t)); 21 | } 22 | } 23 | 24 | /** 25 | * Get or set the transforms on the given node. 26 | * 27 | * @param {SVGElement} node 28 | * @param {SVGTransform|SVGMatrix|Array} [transforms] 29 | * 30 | * @return {SVGTransform} the consolidated transform 31 | */ 32 | export default function transform(node, transforms) { 33 | var transformList = node.transform.baseVal; 34 | 35 | if (transforms) { 36 | 37 | if (!Array.isArray(transforms)) { 38 | transforms = [ transforms ]; 39 | } 40 | 41 | setTransforms(transformList, transforms); 42 | } 43 | 44 | return transformList.consolidate(); 45 | } -------------------------------------------------------------------------------- /lib/util/ensureImported.js: -------------------------------------------------------------------------------- 1 | export default function ensureImported(element, target) { 2 | 3 | if (element.ownerDocument !== target.ownerDocument) { 4 | try { 5 | 6 | // may fail on webkit 7 | return target.ownerDocument.importNode(element, true); 8 | } catch (e) { 9 | 10 | // ignore 11 | } 12 | } 13 | 14 | return element; 15 | } -------------------------------------------------------------------------------- /lib/util/ns.js: -------------------------------------------------------------------------------- 1 | export default { 2 | svg: 'http://www.w3.org/2000/svg' 3 | }; 4 | -------------------------------------------------------------------------------- /lib/util/parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DOM parsing utility 3 | */ 4 | 5 | import ns from './ns.js'; 6 | 7 | var SVG_START = '' + svg + ''; 22 | unwrap = true; 23 | } 24 | 25 | var parsed = parseDocument(svg); 26 | 27 | if (!unwrap) { 28 | return parsed; 29 | } 30 | 31 | var fragment = document.createDocumentFragment(); 32 | 33 | var parent = parsed.firstChild; 34 | 35 | while (parent.firstChild) { 36 | fragment.appendChild(parent.firstChild); 37 | } 38 | 39 | return fragment; 40 | } 41 | 42 | function parseDocument(svg) { 43 | 44 | var parser; 45 | 46 | // parse 47 | parser = new DOMParser(); 48 | parser.async = false; 49 | 50 | return parser.parseFromString(svg, 'text/xml'); 51 | } -------------------------------------------------------------------------------- /lib/util/serialize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Serialization util 3 | */ 4 | 5 | var TEXT_ENTITIES = /([&<>]{1})/g; 6 | var ATTR_ENTITIES = /([&<>\n\r"]{1})/g; 7 | 8 | var ENTITY_REPLACEMENT = { 9 | '&': '&', 10 | '<': '<', 11 | '>': '>', 12 | '"': '\'' 13 | }; 14 | 15 | function escape(str, pattern) { 16 | 17 | function replaceFn(match, entity) { 18 | return ENTITY_REPLACEMENT[entity] || entity; 19 | } 20 | 21 | return str.replace(pattern, replaceFn); 22 | } 23 | 24 | export default function serialize(node, output) { 25 | 26 | var i, len, attrMap, attrNode, childNodes; 27 | 28 | switch (node.nodeType) { 29 | 30 | // TEXT 31 | case 3: 32 | 33 | // replace special XML characters 34 | output.push(escape(node.textContent, TEXT_ENTITIES)); 35 | break; 36 | 37 | // ELEMENT 38 | case 1: 39 | output.push('<', node.tagName); 40 | 41 | if (node.hasAttributes()) { 42 | attrMap = node.attributes; 43 | for (i = 0, len = attrMap.length; i < len; ++i) { 44 | attrNode = attrMap.item(i); 45 | output.push(' ', attrNode.name, '="', escape(attrNode.value, ATTR_ENTITIES), '"'); 46 | } 47 | } 48 | 49 | if (node.hasChildNodes()) { 50 | output.push('>'); 51 | childNodes = node.childNodes; 52 | for (i = 0, len = childNodes.length; i < len; ++i) { 53 | serialize(childNodes.item(i), output); 54 | } 55 | output.push(''); 56 | } else { 57 | output.push('/>'); 58 | } 59 | break; 60 | 61 | // COMMENT 62 | case 8: 63 | output.push(''); 64 | break; 65 | 66 | // CDATA 67 | case 4: 68 | output.push('', node.nodeValue, ''); 69 | break; 70 | 71 | default: 72 | throw new Error('unable to handle node ' + node.nodeType); 73 | } 74 | 75 | return output; 76 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-svg", 3 | "version": "4.1.3", 4 | "description": "A minimal toolbelt for builing fast SVG-based applications", 5 | "type": "module", 6 | "scripts": { 7 | "all": "run-s lint test", 8 | "pretest": "run-s bundle", 9 | "bundle": "rollup -c --bundleConfigAsCjs", 10 | "dev": "npm test -- --auto-watch --no-single-run", 11 | "lint": "eslint .", 12 | "prepare": "run-s bundle", 13 | "test": "karma start karma.conf.cjs" 14 | }, 15 | "exports": { 16 | ".": { 17 | "import": "./dist/index.js", 18 | "types": "./dist/index.d.ts" 19 | }, 20 | "./package.json": "./package.json" 21 | }, 22 | "types": "dist/index.d.ts", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/bpmn-io/tiny-svg" 26 | }, 27 | "keywords": [ 28 | "svg", 29 | "library", 30 | "geometry" 31 | ], 32 | "engines": { 33 | "node": ">= 16" 34 | }, 35 | "author": { 36 | "name": "Nico Rehwaldt", 37 | "url": "https://github.com/nikku" 38 | }, 39 | "license": "MIT", 40 | "sideEffects": false, 41 | "devDependencies": { 42 | "chai": "^4.4.1", 43 | "eslint": "^8.57.0", 44 | "eslint-plugin-bpmn-io": "^1.0.0", 45 | "karma": "^6.4.3", 46 | "karma-chai": "^0.1.0", 47 | "karma-chrome-launcher": "^3.2.0", 48 | "karma-firefox-launcher": "^2.1.3", 49 | "karma-mocha": "^2.0.1", 50 | "karma-webpack": "^5.0.1", 51 | "mocha": "^10.3.0", 52 | "npm-run-all": "^4.1.5", 53 | "puppeteer": "^22.4.0", 54 | "rollup": "^4.12.1", 55 | "webpack": "^5.90.3" 56 | }, 57 | "files": [ 58 | "dist" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json'; 2 | 3 | const pkgExport = pkg.exports['.']; 4 | 5 | export default [ 6 | { 7 | input: 'lib/index.js', 8 | output: [ 9 | { file: pkgExport.import, format: 'es', sourcemap: true } 10 | ] 11 | } 12 | ]; -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plugin:bpmn-io/mocha" 3 | } -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | export function createContainer() { 2 | var container = document.createElement('div'); 3 | 4 | document.body.appendChild(container); 5 | 6 | return container; 7 | } 8 | 9 | 10 | export function normalizeAttr(str) { 11 | return str.replace(/, /g, ' '); 12 | } -------------------------------------------------------------------------------- /test/spec/append.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | append 4 | } from 'tiny-svg'; 5 | 6 | import { 7 | createContainer 8 | } from '../helper.js'; 9 | 10 | 11 | describe('append', function() { 12 | 13 | it('should append + return parent', function() { 14 | 15 | // given 16 | var container = createContainer(), 17 | svg = create('svg'); 18 | 19 | // when 20 | var result = append(container, svg); 21 | 22 | // then 23 | expect(result).to.equal(container); 24 | 25 | expect(svg.parentNode).to.eql(result); 26 | }); 27 | 28 | }); -------------------------------------------------------------------------------- /test/spec/appendTo.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | appendTo 4 | } from 'tiny-svg'; 5 | 6 | import { 7 | createContainer 8 | } from '../helper.js'; 9 | 10 | 11 | describe('appendTo', function() { 12 | 13 | it('should append + return node', function() { 14 | 15 | // given 16 | var container = createContainer(); 17 | var svg = create('svg'); 18 | 19 | // when 20 | var result = appendTo(svg, container); 21 | 22 | // then 23 | expect(result).to.equal(svg); 24 | 25 | expect(result.parentNode).to.eql(container); 26 | }); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /test/spec/attr.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | select, 4 | attr 5 | } from 'tiny-svg'; 6 | 7 | import { 8 | normalizeAttr 9 | } from '../helper.js'; 10 | 11 | 12 | describe('attr', function() { 13 | 14 | it('should get attribute', function() { 15 | 16 | // given 17 | var svg = create('svg'); 18 | 19 | // when 20 | var viewbox = attr(svg, 'viewBox'); 21 | 22 | // then 23 | expect(!!viewbox).to.be.false; 24 | }); 25 | 26 | 27 | it('should get css', function() { 28 | 29 | // given 30 | var rect = create('rect'); 31 | 32 | // when 33 | var stroke = attr(rect, 'stroke'); 34 | 35 | // then 36 | expect(!!stroke).to.be.false; 37 | }); 38 | 39 | 40 | it('should set attribute', function() { 41 | 42 | // given 43 | var svg = create('svg'); 44 | 45 | // when 46 | attr(svg, 'viewBox', '100 100 200 200'); 47 | 48 | // then 49 | expect(attr(svg, 'viewBox')).to.eql('100 100 200 200'); 50 | }); 51 | 52 | 53 | it('should set css attribute', function() { 54 | 55 | // given 56 | var rect = create('rect'); 57 | 58 | // when 59 | attr(rect, 'stroke-width', '2px'); 60 | 61 | // then 62 | expect(attr(rect, 'stroke-width')).to.eql('2px'); 63 | }); 64 | 65 | 66 | it('should set marker-start attribute', function() { 67 | 68 | // given 69 | var rect = create('rect'); 70 | 71 | // when 72 | attr(rect, 'marker-start', 'url("#foo")'); 73 | 74 | // then 75 | // Chrome, Firefox export url enclosed in <""> 76 | expect(attr(rect, 'marker-start')).to.match(/url\((#foo|"#foo")\)/); 77 | }); 78 | 79 | 80 | it('should set markerEnd attribute', function() { 81 | 82 | // given 83 | var rect = create('rect'); 84 | 85 | // when 86 | attr(rect, 'markerEnd', 'url(#bar)'); 87 | 88 | // then 89 | // Chrome, Firefox export url enclosed in <""> 90 | expect(attr(rect, 'marker-end')).to.match(/url\((#bar|"#bar")\)/); 91 | }); 92 | 93 | 94 | it('should set attribute', function() { 95 | 96 | // given 97 | var svg = create('svg'); 98 | 99 | // when 100 | attr(svg, 'view-box', '100 100 200 200'); 101 | 102 | // then 103 | expect(attr(svg, 'view-box')).to.eql('100 100 200 200'); 104 | }); 105 | 106 | 107 | it('should get transform attribute', function() { 108 | 109 | // given 110 | var svg = create(''), 111 | g = select(svg, 'g'); 112 | 113 | // when 114 | var transform = attr(g, 'transform'); 115 | 116 | // then 117 | expect(normalizeAttr(transform)).to.eql('translate(100 100)'); 118 | }); 119 | 120 | }); -------------------------------------------------------------------------------- /test/spec/classes.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | classes 4 | } from 'tiny-svg'; 5 | 6 | 7 | function classList(el) { 8 | return el.className.baseVal.split(' '); 9 | } 10 | 11 | 12 | describe('classes', function() { 13 | 14 | it('should add a class', function() { 15 | 16 | // given 17 | var rect = create('rect'); 18 | 19 | // when 20 | classes(rect).add('foo'); 21 | 22 | // then 23 | expect(classList(rect)).to.contain('foo'); 24 | 25 | }); 26 | 27 | 28 | it('should remove a class', function() { 29 | 30 | // given 31 | var rect = create('rect'); 32 | classes(rect).add('foo'); 33 | 34 | // when 35 | classes(rect).remove('foo'); 36 | 37 | // then 38 | expect(classList(rect)).not.to.contain('foo'); 39 | 40 | }); 41 | 42 | 43 | it('should toggle a class', function() { 44 | 45 | // given 46 | var rect = create('rect'); 47 | 48 | // when 49 | classes(rect).toggle('foo'); 50 | 51 | // then 52 | expect(classList(rect)).to.contain('foo'); 53 | 54 | // when 55 | classes(rect).toggle('foo'); 56 | 57 | // then 58 | expect(classList(rect)).not.to.contain('foo'); 59 | 60 | }); 61 | 62 | 63 | it('should check for class \'has\'', function() { 64 | 65 | // given 66 | var rect = create('rect'); 67 | classes(rect).add('foo'); 68 | 69 | // then 70 | expect(classes(rect).has('foo')).to.be.true; 71 | 72 | // when 73 | classes(rect).remove('foo'); 74 | 75 | // then 76 | expect(classes(rect).has('foo')).not.to.be.true; 77 | 78 | }); 79 | 80 | 81 | it('should check for class \'contains\'', function() { 82 | 83 | // given 84 | var rect = create('rect'); 85 | classes(rect).add('foo'); 86 | 87 | // then 88 | expect(classes(rect).contains('foo')).to.be.true; 89 | 90 | // when 91 | classes(rect).remove('foo'); 92 | 93 | // then 94 | expect(classes(rect).contains('foo')).not.to.be.true; 95 | 96 | }); 97 | 98 | }); 99 | -------------------------------------------------------------------------------- /test/spec/clear.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | clear, 4 | select 5 | } from 'tiny-svg'; 6 | 7 | 8 | describe('clear', function() { 9 | 10 | it('should clear ', function() { 11 | 12 | // given 13 | var svg = create(` 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | `); 32 | 33 | // when 34 | var result = clear(svg); 35 | 36 | // then 37 | expect(result).to.equal(svg); 38 | expect(result.childNodes.length).to.eql(0); 39 | }); 40 | 41 | 42 | it('should clear ', function() { 43 | 44 | // given 45 | var svg = create(` 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | `); 55 | 56 | var g = select(svg, 'g'); 57 | 58 | // when 59 | var result = clear(g); 60 | 61 | // then 62 | expect(result).to.equal(g); 63 | expect(result.childNodes.length).to.eql(0); 64 | }); 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /test/spec/clone.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | appendTo, 4 | innerSVG as _innerSVG, 5 | clone, 6 | createTransform, 7 | createMatrix as matrix, 8 | transform, 9 | attr as _attr, 10 | select 11 | } from 'tiny-svg'; 12 | 13 | import { 14 | createContainer, 15 | normalizeAttr 16 | } from '../helper.js'; 17 | 18 | 19 | describe('clone', function() { 20 | 21 | var container, svg; 22 | 23 | beforeEach(function() { 24 | container = createContainer(); 25 | svg = appendTo(create('svg'), container); 26 | }); 27 | 28 | 29 | it('should clone svg', function() { 30 | 31 | // given 32 | var text = ''; 33 | 34 | innerSVG(svg, text); 35 | 36 | // when 37 | var cloned = clone(svg); 38 | 39 | // then 40 | expect(innerSVG(cloned)).to.eql(text); 41 | }); 42 | 43 | 44 | it('should clone group', function() { 45 | 46 | // given 47 | var text = ''; 48 | 49 | innerSVG(svg, text); 50 | 51 | // when 52 | var g = select(svg, 'g'); 53 | 54 | var clonedG = clone(g); 55 | 56 | // then 57 | expect(innerSVG(clonedG)).to.eql(innerSVG(g)); 58 | }); 59 | 60 | 61 | it('should clone data-attribute', function() { 62 | 63 | // given 64 | var g = appendTo(create('g'), svg); 65 | 66 | attr(g, 'data-foo', '1000'); 67 | 68 | // when 69 | var clonedG = clone(g); 70 | 71 | // then 72 | expect(attr(clonedG, 'data-foo')).to.eql('1000'); 73 | }); 74 | 75 | 76 | it('should clone transform', function() { 77 | 78 | // given 79 | var g = appendTo(create('g'), svg); 80 | 81 | transform(g, [ 82 | matrix({ e: -50, f: -80 }), 83 | matrix({ e: 100, f: 100 }), 84 | translate(100, 100) 85 | ]); 86 | 87 | // when 88 | var clonedG = clone(g); 89 | 90 | // then 91 | expect(attr(clonedG, 'transform')).to.eql('matrix(1 0 0 1 150 120)'); 92 | }); 93 | 94 | }); 95 | 96 | 97 | // helpers ///////////////////// 98 | 99 | function translate(x, y) { 100 | var t = createTransform(); 101 | 102 | t.setTranslate(x, y); 103 | 104 | return t; 105 | } 106 | 107 | function innerSVG(el, value) { 108 | 109 | if (arguments.length === 2) { 110 | return _innerSVG(el, value); 111 | } else { 112 | return normalizeAttr(_innerSVG(el)); 113 | } 114 | } 115 | 116 | 117 | function attr(el, key, value) { 118 | if (arguments.length === 3) { 119 | return _attr(el, key, value); 120 | } else { 121 | return normalizeAttr(_attr(el, key)); 122 | } 123 | } -------------------------------------------------------------------------------- /test/spec/create.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | appendTo, 4 | append 5 | } from 'tiny-svg'; 6 | 7 | import { 8 | createContainer 9 | } from '../helper.js'; 10 | 11 | 12 | describe('create', function() { 13 | 14 | it('should create SVGElement', function() { 15 | 16 | // when 17 | var svg = create('svg'); 18 | 19 | // then 20 | expect(svg).to.exist; 21 | }); 22 | 23 | 24 | it('should create + append SVGElement', function() { 25 | 26 | // given 27 | var container = createContainer(); 28 | 29 | // when 30 | var svg = appendTo(create('svg', { viewBox: '100 100 200 200', width: 200, height: 200 }), container); 31 | 32 | // then 33 | expect(svg).to.exist; 34 | }); 35 | 36 | 37 | it('should create from markup', function() { 38 | 39 | // given 40 | var container = createContainer(); 41 | 42 | // when 43 | var svg = create(''); 44 | 45 | append(container, svg); 46 | 47 | // then 48 | expect(svg.nodeName).to.eql('svg'); 49 | expect(svg.children.length).to.eql(1); 50 | expect(svg.children[0].nodeName).to.eql('g'); 51 | }); 52 | 53 | 54 | it('should create from multi-line markup (trimming whitespace)', function() { 55 | 56 | // given 57 | var container = createContainer(); 58 | 59 | // when 60 | var svg = create(` 61 | 62 | 63 | 64 | 65 | 66 | `); 67 | 68 | append(container, svg); 69 | 70 | // then 71 | expect(svg.nodeName).to.eql('svg'); 72 | expect(svg.children.length).to.eql(1); 73 | expect(svg.children[0].nodeName).to.eql('g'); 74 | }); 75 | 76 | 77 | it('should create from markup', function() { 78 | 79 | // given 80 | var container = createContainer(); 81 | 82 | // when 83 | var g = create(''); 84 | 85 | append(container, g); 86 | 87 | // then 88 | expect(g.nodeName).to.eql('g'); 89 | expect(g.id).to.eql('G'); 90 | expect(g.childNodes.length).to.eql(1); 91 | expect(g.childNodes[0].nodeName).to.eql('circle'); 92 | }); 93 | 94 | }); -------------------------------------------------------------------------------- /test/spec/geometry.js: -------------------------------------------------------------------------------- 1 | import { 2 | createMatrix, 3 | createPoint, 4 | createTransform 5 | } from 'tiny-svg'; 6 | 7 | 8 | describe('geometry', function() { 9 | 10 | describe('createPoint', function() { 11 | 12 | it('should create point', function() { 13 | 14 | // when 15 | var p = createPoint(); 16 | 17 | // then 18 | expect(p instanceof SVGPoint).to.be.true; 19 | 20 | expect(p.x).to.eql(0); 21 | expect(p.y).to.eql(0); 22 | }); 23 | 24 | 25 | it('should create point from (x, y)', function() { 26 | 27 | // when 28 | var p = createPoint(10, 20); 29 | 30 | // then 31 | expect(p.x).to.eql(10); 32 | expect(p.y).to.eql(20); 33 | }); 34 | 35 | 36 | it('should create point from Object{x, y}', function() { 37 | 38 | // when 39 | var p = createPoint({ x: 10, y: 20 }); 40 | 41 | // then 42 | expect(p.x).to.eql(10); 43 | expect(p.y).to.eql(20); 44 | }); 45 | 46 | }); 47 | 48 | 49 | describe('createMatrix', function() { 50 | 51 | it('should create matrix (object notation)', function() { 52 | 53 | // when 54 | var m = createMatrix({ e: 10, f: 20 }); 55 | 56 | // then 57 | expect(m instanceof SVGMatrix).to.be.true; 58 | 59 | expect(m.e).to.eql(10); 60 | expect(m.f).to.eql(20); 61 | }); 62 | 63 | 64 | it('should create matrix (a..f)', function() { 65 | 66 | // when 67 | var m = createMatrix(0, 0, 0, 0, 10, 20); 68 | 69 | // then 70 | expect(m instanceof SVGMatrix).to.be.true; 71 | 72 | expect(m.a).to.eql(0); 73 | expect(m.b).to.eql(0); 74 | expect(m.c).to.eql(0); 75 | expect(m.d).to.eql(0); 76 | expect(m.e).to.eql(10); 77 | expect(m.f).to.eql(20); 78 | }); 79 | 80 | }); 81 | 82 | 83 | describe('createTransform', function() { 84 | 85 | it('should create transform', function() { 86 | 87 | // then 88 | expect(createTransform).to.exist; 89 | }); 90 | 91 | }); 92 | 93 | }); -------------------------------------------------------------------------------- /test/spec/innerSVG.js: -------------------------------------------------------------------------------- 1 | import { 2 | create, 3 | appendTo, 4 | innerSVG, 5 | attr 6 | } from 'tiny-svg'; 7 | 8 | import { 9 | createContainer 10 | } from '../helper.js'; 11 | 12 | 13 | describe('inner-svg', function() { 14 | 15 | describe('set', function() { 16 | 17 | it('should set simple', function() { 18 | 19 | // given 20 | var container = createContainer(); 21 | var element = appendTo(create('svg'), container); 22 | 23 | var text = ''; 24 | 25 | // when 26 | innerSVG(element, text); 27 | 28 | // then 29 | expect(element.childNodes.length).to.eql(1); 30 | }); 31 | 32 | 33 | it('should set CDATA', function() { 34 | 35 | // given 36 | var container = createContainer(); 37 | var element = appendTo(create('svg'), container); 38 | 39 | var text = 40 | '' + 41 | '' + 44 | ''; 45 | 46 | // when 47 | innerSVG(element, text); 48 | 49 | // then 50 | expect(element.childNodes.length).to.eql(1); 51 | }); 52 | 53 | 54 | it('should set url style', function() { 55 | 56 | // given 57 | var container = createContainer(); 58 | var element = appendTo(create('svg'), container); 59 | 60 | var text = ''; 61 | 62 | // when 63 | innerSVG(element, text); 64 | 65 | var groupNode = element.childNodes[0]; 66 | 67 | // then 68 | // Chrome, Firefox export url enclosed in <""> 69 | expect(groupNode.style['marker-start']).to.match(/url\((#foo|"#foo")\)/); 70 | }); 71 | 72 | 73 | it('should set from SVG', function() { 74 | 75 | // given 76 | var container = createContainer(); 77 | var element = appendTo(create('svg'), container); 78 | 79 | var text = 80 | '' + 81 | '' + 82 | '' + 83 | ''; 84 | 85 | // when 86 | innerSVG(element, text); 87 | 88 | // then 89 | expect(element.childNodes.length).to.eql(2); 90 | }); 91 | 92 | 93 | it('should set fragment', function() { 94 | 95 | // given 96 | var container = createContainer(); 97 | var element = appendTo(create('svg'), container); 98 | 99 | var text = 100 | '' + 101 | ''; 102 | 103 | // when 104 | innerSVG(element, text); 105 | 106 | // then 107 | expect(element.childNodes.length).to.eql(2); 108 | }); 109 | 110 | }); 111 | 112 | 113 | describe('get', function() { 114 | 115 | it('should get simple', function() { 116 | 117 | // given 118 | var container = createContainer(); 119 | var element = appendTo(create('svg'), container); 120 | 121 | var text = ''; 122 | 123 | innerSVG(element, text); 124 | 125 | // when 126 | var svg = innerSVG(element); 127 | 128 | // then 129 | expect(svg).to.eql(text); 130 | }); 131 | 132 | 133 | it('should get CDATA', function() { 134 | 135 | // given 136 | var container = createContainer(); 137 | var element = appendTo(create('svg'), container); 138 | 139 | var text = 140 | '' + 141 | '' + 142 | ''; 143 | 144 | innerSVG(element, text); 145 | 146 | // when 147 | var svg = innerSVG(element); 148 | 149 | // then 150 | expect(svg).to.eql(text); 151 | }); 152 | 153 | 154 | it('should get url style', function() { 155 | 156 | // given 157 | var container = createContainer(); 158 | var element = appendTo(create('svg'), container); 159 | 160 | var group = appendTo(create('g'), element); 161 | var path = appendTo(create('path'), group); 162 | 163 | attr(path, { 164 | d: 'm 272,202L340,202', 165 | markerEnd: 'url("#sequenceflow-end")' 166 | }); 167 | 168 | // when 169 | var svg = innerSVG(element); 170 | 171 | // then 172 | // Chrome, Firefox export url enclosed in <"">, 173 | // we need to properly escape <"> with <'> to produce 174 | // valid SVG 175 | expect(svg).to.match(/.*url\(('#sequenceflow-end'|#sequenceflow-end)\).*/); 176 | }); 177 | 178 | 179 | it('should get CDATA