@zyrab/domo 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ import EventsServer from "./events.server.js";
2
+
3
+ /**
4
+ * @class ChildrenServer
5
+ * @extends EventsServer
6
+ */
7
+ class ChildrenServer extends EventsServer {
8
+ child(children = []) {
9
+ const flattenedChildren = Array.isArray(children) ? children.flat() : [children];
10
+ flattenedChildren.forEach((child) => {
11
+ // Store reference. If it's a Domo instance, the Builder will call .build() on it later.
12
+ this.element._child.push(child);
13
+ });
14
+ return this;
15
+ }
16
+
17
+ append(children = []) {
18
+ return this.child(children);
19
+ }
20
+
21
+ appendTo(target) {
22
+ const targetEl = target && target._isDomo ? target.element : target;
23
+ if (targetEl && Array.isArray(targetEl._child)) {
24
+ targetEl._child.push(this);
25
+ }
26
+ return this;
27
+ }
28
+
29
+ parent(target) {
30
+ return this.appendTo(target);
31
+ }
32
+
33
+ clear() {
34
+ this.element._child = [];
35
+ return this;
36
+ }
37
+
38
+ replace(child, newChild) {
39
+ const idx = this.element._child.indexOf(child);
40
+ if (idx !== -1) {
41
+ this.element._child[idx] = newChild;
42
+ }
43
+ return this;
44
+ }
45
+
46
+ show(visible = true) {
47
+ if (!visible) {
48
+ this.element._attr["hidden"] = true;
49
+ this.element._css["display"] = "none";
50
+ } else {
51
+ delete this.element._attr["hidden"];
52
+ delete this.element._css["display"];
53
+ }
54
+ return this;
55
+ }
56
+
57
+ if(condition) {
58
+ if (!condition) {
59
+ return new this.constructor("if")
60
+ .attr({ hidden: true })
61
+ .data({ if: this.element._tag.toLowerCase() });
62
+ }
63
+ return this;
64
+ }
65
+ }
66
+
67
+ export default ChildrenServer;
@@ -0,0 +1,42 @@
1
+ import PropertiesServer from "./properties.server.js";
2
+
3
+ /**
4
+ * @class ClassesServer
5
+ * @extends PropertiesServer
6
+ */
7
+ class ClassesServer extends PropertiesServer {
8
+ /**
9
+ * Normalizes various inputs into a clean array of class names.
10
+ * @private
11
+ */
12
+ _parseClassList(input) {
13
+ return Array.isArray(input) ? input.filter(Boolean) : String(input).split(" ").filter(Boolean);
14
+ }
15
+
16
+ cls(classes) {
17
+ if (!classes) return this;
18
+ const clsList = this._parseClassList(classes);
19
+ this.element._cls.push(...clsList);
20
+ return this;
21
+ }
22
+
23
+ rmvCls(classes) {
24
+ if (classes) {
25
+ const clsList = this._parseClassList(classes);
26
+ this.element._cls = this.element._cls.filter((c) => !clsList.includes(c));
27
+ }
28
+ return this;
29
+ }
30
+
31
+ tgglCls(className, force) {
32
+ const active = typeof force === "boolean" ? force : !this.element._cls.includes(className);
33
+ if (active) {
34
+ if (!this.element._cls.includes(className)) this.element._cls.push(className);
35
+ } else {
36
+ this.element._cls = this.element._cls.filter((c) => c !== className);
37
+ }
38
+ return this;
39
+ }
40
+ }
41
+
42
+ export default ClassesServer;
@@ -0,0 +1,24 @@
1
+ import BuilderServer from "./builder.server.js";
2
+
3
+ /**
4
+ * @class DomoServer
5
+ * @extends BuilderServer
6
+ * @description Main Domo class for the Server (SSG) environment.
7
+ */
8
+ class DomoServer extends BuilderServer {
9
+ constructor(el = "div") {
10
+ super(el);
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Factory function for Server Domo elements.
16
+ * @param {string} [el="div"]
17
+ * @returns {DomoServer}
18
+ */
19
+ function Domo(el = "div") {
20
+ return new DomoServer(el);
21
+ }
22
+
23
+ export { DomoServer };
24
+ export default Domo;
@@ -0,0 +1,79 @@
1
+ import ClassesServer from "./classes.server.js";
2
+
3
+ /**
4
+ * @class EventsServer
5
+ * @extends ClassesServer
6
+ */
7
+ class EventsServer extends ClassesServer {
8
+ /**
9
+ * Captures event metadata for the SSG.
10
+ * @param {string|object} eventMapOrName
11
+ * @param {Function} [callback]
12
+ * @param {object} [meta={}] - Injected by plugin: { _source, _name, ssg: boolean }
13
+ */
14
+ on(eventMapOrName, callback, meta = {}) {
15
+ // If user passed options (meta), check if ssg is explicitly disabled
16
+ if (meta.ssg === false) return this;
17
+
18
+ const id = this._getOrSetId();
19
+
20
+ if (typeof eventMapOrName === "object" && eventMapOrName !== null) {
21
+ // In object mode: .on({ click: fn }, { _source: '...', _name: '...' })
22
+ // callback actually acts as the 'meta' argument here
23
+ const metadata = callback || {};
24
+ for (const [event, value] of Object.entries(eventMapOrName)) {
25
+ const handler = Array.isArray(value) ? value[0] : value;
26
+ this._pushEvent(id, "direct", event, handler, null, metadata);
27
+ }
28
+ } else if (typeof callback === "function") {
29
+ // In single mode: .on('click', fn, { _source: '...', _name: '...' })
30
+ this._pushEvent(id, "direct", eventMapOrName, callback, null, meta);
31
+ }
32
+ return this;
33
+ }
34
+
35
+ onClosest(event, selectors = {}, meta = {}) {
36
+ if (meta.ssg === false) return this;
37
+ const id = this._getOrSetId();
38
+ for (const [selector, handler] of Object.entries(selectors)) {
39
+ this._pushEvent(id, "closest", event, handler, selector, meta);
40
+ }
41
+ return this;
42
+ }
43
+
44
+ onMatch(event, selectors = {}, meta = {}) {
45
+ if (meta.ssg === false) return this;
46
+ const id = this._getOrSetId();
47
+ for (const [selector, handler] of Object.entries(selectors)) {
48
+ this._pushEvent(id, "match", event, handler, selector, meta);
49
+ }
50
+ return this;
51
+ }
52
+
53
+ /**
54
+ * Internal helper to push event metadata.
55
+ * @private
56
+ */
57
+ _pushEvent(id, type, event, handler, selector = null, meta = {}) {
58
+ const evArr = (this.element._events ??= []);
59
+ let existing = evArr.find((e) => e.id === id && e.event === event);
60
+
61
+ if (!existing) {
62
+ existing = { id, event, handlers: [] };
63
+ evArr.push(existing);
64
+ }
65
+
66
+ // Capture path and name from the 'meta' object stamped by the plugin
67
+ existing.handlers.push({
68
+ type,
69
+ name: meta._name || handler.name || "anonymous",
70
+ handler,
71
+ path: meta._source || null, // STAMPED PATH
72
+ ...(selector && { selector }),
73
+ });
74
+
75
+ // Tag as island so SSG knows to include domo.client.js
76
+ }
77
+ }
78
+
79
+ export default EventsServer;
@@ -0,0 +1,81 @@
1
+ import BaseServer from "./base.server.js";
2
+
3
+ /**
4
+ * @class PropertiesServer
5
+ * @extends BaseServer
6
+ */
7
+ class PropertiesServer extends BaseServer {
8
+ /**
9
+ * Sets a metadata property.
10
+ * @protected
11
+ * @param {string} key
12
+ * @param {*} val
13
+ * @param {string} [type="_attr"]
14
+ * @returns {this}
15
+ */
16
+ _set(key, val, type = "_attr") {
17
+ if (val === undefined) return this;
18
+ if (type === "txt") {
19
+ this.element._child.push(String(val));
20
+ } else {
21
+ this.element[type][key] = val;
22
+ }
23
+ return this;
24
+ }
25
+
26
+ id(id) {
27
+ return this._set("id", id);
28
+ }
29
+
30
+ val(value) {
31
+ return this._set("value", value);
32
+ }
33
+
34
+ txt(text) {
35
+ return this._set("textContent", text, "txt");
36
+ }
37
+
38
+ attr(attributes = {}) {
39
+ for (const [key, value] of Object.entries(attributes)) {
40
+ this.element._attr[key] = value;
41
+ }
42
+ return this;
43
+ }
44
+
45
+ tgglAttr(attrName, force) {
46
+ if (typeof force === "boolean") {
47
+ if (force) this.element._attr[attrName] = true;
48
+ else delete this.element._attr[attrName];
49
+ } else {
50
+ if (this.element._attr[attrName]) delete this.element._attr[attrName];
51
+ else this.element._attr[attrName] = true;
52
+ }
53
+ return this;
54
+ }
55
+
56
+ data(data = {}) {
57
+ Object.entries(data).forEach(([key, val]) => {
58
+ this.element._data[key] = val;
59
+ });
60
+ return this;
61
+ }
62
+
63
+ css(styles = {}) {
64
+ Object.assign(this.element._css, styles);
65
+ return this;
66
+ }
67
+
68
+ /**
69
+ * Serializes state object into data-domo-state attribute.
70
+ * @param {object} obj
71
+ * @returns {this}
72
+ */
73
+ state(state = {}) {
74
+ Object.entries(state).forEach(([key, val]) => {
75
+ this.element._state[key] = val;
76
+ });
77
+ return this;
78
+ }
79
+ }
80
+
81
+ export default PropertiesServer;
package/src/base.js DELETED
@@ -1,80 +0,0 @@
1
- // Base.js
2
- const isServer = typeof document === "undefined";
3
-
4
- /**
5
- * @class Base
6
- * @description The foundational class for creating and managing DOM or virtual elements.
7
- * Handles the basic setup and provides access to the underlying element.
8
- */
9
- class Base {
10
- /**
11
- * @private
12
- * @property {boolean} _virtual - True if running in a server-side (virtual DOM) environment.
13
- */
14
- _virtual;
15
-
16
- /**
17
- * @property {HTMLElement|object} element - The actual DOM element or its virtual representation.
18
- */
19
- element;
20
-
21
- /**
22
- * Creates an instance of Base.
23
- * @param {string} [el="div"] - The HTML tag name for the element to create (e.g., 'div', 'span', 'button').
24
- */
25
- constructor(el = "div") {
26
- this._virtual = isServer;
27
- this.element = this._virtual ? this._vel(el) : this.el(el);
28
- }
29
-
30
- /**
31
- * Creates a real HTML DOM element.
32
- * @param {string} el - The HTML tag name (e.g., 'div', 'p').
33
- * @returns {HTMLElement} The created DOM element.
34
- * @example
35
- * // Internally used:
36
- * // this.el('div'); // Creates a <div> element
37
- */
38
- el(el) {
39
- return document.createElement(String(el || "div").toLowerCase());
40
- }
41
-
42
- /**
43
- * Creates a simple JavaScript object representing a virtual element for server-side rendering.
44
- * @private
45
- * @param {string} el - The HTML tag name.
46
- * @returns {object} A plain object structure representing the virtual element.
47
- * @example
48
- * // Internally used for server-side:
49
- * // this._vel('div'); // Returns { _tag: 'div', _cls: [], ... }
50
- */
51
- _vel(el) {
52
- return {
53
- _tag: el,
54
- _cls: [],
55
- _data: {},
56
- _attr: {},
57
- _css: {},
58
- _child: [],
59
- _events: [],
60
- };
61
- }
62
-
63
- /**
64
- * In browser context, passes the actual DOM element to the callback.
65
- *
66
- * @param {function(HTMLElement|string): void} callback -
67
- * Receives the actual DOM element (in browser).
68
- *
69
- * @returns {this} The current Domo instance for chaining.
70
-
71
- **/
72
- ref(callback) {
73
- if (this._virtual) return this;
74
- if (typeof callback === "function") callback(this.element);
75
-
76
- return this;
77
- }
78
- }
79
-
80
- export default Base;
package/src/builder.js DELETED
@@ -1,46 +0,0 @@
1
- class Builder {
2
- /**
3
- * Builds and returns the final DOM element or its HTML string representation.
4
- * If running in a browser environment, it returns the actual HTML element.
5
- * If running in a server-side (virtual DOM) environment, it generates an HTML string.
6
- * @returns {HTMLElement|string} The constructed DOM element or an HTML string.
7
- * @example
8
- * // In a browser:
9
- * const myDiv = Domo('div').txt('Hello').build();
10
- * document.body.appendChild(myDiv); // myDiv is an actual <div> element
11
- *
12
- * @example
13
- * // In a server-side environment:
14
- * // const htmlString = Domo('p').txt('Server-rendered content').build();
15
- * // console.log(htmlString); // Outputs: "<p>Server-rendered content</p>"
16
- */
17
- build() {
18
- if (!this._virtual) return this.element; // If not virtual, return the actual DOM element
19
-
20
- const tag = this.element._tag;
21
- const cls = this.element._cls.join(" ");
22
- const toKebab = (s) => s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
23
- const css = Object.entries(this.element._css)
24
- .map(([k, v]) => `${toKebab(k)}:${v}`)
25
- .join(";");
26
-
27
- const attrs = Object.entries(this.element._attr).map(([k, v]) =>
28
- v === true ? k : `${k}="${String(v).replace(/"/g, "&quot;")}"`
29
- );
30
-
31
- const data = Object.entries(this.element._data).map(([k, v]) => `data-${k}="${String(v).replace(/"/g, "&quot;")}"`);
32
-
33
- if (cls) attrs.push(`class="${cls}"`);
34
- if (css) attrs.push(`style="${css}"`);
35
- const escapeHTML = (str) => String(str).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
36
-
37
- const attrStr = attrs.concat(data).join(" ");
38
- const children = this.element._child
39
- .map((c) => (typeof c === "string" ? escapeHTML(c) : c.build?.() || ""))
40
- .join("");
41
-
42
- return `<${tag}${attrStr ? " " + attrStr : ""}>${children}</${tag}>`;
43
- }
44
- }
45
-
46
- export default Builder;
package/src/children.js DELETED
@@ -1,184 +0,0 @@
1
- import { DomoClass } from "./domo.js";
2
-
3
- class Children {
4
- /**
5
- * Internally handles various types of input to ensure they become valid DOM Nodes
6
- * or a string representation for virtual DOM.
7
- * @private
8
- * @param {*} element - The input to process (Domo instance, Node, string, number).
9
- * @returns {Node|string|Domo} A DOM Node (HTMLElement, TextNode, DocumentFragment),
10
- * a string for virtual children, or a Domo instance for virtual children.
11
- */
12
- _handleElementInstance(element) {
13
- if (element instanceof DomoClass) return element.build();
14
-
15
- if (element instanceof DocumentFragment) return element;
16
- if (element instanceof Node) return element;
17
- if (typeof element === "string" || typeof element === "number") {
18
- return document.createTextNode(element); // Convert strings/numbers to text nodes
19
- }
20
- return document.createTextNode(`⚠ Invalid child: ${String(element)}`); // Fallback for invalid input
21
- }
22
-
23
- /**
24
- * Appends one or more children to the element.
25
- * This method accepts a wide range of child types, including other Domo instances,
26
- * native DOM nodes, DocumentFragments, and simple text/numbers, even in nested arrays.
27
- * @param {(Node|string|number|Domo|DocumentFragment|Array<any>)[]} [children=[]] - An array of children to append. Can contain mixed types and nested arrays.
28
- * @returns {this} The current Domo instance for chaining.
29
- * @example
30
- * Domo('div').child([
31
- * Domo('span').txt('Hello'),
32
- * document.createElement('br'),
33
- * 'World!',
34
- * [Domo('em').txt('Nested')]
35
- * ]);
36
- */
37
- child(children = []) {
38
- const flattenedChildren = children.flat(); // Handle nested arrays of children
39
- flattenedChildren.forEach((child) => {
40
- if (this._virtual) {
41
- // In virtual mode, store Domo instances or their string representation
42
- // @ts-ignore - Ignore potential 'Domo is not defined' for JSDoc tool
43
- this.element._child.push(child instanceof DomoClass ? child : String(child));
44
- } else this.element.appendChild(this._handleElementInstance(child));
45
- });
46
- return this;
47
- }
48
-
49
- /**
50
- * Appends one or more children to the element.
51
- * This is an alias for the `child` method, offering a more common naming convention.
52
- * @param {(Node|string|number|Domo|DocumentFragment|Array<any>)[]} [children=[]] - An array of children to append. Can contain mixed types and nested arrays.
53
- * @returns {this} The current Domo instance for chaining.
54
- * @example
55
- * Domo('ul').append([
56
- * Domo('li').txt('Item 1'),
57
- * Domo('li').txt('Item 2')
58
- * ]);
59
- */
60
- append(children = []) {
61
- return this.child(children);
62
- }
63
-
64
- /**
65
- * Appends the current Domo element to a specified target element in the DOM.
66
- * @param {HTMLElement|Domo|DocumentFragment} target - The element to which the current Domo element will be appended. Can be a native HTMLElement, another Domo instance, or a DocumentFragment.
67
- * @returns {this} The current Domo instance for chaining.
68
- * @example
69
- * const myDiv = Domo('div').txt('My Content');
70
- * myDiv.appendTo(document.body); // Appends the div to the body
71
- *
72
- * const container = Domo('main');
73
- * myDiv.appendTo(container); // Appends to another Domo instance
74
- */
75
- appendTo(target) {
76
- // @ts-ignore - Ignore potential 'Domo is not defined' for JSDoc tool
77
- const targetNode = target instanceof DomoClass ? target.element : target; // Get native element if Domo instance
78
- if (targetNode instanceof Node) {
79
- // Ensure it's a DOM Node
80
- targetNode.appendChild(this.element);
81
- }
82
- return this;
83
- }
84
-
85
- /**
86
- * Appends the current Domo element to a specified target parent.
87
- * This is an alias for the `appendTo` method.
88
- * @param {HTMLElement|Domo|DocumentFragment} target - The parent element to which the current Domo element will be appended.
89
- * @returns {this} The current Domo instance for chaining.
90
- * @example
91
- * Domo('img').attr({ src: 'image.jpg' }).parent(document.getElementById('image-gallery'));
92
- */
93
- parent(target) {
94
- return this.appendTo(target);
95
- }
96
-
97
- /**
98
- * Removes all child nodes from the element, effectively emptying its content.
99
- * @returns {this} The current Domo instance for chaining.
100
- * @example
101
- * Domo('ul').child([Domo('li'), Domo('li')]).clear(); // Removes both <li>s
102
- */
103
- clear() {
104
- this.element.replaceChildren();
105
- return this;
106
- }
107
-
108
- /**
109
- * Replaces an existing child element with a new one, or replaces the Domo element itself if it's the target.
110
- * @param {Node} child - The old child element (or the Domo element itself) to be replaced.
111
- * @param {(Node|string|number|Domo|DocumentFragment|Array<any>)} newChild - The new element (or content) to insert. Can be a Domo instance, native Node, string, or number.
112
- * @returns {this} The current Domo instance for chaining (or the new Domo instance if the original element was replaced).
113
- * @example
114
- * const oldParagraph = document.getElementById('old-p');
115
- * const newDiv = Domo('div').txt('New Content');
116
- * Domo('body').replace(oldParagraph, newDiv);
117
- *
118
- * // To replace the Domo element itself:
119
- * // const myButton = Domo('button').id('myBtn');
120
- * // myButton.replace(myButton.element, Domo('a').txt('Link'));
121
- */
122
- replace(child, newChild) {
123
- const resolvedNew = this._handleElementInstance(newChild);
124
- const resolvedOld = child; // Child is expected to be a Node already based on context
125
-
126
- if (resolvedOld === this.element) {
127
- this.element.replaceWith(resolvedNew);
128
- this.element = resolvedNew; // Update the internal reference to the new element
129
- } else if (this.element.contains(resolvedOld)) {
130
- resolvedOld.replaceWith(resolvedNew);
131
- }
132
-
133
- return this;
134
- }
135
-
136
- /**
137
- * Shows or hides the element by controlling its CSS `display` property.
138
- * @param {boolean} [visible=true] - If `true`, the element is shown. If `false`, it's hidden.
139
- * @param {string} [displayValue='block'] - The CSS `display` value to use when showing the element (e.g., 'block', 'flex', 'inline-block').
140
- * @returns {this} The current Domo instance for chaining.
141
- * @example
142
- * Domo('div').show(false); // Hides the div
143
- * Domo('span').show(true, 'inline'); // Shows the span as inline
144
- */
145
- show(visible = true, displayValue = "block") {
146
- this.element.style.display = visible ? displayValue : "none";
147
- return this;
148
- }
149
-
150
- /**
151
- * Conditionally returns the current Domo instance or a hidden placeholder element.
152
- * This allows for conditional rendering based on a boolean condition. If the condition is false,
153
- * a hidden 'if' element is returned, ensuring method chaining can continue without rendering
154
- * the actual element.
155
- * @param {boolean} condition - The condition to evaluate.
156
- * @returns {this|Domo} The current Domo instance if the condition is true,
157
- * or a new hidden Domo instance representing the 'if' placeholder if false.
158
- * @example
159
- * const dataExists = true;
160
- * Domo('div')
161
- * .if(dataExists)
162
- * .txt('Data is here!'); // This will render
163
- *
164
- * const userIsAdmin = false;
165
- * Domo('button')
166
- * .if(userIsAdmin)
167
- * .txt('Admin Panel'); // This button will not be rendered (a hidden placeholder is returned)
168
- */
169
- if(condition) {
170
- // @ts-ignore - Ignore potential 'Domo is not defined' for JSDoc tool
171
- if (!condition) {
172
- return new DomoClass("if")
173
- .attr({
174
- hidden: true,
175
- })
176
- .data({
177
- if: !this._virtual ? this.element.tagName.toLowerCase() : this.element._tag.toLowerCase(),
178
- });
179
- }
180
- return this;
181
- }
182
- }
183
-
184
- export default Children;
package/src/classes.js DELETED
@@ -1,62 +0,0 @@
1
- class Classes {
2
- /**
3
- * Normalizes various inputs into a clean array of class names.
4
- * Handles strings (space-separated) and arrays of strings.
5
- * @private
6
- * @param {string|string[]} input - A string of space-separated class names, or an array of class names.
7
- * @returns {string[]} An array of cleaned class names.
8
- */
9
- _parseClassList(input) {
10
- return Array.isArray(input) ? input.filter(Boolean) : String(input).split(" ").filter(Boolean);
11
- }
12
-
13
- /**
14
- * Adds one or more CSS classes to the element.
15
- * @param {string|string[]} classes - A single class name, a string of space-separated class names, or an array of class names.
16
- * @returns {this} The current Domo instance for chaining.
17
- * @example
18
- * Domo('div').cls('active highlight');
19
- * Domo('button').cls(['btn', 'btn-primary']);
20
- */
21
- cls(classes) {
22
- if (!classes) return this;
23
- const clsList = this._parseClassList(classes);
24
- if (this._virtual) this.element._cls.push(...clsList);
25
- else this.element.classList.add(...clsList);
26
- return this;
27
- }
28
-
29
- /**
30
- * Removes one or more CSS classes from the element.
31
- * @param {string|string[]} classes - A single class name, a string of space-separated class names, or an array of class names.
32
- * @returns {this} The current Domo instance for chaining.
33
- * @example
34
- * Domo('div').rmvCls('active');
35
- * Domo('p').rmvCls(['old-style', 'unused']);
36
- */
37
- rmvCls(classes) {
38
- if (classes) {
39
- this.element.classList.remove(...this._parseClassList(classes));
40
- }
41
- return this;
42
- }
43
-
44
- /**
45
- * Toggles a single CSS class on or off for the element.
46
- * @param {string} className - The class name to toggle.
47
- * @param {boolean} [force] - If `true`, the class is added. If `false`, it's removed.
48
- * If omitted, the class's presence is flipped.
49
- * @returns {this} The current Domo instance for chaining.
50
- * @example
51
- * // Toggle 'active' class:
52
- * Domo('button').tgglCls('active');
53
- * // Force add 'highlight' class:
54
- * Domo('div').tgglCls('highlight', true);
55
- */
56
- tgglCls(className, force) {
57
- this.element.classList.toggle(className, force);
58
- return this;
59
- }
60
- }
61
-
62
- export default Classes;