@kaiserofthenight/human-js 1.0.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,130 @@
1
+ /**
2
+ * EVENT HANDLING SYSTEM
3
+ *
4
+ * Attach and manage DOM event listeners.
5
+ * Supports event delegation and automatic cleanup.
6
+ */
7
+
8
+ /**
9
+ * Attach events to DOM elements
10
+ * @param {HTMLElement} rootElement
11
+ * @param {Object} eventMap - { selector: { eventType: handler } }
12
+ */
13
+ export function attachEvents(rootElement, eventMap = {}) {
14
+ const attachedListeners = [];
15
+
16
+ Object.keys(eventMap).forEach(selector => {
17
+ const events = eventMap[selector];
18
+
19
+ // Find target element
20
+ let targetElement;
21
+ if (selector === 'root' || selector === ':root') {
22
+ targetElement = rootElement;
23
+ } else {
24
+ targetElement = rootElement.querySelector(selector);
25
+ }
26
+
27
+ if (!targetElement) {
28
+ console.warn(`[HumanJS] Element not found: ${selector}`);
29
+ return;
30
+ }
31
+
32
+ // Attach each event type
33
+ Object.keys(events).forEach(eventType => {
34
+ const handler = events[eventType];
35
+ targetElement.addEventListener(eventType, handler);
36
+
37
+ // Store for cleanup
38
+ attachedListeners.push({
39
+ element: targetElement,
40
+ type: eventType,
41
+ handler
42
+ });
43
+ });
44
+ });
45
+
46
+ // Return cleanup function
47
+ return () => {
48
+ attachedListeners.forEach(({ element, type, handler }) => {
49
+ element.removeEventListener(type, handler);
50
+ });
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Event delegation - attach one listener to parent
56
+ * @param {HTMLElement} parent
57
+ * @param {String} eventType
58
+ * @param {String} selector
59
+ * @param {Function} handler
60
+ */
61
+ export function delegate(parent, eventType, selector, handler) {
62
+ const listener = (event) => {
63
+ const target = event.target.closest(selector);
64
+ if (target && parent.contains(target)) {
65
+ handler.call(target, event);
66
+ }
67
+ };
68
+
69
+ parent.addEventListener(eventType, listener);
70
+
71
+ return () => parent.removeEventListener(eventType, listener);
72
+ }
73
+
74
+ /**
75
+ * Debounce function calls
76
+ * @param {Function} fn
77
+ * @param {Number} delay
78
+ */
79
+ export function debounce(fn, delay = 300) {
80
+ let timeoutId;
81
+ return function(...args) {
82
+ clearTimeout(timeoutId);
83
+ timeoutId = setTimeout(() => fn.apply(this, args), delay);
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Throttle function calls
89
+ * @param {Function} fn
90
+ * @param {Number} limit
91
+ */
92
+ export function throttle(fn, limit = 300) {
93
+ let inThrottle;
94
+ return function(...args) {
95
+ if (!inThrottle) {
96
+ fn.apply(this, args);
97
+ inThrottle = true;
98
+ setTimeout(() => inThrottle = false, limit);
99
+ }
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Common event helpers
105
+ */
106
+ export const on = {
107
+ click: (selector, handler) => ({ [selector]: { click: handler } }),
108
+ input: (selector, handler) => ({ [selector]: { input: handler } }),
109
+ submit: (selector, handler) => ({
110
+ [selector]: {
111
+ submit: (e) => {
112
+ e.preventDefault();
113
+ handler(e);
114
+ }
115
+ }
116
+ }),
117
+ change: (selector, handler) => ({ [selector]: { change: handler } }),
118
+ focus: (selector, handler) => ({ [selector]: { focus: handler } }),
119
+ blur: (selector, handler) => ({ [selector]: { blur: handler } }),
120
+ keydown: (selector, handler) => ({ [selector]: { keydown: handler } }),
121
+ keyup: (selector, handler) => ({ [selector]: { keyup: handler } })
122
+ };
123
+
124
+ /**
125
+ * Merge multiple event maps
126
+ * @param {...Object} eventMaps
127
+ */
128
+ export function mergeEvents(...eventMaps) {
129
+ return Object.assign({}, ...eventMaps);
130
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * HTML RENDERING SYSTEM
3
+ *
4
+ * Convert tagged template literals into real DOM elements.
5
+ * Supports dynamic values, nested elements, and arrays.
6
+ */
7
+
8
+ /**
9
+ * Main html tagged template function
10
+ * @param {Array} strings - Template string parts
11
+ * @param {...any} values - Dynamic values
12
+ * @returns {HTMLElement} DOM element
13
+ */
14
+ export function html(strings, ...values) {
15
+ // Build complete HTML string
16
+ let htmlString = '';
17
+
18
+ strings.forEach((str, i) => {
19
+ htmlString += str;
20
+
21
+ if (i < values.length) {
22
+ const value = values[i];
23
+
24
+ // Handle different value types
25
+ if (value === null || value === undefined) {
26
+ htmlString += '';
27
+ } else if (Array.isArray(value)) {
28
+ // For arrays, join DOM elements
29
+ htmlString += value.map(v => {
30
+ if (v instanceof HTMLElement) {
31
+ return v.outerHTML;
32
+ }
33
+ return String(v);
34
+ }).join('');
35
+ } else if (value instanceof HTMLElement) {
36
+ htmlString += value.outerHTML;
37
+ } else {
38
+ htmlString += String(value);
39
+ }
40
+ }
41
+ });
42
+
43
+ // Create DOM from string
44
+ return createElementFromHTML(htmlString.trim());
45
+ }
46
+
47
+ /**
48
+ * Create a DOM element from HTML string
49
+ * @param {String} htmlString - HTML markup
50
+ * @returns {HTMLElement} DOM element
51
+ */
52
+ function createElementFromHTML(htmlString) {
53
+ const template = document.createElement('template');
54
+ template.innerHTML = htmlString;
55
+
56
+ const element = template.content.firstChild;
57
+
58
+ if (!element) {
59
+ throw new Error('Invalid HTML: ' + htmlString);
60
+ }
61
+
62
+ return element;
63
+ }
64
+
65
+ /**
66
+ * Create element with props (alternative to html``)
67
+ * @param {String} tag - Element tag name
68
+ * @param {Object} props - Element properties
69
+ * @param {...any} children - Child elements
70
+ * @returns {HTMLElement}
71
+ */
72
+ export function createElement(tag, props = {}, ...children) {
73
+ const element = document.createElement(tag);
74
+
75
+ // Apply props
76
+ Object.keys(props).forEach(key => {
77
+ if (key === 'className') {
78
+ element.className = props[key];
79
+ } else if (key === 'style' && typeof props[key] === 'object') {
80
+ Object.assign(element.style, props[key]);
81
+ } else if (key.startsWith('on')) {
82
+ const eventName = key.substring(2).toLowerCase();
83
+ element.addEventListener(eventName, props[key]);
84
+ } else {
85
+ element.setAttribute(key, props[key]);
86
+ }
87
+ });
88
+
89
+ // Append children
90
+ children.forEach(child => {
91
+ if (child === null || child === undefined) return;
92
+
93
+ if (typeof child === 'string') {
94
+ element.appendChild(document.createTextNode(child));
95
+ } else if (child instanceof HTMLElement) {
96
+ element.appendChild(child);
97
+ } else if (Array.isArray(child)) {
98
+ child.forEach(c => {
99
+ if (c instanceof HTMLElement) {
100
+ element.appendChild(c);
101
+ } else {
102
+ element.appendChild(document.createTextNode(String(c)));
103
+ }
104
+ });
105
+ } else {
106
+ element.appendChild(document.createTextNode(String(child)));
107
+ }
108
+ });
109
+
110
+ return element;
111
+ }
112
+
113
+ /**
114
+ * Render multiple children
115
+ * @param {...any} children
116
+ * @returns {DocumentFragment}
117
+ */
118
+ export function fragment(...children) {
119
+ const frag = document.createDocumentFragment();
120
+
121
+ children.forEach(child => {
122
+ if (child instanceof HTMLElement) {
123
+ frag.appendChild(child);
124
+ } else if (typeof child === 'string') {
125
+ frag.appendChild(document.createTextNode(child));
126
+ }
127
+ });
128
+
129
+ return frag;
130
+ }
131
+
132
+ /**
133
+ * Conditionally render elements
134
+ * @param {Boolean} condition
135
+ * @param {Function} trueRender
136
+ * @param {Function} falseRender
137
+ * @returns {HTMLElement}
138
+ */
139
+ export function when(condition, trueRender, falseRender = () => html`<span></span>`) {
140
+ return condition ? trueRender() : falseRender();
141
+ }
142
+
143
+ /**
144
+ * Render list of items
145
+ * @param {Array} items
146
+ * @param {Function} renderItem
147
+ * @returns {Array<HTMLElement>}
148
+ */
149
+ export function each(items, renderItem) {
150
+ return items.map((item, index) => renderItem(item, index));
151
+ }
@@ -0,0 +1,182 @@
1
+ /**
2
+ * SPA ROUTING SYSTEM
3
+ *
4
+ * Hash-based routing for single page applications.
5
+ * No server configuration required.
6
+ */
7
+
8
+ /**
9
+ * Create a router
10
+ * @param {Object} routes - Route definitions { '/path': component }
11
+ * @param {Object} options - Router options
12
+ */
13
+ export function createRouter(routes, options = {}) {
14
+ const {
15
+ root = document.getElementById('app'),
16
+ notFound = () => {
17
+ const el = document.createElement('div');
18
+ el.innerHTML = '<h1>404 - Page Not Found</h1>';
19
+ return el;
20
+ },
21
+ beforeEach = null,
22
+ afterEach = null
23
+ } = options;
24
+
25
+ let currentRoute = null;
26
+ let currentComponent = null;
27
+
28
+ // Parse route params (e.g., /user/:id)
29
+ function matchRoute(hash) {
30
+ const path = hash.slice(1) || '/';
31
+
32
+ // Try exact match first
33
+ if (routes[path]) {
34
+ return { route: path, params: {} };
35
+ }
36
+
37
+ // Try parameterized routes
38
+ for (const route in routes) {
39
+ const paramNames = [];
40
+ const regexPattern = route.replace(/:([^/]+)/g, (_, paramName) => {
41
+ paramNames.push(paramName);
42
+ return '([^/]+)';
43
+ });
44
+
45
+ const regex = new RegExp(`^${regexPattern}$`);
46
+ const match = path.match(regex);
47
+
48
+ if (match) {
49
+ const params = {};
50
+ paramNames.forEach((name, index) => {
51
+ params[name] = match[index + 1];
52
+ });
53
+ return { route, params };
54
+ }
55
+ }
56
+
57
+ return null;
58
+ }
59
+
60
+ // Navigate to a route
61
+ function navigate(path, replace = false) {
62
+ if (replace) {
63
+ window.location.replace(`#${path}`);
64
+ } else {
65
+ window.location.hash = path;
66
+ }
67
+ }
68
+
69
+ // Render current route
70
+ function render() {
71
+ const hash = window.location.hash;
72
+ const match = matchRoute(hash);
73
+
74
+ let route, params;
75
+ if (match) {
76
+ route = match.route;
77
+ params = match.params;
78
+ } else {
79
+ route = '*';
80
+ params = {};
81
+ }
82
+
83
+ // Call beforeEach guard
84
+ if (beforeEach) {
85
+ const result = beforeEach(route, currentRoute, params);
86
+ if (result === false) return; // Cancel navigation
87
+ }
88
+
89
+ // Get route component
90
+ const routeComponent = routes[route] || notFound;
91
+
92
+ // Destroy previous component
93
+ if (currentComponent && currentComponent.destroy) {
94
+ currentComponent.destroy();
95
+ }
96
+
97
+ // Clear root
98
+ root.innerHTML = '';
99
+
100
+ // Render new component
101
+ if (typeof routeComponent === 'function') {
102
+ const element = routeComponent(params);
103
+
104
+ if (element instanceof HTMLElement) {
105
+ root.appendChild(element);
106
+ } else if (element && element.element) {
107
+ currentComponent = element;
108
+ root.appendChild(element.element);
109
+ }
110
+ }
111
+
112
+ const previousRoute = currentRoute;
113
+ currentRoute = route;
114
+
115
+ // Call afterEach hook
116
+ if (afterEach) {
117
+ afterEach(route, previousRoute, params);
118
+ }
119
+ }
120
+
121
+ // Listen for hash changes
122
+ window.addEventListener('hashchange', render);
123
+
124
+ // Initial render
125
+ render();
126
+
127
+ // Return router API
128
+ return {
129
+ navigate,
130
+ back: () => window.history.back(),
131
+ forward: () => window.history.forward(),
132
+ getCurrentRoute: () => currentRoute,
133
+ destroy: () => {
134
+ window.removeEventListener('hashchange', render);
135
+ }
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Link component for navigation
141
+ */
142
+ export function Link(to, text, className = '') {
143
+ const link = document.createElement('a');
144
+ link.href = `#${to}`;
145
+ link.textContent = text;
146
+ link.className = className;
147
+
148
+ // Prevent default and use router navigation
149
+ link.addEventListener('click', (e) => {
150
+ e.preventDefault();
151
+ window.location.hash = to;
152
+ });
153
+
154
+ return link;
155
+ }
156
+
157
+ /**
158
+ * Get route params from URL
159
+ */
160
+ export function getParams() {
161
+ const hash = window.location.hash.slice(1);
162
+ const [path, query] = hash.split('?');
163
+
164
+ if (!query) return {};
165
+
166
+ return query.split('&').reduce((params, param) => {
167
+ const [key, value] = param.split('=');
168
+ params[decodeURIComponent(key)] = decodeURIComponent(value || '');
169
+ return params;
170
+ }, {});
171
+ }
172
+
173
+ /**
174
+ * Build URL with query params
175
+ */
176
+ export function buildUrl(path, params = {}) {
177
+ const query = Object.keys(params)
178
+ .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
179
+ .join('&');
180
+
181
+ return query ? `${path}?${query}` : path;
182
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * REACTIVE STATE MANAGEMENT
3
+ *
4
+ * Creates observable state objects that trigger callbacks on changes.
5
+ * Uses JavaScript Proxy to intercept property access and modifications.
6
+ */
7
+
8
+ /**
9
+ * Create a reactive state object
10
+ * @param {Object} initialState - Initial state values
11
+ * @param {Function} onChange - Callback fired on any state change
12
+ * @returns {Proxy} Reactive state object
13
+ */
14
+ export function createState(initialState = {}, onChange = null) {
15
+ // Store the raw state
16
+ const state = { ...initialState };
17
+
18
+ // Store computed properties
19
+ const computed = {};
20
+
21
+ // Store watchers for specific properties
22
+ const watchers = {};
23
+
24
+ // Create reactive proxy
25
+ const proxy = new Proxy(state, {
26
+ get(target, property) {
27
+ // Return computed value if it exists
28
+ if (computed[property]) {
29
+ return computed[property]();
30
+ }
31
+ return target[property];
32
+ },
33
+
34
+ set(target, property, value) {
35
+ const oldValue = target[property];
36
+
37
+ // Only update if value actually changed
38
+ if (oldValue === value) return true;
39
+
40
+ // Update the value
41
+ target[property] = value;
42
+
43
+ // Call property-specific watchers
44
+ if (watchers[property]) {
45
+ watchers[property].forEach(watcher => {
46
+ watcher(value, oldValue);
47
+ });
48
+ }
49
+
50
+ // Call global onChange callback
51
+ if (onChange) {
52
+ onChange(property, value, oldValue);
53
+ }
54
+
55
+ return true;
56
+ }
57
+ });
58
+
59
+ // Add utility methods
60
+ proxy.$watch = function(property, callback) {
61
+ if (!watchers[property]) {
62
+ watchers[property] = [];
63
+ }
64
+ watchers[property].push(callback);
65
+
66
+ // Return unwatch function
67
+ return () => {
68
+ const index = watchers[property].indexOf(callback);
69
+ if (index > -1) {
70
+ watchers[property].splice(index, 1);
71
+ }
72
+ };
73
+ };
74
+
75
+ proxy.$computed = function(property, computeFn) {
76
+ computed[property] = computeFn.bind(proxy);
77
+ };
78
+
79
+ proxy.$reset = function(newState = initialState) {
80
+ Object.keys(state).forEach(key => delete state[key]);
81
+ Object.assign(state, newState);
82
+ if (onChange) onChange('$reset', newState);
83
+ };
84
+
85
+ proxy.$raw = function() {
86
+ return { ...state };
87
+ };
88
+
89
+ return proxy;
90
+ }
91
+
92
+ /**
93
+ * Create a global store (shared state across components)
94
+ */
95
+ export function createStore(initialState = {}) {
96
+ const listeners = [];
97
+ const state = createState(initialState, (prop, value) => {
98
+ listeners.forEach(listener => listener(prop, value, state));
99
+ });
100
+
101
+ return {
102
+ state,
103
+ subscribe(callback) {
104
+ listeners.push(callback);
105
+ return () => {
106
+ const index = listeners.indexOf(callback);
107
+ if (index > -1) listeners.splice(index, 1);
108
+ };
109
+ },
110
+ getState() {
111
+ return state.$raw();
112
+ }
113
+ };
114
+ }
package/src/index.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * HUMANJS FRAMEWORK
3
+ *
4
+ * A human-first frontend framework.
5
+ * Simple, readable, and easy to extend.
6
+ *
7
+ * @version 1.0.0
8
+ * @author HumanJS Team
9
+ */
10
+
11
+ // Core modules
12
+ export { createState, createStore } from './core/state.js';
13
+ export { html, createElement, fragment, when, each } from './core/render.js';
14
+ export { createComponent, app } from './core/component.js';
15
+ export { createRouter, Link, getParams, buildUrl } from './core/router.js';
16
+ export { attachEvents, delegate, debounce, throttle, on, mergeEvents } from './core/events.js';
17
+
18
+ // Plugins
19
+ export { createValidator, rules, getFormData, displayErrors } from './plugins/validator.js';
20
+ export { createHttp, http } from './plugins/http.js';
21
+ export { local, session, createNamespace, onStorageChange } from './plugins/storage.js';
22
+
23
+ // Utilities
24
+ export * as helpers from './utils/helpers.js';
25
+
26
+ /**
27
+ * Framework version
28
+ */
29
+ export const VERSION = '1.0.0';
30
+
31
+ /**
32
+ * Quick start helper
33
+ */
34
+ export function humanjs() {
35
+ console.log(`
36
+ 🎯 HumanJS Framework v${VERSION}
37
+
38
+ A framework built for humans, not machines.
39
+
40
+ Quick start:
41
+ import { app, html } from './src/index.js';
42
+
43
+ app.create({
44
+ state: { count: 0 },
45
+ render: (state) => html\`<div>\${state.count}</div>\`
46
+ });
47
+
48
+ Documentation: https://github.com/kaiserofthenight/humanjs
49
+ `);
50
+ }
51
+
52
+ /**
53
+ * Global error handler (optional)
54
+ */
55
+ if (typeof window !== 'undefined') {
56
+ window.addEventListener('error', (event) => {
57
+ console.error('[HumanJS] Uncaught error:', event.error);
58
+ });
59
+
60
+ window.addEventListener('unhandledrejection', (event) => {
61
+ console.error('[HumanJS] Unhandled promise rejection:', event.reason);
62
+ });
63
+ }