First commit.

Signed-off-by: Chen Xiao <abigwc@gmail.com>
This commit is contained in:
Chen Xiao
2026-05-08 14:43:16 +08:00
commit 0b64e2de94
10989 changed files with 2253791 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
import { IconifyInfo } from "@iconify/types";
/**
* Item provided by API or loaded from collections.json, slightly different from IconifyInfo
*/
interface LegacyIconifyInfo {
name: string;
total?: number;
version?: string;
author?: string;
url?: string;
license?: string;
licenseURL?: string;
licenseSPDX?: string;
samples?: string[];
height?: number | number[];
displayHeight?: number;
samplesHeight?: number;
category?: string;
palette?: 'Colorless' | 'Colorful';
hidden?: boolean;
}
/**
* Convert data to valid CollectionInfo
*/
declare function convertIconSetInfo(data: unknown, expectedPrefix?: string): IconifyInfo | null;
export { LegacyIconifyInfo, convertIconSetInfo };
+125
View File
@@ -0,0 +1,125 @@
const minDisplayHeight = 16;
const maxDisplayHeight = 24;
/**
* Check if displayHeight value is valid, returns 0 on failure
*/
function validateDisplayHeight(value) {
while (value < minDisplayHeight) value *= 2;
while (value > maxDisplayHeight) value /= 2;
return value === Math.round(value) && value >= minDisplayHeight && value <= maxDisplayHeight ? value : 0;
}
/**
* Convert data to valid CollectionInfo
*/
function convertIconSetInfo(data, expectedPrefix = "") {
if (typeof data !== "object" || data === null) return null;
const source = data;
const getSourceNestedString = (field, key, defaultValue = "") => {
if (typeof source[field] !== "object") return defaultValue;
const obj = source[field];
return typeof obj[key] === "string" ? obj[key] : defaultValue;
};
let name;
if (typeof source.name === "string") name = source.name;
else if (typeof source.title === "string") name = source.title;
else return null;
if (expectedPrefix !== "" && typeof source.prefix === "string" && source.prefix !== expectedPrefix) return null;
const info = { name };
switch (typeof source.total) {
case "number":
info.total = source.total;
break;
case "string": {
const num = parseInt(source.total);
if (num > 0) info.total = num;
break;
}
}
if (typeof source.version === "string") info.version = source.version;
info.author = { name: getSourceNestedString("author", "name", typeof source.author === "string" ? source.author : "") };
if (typeof source.author === "object") {
const sourceAuthor = source.author;
if (typeof sourceAuthor.url === "string") info.author.url = sourceAuthor.url;
}
info.license = { title: getSourceNestedString("license", "title", typeof source.license === "string" ? source.license : "") };
if (typeof source.license === "object") {
const sourceLicense = source.license;
if (typeof sourceLicense.spdx === "string") info.license.spdx = sourceLicense.spdx;
if (typeof sourceLicense.url === "string") info.license.url = sourceLicense.url;
}
if (source.samples instanceof Array) {
const samples = [];
source.samples.forEach((item) => {
if (typeof item === "string" && !samples.includes(item)) samples.push(item);
});
if (samples.length) info.samples = samples;
}
if (typeof source.height === "number" || typeof source.height === "string") {
const num = parseInt(source.height);
if (num > 0) info.height = num;
}
if (source.height instanceof Array) {
source.height.forEach((item) => {
const num = parseInt(item);
if (num > 0) {
if (!(info.height instanceof Array)) info.height = [];
info.height.push(num);
}
});
switch (info.height.length) {
case 0:
delete info.height;
break;
case 1: info.height = info.height[0];
}
}
if (typeof info.height === "number") {
const displayHeight = validateDisplayHeight(info.height);
if (displayHeight && displayHeight !== info.height) info.displayHeight = displayHeight;
}
["samplesHeight", "displayHeight"].forEach((prop) => {
const value = source[prop];
if (typeof value === "number" || typeof value === "string") {
const displayHeight = validateDisplayHeight(parseInt(value));
if (displayHeight) info.displayHeight = displayHeight;
}
});
if (typeof source.category === "string") info.category = source.category;
if (source.tags instanceof Array) info.tags = source.tags;
switch (typeof source.palette) {
case "boolean":
info.palette = source.palette;
break;
case "string":
switch (source.palette.toLowerCase()) {
case "colorless":
case "false":
info.palette = false;
break;
case "colorful":
case "true": info.palette = true;
}
break;
}
if (source.hidden === true) info.hidden = true;
Object.keys(source).forEach((key) => {
const value = source[key];
if (typeof value !== "string") return;
switch (key) {
case "url":
case "uri":
info.author.url = value;
break;
case "licenseURL":
case "licenseURI":
info.license.url = value;
break;
case "licenseID":
case "licenseSPDX":
info.license.spdx = value;
break;
}
});
return info;
}
export { convertIconSetInfo };
+8
View File
@@ -0,0 +1,8 @@
import { IconifyJSON } from "@iconify/types";
/**
* Expand minified icon set
*
* Opposite of minifyIconSet() from ./minify.ts
*/
declare function expandIconSet(data: IconifyJSON): void;
export { expandIconSet };
+19
View File
@@ -0,0 +1,19 @@
import { defaultIconDimensions } from "../icon/defaults.js";
/**
* Expand minified icon set
*
* Opposite of minifyIconSet() from ./minify.ts
*/
function expandIconSet(data) {
const icons = Object.keys(data.icons);
Object.keys(defaultIconDimensions).forEach((prop) => {
if (typeof data[prop] !== typeof defaultIconDimensions[prop]) return;
const value = data[prop];
icons.forEach((name) => {
const item = data.icons[name];
if (!(prop in item)) item[prop] = value;
});
delete data[prop];
});
}
export { expandIconSet };
+10
View File
@@ -0,0 +1,10 @@
import { ExtendedIconifyIcon, IconifyJSON } from "@iconify/types";
/**
* Get icon data, using prepared aliases tree
*/
declare function internalGetIconData(data: IconifyJSON, name: string, tree: string[]): ExtendedIconifyIcon;
/**
* Get data for icon
*/
declare function getIconData(data: IconifyJSON, name: string): ExtendedIconifyIcon | null;
export { getIconData, internalGetIconData };
+25
View File
@@ -0,0 +1,25 @@
import { mergeIconData } from "../icon/merge.js";
import { getIconsTree } from "./tree.js";
/**
* Get icon data, using prepared aliases tree
*/
function internalGetIconData(data, name, tree) {
const icons = data.icons;
const aliases = data.aliases || Object.create(null);
let currentProps = {};
function parse(name) {
currentProps = mergeIconData(icons[name] || aliases[name], currentProps);
}
parse(name);
tree.forEach(parse);
return mergeIconData(data, currentProps);
}
/**
* Get data for icon
*/
function getIconData(data, name) {
if (data.icons[name]) return internalGetIconData(data, name, []);
const tree = getIconsTree(data, [name])[name];
return tree ? internalGetIconData(data, name, tree) : null;
}
export { getIconData, internalGetIconData };
+10
View File
@@ -0,0 +1,10 @@
import { IconifyJSON } from "@iconify/types";
/**
* Optional properties that must be copied when copying icon set
*/
declare const propsToCopy: (keyof IconifyJSON)[];
/**
* Extract icons from icon set
*/
declare function getIcons(data: IconifyJSON, names: string[], not_found?: boolean): IconifyJSON | null;
export { getIcons, propsToCopy };
+36
View File
@@ -0,0 +1,36 @@
import { defaultIconDimensions } from "../icon/defaults.js";
import { getIconsTree } from "./tree.js";
/**
* Optional properties that must be copied when copying icon set
*/
const propsToCopy = Object.keys(defaultIconDimensions).concat(["provider"]);
/**
* Extract icons from icon set
*/
function getIcons(data, names, not_found) {
const icons = Object.create(null);
const aliases = Object.create(null);
const result = {
prefix: data.prefix,
icons
};
const sourceIcons = data.icons;
const sourceAliases = data.aliases || Object.create(null);
if (data.lastModified) result.lastModified = data.lastModified;
const tree = getIconsTree(data, names);
let empty = true;
for (const name in tree) if (!tree[name]) {
if (not_found && names.indexOf(name) !== -1) (result.not_found || (result.not_found = [])).push(name);
} else if (sourceIcons[name]) {
icons[name] = { ...sourceIcons[name] };
empty = false;
} else {
aliases[name] = { ...sourceAliases[name] };
result.aliases = aliases;
}
propsToCopy.forEach((attr) => {
if (attr in data) result[attr] = data[attr];
});
return empty && not_found !== true ? null : result;
}
export { getIcons, propsToCopy };
+43
View File
@@ -0,0 +1,43 @@
import { IconifyJSON } from "@iconify/types";
/**
* Minify icon set
*
* Function finds common values for few numeric properties, such as 'width' and 'height' (see defaultIconDimensions keys for list of properties),
* removes entries from icons and sets default entry in root of icon set object.
*
* For example, this:
* {
* icons: {
* foo: {
* body: '<g />',
* width: 24
* },
* bar: {
* body: '<g />',
* width: 24
* },
* baz: {
* body: '<g />',
* width: 16
* }
* }
* }
* is changed to this:
* {
* icons: {
* foo: {
* body: '<g />'
* },
* bar: {
* body: '<g />'
* },
* baz: {
* body: '<g />',
* width: 16
* }
* },
* width: 24
* }
*/
declare function minifyIconSet(data: IconifyJSON): void;
export { minifyIconSet };
+91
View File
@@ -0,0 +1,91 @@
import { defaultIconDimensions } from "../icon/defaults.js";
/**
* Minify icon set
*
* Function finds common values for few numeric properties, such as 'width' and 'height' (see defaultIconDimensions keys for list of properties),
* removes entries from icons and sets default entry in root of icon set object.
*
* For example, this:
* {
* icons: {
* foo: {
* body: '<g />',
* width: 24
* },
* bar: {
* body: '<g />',
* width: 24
* },
* baz: {
* body: '<g />',
* width: 16
* }
* }
* }
* is changed to this:
* {
* icons: {
* foo: {
* body: '<g />'
* },
* bar: {
* body: '<g />'
* },
* baz: {
* body: '<g />',
* width: 16
* }
* },
* width: 24
* }
*/
function minifyIconSet(data) {
const icons = Object.keys(data.icons);
Object.keys(defaultIconDimensions).forEach((prop) => {
if (data[prop] === defaultIconDimensions[prop]) delete data[prop];
const defaultValue = defaultIconDimensions[prop];
const propType = typeof defaultValue;
const hasMinifiedDefault = typeof data[prop] === propType && data[prop] !== defaultValue;
let maxCount = 0;
let maxValue = null;
const counters = /* @__PURE__ */ new Map();
for (let i = 0; i < icons.length; i++) {
const item = data.icons[icons[i]];
let value;
if (typeof item[prop] === propType) value = item[prop];
else if (hasMinifiedDefault) value = data[prop];
else value = defaultIconDimensions[prop];
if (i === 0) {
maxCount = 1;
maxValue = value;
counters.set(value, 1);
continue;
}
if (!counters.has(value)) {
counters.set(value, 1);
continue;
}
const count = counters.get(value) + 1;
counters.set(value, count);
if (count > maxCount) {
maxCount = count;
maxValue = value;
}
}
const canMinify = maxValue !== null && maxCount > 1;
const oldDefault = hasMinifiedDefault ? data[prop] : null;
const newDefault = canMinify ? maxValue : oldDefault;
if (newDefault === defaultValue) delete data[prop];
else if (canMinify) data[prop] = newDefault;
icons.forEach((key) => {
const item = data.icons[key];
const value = prop in item ? item[prop] : hasMinifiedDefault ? oldDefault : defaultValue;
if (value === newDefault || newDefault === null && value === defaultValue) {
delete item[prop];
return;
}
if (canMinify && !(prop in item)) item[prop] = value;
});
});
}
export { minifyIconSet };
+19
View File
@@ -0,0 +1,19 @@
import { ExtendedIconifyIcon, IconifyJSON } from "@iconify/types";
/**
* Callback to call for each icon.
*
* If data === null, icon is missing.
*/
type SplitIconSetCallback = (name: string, data: ExtendedIconifyIcon | null) => unknown;
type SplitIconSetAsyncCallback = (name: string, data: ExtendedIconifyIcon | null) => Promise<unknown>;
/**
* Extract icons from an icon set
*
* Returns list of icons that were found in icon set
*/
declare function parseIconSet(data: IconifyJSON, callback: SplitIconSetCallback): string[];
/**
* Async version of parseIconSet()
*/
declare function parseIconSetAsync(data: IconifyJSON, callback: SplitIconSetAsyncCallback): Promise<string[]>;
export { SplitIconSetAsyncCallback, SplitIconSetCallback, parseIconSet, parseIconSetAsync };
+46
View File
@@ -0,0 +1,46 @@
import { getIconsTree } from "./tree.js";
import { internalGetIconData } from "./get-icon.js";
/**
* Extract icons from an icon set
*
* Returns list of icons that were found in icon set
*/
function parseIconSet(data, callback) {
const names = [];
if (typeof data !== "object" || typeof data.icons !== "object") return names;
if (data.not_found instanceof Array) data.not_found.forEach((name) => {
callback(name, null);
names.push(name);
});
const tree = getIconsTree(data);
for (const name in tree) {
const item = tree[name];
if (item) {
callback(name, internalGetIconData(data, name, item));
names.push(name);
}
}
return names;
}
/**
* Async version of parseIconSet()
*/
async function parseIconSetAsync(data, callback) {
const names = [];
if (typeof data !== "object" || typeof data.icons !== "object") return names;
if (data.not_found instanceof Array) for (let i = 0; i < data.not_found.length; i++) {
const name = data.not_found[i];
await callback(name, null);
names.push(name);
}
const tree = getIconsTree(data);
for (const name in tree) {
const item = tree[name];
if (item) {
await callback(name, internalGetIconData(data, name, item));
names.push(name);
}
}
return names;
}
export { parseIconSet, parseIconSetAsync };
+12
View File
@@ -0,0 +1,12 @@
import { IconifyJSON } from "@iconify/types";
/** Parent icons, first is direct parent, then parent of parent and so on. Does not include self */
type ParentIconsList = string[];
/** Result. Key is icon, value is list of parent icons */
type ParentIconsTree = Record<string, ParentIconsList | null>;
/**
* Resolve icon set icons
*
* Returns parent icon for each icon
*/
declare function getIconsTree(data: IconifyJSON, names?: string[]): ParentIconsTree;
export { ParentIconsList, ParentIconsTree, getIconsTree };
+23
View File
@@ -0,0 +1,23 @@
/**
* Resolve icon set icons
*
* Returns parent icon for each icon
*/
function getIconsTree(data, names) {
const icons = data.icons;
const aliases = data.aliases || Object.create(null);
const resolved = Object.create(null);
function resolve(name) {
if (icons[name]) return resolved[name] = [];
if (!(name in resolved)) {
resolved[name] = null;
const parent = aliases[name] && aliases[name].parent;
const value = parent && resolve(parent);
if (value) resolved[name] = [parent].concat(value);
}
return resolved[name];
}
(names || Object.keys(icons).concat(Object.keys(aliases))).forEach(resolve);
return resolved;
}
export { getIconsTree };
+9
View File
@@ -0,0 +1,9 @@
import { IconifyJSON } from "@iconify/types";
/**
* Validate icon set, return it as IconifyJSON on success, null on failure
*
* Unlike validateIconSet(), this function is very basic.
* It does not throw exceptions, it does not check metadata, it does not fix stuff.
*/
declare function quicklyValidateIconSet(obj: unknown): IconifyJSON | null;
export { quicklyValidateIconSet };
+42
View File
@@ -0,0 +1,42 @@
import { defaultExtendedIconProps, defaultIconDimensions } from "../icon/defaults.js";
/**
* Optional properties
*/
const optionalPropertyDefaults = {
provider: "",
aliases: {},
not_found: {},
...defaultIconDimensions
};
/**
* Check props
*/
function checkOptionalProps(item, defaults) {
for (const prop in defaults) if (prop in item && typeof item[prop] !== typeof defaults[prop]) return false;
return true;
}
/**
* Validate icon set, return it as IconifyJSON on success, null on failure
*
* Unlike validateIconSet(), this function is very basic.
* It does not throw exceptions, it does not check metadata, it does not fix stuff.
*/
function quicklyValidateIconSet(obj) {
if (typeof obj !== "object" || obj === null) return null;
const data = obj;
if (typeof data.prefix !== "string" || !obj.icons || typeof obj.icons !== "object") return null;
if (!checkOptionalProps(obj, optionalPropertyDefaults)) return null;
const icons = data.icons;
for (const name in icons) {
const icon = icons[name];
if (!name || typeof icon.body !== "string" || !checkOptionalProps(icon, defaultExtendedIconProps)) return null;
}
const aliases = data.aliases || Object.create(null);
for (const name in aliases) {
const icon = aliases[name];
const parent = icon.parent;
if (!name || typeof parent !== "string" || !icons[parent] && !aliases[parent] || !checkOptionalProps(icon, defaultExtendedIconProps)) return null;
}
return data;
}
export { quicklyValidateIconSet };
+18
View File
@@ -0,0 +1,18 @@
import { IconifyJSON } from "@iconify/types";
/**
* Match character
*/
declare const matchChar: RegExp;
interface IconSetValidationOptions {
/** Whether validation function will attempt to fix icon set instead of throwing errors. */
fix?: boolean;
/** Values for provider and prefix. If missing, validation should add them. */
prefix?: string;
provider?: string;
}
/**
* Validate icon set
* @returns param obj as IconifyJSON type on success, throw error on failure
*/
declare function validateIconSet(obj: unknown, options?: IconSetValidationOptions): IconifyJSON;
export { IconSetValidationOptions, matchChar, validateIconSet };
+123
View File
@@ -0,0 +1,123 @@
import { defaultExtendedIconProps } from "../icon/defaults.js";
import { getIconsTree } from "./tree.js";
/**
* Match character
*/
const matchChar = /^[a-f0-9]+(-[a-f0-9]+)*$/;
/**
* Validate icon
*
* Returns name of property that failed validation or null on success
*/
function validateIconProps(item, fix, checkOtherProps) {
for (const key in item) {
const attr = key;
const type = typeof item[attr];
if (type === "undefined") {
delete item[attr];
continue;
}
const expectedType = typeof defaultExtendedIconProps[attr];
if (expectedType !== "undefined") {
if (type !== expectedType) {
if (fix) {
delete item[attr];
continue;
}
return attr;
}
continue;
}
if (checkOtherProps && type === "object") if (fix) delete item[attr];
else return key;
}
return null;
}
/**
* Validate icon set
* @returns param obj as IconifyJSON type on success, throw error on failure
*/
function validateIconSet(obj, options) {
const fix = !!(options && options.fix);
if (typeof obj !== "object" || obj === null || typeof obj.icons !== "object" || !obj.icons) throw new Error("Bad icon set");
const data = obj;
if (options && typeof options.prefix === "string") data.prefix = options.prefix;
else if (typeof data.prefix !== "string" || !data.prefix) throw new Error("Invalid prefix");
if (options && typeof options.provider === "string") data.provider = options.provider;
else if (data.provider !== void 0) {
if (typeof data.provider !== "string") if (fix) delete data.provider;
else throw new Error("Invalid provider");
}
if (data.aliases !== void 0) {
if (typeof data.aliases !== "object" || data.aliases === null) if (fix) delete data.aliases;
else throw new Error("Invalid aliases list");
}
const tree = getIconsTree(data);
const icons = data.icons;
const aliases = data.aliases || Object.create(null);
for (const name in tree) {
const treeItem = tree[name];
const isAlias = !icons[name];
const parentObj = isAlias ? aliases : icons;
if (!treeItem) {
if (fix) {
delete parentObj[name];
continue;
}
throw new Error(`Invalid alias: ${name}`);
}
if (!name) {
if (fix) {
delete parentObj[name];
continue;
}
throw new Error(`Invalid icon name: "${name}"`);
}
const item = parentObj[name];
if (!isAlias) {
if (typeof item.body !== "string") {
if (fix) {
delete parentObj[name];
continue;
}
throw new Error(`Invalid icon: "${name}"`);
}
}
const requiredProp = isAlias ? "parent" : "body";
const key = typeof item[requiredProp] !== "string" ? requiredProp : validateIconProps(item, fix, true);
if (key !== null) throw new Error(`Invalid property "${key}" in "${name}"`);
}
if (data.not_found !== void 0 && !(data.not_found instanceof Array)) if (fix) delete data.not_found;
else throw new Error("Invalid not_found list");
if (!Object.keys(data.icons).length && !(data.not_found && data.not_found.length)) throw new Error("Icon set is empty");
if (fix && !Object.keys(aliases).length) delete data.aliases;
const failedOptionalProp = validateIconProps(data, false, false);
if (failedOptionalProp) throw new Error(`Invalid value type for "${failedOptionalProp}"`);
if (data.chars !== void 0) {
if (typeof data.chars !== "object" || data.chars === null) if (fix) delete data.chars;
else throw new Error("Invalid characters map");
}
if (typeof data.chars === "object") {
const chars = data.chars;
Object.keys(chars).forEach((char) => {
if (!matchChar.exec(char) || typeof chars[char] !== "string") {
if (fix) {
delete chars[char];
return;
}
throw new Error(`Invalid character "${char}"`);
}
const target = chars[char];
if (!data.icons[target] && (!data.aliases || !data.aliases[target])) {
if (fix) {
delete chars[char];
return;
}
throw new Error(`Character "${char}" points to missing icon "${target}"`);
}
});
if (fix && !Object.keys(data.chars).length) delete data.chars;
}
return data;
}
export { matchChar, validateIconSet };