diff --git a/playground/src/pages/components/api-link.astro b/playground/src/pages/components/api-link.astro index bdfa5f3..88c74c3 100644 --- a/playground/src/pages/components/api-link.astro +++ b/playground/src/pages/components/api-link.astro @@ -15,7 +15,6 @@ const headings = [ { depth: 2, slug: 'function', text: 'Function' }, { depth: 2, slug: 'type-alias', text: 'Type alias' }, { depth: 2, slug: 'sub-package', text: 'Sub-package' }, - { depth: 2, slug: 'platform-overrides', text: 'Platform overrides' }, { depth: 2, slug: 'sass-links', text: 'Sass links' }, { depth: 2, slug: 'platform-env', text: 'Platform env var' }, ]; @@ -49,11 +48,6 @@ const headings = [
Use the pkg prop to target a sub-package:
exclude renders plain code on matching platforms:
excludePrefixFor keeps symbols unprefixed on matching platforms:
excludeSuffixFor suppresses framework class suffixes on matching platforms:
Sass module page with prose label:
Sass API anchor with code label:
link pointing to the TypeDoc API reference for a class,
- * interface, enum, type alias, variable, or function — and optionally a
- * specific member (property / method) on a class or interface.
- *
- * Usage:
- *
- *
- * → IgrToast
- *
- *
- *
- * → Show
- *
- *
- *
- *
- *
- *
- * → configureTheme
- *
- *
- *
- * → …
- *
- *
- *
- * → AbsolutePosition
- *
- *
- *
- * → …
- *
- *
- *
- * → …
+ * TypeDoc links first resolve against the compact api-docs ApiLink index. If
+ * the index is unavailable, the component falls back to legacy URL generation
+ * so existing docs can migrate gradually.
*/
-import type { PlatformContext } from '../../../lib/types.ts';
+import type { ApiPackageConfig, PlatformContext } from '../../../lib/types.ts';
+import {
+ KIND_SEGMENT,
+ resolveApiLinkFromIndex,
+ type TypeDocKind,
+} from './api-link-index';
import './ApiLink.scss';
-type ApiKind = 'class' | 'interface' | 'enum' | 'type' | 'variable' | 'function' | 'sass';
-
-/** Props for SASS API documentation links (`kind="sass"`). */
+/** Props for Sass API documentation links (`kind="sass"`). */
type SassProps = {
+ /** Selects Sass API link mode. */
kind: 'sass';
- /** The anchor fragment without "#", e.g. "mixin-slide-in-left". Optional when linking to the module page. */
+ /** Anchor fragment without `#`, e.g. `mixin-slide-in-left`. */
type?: string;
- /** SASS module name, e.g. "animations", "themes". Required for correct URL generation. */
+ /** Sass module path segment, e.g. `animations` or `themes`. */
module?: string;
- /**
- * When true, wraps the label in .
- * @default true — matches TypeDoc ApiLink code-formatting behaviour.
- * Set to false for descriptive/prose labels.
- */
+ /** Wrap the rendered label in ``. Defaults to `true`. */
code?: boolean;
- /** Override the display text. Defaults to `type ?? module ?? ''`. */
+ /** Override the rendered label. */
label?: string;
};
-/** Props for TypeDoc API documentation links (all non-sass kinds). */
+/** Props for TypeDoc API documentation links. */
type TypeDocProps = {
/**
- * TypeDoc symbol kind. Determines the URL segment used.
+ * TypeDoc symbol kind. Narrows registry lookup and determines the legacy
+ * fallback URL segment.
* @default "class"
*/
- kind?: Exclude;
+ kind?: TypeDocKind;
/**
* Short type/symbol name without platform prefix, e.g. "Toast".
* Required for all TypeDoc kinds.
*/
type: string;
- /** Optional member name (property / method), e.g. "show". Appended as #anchor. */
+ /** Optional class/interface member, property, method, or enum value anchor. */
member?: string;
- /**
- * Package key as defined in platform-context apiPackages.
- * Defaults to "core" (the main component package).
- * Use "charts", "grids", "gauges", "maps", "inputs", "layouts",
- * "excel", "spreadsheet", "datasources" for sub-packages.
- */
+ /** Package key used only to filter ambiguous registry matches. */
pkg?: string;
- /**
- * Override the display text. Defaults to the (prefixed) name,
- * optionally suffixed with ".member".
- */
+ /** Override the rendered label. */
label?: string;
/**
- * When true (default) the platform prefix (Igr/Igx/Igc/Igb) is
- * prepended to `type` automatically. Set to false when passing a
- * fully-qualified name or a non-prefixed symbol like a function name.
+ * Whether to prepend the platform prefix before registry lookup or legacy
+ * fallback URL generation.
+ * @default true
*/
prefixed?: boolean;
/**
- * When true (default) the package classSuffix (e.g. "Component" for Angular)
- * is appended to the class name. Set to false for utility/non-component classes
- * that do not carry the platform class suffix (e.g. FilteringOperand, SortingStrategy).
+ * Whether to try the platform class suffix before the unsuffixed symbol.
* @default true
*/
suffix?: boolean;
- /**
- * Comma-separated list of platforms (e.g. "React" or "React,Blazor") for
- * which the API link does NOT exist on the documentation site. When the
- * current platform matches any entry the component renders the type name
- * as plain inline code (no link), preserving the symbol reference for the
- * reader without producing a broken URL.
- *
- * Use this instead of wrapping a single in a
- * just to hide it from a specific platform.
- */
- exclude?: string;
- /**
- * Comma-separated list of platforms for which the package classSuffix
- * (e.g. "Component") should NOT be appended, overriding the package default.
- * Useful when the same type is a plain class on some platforms but carries
- * a framework suffix on others (e.g. "Angular,WebComponents").
- *
- * Does not suppress the link — combine with `exclude` to suppress entirely.
- */
- excludeSuffixFor?: string;
- /**
- * Comma-separated list of platforms for which the platform prefix
- * (Igr/Igx/Igc/Igb) should NOT be prepended, overriding the package default.
- * Useful when a symbol has no prefix on certain platforms.
- *
- * Does not suppress the link — combine with `exclude` to suppress entirely.
- */
- excludePrefixFor?: string;
-
};
type Props = SassProps | TypeDocProps;
-const KIND_SEGMENT: Record, string> = {
- class: 'classes',
- interface: 'interfaces',
- enum: 'enums',
- type: 'types',
- variable: 'variables',
- function: 'functions',
-};
-
const ctx = Astro.locals.platformContext as PlatformContext;
-const { name: platformName, prefix, apiPackages } = ctx;
+const { prefix, apiPackages } = ctx;
const label = Astro.props.label;
-const splitList = (s?: string) => s ? s.split(',').map(p => p.trim()).filter(Boolean) : [];
+const upperFirst = (value: string) => value ? value.charAt(0).toUpperCase() + value.slice(1) : value;
+
+function buildLegacyUrl(options: {
+ type: string;
+ kind: TypeDocKind;
+ member?: string;
+ prefix: string;
+ prefixed: boolean;
+ suffix: boolean;
+ pkgConfig: ApiPackageConfig;
+}) {
+ const { type, kind, member, prefix, prefixed, suffix, pkgConfig } = options;
+ const baseType = prefixed ? `${prefix}${type}` : type;
+ const segment = KIND_SEGMENT[kind];
+
+ if (kind === 'class') {
+ const fullType = (suffix && pkgConfig.classSuffix) ? `${baseType}${pkgConfig.classSuffix}` : baseType;
+ const cased = pkgConfig.preserveCase ? fullType : fullType.toLowerCase();
+ const classSlug = pkgConfig.noPackagePrefix
+ ? cased
+ : `${pkgConfig.packageId}.${cased}`;
+ const memberAnchor = member
+ ? `#${pkgConfig.pascalCaseMembers ? upperFirst(member) : member}`
+ : '';
+ return `${pkgConfig.docRoot}/classes/${classSlug}${memberAnchor}`;
+ }
+
+ const slug = pkgConfig.noPackagePrefix
+ ? baseType
+ : `${pkgConfig.packageId}.${baseType}`;
+ const memberAnchor = member
+ ? `#${kind === 'enum' ? member : pkgConfig.pascalCaseMembers ? upperFirst(member) : member.toLowerCase()}`
+ : '';
+
+ return `${pkgConfig.docRoot}/${segment}/${slug}${memberAnchor}`;
+}
+
+function getIndexedDisplayName(resolvedName: string, fallbackName: string, type: string, classSuffix?: string) {
+ if (resolvedName === type) return type;
+ if (classSuffix && resolvedName === `${type}${classSuffix}`) return type;
+ return fallbackName;
+}
let url: string;
let displayLabel: string;
let renderCode: boolean;
-let isExcluded = false;
if (Astro.props.kind === 'sass') {
const { type, module, code = true } = Astro.props;
- if (!module) console.warn('[ApiLink] kind="sass" requires a `module` prop — link may be malformed.');
+ if (!module) console.warn('[ApiLink] kind="sass" requires a `module` prop - link may be malformed.');
const base = ctx.sassApiUrl?.trim().replace(/\/+$/, '');
const anchor = type ? `#${type}` : '';
if (!base) {
- console.warn('[ApiLink] kind="sass" requires `platformContext.sassApiUrl` to be configured — falling back to "#".');
+ console.warn('[ApiLink] kind="sass" requires `platformContext.sassApiUrl` to be configured - falling back to "#".');
url = '#';
} else {
url = `${base}/${module ?? ''}${anchor}`;
@@ -167,48 +128,55 @@ if (Astro.props.kind === 'sass') {
renderCode = code;
} else {
const {
- type, kind = 'class', member, pkg = 'core',
- prefixed = true, suffix = true,
- exclude, excludeSuffixFor, excludePrefixFor,
+ type,
+ member,
+ pkg = 'core',
+ prefixed = true,
+ suffix = true,
} = Astro.props;
- isExcluded = splitList(exclude).includes(platformName);
- const isSuffixExcluded = splitList(excludeSuffixFor).includes(platformName);
- const isPrefixExcluded = splitList(excludePrefixFor).includes(platformName);
- const effectivePrefixed = prefixed && !isPrefixExcluded;
- const effectiveSuffix = suffix && !isSuffixExcluded;
-
- const pkgConfig = apiPackages[pkg] ?? apiPackages['core'];
- const baseType = effectivePrefixed ? `${prefix}${type}` : type;
- const segment = KIND_SEGMENT[kind];
- if (kind === 'class') {
- const fullType = (effectiveSuffix && pkgConfig.classSuffix) ? `${baseType}${pkgConfig.classSuffix}` : baseType;
- const cased = pkgConfig.preserveCase ? fullType : fullType.toLowerCase();
- const classSlug = pkgConfig.noPackagePrefix
- ? cased
- : `${pkgConfig.packageId}.${cased}`;
- const memberAnchor = member
- ? `#${pkgConfig.pascalCaseMembers ? member.charAt(0).toUpperCase() + member.slice(1) : member}`
- : '';
- url = `${pkgConfig.docRoot}/classes/${classSlug}${memberAnchor}`;
- } else {
- const slug = pkgConfig.noPackagePrefix
- ? baseType
- : `${pkgConfig.packageId}.${baseType}`;
- const memberAnchorNonClass = member
- ? `#${
- kind === 'enum'
- ? member
- : pkgConfig.pascalCaseMembers
- ? member.charAt(0).toUpperCase() + member.slice(1)
- : member.toLowerCase()
- }`
- : '';
- url = `${pkgConfig.docRoot}/${segment}/${slug}${memberAnchorNonClass}`;
- }
+ const explicitKind = 'kind' in Astro.props ? Astro.props.kind : undefined;
+ const kind: TypeDocKind = explicitKind ?? 'class';
+
+ const effectivePrefixed = prefixed;
+ const effectiveSuffix = suffix;
+
+ // pkg is an ambiguity override. Without an explicit pkg prop, search the
+ // combined api-docs index so symbols can resolve from any package.
+ const explicitPkg = 'pkg' in Astro.props && typeof pkg === 'string' && pkg.length > 0;
+ const pkgConfig = apiPackages[explicitPkg ? pkg : 'core'] ?? apiPackages.core;
+ const baseType = effectivePrefixed ? `${prefix}${type}` : type;
+
+ url = buildLegacyUrl({
+ type,
+ kind,
+ member,
+ prefix,
+ prefixed: effectivePrefixed,
+ suffix: effectiveSuffix,
+ pkgConfig,
+ });
displayLabel = label ?? (member ? `${baseType}.${member}` : baseType);
renderCode = true;
+
+ const indexed = await resolveApiLinkFromIndex({
+ ctx,
+ pkgConfig,
+ explicitPkg,
+ type,
+ member,
+ explicitKind,
+ prefix,
+ prefixed: effectivePrefixed,
+ suffix: effectiveSuffix,
+ });
+
+ if (indexed.status === 'resolved') {
+ url = indexed.url;
+ const indexedDisplay = getIndexedDisplayName(indexed.symbolName, baseType, type, pkgConfig.classSuffix);
+ displayLabel = label ?? (indexed.memberName ? `${indexedDisplay}.${indexed.memberName}` : indexedDisplay);
+ } else if (indexed.status === 'missing' && import.meta.env.DEV) {
+ console.warn(`[ApiLink] Registry miss for ${type}${member ? `.${member}` : ''}; using legacy URL fallback.`);
+ }
}
---
-{isExcluded
- ? {displayLabel}
- : {renderCode ? {displayLabel} : displayLabel}}
+{renderCode ? {displayLabel} : displayLabel}
diff --git a/src/components/mdx/ApiLink/README.md b/src/components/mdx/ApiLink/README.md
index 5b45efc..309f5ae 100644
--- a/src/components/mdx/ApiLink/README.md
+++ b/src/components/mdx/ApiLink/README.md
@@ -1,10 +1,13 @@
# ApiLink
-Renders an inline API link with platform-aware URL generation.
+Renders an inline API documentation link with platform-aware resolution.
-For TypeDoc symbols, it renders an inline `` link to a class,
-interface, enum, type alias, variable, or function. It can also render Sass API
-links when `kind="sass"`.
+For TypeDoc symbols, `ApiLink` first queries the generated api-docs link index.
+That index contains the real symbol names, packages, URL segments, and member
+anchors produced by api-docs. If the index is unavailable, `ApiLink` falls back
+to legacy URL generation so migrated MDX continues to build.
+
+Sass links are separate and still use `kind="sass"`.
## Import
@@ -12,101 +15,95 @@ links when `kind="sass"`.
import ApiLink from 'igniteui-astro-components/components/mdx/ApiLink.astro';
```
-## TypeDoc props
+## Preferred Usage
-| Prop | Type | Default | Description |
-|------|------|---------|-------------|
-| `type` | `string` | *(required)* | Short symbol name without platform prefix, e.g. `"Toast"`. |
-| `kind` | `'class' \| 'interface' \| 'enum' \| 'type' \| 'variable' \| 'function'` | `'class'` | TypeDoc symbol kind — determines the URL path segment. |
-| `member` | `string` | — | Optional member name (property/method). Appended as a `#anchor`. |
-| `pkg` | `string` | `'core'` | Package key: `'core'` \| `'charts'` \| `'grids'` \| `'gauges'` \| `'maps'` \| `'inputs'` \| `'layouts'` \| `'excel'` \| `'spreadsheet'` \| `'datasources'`. |
-| `label` | `string` | auto | Override the display text. Defaults to the prefixed name (+ `.member` when provided). |
-| `prefixed` | `boolean` | `true` | When `true`, prepends the platform prefix (`Igr`/`Igx`/`Igc`/`Igb`) to `type`. Set `false` for already-qualified names or non-prefixed symbols like function names. |
-| `suffix` | `boolean` | `true` | When `true`, appends the platform class suffix (e.g. `Component` for Angular). Set `false` for utility classes that do not carry a suffix. |
-| `exclude` | `string` | — | Comma-separated platform names where the link should not render. Matching platforms render the label as plain inline ``. |
-| `excludeSuffixFor` | `string` | — | Comma-separated platform names where `classSuffix` should not be appended, even when `suffix` is `true`. |
-| `excludePrefixFor` | `string` | — | Comma-separated platform names where the platform prefix should not be prepended, even when `prefixed` is `true`. |
+Author MDX with the unprefixed API name:
+
+```mdx
+
+
+
+```
-Platform names use the display form from `PlatformContext.name`: `Angular`,
-`React`, `WebComponents`, or `Blazor`.
+The resolver handles platform prefix/suffix candidates, package lookup, kind
+lookup, URL segment lookup, and member anchor lookup.
-## Sass props
+Omitting `pkg` relies on `platformContext.apiLinkIndex` being available. When
+the index is unavailable, `ApiLink` falls back to legacy URL generation and
+cannot reliably infer the correct package for non-core symbols.
-Use `kind="sass"` for Sass API reference links. Sass links read their base URL
-from `platformContext.sassApiUrl`.
+## TypeDoc Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
-| `kind` | `'sass'` | *(required)* | Enables Sass API link mode. |
-| `module` | `string` | — | Sass module path segment, e.g. `"animations"` or `"themes"`. |
-| `type` | `string` | — | Anchor fragment without `#`, e.g. `"mixin-slide-in-left"`. Omit it to link to the module page. |
-| `label` | `string` | auto | Override the display text. Defaults to `type`, then `module`, then an empty string. |
-| `code` | `boolean` | `true` | Wrap the label in ``. Set `false` for prose labels. |
+| `type` | `string` | *(required)* | Short symbol name without platform prefix, e.g. `"Grid"` instead of `"IgrGrid"`. |
+| `member` | `string` | — | Optional member name. Resolved through the generated member map when available. |
+| `label` | `string` | auto | Override the display text. |
+| `pkg` | `string` | — | Ambiguity override only. Use when the same symbol exists in multiple packages and the combined index cannot choose safely. |
+| `kind` | `'class' \| 'interface' \| 'enum' \| 'type' \| 'variable' \| 'function'` | auto / legacy `'class'` | Optional narrowing. Usually unnecessary when the index is available. |
+| `prefixed` | `boolean` | `true` | Legacy override for symbols that are never platform-prefixed. Avoid for new docs when the index can resolve the symbol. |
+| `suffix` | `boolean` | `true` | Legacy override for symbols that never use the platform class suffix. Avoid for new docs when the index can resolve the symbol. |
+
+Platform names use `PlatformContext.name`: `Angular`, `React`,
+`WebComponents`, or `Blazor`.
## Examples
```mdx
-{/* Core class — auto-prefix applied */}
-
-{/* → IgrToast */}
+{/* Let the generated index find package, kind, and exact symbol name. */}
+
-{/* Class member */}
-
+{/* Member anchors are resolved from the generated member map. */}
+
-{/* Sub-package */}
-
+{/* Use pkg only when the symbol name is ambiguous across packages. */}
+
+```
-{/* Function — no prefix, no suffix */}
-
+## ApiLink Index
-{/* Type alias */}
-
+`ApiLink` resolves TypeDoc symbols from `platformContext.apiLinkIndex`, a compact
+registry generated from api-docs TypeDoc data and stored by the docs host. The
+component does not fetch api-docs during the documentation build.
-{/* Interface */}
-
+The docs host loads one latest registry per platform, for example:
-{/* Enum */}
-
+```txt
+src/data/api-link-index/angular/staging-latest.json
+src/data/api-link-index/webcomponents/staging-latest.json
+```
-{/* Utility class without platform suffix */}
-
+Registry symbol fields are intentionally short because the files are large:
-{/* Hide a broken API link on selected platforms */}
-
+| Field | Meaning |
+|-------|---------|
+| `p` | Package id that owns the symbol. |
+| `u` | Root-relative API URL for the symbol. |
+| `k` | Symbol kind, e.g. `class`, `interface`, `enum`, `type`. |
+| `s` | API docs URL segment, e.g. `classes` or `interfaces`. |
+| `m` | Member name to anchor map. |
-{/* Keep the symbol unprefixed only for React */}
-
+When `pkg` is present, `ApiLink` uses it only as a package filter for ambiguous
+symbol names; package URLs and kinds still come from the registry.
-{/* Suppress the Angular class suffix for this symbol */}
-
+## Sass Props
+Use `kind="sass"` for Sass API reference links. Sass links do not use the
+api-docs link index; they read their base URL from `platformContext.sassApiUrl`.
-{/* Sass module page */}
-
+| Prop | Type | Default | Description |
+|------|------|---------|-------------|
+| `kind` | `'sass'` | *(required)* | Enables Sass API link mode. |
+| `module` | `string` | — | Sass module path segment, e.g. `"animations"` or `"themes"`. |
+| `type` | `string` | — | Anchor fragment without `#`, e.g. `"mixin-slide-in-left"`. Omit it to link to the module page. |
+| `label` | `string` | auto | Override the display text. Defaults to `type`, then `module`, then an empty string. |
+| `code` | `boolean` | `true` | Wrap the label in ``. Set `false` for prose labels. |
-{/* Sass symbol anchor */}
+```mdx
+
```
-## Platform context
-
-The current platform is read from the `PLATFORM` environment variable (set at build time). Use `siteMetaIntegration({ platform })` or `createDocsSite({ platform })` in `astro.config.ts`:
-
-```ts
-createDocsSite({ platform: 'react' });
-// prefix → 'Igr', docRoot → 'https://www.infragistics.com/products/ignite-ui-react/docs/typescript/latest'
-```
-
-Supported platforms and their prefixes:
-
-| Platform | Prefix | Class suffix |
-|----------|--------|--------------|
-| `Angular` | `Igx` | `Component` (DV pkgs only) |
-| `React` | `Igr` | — |
-| `WebComponents` | `Igc` | — |
-| `Blazor` | `Igb` | — |
-
-For Sass links, make sure `platformContext.sassApiUrl` is configured. The URL is
-assembled as:
+Sass URL shape:
```txt
{sassApiUrl}/{module}#{type}
diff --git a/src/components/mdx/ApiLink/api-link-index.ts b/src/components/mdx/ApiLink/api-link-index.ts
new file mode 100644
index 0000000..86033a2
--- /dev/null
+++ b/src/components/mdx/ApiLink/api-link-index.ts
@@ -0,0 +1,186 @@
+import type { ApiPackageConfig, PlatformContext } from '../../../lib/types';
+
+export type ApiKind = 'class' | 'interface' | 'enum' | 'type' | 'variable' | 'function' | 'sass';
+export type TypeDocKind = Exclude;
+
+export const KIND_SEGMENT: Record = {
+ class: 'classes',
+ interface: 'interfaces',
+ enum: 'enums',
+ type: 'types',
+ variable: 'variables',
+ function: 'functions',
+};
+
+type ApiLinkIndexSymbol = {
+ /** Package id. */
+ p?: string;
+ /** URL. */
+ u: string;
+ /** Kind. */
+ k?: TypeDocKind;
+ /** URL segment. */
+ s?: string;
+ /** Member name to anchor map. */
+ m?: Record;
+};
+
+type ApiLinkIndexFile = {
+ symbols?: Record;
+};
+
+export type ApiLinkIndexResolution =
+ | { status: 'resolved'; url: string; symbolName: string; memberName: string; memberAnchor: string }
+ | { status: 'missing' }
+ | { status: 'unavailable' };
+
+const upperFirst = (value: string) => value ? value.charAt(0).toUpperCase() + value.slice(1) : value;
+
+function buildCandidateNames(options: {
+ type: string;
+ explicitKind?: TypeDocKind;
+ prefix: string;
+ prefixed: boolean;
+ suffix: boolean;
+ classSuffix?: string;
+}): string[] {
+ const candidates = new Set();
+ const baseNames = new Set();
+
+ if (options.prefixed) baseNames.add(`${options.prefix}${options.type}`);
+ baseNames.add(options.type);
+
+ for (const baseName of baseNames) {
+ if ((!options.explicitKind || options.explicitKind === 'class') && options.suffix && options.classSuffix) {
+ candidates.add(`${baseName}${options.classSuffix}`);
+ }
+ candidates.add(baseName);
+ }
+
+ return [...candidates];
+}
+
+function resolveIndexedMember(symbol: ApiLinkIndexSymbol, member: string | undefined): { memberName: string; memberAnchor: string } | null {
+ if (!member) return { memberName: '', memberAnchor: '' };
+
+ const members = symbol.m ?? {};
+ const candidates = new Set([member, upperFirst(member), member.toLowerCase()]);
+
+ for (const candidate of candidates) {
+ if (Object.hasOwn(members, candidate)) {
+ return { memberName: candidate, memberAnchor: members[candidate] };
+ }
+ }
+
+ const normalized = member.toLowerCase();
+ for (const [registryMember, memberAnchor] of Object.entries(members)) {
+ if (registryMember.toLowerCase() === normalized) {
+ return { memberName: registryMember, memberAnchor };
+ }
+ }
+
+ return null;
+}
+
+function findIndexedSymbol(options: {
+ index: ApiLinkIndexFile;
+ candidates: string[];
+ packageId?: string;
+ explicitKind?: TypeDocKind;
+ member?: string;
+}) {
+ const symbols = options.index.symbols ?? {};
+
+ for (const name of options.candidates) {
+ const value = symbols[name];
+ if (!value) continue;
+
+ const symbolList = Array.isArray(value) ? value : [value];
+ for (const symbol of symbolList) {
+ if (options.packageId && symbol.p && symbol.p !== options.packageId) continue;
+ if (options.explicitKind && symbol.k && symbol.k !== options.explicitKind) continue;
+ const memberMatch = resolveIndexedMember(symbol, options.member);
+ if (memberMatch === null) continue;
+ return { name, symbol, memberName: memberMatch.memberName, memberAnchor: memberMatch.memberAnchor };
+ }
+ }
+
+ return null;
+}
+
+function absolutizeIndexUrl(indexedPath: string, docRoot: string): string {
+ if (!indexedPath.startsWith('/')) return indexedPath;
+
+ try {
+ return `${new URL(docRoot).origin}${indexedPath}`;
+ } catch {
+ return indexedPath;
+ }
+}
+
+function getCandidateClassSuffixes(options: {
+ ctx: PlatformContext;
+ explicitPkg: boolean;
+ pkgConfig: ApiPackageConfig;
+}): Array {
+ if (options.explicitPkg) {
+ return [options.pkgConfig.classSuffix];
+ }
+
+ const suffixes = new Set();
+ for (const pkg of Object.values(options.ctx.apiPackages)) {
+ suffixes.add(pkg.classSuffix);
+ }
+
+ return [...suffixes];
+}
+
+export async function resolveApiLinkFromIndex(options: {
+ ctx: PlatformContext;
+ pkgConfig: ApiPackageConfig;
+ explicitPkg: boolean;
+ type: string;
+ member?: string;
+ explicitKind?: TypeDocKind;
+ prefix: string;
+ prefixed: boolean;
+ suffix: boolean;
+}): Promise {
+ const index = options.ctx.apiLinkIndex as ApiLinkIndexFile | undefined;
+ if (!index?.symbols) {
+ return { status: 'unavailable' };
+ }
+
+ const candidates = new Set();
+ for (const classSuffix of getCandidateClassSuffixes(options)) {
+ for (const candidate of buildCandidateNames({
+ type: options.type,
+ explicitKind: options.explicitKind,
+ prefix: options.prefix,
+ prefixed: options.prefixed,
+ suffix: options.suffix,
+ classSuffix,
+ })) {
+ candidates.add(candidate);
+ }
+ }
+
+ const indexed = findIndexedSymbol({
+ index,
+ candidates: [...candidates],
+ packageId: options.explicitPkg ? options.pkgConfig.packageId : undefined,
+ explicitKind: options.explicitKind,
+ member: options.member,
+ });
+
+ if (!indexed) return { status: 'missing' };
+
+ const path = `${indexed.symbol.u}${indexed.memberAnchor ? `#${indexed.memberAnchor}` : ''}`;
+ return {
+ status: 'resolved',
+ url: absolutizeIndexUrl(path, options.pkgConfig.docRoot),
+ symbolName: indexed.name,
+ memberName: indexed.memberName,
+ memberAnchor: indexed.memberAnchor,
+ };
+}
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 3bf22a5..1838a74 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -20,10 +20,9 @@ export interface ApiPackageConfig {
*/
preserveCase?: boolean;
/**
- * Optional suffix appended to the class name before lowercasing, e.g.
- * Angular DV packages append "Component" so `CategoryChart` resolves to
- * `igniteui_angular_charts.igxcategorychartcomponent.html`.
- * Only applied when `prefixed={true}`.
+ * Preferred class-name suffix used by ApiLink. The generated registry tries
+ * both the suffixed and unsuffixed names, so this does not mean every API
+ * symbol is expected to have the suffix.
*/
classSuffix?: string;
/**
@@ -35,6 +34,10 @@ export interface ApiPackageConfig {
export interface PlatformContext {
name: PlatformName;
+ /** Optional compact ApiLink symbol index loaded by the docs host at build time. */
+ apiLinkIndex?: {
+ symbols?: Record;
+ };
/** Lower-case slug used in URLs, e.g. "angular" */
lower: string;
/** Component class prefix, e.g. "Igx" / "Igr" / "Igc" / "Igb" */