2 const componentMap = {};
5 * Initialize a component instance on the given dom element.
7 * @param {Element} element
9 function initComponent(name, element) {
10 /** @type {Function<Component>|undefined} **/
11 const componentModel = componentMap[name];
12 if (componentModel === undefined) return;
14 // Create our component instance
15 /** @type {Component} **/
18 instance = new componentModel();
19 instance.$name = name;
20 instance.$el = element;
21 const allRefs = parseRefs(name, element);
22 instance.$refs = allRefs.refs;
23 instance.$manyRefs = allRefs.manyRefs;
24 instance.$opts = parseOpts(name, element);
27 console.error('Failed to create component', e, name, element);
30 // Add to global listing
31 if (typeof components[name] === "undefined") {
32 components[name] = [];
34 components[name].push(instance);
36 // Add to element listing
37 if (typeof element.components === 'undefined') {
38 element.components = {};
40 element.components[name] = instance;
44 * Parse out the element references within the given element
45 * for the given component name.
46 * @param {String} name
47 * @param {Element} element
49 function parseRefs(name, element) {
53 const prefix = `${name}@`
54 const selector = `[refs*="${prefix}"]`;
55 const refElems = [...element.querySelectorAll(selector)];
56 if (element.matches(selector)) {
57 refElems.push(element);
60 for (const el of refElems) {
61 const refNames = el.getAttribute('refs')
63 .filter(str => str.startsWith(prefix))
64 .map(str => str.replace(prefix, ''))
66 for (const ref of refNames) {
68 if (typeof manyRefs[ref] === 'undefined') {
71 manyRefs[ref].push(el);
74 return {refs, manyRefs};
78 * Parse out the element component options.
79 * @param {String} name
80 * @param {Element} element
81 * @return {Object<String, String>}
83 function parseOpts(name, element) {
85 const prefix = `option:${name}:`;
86 for (const {name, value} of element.attributes) {
87 if (name.startsWith(prefix)) {
88 const optName = name.replace(prefix, '');
89 opts[kebabToCamel(optName)] = value || '';
96 * Convert a kebab-case string to camelCase
97 * @param {String} kebab
100 function kebabToCamel(kebab) {
101 const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
102 const words = kebab.split('-');
103 return words[0] + words.slice(1).map(ucFirst).join('');
107 * Initialize all components found within the given element.
108 * @param {Element|Document} parentElement
110 export function init(parentElement = document) {
111 const componentElems = parentElement.querySelectorAll(`[component],[components]`);
113 for (const el of componentElems) {
114 const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean);
115 for (const name of componentNames) {
116 initComponent(name, el);
122 * Register the given component mapping into the component system.
123 * @param {Object<String, ObjectConstructor<Component>>} mapping
125 export function register(mapping) {
126 const keys = Object.keys(mapping);
127 for (const key of keys) {
128 componentMap[camelToKebab(key)] = mapping[key];
130 console.log(componentMap);
134 * Get the first component of the given name.
135 * @param {String} name
136 * @returns {Component|null}
138 export function first(name) {
139 return (components[name] || [null])[0];
143 * Get all the components of the given name.
144 * @param {String} name
145 * @returns {Component[]}
147 export function get(name = '') {
148 return components[name] || [];
151 function camelToKebab(camelStr) {
152 return camelStr.replace(/[A-Z]/g, (str, offset) => (offset > 0 ? '-' : '') + str.toLowerCase());