cotomy 0.3.1 → 0.3.3

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 CHANGED
@@ -43,6 +43,7 @@ The View layer provides thin wrappers around DOM elements and window events.
43
43
  - Static helpers
44
44
  - `CotomyElement.encodeHtml(text)`
45
45
  - `CotomyElement.first(selector, type?)`
46
+ - `CotomyElement.last(selector, type?)`
46
47
  - `CotomyElement.find(selector, type?)`
47
48
  - `CotomyElement.contains(selector)` / `CotomyElement.containsById(id)`
48
49
  - `CotomyElement.byId(id, type?)`
@@ -71,7 +72,7 @@ The View layer provides thin wrappers around DOM elements and window events.
71
72
  - `firstChild(selector = "*", type?)`
72
73
  - `lastChild(selector = "*", type?)`
73
74
  - `closest(selector, type?)`
74
- - `find(selector, type?)` / `first(selector = "*", type?)` / `contains(selector)`
75
+ - `find(selector, type?)` / `first(selector = "*", type?)` / `last(selector = "*", type?)` / `contains(selector)`
75
76
  - `append(child): this` / `prepend(child): this` / `appendAll(children): this`
76
77
  - `insertBefore(sibling): this` / `insertAfter(sibling): this`
77
78
  - `appendTo(target): this` / `prependTo(target): this`
@@ -99,6 +100,8 @@ The View layer provides thin wrappers around DOM elements and window events.
99
100
  - Focus: `focus`, `blur`, `focusin`, `focusout`
100
101
  - Viewport: `inview`, `outview` (uses `IntersectionObserver`)
101
102
  - Layout (custom): `resize`, `scroll`, `changelayout` — requires `listenLayoutEvents()` on the element
103
+ - Move lifecycle: `cotomy:transitstart`, `cotomy:transitend` — emitted automatically by `append`, `prepend`, `insertBefore/After`, `appendTo`, and `prependTo`. While moving, the element (and its descendants) receive a temporary `data-cotomy-moving` attribute so removal observers know the node is still in transit.
104
+ - Removal: `removed` — fired when an element actually leaves the DOM (MutationObserver-backed). Because `cotomy:transitstart`/`transitend` manage the `data-cotomy-moving` flag, `removed` only runs for true detachments, making it safe for cleanup.
102
105
  - File: `filedrop(handler: (files: File[]) => void)`
103
106
 
104
107
  Example (scoped CSS and events):
@@ -892,6 +892,10 @@ class CotomyElement {
892
892
  const ctor = (type ?? CotomyElement);
893
893
  return new ctor(element);
894
894
  }
895
+ static last(selector, type) {
896
+ const elements = this.find(selector, type);
897
+ return elements.pop();
898
+ }
895
899
  static find(selector, type) {
896
900
  const elements = document.querySelectorAll(selector);
897
901
  const ctor = (type ?? CotomyElement);
@@ -939,10 +943,24 @@ class CotomyElement {
939
943
  }
940
944
  }
941
945
  }
942
- this.removed(() => {
943
- this._element = CotomyElement.createHTMLElement(`<div data-cotomy-invalidated style="display: none;"></div>`);
944
- EventRegistry.instance.clear(this);
945
- });
946
+ if (!this.instanceId) {
947
+ this.attribute("data-cotomy-instance", `__cotomy_instance_${cuid_default()()}`);
948
+ this.removed(() => {
949
+ this._element = CotomyElement.createHTMLElement(`<div data-cotomy-invalidated style="display: none;"></div>`);
950
+ EventRegistry.instance.clear(this);
951
+ });
952
+ this.on("cotomy:transitstart", () => {
953
+ this.attribute("data-cotomy-moving", "");
954
+ this.find("[data-cotomy-instance]").forEach(e => e.attribute("data-cotomy-moving", ""));
955
+ });
956
+ this.on("cotomy:transitend", () => {
957
+ this.attribute("data-cotomy-moving", null);
958
+ this.find("[data-cotomy-instance]").forEach(e => e.attribute("data-cotomy-moving", null));
959
+ });
960
+ }
961
+ }
962
+ get instanceId() {
963
+ return this.attribute("data-cotomy-instance");
946
964
  }
947
965
  get scopeId() {
948
966
  if (!this._scopeId) {
@@ -1389,15 +1407,32 @@ class CotomyElement {
1389
1407
  const elements = this.find(selector, type);
1390
1408
  return elements.shift() ?? undefined;
1391
1409
  }
1410
+ last(selector = "*", type) {
1411
+ const elements = this.find(selector, type);
1412
+ return elements.pop() ?? undefined;
1413
+ }
1392
1414
  contains(selector) {
1393
1415
  return this.find(selector).length > 0;
1394
1416
  }
1417
+ static runWithMoveEvents(target, operation) {
1418
+ target.trigger("cotomy:transitstart");
1419
+ try {
1420
+ operation();
1421
+ }
1422
+ finally {
1423
+ target.trigger("cotomy:transitend");
1424
+ }
1425
+ }
1395
1426
  prepend(prepend) {
1396
- this._element.prepend(prepend.element);
1427
+ CotomyElement.runWithMoveEvents(prepend, () => {
1428
+ this.element.prepend(prepend.element);
1429
+ });
1397
1430
  return this;
1398
1431
  }
1399
1432
  append(target) {
1400
- this.element.append(target.element);
1433
+ CotomyElement.runWithMoveEvents(target, () => {
1434
+ this.element.append(target.element);
1435
+ });
1401
1436
  return this;
1402
1437
  }
1403
1438
  appendAll(targets) {
@@ -1405,19 +1440,27 @@ class CotomyElement {
1405
1440
  return this;
1406
1441
  }
1407
1442
  insertBefore(append) {
1408
- this.element.before(append.element);
1443
+ CotomyElement.runWithMoveEvents(append, () => {
1444
+ this.element.before(append.element);
1445
+ });
1409
1446
  return this;
1410
1447
  }
1411
1448
  insertAfter(append) {
1412
- this.element.after(append.element);
1449
+ CotomyElement.runWithMoveEvents(append, () => {
1450
+ this.element.after(append.element);
1451
+ });
1413
1452
  return this;
1414
1453
  }
1415
1454
  appendTo(target) {
1416
- target.element.append(this.element);
1455
+ CotomyElement.runWithMoveEvents(this, () => {
1456
+ target.element.append(this.element);
1457
+ });
1417
1458
  return this;
1418
1459
  }
1419
1460
  prependTo(target) {
1420
- target.element.prepend(this.element);
1461
+ CotomyElement.runWithMoveEvents(this, () => {
1462
+ target.element.prepend(this.element);
1463
+ });
1421
1464
  return this;
1422
1465
  }
1423
1466
  trigger(event, e) {
@@ -1815,7 +1858,9 @@ class CotomyWindow {
1815
1858
  mutation.removedNodes.forEach(node => {
1816
1859
  if (typeof HTMLElement !== "undefined" && node instanceof HTMLElement) {
1817
1860
  const element = new CotomyElement(node);
1818
- element.trigger("removed");
1861
+ if (!element.hasAttribute("data-cotomy-moving") && !element.attached) {
1862
+ element.trigger("removed");
1863
+ }
1819
1864
  }
1820
1865
  });
1821
1866
  });