Added tabs components and refactored stuff to work properly with composed / nested elements etc...
All checks were successful
Build, Test, and Publish (to Private NPM Registry) UI Components Library / publish (push) Successful in 58s
All checks were successful
Build, Test, and Publish (to Private NPM Registry) UI Components Library / publish (push) Successful in 58s
This commit is contained in:
parent
1c2d794a18
commit
d2e99cc280
27 changed files with 1579 additions and 145 deletions
124
src/components/ComposableElement.mjs
Normal file
124
src/components/ComposableElement.mjs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Abstract Base Class for Bridgeman Accessible Web Components
|
||||
* Provides helper methods for easily initializing components, composing complex components, and managing nested custom elements.
|
||||
*/
|
||||
export class ComposableElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to load and parse a JSON configuration ("Data Island") from a script tag within the Light DOM of the component.
|
||||
* The script tag should have a `data-{name}-config` attribute where `{name}` is a unique identifier for the component (e.g., 'tablist' for 'ba-tablist').
|
||||
*
|
||||
* @param {string} name - The suffix of the component (e.g., 'tablist' for 'ba-tablist')
|
||||
* @returns {Object|null} The parsed JSON configuration object, or null if not found
|
||||
*/
|
||||
loadDataIslandConfig(name) {
|
||||
const configScript = this.querySelector(`script[data-${name}-config]`);
|
||||
|
||||
if (!configScript) {
|
||||
console.error(`${name} configuration missing.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const config = JSON.parse(configScript.textContent);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to include the necessary stylesheets and scripts for a component based on its configuration.
|
||||
* This method should be called within the `createTemplateInJS` of a component after loading it's "data island" configurations.
|
||||
*
|
||||
* @param {string} name - The suffix of the component (e.g., 'tablist' for 'ba-tablist')
|
||||
* @param {Object} config - The JSON configuration object that includes the necessary information about styles and scripts to include
|
||||
* @param {ShadowRoot} shadow - The shadow DOM root to append the link and script tags to
|
||||
*/
|
||||
includeStylesheetsAndScripts(name, config, shadow) {
|
||||
if (config.componentsStyleHref) {
|
||||
// Create and append the link tag for the components library CSS (including component specific styles)
|
||||
const componentLibraryStyle = document.createElement('link');
|
||||
componentLibraryStyle.rel = 'stylesheet';
|
||||
componentLibraryStyle.type = 'text/css';
|
||||
componentLibraryStyle.href = config.componentsStyleHref;
|
||||
shadow.appendChild(componentLibraryStyle);
|
||||
}
|
||||
|
||||
// Create additional link tags for any extra CSS files to include
|
||||
const additionalStyles = config[`${name}ExtraStyles`] || [];
|
||||
additionalStyles.forEach(style => {
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = `/css/${style}.css`;
|
||||
shadow.appendChild(link);
|
||||
});
|
||||
|
||||
// Create and append script tags for any extra JavaScript files to include
|
||||
const additionalScripts = config[`${name}ExtraScripts`] || [];
|
||||
additionalScripts.forEach(script => {
|
||||
const scriptElem = document.createElement('script');
|
||||
scriptElem.type = typeof script === 'object' && typeof script.module === 'boolean' && script.module ? 'module' : typeof script === 'string' && script.endsWith('.mjs') ? 'module' : 'application/javascript';
|
||||
scriptElem.src = typeof script === 'object' ? script.script.startsWith('http') ? script.script : `/js/${script.script.endsWith('.mjs') ? script.script : script.script + '.js'}` : script.startsWith('http') ? script : `/js/${script.endsWith('.mjs') ? script : script + '.js'}`;
|
||||
shadow.appendChild(scriptElem);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to initialize a component by loading its configuration and including necessary stylesheets and scripts.
|
||||
* This method should be called at the beginning of the `createTemplateInJS` of a component to set up everything needed for the component to function properly.
|
||||
*
|
||||
* @param {string} name - The suffix of the component (e.g., 'tablist' for 'ba-tablist')
|
||||
* @param {ShadowRoot} shadow - The shadow DOM root to append the link and script tags to
|
||||
* @param {{ includeStylesAndScripts: boolean, stylesAndScriptsNameOverride?: string, subComponents?: Array<{ name: string, configKey: string, slotNames: Array<string> }> }} options - Options for initializing the component
|
||||
* @returns {Object|null} The parsed JSON configuration object, or null if not found
|
||||
*/
|
||||
initializeComponent(name, shadow, { includeStylesAndScripts = true, stylesAndScriptsNameOverride, subComponents = [] } = {}) {
|
||||
const config = this.loadDataIslandConfig(name);
|
||||
if(config == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (includeStylesAndScripts) {
|
||||
this.includeStylesheetsAndScripts(typeof stylesAndScriptsNameOverride !== 'undefined' ? stylesAndScriptsNameOverride : name, config, shadow);
|
||||
}
|
||||
|
||||
subComponents.forEach(subComp => {
|
||||
const subCompElem = this.createSubComponent(subComp.name, config[subComp.configKey], subComp.slotNames);
|
||||
shadow.appendChild(subCompElem);
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a sub-component, attaches its configuration, and forwards requested slots.
|
||||
*
|
||||
* @param {string} name - The suffix of the component (e.g., 'tablist' for 'ba-tablist')
|
||||
* @param {Object} config - The JSON configuration object to pass to the sub-component
|
||||
* @param {Array<string>} slotNames - An array of exact slot names to forward down
|
||||
* @returns {HTMLElement} The fully constructed sub-component ready to be appended
|
||||
*/
|
||||
createSubComponent(name, config, slotNames = []) {
|
||||
const customElem = document.createElement(`ba-${name}`);
|
||||
|
||||
// Attach the Configuration Data Island
|
||||
const customElemConfig = document.createElement('script');
|
||||
customElemConfig.type = 'application/json';
|
||||
customElemConfig.setAttribute(`data-${name}-config`, '');
|
||||
customElemConfig.textContent = JSON.stringify(config);
|
||||
customElem.appendChild(customElemConfig);
|
||||
|
||||
// Forward the explicit slots
|
||||
slotNames.forEach(slotName => {
|
||||
const slotForwarder = document.createElement('slot');
|
||||
slotForwarder.name = slotName;
|
||||
slotForwarder.slot = slotName;
|
||||
customElem.appendChild(slotForwarder);
|
||||
});
|
||||
|
||||
// Return the element so the child class can append it exactly where it belongs in its specific DOM structure
|
||||
return customElem;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue