UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

295 lines (274 loc) 12.3 kB
import { namespaceParts as defaultNamespaceParts, typeSuffixes as defaultTypeSuffixes } from "./order.mjs"; /** * Base type metadata interface used for organizing exports. * This is a minimal interface that works with both TypesMeta and HighlightedTypesMeta. */ /** * Result of organizing types by export. */ /** * Organizes types data by export name and computes slugs. * * The logic categorizes types as follows: * - Component/hook/function types become the main `type` in their export * - Types ending in .Props, .State, .DataAttributes, etc. become `additionalTypes` for their export * - Non-namespaced types (no dot in the name) go to top-level `additionalTypes` * * Each type is also assigned a `slug` for anchor linking (e.g., "trigger" or "trigger.props"). * * @param variantData - The variant data containing types per variant * @param typeNameMap - Optional map from flat names to dotted names * @returns Exports and additionalTypes organized by export name */ export function organizeTypesByExport(variantData, typeNameMap, ordering) { const namespaceParts = ordering?.namespaceParts ?? defaultNamespaceParts; const typeSuffixes = ordering?.typeSuffixes ?? defaultTypeSuffixes; // Build a mapping from variant name to the type names from that variant const variantTypeNames = {}; for (const [variantName, variant] of Object.entries(variantData)) { variantTypeNames[variantName] = variant.types.map(t => t.name); } // Build a mapping from variant name to its typeNameMap const variantTypeNameMaps = {}; for (const [variantName, variant] of Object.entries(variantData)) { if (variant.typeNameMap && Object.keys(variant.typeNameMap).length > 0) { variantTypeNameMaps[variantName] = variant.typeNameMap; } } // Collect all types from ALL variants and deduplicate by name const typesByName = new Map(); for (const variant of Object.values(variantData)) { for (const typeMeta of variant.types) { const existing = typesByName.get(typeMeta.name); if (!existing) { typesByName.set(typeMeta.name, typeMeta); } else if (typeMeta.type === 'component' || typeMeta.type === 'hook' || typeMeta.type === 'function') { // Prefer components/hooks/functions over other types typesByName.set(typeMeta.name, typeMeta); } } } const allTypes = Array.from(typesByName.values()); if (allTypes.length === 0) { return { exports: {}, additionalTypes: [], variantOnlyAdditionalTypes: {}, variantTypeNames, variantTypeNameMaps }; } // Determine the common component prefix from the first dotted name // E.g., "Accordion.Trigger" -> "Accordion" let componentPrefix = ''; for (const typeMeta of allTypes) { if (typeMeta.name.includes('.')) { componentPrefix = typeMeta.name.split('.')[0]; break; } } // Helper to sort additional types by suffix order (Props, State, DataAttributes, etc.) const sortAdditionalTypes = types => { return types.slice().sort((a, b) => { // Extract suffix from name (last part after dot, or full name if no dot) const getSuffix = name => { const parts = name.split('.'); return parts[parts.length - 1]; }; const aSuffix = getSuffix(a.name); const bSuffix = getSuffix(b.name); const getOrderIndex = suffix => { const idx = typeSuffixes.indexOf(suffix); return idx === -1 ? typeSuffixes.indexOf('__EVERYTHING_ELSE__') : idx; }; const aIdx = getOrderIndex(aSuffix); const bIdx = getOrderIndex(bSuffix); if (aIdx !== bIdx) { return aIdx - bIdx; } // Fallback to alphabetical return a.name.localeCompare(b.name); }); }; const exports = {}; const topLevelAdditionalTypes = []; // First pass: identify all main types (components, hooks, functions) // These are types that are NOT just type aliases for props/state/etc. const mainTypes = new Map(); for (const typeMeta of allTypes) { if (typeMeta.type === 'class' || typeMeta.type === 'component' || typeMeta.type === 'hook' || typeMeta.type === 'function') { mainTypes.set(typeMeta.name, typeMeta); } } // Second pass: categorize all types for (const typeMeta of allTypes) { const name = typeMeta.name; // Check if this is a main type (class/component/hook/function) if (mainTypes.has(name)) { // Extract the export name (e.g., "Root" from "Component.Root" or just "DirectionProvider") const dotIndex = name.lastIndexOf('.'); const exportName = dotIndex > 0 ? name.slice(dotIndex + 1) : name; if (!exports[exportName]) { exports[exportName] = { type: typeMeta, additionalTypes: [] }; } else { // If export already exists (shouldn't happen normally), use this as the main type exports[exportName].type = typeMeta; } } else if (typeMeta.type === 'raw') { // This is a type alias (Props, State, ChangeEventDetails, etc.) // or a standalone type like InputType // Check if it's namespaced (has a dot) if (name.includes('.')) { // Namespaced type - find its parent export // e.g., "Component.Root.Props" -> parent is "Root" // e.g., "Root.Props" -> parent is "Root" // e.g., "Component.Handle" -> this is a standalone export "Handle" (not an additional type) const parts = name.split('.'); // Check if this is a 2-part name where the first part is the component prefix // AND the second part is actually a main type (component/hook/function). // This handles cases like "Accordion.Trigger" where Trigger is a real component. // But NOT cases like "Form.Props" where Props is just a type alias for the Form component. const potentialMainTypeName = `${componentPrefix}.${parts[1]}`; if (parts.length === 2 && parts[0] === componentPrefix && mainTypes.has(potentialMainTypeName)) { const exportName = parts[1]; // Create as a standalone export with the raw type as the main type if (!exports[exportName]) { exports[exportName] = { type: typeMeta, additionalTypes: [] }; } else if (!exports[exportName].type) { // Fill in the placeholder if it was created earlier exports[exportName].type = typeMeta; } else { // Export already has a main type, add this as additional exports[exportName].additionalTypes.push(typeMeta); } } else { // The export name is typically the second-to-last part for namespaced types // "Component.Root.Props" -> exportName = "Root" // "Root.Props" -> exportName = "Root" let exportName; if (parts.length >= 3) { // Full namespace: Component.Part.Suffix exportName = parts[parts.length - 2]; } else if (parts.length === 2) { // Short namespace: Part.Suffix (when componentPrefix doesn't match) exportName = parts[0]; } else { // Single part - shouldn't have a dot, but handle it exportName = parts[0]; } // Find or create the export if (exports[exportName]) { exports[exportName].additionalTypes.push(typeMeta); } else { // Create a placeholder export (the main type might come later) exports[exportName] = { type: null, // Will be filled later additionalTypes: [typeMeta] }; } } } else { // Non-namespaced type - goes to top-level additionalTypes topLevelAdditionalTypes.push(typeMeta); } } } // Clean up any exports that don't have a main type // This shouldn't happen normally, but let's be safe for (const [exportName, exportData] of Object.entries(exports)) { if (!exportData.type) { // Move the additionalTypes to the top level and remove the export topLevelAdditionalTypes.push(...exportData.additionalTypes); delete exports[exportName]; } } // Filter out flat types from topLevelAdditionalTypes that have namespaced equivalents // e.g., if typeNameMap has "AccordionTriggerState" -> "Accordion.Trigger.State", // filter out AccordionTriggerState since Accordion.Trigger.State is already in exports let filteredAdditionalTypes = typeNameMap ? topLevelAdditionalTypes.filter(typeMeta => !typeNameMap[typeMeta.name]) : topLevelAdditionalTypes; // Filter out non-namespaced types when a namespaced version exists. // e.g., if we have both "Orientation" and "Toolbar.Orientation", keep only "Toolbar.Orientation" // since they represent the same type and would display with the same heading. if (componentPrefix) { const namespacedSuffixes = new Set(); for (const typeMeta of filteredAdditionalTypes) { if (typeMeta.name.startsWith(`${componentPrefix}.`)) { // Extract suffix: "Toolbar.Orientation" -> "Orientation" namespacedSuffixes.add(typeMeta.name.slice(componentPrefix.length + 1)); } } // Filter out non-namespaced types that have a namespaced equivalent filteredAdditionalTypes = filteredAdditionalTypes.filter(typeMeta => { // Keep namespaced types if (typeMeta.name.includes('.')) { return true; } // Filter out if a namespaced version exists return !namespacedSuffixes.has(typeMeta.name); }); } // Sort additionalTypes arrays by suffix order (Props, State, DataAttributes, etc.) for (const exportData of Object.values(exports)) { exportData.additionalTypes = sortAdditionalTypes(exportData.additionalTypes); } const sortedAdditionalTypes = sortAdditionalTypes(filteredAdditionalTypes); // Sort exports by namespaceParts order (Root, Provider, Trigger, etc.) // Only use namespaceParts ordering if we have namespaced types (e.g., Component.Root) // For standalone types (e.g., Button, Input), use alphabetical order const getPartOrderIndex = partName => { const idx = namespaceParts.indexOf(partName); return idx === -1 ? namespaceParts.indexOf('__EVERYTHING_ELSE__') : idx; }; const sortedExportNames = Object.keys(exports).sort((a, b) => { if (componentPrefix) { // Namespaced components - sort by namespaceParts order const aIdx = getPartOrderIndex(a); const bIdx = getPartOrderIndex(b); if (aIdx !== bIdx) { return aIdx - bIdx; } } // Fallback to alphabetical for standalone types or items not in the order list return a.localeCompare(b); }); const sortedExports = {}; for (const exportName of sortedExportNames) { sortedExports[exportName] = exports[exportName]; } // Identify variant-only groups: variants whose types are all in additionalTypes // (i.e., they have entries in variantTypeNames but no corresponding key in exports). // Separate their types out so they can be rendered independently. // Only apply when there are multiple variants — a single variant (e.g., "Default") // should keep all its types in the shared additionalTypes. const variantOnlyAdditionalTypes = {}; const variantOnlyTypeNameSet = new Set(); const variantNames = Object.keys(variantTypeNames); if (variantNames.length > 1) { const exportKeySet = new Set(Object.keys(sortedExports)); for (const [variantName, typeNames] of Object.entries(variantTypeNames)) { if (!exportKeySet.has(variantName) && typeNames.length > 0) { const typeNameSet = new Set(typeNames); variantOnlyAdditionalTypes[variantName] = sortedAdditionalTypes.filter(t => typeNameSet.has(t.name)); for (const name of typeNames) { variantOnlyTypeNameSet.add(name); } } } } // Exclude variant-only types from the shared additionalTypes const sharedAdditionalTypes = variantOnlyTypeNameSet.size > 0 ? sortedAdditionalTypes.filter(t => !variantOnlyTypeNameSet.has(t.name)) : sortedAdditionalTypes; return { exports: sortedExports, additionalTypes: sharedAdditionalTypes, variantOnlyAdditionalTypes, variantTypeNames, variantTypeNameMaps }; }