1 import {kebabToCamel, camelToKebab} from "./text";
4 * A mapping of active components keyed by name, with values being arrays of component
5 * instances since there can be multiple components of the same type.
6 * @type {Object<String, Component[]>}
11 * A mapping of component class models, keyed by name.
12 * @type {Object<String, Constructor<Component>>}
14 const componentModelMap = {};
17 * A mapping of active component maps, keyed by the element components are assigned to.
18 * @type {WeakMap<Element, Object<String, Component>>}
20 const elementComponentMap = new WeakMap();
23 * Initialize a component instance on the given dom element.
24 * @param {String} name
25 * @param {Element} element
27 function initComponent(name, element) {
28 /** @type {Function<Component>|undefined} **/
29 const componentModel = componentModelMap[name];
30 if (componentModel === undefined) return;
32 // Create our component instance
33 /** @type {Component} **/
36 instance = new componentModel();
37 instance.$name = name;
38 instance.$el = element;
39 const allRefs = parseRefs(name, element);
40 instance.$refs = allRefs.refs;
41 instance.$manyRefs = allRefs.manyRefs;
42 instance.$opts = parseOpts(name, element);
45 console.error('Failed to create component', e, name, element);
48 // Add to global listing
49 if (typeof components[name] === "undefined") {
50 components[name] = [];
52 components[name].push(instance);
54 // Add to element mapping
55 const elComponents = elementComponentMap.get(element) || {};
56 elComponents[name] = instance;
57 elementComponentMap.set(element, elComponents);
61 * Parse out the element references within the given element
62 * for the given component name.
63 * @param {String} name
64 * @param {Element} element
66 function parseRefs(name, element) {
70 const prefix = `${name}@`
71 const selector = `[refs*="${prefix}"]`;
72 const refElems = [...element.querySelectorAll(selector)];
73 if (element.matches(selector)) {
74 refElems.push(element);
77 for (const el of refElems) {
78 const refNames = el.getAttribute('refs')
80 .filter(str => str.startsWith(prefix))
81 .map(str => str.replace(prefix, ''))
83 for (const ref of refNames) {
85 if (typeof manyRefs[ref] === 'undefined') {
88 manyRefs[ref].push(el);
91 return {refs, manyRefs};
95 * Parse out the element component options.
96 * @param {String} name
97 * @param {Element} element
98 * @return {Object<String, String>}
100 function parseOpts(name, element) {
102 const prefix = `option:${name}:`;
103 for (const {name, value} of element.attributes) {
104 if (name.startsWith(prefix)) {
105 const optName = name.replace(prefix, '');
106 opts[kebabToCamel(optName)] = value || '';
113 * Initialize all components found within the given element.
114 * @param {Element|Document} parentElement
116 export function init(parentElement = document) {
117 const componentElems = parentElement.querySelectorAll(`[component],[components]`);
119 for (const el of componentElems) {
120 const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean);
121 for (const name of componentNames) {
122 initComponent(name, el);
128 * Register the given component mapping into the component system.
129 * @param {Object<String, ObjectConstructor<Component>>} mapping
131 export function register(mapping) {
132 const keys = Object.keys(mapping);
133 for (const key of keys) {
134 componentModelMap[camelToKebab(key)] = mapping[key];
139 * Get the first component of the given name.
140 * @param {String} name
141 * @returns {Component|null}
143 export function first(name) {
144 return (components[name] || [null])[0];
148 * Get all the components of the given name.
149 * @param {String} name
150 * @returns {Component[]}
152 export function get(name) {
153 return components[name] || [];
157 * Get the first component, of the given name, that's assigned to the given element.
158 * @param {Element} element
159 * @param {String} name
160 * @returns {Component|null}
162 export function firstOnElement(element, name) {
163 const elComponents = elementComponentMap.get(element) || {};
164 return elComponents[name] || null;