import './core/app-telemetry.js'; import { createComponentRuntime } from './core/app-component-runtime.js'; import { warnOnce } from './core/app-dom.js'; import { initFlashAutoDismiss } from './components/app-flash-auto-dismiss.js'; import { initPasswordHints } from './components/app-password-hints.js'; import { initBadgeCopy } from './components/app-copy-badge.js'; import { initCopyField } from './components/app-copy-field.js'; import { initMultiSelectComponent } from './components/app-multiselect-init.js'; import { initTokenSelect } from './components/app-token-select.js'; import { initSidebarToggle } from './components/app-toggle-sidebar.js'; import { initAsidePanels } from './components/app-toggle-aside-panels.js'; import { initDetailsAsideToggle } from './components/app-toggle-details-aside.js'; import { initGlobalSearch } from './components/app-global-search.js'; import { initThemeControls } from './components/app-theme-toggle.js'; import { initContrastToggle } from './components/app-contrast-toggle.js'; import { initConfirmActions } from './components/app-confirm-actions.js'; import { initDetailsState } from './components/app-details-state.js'; import { initTenantSwitcher } from './components/app-tenant-switcher.js'; import { initCustomFieldOptionsToggle } from './components/app-custom-field-options-toggle.js'; import { initTenantSsoToggle } from './components/app-tenant-sso-toggle.js'; import { initTenantLdapToggle } from './components/app-tenant-ldap-toggle.js'; import { initTelemetrySettings } from './components/app-settings-telemetry.js'; import { initStandardDetailPages } from './components/app-standard-detail-page.js'; import { initAutoSubmit } from './components/app-auto-submit.js'; import { initTabs } from './components/app-tabs.js'; import { initListTabs } from './components/app-list-tabs.js'; import { initSessionWarning } from './components/app-session-warning.js'; import { initFsLightboxRefresh } from './components/app-fslightbox-refresh.js'; import { initFileUpload } from './components/app-file-upload.js'; import { initLookupFields } from './components/app-lookup-field.js'; const runtime = createComponentRuntime({ root: document, configId: 'app-components', }); const phaseRank = { early: 10, default: 50, late: 90, }; const normalizePhase = (raw) => { const phase = String(raw || '').trim().toLowerCase(); if (phase === 'early' || phase === 'default' || phase === 'late') { return phase; } return 'late'; }; const normalizeModuleRuntimeComponents = (value) => { if (!Array.isArray(value)) { return []; } return value .filter((item) => item && typeof item === 'object') .map((item) => { const key = String(item.key || '').trim(); const moduleId = String(item.moduleId || '').trim(); const componentName = String(item.componentName || '').trim() || (moduleId && key ? `${moduleId}.${key}` : ''); const script = String(item.script || '').trim(); const exportName = String(item.export || '').trim(); const selector = String(item.selector || '').trim(); const scope = String(item.scope || '').trim().toLowerCase(); const configPath = String(item.configPath || '').trim(); const phase = normalizePhase(item.phase); const order = Number.isFinite(Number(item.order)) ? Number(item.order) : 100; return { key, moduleId, componentName, script, exportName, selector, scope, configPath, phase, order, }; }) .filter((item) => item.componentName !== '' && item.script !== '') .sort((a, b) => { const phaseCmp = (phaseRank[a.phase] || phaseRank.late) - (phaseRank[b.phase] || phaseRank.late); if (phaseCmp !== 0) { return phaseCmp; } if (a.order !== b.order) { return a.order - b.order; } return a.componentName.localeCompare(b.componentName); }); }; const registerRuntimeComponent = async (definition) => { const moduleContext = { module: 'app-init', runtimeComponent: definition.componentName, }; const scriptUrl = (() => { try { return new URL(definition.script, document.baseURI).href; } catch { return ''; } })(); if (!scriptUrl) { warnOnce('UI_COMPONENT_INVALID', `Invalid runtime.component script URL for "${definition.componentName}"`, moduleContext); return; } try { const loaded = await import(scriptUrl); const exportName = definition.exportName || 'default'; const init = loaded?.[exportName]; if (typeof init !== 'function') { warnOnce( 'UI_COMPONENT_INVALID', `runtime.component "${definition.componentName}" export "${exportName}" is not a function`, moduleContext ); return; } const componentOptions = {}; if (definition.scope !== '') { componentOptions.scope = definition.scope; } if (definition.selector !== '') { componentOptions.selector = definition.selector; } if (definition.configPath !== '') { componentOptions.configPath = definition.configPath; } runtime.register(definition.componentName, init, componentOptions); } catch (error) { warnOnce('UI_COMPONENT_IMPORT_FAILED', `Failed to import runtime.component "${definition.componentName}"`, { ...moduleContext, error, }); } }; const mountModuleComponentsByPhase = async (phase) => { const pageConfig = runtime.getPageConfig(); const runtimeComponents = normalizeModuleRuntimeComponents(pageConfig?.moduleRuntimeComponents); const phaseComponents = runtimeComponents.filter((item) => item.phase === phase); for (const definition of phaseComponents) { // eslint-disable-next-line no-await-in-loop await registerRuntimeComponent(definition); } }; const bootRuntime = async () => { // Phase A: low risk and global utilities. runtime.register('confirm-actions', initConfirmActions, { scope: 'global', configPath: 'components.confirmActions', }); runtime.register('flash-auto-dismiss', initFlashAutoDismiss, { selector: '[data-app-component="flash-auto-dismiss"]', configPath: 'components.flashAutoDismiss', defaultConfig: { selector: '.notice[data-flash-timeout]', }, }); runtime.register('password-hints', initPasswordHints, { scope: 'global', configPath: 'components.passwordHints', }); runtime.register('copy-badge', initBadgeCopy, { scope: 'global', configPath: 'components.copyBadge', }); runtime.register('copy-field', initCopyField, { scope: 'global', configPath: 'components.copyField', }); runtime.register('multiselect', initMultiSelectComponent, { scope: 'global', configPath: 'components.multiSelect', }); runtime.register('token-select', initTokenSelect, { selector: '[data-app-component="token-select"]', configPath: 'components.tokenSelect', }); runtime.register('details-state', initDetailsState, { scope: 'global', configPath: 'components.detailsState', }); runtime.register('settings-telemetry', initTelemetrySettings, { scope: 'global', configPath: 'components.settingsTelemetry', }); runtime.register('auto-submit', initAutoSubmit, { scope: 'global', configPath: 'components.autoSubmit', }); runtime.register('file-upload', initFileUpload, { selector: '[data-app-component="file-upload"]', configPath: 'components.fileUpload', }); runtime.register('lookup-field', initLookupFields, { scope: 'global', configPath: 'components.lookupField', defaultConfig: { selector: '[data-app-lookup]', }, }); await mountModuleComponentsByPhase('early'); // Phase B: structure-defining components. runtime.register('sidebar-toggle', initSidebarToggle, { scope: 'global', configPath: 'components.sidebarToggle', }); runtime.register('aside-panels', initAsidePanels, { scope: 'global', configPath: 'components.asidePanels', }); runtime.register('details-aside-toggle', initDetailsAsideToggle, { scope: 'global', configPath: 'components.detailsAsideToggle', }); runtime.register('theme-controls', initThemeControls, { selector: '[data-app-component="theme-controls"]', configPath: 'components.themeControls', }); runtime.register('contrast-toggle', initContrastToggle, { selector: '[data-app-component="contrast-toggle"]', configPath: 'components.contrastToggle', }); runtime.register('tenant-switcher', initTenantSwitcher, { selector: '[data-app-component="tenant-switcher"]', configPath: 'components.tenantSwitcher', }); runtime.register('custom-field-options-toggle', initCustomFieldOptionsToggle, { scope: 'global', configPath: 'components.customFieldOptionsToggle', }); runtime.register('tenant-sso-toggle', initTenantSsoToggle, { scope: 'global', configPath: 'components.tenantSsoToggle', }); runtime.register('tenant-ldap-toggle', initTenantLdapToggle, { scope: 'global', configPath: 'components.tenantLdapToggle', }); runtime.register('standard-detail-page', initStandardDetailPages, { scope: 'global', configPath: 'components.standardDetailPage', }); await mountModuleComponentsByPhase('default'); // Phase C: high-risk (search, side panel interactions). runtime.register('global-search', initGlobalSearch, { selector: '[data-app-search-dialog]', configPath: 'components.globalSearch', }); runtime.register('tabs', initTabs, { selector: '[data-app-component="tabs"]', configPath: 'components.tabs', defaultConfig: { selector: '[data-app-component="tabs"]', storageNamespace: 'app-ui', storageVersion: 'v1', }, }); runtime.register('list-tabs', initListTabs, { selector: '.app-list-tabs', configPath: 'components.listTabs', defaultConfig: { selector: '.app-list-tabs', }, }); runtime.register('session-warning', initSessionWarning, { selector: '[data-app-component="session-warning"]', configPath: 'components.sessionWarning', defaultConfig: { selector: '[data-app-session-warning-dialog]', enabled: true, }, }); runtime.register('fslightbox-refresh', initFsLightboxRefresh, { scope: 'global', configPath: 'components.fslightboxRefresh', }); await mountModuleComponentsByPhase('late'); runtime.mountAll(); }; void bootRuntime();