cotomy 0.3.2 → 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
@@ -1,305 +1,307 @@
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
46
  - `CotomyElement.last(selector, type?)`
47
47
  - `CotomyElement.find(selector, type?)`
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?)`
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
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)
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
+ - 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.
105
+ - File: `filedrop(handler: (files: File[]) => void)`
106
+
107
+ Example (scoped CSS and events):
108
+
109
+ ```ts
110
+ import { CotomyElement } from "cotomy";
111
+
112
+ const panel = new CotomyElement({
113
+ html: `<div class="panel"><button class="ok">OK</button></div>`,
114
+ css: `
115
+ [scope] .panel { padding: 8px; }
116
+ [scope] .ok { color: green; }
117
+ `,
118
+ });
119
+
120
+ panel.onSubTree("click", ".ok", () => console.log("clicked!"));
121
+ document.body.appendChild(panel.element);
122
+ ```
123
+
124
+ ### CotomyMetaElement
125
+
126
+ - `CotomyMetaElement.get(name): CotomyMetaElement`
127
+ - `content: string` — Reads `content` attribute.
128
+
129
+ ### CotomyWindow
130
+
131
+ - Singleton
132
+ - `CotomyWindow.instance`
133
+ - `initialized: boolean` — Call `initialize()` once after DOM is ready
134
+ - `initialize(): void`
135
+ - DOM helpers
136
+ - `body: CotomyElement`
137
+ - `append(element: CotomyElement)`
138
+ - `moveNext(focused: CotomyElement, shift = false)` — Move focus to next/previous focusable
139
+ - Window events
140
+ - `on(event, handler)` / `off(event, handler?)` / `trigger(event[, Event])` CotomyWindow’s `trigger` also bubbles by default and accepts an `Event` to override the behavior
141
+ - `load(handler)` / `ready(handler)`
142
+ - `resize([handler])` / `scroll([handler])` / `changeLayout([handler])` / `pageshow([handler])`
143
+ - Window state
144
+ - `scrollTop`, `scrollLeft`, `width`, `height`, `documentWidth`, `documentHeight`
145
+ - `reload(): void` (sets internal `reloading` flag), `reloading: boolean`
146
+
147
+ Quick start:
148
+
149
+ ```ts
150
+ import { CotomyWindow, CotomyElement } from "cotomy";
151
+
152
+ CotomyWindow.instance.initialize();
153
+ CotomyWindow.instance.ready(() => {
154
+ const el = new CotomyElement("<div>Hello</div>");
155
+ CotomyWindow.instance.append(el);
156
+ });
157
+ ```
158
+
159
+ ## Form Reference
160
+
161
+ The Form layer builds on `CotomyElement` for common form flows.
162
+
163
+ - `CotomyForm` — Base class with submit lifecycle hooks
164
+ - `CotomyQueryForm` — Submits to query string (GET)
165
+ - `CotomyApiForm` — Submits via `CotomyApi` (handles `FormData`, errors, events)
166
+ - `CotomyEntityApiForm` — REST entity helper with surrogate key support
167
+ - `CotomyEntityFillApiForm` — Adds automatic field filling and simple view binding
168
+
169
+ ### CotomyForm (base)
170
+
171
+ - Construction & basics
172
+ - Extends `CotomyElement` and expects a `<form>` element
173
+ - `initialize(): this`Wires a `submit` listener that calls `submitAsync()`
174
+ - `initialized: boolean` — Set after `initialize()`
175
+ - `submitAsync(): Promise<void>`Abstract in base
176
+ - Routing & reload
177
+ - `method: string`Getter that defaults to `get` in base; specialized in subclasses
178
+ - `actionUrl: string` — Getter that defaults to the `action` attribute or current path
179
+ - `reloadAsync(): Promise<void>` — Page reload using `CotomyWindow`
180
+ - `autoReload: boolean` — Backed by `data-cotomy-autoreload` (default true)
181
+
182
+ ### CotomyQueryForm
183
+
184
+ - Always uses `GET`
185
+ - `submitAsync()` merges current query string with form inputs and navigates via `location.href`.
186
+
187
+ ### CotomyApiForm
188
+
189
+ - API integration
190
+ - `apiClient(): CotomyApi` — Override to inject a client; default creates a new one
191
+ - `actionUrl: string` — Uses `action` attribute
192
+ - `method: string` — Defaults to `post`
193
+ - `formData(): FormData`Builds from form, converts `datetime-local` to ISO (UTC offset)
194
+ - `submitAsync()` — Calls `submitToApiAsync(formData)`
195
+ - `submitToApiAsync(formData): Promise<CotomyApiResponse>` Uses `CotomyApi.submitAsync`
196
+ - Events
197
+ - `apiFailed(handler)` Listens to `cotomy:apifailed`
198
+ - `submitFailed(handler)` — Listens to `cotomy:submitfailed`
199
+ - Both events bubble from the form element; payload is `CotomyApiFailedEvent`
200
+
201
+ ### CotomyEntityApiForm
202
+
203
+ - Surrogate key flow
204
+ - `data-cotomy-entity-key` — Holds the entity identifier if present
205
+ - `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`
206
+ - `actionUrl` — Appends the key to the base `action` when present; otherwise normalizes trailing slash for collection URL
207
+ - `method` — `put` when key exists; otherwise `post` (unless `method` attribute is explicitly set)
208
+
209
+ ### CotomyEntityFillApiForm
210
+
211
+ - Data loading and field filling
212
+ - `initialize()`Adds default fillers and triggers `loadAsync()` on `CotomyWindow.ready`
213
+ - `reloadAsync()` — Alias to `loadAsync()`
214
+ - `loadAsync(): Promise<CotomyApiResponse>`Calls `CotomyApi.getAsync` when `canLoad()` is true
215
+ - `loadActionUrl(): string` — Defaults to `actionUrl`; override for custom endpoints
216
+ - `canLoad(): boolean` — Defaults to `hasEntityKey`
217
+ - Naming & binding
218
+ - `bindNameGenerator(): ICotomyBindNameGenerator` — Defaults to `CotomyBracketBindNameGenerator` (`user[name]`)
219
+ - `renderer(): CotomyViewRenderer` Applies `[data-cotomy-bind]` to view elements
220
+ - `filler(type, (input, value))` — Register fillers; defaults provided for `datetime-local`, `checkbox`, `radio`
221
+ - Fills non-array, non-object fields by matching input/select/textarea `name`
222
+
223
+ #### Array binding
224
+
225
+ - 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]`).
226
+ - 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.
227
+ - 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.
228
+ - 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.
229
+
230
+ Example:
231
+
232
+ ```ts
233
+ import { CotomyEntityFillApiForm } from "cotomy";
234
+
235
+ const form = new CotomyEntityFillApiForm(document.querySelector("form")!);
236
+ form.initialize();
237
+ form.apiFailed(e => console.error("API failed", e.response.status));
238
+ form.submitFailed(e => console.warn("Submit failed", e.response.status));
239
+ ```
240
+
241
+ ### Entity API forms
242
+
243
+ `CotomyEntityApiForm` targets REST endpoints that identify records with a single surrogate key.
244
+ 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.
245
+ On `201 Created`, the form reads the `Location` header and stores the generated key back into `data-cotomy-entity-key`, enabling subsequent `PUT` submissions.
246
+ 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.
247
+ 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.
248
+
249
+ The core of Cotomy is `CotomyElement`, which is constructed as a wrapper for `Element`.
250
+ By passing HTML and CSS strings to the constructor, it is possible to generate Element designs with a limited scope.
251
+
252
+ ```typescript
253
+ const ce = new CotomyElement({
254
+ html: /* html */`
255
+ <div>
256
+ <p>Text</p>
257
+ </div>
258
+ `,
259
+ css: /* css */`
260
+ [scope] {
261
+ display: block;
262
+ }
263
+ [scope] > p {
264
+ text-align: center;
265
+ }
266
+ `
267
+ });
268
+ ```
269
+
270
+ - `"display HTML in character literals with color coding"` → `"syntax highlighting for embedded HTML"`
271
+ - `"generate Element designs with a limited scope"` → `"generate scoped DOM elements with associated styles"`
272
+
273
+ ## Development
274
+
275
+ Cotomy ships with both ESM (`dist/esm`) and CommonJS (`dist/cjs`) builds, plus generated type definitions in `dist/types`.
276
+ 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).
277
+ Include the minified build like so:
278
+
279
+ ```html
280
+ <script src="https://unpkg.com/cotomy/dist/browser/cotomy.min.js"></script>
281
+ <script>
282
+ const el = new Cotomy.CotomyElement("<div>Hello</div>");
283
+ document.body.appendChild(el.element);
284
+ </script>
285
+ ```
286
+
287
+ Run the build to refresh every target bundle:
288
+
289
+ ```bash
290
+ npm install
291
+ npm run build
292
+ ```
293
+
294
+ The Vitest-based test suite can be executed via:
295
+
296
+ ```bash
297
+ npx vitest run
298
+ ```
299
+
300
+ ## License
301
+
302
+ This project is licensed under the [MIT License](LICENSE).
303
+
304
+ ## Contact
305
+
306
+ You can reach out to me at: [yshr1920@gmail.com](mailto:yshr1920@gmail.com)
307
+ GitHub repository: [https://github.com/yshr1920/cotomy](https://github.com/yshr1920/cotomy)