cotomy 0.3.16 → 0.3.17
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 +13 -9
- package/dist/browser/cotomy.js +27 -8
- package/dist/browser/cotomy.js.map +1 -1
- package/dist/browser/cotomy.min.js +1 -1
- package/dist/browser/cotomy.min.js.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/view.js +27 -8
- package/dist/esm/view.js.map +1 -1
- package/dist/types/view.d.ts +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ The View layer provides thin wrappers around DOM elements and window events.
|
|
|
38
38
|
- Scoped CSS
|
|
39
39
|
- `scopeId: string` - Returns the value stored in the element's `data-cotomy-scopeid` attribute
|
|
40
40
|
- `[scope]` placeholder in provided CSS is replaced by `[data-cotomy-scopeid="..."]`
|
|
41
|
+
- Scoped CSS text is kept on the instance; if the `<style id="css-${scopeId}">` is missing when the element is attached, it will be re-generated automatically
|
|
41
42
|
- `stylable: boolean` - False for tags like `script`, `style`, `link`, `meta`
|
|
42
43
|
- Static helpers
|
|
43
44
|
- `CotomyElement.encodeHtml(text)`
|
|
@@ -77,7 +78,7 @@ The View layer provides thin wrappers around DOM elements and window events.
|
|
|
77
78
|
- `insertBefore(sibling): this` / `insertAfter(sibling): this`
|
|
78
79
|
- `appendTo(target): this` / `prependTo(target): this`
|
|
79
80
|
- `comesBefore(target): boolean` / `comesAfter(target): boolean` — Checks DOM order (returns `false` for the same element or disconnected nodes)
|
|
80
|
-
- `clone(type?): CotomyElement` - Returns a deep-cloned element, optionally typed, and reassigns new `data-cotomy-instance
|
|
81
|
+
- `clone(type?): CotomyElement` - Returns a deep-cloned element, optionally typed, and reassigns a new `data-cotomy-instance` while preserving the `data-cotomy-scopeid` for scoped CSS sharing (strips `data-cotomy-moving`). Cloning an invalidated element (`data-cotomy-invalidated`) throws.
|
|
81
82
|
- `clear(): this` — Removes all descendants and text
|
|
82
83
|
- `remove(): void` — Explicitly non-chainable after removal
|
|
83
84
|
- Geometry & visibility
|
|
@@ -107,6 +108,7 @@ The View layer provides thin wrappers around DOM elements and window events.
|
|
|
107
108
|
- Layout (custom): `resize`, `scroll`, `changelayout` — requires `listenLayoutEvents()` on the element
|
|
108
109
|
- 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.
|
|
109
110
|
- 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.
|
|
111
|
+
- Registry isolation: イベントレジストリは `data-cotomy-instance`(インスタンスID)単位で管理され、クローンは新しいインスタンスIDを持つため、同じ `scopeId` を共有していてもリスナーが混線しません。
|
|
110
112
|
- File: `filedrop(handler: (files: File[]) => void)`
|
|
111
113
|
|
|
112
114
|
Example (scoped CSS and events):
|
|
@@ -128,18 +130,20 @@ document.body.appendChild(panel.element);
|
|
|
128
130
|
|
|
129
131
|
## Testing
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
Scoped CSS sharing/re-hydrationとインスタンス単位のイベント管理は `tests/view.spec.ts` に含まれています。主に確認したい場合は以下のコマンドで個別に実行できます:
|
|
132
134
|
|
|
133
135
|
```bash
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
npm test -- --run tests/view.spec.ts -t "constructs from multiple sources and applies scoped css"
|
|
137
|
+
npm test -- --run tests/view.spec.ts -t "preserves scope ids when cloning, including descendants"
|
|
138
|
+
npm test -- --run tests/view.spec.ts -t "regenerates instance ids and lifecycle hooks when cloning"
|
|
139
|
+
npm test -- --run tests/view.spec.ts -t "keeps event handlers isolated by instance even when sharing scope"
|
|
140
|
+
npm test -- --run tests/view.spec.ts -t "rehydrates scoped css when a clone shares scope after the original was removed"
|
|
141
|
+
npm test -- --run tests/view.spec.ts -t "strips moving flags when cloning"
|
|
142
|
+
npm test -- --run tests/view.spec.ts -t "throws when cloning an invalidated element"
|
|
143
|
+
npm test -- --run tests/view.spec.ts -t "compares document order with comesBefore/comesAfter"
|
|
140
144
|
```
|
|
141
145
|
|
|
142
|
-
|
|
146
|
+
普段は `npm test` で全体を実行できます。上記のコマンドでは `[scope]` 展開、スコープID共有のクローン挙動、インスタンス単位のイベント隔離、クローン後のスコープCSS再生成、移動フラグの除去、無効化要素のクローン拒否、DOM順序判定などをピンポイントで確認できます。
|
|
143
147
|
|
|
144
148
|
### CotomyMetaElement
|
|
145
149
|
|
package/dist/browser/cotomy.js
CHANGED
|
@@ -811,11 +811,11 @@ class EventRegistry {
|
|
|
811
811
|
return this._instance ?? (this._instance = new EventRegistry());
|
|
812
812
|
}
|
|
813
813
|
map(target) {
|
|
814
|
-
const
|
|
815
|
-
let registry = this._registry.get(
|
|
814
|
+
const instanceId = target.instanceId;
|
|
815
|
+
let registry = this._registry.get(instanceId);
|
|
816
816
|
if (!registry) {
|
|
817
817
|
registry = new HandlerRegistory(target);
|
|
818
|
-
this._registry.set(
|
|
818
|
+
this._registry.set(instanceId, registry);
|
|
819
819
|
}
|
|
820
820
|
return registry;
|
|
821
821
|
}
|
|
@@ -824,7 +824,7 @@ class EventRegistry {
|
|
|
824
824
|
registry.add(event, entry);
|
|
825
825
|
}
|
|
826
826
|
off(event, target, entry) {
|
|
827
|
-
const registry = this._registry.get(target.
|
|
827
|
+
const registry = this._registry.get(target.instanceId);
|
|
828
828
|
if (!registry)
|
|
829
829
|
return;
|
|
830
830
|
if (entry) {
|
|
@@ -834,11 +834,11 @@ class EventRegistry {
|
|
|
834
834
|
registry.remove(event);
|
|
835
835
|
}
|
|
836
836
|
if (registry.empty) {
|
|
837
|
-
this._registry.delete(target.
|
|
837
|
+
this._registry.delete(target.instanceId);
|
|
838
838
|
}
|
|
839
839
|
}
|
|
840
840
|
clear(target) {
|
|
841
|
-
this._registry.delete(target.
|
|
841
|
+
this._registry.delete(target.instanceId);
|
|
842
842
|
}
|
|
843
843
|
}
|
|
844
844
|
class CotomyScrollOptions {
|
|
@@ -1004,6 +1004,7 @@ class CotomyElement {
|
|
|
1004
1004
|
}
|
|
1005
1005
|
useScopedCss(css) {
|
|
1006
1006
|
if (css && this.stylable) {
|
|
1007
|
+
this._scopedCss = css;
|
|
1007
1008
|
const cssid = this.scopedCssElementId;
|
|
1008
1009
|
CotomyElement.find(`#${cssid}`).forEach(e => e.remove());
|
|
1009
1010
|
const element = document.createElement("style");
|
|
@@ -1015,11 +1016,22 @@ class CotomyElement {
|
|
|
1015
1016
|
|| new CotomyElement({ html: `<head></head>` }).prependTo(new CotomyElement(document.documentElement));
|
|
1016
1017
|
head.append(new CotomyElement(element));
|
|
1017
1018
|
this.removed(() => {
|
|
1018
|
-
|
|
1019
|
+
const hasSameScope = document.querySelector(`[data-cotomy-scopeid="${this.scopeId}"]`) !== null;
|
|
1020
|
+
if (!hasSameScope) {
|
|
1021
|
+
CotomyElement.find(`#${cssid}`).forEach(e => e.remove());
|
|
1022
|
+
}
|
|
1019
1023
|
});
|
|
1020
1024
|
}
|
|
1021
1025
|
return this;
|
|
1022
1026
|
}
|
|
1027
|
+
ensureScopedCss() {
|
|
1028
|
+
if (!this._scopedCss || !this.stylable)
|
|
1029
|
+
return;
|
|
1030
|
+
const cssid = this.scopedCssElementId;
|
|
1031
|
+
if (!document.getElementById(cssid)) {
|
|
1032
|
+
this.useScopedCss(this._scopedCss);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1023
1035
|
listenLayoutEvents() {
|
|
1024
1036
|
this.attribute(CotomyElement.LISTEN_LAYOUT_EVENTS_ATTRIBUTE, "");
|
|
1025
1037
|
return this;
|
|
@@ -1038,7 +1050,7 @@ class CotomyElement {
|
|
|
1038
1050
|
}
|
|
1039
1051
|
clone(type) {
|
|
1040
1052
|
const ctor = (type ?? CotomyElement);
|
|
1041
|
-
const ATTRIBUTES_TO_STRIP = ["data-cotomy-instance", "data-cotomy-
|
|
1053
|
+
const ATTRIBUTES_TO_STRIP = ["data-cotomy-instance", "data-cotomy-moving"];
|
|
1042
1054
|
const clonedElement = this.element.cloneNode(true);
|
|
1043
1055
|
if (clonedElement.hasAttribute("data-cotomy-invalidated")) {
|
|
1044
1056
|
throw new Error("Cannot clone an invalidated CotomyElement.");
|
|
@@ -1048,6 +1060,7 @@ class CotomyElement {
|
|
|
1048
1060
|
clonedElement.querySelectorAll(`[${attr}]`).forEach(el => el.removeAttribute(attr));
|
|
1049
1061
|
});
|
|
1050
1062
|
const cloned = new ctor(clonedElement);
|
|
1063
|
+
cloned._scopedCss = this._scopedCss;
|
|
1051
1064
|
return cloned;
|
|
1052
1065
|
}
|
|
1053
1066
|
get tagname() {
|
|
@@ -1605,12 +1618,14 @@ class CotomyElement {
|
|
|
1605
1618
|
CotomyElement.runWithMoveEvents(prepend, () => {
|
|
1606
1619
|
this.element.prepend(prepend.element);
|
|
1607
1620
|
});
|
|
1621
|
+
prepend.ensureScopedCss();
|
|
1608
1622
|
return this;
|
|
1609
1623
|
}
|
|
1610
1624
|
append(target) {
|
|
1611
1625
|
CotomyElement.runWithMoveEvents(target, () => {
|
|
1612
1626
|
this.element.append(target.element);
|
|
1613
1627
|
});
|
|
1628
|
+
target.ensureScopedCss();
|
|
1614
1629
|
return this;
|
|
1615
1630
|
}
|
|
1616
1631
|
appendAll(targets) {
|
|
@@ -1621,24 +1636,28 @@ class CotomyElement {
|
|
|
1621
1636
|
CotomyElement.runWithMoveEvents(append, () => {
|
|
1622
1637
|
this.element.before(append.element);
|
|
1623
1638
|
});
|
|
1639
|
+
append.ensureScopedCss();
|
|
1624
1640
|
return this;
|
|
1625
1641
|
}
|
|
1626
1642
|
insertAfter(append) {
|
|
1627
1643
|
CotomyElement.runWithMoveEvents(append, () => {
|
|
1628
1644
|
this.element.after(append.element);
|
|
1629
1645
|
});
|
|
1646
|
+
append.ensureScopedCss();
|
|
1630
1647
|
return this;
|
|
1631
1648
|
}
|
|
1632
1649
|
appendTo(target) {
|
|
1633
1650
|
CotomyElement.runWithMoveEvents(this, () => {
|
|
1634
1651
|
target.element.append(this.element);
|
|
1635
1652
|
});
|
|
1653
|
+
this.ensureScopedCss();
|
|
1636
1654
|
return this;
|
|
1637
1655
|
}
|
|
1638
1656
|
prependTo(target) {
|
|
1639
1657
|
CotomyElement.runWithMoveEvents(this, () => {
|
|
1640
1658
|
target.element.prepend(this.element);
|
|
1641
1659
|
});
|
|
1660
|
+
this.ensureScopedCss();
|
|
1642
1661
|
return this;
|
|
1643
1662
|
}
|
|
1644
1663
|
trigger(event, e) {
|