objs-core 2.0.2 → 2.1.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/objs.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Objs-core library
3
- * @version 2.0
3
+ * @version 2.1
4
4
  * @author Roman Torshin
5
5
  * @license Apache-2.0
6
6
  */
@@ -14,7 +14,7 @@ const __DEV__ = true;
14
14
  * @param {any} query - Selector, DOM element to use, an array of elements, inited ID or nothing for creating an element
15
15
  * @returns {Object} Objs instance with DOM manipulation methods
16
16
  */
17
- const o = (query) => {
17
+ const o = (query) => {
18
18
  let result = {
19
19
  els: [],
20
20
  ie: {},
@@ -22,6 +22,7 @@ const o = (query) => {
22
22
  parented: {},
23
23
  store: {},
24
24
  refs: {},
25
+ _refsByIndex: [],
25
26
  states: [],
26
27
  isDebug: false,
27
28
  currentState: "",
@@ -133,10 +134,49 @@ const o = (query) => {
133
134
  result.states = [];
134
135
  result.ie = {};
135
136
  }
137
+ if (Array.isArray(result._refsByIndex)) {
138
+ const currentLen = result._refsByIndex.length;
139
+ if (currentLen > ln) {
140
+ cycleObj(result._refsByIndex, (k) => {
141
+ const idx = +k;
142
+ if (idx >= ln) {
143
+ delete result._refsByIndex[idx];
144
+ }
145
+ });
146
+ result._refsByIndex.length = ln;
147
+ } else if (currentLen < ln) {
148
+ for (let idx = currentLen; idx < ln; idx++) {
149
+ result._refsByIndex[idx] = {};
150
+ }
151
+ }
152
+ }
136
153
  };
137
154
  // sets new objects to operate
138
155
  result.reset = o;
139
156
 
157
+ /**
158
+ * Auto-hydrate: after innerHTML is set, bind inited instances (e.g. those
159
+ * created and stored in the parent render) to the DOM nodes that came from
160
+ * the container's HTML, so the parent can control them via store/refs.
161
+ * Scopes by container so the elements from HTML are the Objs instances.
162
+ */
163
+ const hydrateDataOInitIn = (containerEl) => {
164
+ if (ssr || !containerEl.querySelectorAll) return;
165
+ const nodes = containerEl.querySelectorAll("[data-o-init]");
166
+ const byId = {};
167
+ nodes.forEach((node) => {
168
+ const id = node.getAttribute("data-o-init");
169
+ if (id === null) return;
170
+ if (!byId[id]) byId[id] = [];
171
+ byId[id].push(node);
172
+ });
173
+ cycleObj(byId, (id) => {
174
+ const inst = o.inits[id];
175
+ if (!inst) return;
176
+ inst.getSSR(Number(id), byId[id]);
177
+ });
178
+ };
179
+
140
180
  /**
141
181
  * Transform DOM elements based on state and props
142
182
  * @param {Element} el - DOM element to transform
@@ -186,7 +226,7 @@ const o = (query) => {
186
226
  ) {
187
227
  // insert html
188
228
  ["html", "innerHTML"].includes(s)
189
- ? (el.innerHTML = value)
229
+ ? (el.innerHTML = value, !ssr && hydrateDataOInitIn(el))
190
230
  : // className alias
191
231
  s === "className"
192
232
  ? el.setAttribute("class", value)
@@ -326,23 +366,23 @@ const o = (query) => {
326
366
 
327
367
  // creation elements for prop in props
328
368
  const newEl = (n, prop = {}) => {
329
- if (type(data) === objectType) {
330
- return D.createElement(data.tag || data.tagName || "div");
331
- } else {
332
- const newElem = D.createElement("div");
333
- newElem.innerHTML = type(data) === functionType ? data(prop) : data;
334
- if (newElem.children.length > ONE || !newElem.firstElementChild) {
335
- newElem.dataset.oInit = n;
336
- return newElem;
337
- } else {
338
- newElem.firstElementChild.dataset.oInit = n;
339
- return newElem.firstElementChild;
340
- }
369
+ const resolved = type(data) === functionType ? data(prop) : data;
370
+ if (type(resolved) === objectType) {
371
+ return D.createElement(resolved.tag || resolved.tagName || "div");
372
+ }
373
+ const newElem = D.createElement("div");
374
+ newElem.innerHTML = resolved;
375
+ if (newElem.children.length > ONE || !newElem.firstElementChild) {
376
+ newElem.dataset.oInit = n;
377
+ return newElem;
341
378
  }
379
+ newElem.firstElementChild.dataset.oInit = n;
380
+ return newElem.firstElementChild;
342
381
  };
343
382
 
344
383
  // properties creation
345
384
  const rawData = props; // raw argument before array-wrapping
385
+ if (!Array.isArray(props)) props = [props];
346
386
  !props.length ? (props = [props]) : props;
347
387
 
348
388
  // creating elements if no one was selected
@@ -383,19 +423,45 @@ const o = (query) => {
383
423
  if (creation) {
384
424
  buff["data-o-init"] = initN;
385
425
  buff["data-o-init-i"] = i;
426
+ if (buff.events) {
427
+ result._hydrateEvents = result._hydrateEvents || [];
428
+ result._hydrateEvents[i] = buff.events;
429
+ }
386
430
  }
387
431
  transform(el, buff, props[j ? i : 0]);
388
432
  }
389
433
  });
390
434
  if (creation) {
435
+ result._refsByIndex = [];
391
436
  result.refs = {};
392
- result.els.forEach((el) => {
437
+ result.els.forEach((el, idx) => {
393
438
  if (!el.querySelectorAll) return;
439
+ const refsForEl = {};
394
440
  el.querySelectorAll("[ref]").forEach((refEl) => {
395
- result.refs[refEl.getAttribute("ref")] = o(refEl);
396
- refEl.removeAttribute("ref");
441
+ const refName = refEl.getAttribute("ref");
442
+ const refInstance = o(refEl);
443
+ refsForEl[refName] = refInstance;
444
+ if (idx === 0) result.refs[refName] = refInstance;
397
445
  });
446
+ result._refsByIndex[idx] = refsForEl;
398
447
  });
448
+ if (!ssr && result._hydrateEvents) {
449
+ result._hydrateEvents.forEach((evts, idx) => {
450
+ if (!evts) return;
451
+ result.select(idx);
452
+ cycleObj(evts, (event) => {
453
+ const spec = evts[event];
454
+ if (type(spec) === objectType && spec.targetRef && type(spec.handler) === functionType) {
455
+ const refsForIdx = result._refsByIndex?.[idx] ?? result.refs;
456
+ const ref = refsForIdx?.[spec.targetRef];
457
+ if (ref) ref.on(event, spec.handler);
458
+ } else if (type(spec) === functionType) {
459
+ result.on(event, spec);
460
+ }
461
+ });
462
+ });
463
+ result.all();
464
+ }
399
465
  }
400
466
  }
401
467
 
@@ -409,24 +475,47 @@ const o = (query) => {
409
475
  });
410
476
  });
411
477
  const renderState = states.render || states;
412
- if (
413
- !ssr &&
414
- type(renderState) === objectType &&
415
- renderState.events &&
416
- renderState.ssr
417
- ) {
478
+ const hasStateEvents = !ssr && type(renderState) === objectType && renderState.events;
479
+ const hasHydrateEvents = !ssr && result._hydrateEvents && result._hydrateEvents.length;
480
+ if (hasStateEvents || hasHydrateEvents) {
418
481
  result.initSSRAfterGettingSSR = () => {
482
+ result._refsByIndex = [];
419
483
  result.refs = {};
420
- result.els.forEach((el) => {
484
+ result.els.forEach((el, idx) => {
421
485
  if (!el.querySelectorAll) return;
486
+ const refsForEl = {};
422
487
  el.querySelectorAll("[ref]").forEach((refEl) => {
423
- result.refs[refEl.getAttribute("ref")] = o(refEl);
488
+ const refName = refEl.getAttribute("ref");
489
+ const refInstance = o(refEl);
490
+ refsForEl[refName] = refInstance;
491
+ if (idx === 0) result.refs[refName] = refInstance;
424
492
  refEl.removeAttribute("ref");
425
493
  });
494
+ result._refsByIndex[idx] = refsForEl;
495
+ if (idx === 0) result.refs = refsForEl;
426
496
  });
427
- cycleObj(renderState.events, (event) => {
428
- result.on(event, renderState.events[event]);
429
- });
497
+ if (hasStateEvents) {
498
+ cycleObj(renderState.events, (event) => {
499
+ result.on(event, renderState.events[event]);
500
+ });
501
+ }
502
+ if (result._hydrateEvents) {
503
+ result._hydrateEvents.forEach((evts, idx) => {
504
+ if (!evts) return;
505
+ result.select(idx);
506
+ cycleObj(evts, (event) => {
507
+ const spec = evts[event];
508
+ if (type(spec) === objectType && spec.targetRef && type(spec.handler) === functionType) {
509
+ const refsForIdx = result._refsByIndex?.[idx] ?? result.refs;
510
+ const ref = refsForIdx?.[spec.targetRef];
511
+ if (ref) ref.on(event, spec.handler);
512
+ } else if (type(spec) === functionType) {
513
+ result.on(event, spec);
514
+ }
515
+ });
516
+ });
517
+ result.all();
518
+ }
430
519
  };
431
520
  }
432
521
  }, "init");
@@ -447,10 +536,13 @@ const o = (query) => {
447
536
  }, "connect");
448
537
 
449
538
  /**
450
- * Get SSR elements
539
+ * Get SSR elements: bind this instance to DOM nodes (by initId or from a list).
540
+ * When called from auto-hydration, fromEls are the nodes from the parent's HTML
541
+ * so the inited instance stored in the parent can control those elements.
451
542
  * @param {number} initId - Initialization ID
543
+ * @param {Element[]} [fromEls] - Optional list of elements to bind to (e.g. from containerEl)
452
544
  */
453
- result.getSSR = returner((initId) => {
545
+ result.getSSR = returner((initId, fromEls) => {
454
546
  typeVerify([[initId, [numberType, undefinedType]]]);
455
547
  const effectiveId = initId !== undefined ? initId : result.initID;
456
548
  if (
@@ -459,17 +551,35 @@ const o = (query) => {
459
551
  ) {
460
552
  return;
461
553
  }
462
- const ssrEls = o.D.querySelectorAll(`[data-o-init="${effectiveId}"]`);
554
+ const ssrEls =
555
+ fromEls && fromEls.length
556
+ ? fromEls
557
+ : o.D.querySelectorAll(`[data-o-init="${effectiveId}"]`);
463
558
 
464
- if (ssrEls.length && !result.els.length) {
559
+ if (ssrEls.length) {
465
560
  result.els = Array.from(ssrEls);
466
- result.initID = initId;
467
- o.inits[initId] = result;
561
+ if (initId !== undefined) {
562
+ result.initID = initId;
563
+ o.inits[initId] = result;
564
+ }
468
565
  setResultVals(false);
469
-
470
566
  if (type(result.initSSRAfterGettingSSR) === functionType) {
471
567
  result.initSSRAfterGettingSSR();
472
- delete result.initSSRAfterGettingSSR;
568
+ } else if (fromEls && fromEls.length) {
569
+ result._refsByIndex = [];
570
+ result.refs = {};
571
+ result.els.forEach((el, idx) => {
572
+ if (!el.querySelectorAll) return;
573
+ const refsForEl = {};
574
+ el.querySelectorAll("[ref]").forEach((refEl) => {
575
+ const refName = refEl.getAttribute("ref");
576
+ refsForEl[refName] = o(refEl);
577
+ if (idx === 0) result.refs[refName] = refsForEl[refName];
578
+ refEl.removeAttribute("ref");
579
+ });
580
+ result._refsByIndex[idx] = refsForEl;
581
+ if (idx === 0) result.refs = refsForEl;
582
+ });
473
583
  }
474
584
  }
475
585
  }, "getSSR");
@@ -648,18 +758,26 @@ const o = (query) => {
648
758
  }, "sample");
649
759
 
650
760
  /**
651
- * Select element to control
652
- * @param {number} i - Index of element to select
761
+ * Select element to control. Accepts index (number) or event: select(e) selects the element in this instance that contains e.target (e.g. the row that had the event).
762
+ * @param {number|Event} i - Index of element, or event object (uses e.target to find containing element)
653
763
  */
654
764
  result.select = returner((i) => {
655
- typeVerify([[i, [numberType, undefinedType]]]);
656
- if (i === u) {
657
- i = result.length - ONE;
658
- }
659
- start = i;
660
- finish = i;
661
- result.el = result.els[i];
765
+ let idx = i;
766
+ if (idx != null && type(idx) === objectType && idx.target && result.els.length) {
767
+ idx = result.els.findIndex((el) => el === idx.target || el.contains(idx.target));
768
+ if (idx < 0) idx = 0;
769
+ }
770
+ typeVerify([[idx, [numberType, undefinedType]]]);
771
+ if (idx === u) {
772
+ idx = result.length - ONE;
773
+ }
774
+ start = idx;
775
+ finish = idx;
776
+ result.el = result.els[idx];
662
777
  select = ONE;
778
+ if (Array.isArray(result._refsByIndex) && result._refsByIndex[idx]) {
779
+ result.refs = result._refsByIndex[idx];
780
+ }
663
781
  }, "select");
664
782
 
665
783
  /**
@@ -670,6 +788,9 @@ const o = (query) => {
670
788
  finish = 0;
671
789
  result.el = result.els[0];
672
790
  select = 0;
791
+ if (Array.isArray(result._refsByIndex) && result._refsByIndex.length) {
792
+ result.refs = result._refsByIndex[0] || {};
793
+ }
673
794
  }, "all");
674
795
 
675
796
  /**
@@ -708,7 +829,10 @@ const o = (query) => {
708
829
  j = finish;
709
830
  }
710
831
 
711
- result.els.splice(i, ONE);
832
+ result.els.splice(j, ONE);
833
+ if (Array.isArray(result._refsByIndex)) {
834
+ result._refsByIndex.splice(j, ONE);
835
+ }
712
836
  setResultVals();
713
837
  }, "skip");
714
838
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "objs-core",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "Lightweight (~6 kB) library for fast, AI-friendly front-end development: samples and state control, built-in store (createStore), routing, caching, and recording → Playwright tests. No build step; split design into samples and give them data and actions. Works standalone or alongside React; SSR and hydrate from server-rendered DOM. v2.0: exportPlaywrightTest(), refs, TypeScript definitions, recording in all builds.",
6
6
  "homepage": "https://en.fous.name/objs/",