Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 53 additions & 11 deletions src/ComponentLoader.js
Original file line number Diff line number Diff line change
@@ -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<module> 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;

Expand All @@ -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}]`);
Expand Down Expand Up @@ -135,6 +176,7 @@ export const loadScssModule = (styles) => {

export default {
init,
registerModules,
initCurrencyInput,
initDateInput,
initGLightbox,
Expand Down
20 changes: 18 additions & 2 deletions src/DateInput/index.js
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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,
Expand Down
17 changes: 17 additions & 0 deletions src/Select2/_init.js
Original file line number Diff line number Diff line change
@@ -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');
4 changes: 2 additions & 2 deletions src/Select2/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/SubmitForm/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down