diff --git a/README.md b/README.md index 8f5d8ad..767dc66 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Other packages: - [Iconify types](./packages/types/) - TypeScript types used by various implementations. - [Iconify core](./packages/core/) - common files used by various implementations. - [React demo](./packages/react-demo/) - demo for React component. Run `npm start` to start demo. +- [React with API demo](./packages/react-demo-with-api/) - demo for React component that loads icons from Iconify API. Run `npm start` to start demo. - [Vue demo](./packages/vue-demo/) - demo for Vue component. Run `npm serve` to start demo. - [Svelte demo](./packages/svelte-demo/) - demo for Svelte component. Run `npm run dev` to start demo. - [Sapper demo](./packages/sapper-demo/) - demo for Sapper, using Svelte component on the server and in the browser. Run `npm run dev` to start the demo. @@ -93,13 +94,13 @@ If links stop working for some reason, run `npm run link` to fix links. If you want to re-install dependencies, run `npm run clean` to clear all repositories (press "Y" to continue), then `npm run bootstrap` to install everything again. -## License +## Licence -Iconify is dual-licensed under Apache 2.0 and GPL 2.0 license. You may select, at your option, one of the above-listed licenses. +Iconify is dual-licensed under Apache 2.0 and GPL 2.0 licence. You may select, at your option, one of the above-listed licences. `SPDX-License-Identifier: Apache-2.0 OR GPL-2.0` -This license does not apply to icons. Icons are released under different licenses, see each icon set for details. -Icons available by default are all licensed under some kind of open-source or free license. +This licence does not apply to icons. Icons are released under different licences, see each icon set for details. +Icons available by default are all licensed under some kind of open-source or free licence. © 2020 Iconify OÜ diff --git a/packages/iconify/api-extractor.without-api.json b/packages/iconify/api-extractor.without-api.json new file mode 100644 index 0000000..66d1484 --- /dev/null +++ b/packages/iconify/api-extractor.without-api.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "mainEntryPointFilePath": "lib/iconify.without-api.d.ts", + "bundledPackages": [ + "@iconify/types", + "@iconify/core", + "@cyberalien/redundancy" + ], + "compiler": {}, + "apiReport": { + "enabled": false + }, + "docModel": { + "enabled": false + }, + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "/dist/iconify.without-api.d.ts" + }, + "tsdocMetadata": { + "enabled": false + }, + "messages": { + "compilerMessageReporting": { + "default": { + "logLevel": "warning" + } + }, + "extractorMessageReporting": { + "default": { + "logLevel": "warning" + }, + "ae-missing-release-tag": { + "logLevel": "none" + } + }, + "tsdocMessageReporting": { + "default": { + "logLevel": "warning" + } + } + } +} diff --git a/packages/iconify/build.js b/packages/iconify/build.js index d9060b4..57d273b 100644 --- a/packages/iconify/build.js +++ b/packages/iconify/build.js @@ -77,6 +77,11 @@ if (compile.core) { }); } +// Add api2 +if (compile.api) { + compile.api2 = true; +} + // Compile other packages Object.keys(compile).forEach((key) => { if (key !== 'core' && compile[key]) { diff --git a/packages/iconify/package.json b/packages/iconify/package.json index 7876788..19ae682 100644 --- a/packages/iconify/package.json +++ b/packages/iconify/package.json @@ -18,7 +18,8 @@ "build": "node build", "build:lib": "tsc -b", "build:dist": "rollup -c rollup.config.js", - "build:api": "api-extractor run --local --verbose" + "build:api": "api-extractor run --local --verbose", + "build:api2": "api-extractor run --local --verbose --config api-extractor.without-api.json" }, "devDependencies": { "@cyberalien/redundancy": "^1.0.0", diff --git a/packages/iconify/rollup.config.js b/packages/iconify/rollup.config.js index be27942..3e78ecd 100644 --- a/packages/iconify/rollup.config.js +++ b/packages/iconify/rollup.config.js @@ -5,7 +5,7 @@ import buble from '@rollup/plugin-buble'; import { terser } from 'rollup-plugin-terser'; import replace from '@rollup/plugin-replace'; -const name = 'iconify'; +const names = ['iconify', 'iconify.without-api']; const global = 'Iconify'; // Wrapper to export module as global and as ES module @@ -71,30 +71,32 @@ if (readme !== oldReadme) { // Export configuration const config = []; -[false, true].forEach((compress) => { - const item = { - input: `lib/${name}.js`, - output: [ - { - file: `dist/${name}${compress ? '.min' : ''}.js`, - format: 'iife', - name: global, - banner: header, - footer, - }, - ], - plugins: [ - resolve({ - browser: true, - }), - commonjs(), - replace(replacements), - buble(), - ], - }; - if (compress) { - item.plugins.push(terser()); - } - config.push(item); +names.forEach((name) => { + [false, true].forEach((compress) => { + const item = { + input: `lib/${name}.js`, + output: [ + { + file: `dist/${name}${compress ? '.min' : ''}.js`, + format: 'iife', + name: global, + banner: header, + footer, + }, + ], + plugins: [ + resolve({ + browser: true, + }), + commonjs(), + replace(replacements), + buble(), + ], + }; + if (compress) { + item.plugins.push(terser()); + } + config.push(item); + }); }); export default config; diff --git a/packages/iconify/src/api/index.ts b/packages/iconify/src/api/index.ts index 79e186c..18a37f5 100644 --- a/packages/iconify/src/api/index.ts +++ b/packages/iconify/src/api/index.ts @@ -11,22 +11,6 @@ import { IconifyAPIConfig } from '@iconify/core/lib/api/config'; * Iconify interface */ export interface IconifyAPI { - /* Getting icons */ - /** - * Check if icon exists - */ - iconExists: (name: string) => boolean; - - /** - * Get icon data with all properties - */ - getIcon: (name: string) => IconifyIcon | null; - - /** - * List all available icons - */ - listIcons: (provider?: string, prefix?: string) => string[]; - /** * Load icons */ @@ -35,17 +19,6 @@ export interface IconifyAPI { callback?: IconifyIconLoaderCallback ) => IconifyIconLoaderAbort; - /* Add icons */ - /** - * Add icon to storage - */ - addIcon: (name: string, data: IconifyIcon) => boolean; - - /** - * Add icon set to storage - */ - addCollection: (data: IconifyJSON, provider?: string) => boolean; - /* API stuff */ /** * Add API provider diff --git a/packages/iconify/src/iconify.ts b/packages/iconify/src/iconify.ts index d0ef0d5..4404578 100644 --- a/packages/iconify/src/iconify.ts +++ b/packages/iconify/src/iconify.ts @@ -159,6 +159,33 @@ export interface IconifyGlobal */ getVersion: () => string; + /* Getting icons */ + /** + * Check if icon exists + */ + iconExists: (name: string) => boolean; + + /** + * Get icon data with all properties + */ + getIcon: (name: string) => IconifyIcon | null; + + /** + * List all available icons + */ + listIcons: (provider?: string, prefix?: string) => string[]; + + /* Add icons */ + /** + * Add icon to storage + */ + addIcon: (name: string, data: IconifyIcon) => boolean; + + /** + * Add icon set to storage + */ + addCollection: (data: IconifyJSON, provider?: string) => boolean; + /* Scan DOM */ /** * Toggle local and session storage diff --git a/packages/iconify/src/iconify.without-api.ts b/packages/iconify/src/iconify.without-api.ts new file mode 100644 index 0000000..9a9569d --- /dev/null +++ b/packages/iconify/src/iconify.without-api.ts @@ -0,0 +1,390 @@ +// Core +import { IconifyJSON } from '@iconify/types'; +import { merge } from '@iconify/core/lib/misc/merge'; +import { + stringToIcon, + validateIcon, + IconifyIconName, +} from '@iconify/core/lib/icon/name'; +import { IconifyIcon, FullIconifyIcon } from '@iconify/core/lib/icon'; +import { + IconifyIconCustomisations, + fullCustomisations, + IconifyIconSize, + IconifyHorizontalIconAlignment, + IconifyVerticalIconAlignment, +} from '@iconify/core/lib/customisations'; +import { + getStorage, + getIcon, + addIcon, + addIconSet, + listStoredProviders, + listStoredPrefixes, +} from '@iconify/core/lib/storage'; +import { iconToSVG, IconifyIconBuildResult } from '@iconify/core/lib/builder'; +import { replaceIDs } from '@iconify/core/lib/builder/ids'; +import { calcSize } from '@iconify/core/lib/builder/calc-size'; + +// Modules +import { coreModules } from '@iconify/core/lib/modules'; +import { browserModules } from './modules'; + +// Finders +import { addFinder } from './finder'; +import { finder as iconifyFinder } from './finders/iconify'; +// import { finder as iconifyIconFinder } from './finders/iconify-icon'; + +// Observer +import { IconifyObserver } from './observer'; +import { observer } from './observer/observer'; + +// Render +import { IconifyRenderer } from './renderer'; +import { renderIcon } from './renderer/render'; + +// Scan +import { IconifyScanner } from './scanner'; +import { scanDOM } from './scanner/scan'; + +/** + * Export required types + */ +// JSON stuff +export { IconifyIcon, IconifyJSON, IconifyIconName }; + +// Customisations +export { + IconifyIconCustomisations, + IconifyIconSize, + IconifyHorizontalIconAlignment, + IconifyVerticalIconAlignment, +}; + +// Build +export { IconifyIconBuildResult }; + +/** + * Exposed internal functions + * + * Used by plug-ins, such as Icon Finder + * + * Important: any changes published in a release must be backwards compatible. + */ +export interface IconifyExposedInternals { + /** + * Calculate width knowing height and width/height ratio (or vice versa) + */ + calculateSize: ( + size: IconifyIconSize, + ratio: number, + precision?: number + ) => IconifyIconSize; +} + +/** + * Iconify interface + */ +export interface IconifyGlobal + extends IconifyScanner, + IconifyObserver, + IconifyRenderer { + /* General section */ + /** + * Get version + */ + getVersion: () => string; + + /* Getting icons */ + /** + * Check if icon exists + */ + iconExists: (name: string) => boolean; + + /** + * Get icon data with all properties + */ + getIcon: (name: string) => IconifyIcon | null; + + /** + * List all available icons + */ + listIcons: (provider?: string, prefix?: string) => string[]; + + /* Add icons */ + /** + * Add icon to storage + */ + addIcon: (name: string, data: IconifyIcon) => boolean; + + /** + * Add icon set to storage + */ + addCollection: (data: IconifyJSON, provider?: string) => boolean; + + /* Scan DOM */ + /** + * Expose internal functions + */ + _internal: IconifyExposedInternals; +} + +// Export dependencies +export { IconifyObserver, IconifyScanner, IconifyRenderer }; + +/** + * Get icon name + */ +function getIconName(name: string): IconifyIconName | null { + const icon = stringToIcon(name); + if (!validateIcon(icon)) { + return null; + } + return icon; +} + +/** + * Get icon data + */ +function getIconData(name: string): FullIconifyIcon | null { + const icon = getIconName(name); + return icon + ? getIcon(getStorage(icon.provider, icon.prefix), icon.name) + : null; +} + +/** + * Get SVG data + */ +function buildIcon( + name: string, + customisations: IconifyIconCustomisations +): IconifyIconBuildResult | null { + // Get icon data + const iconData = getIconData(name); + if (!iconData) { + return null; + } + + // Clean up customisations + const changes = fullCustomisations(customisations); + + // Get data + return iconToSVG(iconData, changes); +} + +/** + * Generate icon + */ +function generateIcon( + name: string, + customisations: IconifyIconCustomisations, + returnString: boolean +): SVGElement | string | null { + // Get icon data + const iconData = getIconData(name); + if (!iconData) { + return null; + } + + // Split name + const iconName = stringToIcon(name); + + // Clean up customisations + const changes = fullCustomisations(customisations); + + // Get data + return (renderIcon( + { + name: iconName, + }, + changes, + iconData, + returnString + ) as unknown) as SVGElement | string | null; +} + +/** + * Add icon set + */ +function addCollection(data: IconifyJSON, provider?: string) { + if (typeof provider !== 'string') { + provider = typeof data.provider === 'string' ? data.provider : ''; + } + + if ( + typeof data !== 'object' || + typeof data.prefix !== 'string' || + !validateIcon({ + provider, + prefix: data.prefix, + name: 'a', + }) + ) { + return false; + } + + const storage = getStorage(provider, data.prefix); + return !!addIconSet(storage, data); +} + +/** + * Global variable + */ +const Iconify: IconifyGlobal = { + // Version + getVersion: () => '__iconify_version__', + + // Check if icon exists + iconExists: (name) => getIconData(name) !== null, + + // Get raw icon data + getIcon: (name) => { + const result = getIconData(name); + return result ? merge(result) : null; + }, + + // List icons + listIcons: (provider?: string, prefix?: string) => { + let icons = []; + + // Get providers + let providers: string[]; + if (typeof provider === 'string') { + providers = [provider]; + } else { + providers = listStoredProviders(); + } + + // Get all icons + providers.forEach((provider) => { + let prefixes: string[]; + + if (typeof prefix === 'string') { + prefixes = [prefix]; + } else { + prefixes = listStoredPrefixes(provider); + } + + prefixes.forEach((prefix) => { + const storage = getStorage(provider, prefix); + let icons = Object.keys(storage.icons).map( + (name) => + (provider !== '' ? '@' + provider + ':' : '') + + prefix + + ':' + + name + ); + icons = icons.concat(icons); + }); + }); + + return icons; + }, + + // Render SVG + renderSVG: (name: string, customisations: IconifyIconCustomisations) => { + return generateIcon(name, customisations, false) as SVGElement | null; + }, + + renderHTML: (name: string, customisations: IconifyIconCustomisations) => { + return generateIcon(name, customisations, true) as string | null; + }, + + // Get rendered icon as object that can be used to create SVG (use replaceIDs on body) + renderIcon: buildIcon, + + // Replace IDs in body + replaceIDs: replaceIDs, + + // Add icon + addIcon: (name, data) => { + const icon = getIconName(name); + if (!icon) { + return false; + } + const storage = getStorage(icon.provider, icon.prefix); + return addIcon(storage, icon.name, data); + }, + + // Add icon set + addCollection: addCollection, + + // Scan DOM + scanDOM: scanDOM, + + // Set root node + setRoot: (root: HTMLElement) => { + browserModules.root = root; + + // Restart observer + observer.init(scanDOM); + + // Scan DOM on next tick + setTimeout(scanDOM); + }, + + // Observer + pauseObserver: observer.pause, + resumeObserver: observer.resume, + + // Exposed internal functions + _internal: { + // Calculate size + calculateSize: calcSize, + }, +}; + +/** + * Initialise stuff + */ +if (typeof document !== 'undefined' && typeof window !== 'undefined') { + // Add finder modules + // addFinder(iconifyIconFinder); + addFinder(iconifyFinder); + + const _window = window; + + // Load icons from global "IconifyPreload" + interface WindowWithIconifyPreload { + IconifyPreload: IconifyJSON[] | IconifyJSON; + } + if ( + ((_window as unknown) as WindowWithIconifyPreload).IconifyPreload !== + void 0 + ) { + const preload = ((_window as unknown) as WindowWithIconifyPreload) + .IconifyPreload; + const err = 'Invalid IconifyPreload syntax.'; + if (typeof preload === 'object' && preload !== null) { + (preload instanceof Array ? preload : [preload]).forEach((item) => { + try { + if ( + // Check if item is an object and not null/array + typeof item !== 'object' || + item === null || + item instanceof Array || + // Check for 'icons' and 'prefix' + typeof item.icons !== 'object' || + typeof item.prefix !== 'string' || + // Add icon set + !addCollection(item) + ) { + console.error(err); + } + } catch (e) { + console.error(err); + } + }); + } + } + + // Load observer + browserModules.observer = observer; + setTimeout(() => { + // Init on next tick when entire document has been parsed + observer.init(scanDOM); + }); +} + +export default Iconify; diff --git a/packages/types/types.ts b/packages/types/types.ts index c31829c..4094bc0 100644 --- a/packages/types/types.ts +++ b/packages/types/types.ts @@ -128,7 +128,7 @@ export interface IconifyInfo { url?: string; }; - // Array of icons that should be used for samples in icons list. + // Array of icons that should be used for samples in icon sets list. samples: string[]; // Icon grid: number or array of numbers.