diff --git a/package.json b/package.json index ceec1f999..bc27dd54d 100755 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "scripts": { "build": "run-s clean build.* prettier", "build.files": "tsx scripts/build.ts", + "build.types": "node scripts/types.mjs", "build.component": "stencil build", "build.collection": "tsx scripts/collection-copy.ts", "clean": "rimraf dist components icons www", diff --git a/scripts/types.mjs b/scripts/types.mjs new file mode 100644 index 000000000..c4a2c2a8f --- /dev/null +++ b/scripts/types.mjs @@ -0,0 +1,54 @@ +#!/usr/bin/env node +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Where Ionicons keeps its JSON catalogue +const IONICONS_JSON_PATH = './dist/ionicons.json'; + +function findIoniconsJson() { + if (fs.existsSync(IONICONS_JSON_PATH)) return IONICONS_JSON_PATH; + console.error( + '[generate-ionicon-types] Could not find ionicons.json. ' + + 'Did you build dist first? Tried:\n' + + IONICONS_JSON_PATH, + ); + process.exit(1); +} + +const srcPath = findIoniconsJson(); +const outDir = path.resolve(__dirname, '../src/types'); +const outFile = path.join(outDir, 'iconicons.d.ts'); + +const parsed = JSON.parse(fs.readFileSync(srcPath, 'utf-8')); +const names = Array.from(new Set((parsed.icons || []).map((i) => i.name).filter(Boolean))).sort(); + +// Build the union as nicely wrapped lines +const unionLines = names.map((n) => ` | "${n}"`).join('\n'); + +const banner = `// AUTO-GENERATED FILE. DO NOT EDIT. +// Generated from: ${path.relative(process.cwd(), srcPath)} +// Run: npm run build.types`; + +const content = `${banner} +// 1) Strict union of built-in names +export type IoniconName = +${unionLines}; + +// 2) Flexible sources: prefer union, still allow any string (URLs, data URIs, etc.) +export type IoniconSource = IoniconName | (string & {}); +`; + +fs.mkdirSync(outDir, { recursive: true }); + +// Only write if changed (keeps TS server quiet) +const prev = fs.existsSync(outFile) ? fs.readFileSync(outFile, 'utf-8') : ''; +if (prev !== content) { + fs.writeFileSync(outFile, content); + console.log(`[generate-ionicon-types] Wrote ${path.relative(process.cwd(), outFile)} with ${names.length} icons.`); +} else { + console.log('[generate-ionicon-types] No changes.'); +} diff --git a/src/components.d.ts b/src/components.d.ts index 8cb296879..59f46c23d 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -5,6 +5,8 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from '@stencil/core/internal'; +import { IoniconName, IoniconSource } from './types/iconicons.d'; +export { IoniconName, IoniconSource } from './types/iconicons.d'; export namespace Components { interface IonIcon { /** @@ -18,7 +20,7 @@ export namespace Components { /** * A combination of both `name` and `src`. If a `src` url is detected it will set the `src` property. Otherwise it assumes it's a built-in named SVG and set the `name` property. */ - icon?: any; + icon?: IoniconSource; /** * Specifies which icon to use on `ios` mode. */ @@ -34,13 +36,13 @@ export namespace Components { md?: string; /** * The mode determines which platform styles to use. - * @default getIonMode() + * @default getIonMode() as any */ - mode: string; + mode: any; /** * Specifies which icon to use from the built-in set of icons. */ - name?: string; + name?: IoniconName; /** * When set to `false`, SVG content that is HTTP fetched will not be checked if the response SVG content has any `