cotomy 0.2.2 → 0.3.2

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/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- The MIT License (MIT)
2
- Copyright (c) 2025 Yasuhiro Arakawa
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining a copy
5
- of this software and associated documentation files (the "Software"), to deal
6
- in the Software without restriction, including without limitation the rights
7
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the Software is
9
- furnished to do so, subject to the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be included in all
12
- copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- SOFTWARE.
1
+ The MIT License (MIT)
2
+ Copyright (c) 2025 Yasuhiro Arakawa
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md CHANGED
@@ -1,304 +1,305 @@
1
- # Cotomy
2
-
3
- > This library targets ES2020+.
4
- > For older browsers (e.g. iOS 13 or IE), you will need a Polyfill such as `core-js`.
5
-
6
- **Cotomy** is a lightweight framework for managing form behavior and page controllers in web applications.
7
- It is suitable for both SPAs (Single Page Applications) and traditional web apps requiring dynamic form operations.
8
-
9
- ⚠️ **Warning**: This project is in early development. APIs may change without notice until version 1.0.0.
10
-
11
-
12
- To install Cotomy in your project, run the following command:
13
-
14
- ```bash
15
- npm i cotomy
16
- ```
17
-
18
- ## Usage
19
-
20
- Cotomy will continue to expand with more detailed usage instructions and code examples added to the README in the future.
21
- For the latest updates, please check the official documentation or repository regularly.
22
-
23
- ## View Reference
24
-
25
- The View layer provides thin wrappers around DOM elements and window events.
26
-
27
- - `CotomyElement` — A wrapper around `HTMLElement` with convenient utilities for scoped CSS, querying, attributes/styles, geometry, and event handling.
28
- - `CotomyMetaElement` — Convenience wrapper for `<meta>` tags.
29
- - `CotomyWindow` — A singleton that exposes window-level events and helpers.
30
-
31
- ### CotomyElement
32
-
33
- - Constructor
34
- - `new CotomyElement(element: HTMLElement)`
35
- - `new CotomyElement(html: string)` — Creates an element from HTML (single root required)
36
- - `new CotomyElement({ html, css? })` — Creates from HTML and injects scoped CSS
37
- - `new CotomyElement({ tagname, text?, css? })`
38
- - Scoped CSS
39
- - `scopeId: string` — Unique attribute injected into the element for scoping
40
- - `scopedSelector: string` — Selector string like `[__cotomy_scope__...]`
41
- - `[scope]` placeholder in provided CSS is replaced by the element’s scope
42
- - `stylable: boolean` — False for tags like `script`, `style`, `link`, `meta`
43
- - Static helpers
44
- - `CotomyElement.encodeHtml(text)`
1
+ # Cotomy
2
+
3
+ > This library targets ES2020+.
4
+ > For older browsers (e.g. iOS 13 or IE), you will need a Polyfill such as `core-js`.
5
+
6
+ **Cotomy** is a lightweight framework for managing form behavior and page controllers in web applications.
7
+ It is suitable for both SPAs (Single Page Applications) and traditional web apps requiring dynamic form operations.
8
+
9
+ ⚠️ **Warning**: This project is in early development. APIs may change without notice until version 1.0.0.
10
+
11
+
12
+ To install Cotomy in your project, run the following command:
13
+
14
+ ```bash
15
+ npm i cotomy
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ Cotomy will continue to expand with more detailed usage instructions and code examples added to the README in the future.
21
+ For the latest updates, please check the official documentation or repository regularly.
22
+
23
+ ## View Reference
24
+
25
+ The View layer provides thin wrappers around DOM elements and window events.
26
+
27
+ - `CotomyElement` — A wrapper around `HTMLElement` with convenient utilities for scoped CSS, querying, attributes/styles, geometry, and event handling.
28
+ - `CotomyMetaElement` — Convenience wrapper for `<meta>` tags.
29
+ - `CotomyWindow` — A singleton that exposes window-level events and helpers.
30
+
31
+ ### CotomyElement
32
+
33
+ - Constructor
34
+ - `new CotomyElement(element: HTMLElement)`
35
+ - `new CotomyElement(html: string)` — Creates an element from HTML (single root required)
36
+ - `new CotomyElement({ html, css? })` — Creates from HTML and injects scoped CSS
37
+ - `new CotomyElement({ tagname, text?, css? })`
38
+ - Scoped CSS
39
+ - `scopeId: string` — Unique attribute injected into the element for scoping
40
+ - `scopedSelector: string` — Selector string like `[__cotomy_scope__...]`
41
+ - `[scope]` placeholder in provided CSS is replaced by the element’s scope
42
+ - `stylable: boolean` — False for tags like `script`, `style`, `link`, `meta`
43
+ - Static helpers
44
+ - `CotomyElement.encodeHtml(text)`
45
45
  - `CotomyElement.first(selector, type?)`
46
+ - `CotomyElement.last(selector, type?)`
46
47
  - `CotomyElement.find(selector, type?)`
47
- - `CotomyElement.contains(selector)` / `CotomyElement.containsById(id)`
48
- - `CotomyElement.byId(id, type?)`
49
- - `CotomyElement.empty(type?)` — Creates a hidden placeholder element
50
- - Identity & matching
51
- - `id: string | null | undefined`
52
- - `generateId(prefix = "__cotomy_elem__"): this`
53
- - `is(selector: string): boolean` — Parent-aware matching helper
54
- - `empty: boolean` — True for tags that cannot have children or have no content
55
- - Attributes, classes, styles
56
- - `attribute(name)` / `attribute(name, value | null): this`
57
- - `hasAttribute(name): boolean`
58
- - `addClass(name): this` / `removeClass(name): this` / `toggleClass(name, force?): this` / `hasClass(name): boolean`
59
- - `style(name)` / `style(name, value | null): this`
60
- - Content & value
61
- - `text: string` (get/set)
62
- - `html: string` (get/set)
63
- - `value: string` — Works for inputs; falls back to `data-cotomy-value` otherwise
64
- - `readonly: boolean` (get/set) — Uses native property if available, otherwise attribute
65
- - `enabled: boolean` (get/set) — Toggles `disabled` attribute
66
- - `setFocus(): void`
67
- - Tree traversal & manipulation
68
- - `parent: CotomyElement`
69
- - `parents: CotomyElement[]`
70
- - `children(selector = "*", type?): T[]` (direct children only)
71
- - `firstChild(selector = "*", type?)`
72
- - `lastChild(selector = "*", type?)`
73
- - `closest(selector, type?)`
74
- - `find(selector, type?)` / `first(selector = "*", type?)` / `contains(selector)`
75
- - `append(child): this` / `prepend(child): this` / `appendAll(children): this`
76
- - `insertBefore(sibling): this` / `insertAfter(sibling): this`
77
- - `appendTo(target): this` / `prependTo(target): this`
78
- - `clone(type?): CotomyElement` — Returns a deep-cloned element, optionally typed
79
- - `clear(): this` — Removes all descendants and text
80
- - `remove(): void`
81
- - Geometry & visibility
82
- - `visible: boolean`
83
- - `width: number` (get/set px)
84
- - `height: number` (get/set px)
85
- - `innerWidth: number` / `innerHeight: number`
86
- - `outerWidth: number` / `outerHeight: number` — Includes margins
87
- - `scrollWidth: number` / `scrollHeight: number` / `scrollTop: number`
88
- - `position(): { top, left }` — Relative to viewport
89
- - `absolutePosition(): { top, left }` — Viewport + page scroll offset
90
- - `screenPosition(): { top, left }`
91
- - `rect(): { top, left, width, height }`
92
- - `innerRect()` — Subtracts padding
93
- - Events
94
- - Generic: `on(event, handler, options?)`, `off(event, handler?, options?)`, `once(event, handler, options?)`, `trigger(event[, Event])`
95
- - Delegation: `onSubTree(event, selector, handler, options?)`
96
- - Mouse: `click`, `dblclick`, `mouseover`, `mouseout`, `mousedown`, `mouseup`, `mousemove`, `mouseenter`, `mouseleave`
97
- - Keyboard: `keydown`, `keyup`, `keypress`
98
- - Inputs: `change`, `input`
99
- - Focus: `focus`, `blur`, `focusin`, `focusout`
100
- - Viewport: `inview`, `outview` (uses `IntersectionObserver`)
101
- - Layout (custom): `resize`, `scroll`, `changelayout` — requires `listenLayoutEvents()` on the element
102
- - File: `filedrop(handler: (files: File[]) => void)`
103
-
104
- Example (scoped CSS and events):
105
-
106
- ```ts
107
- import { CotomyElement } from "cotomy";
108
-
109
- const panel = new CotomyElement({
110
- html: `<div class="panel"><button class="ok">OK</button></div>`,
111
- css: `
112
- [scope] .panel { padding: 8px; }
113
- [scope] .ok { color: green; }
114
- `,
115
- });
116
-
117
- panel.onSubTree("click", ".ok", () => console.log("clicked!"));
118
- document.body.appendChild(panel.element);
119
- ```
120
-
121
- ### CotomyMetaElement
122
-
123
- - `CotomyMetaElement.get(name): CotomyMetaElement`
124
- - `content: string` — Reads `content` attribute.
125
-
126
- ### CotomyWindow
127
-
128
- - Singleton
129
- - `CotomyWindow.instance`
130
- - `initialized: boolean` — Call `initialize()` once after DOM is ready
131
- - `initialize(): void`
132
- - DOM helpers
133
- - `body: CotomyElement`
134
- - `append(element: CotomyElement)`
135
- - `moveNext(focused: CotomyElement, shift = false)` — Move focus to next/previous focusable
136
- - Window events
137
- - `on(event, handler)` / `off(event, handler?)` / `trigger(event)`
138
- - `load(handler)` / `ready(handler)`
139
- - `resize([handler])` / `scroll([handler])` / `changeLayout([handler])` / `pageshow([handler])`
140
- - Window state
141
- - `scrollTop`, `scrollLeft`, `width`, `height`, `documentWidth`, `documentHeight`
142
- - `reload(): void` (sets internal `reloading` flag), `reloading: boolean`
143
-
144
- Quick start:
145
-
146
- ```ts
147
- import { CotomyWindow, CotomyElement } from "cotomy";
148
-
149
- CotomyWindow.instance.initialize();
150
- CotomyWindow.instance.ready(() => {
151
- const el = new CotomyElement("<div>Hello</div>");
152
- CotomyWindow.instance.append(el);
153
- });
154
- ```
155
-
156
- ## Form Reference
157
-
158
- The Form layer builds on `CotomyElement` for common form flows.
159
-
160
- - `CotomyForm` — Base class with submit lifecycle hooks
161
- - `CotomyQueryForm` — Submits to query string (GET)
162
- - `CotomyApiForm` — Submits via `CotomyApi` (handles `FormData`, errors, events)
163
- - `CotomyEntityApiForm` — REST entity helper with surrogate key support
164
- - `CotomyEntityFillApiForm` — Adds automatic field filling and simple view binding
165
-
166
- ### CotomyForm (base)
167
-
168
- - Construction & basics
169
- - Extends `CotomyElement` and expects a `<form>` element
170
- - `initialize(): this` — Wires a `submit` listener that calls `submitAsync()`
171
- - `initialized: boolean` — Set after `initialize()`
172
- - `submitAsync(): Promise<void>` — Abstract in base
173
- - Routing & reload
174
- - `method: string` — Getter that defaults to `get` in base; specialized in subclasses
175
- - `actionUrl: string` — Getter that defaults to the `action` attribute or current path
176
- - `reloadAsync(): Promise<void>` — Page reload using `CotomyWindow`
177
- - `autoReload: boolean` — Backed by `data-cotomy-autoreload` (default true)
178
-
179
- ### CotomyQueryForm
180
-
181
- - Always uses `GET`
182
- - `submitAsync()` merges current query string with form inputs and navigates via `location.href`.
183
-
184
- ### CotomyApiForm
185
-
186
- - API integration
187
- - `apiClient(): CotomyApi` — Override to inject a client; default creates a new one
188
- - `actionUrl: string` — Uses `action` attribute
189
- - `method: string` — Defaults to `post`
190
- - `formData(): FormData` — Builds from form, converts `datetime-local` to ISO (UTC offset)
191
- - `submitAsync()` — Calls `submitToApiAsync(formData)`
192
- - `submitToApiAsync(formData): Promise<CotomyApiResponse>` — Uses `CotomyApi.submitAsync`
193
- - Events
194
- - `apiFailed(handler)` — Listens to `cotomy:apifailed`
195
- - `submitFailed(handler)` — Listens to `cotomy:submitfailed`
196
- - Both events bubble from the form element; payload is `CotomyApiFailedEvent`
197
-
198
- ### CotomyEntityApiForm
199
-
200
- - Surrogate key flow
201
- - `data-cotomy-entity-key` — Holds the entity identifier if present
202
- - `data-cotomy-identify` — Defaults to true; when true and `201 Created` is returned, the form extracts the key from `Location` and stores it in `data-cotomy-entity-key`
203
- - `actionUrl` — Appends the key to the base `action` when present; otherwise normalizes trailing slash for collection URL
204
- - `method` — `put` when key exists; otherwise `post` (unless `method` attribute is explicitly set)
205
-
206
- ### CotomyEntityFillApiForm
207
-
208
- - Data loading and field filling
209
- - `initialize()` — Adds default fillers and triggers `loadAsync()` on `CotomyWindow.ready`
210
- - `reloadAsync()` — Alias to `loadAsync()`
211
- - `loadAsync(): Promise<CotomyApiResponse>` — Calls `CotomyApi.getAsync` when `canLoad()` is true
212
- - `loadActionUrl(): string` — Defaults to `actionUrl`; override for custom endpoints
213
- - `canLoad(): boolean` — Defaults to `hasEntityKey`
214
- - Naming & binding
215
- - `bindNameGenerator(): ICotomyBindNameGenerator` — Defaults to `CotomyBracketBindNameGenerator` (`user[name]`)
216
- - `renderer(): CotomyViewRenderer` — Applies `[data-cotomy-bind]` to view elements
217
- - `filler(type, (input, value))` — Register fillers; defaults provided for `datetime-local`, `checkbox`, `radio`
218
- - Fills non-array, non-object fields by matching input/select/textarea `name`
219
-
220
- #### Array binding
221
-
222
- - 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]`).
223
- - Cotomy does **not** create or clone templates for you. Prepare the necessary DOM (e.g., table rows, list items, individual inputs) ahead of time, then call `fillAsync`/`applyAsync` to populate the values.
224
- - Primitive arrays (strings, numbers, booleans, etc.) are treated the same way—have matching `[data-cotomy-bind]`/`name` attributes ready for every index you want to show.
225
- - If you need dynamic row counts, generate the markup yourself before invoking Cotomy; the framework purposely avoids mutating the structure so it does not get in your way.
226
-
227
- Example:
228
-
229
- ```ts
230
- import { CotomyEntityFillApiForm } from "cotomy";
231
-
232
- const form = new CotomyEntityFillApiForm(document.querySelector("form")!);
233
- form.initialize();
234
- form.apiFailed(e => console.error("API failed", e.response.status));
235
- form.submitFailed(e => console.warn("Submit failed", e.response.status));
236
- ```
237
-
238
- ### Entity API forms
239
-
240
- `CotomyEntityApiForm` targets REST endpoints that identify records with a single surrogate key.
241
- Attach `data-cotomy-entity-key="<id>"` to the form when editing an existing entity; omit the attribute (or leave it empty) to issue a `POST` to the base `action` URL.
242
- On `201 Created`, the form reads the `Location` header and stores the generated key back into `data-cotomy-entity-key`, enabling subsequent `PUT` submissions.
243
- Composite or natural keys are no longer supported—migrate any legacy markup that relied on `data-cotomy-keyindex` or multiple key inputs to the new surrogate-key flow.
244
- When you must integrate with endpoints that still expect natural identifiers, subclass `CotomyEntityApiForm`/`CotomyEntityFillApiForm`, override `canLoad()` to supply your own load condition, and adjust `loadActionUrl()` (plus any submission hooks) to build the appropriate URL fragments.
245
-
246
- The core of Cotomy is `CotomyElement`, which is constructed as a wrapper for `Element`.
247
- By passing HTML and CSS strings to the constructor, it is possible to generate Element designs with a limited scope.
248
-
249
- ```typescript
250
- const ce = new CotomyElement({
251
- html: /* html */`
252
- <div>
253
- <p>Text</p>
254
- </div>
255
- `,
256
- css: /* css */`
257
- [scope] {
258
- display: block;
259
- }
260
- [scope] > p {
261
- text-align: center;
262
- }
263
- `
264
- });
265
- ```
266
-
267
- - `"display HTML in character literals with color coding"` → `"syntax highlighting for embedded HTML"`
268
- - `"generate Element designs with a limited scope"` → `"generate scoped DOM elements with associated styles"`
269
-
270
- ## Development
271
-
272
- Cotomy ships with both ESM (`dist/esm`) and CommonJS (`dist/cjs`) builds, plus generated type definitions in `dist/types`.
273
- For direct `<script>` usage, browser-ready bundles are available at `dist/browser/cotomy.js` and `dist/browser/cotomy.min.js` (also served via the npm `unpkg` entry).
274
- Include the minified build like so:
275
-
276
- ```html
277
- <script src="https://unpkg.com/cotomy/dist/browser/cotomy.min.js"></script>
278
- <script>
279
- const el = new Cotomy.CotomyElement("<div>Hello</div>");
280
- document.body.appendChild(el.element);
281
- </script>
282
- ```
283
-
284
- Run the build to refresh every target bundle:
285
-
286
- ```bash
287
- npm install
288
- npm run build
289
- ```
290
-
291
- The Vitest-based test suite can be executed via:
292
-
293
- ```bash
294
- npx vitest run
295
- ```
296
-
297
- ## License
298
-
299
- This project is licensed under the [MIT License](LICENSE).
300
-
301
- ## Contact
302
-
303
- You can reach out to me at: [yshr1920@gmail.com](mailto:yshr1920@gmail.com)
304
- GitHub repository: [https://github.com/yshr1920/cotomy](https://github.com/yshr1920/cotomy)
48
+ - `CotomyElement.contains(selector)` / `CotomyElement.containsById(id)`
49
+ - `CotomyElement.byId(id, type?)`
50
+ - `CotomyElement.empty(type?)` — Creates a hidden placeholder element
51
+ - Identity & matching
52
+ - `id: string | null | undefined`
53
+ - `generateId(prefix = "__cotomy_elem__"): this`
54
+ - `is(selector: string): boolean` — Parent-aware matching helper
55
+ - `empty: boolean` — True for tags that cannot have children or have no content
56
+ - Attributes, classes, styles
57
+ - `attribute(name)` / `attribute(name, value | null): this`
58
+ - `hasAttribute(name): boolean`
59
+ - `addClass(name): this` / `removeClass(name): this` / `toggleClass(name, force?): this` / `hasClass(name): boolean`
60
+ - `style(name)` / `style(name, value | null): this`
61
+ - Content & value
62
+ - `text: string` (get/set)
63
+ - `html: string` (get/set)
64
+ - `value: string` — Works for inputs; falls back to `data-cotomy-value` otherwise
65
+ - `readonly: boolean` (get/set) — Uses native property if available, otherwise attribute
66
+ - `enabled: boolean` (get/set) — Toggles `disabled` attribute
67
+ - `setFocus(): void`
68
+ - Tree traversal & manipulation
69
+ - `parent: CotomyElement`
70
+ - `parents: CotomyElement[]`
71
+ - `children(selector = "*", type?): T[]` (direct children only)
72
+ - `firstChild(selector = "*", type?)`
73
+ - `lastChild(selector = "*", type?)`
74
+ - `closest(selector, type?)`
75
+ - `find(selector, type?)` / `first(selector = "*", type?)` / `last(selector = "*", type?)` / `contains(selector)`
76
+ - `append(child): this` / `prepend(child): this` / `appendAll(children): this`
77
+ - `insertBefore(sibling): this` / `insertAfter(sibling): this`
78
+ - `appendTo(target): this` / `prependTo(target): this`
79
+ - `clone(type?): CotomyElement` — Returns a deep-cloned element, optionally typed
80
+ - `clear(): this` — Removes all descendants and text
81
+ - `remove(): void`
82
+ - Geometry & visibility
83
+ - `visible: boolean`
84
+ - `width: number` (get/set px)
85
+ - `height: number` (get/set px)
86
+ - `innerWidth: number` / `innerHeight: number`
87
+ - `outerWidth: number` / `outerHeight: number` — Includes margins
88
+ - `scrollWidth: number` / `scrollHeight: number` / `scrollTop: number`
89
+ - `position(): { top, left }` — Relative to viewport
90
+ - `absolutePosition(): { top, left }` — Viewport + page scroll offset
91
+ - `screenPosition(): { top, left }`
92
+ - `rect(): { top, left, width, height }`
93
+ - `innerRect()` — Subtracts padding
94
+ - Events
95
+ - Generic: `on(event, handler, options?)`, `off(event, handler?, options?)`, `once(event, handler, options?)`, `trigger(event[, Event])` — `trigger` emits bubbling events by default and can be customized by passing an `Event`
96
+ - Delegation: `onSubTree(event, selector, handler, options?)`
97
+ - Mouse: `click`, `dblclick`, `mouseover`, `mouseout`, `mousedown`, `mouseup`, `mousemove`, `mouseenter`, `mouseleave`
98
+ - Keyboard: `keydown`, `keyup`, `keypress`
99
+ - Inputs: `change`, `input`
100
+ - Focus: `focus`, `blur`, `focusin`, `focusout`
101
+ - Viewport: `inview`, `outview` (uses `IntersectionObserver`)
102
+ - Layout (custom): `resize`, `scroll`, `changelayout` — requires `listenLayoutEvents()` on the element
103
+ - File: `filedrop(handler: (files: File[]) => void)`
104
+
105
+ Example (scoped CSS and events):
106
+
107
+ ```ts
108
+ import { CotomyElement } from "cotomy";
109
+
110
+ const panel = new CotomyElement({
111
+ html: `<div class="panel"><button class="ok">OK</button></div>`,
112
+ css: `
113
+ [scope] .panel { padding: 8px; }
114
+ [scope] .ok { color: green; }
115
+ `,
116
+ });
117
+
118
+ panel.onSubTree("click", ".ok", () => console.log("clicked!"));
119
+ document.body.appendChild(panel.element);
120
+ ```
121
+
122
+ ### CotomyMetaElement
123
+
124
+ - `CotomyMetaElement.get(name): CotomyMetaElement`
125
+ - `content: string` — Reads `content` attribute.
126
+
127
+ ### CotomyWindow
128
+
129
+ - Singleton
130
+ - `CotomyWindow.instance`
131
+ - `initialized: boolean` — Call `initialize()` once after DOM is ready
132
+ - `initialize(): void`
133
+ - DOM helpers
134
+ - `body: CotomyElement`
135
+ - `append(element: CotomyElement)`
136
+ - `moveNext(focused: CotomyElement, shift = false)` — Move focus to next/previous focusable
137
+ - Window events
138
+ - `on(event, handler)` / `off(event, handler?)` / `trigger(event[, Event])` — CotomyWindow’s `trigger` also bubbles by default and accepts an `Event` to override the behavior
139
+ - `load(handler)` / `ready(handler)`
140
+ - `resize([handler])` / `scroll([handler])` / `changeLayout([handler])` / `pageshow([handler])`
141
+ - Window state
142
+ - `scrollTop`, `scrollLeft`, `width`, `height`, `documentWidth`, `documentHeight`
143
+ - `reload(): void` (sets internal `reloading` flag), `reloading: boolean`
144
+
145
+ Quick start:
146
+
147
+ ```ts
148
+ import { CotomyWindow, CotomyElement } from "cotomy";
149
+
150
+ CotomyWindow.instance.initialize();
151
+ CotomyWindow.instance.ready(() => {
152
+ const el = new CotomyElement("<div>Hello</div>");
153
+ CotomyWindow.instance.append(el);
154
+ });
155
+ ```
156
+
157
+ ## Form Reference
158
+
159
+ The Form layer builds on `CotomyElement` for common form flows.
160
+
161
+ - `CotomyForm` — Base class with submit lifecycle hooks
162
+ - `CotomyQueryForm` — Submits to query string (GET)
163
+ - `CotomyApiForm` — Submits via `CotomyApi` (handles `FormData`, errors, events)
164
+ - `CotomyEntityApiForm` — REST entity helper with surrogate key support
165
+ - `CotomyEntityFillApiForm` — Adds automatic field filling and simple view binding
166
+
167
+ ### CotomyForm (base)
168
+
169
+ - Construction & basics
170
+ - Extends `CotomyElement` and expects a `<form>` element
171
+ - `initialize(): this` — Wires a `submit` listener that calls `submitAsync()`
172
+ - `initialized: boolean` — Set after `initialize()`
173
+ - `submitAsync(): Promise<void>` — Abstract in base
174
+ - Routing & reload
175
+ - `method: string` — Getter that defaults to `get` in base; specialized in subclasses
176
+ - `actionUrl: string` — Getter that defaults to the `action` attribute or current path
177
+ - `reloadAsync(): Promise<void>` — Page reload using `CotomyWindow`
178
+ - `autoReload: boolean` — Backed by `data-cotomy-autoreload` (default true)
179
+
180
+ ### CotomyQueryForm
181
+
182
+ - Always uses `GET`
183
+ - `submitAsync()` merges current query string with form inputs and navigates via `location.href`.
184
+
185
+ ### CotomyApiForm
186
+
187
+ - API integration
188
+ - `apiClient(): CotomyApi` — Override to inject a client; default creates a new one
189
+ - `actionUrl: string` — Uses `action` attribute
190
+ - `method: string` — Defaults to `post`
191
+ - `formData(): FormData` — Builds from form, converts `datetime-local` to ISO (UTC offset)
192
+ - `submitAsync()` — Calls `submitToApiAsync(formData)`
193
+ - `submitToApiAsync(formData): Promise<CotomyApiResponse>` — Uses `CotomyApi.submitAsync`
194
+ - Events
195
+ - `apiFailed(handler)` — Listens to `cotomy:apifailed`
196
+ - `submitFailed(handler)` — Listens to `cotomy:submitfailed`
197
+ - Both events bubble from the form element; payload is `CotomyApiFailedEvent`
198
+
199
+ ### CotomyEntityApiForm
200
+
201
+ - Surrogate key flow
202
+ - `data-cotomy-entity-key` — Holds the entity identifier if present
203
+ - `data-cotomy-identify` — Defaults to true; when true and `201 Created` is returned, the form extracts the key from `Location` and stores it in `data-cotomy-entity-key`
204
+ - `actionUrl` — Appends the key to the base `action` when present; otherwise normalizes trailing slash for collection URL
205
+ - `method` — `put` when key exists; otherwise `post` (unless `method` attribute is explicitly set)
206
+
207
+ ### CotomyEntityFillApiForm
208
+
209
+ - Data loading and field filling
210
+ - `initialize()` — Adds default fillers and triggers `loadAsync()` on `CotomyWindow.ready`
211
+ - `reloadAsync()` — Alias to `loadAsync()`
212
+ - `loadAsync(): Promise<CotomyApiResponse>` — Calls `CotomyApi.getAsync` when `canLoad()` is true
213
+ - `loadActionUrl(): string` — Defaults to `actionUrl`; override for custom endpoints
214
+ - `canLoad(): boolean` — Defaults to `hasEntityKey`
215
+ - Naming & binding
216
+ - `bindNameGenerator(): ICotomyBindNameGenerator` — Defaults to `CotomyBracketBindNameGenerator` (`user[name]`)
217
+ - `renderer(): CotomyViewRenderer` — Applies `[data-cotomy-bind]` to view elements
218
+ - `filler(type, (input, value))` — Register fillers; defaults provided for `datetime-local`, `checkbox`, `radio`
219
+ - Fills non-array, non-object fields by matching input/select/textarea `name`
220
+
221
+ #### Array binding
222
+
223
+ - 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]`).
224
+ - Cotomy does **not** create or clone templates for you. Prepare the necessary DOM (e.g., table rows, list items, individual inputs) ahead of time, then call `fillAsync`/`applyAsync` to populate the values.
225
+ - Primitive arrays (strings, numbers, booleans, etc.) are treated the same way—have matching `[data-cotomy-bind]`/`name` attributes ready for every index you want to show.
226
+ - If you need dynamic row counts, generate the markup yourself before invoking Cotomy; the framework purposely avoids mutating the structure so it does not get in your way.
227
+
228
+ Example:
229
+
230
+ ```ts
231
+ import { CotomyEntityFillApiForm } from "cotomy";
232
+
233
+ const form = new CotomyEntityFillApiForm(document.querySelector("form")!);
234
+ form.initialize();
235
+ form.apiFailed(e => console.error("API failed", e.response.status));
236
+ form.submitFailed(e => console.warn("Submit failed", e.response.status));
237
+ ```
238
+
239
+ ### Entity API forms
240
+
241
+ `CotomyEntityApiForm` targets REST endpoints that identify records with a single surrogate key.
242
+ Attach `data-cotomy-entity-key="<id>"` to the form when editing an existing entity; omit the attribute (or leave it empty) to issue a `POST` to the base `action` URL.
243
+ On `201 Created`, the form reads the `Location` header and stores the generated key back into `data-cotomy-entity-key`, enabling subsequent `PUT` submissions.
244
+ Composite or natural keys are no longer supported—migrate any legacy markup that relied on `data-cotomy-keyindex` or multiple key inputs to the new surrogate-key flow.
245
+ When you must integrate with endpoints that still expect natural identifiers, subclass `CotomyEntityApiForm`/`CotomyEntityFillApiForm`, override `canLoad()` to supply your own load condition, and adjust `loadActionUrl()` (plus any submission hooks) to build the appropriate URL fragments.
246
+
247
+ The core of Cotomy is `CotomyElement`, which is constructed as a wrapper for `Element`.
248
+ By passing HTML and CSS strings to the constructor, it is possible to generate Element designs with a limited scope.
249
+
250
+ ```typescript
251
+ const ce = new CotomyElement({
252
+ html: /* html */`
253
+ <div>
254
+ <p>Text</p>
255
+ </div>
256
+ `,
257
+ css: /* css */`
258
+ [scope] {
259
+ display: block;
260
+ }
261
+ [scope] > p {
262
+ text-align: center;
263
+ }
264
+ `
265
+ });
266
+ ```
267
+
268
+ - `"display HTML in character literals with color coding"` → `"syntax highlighting for embedded HTML"`
269
+ - `"generate Element designs with a limited scope"` → `"generate scoped DOM elements with associated styles"`
270
+
271
+ ## Development
272
+
273
+ Cotomy ships with both ESM (`dist/esm`) and CommonJS (`dist/cjs`) builds, plus generated type definitions in `dist/types`.
274
+ For direct `<script>` usage, browser-ready bundles are available at `dist/browser/cotomy.js` and `dist/browser/cotomy.min.js` (also served via the npm `unpkg` entry).
275
+ Include the minified build like so:
276
+
277
+ ```html
278
+ <script src="https://unpkg.com/cotomy/dist/browser/cotomy.min.js"></script>
279
+ <script>
280
+ const el = new Cotomy.CotomyElement("<div>Hello</div>");
281
+ document.body.appendChild(el.element);
282
+ </script>
283
+ ```
284
+
285
+ Run the build to refresh every target bundle:
286
+
287
+ ```bash
288
+ npm install
289
+ npm run build
290
+ ```
291
+
292
+ The Vitest-based test suite can be executed via:
293
+
294
+ ```bash
295
+ npx vitest run
296
+ ```
297
+
298
+ ## License
299
+
300
+ This project is licensed under the [MIT License](LICENSE).
301
+
302
+ ## Contact
303
+
304
+ You can reach out to me at: [yshr1920@gmail.com](mailto:yshr1920@gmail.com)
305
+ GitHub repository: [https://github.com/yshr1920/cotomy](https://github.com/yshr1920/cotomy)