vadivam 0.0.42 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/createElement.js +3 -5
- package/dist/font/vadivam.woff2 +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/runtimePolicy.js +142 -0
- package/dist/types.d.ts +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Tree-shakeable 24px outline SVG icons for JavaScript, with raw assets and metada
|
|
|
8
8
|
[](https://www.npmjs.com/package/vadivam)
|
|
9
9
|
[](https://github.com/praveenjuge/vadivam/blob/main/LICENSE)
|
|
10
10
|
|
|
11
|
-

|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
npm install vadivam
|
package/dist/createElement.js
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import defaultAttributes from "./defaultAttributes.js";
|
|
2
|
+
import { sanitizeIconNode, sanitizeRootAttributes } from "./runtimePolicy.js";
|
|
2
3
|
|
|
3
|
-
const unsafeAttribute = /^(?:on|href$|xlink:href$|src$)/i;
|
|
4
4
|
function createSvgElement(document, [tag, attrs, children]) {
|
|
5
5
|
const element = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
6
|
-
for (const [name, value] of Object.entries(attrs))
|
|
7
|
-
if (value !== undefined && !unsafeAttribute.test(name)) element.setAttribute(name, String(value));
|
|
8
|
-
}
|
|
6
|
+
for (const [name, value] of Object.entries(attrs)) element.setAttribute(name, String(value));
|
|
9
7
|
for (const child of children ?? []) element.appendChild(createSvgElement(document, child));
|
|
10
8
|
return element;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
export function createElement(iconNode, customAttrs = {}) {
|
|
14
12
|
if (typeof document === "undefined") throw new Error("createElement() only works in a browser environment.");
|
|
15
|
-
return createSvgElement(document, ["svg", { ...defaultAttributes, ...customAttrs }, iconNode]);
|
|
13
|
+
return createSvgElement(document, ["svg", { ...defaultAttributes, ...sanitizeRootAttributes(customAttrs) }, sanitizeIconNode(iconNode)]);
|
|
16
14
|
}
|
|
17
15
|
export default createElement;
|
package/dist/font/vadivam.woff2
CHANGED
|
Binary file
|
package/dist/index.d.ts
CHANGED
|
@@ -512,4 +512,4 @@ export { default as ZoomIn } from "./icons/zoom-in.js";
|
|
|
512
512
|
export { default as ZoomOut } from "./icons/zoom-out.js";
|
|
513
513
|
export type { CreateIconsOptions } from "./createIcons.js";
|
|
514
514
|
export type { VadivamIconMetadata } from "./manifest.js";
|
|
515
|
-
export type { IconName, IconNode, Icons, SVGProps } from "./types.js";
|
|
515
|
+
export type { IconName, IconNode, Icons, SVGElementType, SVGProps } from "./types.js";
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const SAFE_ICON_TAGS = new Set(["circle","ellipse","line","path","polygon","polyline","rect"]);
|
|
2
|
+
const GEOMETRY_ATTRIBUTES = {
|
|
3
|
+
circle: new Set(["cx", "cy", "r"]),
|
|
4
|
+
ellipse: new Set(["cx", "cy", "rx", "ry"]),
|
|
5
|
+
line: new Set(["x1", "y1", "x2", "y2"]),
|
|
6
|
+
path: new Set(["d", "pathLength"]),
|
|
7
|
+
polygon: new Set(["points", "pathLength"]),
|
|
8
|
+
polyline: new Set(["points", "pathLength"]),
|
|
9
|
+
rect: new Set(["x", "y", "width", "height", "rx", "ry", "pathLength"]),
|
|
10
|
+
};
|
|
11
|
+
const PRESENTATION_ATTRIBUTES = new Set([
|
|
12
|
+
"class", "className", "id", "role", "key", "opacity", "transform",
|
|
13
|
+
"clip-rule", "clipRule", "fill", "fill-opacity", "fillOpacity", "fill-rule", "fillRule",
|
|
14
|
+
"shape-rendering", "shapeRendering", "stroke", "stroke-dasharray", "strokeDasharray",
|
|
15
|
+
"stroke-dashoffset", "strokeDashoffset", "stroke-linecap", "strokeLinecap",
|
|
16
|
+
"stroke-linejoin", "strokeLinejoin", "stroke-miterlimit", "strokeMiterlimit",
|
|
17
|
+
"stroke-opacity", "strokeOpacity", "stroke-width", "strokeWidth", "vector-effect", "vectorEffect",
|
|
18
|
+
]);
|
|
19
|
+
const SAFE_ATTRIBUTE_NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
|
|
20
|
+
const SAFE_DATA_ATTRIBUTE = /^data-[A-Za-z0-9_.-]+$/;
|
|
21
|
+
const SAFE_ARIA_ATTRIBUTE = /^aria-[A-Za-z0-9_.-]+$/;
|
|
22
|
+
const SAFE_STYLE_NAME = /^(?:--[A-Za-z0-9_-]+|[A-Za-z_][A-Za-z0-9_-]*)$/;
|
|
23
|
+
const SAFE_EVENT_NAME = /^on(?::[A-Za-z][A-Za-z0-9_.-]*|[A-Za-z][A-Za-z0-9_.-]*)$/;
|
|
24
|
+
const UNSAFE_ROOT_ATTRIBUTE = /^(?:dangerouslySetInnerHTML|innerHTML|innerText|textContent|children|href|xlinkHref|src|srcdoc)$/i;
|
|
25
|
+
const URL_VALUE_ATTRIBUTE = /^(?:fill|stroke|filter|clipPath|clip-path|mask|marker|markerStart|marker-start|markerMid|marker-mid|markerEnd|marker-end|cursor)$/;
|
|
26
|
+
const SCRIPT_SCHEME = /(?:java|vb)script\s*:/i;
|
|
27
|
+
const URL_FUNCTION = /url\s*\(/i;
|
|
28
|
+
const COMPLETE_URL_FUNCTION = /url\s*\(\s*(["']?)([^"')\s]+)\1\s*\)/gi;
|
|
29
|
+
const EXTERNAL_SCHEME = /(?:https?|data|blob|file|ftp)\s*:/i;
|
|
30
|
+
const CSS_ESCAPE = /\\/;
|
|
31
|
+
const CSS_RESOURCE_FUNCTION = /(?:image-set|cross-fade|image|src)\s*\(/i;
|
|
32
|
+
const CSS_IMPORT = /@import\b/i;
|
|
33
|
+
const MAX_ICON_DEPTH = 32;
|
|
34
|
+
const MAX_ICON_NODES = 1024;
|
|
35
|
+
|
|
36
|
+
function isRecord(value) {
|
|
37
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isSafeValue(name, value) {
|
|
41
|
+
if (value === undefined || value === null || typeof value === "function") return false;
|
|
42
|
+
if (typeof value === "object") return false;
|
|
43
|
+
if (typeof value === "string" && SCRIPT_SCHEME.test(value)) return false;
|
|
44
|
+
if ((name === "fill" || name === "stroke") && typeof value === "string" && hasExternalUrl(value)) return false;
|
|
45
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasExternalUrl(value) {
|
|
49
|
+
if (!URL_FUNCTION.test(value)) return false;
|
|
50
|
+
COMPLETE_URL_FUNCTION.lastIndex = 0;
|
|
51
|
+
let matched = false;
|
|
52
|
+
let match;
|
|
53
|
+
while ((match = COMPLETE_URL_FUNCTION.exec(value)) !== null) {
|
|
54
|
+
matched = true;
|
|
55
|
+
if (!match[2].startsWith("#")) return true;
|
|
56
|
+
}
|
|
57
|
+
return !matched;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hasUnsafeStyleValue(value) {
|
|
61
|
+
return SCRIPT_SCHEME.test(value) || EXTERNAL_SCHEME.test(value) || CSS_ESCAPE.test(value) ||
|
|
62
|
+
CSS_RESOURCE_FUNCTION.test(value) || CSS_IMPORT.test(value) || hasExternalUrl(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isSafeIconAttribute(tag, name) {
|
|
66
|
+
return SAFE_ATTRIBUTE_NAME.test(name) && (
|
|
67
|
+
GEOMETRY_ATTRIBUTES[tag]?.has(name) ||
|
|
68
|
+
PRESENTATION_ATTRIBUTES.has(name) ||
|
|
69
|
+
SAFE_DATA_ATTRIBUTE.test(name) ||
|
|
70
|
+
SAFE_ARIA_ATTRIBUTE.test(name)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sanitizeStyle(value) {
|
|
75
|
+
if (typeof value === "string") return hasUnsafeStyleValue(value) ? undefined : value;
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
const safeValues = value.map(sanitizeStyle).filter((entry) => entry !== undefined);
|
|
78
|
+
return safeValues.length ? safeValues : undefined;
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(value)) return undefined;
|
|
81
|
+
const safeStyle = {};
|
|
82
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
83
|
+
if (!SAFE_STYLE_NAME.test(name) || (typeof entry !== "string" && typeof entry !== "number")) continue;
|
|
84
|
+
if (typeof entry === "string" && hasUnsafeStyleValue(entry)) continue;
|
|
85
|
+
safeStyle[name] = entry;
|
|
86
|
+
}
|
|
87
|
+
return Object.keys(safeStyle).length ? safeStyle : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function safeRootValue(name, value) {
|
|
91
|
+
if (name === "style") return sanitizeStyle(value);
|
|
92
|
+
if (value === undefined || value === null || typeof value === "object" || typeof value === "function") return undefined;
|
|
93
|
+
if (typeof value === "string" && URL_VALUE_ATTRIBUTE.test(name) && (SCRIPT_SCHEME.test(value) || hasExternalUrl(value))) return undefined;
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function sanitizePaint(value, fallback = "currentColor") {
|
|
98
|
+
const safeValue = safeRootValue("stroke", value);
|
|
99
|
+
return typeof safeValue === "string" || typeof safeValue === "number" ? safeValue : fallback;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function sanitizeNodes(iconNode, activeArrays, state, depth) {
|
|
103
|
+
if (!Array.isArray(iconNode) || depth > MAX_ICON_DEPTH || activeArrays.has(iconNode)) return [];
|
|
104
|
+
activeArrays.add(iconNode);
|
|
105
|
+
const safeNodes = [];
|
|
106
|
+
for (const node of iconNode) {
|
|
107
|
+
state.count += 1;
|
|
108
|
+
if (state.count > MAX_ICON_NODES) break;
|
|
109
|
+
if (!Array.isArray(node) || typeof node[0] !== "string" || !SAFE_ICON_TAGS.has(node[0]) || !isRecord(node[1])) continue;
|
|
110
|
+
const [tag, attrs, children] = node;
|
|
111
|
+
const safeAttrs = {};
|
|
112
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
113
|
+
if (isSafeIconAttribute(tag, name) && isSafeValue(name, value)) safeAttrs[name] = value;
|
|
114
|
+
}
|
|
115
|
+
const safeChildren = sanitizeNodes(children, activeArrays, state, depth + 1);
|
|
116
|
+
safeNodes.push(safeChildren.length ? [tag, safeAttrs, safeChildren] : [tag, safeAttrs]);
|
|
117
|
+
}
|
|
118
|
+
activeArrays.delete(iconNode);
|
|
119
|
+
return safeNodes;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function sanitizeIconNode(iconNode) {
|
|
123
|
+
return sanitizeNodes(iconNode, new WeakSet(), { count: 0 }, 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function sanitizeRootAttributes(attrs, allowEventFunctions = false) {
|
|
127
|
+
if (!isRecord(attrs)) return {};
|
|
128
|
+
const safeAttrs = {};
|
|
129
|
+
for (const name of Object.keys(attrs)) {
|
|
130
|
+
const value = attrs[name];
|
|
131
|
+
if (/^on/i.test(name)) {
|
|
132
|
+
if (allowEventFunctions && typeof value === "function" && SAFE_EVENT_NAME.test(name)) {
|
|
133
|
+
Object.defineProperty(safeAttrs, name, { enumerable: true, get: () => typeof attrs[name] === "function" ? attrs[name] : undefined });
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (!SAFE_ATTRIBUTE_NAME.test(name) || UNSAFE_ROOT_ATTRIBUTE.test(name) || /^prop/i.test(name)) continue;
|
|
138
|
+
if (safeRootValue(name, value) === undefined) continue;
|
|
139
|
+
Object.defineProperty(safeAttrs, name, { enumerable: true, get: () => safeRootValue(name, attrs[name]) });
|
|
140
|
+
}
|
|
141
|
+
return safeAttrs;
|
|
142
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export type
|
|
1
|
+
export type SVGElementType = "circle" | "ellipse" | "line" | "path" | "polygon" | "polyline" | "rect";
|
|
2
|
+
export type SVGProps = Record<string, string | number | boolean | undefined>;
|
|
3
|
+
export type IconNode = readonly [tag: SVGElementType, attrs: SVGProps, children?: IconNode][];
|
|
3
4
|
export type Icons = Record<string, IconNode>;
|
|
4
5
|
export type IconName = "a-arrow-down" | "a-arrow-up" | "a-large-small" | "accessibility" | "activity" | "air-vent" | "airplay" | "alarm-clock-check" | "alarm-clock-minus" | "alarm-clock-off" | "alarm-clock-plus" | "alarm-clock" | "alarm-smoke" | "album" | "align-center-horizontal" | "align-center-vertical" | "align-end-horizontal" | "align-end-vertical" | "align-horizontal-distribute-center" | "align-horizontal-distribute-end" | "align-horizontal-distribute-start" | "align-horizontal-justify-center" | "align-horizontal-justify-end" | "align-horizontal-justify-start" | "align-horizontal-space-around" | "align-horizontal-space-between" | "align-start-horizontal" | "align-start-vertical" | "align-vertical-distribute-center" | "align-vertical-distribute-end" | "align-vertical-distribute-start" | "align-vertical-justify-center" | "align-vertical-justify-end" | "align-vertical-justify-start" | "align-vertical-space-around" | "align-vertical-space-between" | "ambulance" | "ampersand" | "ampersands" | "amphora" | "anchor" | "antenna" | "anvil" | "aperture" | "app-window-mac" | "app-window" | "apple" | "archive-restore" | "archive-x" | "archive" | "armchair" | "arrow-big-down-dash" | "arrow-big-down" | "arrow-big-left-dash" | "arrow-big-left" | "arrow-big-right-dash" | "arrow-big-right" | "arrow-big-up-dash" | "arrow-big-up" | "arrow-down-0-1" | "arrow-down-1-0" | "arrow-down-a-z" | "arrow-down-from-line" | "arrow-down-left" | "arrow-down-narrow-wide" | "arrow-down-right" | "arrow-down-to-dot" | "arrow-down-to-line" | "arrow-down-up" | "arrow-down-wide-narrow" | "arrow-down-z-a" | "arrow-down" | "arrow-left-from-line" | "arrow-left-right" | "arrow-left-to-line" | "arrow-left" | "arrow-right-from-line" | "arrow-right-left" | "arrow-right-to-line" | "arrow-right" | "arrow-up-0-1" | "arrow-up-1-0" | "arrow-up-a-z" | "arrow-up-down" | "arrow-up-from-dot" | "arrow-up-from-line" | "arrow-up-left" | "arrow-up-narrow-wide" | "arrow-up-right" | "arrow-up-to-line" | "arrow-up-wide-narrow" | "arrow-up-z-a" | "arrow-up" | "arrows-up-from-line" | "asterisk" | "astroid" | "at-sign" | "atom" | "audio-lines" | "audio-waveform" | "award" | "axe" | "axis-3d" | "baby" | "backpack" | "badge-alert" | "badge-cent" | "badge-check" | "badge-dollar-sign" | "badge-euro" | "badge-indian-rupee" | "badge-info" | "badge-japanese-yen" | "badge-minus" | "badge-percent" | "badge-plus" | "badge-pound-sterling" | "badge-question-mark" | "badge-russian-ruble" | "badge-swiss-franc" | "badge-turkish-lira" | "badge-x" | "badge" | "baggage-claim" | "balloon" | "ban" | "banana" | "bandage" | "banknote-arrow-down" | "banknote-arrow-up" | "banknote-x" | "banknote" | "barcode" | "barrel" | "baseline" | "bath" | "battery-charging" | "battery-full" | "battery-low" | "battery-medium" | "battery-plus" | "battery-warning" | "battery" | "beaker" | "bean-off" | "bean" | "bed-double" | "bed-single" | "bed" | "beef-off" | "beef" | "beer-off" | "beer" | "bell-check" | "bell-dot" | "bell-electric" | "bell-minus" | "bell-off" | "bell-plus" | "bell-ring" | "bell" | "between-horizontal-end" | "between-horizontal-start" | "between-vertical-end" | "between-vertical-start" | "blocks" | "bluetooth" | "bold" | "book-open-check" | "book-open-text" | "book-open" | "book" | "bookmark" | "bot" | "box" | "boxes" | "braces" | "brain" | "briefcase-business" | "briefcase" | "bug" | "building-2" | "building" | "calculator" | "calendar-clock" | "calendar-days" | "calendar-minus" | "calendar-plus" | "calendar" | "camera" | "captions" | "car" | "chart-area" | "chart-bar" | "chart-column-big" | "chart-line" | "chart-pie" | "check-check" | "check" | "chevron-down" | "chevron-left" | "chevron-right" | "chevron-up" | "chevrons-left" | "chevrons-right" | "chevrons-up-down" | "circle-alert" | "circle-arrow-down" | "circle-arrow-left" | "circle-arrow-right" | "circle-arrow-up" | "circle-check-big" | "circle-check" | "circle-dashed" | "circle-dot-dashed" | "circle-dot" | "circle-fading-arrow-up" | "circle-fading-plus" | "circle-off" | "circle-plus" | "circle-question-mark" | "circle-stop" | "circle-user-round" | "circle-user" | "circle-x" | "circle" | "clipboard-list" | "clipboard-paste" | "clipboard" | "clock-1" | "clock-10" | "clock-11" | "clock-12" | "clock-2" | "clock-3" | "clock-4" | "clock-5" | "clock-6" | "clock-7" | "clock-8" | "clock-9" | "clock" | "cloud-upload" | "cloud" | "code-xml" | "code" | "coffee" | "cog" | "coins" | "columns-2" | "columns-3" | "columns-4" | "command" | "compass" | "container" | "copy" | "corner-down-left" | "corner-down-right" | "corner-left-down" | "corner-left-up" | "corner-right-down" | "corner-right-up" | "corner-up-left" | "corner-up-right" | "cpu" | "credit-card" | "crown" | "database" | "dollar-sign" | "dot" | "download" | "ellipsis-vertical" | "ellipsis" | "eraser" | "external-link" | "eye-off" | "eye" | "face-angry" | "face-expressionless" | "face-slightly-smiling" | "file-archive" | "file-chart-column-increasing" | "file-chart-column" | "file-code" | "file-down" | "file-exclamation-point" | "file-image" | "file-minus-corner" | "file-minus" | "file-plus-corner" | "file-plus" | "file-search-corner" | "file-search" | "file-text" | "file-up" | "file" | "film" | "flag-triangle-left" | "flag-triangle-right" | "flag" | "flame" | "flask-conical" | "folder-code" | "folder-open" | "folder-plus" | "folder-search-2" | "folder-search" | "folder" | "footprints" | "forward" | "frame" | "fullscreen" | "funnel" | "gallery-horizontal-end" | "gallery-horizontal" | "gallery-thumbnails" | "gallery-vertical-end" | "gallery-vertical" | "gauge" | "gift" | "git-branch-minus" | "git-branch-plus" | "git-branch" | "git-commit-horizontal" | "git-commit-vertical" | "globe" | "grip-horizontal" | "grip-vertical" | "hard-drive" | "hash" | "heading-1" | "heading-2" | "heading-3" | "heading-4" | "heading-5" | "heading-6" | "heading" | "heart" | "hexagon" | "house" | "image" | "inbox" | "info" | "italic" | "key-round" | "key" | "keyboard" | "languages" | "laptop" | "layers" | "layout-dashboard" | "layout-grid" | "library" | "life-buoy" | "lightbulb" | "link-2" | "link" | "list-checks" | "list-filter" | "list-ordered" | "list-todo" | "list" | "loader-circle" | "loader" | "lock-keyhole" | "lock" | "log-in" | "log-out" | "mail-check" | "mail" | "map-pin" | "map" | "maximize-2" | "maximize" | "menu" | "message-circle-dashed" | "message-circle-question-mark" | "message-circle" | "message-square" | "mic" | "minimize-2" | "minimize" | "minus" | "monitor" | "moon" | "mouse-pointer-2" | "mouse-pointer-click" | "mouse-pointer" | "move-right" | "music" | "network" | "octagon-x" | "package" | "paintbrush" | "palette" | "panel-left-close" | "panel-left-open" | "panel-left" | "panel-right-close" | "panel-right-open" | "panel-right" | "panels-top-left" | "paperclip" | "pause" | "pencil-line" | "pencil" | "phone" | "pin" | "play" | "plug" | "plus" | "popcorn" | "power" | "presentation" | "puzzle" | "qr-code" | "quote" | "radar" | "radio" | "redo" | "refresh-ccw" | "refresh-cw" | "repeat" | "rocket" | "rotate-ccw-clock" | "rotate-ccw" | "rotate-cw" | "save" | "scale" | "scissors" | "scroll-text" | "scroll" | "search" | "send" | "server" | "settings-2" | "settings" | "share-2" | "share" | "shield-alert" | "shield-check" | "shield" | "shopping-bag" | "shopping-cart" | "shuffle" | "skip-forward" | "slash" | "sliders-horizontal" | "smartphone" | "sparkles" | "square-centerline-dashed-horizontal" | "square-centerline-dashed-vertical" | "square-dashed" | "square-pen" | "square-terminal" | "square" | "star-off" | "star" | "strikethrough" | "sun" | "table" | "tablet" | "tag" | "target" | "telescope" | "terminal" | "text-align-center" | "text-align-end" | "text-align-start" | "thermometer" | "thumbs-down" | "thumbs-up" | "timer-off" | "timer" | "trash-2" | "trash" | "trending-down" | "trending-up" | "triangle-alert" | "trophy" | "tv" | "type" | "underline" | "undo-2" | "undo" | "upload" | "user-check" | "user-plus" | "user-round-x" | "user-round" | "user" | "users" | "video" | "volume-1" | "volume-2" | "volume-off" | "volume-x" | "volume" | "wallet" | "waves-horizontal" | "wifi-off" | "wifi" | "workflow" | "wrench" | "x" | "zap" | "zoom-in" | "zoom-out";
|