@zyrab/domo 1.1.1 → 1.2.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.
package/src/events.js ADDED
@@ -0,0 +1,165 @@
1
+ class Events {
2
+ _pushVirtualHandler(id, event, entry) {
3
+ const evArr = (this.element._events ??= []);
4
+ const existing = evArr.find((e) => e.id === id && e.event === event);
5
+ if (existing) {
6
+ existing.handlers.push(entry);
7
+ } else {
8
+ evArr.push({ id, event, handlers: [entry] });
9
+ }
10
+ }
11
+
12
+ /**
13
+ * Adds one or multiple event listeners to the element.
14
+ *
15
+ * This method supports two main ways to add events:
16
+ * 1. A single event: `Domo(...).on('click', myClickHandler)`
17
+ * 2. Multiple events using an object: `Domo(...).on({ click: myClickHandler, mouseenter: [anotherHandler, { once: true }] })`
18
+ *
19
+ * @param {string | Record<string, Function | [Function, AddEventListenerOptions]>} eventMapOrName -
20
+ * Either the name of a single event (e.g., 'click'), or an object where keys are event names
21
+ * and values are event handler functions (or arrays containing the handler and options).
22
+ * @param {EventListenerOrEventListenerObject} [callback] - The function to call when the event occurs. Required if `eventMapOrName` is a string.
23
+ * @param {AddEventListenerOptions} [options={}] - An object specifying characteristics about the event listener (e.g., `{ once: true, capture: true }`). Required if `eventMapOrName` is a string.
24
+ * @returns {this} The current Domo instance for chaining.
25
+ * @example
26
+ * // Single event:
27
+ * Domo('button').on('click', () => console.log('Button clicked!'));
28
+ *
29
+ * @example
30
+ * // Multiple events with options:
31
+ * Domo('input').on({
32
+ * focus: () => console.log('Input focused'),
33
+ * blur: [() => console.log('Input blurred'), { once: true }]
34
+ * });
35
+ */
36
+
37
+ on(eventMapOrName, callback, options = {}) {
38
+ if (this._virtual && eventMapOrName !== null) {
39
+ if (options.ssg === false) return this;
40
+ const elId = this.element._attr?.id;
41
+ if (!elId) throw new Error(`[Domo.on] .id() must be called before .on(...) in SSG mode`);
42
+
43
+ if (typeof eventMapOrName === "object") {
44
+ for (const [event, value] of Object.entries(eventMapOrName)) {
45
+ const handler = Array.isArray(value) ? value[0] : value;
46
+ this._pushVirtualHandler(elId, event, { type: "direct", handler });
47
+ }
48
+ } else {
49
+ this._pushVirtualHandler(elId, eventMapOrName, { type: "direct", handler: callback });
50
+ }
51
+
52
+ return this;
53
+ }
54
+ if (typeof eventMapOrName === "object" && eventMapOrName !== null) {
55
+ for (const [event, value] of Object.entries(eventMapOrName)) {
56
+ if (typeof value === "function") {
57
+ this.element.addEventListener(event, value);
58
+ } else if (Array.isArray(value)) {
59
+ const [cb, opts] = value;
60
+ this.element.addEventListener(event, cb, opts);
61
+ }
62
+ }
63
+ } else {
64
+ this.element.addEventListener(eventMapOrName, callback, options);
65
+ }
66
+ return this;
67
+ }
68
+
69
+ /**
70
+ * Internal helper for `onClosest`. Finds the closest ancestor matching a selector and calls its handler.
71
+ * @private
72
+ * @param {Event} e - The native event object.
73
+ * @param {Record<string, (e: Event, target: Element) => void>} map - An object mapping CSS selectors to handler functions.
74
+ */
75
+ _handleClosest(e, map) {
76
+ for (const [selector, handler] of Object.entries(map)) {
77
+ const match = e.target.closest(selector);
78
+ if (match) handler(e, match);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Attaches an event listener that triggers when an event occurs on an element,
84
+ * but the handler is only called if the event target or its closest ancestor matches a given selector.
85
+ * This is useful for handling events on dynamically added elements or multiple elements with similar structure.
86
+ * @param {string} event - The name of the event to listen for (e.g., 'click', 'mouseover').
87
+ * @param {Record<string, (e: Event, target: Element) => void>} selectors - An object where keys are CSS selectors and values are handler functions.
88
+ * The handler receives the event object and the matching element (which could be the target or an ancestor).
89
+ * @param {AddEventListenerOptions} [options] - Options for `addEventListener`.
90
+ * @returns {this} The current Domo instance for chaining.
91
+ * @example
92
+ * Domo('ul').onClosest('click', {
93
+ * 'li.item': (e, li) => console.log('Clicked list item:', li.textContent),
94
+ * 'button.delete': (e, btn) => console.log('Delete button clicked for:', btn.closest('li').id)
95
+ * });
96
+ */
97
+ onClosest(event, selectors = {}, options = {}) {
98
+ if (this._virtual && event !== null) {
99
+ if (options.ssg === false) return this;
100
+ const elId = this.element._attr?.id;
101
+ if (!elId) throw new Error(`[Domo.onClosest] .id() must be called before .onClosest(...) in SSG mode`);
102
+
103
+ for (const [selector, handler] of Object.entries(selectors)) {
104
+ this._pushVirtualHandler(elId, event, {
105
+ type: "closest",
106
+ selector,
107
+ handler,
108
+ });
109
+ }
110
+
111
+ return this;
112
+ }
113
+
114
+ return this.on(event, (e) => this._handleClosest(e, selectors), options);
115
+ }
116
+
117
+ /**
118
+ * Internal helper for `onMatch`. Checks if the event target matches a selector and calls its handler.
119
+ * @private
120
+ * @param {Event} e - The native event object.
121
+ * @param {Record<string, (e: Event, target: Element) => void>} map - An object mapping CSS selectors to handler functions.
122
+ */
123
+ _handleMatches(e, map) {
124
+ for (const [selector, handler] of Object.entries(map)) {
125
+ if (e.target.matches(selector)) handler(e, e.target);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Attaches an event listener that triggers a handler only if the event's direct target element matches a specific CSS selector.
131
+ * This is useful for handling events on multiple elements where you only care about the exact element clicked.
132
+ * @param {string} event - The name of the event to listen for (e.g., 'click', 'keyup').
133
+ * @param {Record<string, (e: Event, target: Element) => void>} selectors - An object where keys are CSS selectors and values are handler functions.
134
+ * The handler receives the event object and the exact matching element.
135
+ * @param {AddEventListenerOptions} [options] - Options for `addEventListener`.
136
+ * @returns {this} The current Domo instance for chaining.
137
+ * @example
138
+ * // Only logs if a button with class 'action-btn' is *directly* clicked
139
+ * Domo('div').onMatch('click', {
140
+ * 'button.action-btn': (e, btn) => console.log('Action button clicked:', btn.textContent),
141
+ * 'input[type="checkbox"]': (e, checkbox) => console.log('Checkbox toggled:', checkbox.checked)
142
+ * });
143
+ */
144
+ onMatch(event, selectors = {}, options = {}) {
145
+ if (this._virtual && event !== null) {
146
+ if (options.ssg === false) return this;
147
+ const elId = this.element._attr?.id;
148
+ if (!elId) throw new Error(`[Domo.onMatch] .id() must be called before .onMatch(...) in SSG mode`);
149
+
150
+ for (const [selector, handler] of Object.entries(selectors)) {
151
+ this._pushVirtualHandler(elId, event, {
152
+ type: "match",
153
+ selector,
154
+ handler,
155
+ });
156
+ }
157
+
158
+ return this;
159
+ }
160
+
161
+ return this.on(event, (e) => this._handleMatches(e, selectors), options);
162
+ }
163
+ }
164
+
165
+ export default Events;
@@ -0,0 +1,162 @@
1
+ class Properties {
2
+ /**
3
+ * Sets a property on the element or its virtual representation.
4
+ * This is a core internal method used by id(), val(), and txt().
5
+ * @private
6
+ * @param {string} key - The property name (e.g., "id", "value", "textContent").
7
+ * @param {*} val - The value to set for the property.
8
+ * @param {string} [type="_attr"] - Internal type for virtual DOM handling (e.g., "txt" for textContent).
9
+ * @returns {this} The current Domo instance for chaining.
10
+ */
11
+ _set(key, val, type = "_attr") {
12
+ if (val === undefined) return this;
13
+ if (this._isLockedAttr(key)) return this;
14
+ if (this._virtual) {
15
+ if (type === "txt") {
16
+ this.element._child.push(String(val));
17
+ } else {
18
+ this.element[type] ??= {};
19
+ this.element[type][key] = val;
20
+ }
21
+ } else {
22
+ this.element[key] = val;
23
+ }
24
+
25
+ return this;
26
+ }
27
+
28
+ _isLockedAttr(key) {
29
+ return key === "id" && this._refLocked;
30
+ }
31
+
32
+ /**
33
+ * Sets the 'id' attribute of the element.
34
+ * @param {string} id - The ID string to set.
35
+ * @returns {this} The current Domo instance for chaining.
36
+ * @example
37
+ * Domo('div').id('myUniqueId');
38
+ */
39
+ id(id) {
40
+ return this._set("id", id);
41
+ }
42
+
43
+ /**
44
+ * Sets the 'value' property of the element. Useful for input fields, text areas, or select elements.
45
+ * @param {string} value - The value to set.
46
+ * @returns {this} The current Domo instance for chaining.
47
+ * @example
48
+ * Domo('input').val('Initial text');
49
+ */
50
+ val(value) {
51
+ return this._set("value", value);
52
+ }
53
+
54
+ /**
55
+ * Sets the visible text content of the element. This replaces any existing child text or elements.
56
+ * @param {string} text - The text string to display.
57
+ * @returns {this} The current Domo instance for chaining.
58
+ * @example
59
+ * Domo('p').txt('This is some paragraph text.');
60
+ */
61
+ txt(text) {
62
+ return this._set("textContent", text, "txt");
63
+ }
64
+
65
+ /**
66
+ * Sets multiple HTML attributes on the element.
67
+ * Boolean `true` values will set the attribute without a value (e.g., `disabled`).
68
+ * @param {Record<string, any>} attributes - An object where keys are attribute names and values are their desired values.
69
+ * @returns {this} The current Domo instance for chaining.
70
+ * @example
71
+ * Domo('button').attr({
72
+ * type: 'submit',
73
+ * disabled: true,
74
+ * 'aria-label': 'Submit Form'
75
+ * });
76
+ */
77
+ attr(attributes = {}) {
78
+ for (const [key, value] of Object.entries(attributes)) {
79
+ if (key.startsWith("on")) continue;
80
+ if (this._isLockedAttr(key)) continue;
81
+
82
+ if (this._virtual) {
83
+ this.element._attr[key] = value;
84
+ } else {
85
+ if (typeof value === "boolean") {
86
+ value ? this.element.setAttribute(key, "") : this.element.removeAttribute(key);
87
+ } else if (value != null) {
88
+ this.element.setAttribute(key, value);
89
+ }
90
+ }
91
+ }
92
+ return this;
93
+ }
94
+
95
+ /**
96
+ * Toggles a boolean HTML attribute (e.g., `hidden`, `disabled`) on or off.
97
+ * @param {string} attrName - The name of the attribute to toggle.
98
+ * @param {boolean} [force] - If `true`, the attribute is added. If `false`, it's removed.
99
+ * If omitted, the attribute's presence is flipped.
100
+ * @returns {this} The current Domo instance for chaining.
101
+ * @example
102
+ * // Toggle 'hidden' attribute:
103
+ * Domo('div').tgglAttr('hidden');
104
+ * // Force add 'disabled' attribute:
105
+ * Domo('button').tgglAttr('disabled', true);
106
+ */
107
+ tgglAttr(attrName, force) {
108
+ if (attrName.startsWith("on")) return; // Skip event attributes
109
+ if (typeof force === "boolean") {
110
+ if (force) {
111
+ this.element.setAttribute(attrName, "");
112
+ } else {
113
+ this.element.removeAttribute(attrName);
114
+ }
115
+ } else {
116
+ if (this.element.hasAttribute(attrName)) {
117
+ this.element.removeAttribute(attrName);
118
+ } else {
119
+ this.element.setAttribute(attrName, "");
120
+ }
121
+ }
122
+ return this;
123
+ }
124
+
125
+ /**
126
+ * Sets custom `data-*` attributes on the element.
127
+ * @param {Record<string, string>} data - An object where keys are data attribute names (camelCase) and values are their strings.
128
+ * @returns {this} The current Domo instance for chaining.
129
+ * @example
130
+ * Domo('div').data({
131
+ * userId: '123',
132
+ * itemName: 'Laptop'
133
+ * });
134
+ * // This results in: <div data-user-id="123" data-item-name="Laptop"></div>
135
+ */
136
+ data(data = {}) {
137
+ Object.entries(data).forEach(([key, val]) => {
138
+ if (this._virtual) this.element._data[key] = val;
139
+ else this.element.dataset[key] = val;
140
+ });
141
+ return this;
142
+ }
143
+
144
+ /**
145
+ * Applies CSS styles directly to the element's `style` property.
146
+ * @param {Partial<CSSStyleDeclaration>} styles - An object where keys are CSS property names (camelCase) and values are their style values.
147
+ * @returns {this} The current Domo instance for chaining.
148
+ * @example
149
+ * Domo('p').css({
150
+ * color: 'blue',
151
+ * fontSize: '16px',
152
+ * marginTop: '10px'
153
+ * });
154
+ */
155
+ css(styles = {}) {
156
+ if (this._virtual) Object.assign(this.element._css, styles);
157
+ else Object.assign(this.element.style, styles);
158
+ return this;
159
+ }
160
+ }
161
+
162
+ export default Properties;
package/README.md DELETED
@@ -1,144 +0,0 @@
1
- # Domo
2
-
3
- ![Domo Logo](./assets/logo.png)
4
-
5
- **Domo** is a tiny fluent helper for creating and working with DOM elements.
6
- It simplifies native APIs with a chainable, intuitive interface.
7
-
8
- Originally built for personal use, it's growing into a lightweight UI toolkit with a router, planned components, and scoped styles.
9
- No dependencies. No build step. Just clean, direct DOM manipulation.
10
-
11
- ### Size
12
-
13
- - Minified: 5120 bytes
14
- - Gzipped: 2054 bytes
15
-
16
- Domo is highly optimized for performance, ensuring fast load times even in constrained environments.
17
-
18
- ---
19
-
20
- ## Features
21
-
22
- - Fluent, chainable DOM API
23
- - Set ID, text, value, class, style, data, attributes
24
- - Conditional rendering with `.if()` and `.show()`
25
- - Event handling: `.on`, `.onClosest`, `.onMatch`
26
- - Simple DOM ops: `.append`, `.clear`, `.replace`
27
- - Built-in router:
28
- - History API, nested/dynamic routes
29
- - Scroll and metadata handling
30
- - Route info and listeners
31
-
32
- ---
33
-
34
- ## Installation
35
-
36
- ```bash
37
- npm install @zyrab/domo
38
- ```
39
-
40
- ---
41
-
42
- ## Usage
43
-
44
- ```js
45
- import { Domo } from " @zyrab/domo";
46
-
47
- const btn = Domo("button")
48
- .id("submit-btn")
49
- .cls(["btn", "primary"])
50
- .txt("Submit")
51
- .on("click", () => alert("Submitted!"))
52
- .build();
53
-
54
- document.body.appendChild(btn);
55
- ```
56
-
57
- ---
58
-
59
- ## Router
60
-
61
- The built-in router enables history-based navigation with a simple nested config structure.
62
-
63
- ```js
64
- import { Router } from "@zyrab/domo";
65
-
66
- Router.routes({
67
- "/": { component: Home, meta: { title: "Home" } },
68
- "/about": { component: About, meta: { title: "About" } },
69
- "/blog": {
70
- children: {
71
- "/": { component: Blog, meta: { title: "Blog" } },
72
- "/:slug": { component: BlogPost, meta: { title: "Post" } },
73
- },
74
- },
75
- "*": { component: Error, meta: { title: "404" } },
76
- });
77
-
78
- document.body.appendChild(Router.mount());
79
- Router.init();
80
-
81
- Router.goTo("/about");
82
-
83
- Router.back();
84
-
85
- Router.listen(({ meta, params }) => {
86
- console.log("Route changed:", meta.title, params);
87
- });
88
-
89
- const { meta, params, segments } = Router.info();
90
- ```
91
-
92
- ---
93
-
94
- ## Planned
95
-
96
- - DOM-based components
97
- - Custom scoped style system
98
- - Prebuilt reusable elements
99
- - Examples folder with real use cases
100
-
101
- ---
102
-
103
- ## API Reference
104
-
105
- - Domo(tag = "div") — Creates a DOM element
106
- - .id(string)
107
- - .val(string)
108
- - .txt(string)
109
- - .cls(string | string[])
110
- - .rmvCls(string | string[])
111
- - .tgglCls(string, force?)
112
- - .attr(object)
113
- - .tgglAttr(name, force?)
114
- - .data(object)
115
- - .css(styles)
116
- - .on(event, callback)
117
- - .onMatch(event, { selector: callback })
118
- - .onClosest(event, { selector: callback })
119
- - .child([children])
120
- - .clear()
121
- - .replace(child, newChild)
122
- - .show(bool, display?)
123
- - .if(condition)
124
- - .ref(callback) — Callback access to raw element
125
- - .build() — Returns the constructed HTMLElement
126
-
127
- Full reference:
128
- [Domo](docs/Domo.md)
129
- [Router](docs/Router.md)
130
-
131
- ---
132
-
133
- ## Contributing
134
-
135
- Suggestions, fixes, or features are welcome.
136
- This is a small project made for personal use — but if you see something worth improving, feel free to help.
137
-
138
- → [Read the Contributing Guide](CONTRIBUTING)
139
-
140
- ---
141
-
142
- ## License
143
-
144
- [MIT License](LICENSE)
package/index.js DELETED
@@ -1,35 +0,0 @@
1
- // @ts-check
2
-
3
- import DomoClass from "./src/core/Domo.js";
4
- import Router from "./src/core/Router.js";
5
- import DomoSVGClass from "./src/core/DomoSVG.js";
6
-
7
- /**
8
- * @typedef {import('./src/core/Domo.js').default} DomoInstance
9
- * @typedef {import('./src/core/DomoSVG.js').default} DomoSVGInstance
10
- */
11
-
12
- /**
13
- * @typedef {((tag?: string) => DomoInstance) & { Class: typeof DomoClass }} DomoFactory
14
- * @typedef {((tag?: string) => DomoSVGInstance)} DomoSVGFactory
15
- */
16
-
17
- /**
18
- * @param {string} [tag]
19
- * @returns {DomoInstance}
20
- */
21
- function Domo(tag) {
22
- return new DomoClass(tag);
23
- }
24
- Domo.Class = DomoClass;
25
-
26
- /**
27
- * @param {string} [tag]
28
- * @returns {DomoSVGInstance}
29
- */
30
- function DSVG(tag) {
31
- return new DomoSVGClass(tag);
32
- }
33
- DSVG.Class = DomoSVGClass;
34
-
35
- export { Domo, Router, DSVG, DomoClass };