cotomy 0.3.4 → 0.3.6
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 +37 -5
- package/dist/browser/cotomy.js +24 -16
- 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/api.js +15 -5
- package/dist/esm/api.js.map +1 -1
- package/dist/esm/view.js +9 -11
- package/dist/esm/view.js.map +1 -1
- package/dist/types/api.d.ts +4 -1
- package/dist/types/view.d.ts +0 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,10 +36,9 @@ The View layer provides thin wrappers around DOM elements and window events.
|
|
|
36
36
|
- `new CotomyElement({ html, css? })` — Creates from HTML and injects scoped CSS
|
|
37
37
|
- `new CotomyElement({ tagname, text?, css? })`
|
|
38
38
|
- Scoped CSS
|
|
39
|
-
- `scopeId: string`
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
42
|
-
- `stylable: boolean` — False for tags like `script`, `style`, `link`, `meta`
|
|
39
|
+
- `scopeId: string` - Returns the value stored in the element's `data-cotomy-scopeid` attribute
|
|
40
|
+
- `[scope]` placeholder in provided CSS is replaced by `[data-cotomy-scopeid="..."]`
|
|
41
|
+
- `stylable: boolean` - False for tags like `script`, `style`, `link`, `meta`
|
|
43
42
|
- Static helpers
|
|
44
43
|
- `CotomyElement.encodeHtml(text)`
|
|
45
44
|
- `CotomyElement.first(selector, type?)`
|
|
@@ -76,7 +75,7 @@ The View layer provides thin wrappers around DOM elements and window events.
|
|
|
76
75
|
- `append(child): this` / `prepend(child): this` / `appendAll(children): this`
|
|
77
76
|
- `insertBefore(sibling): this` / `insertAfter(sibling): this`
|
|
78
77
|
- `appendTo(target): this` / `prependTo(target): this`
|
|
79
|
-
- `clone(type?): CotomyElement`
|
|
78
|
+
- `clone(type?): CotomyElement` - Returns a deep-cloned element, optionally typed, and reassigns new `data-cotomy-scopeid` values to the clone and all descendants so scoped CSS and event registries stay isolated
|
|
80
79
|
- `clear(): this` — Removes all descendants and text
|
|
81
80
|
- `remove(): void`
|
|
82
81
|
- Geometry & visibility
|
|
@@ -121,6 +120,17 @@ panel.onSubTree("click", ".ok", () => console.log("clicked!"));
|
|
|
121
120
|
document.body.appendChild(panel.element);
|
|
122
121
|
```
|
|
123
122
|
|
|
123
|
+
## Testing
|
|
124
|
+
|
|
125
|
+
The scoped CSS replacement and scope-id isolation logic are covered by `tests/view.spec.ts`. Run the focused specs below to verify the behavior:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx vitest run tests/view.spec.ts -t "constructs from multiple sources and applies scoped css"
|
|
129
|
+
npx vitest run tests/view.spec.ts -t "assigns fresh scope ids when cloning, including descendants"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The first command ensures `[scope]` expands to `[data-cotomy-scopeid="..."]` in injected styles, while the second confirms that cloning reassigns new `data-cotomy-scopeid` attributes to the cloned tree.
|
|
133
|
+
|
|
124
134
|
### CotomyMetaElement
|
|
125
135
|
|
|
126
136
|
- `CotomyMetaElement.get(name): CotomyMetaElement`
|
|
@@ -220,6 +230,28 @@ The Form layer builds on `CotomyElement` for common form flows.
|
|
|
220
230
|
- `filler(type, (input, value))` — Register fillers; defaults provided for `datetime-local`, `checkbox`, `radio`
|
|
221
231
|
- Fills non-array, non-object fields by matching input/select/textarea `name`
|
|
222
232
|
|
|
233
|
+
#### View binding renderers
|
|
234
|
+
|
|
235
|
+
`CotomyViewRenderer` includes a few built-in helpers for `[data-cotomy-bindtype]`:
|
|
236
|
+
|
|
237
|
+
- `mail`, `tel`, `url` — Wrap the value in a corresponding anchor tag.
|
|
238
|
+
- `number` — Uses `Intl.NumberFormat` with `data-cotomy-locale`/`data-cotomy-currency` inheritance.
|
|
239
|
+
- `utc` — Treats the value as UTC (or appends `Z` when missing) and formats with `data-cotomy-format` (default `YYYY/MM/DD HH:mm`).
|
|
240
|
+
- `date` — Renders local dates with `data-cotomy-format` (default `YYYY/MM/DD`) when the input is a valid `Date` value.
|
|
241
|
+
|
|
242
|
+
Example:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
const view = new CotomyViewRenderer(
|
|
246
|
+
new CotomyElement(document.querySelector("#profile")!),
|
|
247
|
+
new CotomyBracketBindNameGenerator()
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
await view.applyAsync(apiResponse); // apiResponse is CotomyApiResponse from CotomyApi
|
|
251
|
+
// <span data-cotomy-bind="user.birthday" data-cotomy-bindtype="date" data-cotomy-format="MMM D, YYYY"></span>
|
|
252
|
+
// → renders localized date text if the API payload contains user.birthday
|
|
253
|
+
```
|
|
254
|
+
|
|
223
255
|
#### Array binding
|
|
224
256
|
|
|
225
257
|
- Both `CotomyViewRenderer.applyAsync` and `CotomyEntityFillApiForm.fillAsync` resolve array elements by index via the active `ICotomyBindNameGenerator` (dot style → `items[0].name`, bracket style → `items[0][name]`).
|
package/dist/browser/cotomy.js
CHANGED
|
@@ -916,7 +916,6 @@ class CotomyElement {
|
|
|
916
916
|
}
|
|
917
917
|
constructor(element) {
|
|
918
918
|
this._parentElement = null;
|
|
919
|
-
this._scopeId = null;
|
|
920
919
|
if (element instanceof HTMLElement) {
|
|
921
920
|
this._element = element;
|
|
922
921
|
}
|
|
@@ -944,7 +943,7 @@ class CotomyElement {
|
|
|
944
943
|
}
|
|
945
944
|
}
|
|
946
945
|
if (!this.instanceId) {
|
|
947
|
-
this.attribute("data-cotomy-instance",
|
|
946
|
+
this.attribute("data-cotomy-instance", cuid_default()());
|
|
948
947
|
this.removed(() => {
|
|
949
948
|
this._element = CotomyElement.createHTMLElement(`<div data-cotomy-invalidated style="display: none;"></div>`);
|
|
950
949
|
EventRegistry.instance.clear(this);
|
|
@@ -963,14 +962,10 @@ class CotomyElement {
|
|
|
963
962
|
return this.attribute("data-cotomy-instance");
|
|
964
963
|
}
|
|
965
964
|
get scopeId() {
|
|
966
|
-
if (!this.
|
|
967
|
-
this.
|
|
968
|
-
this.attribute(this._scopeId, "");
|
|
965
|
+
if (!this.hasAttribute("data-cotomy-scopeid")) {
|
|
966
|
+
this.attribute("data-cotomy-scopeid", cuid_default()());
|
|
969
967
|
}
|
|
970
|
-
return this.
|
|
971
|
-
}
|
|
972
|
-
get scopedSelector() {
|
|
973
|
-
return `[${this.scopeId}]`;
|
|
968
|
+
return this.attribute("data-cotomy-scopeid");
|
|
974
969
|
}
|
|
975
970
|
get stylable() {
|
|
976
971
|
return !["script", "style", "link", "meta"].includes(this.tagname);
|
|
@@ -983,7 +978,7 @@ class CotomyElement {
|
|
|
983
978
|
const cssid = this.scopedCssElementId;
|
|
984
979
|
CotomyElement.find(`#${cssid}`).forEach(e => e.remove());
|
|
985
980
|
const element = document.createElement("style");
|
|
986
|
-
const writeCss = css.replace(/\[scope\]/g, `[${this.scopeId}]`);
|
|
981
|
+
const writeCss = css.replace(/\[scope\]/g, `[data-cotomy-scopeid="${this.scopeId}"]`);
|
|
987
982
|
const node = document.createTextNode(writeCss);
|
|
988
983
|
element.appendChild(node);
|
|
989
984
|
element.id = cssid;
|
|
@@ -1014,7 +1009,10 @@ class CotomyElement {
|
|
|
1014
1009
|
}
|
|
1015
1010
|
clone(type) {
|
|
1016
1011
|
const ctor = (type ?? CotomyElement);
|
|
1017
|
-
|
|
1012
|
+
const cloned = new ctor(this.element.cloneNode(true));
|
|
1013
|
+
cloned.attribute("data-cotomy-scopeid", null);
|
|
1014
|
+
cloned.find("[data-cotomy-scopeid]").forEach(e => e.attribute("data-cotomy-scopeid", null));
|
|
1015
|
+
return cloned;
|
|
1018
1016
|
}
|
|
1019
1017
|
get tagname() {
|
|
1020
1018
|
return this.element.tagName.toLowerCase();
|
|
@@ -2220,6 +2218,10 @@ class CotomyViewRenderer {
|
|
|
2220
2218
|
this._renderers[type] = callback;
|
|
2221
2219
|
return this;
|
|
2222
2220
|
}
|
|
2221
|
+
get renderers() {
|
|
2222
|
+
this.initialize();
|
|
2223
|
+
return this._renderers;
|
|
2224
|
+
}
|
|
2223
2225
|
get initialized() {
|
|
2224
2226
|
return this._builded;
|
|
2225
2227
|
}
|
|
@@ -2259,6 +2261,15 @@ class CotomyViewRenderer {
|
|
|
2259
2261
|
}
|
|
2260
2262
|
}
|
|
2261
2263
|
});
|
|
2264
|
+
this.renderer("date", (element, value) => {
|
|
2265
|
+
if (value) {
|
|
2266
|
+
const date = new Date(value);
|
|
2267
|
+
if (!isNaN(date.getTime())) {
|
|
2268
|
+
const format = element.attribute("data-cotomy-format") ?? "YYYY/MM/DD";
|
|
2269
|
+
element.text = dayjs_min_default()(date).format(format);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2262
2273
|
this._builded = true;
|
|
2263
2274
|
}
|
|
2264
2275
|
return this;
|
|
@@ -2269,8 +2280,8 @@ class CotomyViewRenderer {
|
|
|
2269
2280
|
console.debug(`Binding data to element [data-cotomy-bind="${propertyName}"]:`, value);
|
|
2270
2281
|
}
|
|
2271
2282
|
const type = element.attribute("data-cotomy-bindtype")?.toLowerCase();
|
|
2272
|
-
if (type && this.
|
|
2273
|
-
this.
|
|
2283
|
+
if (type && this.renderers[type]) {
|
|
2284
|
+
this.renderers[type](element, value);
|
|
2274
2285
|
}
|
|
2275
2286
|
else {
|
|
2276
2287
|
element.text = String(value ?? "");
|
|
@@ -2310,9 +2321,6 @@ class CotomyViewRenderer {
|
|
|
2310
2321
|
}
|
|
2311
2322
|
}
|
|
2312
2323
|
async applyAsync(respose) {
|
|
2313
|
-
if (!this.initialized) {
|
|
2314
|
-
this.initialize();
|
|
2315
|
-
}
|
|
2316
2324
|
if (!respose.available) {
|
|
2317
2325
|
throw new Error("Response is not available.");
|
|
2318
2326
|
}
|