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 +20 -20
- package/README.md +303 -302
- package/dist/browser/cotomy.js +15 -7
- 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/form.js +3 -3
- package/dist/esm/view.js +11 -3
- package/dist/esm/view.js.map +1 -1
- package/dist/types/view.d.ts +3 -0
- package/package.json +61 -61
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)
|