diff --git a/src/ComponentLoader.js b/src/ComponentLoader.js index 0c22ea7..0973499 100644 --- a/src/ComponentLoader.js +++ b/src/ComponentLoader.js @@ -1,6 +1,52 @@ const loadedComponents = []; let componentsConfig = {}; +// Component module resolution (Vite only). +// +// Rollup/Vite cannot statically analyse concatenated dynamic imports, so callers +// register `import.meta.glob()` maps that this loader resolves from. Maps are +// MERGED per scope, so multiple callers can contribute — e.g. FancyAdmin registers +// its own components ('fancyadmin'), while the consuming app registers its own +// ('app') and the built-ins it uses ('builtin'): +// AdtJsComponents.registerModules({ +// app: import.meta.glob('.../app/UI/**/index.js'), +// builtin: import.meta.glob('.../node_modules/adt-js-components/src/{Select2,...}/index.js'), +// }); +const registeredModules = { fancyadmin: {}, app: {}, builtin: {} }; + +const registerModules = (maps) => { + for (const scope of Object.keys(maps)) { + registeredModules[scope] = Object.assign(registeredModules[scope] || {}, maps[scope]); + } +}; + +// Find the loader in a glob map whose key resolves to the requested sub-path. +// Match on a leading-slash boundary so e.g. "Map" never matches "Sitemap". +const resolveFromMap = (map, subPath) => { + if (!map) return null; + const needle = subPath + '/index.js'; + const key = Object.keys(map).find((k) => k === needle || k.endsWith('/' + needle)); + return key ? map[key] : null; +}; + +// Returns a Promise for the given component path, resolved from the +// registered import.meta.glob() maps. +const importComponent = (path) => { + let loader; + if (path.startsWith('~')) { + loader = resolveFromMap(registeredModules.fancyadmin, path.slice(1)); + } else if (path.includes('/')) { + loader = resolveFromMap(registeredModules.app, path); + } else { + loader = resolveFromMap(registeredModules.builtin, path); + } + + if (!loader) { + return Promise.reject(new Error(`adt-js-components: component "${path}" not found in registered modules.`)); + } + return loader(); +}; + const init = (selector, path) => { let bodyDataset = document.querySelector(`body`).dataset.adtJsComponents; @@ -11,19 +57,14 @@ const init = (selector, path) => { } const runPath = (path, selector) => { - if (path.startsWith('~')) { - import('~/src/' + path.slice(1) + '/index.js').then(component => { - component.default.run(componentsConfig[selector] || {}); - }); - } else if (path.includes('/')) { - import('JsComponents/' + path + '/index.js').then(component => { + importComponent(path) + .then(component => { component.default.run(componentsConfig[selector] || {}); + }) + .catch(err => { + // Don't let a single missing/optional component break page init. + console.warn(`adt-js-components: failed to load component "${path}":`, err); }); - } else { - import('adt-js-components/src/' + path + '/index.js').then(component => { - component.default.run(componentsConfig[selector] || {}); - }); - } }; const existingTarget = document.querySelector(`[data-adt-${selector}]`); @@ -135,6 +176,7 @@ export const loadScssModule = (styles) => { export default { init, + registerModules, initCurrencyInput, initDateInput, initGLightbox, diff --git a/src/DateInput/index.js b/src/DateInput/index.js index b898153..0c61b7e 100644 --- a/src/DateInput/index.js +++ b/src/DateInput/index.js @@ -1,11 +1,25 @@ import flatpickr from "flatpickr"; import 'flatpickr/dist/flatpickr.min.css'; -import 'script-loader!timepicker'; +// timepicker is a jQuery plugin attaching to the global $ (provided by the bundler). +// Plain side-effect import works in both webpack and Vite; the "script-loader!" prefix +// was webpack-only and unresolvable in Vite/Rollup. +import 'timepicker'; import 'timepicker/jquery.timepicker.min.css'; const locale = document.querySelector('html').getAttribute('lang'); +// flatpickr locale modules, resolved lazily (Vite). Replaces the webpack-only +// dynamic require('flatpickr/dist/l10n/' + locale + '.js'). +const flatpickrLocales = import.meta.glob('../../../flatpickr/dist/l10n/*.js'); + +async function loadFlatpickrLocale(loc) { + const key = Object.keys(flatpickrLocales).find((k) => k.endsWith(`/l10n/${loc}.js`)); + if (!key) return null; + const mod = await flatpickrLocales[key](); + return mod.default[loc]; +} + function initTime(input, options) { $(input).timepicker({ scrollDefault: 'now', @@ -24,11 +38,13 @@ async function initDate(input, options) { options.locale = locale; } + const localeData = options.locale ? await loadFlatpickrLocale(options.locale) : null; + flatpickr(input, { dateFormat: options.format, // default datetime-local enableTime: options.type === 'datetime' || options.type === 'datetime-local', time_24hr: true, - locale: options.locale ? require('flatpickr/dist/l10n/' + options.locale + '.js').default[options.locale] : null, + locale: localeData, defaultDate: options.value ? new Date(options.value) : null, minDate: options.minDate ? new Date(options.minDate) : null, maxDate: options.maxDate ? new Date(options.maxDate) : null, diff --git a/src/Select2/_init.js b/src/Select2/_init.js new file mode 100644 index 0000000..1484cf2 --- /dev/null +++ b/src/Select2/_init.js @@ -0,0 +1,17 @@ +// The select2 "full" UMD build resolves to its CommonJS branch under Vite/Rollup, +// which exports an uninvoked factory — a side-effect import alone does not attach +// $.fn.select2 (nor $.fn.select2.amd). Invoke the factory against the global jQuery. +// Namespace import avoids the static "default is not exported" error for the UMD file. +import $ from 'jquery'; +import * as select2Full from 'select2/dist/js/select2.full'; + +const initSelect2 = select2Full.default || select2Full; +if (typeof initSelect2 === 'function') { + initSelect2(window, $); +} + +// The i18n locale needs $.fn.select2.amd to already exist. A static import can't +// guarantee that — bundlers order modules by dependency, not source order, so a +// dependency-free locale file would evaluate before this one. A dynamic import runs +// here, after select2 is registered. +import('select2/dist/js/i18n/cs'); diff --git a/src/Select2/index.js b/src/Select2/index.js index 8f4e019..879da0e 100644 --- a/src/Select2/index.js +++ b/src/Select2/index.js @@ -1,7 +1,7 @@ -import 'select2/dist/js/select2.full'; +// Initializes $.fn.select2 against the global jQuery, then loads its i18n locale. +import './_init'; import 'select2/dist/css/select2.min.css'; import 'select2-bootstrap-5-theme/dist/select2-bootstrap-5-theme.css' -import 'select2/dist/js/i18n/cs' function run(options) { const noSelect2Class = '.select-default'; diff --git a/src/SubmitForm/index.js b/src/SubmitForm/index.js index 22b6a35..60296b7 100644 --- a/src/SubmitForm/index.js +++ b/src/SubmitForm/index.js @@ -1,4 +1,4 @@ -const Scrollparent = require("scrollparent"); +import Scrollparent from "scrollparent"; function isList(el) { return el.type === 'checkbox' && el.name.endsWith('[]') || el.type === 'radio';