@twobirds/microcomponents 0.1.19 → 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.
@@ -1,31 +1,65 @@
1
- 'use strict';
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 that = this,
94
- ret: string[] = [];
177
+ const ret: string[] = [];
95
178
 
96
179
  // get all options
97
- [...that.#e.querySelectorAll('option')]
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
- [...that.#e.querySelectorAll('option')].forEach((o) => {
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,
@@ -0,0 +1,15 @@
1
+ export declare function isBrowser(): boolean;
2
+ export interface LooseObject {
3
+ [key: string | symbol]: any;
4
+ }
5
+ export declare function isNativeObject(value: any): boolean;
6
+ export declare function sortObject(o: LooseObject): LooseObject;
7
+ export declare function flattenObject(o: LooseObject, doSort?: boolean): LooseObject;
8
+ export declare function deepEqual(o1: LooseObject, o2: LooseObject): boolean;
9
+ export declare function nameSpace(ns: string | string[], obj?: LooseObject): any;
10
+ export declare function parse(what: string | object | any[], parseThis: object): any;
11
+ export declare function kebabToPascal(str: string): string;
12
+ export declare function debounce(func: Function, milliseconds: number): Function;
13
+ export declare function htmlToElements(html: string): HTMLElement[];
14
+ export declare function copyGettersSetters(source: LooseObject, target: LooseObject): void;
15
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,113 @@
1
+ export function isBrowser() {
2
+ return window ? true : false;
3
+ }
4
+ export function isNativeObject(value) {
5
+ return value && value['constructor'] && value.constructor === Object;
6
+ }
7
+ export function sortObject(o) {
8
+ let result = {}, keys = [...Object.keys(o)].sort();
9
+ keys.forEach((key) => {
10
+ if (isNativeObject(o[key])) {
11
+ result[key] = sortObject(o[key]);
12
+ }
13
+ else {
14
+ result[key] = o[key];
15
+ }
16
+ });
17
+ return result;
18
+ }
19
+ export function flattenObject(o, doSort = true) {
20
+ let result = {};
21
+ [...Object.getOwnPropertyNames(o)].forEach((propName) => {
22
+ if (o[propName] instanceof Object) {
23
+ const temp = flattenObject(o[propName], false);
24
+ result[propName] = {};
25
+ for (const j in temp) {
26
+ result[propName + '.' + j] = temp[j];
27
+ }
28
+ }
29
+ else {
30
+ result[propName] = o[propName];
31
+ }
32
+ });
33
+ return doSort ? sortObject(result) : result;
34
+ }
35
+ export function deepEqual(o1, o2) {
36
+ return JSON.stringify(flattenObject(o1)) === JSON.stringify(flattenObject(o2));
37
+ }
38
+ export function nameSpace(ns, obj = {}) {
39
+ let nsa = ns.constructor === String ? ns.split('.') : ns;
40
+ if (!obj || !nsa[0] || !obj[nsa[0]]) {
41
+ console.warn('ns', nsa[0], 'not in', obj);
42
+ return;
43
+ }
44
+ if (obj[nsa[0]].constructor === Object && nsa.length > 1) {
45
+ return nameSpace(nsa.splice(1), obj[nsa[0]]);
46
+ }
47
+ return obj[nsa[0]];
48
+ }
49
+ export function parse(what, parseThis) {
50
+ var args = Array.from(arguments);
51
+ if (args.length > 2) {
52
+ while (args.length > 1) {
53
+ args[0] = parse(args[0], args[1]);
54
+ args.splice(1, 1);
55
+ }
56
+ return args[0];
57
+ }
58
+ if (typeof what === 'string') {
59
+ what.match(/\{[^\{\}]*\}/g)?.forEach(function (pPropname) {
60
+ var propname = pPropname.substring(1, pPropname.length - 1), value = nameSpace(propname, parseThis);
61
+ if (typeof value !== 'undefined') {
62
+ what = what.replace(pPropname, value);
63
+ }
64
+ });
65
+ }
66
+ else if (isNativeObject(what)) {
67
+ switch (what.constructor) {
68
+ case Object:
69
+ Object.keys(what).forEach(function (pKey) {
70
+ if (what.hasOwnProperty(pKey)) {
71
+ what[pKey] = parse(what[pKey], parseThis);
72
+ }
73
+ });
74
+ break;
75
+ case Array:
76
+ what.forEach(function (pValue, pKey, original) {
77
+ original[pKey] = parse(what[pKey], parseThis);
78
+ });
79
+ break;
80
+ }
81
+ }
82
+ return what;
83
+ }
84
+ export function kebabToPascal(str) {
85
+ return str
86
+ .split('-')
87
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
88
+ .join('');
89
+ }
90
+ export function debounce(func, milliseconds) {
91
+ let timeout;
92
+ return () => {
93
+ clearTimeout(timeout);
94
+ timeout = setTimeout(function () {
95
+ func(...arguments);
96
+ }, milliseconds);
97
+ };
98
+ }
99
+ export function htmlToElements(html) {
100
+ let template = document.createElement('template');
101
+ html = html.replace(/<([A-Za-z0-9\-]*)([^>\/]*)(\/>)/gi, '<$1$2></$1>');
102
+ template.innerHTML = html;
103
+ template.content.normalize();
104
+ return Array.from(template.content.childNodes);
105
+ }
106
+ export function copyGettersSetters(source, target) {
107
+ const descriptors = Object.getOwnPropertyDescriptors(source);
108
+ for (const [key, descriptor] of Object.entries(descriptors)) {
109
+ if ('get' in descriptor || 'set' in descriptor) {
110
+ Object.defineProperty(target, key, descriptor);
111
+ }
112
+ }
113
+ }
package/dist/helpers.ts CHANGED
@@ -1,13 +1,41 @@
1
- 'use strict';
1
+ /**
2
+ * Determines whether the current runtime environment is a web browser.
3
+ *
4
+ * This function works by attempting to access `this` within an anonymous function
5
+ * and checking if it refers to the global `window` object. It returns `true`
6
+ * when executed in a browser context, and `false` otherwise (e.g., Node.js).
7
+ *
8
+ * Note: While modern environments typically provide more reliable detection mechanisms,
9
+ * this fallback remains compatible across older runtimes where other detection techniques
10
+ * may be unreliable.
11
+ *
12
+ * @returns {boolean} `true` if the code is running in a browser environment, `false` otherwise.
13
+ */
14
+ export function isBrowser() {
15
+ return window ? true : false;
16
+ }
2
17
 
18
+ /**
19
+ * Interface for objects that can have string or symbol keys with any type of values.
20
+ */
3
21
  export interface LooseObject {
4
22
  [key: string | symbol]: any;
5
23
  }
6
24
 
25
+ /**
26
+ * Checks if a value is a native JavaScript object (created via {} or new Object()).
27
+ * @param value - The value to check.
28
+ * @returns True if the value is a plain object, false otherwise.
29
+ */
7
30
  export function isNativeObject(value: any): boolean {
8
31
  return value && value['constructor'] && value.constructor === Object;
9
32
  }
10
33
 
34
+ /**
35
+ * Recursively sorts all keys in an object alphabetically, preserving nested structure.
36
+ * @param o - The object to sort.
37
+ * @returns A new object with sorted keys at all levels.
38
+ */
11
39
  export function sortObject(o: LooseObject): LooseObject {
12
40
  let result: LooseObject = {},
13
41
  keys = [...Object.keys(o)].sort();
@@ -23,6 +51,13 @@ export function sortObject(o: LooseObject): LooseObject {
23
51
  return result;
24
52
  }
25
53
 
54
+ /**
55
+ * Flattens a nested object into a single-level object with dot-separated keys.
56
+ * Optionally sorts the resulting flat keys alphabetically (default: true).
57
+ * @param o - The object to flatten.
58
+ * @param doSort - Whether to sort the resulting keys (default: true).
59
+ * @returns A flattened object with dot-notation keys.
60
+ */
26
61
  export function flattenObject(o: LooseObject, doSort: boolean = true): LooseObject {
27
62
  let result: LooseObject = {};
28
63
 
@@ -41,10 +76,22 @@ export function flattenObject(o: LooseObject, doSort: boolean = true): LooseObje
41
76
  return doSort ? sortObject(result) : result;
42
77
  }
43
78
 
79
+ /**
80
+ * Compares two objects for deep equality by flattening them and comparing their JSON representations.
81
+ * @param o1 - The first object to compare.
82
+ * @param o2 - The second object to compare.
83
+ * @returns True if the objects are deeply equal, false otherwise.
84
+ */
44
85
  export function deepEqual(o1: LooseObject, o2: LooseObject): boolean {
45
86
  return JSON.stringify(flattenObject(o1)) === JSON.stringify(flattenObject(o2));
46
87
  }
47
88
 
89
+ /**
90
+ * Retrieves a nested value from an object using a namespace path (dot-separated).
91
+ * @param ns - The namespace string or array of keys representing the path.
92
+ * @param obj - The object to traverse.
93
+ * @returns The value at the specified namespace path, or undefined if not found.
94
+ */
48
95
  export function nameSpace(ns: string | string[], obj: LooseObject = {}): any {
49
96
  let nsa = ns.constructor === String ? ns.split('.') : ns;
50
97
 
@@ -58,6 +105,14 @@ export function nameSpace(ns: string | string[], obj: LooseObject = {}): any {
58
105
  return obj[nsa[0]];
59
106
  }
60
107
 
108
+ /**
109
+ * Parses a template string or object, replacing placeholders in the form of {key.path} with
110
+ * corresponding values from the provided data object using namespace resolution.
111
+ * Also recursively processes objects and arrays within the input.
112
+ * @param what - The string or object to parse (can contain {{placeholder}} expressions).
113
+ * @param parseThis - The object containing values for placeholder substitution.
114
+ * @returns The parsed result, with placeholders replaced by actual values.
115
+ */
61
116
  export function parse(what: string | object | any[], parseThis: object) {
62
117
  var args = Array.from(arguments);
63
118
 
@@ -98,6 +153,12 @@ export function parse(what: string | object | any[], parseThis: object) {
98
153
  return what;
99
154
  }
100
155
 
156
+ /**
157
+ * Converts a kebab-case string to PascalCase.
158
+ * Example: 'my-component-name' -> 'MyComponentName'
159
+ * @param str - The kebab-case string to convert.
160
+ * @returns The converted PascalCase string.
161
+ */
101
162
  export function kebabToPascal(str: string) {
102
163
  return str
103
164
  .split('-') // Split by hyphens
@@ -105,6 +166,14 @@ export function kebabToPascal(str: string) {
105
166
  .join(''); // Join without spaces
106
167
  }
107
168
 
169
+ /**
170
+ * Creates a debounced version of a function that delays execution until after the specified
171
+ * number of milliseconds have elapsed since the last time this function was invoked.
172
+ * Useful for rate-limiting expensive operations like event handlers or API calls.
173
+ * @param func - The function to debounce.
174
+ * @param milliseconds - The number of milliseconds to delay execution.
175
+ * @returns A debounced version of the input function.
176
+ */
108
177
  export function debounce(func: Function, milliseconds: number): Function {
109
178
  let timeout: any;
110
179
  return () => {
@@ -116,6 +185,12 @@ export function debounce(func: Function, milliseconds: number): Function {
116
185
  };
117
186
  }
118
187
 
188
+ /**
189
+ * Parses an HTML string and returns an array of DOM elements.
190
+ * Handles self-closing tags (XHTML style) by converting them to full closing tags first.
191
+ * @param html - The HTML string to parse.
192
+ * @returns An array of HTMLElement objects representing the parsed HTML nodes.
193
+ */
119
194
  export function htmlToElements(html: string): HTMLElement[] {
120
195
  let template = document.createElement('template');
121
196
 
@@ -128,6 +203,12 @@ export function htmlToElements(html: string): HTMLElement[] {
128
203
  return Array.from(template.content.childNodes) as HTMLElement[];
129
204
  }
130
205
 
206
+ /**
207
+ * Copies all getter and setter properties from a source object to a target object.
208
+ * Does not copy methods or regular data properties—only accessor properties (get/set).
209
+ * @param source - The source object with getters and/or setters to copy.
210
+ * @param target - The target object to which the accessors will be copied.
211
+ */
131
212
  export function copyGettersSetters(source: LooseObject, target: LooseObject) {
132
213
  const descriptors = Object.getOwnPropertyDescriptors(source);
133
214
  for (const [key, descriptor] of Object.entries(descriptors)) {
package/dist/main.mjs ADDED
@@ -0,0 +1 @@
1
+ function e(t){let n={};return[...Object.keys(t)].sort().forEach((a=>{var l;(l=t[a])&&l.constructor&&l.constructor===Object?n[a]=e(t[a]):n[a]=t[a]})),n}function t(n,a=!0){let l={};return[...Object.getOwnPropertyNames(n)].forEach((e=>{if(n[e]instanceof Object){const a=t(n[e],!1);l[e]={};for(const t in a)l[e+"."+t]=a[t]}else l[e]=n[e]})),a?e(l):l}function n(e,t){const n=Object.getOwnPropertyDescriptors(e);for(const[e,a]of Object.entries(n))("get"in a||"set"in a)&&Object.defineProperty(t,e,a)}function a(e){return"_mc"+e[0].toUpperCase()+e.substring(1)}function l(e,t,n,l=!1){return e.addEventListener(a(t),n,{capture:!1,once:l}),n}class r{stopped=!1;constructor(){}stop(){this.stopped=!0}}function s(e,t,n={},l="l",s){const i=/^_mc/.test(t)?t:a(t),o=s||new r,c=new CustomEvent(i,{detail:{data:n,control:o},bubbles:!1});o.stopped||/l/.test(l)&&(e.dispatchEvent(c),c.stopImmediatePropagation(),o.stopped)||/[ud]/.test(l)&&(-1===l.indexOf("l")&&(l+="l"),setTimeout((()=>{if(l.indexOf("u")>-1){let t=e.parentElement?.closest("[data-mc]");t&&t?._mc.trigger(i,n,l,o)}if(l.indexOf("d")>-1){let t=e._mc.children();t&&t.forEach((e=>e.trigger(i,n,l)))}}),0))}const i=new Function("try {return this===window;}catch(e){ return false;}");function o(e){return e&&e instanceof HTMLElement}class c{_target;constructor(e){var t;this._target=new WeakRef(e||{}),function(e){if(!i()||e.constructor===u)return;const t=e.target;if(t instanceof HTMLElement){let n=e;Object.getOwnPropertyNames(n.constructor.prototype).filter((e=>/^(on[A-Z]|one[A-Z])/.test(e)&&"function"==typeof n.constructor.prototype[e])).forEach((e=>{let a=/^one/.test(e),r=e.replace(/^on[e]{0,1}/,""),s=r.toLowerCase();if("on"+s in t){let a=t=>{n[e](t)};t.addEventListener(s,a)}else l(t,r,(t=>{n[e](t)}),a)}))}}(this),t=this,setTimeout((()=>{t.constructor!==u&&t.trigger("Init")}),0)}get target(){return this._target.deref()}}class u extends c{constructor(e){super(e)}static add(e,t,n={}){return function(e,t,n={}){let a=e;a._mc||(a._mc=new u(e));const l=a._mc;return t.length&&!l[t]&&(l[t]=n,e instanceof HTMLElement&&!e.getAttribute("data-mc")&&e.setAttribute("data-mc","")),a}(e,t,n)}static remove(e,t){!function(e,t){if(!t)return;const n=e?._mc||{};n&&n[t]&&delete n[t],!Object.keys(n).length&&e instanceof HTMLElement&&e.hasAttribute("data-mc")&&e.removeAttribute("data-mc")}(e,t)}off(e,t){!function(e,t,n){e.removeEventListener(a(t),n)}(this.target,e,t)}on(e,t){return l(this.target,e,t)}one(e,t){return function(e,t,n){return l(e,t,n,!0),n}(this.target,e,t)}trigger(e,t={},n="l",a){s(this.target,e,t,n,a)}ancestors(){let e=this?.target,t=[];if(!o(e))return[];for(;e=e.closest("[data-mc]");)e?._mc&&t.push(e?._mc);return t.length?t:null}closest(){let e=this.target;return o(e)&&e.closest("[data-mc]")?._mc||null}children(){let e=this?.target;return o(e)?function(e){const t=Math.random().toString().replace(".",""),n='[data-mc]:not([temp_id="'+t+'"] [data-mc] [data-mc])';e.setAttribute("temp_id",t);const a=[...e.querySelectorAll(n)].filter((e=>!!e._mc)).map((e=>e._mc));return e.removeAttribute("temp_id"),a.length?a:null}(e):null}descendants(){const e=this?.target;if(!o(e))return[];const t=[...e.querySelectorAll("[data-mc]")].filter((e=>!!e._mc)).map((e=>e._mc));return t.length?t:null}}function p(e){return null!=e&&"object"==typeof e}function d(e){let t=new Set,n=/\{[^\{\}]*\}/g;return(e.getAttributeNames?e.getAttributeNames():[]).filter((t=>e.getAttribute(t)?.match(n))).forEach((a=>{new Set(e.getAttribute(a)?.match(n)).forEach((e=>t.add(e.replace(/[\{\}]/g,""))))})),[...e.childNodes].filter((e=>3===e.nodeType&&(e.nodeValue||"").match(n))).forEach((e=>{(e?.nodeValue||"").match(n).forEach((e=>t.add(e.replace(/[\{\}]/g,""))))})),[...t]}new Set;const b=[HTMLInputElement,HTMLSelectElement,HTMLTextAreaElement];class m extends u{data={};oldDisplay="";constructor(e){super(e),d(e).forEach((e=>this.data[e]="")),g(this),this.#e()}#e(){let e=this.target,t=!1;(e.getAttributeNames?e.getAttributeNames():[]).filter((t=>e.getAttribute(t)?.match(/\{[^\{\}]*\}/g))).forEach((t=>{let n=new Set(e.getAttribute(t)?.match(/\{[^\{\}]*\}/g));var a;this.data.subscribe((a=e.getAttribute(t),l=>{let r=a;n.forEach((e=>{r=r.replace(e,l[e.replace(/[\{\}]/g,"")])})),e.setAttribute(t,r)}))})),[...e.childNodes].filter((e=>3===e.nodeType&&(e.nodeValue||"").match(/\{[^\{\}]*\}/g))).forEach((n=>{t=!0;let a=new Set(n.nodeValue.match(/\{[^\{\}]*\}/g));this.data.subscribe((t=>l=>{let r=t;a.forEach((e=>{r=r.replace(e,l[e.replace(/[\{\}]/g,"")])})),this.oldDisplay?e.style=this.oldDisplay:e.removeAttribute("style"),delete this.oldDisplay,n.nodeValue=r})(n.nodeValue)),this.oldDisplay=e.getAttribute("style")||"",t&&!b.includes(e.constructor)&&"FIELDSET"!==e.tagName&&(e.style.display="none")}))}onData(e){let t={};Object.keys(e.detail.data).forEach((n=>{this.data.hasOwnProperty(n)&&(t[n]=e.detail.data[n])})),JSON.stringify(t)!==JSON.stringify(this.data)&&Object.assign(this.data,t)}}const h={};function f(e){let n=e.constructor.name;return h[n]||function(e){const n=e.constructor,a=n.name;let l=class extends n{#t=!0;#n=[];constructor(e){super(e),this.constructor.callbacks=[],p(e)&&Object.getOwnPropertyNames(e).forEach((t=>{this[t]=e[t]}))}get callbacks(){return this.#n}set callbacks(e){this.#n=e}subscribe(e){return this.#n.push(e),this}bind(e){if(p(this.valueOf())){!function(e){var t;(t=e,[t].concat([...t.querySelectorAll("*")]).filter((e=>!!d(e).length)).filter((e=>"STYLE"!==e?.tagName)).reverse()).forEach((e=>{e?._mc?._placeholders||u.add(e,"_placeholders",new m(e))}))}(e);const n=[e,...e.querySelectorAll("[data-mc]")].filter((e=>!!e?._mc?._placeholders)),a=function(e){let a=t(e);n.forEach((e=>{e&&s(e,"data",a)}))};this.#n.push(a),this.notify()}else console.warn("IGNORED - cannot bind a non-Object to the dom, IS:",typeof this.valueOf(),this);return this}notify(e){let t=p(this.valueOf())&&this.valueOf()instanceof Array==0?structuredClone(Object.assign({},this)):this.valueOf();return void 0!==e&&(this.#t=e),this.#t&&this.#n.forEach((e=>{e(t)})),this}};return h[a]=l,l}(e)}const g=(e,a)=>((a?[a]:[...Object.getOwnPropertyNames(e)]).forEach((a=>{if(e.hasOwnProperty(a)&&"function"!=typeof e[a]&&!e?.[a]?.constructor?.prototype?.subscribe){if("_"===a[0]||void 0===e?.[a])return e;let l=e[a],r=new(f(l))(l),s=function(){let e;return()=>{clearTimeout(e),e=setTimeout((function(){!function(){r.notify()}(...arguments)}),0)}}();Object.defineProperty(e,a,{enumerable:!0,get(){if(p(r.valueOf())){let e=structuredClone(Object.assign({},r));setTimeout((function(){var n,a;n=structuredClone(Object.assign({},r)),a=e,JSON.stringify(t(n))!==JSON.stringify(t(a))&&s()}),0)}return r},set(e){if(typeof e==typeof r.valueOf())if(p(r.valueOf())&&r.valueOf()instanceof Array==0)void 0===r?.constructor?.prototype?.subscribe&&(r=new(f(e))(structuredClone(Object.assign({},e))),n(e,r)),Object.assign(r,e),r.notify();else{let t=r.valueOf(),n=r.callbacks;e!==t&&(r=new(f(e))(e),r.callbacks=n,s())}else console.warn("IGNORED - cannot change observable types, IS:",typeof r.valueOf(),r,"- CANNOT BECOME:",typeof e)}}),e[a]=l}})),e);class y{constructor(e){}}class v extends y{#a;constructor(e){super(e),this.#a=e}get val(){return new FormData(this.#a.form).get(this.#a.name)}set val(e){[...this.#a.form.querySelectorAll(`input[type="radio"][name="${this.#a.name}"]`)].filter((t=>t.getAttribute("value")===e)).shift().checked=!0}}class E extends y{#a;constructor(e){super(e),this.#a=e}get val(){return this.#a.checked}set val(e){this.#a.checked=e}}class O extends y{#a;constructor(e){super(e),this.#a=e}get val(){return this.#a.value}set val(e){this.#a.value=e}}class w extends y{#a;#l=!1;constructor(e){super(e),this.#a=e,this.#l=e.multiple}get val(){const e=[];return[...this.#a.querySelectorAll("option")].filter((e=>e.selected)).forEach((t=>{e.push(t.value)})),this.#l?e:e.length?e[0]:""}set val(e){e="string"==typeof e?[e]:e,[...this.#a.querySelectorAll("option")].forEach((t=>{e.indexOf(t.value)>-1?t.selected=!0:t.selected=!1}))}}class k extends y{#a;constructor(e){super(e),this.#a=e}get val(){return this.#a.value||""}set val(e){this.#a.value=e}}!function(e,t,n=[]){if(customElements.get(e))return void console.warn(`IGNORED: Custom element "${e}" is already defined!`);if(!e.includes("-"))throw new Error('createCustomElement: tagName must contain at least one hyphen ("-").');let a=e.split("-").map((e=>e.charAt(0).toUpperCase()+e.slice(1))).join("");t.prototype instanceof c?customElements.define(e,function(e,t){return Object.defineProperty(t,"name",{value:e})}(a,class extends HTMLElement{constructor(e){super(),u.add(this,a,new t(this,e))}connectedCallback(){this.dispatchEvent(new CustomEvent("_mcConnected"))}connectedMoveCallback(){this.dispatchEvent(new CustomEvent("_mcConnectedMove"))}disconnectedCallback(){this.dispatchEvent(new CustomEvent("_mcDisconnected"))}adoptedCallback(){this.dispatchEvent(new CustomEvent("_mcAdopted"))}attributeChangedCallback(e,t,n){this.dispatchEvent(new CustomEvent("_mcAttributeChanged",{detail:{name:e,oldValue:t,newValue:n}}))}formResetCallback(){this.dispatchEvent(new CustomEvent("_mcFormReset"))}static get observedAttributes(){return n}})):customElements.define(e,t)}("test-testform",class extends u{data;constructor(e){super(e);let t=this.target.attachShadow({mode:"open"});t.innerHTML=`<style>${this.css}</style>${this.template}`,function(e,t,a){let l=(e=>{let t=[...(e||document).querySelectorAll("input,select,textarea")],n={};return t.forEach((e=>{const t=e.getAttribute("name");if(t){let a=!1;switch((e=>e.tagName.toLowerCase())(e)){case"input":switch(e.type){case"radio":a=new v(e);break;case"checkbox":a=new E(e);break;default:a=new O(e)}break;case"select":a=new w(e);break;case"textarea":a=new k(e);break;default:console.warn("handler missing:",t,"for",e.tagName.toLowerCase(),e)}a&&(u.add(e,t,a),Object.defineProperty(n,t,{enumerable:!0,configurable:!0,get:()=>a.val,set:e=>{a.val!==e&&(a.val=e)}}))}})),n})(a);e[t]=Object.assign({},l),g(e,t),n(l,e[t]),[...a.querySelectorAll("input, textarea, select")].forEach((n=>{n.addEventListener("input",(n=>{e[t].notify()}))}))}(this,"data",t),this.data.bind(t),fetch("./testform.json").then((e=>e.json())).then((e=>{this.data=e}))}get template(){return'\n <form>\n \x3c!-- sample form to start with --\x3e\n <fieldset>\n <legend>Inputs</legend>\n <label>text</label><input type="text" name="textInput" value="Hallo" />&nbsp;<strong>2wB:&nbsp;</strong>{textInput}<br />\n <label>color</label><input type="color" name="colorInput" value="#000000" /><br />\n <label>date</label><input type="date" name="dateInput" value="" /><br />\n <label>datetime-local</label><input type="datetime-local" name="dtlInput" value="" /><br />\n <label>email</label><input type="email" name="email" value="" /><br />\n <label>file</label><input type="file" name="fileInput" value="" /><br />\n <label>hidden</label><input type="hidden" name="hiddenInput" value="hiddenVal" /><br />\n <label>month</label><input type="month" name="month" value="" /><br />\n <label>number</label><input type="number" name="number" value="" /><br />\n <label>password</label><input type="password" name="passInput" value="" /><br />\n <label>range</label><input type="range" name="rangeInput" value="" /><br />\n <label>search</label><input type="search" name="searchInput" value="" /><br />\n <label>tel</label><input type="tel" name="telInput" value="" /><br />\n <label>time</label><input type="time" name="timeInput" value="" /><br />\n <label>url</label><input type="url" name="urlInput" value="" /><br />\n <label>week</label><input type="week" name="weekInput" value="" /><br />\n </fieldset>\n\n <fieldset>\n <legend>Selects&nbsp;<strong>2wB:&nbsp;</strong>&nbsp;<span>"{singleSelect}"</span>&nbsp;<span>[{multiSelect}]</span></legend>\n\n <label>singleSelect</label>\n <select name="singleSelect">\n <option disabled selected value> -- select -- </option>\n <option value="1">Option 1</option>\n <option value="2">Option 2</option>\n <option value="3">Option 3</option>\n <option value="4">Option 4</option>\n </select><br />\n\n <label>multiSelect</label>\n <select name="multiSelect" multiple="multiple" type="select-multiple">\n <option value="1">Option 1</option>\n <option value="2" selected=true>Option 2</option>\n <option value="3">Option 3</option>\n <option value="4" selected=true>Option 4</option>\n </select>\n\n </fieldset>\n\n <fieldset>\n <legend>Radiobuttons&nbsp;<strong>2wB:&nbsp;</strong><span>"{myRadio}"</span></legend>\n\n <label>radio1</label><input type="radio" name="myRadio" value="radio1"><br />\n <label>radio2</label><input type="radio" name="myRadio" value="radio2"><br />\n <label>radio3</label><input type="radio" name="myRadio" value="radio3"><br />\n\n </fieldset>\n\n <fieldset>\n <legend>Checkboxes</legend>\n <label>cb1</label><input type="checkbox" name="cb1"><br />\n <label>cb2</label><input type="checkbox" name="cb2"><br />\n <label>cb3</label><input type="checkbox" name="cb3"><br />\n <label>cb4</label><input type="checkbox" name="cb4"><br />\n <label>cb5</label><input type="checkbox" name="cb5"><br />\n\n </fieldset>\n\n <fieldset>\n <legend>TextArea</legend>\n <label>textArea</label>\n <textarea name="textArea">abc</textarea>\n </fieldset>\n\n </form>\n '}get css(){return'\nlegend {\n display: inherit;\n margin: inherit;\n vertical-align: inherit;\n font-weight: bold;\n}\n\nlabel {\n display: inline-block;\n margin-top: 1em;\n margin-left: 5px;\n vertical-align: top;\n min-width:10em;\n text-align: right;\n}\n\nform input,\nform select,\nform textarea {\n display: inline-block;\n margin-left: 5px;\n margin-top: 1.1em;\n}\n\ninput[type="radio"],\ninput[type="checkbox"] {\n margin-top: 1.3em;\n}\n\nbody > form {\n display: block;\n}\n\nform fieldset {\n border: 1px solid #aaaaaa;\n border-radius: 3px;\n margin: 0.5em 0;\n}\n '}});
@@ -0,0 +1,6 @@
1
+ export * from './helpers.js';
2
+ export * from './MC.js';
3
+ export * from './elements.js';
4
+ export * from './observables.js';
5
+ export * from './formValues.js';
6
+ //# sourceMappingURL=microcomponents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"microcomponents.d.ts","sourceRoot":"","sources":["../src/microcomponents.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+ export * from './helpers.js';
3
+ export * from './MC.js';
4
+ export * from './elements.js';
5
+ export * from './observables.js';
6
+ export * from './formValues.js';