@twobirds/microcomponents 0.1.20 → 0.1.21
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/README.md +12 -16
- package/dist/MC.d.ts +2 -10
- package/dist/MC.d.ts.map +1 -1
- package/dist/MC.js +11 -61
- package/dist/MC.min.js +63 -60
- package/dist/MC.ts +268 -112
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +1 -2
- package/dist/elements.min.js +1 -1
- package/dist/elements.ts +26 -6
- package/dist/events.d.ts +11 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +55 -0
- package/dist/events.ts +158 -0
- package/dist/formValues.d.ts.map +1 -1
- package/dist/formValues.js +4 -6
- package/dist/formValues.min.js +1 -1
- package/dist/formValues.ts +168 -10
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +3 -1
- package/dist/helpers.ts +82 -1
- package/dist/main.mjs +1 -0
- package/dist/microcomponents.d.ts.map +1 -1
- package/dist/microcomponents.min.js +1 -1
- package/dist/observables.d.ts +1 -1
- package/dist/observables.d.ts.map +1 -1
- package/dist/observables.js +14 -14
- package/dist/observables.min.js +1 -1
- package/dist/observables.ts +132 -21
- package/package.json +7 -7
package/dist/events.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
export function getMcEventName(eventName) {
|
|
3
|
+
return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
|
|
4
|
+
}
|
|
5
|
+
export function off(domNode, eventName, handler) {
|
|
6
|
+
domNode.removeEventListener(getMcEventName(eventName), handler);
|
|
7
|
+
}
|
|
8
|
+
export function on(domNode, eventName, handler, once = false) {
|
|
9
|
+
domNode.addEventListener(getMcEventName(eventName), handler, { capture: false, once });
|
|
10
|
+
return handler;
|
|
11
|
+
}
|
|
12
|
+
export function one(domNode, eventName, handler) {
|
|
13
|
+
on(domNode, eventName, handler, true);
|
|
14
|
+
return handler;
|
|
15
|
+
}
|
|
16
|
+
export class BubbleController {
|
|
17
|
+
stopped = false;
|
|
18
|
+
constructor() { }
|
|
19
|
+
stop() {
|
|
20
|
+
this.stopped = true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function trigger(domNode, event, data = {}, bubble = 'l', controller) {
|
|
24
|
+
const eventName = /^_mc/.test(event) ? event : getMcEventName(event), bubbleController = controller ? controller : new BubbleController(), ev = new CustomEvent(eventName, {
|
|
25
|
+
detail: { data, control: bubbleController },
|
|
26
|
+
bubbles: false,
|
|
27
|
+
});
|
|
28
|
+
if (bubbleController.stopped)
|
|
29
|
+
return;
|
|
30
|
+
if (/l/.test(bubble)) {
|
|
31
|
+
domNode.dispatchEvent(ev);
|
|
32
|
+
ev.stopImmediatePropagation();
|
|
33
|
+
if (bubbleController.stopped)
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (/[ud]/.test(bubble)) {
|
|
37
|
+
if (bubble.indexOf('l') === -1) {
|
|
38
|
+
bubble += 'l';
|
|
39
|
+
}
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
if (bubble.indexOf('u') > -1) {
|
|
42
|
+
let parentMc = domNode.parentElement?.closest('[data-mc]');
|
|
43
|
+
if (parentMc) {
|
|
44
|
+
parentMc?._mc.trigger(eventName, data, bubble, bubbleController);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (bubble.indexOf('d') > -1) {
|
|
48
|
+
let childNodes = domNode._mc.children();
|
|
49
|
+
if (childNodes) {
|
|
50
|
+
childNodes.forEach((_mc) => _mc.trigger(eventName, data, bubble));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}, 0);
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/events.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { HTMLMcElement, DC } from './MC.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
@hidden
|
|
7
|
+
Converts a standard event name to its microcomponent-prefixed variant.
|
|
8
|
+
|
|
9
|
+
This utility is used internally to namespace custom events (e.g., 'Click' → '_mcClick').
|
|
10
|
+
|
|
11
|
+
@param {string} eventName - The original event name (e.g., 'click', 'update').
|
|
12
|
+
@returns {string} The microcomponent-prefixed event name.
|
|
13
|
+
*/
|
|
14
|
+
export function getMcEventName(eventName: string): string {
|
|
15
|
+
return '_mc' + eventName[0].toUpperCase() + eventName.substring(1);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
Removes a previously attached event handler from the DOM node. See {@link on} and {@link one}.
|
|
19
|
+
|
|
20
|
+
Uses internal microcomponent-prefixed naming for event handlers.
|
|
21
|
+
|
|
22
|
+
@param {Node} domNode - The DOM element from which to remove the listener.
|
|
23
|
+
@param {string} eventName - The name of the event (e.g., 'click', 'update').
|
|
24
|
+
@param {EventListenerOrEventListenerObject} handler - The listener function or object to detach.
|
|
25
|
+
*/
|
|
26
|
+
export function off(domNode: Node, eventName: string, handler: EventListenerOrEventListenerObject) {
|
|
27
|
+
domNode.removeEventListener(getMcEventName(eventName), handler);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
Attaches a persistent event listener that will fire every time the specified event occurs.
|
|
32
|
+
|
|
33
|
+
@param {Node} domNode - The DOM element to attach the listener to.
|
|
34
|
+
@param {string} eventName - The name of the event (e.g., 'click', 'update').
|
|
35
|
+
@param {EventListenerOrEventListenerObject} handler - The callback function or object to invoke on each event.
|
|
36
|
+
@param {boolean} [once=false] - If true, remove the listener after first invocation.
|
|
37
|
+
@returns {EventListenerOrEventListenerObject} The attached handler reference. Useful for later deletion with {@link off}.
|
|
38
|
+
*/
|
|
39
|
+
export function on(
|
|
40
|
+
domNode: Node,
|
|
41
|
+
eventName: string,
|
|
42
|
+
handler: EventListenerOrEventListenerObject,
|
|
43
|
+
once: boolean = false
|
|
44
|
+
): EventListenerOrEventListenerObject {
|
|
45
|
+
domNode.addEventListener(getMcEventName(eventName), handler, { capture: false, once });
|
|
46
|
+
return handler;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
Attaches an event listener that will fire only once.
|
|
50
|
+
|
|
51
|
+
Internally calls {@link on} with the `once` flag set to true. The handler
|
|
52
|
+
is automatically removed after its first invocation.
|
|
53
|
+
|
|
54
|
+
@param {Node} domNode - The DOM element to attach the listener to.
|
|
55
|
+
@param {string} eventName - The name of the event (e.g., 'click', 'update').
|
|
56
|
+
@param {EventListenerOrEventListenerObject} handler - The callback function or object to invoke once.
|
|
57
|
+
@returns {EventListenerOrEventListenerObject} The attached one-time handler reference. Useful for later deletion with {@link off}.
|
|
58
|
+
*/
|
|
59
|
+
export function one(
|
|
60
|
+
domNode: Node,
|
|
61
|
+
eventName: string,
|
|
62
|
+
handler: EventListenerOrEventListenerObject
|
|
63
|
+
): EventListenerOrEventListenerObject {
|
|
64
|
+
on(domNode, eventName, handler, true);
|
|
65
|
+
return handler;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
@hidden
|
|
70
|
+
A controller object used to manage and potentially abort event bubbling.
|
|
71
|
+
|
|
72
|
+
The `trigger()` function accepts an optional instance of this class,
|
|
73
|
+
allowing the handler to call `.stop()` before or during dispatch to
|
|
74
|
+
prevent further propagation in either direction (up/down).
|
|
75
|
+
*/
|
|
76
|
+
export class BubbleController {
|
|
77
|
+
stopped = false;
|
|
78
|
+
constructor() {}
|
|
79
|
+
/**
|
|
80
|
+
Signals that event bubbling should be halted.
|
|
81
|
+
|
|
82
|
+
After calling this method, subsequent `trigger()` calls will abort
|
|
83
|
+
before dispatching the event to any targets.
|
|
84
|
+
*/
|
|
85
|
+
stop() {
|
|
86
|
+
this.stopped = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
Creates and dispatches a custom event on the specified DOM node.
|
|
92
|
+
|
|
93
|
+
The event is namespaced with an `_mc` prefix by default, unless already prefixed.
|
|
94
|
+
Supports bidirectional bubbling (up/down the DOM tree) based on the `bubble` parameter:
|
|
95
|
+
- 'l': local dispatch only
|
|
96
|
+
- 'u': bubble up towards ancestors
|
|
97
|
+
- 'd': bubble down to descendants
|
|
98
|
+
|
|
99
|
+
The optional `controller` allows aborting event propagation via `.stop()`.
|
|
100
|
+
|
|
101
|
+
@param {HTMLElement} domNode - The DOM element on which to trigger the event.
|
|
102
|
+
@param {string} event - The name of the event (will be prefixed if needed).
|
|
103
|
+
@param {any} [data={}] - Additional data payload attached to `event.detail.data`.
|
|
104
|
+
@param {string} [bubble='l'] - Bubbling direction ('l', 'u', 'd' or combinations).
|
|
105
|
+
@param {BubbleController} [controller] - Optional controller for managing bubbling behavior.
|
|
106
|
+
*/
|
|
107
|
+
export function trigger(
|
|
108
|
+
domNode: HTMLElement,
|
|
109
|
+
event: string,
|
|
110
|
+
data: any = {},
|
|
111
|
+
bubble: string = 'l', // direction of bubbling
|
|
112
|
+
controller?: BubbleController
|
|
113
|
+
) {
|
|
114
|
+
//console.warn('trigger args', ...arguments);
|
|
115
|
+
|
|
116
|
+
const eventName = /^_mc/.test(event) ? event : getMcEventName(event),
|
|
117
|
+
bubbleController = controller ? controller : new BubbleController(),
|
|
118
|
+
ev = new CustomEvent(eventName, {
|
|
119
|
+
detail: { data, control: bubbleController },
|
|
120
|
+
bubbles: false,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (bubbleController.stopped) return;
|
|
124
|
+
|
|
125
|
+
if (/l/.test(bubble)) {
|
|
126
|
+
domNode.dispatchEvent(ev);
|
|
127
|
+
ev.stopImmediatePropagation();
|
|
128
|
+
|
|
129
|
+
if (bubbleController.stopped) return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// bubble if up or down is indicated
|
|
133
|
+
if (/[ud]/.test(bubble)) {
|
|
134
|
+
// add missing 'l', or else no handlers are executed
|
|
135
|
+
if (bubble.indexOf('l') === -1) {
|
|
136
|
+
bubble += 'l';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
// bubble up towards document.body
|
|
141
|
+
if (bubble.indexOf('u') > -1) {
|
|
142
|
+
let parentMc: HTMLMcElement | null | undefined =
|
|
143
|
+
domNode.parentElement?.closest('[data-mc]');
|
|
144
|
+
if (parentMc) {
|
|
145
|
+
parentMc?._mc.trigger(eventName, data, bubble, bubbleController);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// bubble down towards DOM children
|
|
150
|
+
if (bubble.indexOf('d') > -1) {
|
|
151
|
+
let childNodes: DC[] | null = (domNode as HTMLMcElement)._mc.children();
|
|
152
|
+
if (childNodes) {
|
|
153
|
+
childNodes.forEach((_mc: DC) => _mc.trigger(eventName, data, bubble)); // event multiplex - dont carry bubble controller
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}, 0);
|
|
157
|
+
}
|
|
158
|
+
}
|
package/dist/formValues.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formValues.d.ts","sourceRoot":"","sources":["formValues.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"formValues.d.ts","sourceRoot":"","sources":["../src/formValues.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,WAAW,EAAE,MAAM,cAAc,CAAC;AA4V/D,wBAAgB,UAAU,CACzB,YAAY,EAAE,WAAW,EACzB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,WAAW,GAAG,UAAU,QAkB7B"}
|
package/dist/formValues.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
import { copyGettersSetters } from './helpers.js';
|
|
3
1
|
import { DC } from './MC.js';
|
|
2
|
+
import { copyGettersSetters } from './helpers.js';
|
|
4
3
|
import { observe } from './observables.js';
|
|
5
4
|
const getInputName = (e) => {
|
|
6
5
|
return e.tagName.toLowerCase();
|
|
@@ -61,8 +60,8 @@ class SelectHandler extends InputBase {
|
|
|
61
60
|
this.#multi = e.multiple;
|
|
62
61
|
}
|
|
63
62
|
get val() {
|
|
64
|
-
const
|
|
65
|
-
[...
|
|
63
|
+
const ret = [];
|
|
64
|
+
[...this.#e.querySelectorAll('option')]
|
|
66
65
|
.filter((o) => o.selected)
|
|
67
66
|
.forEach((o) => {
|
|
68
67
|
ret.push(o.value);
|
|
@@ -72,9 +71,8 @@ class SelectHandler extends InputBase {
|
|
|
72
71
|
return ret.length ? ret[0] : '';
|
|
73
72
|
}
|
|
74
73
|
set val(value) {
|
|
75
|
-
const that = this;
|
|
76
74
|
value = typeof value === 'string' ? [value] : value;
|
|
77
|
-
[...
|
|
75
|
+
[...this.#e.querySelectorAll('option')].forEach((o) => {
|
|
78
76
|
if (value.indexOf(o.value) > -1) {
|
|
79
77
|
o.selected = true;
|
|
80
78
|
}
|
package/dist/formValues.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};function r(t){
|
|
1
|
+
var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};function r(t){return"_mc"+t[0].toUpperCase()+t.substring(1)}function n(t,e,n,s=!1){return t.addEventListener(r(e),n,{capture:!1,once:s}),n}t.d(e,{R:()=>S});class s{stopped=!1;constructor(){}stop(){this.stopped=!0}}function a(t,e,n={},a="l",c){const o=/^_mc/.test(e)?e:r(e),l=c||new s,i=new CustomEvent(o,{detail:{data:n,control:l},bubbles:!1});l.stopped||/l/.test(a)&&(t.dispatchEvent(i),i.stopImmediatePropagation(),l.stopped)||/[ud]/.test(a)&&(-1===a.indexOf("l")&&(a+="l"),setTimeout((()=>{if(a.indexOf("u")>-1){let e=t.parentElement?.closest("[data-mc]");e&&e?._mc.trigger(o,n,a,l)}if(a.indexOf("d")>-1){let e=t._mc.children();e&&e.forEach((t=>t.trigger(o,n,a)))}}),0))}const c=new Function("try {return this===window;}catch(e){ return false;}");function o(t){return t&&t instanceof HTMLElement}class l{_target;constructor(t){var e;this._target=new WeakRef(t||{}),function(t){if(!c()||t.constructor===i)return;const e=t.target;if(e instanceof HTMLElement){let r=t;Object.getOwnPropertyNames(r.constructor.prototype).filter((t=>/^(on[A-Z]|one[A-Z])/.test(t)&&"function"==typeof r.constructor.prototype[t])).forEach((t=>{let s=/^one/.test(t),a=t.replace(/^on[e]{0,1}/,""),c=a.toLowerCase();if("on"+c in e){let n=e=>{r[t](e)};e.addEventListener(c,n)}else n(e,a,(e=>{r[t](e)}),s)}))}}(this),e=this,setTimeout((()=>{e.constructor!==i&&e.trigger("Init")}),0)}get target(){return this._target.deref()}}class i extends l{constructor(t){super(t)}static add(t,e,r={}){return function(t,e,r={}){let n=t;n._mc||(n._mc=new i(t));const s=n._mc;return e.length&&!s[e]&&(s[e]=r,t instanceof HTMLElement&&!t.getAttribute("data-mc")&&t.setAttribute("data-mc","")),n}(t,e,r)}static remove(t,e){!function(t,e){if(!e)return;const r=t?._mc||{};r&&r[e]&&delete r[e],!Object.keys(r).length&&t instanceof HTMLElement&&t.hasAttribute("data-mc")&&t.removeAttribute("data-mc")}(t,e)}off(t,e){!function(t,e,n){t.removeEventListener(r(e),n)}(this.target,t,e)}on(t,e){return n(this.target,t,e)}one(t,e){return function(t,e,r){return n(t,e,r,!0),r}(this.target,t,e)}trigger(t,e={},r="l",n){a(this.target,t,e,r,n)}ancestors(){let t=this?.target,e=[];if(!o(t))return[];for(;t=t.closest("[data-mc]");)t?._mc&&e.push(t?._mc);return e.length?e:null}closest(){let t=this.target;return o(t)&&t.closest("[data-mc]")?._mc||null}children(){let t=this?.target;return o(t)?function(t){const e=Math.random().toString().replace(".",""),r='[data-mc]:not([temp_id="'+e+'"] [data-mc] [data-mc])';t.setAttribute("temp_id",e);const n=[...t.querySelectorAll(r)].filter((t=>!!t._mc)).map((t=>t._mc));return t.removeAttribute("temp_id"),n.length?n:null}(t):null}descendants(){const t=this?.target;if(!o(t))return[];const e=[...t.querySelectorAll("[data-mc]")].filter((t=>!!t._mc)).map((t=>t._mc));return e.length?e:null}}function u(t){let e={};return[...Object.keys(t)].sort().forEach((r=>{var n;(n=t[r])&&n.constructor&&n.constructor===Object?e[r]=u(t[r]):e[r]=t[r]})),e}function f(t,e=!0){let r={};return[...Object.getOwnPropertyNames(t)].forEach((e=>{if(t[e]instanceof Object){const n=f(t[e],!1);r[e]={};for(const t in n)r[e+"."+t]=n[t]}else r[e]=t[e]})),e?u(r):r}function h(t,e){const r=Object.getOwnPropertyDescriptors(t);for(const[t,n]of Object.entries(r))("get"in n||"set"in n)&&Object.defineProperty(e,t,n)}function d(t){return null!=t&&"object"==typeof t}function p(t){let e=new Set,r=/\{[^\{\}]*\}/g;return(t.getAttributeNames?t.getAttributeNames():[]).filter((e=>t.getAttribute(e)?.match(r))).forEach((n=>{new Set(t.getAttribute(n)?.match(r)).forEach((t=>e.add(t.replace(/[\{\}]/g,""))))})),[...t.childNodes].filter((t=>3===t.nodeType&&(t.nodeValue||"").match(r))).forEach((t=>{(t?.nodeValue||"").match(r).forEach((t=>e.add(t.replace(/[\{\}]/g,""))))})),[...e]}new Set;const m=[HTMLInputElement,HTMLSelectElement,HTMLTextAreaElement];class g extends i{data={};oldDisplay="";constructor(t){super(t),p(t).forEach((t=>this.data[t]="")),v(this),this.#t()}#t(){let t=this.target,e=!1;(t.getAttributeNames?t.getAttributeNames():[]).filter((e=>t.getAttribute(e)?.match(/\{[^\{\}]*\}/g))).forEach((e=>{let r=new Set(t.getAttribute(e)?.match(/\{[^\{\}]*\}/g));var n;this.data.subscribe((n=t.getAttribute(e),s=>{let a=n;r.forEach((t=>{a=a.replace(t,s[t.replace(/[\{\}]/g,"")])})),t.setAttribute(e,a)}))})),[...t.childNodes].filter((t=>3===t.nodeType&&(t.nodeValue||"").match(/\{[^\{\}]*\}/g))).forEach((r=>{e=!0;let n=new Set(r.nodeValue.match(/\{[^\{\}]*\}/g));this.data.subscribe((e=>s=>{let a=e;n.forEach((t=>{a=a.replace(t,s[t.replace(/[\{\}]/g,"")])})),this.oldDisplay?t.style=this.oldDisplay:t.removeAttribute("style"),delete this.oldDisplay,r.nodeValue=a})(r.nodeValue)),this.oldDisplay=t.getAttribute("style")||"",e&&!m.includes(t.constructor)&&"FIELDSET"!==t.tagName&&(t.style.display="none")}))}onData(t){let e={};Object.keys(t.detail.data).forEach((r=>{this.data.hasOwnProperty(r)&&(e[r]=t.detail.data[r])})),JSON.stringify(e)!==JSON.stringify(this.data)&&Object.assign(this.data,e)}}const b={};function y(t){let e=t.constructor.name;return b[e]||function(t){const e=t.constructor,r=e.name;let n=class extends e{#e=!0;#r=[];constructor(t){super(t),this.constructor.callbacks=[],d(t)&&Object.getOwnPropertyNames(t).forEach((e=>{this[e]=t[e]}))}get callbacks(){return this.#r}set callbacks(t){this.#r=t}subscribe(t){return this.#r.push(t),this}bind(t){if(d(this.valueOf())){!function(t){var e;(e=t,[e].concat([...e.querySelectorAll("*")]).filter((t=>!!p(t).length)).filter((t=>"STYLE"!==t?.tagName)).reverse()).forEach((t=>{t?._mc?._placeholders||i.add(t,"_placeholders",new g(t))}))}(t);const e=[t,...t.querySelectorAll("[data-mc]")].filter((t=>!!t?._mc?._placeholders)),r=function(t){let r=f(t);e.forEach((t=>{t&&a(t,"data",r)}))};this.#r.push(r),this.notify()}else console.warn("IGNORED - cannot bind a non-Object to the dom, IS:",typeof this.valueOf(),this);return this}notify(t){let e=d(this.valueOf())&&this.valueOf()instanceof Array==0?structuredClone(Object.assign({},this)):this.valueOf();return void 0!==t&&(this.#e=t),this.#e&&this.#r.forEach((t=>{t(e)})),this}};return b[r]=n,n}(t)}const v=(t,e)=>((e?[e]:[...Object.getOwnPropertyNames(t)]).forEach((e=>{if(t.hasOwnProperty(e)&&"function"!=typeof t[e]&&!t?.[e]?.constructor?.prototype?.subscribe){if("_"===e[0]||void 0===t?.[e])return t;let r=t[e],n=new(y(r))(r),s=function(){let t;return()=>{clearTimeout(t),t=setTimeout((function(){!function(){n.notify()}(...arguments)}),0)}}();Object.defineProperty(t,e,{enumerable:!0,get(){if(d(n.valueOf())){let t=structuredClone(Object.assign({},n));setTimeout((function(){var e,r;e=structuredClone(Object.assign({},n)),r=t,JSON.stringify(f(e))!==JSON.stringify(f(r))&&s()}),0)}return n},set(t){if(typeof t==typeof n.valueOf())if(d(n.valueOf())&&n.valueOf()instanceof Array==0)void 0===n?.constructor?.prototype?.subscribe&&(n=new(y(t))(structuredClone(Object.assign({},t))),h(t,n)),Object.assign(n,t),n.notify();else{let e=n.valueOf(),r=n.callbacks;t!==e&&(n=new(y(t))(t),n.callbacks=r,s())}else console.warn("IGNORED - cannot change observable types, IS:",typeof n.valueOf(),n,"- CANNOT BECOME:",typeof t)}}),t[e]=r}})),t);class O{constructor(t){}}class E extends O{#n;constructor(t){super(t),this.#n=t}get val(){return new FormData(this.#n.form).get(this.#n.name)}set val(t){[...this.#n.form.querySelectorAll(`input[type="radio"][name="${this.#n.name}"]`)].filter((e=>e.getAttribute("value")===t)).shift().checked=!0}}class w extends O{#n;constructor(t){super(t),this.#n=t}get val(){return this.#n.checked}set val(t){this.#n.checked=t}}class A extends O{#n;constructor(t){super(t),this.#n=t}get val(){return this.#n.value}set val(t){this.#n.value=t}}class _ extends O{#n;#s=!1;constructor(t){super(t),this.#n=t,this.#s=t.multiple}get val(){const t=[];return[...this.#n.querySelectorAll("option")].filter((t=>t.selected)).forEach((e=>{t.push(e.value)})),this.#s?t:t.length?t[0]:""}set val(t){t="string"==typeof t?[t]:t,[...this.#n.querySelectorAll("option")].forEach((e=>{t.indexOf(e.value)>-1?e.selected=!0:e.selected=!1}))}}class k extends O{#n;constructor(t){super(t),this.#n=t}get val(){return this.#n.value||""}set val(t){this.#n.value=t}}const j=t=>{let e=[...(t||document).querySelectorAll("input,select,textarea")],r={};return e.forEach((t=>{const e=t.getAttribute("name");if(e){let n=!1;switch((t=>t.tagName.toLowerCase())(t)){case"input":switch(t.type){case"radio":n=new E(t);break;case"checkbox":n=new w(t);break;default:n=new A(t)}break;case"select":n=new _(t);break;case"textarea":n=new k(t);break;default:console.warn("handler missing:",e,"for",t.tagName.toLowerCase(),t)}n&&(i.add(t,e,n),Object.defineProperty(r,e,{enumerable:!0,configurable:!0,get:()=>n.val,set:t=>{n.val!==t&&(n.val=t)}}))}})),r};function S(t,e,r){let n=j(r);t[e]=Object.assign({},n),v(t,e),h(n,t[e]),[...r.querySelectorAll("input, textarea, select")].forEach((r=>{r.addEventListener("input",(r=>{t[e].notify()}))}))}var N=e.R;export{N as formValues};
|
package/dist/formValues.ts
CHANGED
|
@@ -1,31 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { LooseObject, copyGettersSetters } from './helpers.js';
|
|
4
|
-
import { DC, on } from './MC.js';
|
|
1
|
+
import { DC } from './MC.js';
|
|
2
|
+
import { copyGettersSetters, LooseObject } from './helpers.js';
|
|
5
3
|
import { observe } from './observables.js';
|
|
6
4
|
|
|
5
|
+
/**
|
|
6
|
+
Union type for form input elements.
|
|
7
|
+
*/
|
|
7
8
|
type inputTypes = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
Extracts the lowercase tag name of an element (e.g., 'input', 'select', 'textarea').
|
|
12
|
+
@param e - The HTML element to extract the tag name from.
|
|
13
|
+
@returns The lowercase tag name string.
|
|
14
|
+
*/
|
|
9
15
|
const getInputName = (e: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): string => {
|
|
10
16
|
return e.tagName.toLowerCase();
|
|
11
17
|
};
|
|
12
18
|
|
|
19
|
+
/**
|
|
20
|
+
Abstract base class for form input value handlers.
|
|
21
|
+
Defines the interface that all handler classes must implement.
|
|
22
|
+
*/
|
|
13
23
|
abstract class InputBase {
|
|
24
|
+
/**
|
|
25
|
+
Creates a new InputBase instance.
|
|
26
|
+
@param e - The HTML element being handled (input, select, or textarea).
|
|
27
|
+
*/
|
|
14
28
|
constructor(e: inputTypes) {}
|
|
15
29
|
|
|
30
|
+
/**
|
|
31
|
+
Gets the current value of the form control.
|
|
32
|
+
@returns The value in an appropriate type for the control.
|
|
33
|
+
*/
|
|
16
34
|
abstract get val(): any;
|
|
17
35
|
|
|
36
|
+
/**
|
|
37
|
+
Sets the value of the form control.
|
|
38
|
+
@param value - The new value to assign (type depends on control).
|
|
39
|
+
*/
|
|
18
40
|
abstract set val(value: any);
|
|
19
41
|
}
|
|
20
42
|
|
|
43
|
+
/**
|
|
44
|
+
Handler class specifically for radio button groups.
|
|
45
|
+
Provides synchronized access to the selected value in a group of related radio buttons.
|
|
46
|
+
*/
|
|
21
47
|
class RadioHandler extends InputBase {
|
|
22
48
|
#e: HTMLInputElement;
|
|
23
49
|
|
|
50
|
+
/**
|
|
51
|
+
Creates a new RadioHandler instance.
|
|
52
|
+
@param e - The first radio button element in the group (used to determine name and form scope).
|
|
53
|
+
*/
|
|
24
54
|
constructor(e: HTMLInputElement) {
|
|
25
55
|
super(e);
|
|
26
56
|
this.#e = e;
|
|
27
57
|
}
|
|
28
58
|
|
|
59
|
+
/**
|
|
60
|
+
Gets the currently selected value of the radio button group using FormData API.
|
|
61
|
+
@returns The string value of the selected radio button, or undefined if none is selected.
|
|
62
|
+
*/
|
|
29
63
|
get val(): string {
|
|
30
64
|
let ret = new FormData(this.#e.form as HTMLFormElement).get(this.#e.name);
|
|
31
65
|
|
|
@@ -33,6 +67,11 @@ class RadioHandler extends InputBase {
|
|
|
33
67
|
return ret as string;
|
|
34
68
|
}
|
|
35
69
|
|
|
70
|
+
/**
|
|
71
|
+
Sets the selected radio button by value.
|
|
72
|
+
Finds and checks the first radio input matching the specified value within the same group.
|
|
73
|
+
@param value - The value to match against radio inputs in this group.
|
|
74
|
+
*/
|
|
36
75
|
set val(value: string) {
|
|
37
76
|
let arr = [
|
|
38
77
|
...(this.#e.form as HTMLFormElement).querySelectorAll(
|
|
@@ -45,56 +84,100 @@ class RadioHandler extends InputBase {
|
|
|
45
84
|
}
|
|
46
85
|
}
|
|
47
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Handler class for checkbhox inputs.
|
|
89
|
+
* Provides synchronized get/set access to the checked state of a single checkbox.
|
|
90
|
+
*/
|
|
48
91
|
class CheckboxHandler extends InputBase {
|
|
49
92
|
#e: HTMLInputElement;
|
|
50
93
|
|
|
94
|
+
/**
|
|
95
|
+
Creates a new CheckboxHandler instance.
|
|
96
|
+
@param e - The checkbox element to handle.
|
|
97
|
+
*/
|
|
51
98
|
constructor(e: HTMLInputElement) {
|
|
52
99
|
super(e);
|
|
53
100
|
this.#e = e;
|
|
54
101
|
}
|
|
55
102
|
|
|
103
|
+
/**
|
|
104
|
+
Gets the checked state of the checkbox.
|
|
105
|
+
@returns True if the checkbox is checked, false otherwise.
|
|
106
|
+
*/
|
|
56
107
|
get val(): boolean {
|
|
57
108
|
return this.#e.checked;
|
|
58
109
|
}
|
|
59
110
|
|
|
111
|
+
/**
|
|
112
|
+
Sets the checked state of the checkbox.
|
|
113
|
+
@param value - The boolean value to assign as the new checked state.
|
|
114
|
+
*/
|
|
60
115
|
set val(value: boolean) {
|
|
61
116
|
this.#e.checked = value;
|
|
62
117
|
}
|
|
63
118
|
}
|
|
64
119
|
|
|
120
|
+
/**
|
|
121
|
+
Handler class for standard single-line input fields (text, password, email, etc.).
|
|
122
|
+
Provides synchronized get/set access to the value of an input field.
|
|
123
|
+
*/
|
|
65
124
|
class InputHandler extends InputBase {
|
|
66
125
|
#e: HTMLInputElement;
|
|
67
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Creates a new InputHandler instance.
|
|
129
|
+
* @param e - The HTML input element to handle.
|
|
130
|
+
*/
|
|
68
131
|
constructor(e: HTMLInputElement) {
|
|
69
132
|
super(e);
|
|
70
133
|
this.#e = e;
|
|
71
134
|
}
|
|
72
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Gets the current value of the input field.
|
|
138
|
+
* @returns The string content of the input element.
|
|
139
|
+
*/
|
|
73
140
|
get val(): string {
|
|
74
141
|
return this.#e.value;
|
|
75
142
|
}
|
|
76
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Sets the value of the input field.
|
|
146
|
+
* @param value - The new string value for the input element.
|
|
147
|
+
*/
|
|
77
148
|
set val(value: string) {
|
|
78
149
|
this.#e.value = value;
|
|
79
150
|
}
|
|
80
151
|
}
|
|
81
152
|
|
|
153
|
+
/**
|
|
154
|
+
Handler class for select dropdown elements, supporting both single and multiple selection modes.
|
|
155
|
+
Provides synchronized get/set access to selected option values.
|
|
156
|
+
*/
|
|
82
157
|
class SelectHandler extends InputBase {
|
|
83
158
|
#e: HTMLSelectElement;
|
|
84
159
|
#multi: boolean = false;
|
|
85
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Creates a new SelectHandler instance.
|
|
163
|
+
* @param e - The select element to handle.
|
|
164
|
+
*/
|
|
86
165
|
constructor(e: HTMLSelectElement) {
|
|
87
166
|
super(e);
|
|
88
167
|
this.#e = e;
|
|
89
168
|
this.#multi = e.multiple;
|
|
90
169
|
}
|
|
91
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Gets the currently selected option value(s).
|
|
173
|
+
* For single selection, returns a string. For multiple selection, returns an array of strings.
|
|
174
|
+
* @returns A string or array of strings representing selected values.
|
|
175
|
+
*/
|
|
92
176
|
get val(): string[] | string {
|
|
93
|
-
const
|
|
94
|
-
ret: string[] = [];
|
|
177
|
+
const ret: string[] = [];
|
|
95
178
|
|
|
96
179
|
// get all options
|
|
97
|
-
[...
|
|
180
|
+
[...this.#e.querySelectorAll('option')]
|
|
98
181
|
.filter((o) => o.selected)
|
|
99
182
|
.forEach((o) => {
|
|
100
183
|
ret.push(o.value);
|
|
@@ -104,13 +187,15 @@ class SelectHandler extends InputBase {
|
|
|
104
187
|
return ret.length ? ret[0] : '';
|
|
105
188
|
}
|
|
106
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Sets the selected option(s) by value.
|
|
192
|
+
* @param value - A string or array of strings representing values to select.
|
|
193
|
+
*/
|
|
107
194
|
set val(value: string[] | string) {
|
|
108
|
-
const that = this;
|
|
109
|
-
|
|
110
195
|
value = typeof value === 'string' ? [value] : value;
|
|
111
196
|
|
|
112
197
|
// set all options
|
|
113
|
-
[...
|
|
198
|
+
[...this.#e.querySelectorAll('option')].forEach((o) => {
|
|
114
199
|
if (value.indexOf(o.value) > -1) {
|
|
115
200
|
o.selected = true;
|
|
116
201
|
} else {
|
|
@@ -120,22 +205,47 @@ class SelectHandler extends InputBase {
|
|
|
120
205
|
}
|
|
121
206
|
}
|
|
122
207
|
|
|
208
|
+
/**
|
|
209
|
+
Handler class for textarea elements.
|
|
210
|
+
Provides synchronized get/set access to the multi-line text content of a textarea.
|
|
211
|
+
*/
|
|
123
212
|
class TextAreaHandler extends InputBase {
|
|
124
213
|
#e: HTMLTextAreaElement;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Creates a new TextAreaHandler instance.
|
|
217
|
+
* @param e - The textarea element to handle.
|
|
218
|
+
*/
|
|
125
219
|
constructor(e: HTMLTextAreaElement) {
|
|
126
220
|
super(e);
|
|
127
221
|
this.#e = e;
|
|
128
222
|
}
|
|
129
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Gets the current value of the textarea (defaults to empty string if null/undefined).
|
|
226
|
+
* @returns The string content of the textarea element.
|
|
227
|
+
*/
|
|
130
228
|
get val(): string {
|
|
131
229
|
return this.#e.value || '';
|
|
132
230
|
}
|
|
133
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Sets the value of the textarea.
|
|
234
|
+
* @param value - The new string value for the textarea element.
|
|
235
|
+
*/
|
|
134
236
|
set val(value: string) {
|
|
135
237
|
this.#e.value = value;
|
|
136
238
|
}
|
|
137
239
|
}
|
|
138
240
|
|
|
241
|
+
/**
|
|
242
|
+
Collects all form controls within a given DOM scope and creates handler objects
|
|
243
|
+
that provide synchronized get/set access to their values.
|
|
244
|
+
|
|
245
|
+
@param e - Optional root DOM element or shadow root to search within; defaults to document if omitted.
|
|
246
|
+
@returns An object where each key is the name attribute of an input, select, or textarea,
|
|
247
|
+
and each value is a proxy-like accessor object with `val` getter/setter methods.
|
|
248
|
+
*/
|
|
139
249
|
const getInputHandlers = (e: HTMLElement | ShadowRoot): LooseObject => {
|
|
140
250
|
let inputList = [...(e || document).querySelectorAll('input,select,textarea')],
|
|
141
251
|
handlers: LooseObject = {};
|
|
@@ -189,6 +299,54 @@ const getInputHandlers = (e: HTMLElement | ShadowRoot): LooseObject => {
|
|
|
189
299
|
return handlers;
|
|
190
300
|
};
|
|
191
301
|
|
|
302
|
+
/**
|
|
303
|
+
Attaches a form-values observable to a target object property, allowing two-way binding
|
|
304
|
+
between the DOM form controls and the object's state.
|
|
305
|
+
|
|
306
|
+
@param targetObject - The object to which the form values will be attached as an observable.
|
|
307
|
+
@param propertyName - The name of the property on the target object that will hold the form data.
|
|
308
|
+
@param dom - The DOM element or shadow root (typically a form) whose inputs should be observed and bound.
|
|
309
|
+
|
|
310
|
+
@example
|
|
311
|
+
HTML
|
|
312
|
+
```html
|
|
313
|
+
<!-- HTML Form -->
|
|
314
|
+
<form id="myForm">
|
|
315
|
+
<input type="text" name="username" value="">
|
|
316
|
+
<select name="role">
|
|
317
|
+
<option value="admin">Admin</option>
|
|
318
|
+
<option value="user">User</option>
|
|
319
|
+
</select>
|
|
320
|
+
<textarea name="bio"></textarea>
|
|
321
|
+
<input type="checkbox" name="active">
|
|
322
|
+
<input type="radio" name="gender" value="male"> Male
|
|
323
|
+
<input type="radio" name="gender" value="female"> Female
|
|
324
|
+
</form>
|
|
325
|
+
```
|
|
326
|
+
Javascript
|
|
327
|
+
```typescript
|
|
328
|
+
import { formValues } from './formValues.js';
|
|
329
|
+
|
|
330
|
+
const state = {};
|
|
331
|
+
|
|
332
|
+
// Bind the form to state.formData
|
|
333
|
+
formValues(state, 'formData', document.getElementById('myForm'));
|
|
334
|
+
|
|
335
|
+
// Read values from the form
|
|
336
|
+
console.log(state.formData.username); // string
|
|
337
|
+
console.log(state.formData.role); // "admin" or "user"
|
|
338
|
+
console.log(state.formData.bio); // string
|
|
339
|
+
console.log(state.formData.active); // boolean
|
|
340
|
+
console.log(state.formData.gender); // "male" or "female"
|
|
341
|
+
|
|
342
|
+
// Write values to the form (updates DOM automatically)
|
|
343
|
+
state.formData.username = 'john_doe';
|
|
344
|
+
state.formData.role = 'user';
|
|
345
|
+
state.formData.bio = 'Hello world!';
|
|
346
|
+
state.formData.active = true;
|
|
347
|
+
state.formData.gender = 'male';
|
|
348
|
+
```
|
|
349
|
+
*/
|
|
192
350
|
export function formValues(
|
|
193
351
|
targetObject: LooseObject,
|
|
194
352
|
propertyName: string,
|
package/dist/helpers.d.ts
CHANGED
package/dist/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAaA,wBAAgB,SAAS,YAExB;AAKD,MAAM,WAAW,WAAW;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC;CAC5B;AAOD,wBAAgB,cAAc,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAElD;AAOD,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,GAAG,WAAW,CAatD;AASD,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,GAAE,OAAc,GAAG,WAAW,CAgBjF;AAQD,wBAAgB,SAAS,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,GAAG,OAAO,CAEnE;AAQD,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,GAAG,GAAE,WAAgB,GAAG,GAAG,CAW3E;AAUD,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,GAAG,EAAE,EAAE,SAAS,EAAE,MAAM,OAsCrE;AAQD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,UAKxC;AAUD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,GAAG,QAAQ,CASvE;AAQD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAU1D;AAQD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,QAO1E"}
|