cotomy 0.1.59 → 0.1.62
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 +16 -0
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/esm/api.js +468 -0
- package/dist/esm/api.js.map +1 -0
- package/dist/esm/debug.js +42 -0
- package/dist/esm/debug.js.map +1 -0
- package/dist/esm/form.js +383 -0
- package/dist/esm/form.js.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/page.js +128 -0
- package/dist/esm/page.js.map +1 -0
- package/dist/esm/view.js +1198 -0
- package/dist/esm/view.js.map +1 -0
- package/dist/{view.d.ts → types/view.d.ts} +10 -3
- package/package.json +25 -6
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- /package/dist/{api.d.ts → types/api.d.ts} +0 -0
- /package/dist/{debug.d.ts → types/debug.d.ts} +0 -0
- /package/dist/{form.d.ts → types/form.d.ts} +0 -0
- /package/dist/{index.d.ts → types/index.d.ts} +0 -0
- /package/dist/{page.d.ts → types/page.d.ts} +0 -0
package/dist/esm/view.js
ADDED
|
@@ -0,0 +1,1198 @@
|
|
|
1
|
+
import cuid from "cuid";
|
|
2
|
+
import { CotomyDebugFeature, CotomyDebugSettings } from "./debug";
|
|
3
|
+
class HandlerEntry {
|
|
4
|
+
constructor(handle, wrapper, options) {
|
|
5
|
+
this.handle = handle;
|
|
6
|
+
this.wrapper = wrapper;
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
get current() {
|
|
10
|
+
return this.wrapper ?? this.handle;
|
|
11
|
+
}
|
|
12
|
+
equals(entryOrHandle, optionsOrMode, wrapper, mode) {
|
|
13
|
+
let targetHandle;
|
|
14
|
+
let targetWrapper;
|
|
15
|
+
let targetOptions;
|
|
16
|
+
let compareMode = "strict";
|
|
17
|
+
if (entryOrHandle instanceof HandlerEntry) {
|
|
18
|
+
targetHandle = entryOrHandle.handle;
|
|
19
|
+
targetWrapper = entryOrHandle.wrapper;
|
|
20
|
+
targetOptions = entryOrHandle.options;
|
|
21
|
+
compareMode = optionsOrMode ?? "strict";
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
targetHandle = entryOrHandle;
|
|
25
|
+
if (typeof optionsOrMode === "string") {
|
|
26
|
+
compareMode = optionsOrMode;
|
|
27
|
+
targetWrapper = wrapper;
|
|
28
|
+
targetOptions = undefined;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
targetOptions = optionsOrMode;
|
|
32
|
+
targetWrapper = wrapper;
|
|
33
|
+
compareMode = mode ?? "strict";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (this.handle !== targetHandle) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (compareMode === "strict" && this.wrapper !== targetWrapper) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return HandlerEntry.optionsEquals(this.options, targetOptions);
|
|
43
|
+
}
|
|
44
|
+
static optionsEquals(left, right) {
|
|
45
|
+
const getBoolean = (options, key) => options?.[key] ?? false;
|
|
46
|
+
const getSignal = (options) => options?.signal;
|
|
47
|
+
const leftSignal = getSignal(left);
|
|
48
|
+
const rightSignal = getSignal(right);
|
|
49
|
+
const signalsEqual = leftSignal === rightSignal;
|
|
50
|
+
return getBoolean(left, "capture") === getBoolean(right, "capture")
|
|
51
|
+
&& getBoolean(left, "once") === getBoolean(right, "once")
|
|
52
|
+
&& getBoolean(left, "passive") === getBoolean(right, "passive")
|
|
53
|
+
&& signalsEqual;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
class HandlerRegistory {
|
|
57
|
+
constructor(target) {
|
|
58
|
+
this.target = target;
|
|
59
|
+
this._registory = new Map();
|
|
60
|
+
}
|
|
61
|
+
ensure(event) {
|
|
62
|
+
return this._registory.get(event) ?? this._registory.set(event, []).get(event);
|
|
63
|
+
}
|
|
64
|
+
find(event, entry) {
|
|
65
|
+
return this._registory.get(event)?.find(e => e.equals(entry)) ?? undefined;
|
|
66
|
+
}
|
|
67
|
+
add(event, entry) {
|
|
68
|
+
if (entry.options?.once) {
|
|
69
|
+
this.remove(event, entry);
|
|
70
|
+
}
|
|
71
|
+
if (!this.find(event, entry)) {
|
|
72
|
+
this.ensure(event).push(entry);
|
|
73
|
+
this.target.element.addEventListener(event, entry.current, entry.options);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
remove(event, entry) {
|
|
77
|
+
if (!entry) {
|
|
78
|
+
const list = this._registory.get(event);
|
|
79
|
+
if (!list)
|
|
80
|
+
return;
|
|
81
|
+
list.forEach(e => this.target.element.removeEventListener(event, e.current, e.options?.capture ?? false));
|
|
82
|
+
this._registory.delete(event);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const list = this._registory.get(event);
|
|
86
|
+
if (list) {
|
|
87
|
+
const remaining = [];
|
|
88
|
+
for (const e of list) {
|
|
89
|
+
if (e.equals(entry, "remove")) {
|
|
90
|
+
this.target.element.removeEventListener(event, e.current, e.options?.capture ?? false);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
remaining.push(e);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (remaining.length === 0) {
|
|
97
|
+
this._registory.delete(event);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this._registory.set(event, remaining);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
get empty() {
|
|
105
|
+
return this._registory.size === 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
class EventRegistry {
|
|
109
|
+
static get instance() {
|
|
110
|
+
return this._instance ?? (this._instance = new EventRegistry());
|
|
111
|
+
}
|
|
112
|
+
constructor() {
|
|
113
|
+
this._registry = new Map();
|
|
114
|
+
}
|
|
115
|
+
map(target) {
|
|
116
|
+
const scopeId = target.scopeId;
|
|
117
|
+
let registry = this._registry.get(scopeId);
|
|
118
|
+
if (!registry) {
|
|
119
|
+
registry = new HandlerRegistory(target);
|
|
120
|
+
this._registry.set(scopeId, registry);
|
|
121
|
+
}
|
|
122
|
+
return registry;
|
|
123
|
+
}
|
|
124
|
+
on(event, target, entry) {
|
|
125
|
+
const registry = this.map(target);
|
|
126
|
+
registry.add(event, entry);
|
|
127
|
+
}
|
|
128
|
+
off(event, target, entry) {
|
|
129
|
+
const registry = this._registry.get(target.scopeId);
|
|
130
|
+
if (!registry)
|
|
131
|
+
return;
|
|
132
|
+
if (entry) {
|
|
133
|
+
registry.remove(event, entry);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
registry.remove(event);
|
|
137
|
+
}
|
|
138
|
+
if (registry.empty) {
|
|
139
|
+
this._registry.delete(target.scopeId);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
clear(target) {
|
|
143
|
+
this._registry.delete(target.scopeId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export class CotomyElement {
|
|
147
|
+
static encodeHtml(text) {
|
|
148
|
+
const div = document.createElement("div");
|
|
149
|
+
div.textContent = text ?? "";
|
|
150
|
+
return div.innerHTML;
|
|
151
|
+
}
|
|
152
|
+
static createHTMLElement(html) {
|
|
153
|
+
const wrapperMap = {
|
|
154
|
+
"tr": { prefix: "<table><tbody>", suffix: "</tbody></table>" },
|
|
155
|
+
"td": { prefix: "<table><tbody><tr>", suffix: "</tr></tbody></table>" },
|
|
156
|
+
"th": { prefix: "<table><tbody><tr>", suffix: "</tr></tbody></table>" },
|
|
157
|
+
"thead": { prefix: "<table>", suffix: "</table>" },
|
|
158
|
+
"tbody": { prefix: "<table>", suffix: "</table>" },
|
|
159
|
+
"tfoot": { prefix: "<table>", suffix: "</table>" },
|
|
160
|
+
"caption": { prefix: "<table>", suffix: "</table>" },
|
|
161
|
+
"colgroup": { prefix: "<table>", suffix: "</table>" },
|
|
162
|
+
"col": { prefix: "<table><colgroup>", suffix: "</colgroup></table>" },
|
|
163
|
+
"option": { prefix: "<select>", suffix: "</select>" },
|
|
164
|
+
"optgroup": { prefix: "<select>", suffix: "</select>" },
|
|
165
|
+
"legend": { prefix: "<fieldset>", suffix: "</fieldset>" },
|
|
166
|
+
"li": { prefix: "<ul>", suffix: "</ul>" }
|
|
167
|
+
};
|
|
168
|
+
const match = html.match(/<\s*([a-z0-9]+)/i);
|
|
169
|
+
if (!match) {
|
|
170
|
+
throw new Error(`Invalid HTML: cannot extract tag from "${html}"`);
|
|
171
|
+
}
|
|
172
|
+
const tag = match[1].toLowerCase();
|
|
173
|
+
const wrap = wrapperMap[tag];
|
|
174
|
+
const wrappedHtml = wrap ? `${wrap.prefix}${html}${wrap.suffix}` : html;
|
|
175
|
+
const parser = new DOMParser();
|
|
176
|
+
const doc = parser.parseFromString(wrappedHtml, "text/html");
|
|
177
|
+
const errors = doc.querySelector("parsererror");
|
|
178
|
+
if (errors) {
|
|
179
|
+
throw new Error(`HTML parsing failed for tag <${tag}>: "${html}"`);
|
|
180
|
+
}
|
|
181
|
+
if (doc.body.children.length !== 1) {
|
|
182
|
+
throw new Error(`CotomyElement requires a single root element, but got ${doc.body.children.length}.`);
|
|
183
|
+
}
|
|
184
|
+
const element = doc.body.querySelector(tag);
|
|
185
|
+
if (!element) {
|
|
186
|
+
throw new Error(`Parsed but <${tag}> element not found: "${html}"`);
|
|
187
|
+
}
|
|
188
|
+
return element;
|
|
189
|
+
}
|
|
190
|
+
static first(selector, type) {
|
|
191
|
+
const element = document.querySelector(selector);
|
|
192
|
+
if (!element)
|
|
193
|
+
return undefined;
|
|
194
|
+
const ctor = (type ?? CotomyElement);
|
|
195
|
+
return new ctor(element);
|
|
196
|
+
}
|
|
197
|
+
static find(selector, type) {
|
|
198
|
+
const elements = document.querySelectorAll(selector);
|
|
199
|
+
const ctor = (type ?? CotomyElement);
|
|
200
|
+
return Array.from(elements).map(e => new ctor(e));
|
|
201
|
+
}
|
|
202
|
+
static contains(selector) {
|
|
203
|
+
return document.querySelector(selector) !== null;
|
|
204
|
+
}
|
|
205
|
+
static byId(id, type) {
|
|
206
|
+
return this.first(`#${id}`, type);
|
|
207
|
+
}
|
|
208
|
+
static containsById(id) {
|
|
209
|
+
return document.getElementById(id) !== null;
|
|
210
|
+
}
|
|
211
|
+
static empty(type) {
|
|
212
|
+
const ctor = (type ?? CotomyElement);
|
|
213
|
+
return new ctor(document.createElement("div")).attribute("data-cotomy-empty", "").style("display", "none");
|
|
214
|
+
}
|
|
215
|
+
constructor(element) {
|
|
216
|
+
this._parentElement = null;
|
|
217
|
+
this._scopeId = null;
|
|
218
|
+
if (element instanceof HTMLElement) {
|
|
219
|
+
this._element = element;
|
|
220
|
+
}
|
|
221
|
+
else if (typeof element === "string") {
|
|
222
|
+
this._element = CotomyElement.createHTMLElement(element);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this._element = CotomyElement.createHTMLElement("html" in element ? element.html : `<${element.tagname}></${element.tagname}>`);
|
|
226
|
+
if ("tagname" in element && element.text) {
|
|
227
|
+
this._element.textContent = element.text;
|
|
228
|
+
}
|
|
229
|
+
if (element.css) {
|
|
230
|
+
this.useScopedCss(element.css);
|
|
231
|
+
}
|
|
232
|
+
if (CotomyDebugSettings.isEnabled(CotomyDebugFeature.Html)) {
|
|
233
|
+
if ("html" in element) {
|
|
234
|
+
console.debug(`CotomyElement {html: "${element.html}" } is created`);
|
|
235
|
+
}
|
|
236
|
+
if ("tagname" in element) {
|
|
237
|
+
console.debug(`CotomyElement {tagname: "${element.tagname}", text: "${element.text ?? ""}"} is created`);
|
|
238
|
+
}
|
|
239
|
+
if (element.css) {
|
|
240
|
+
console.debug(`CotomyElement {css: "${element.css}" } is applied`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
this.removed(() => {
|
|
245
|
+
this._element = CotomyElement.createHTMLElement(`<div data-cotomy-invalidated style="display: none;"></div>`);
|
|
246
|
+
EventRegistry.instance.clear(this);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
get scopeId() {
|
|
250
|
+
if (!this._scopeId) {
|
|
251
|
+
this._scopeId = `__cotomy_scope__${cuid()}`;
|
|
252
|
+
this.attribute(this._scopeId, "");
|
|
253
|
+
}
|
|
254
|
+
return this._scopeId;
|
|
255
|
+
}
|
|
256
|
+
get scopedSelector() {
|
|
257
|
+
return `[${this.scopeId}]`;
|
|
258
|
+
}
|
|
259
|
+
get stylable() {
|
|
260
|
+
return !["script", "style", "link", "meta"].includes(this.tagname);
|
|
261
|
+
}
|
|
262
|
+
get scopedCssElementId() {
|
|
263
|
+
return `css-${this.scopeId}`;
|
|
264
|
+
}
|
|
265
|
+
useScopedCss(css) {
|
|
266
|
+
if (css && this.stylable) {
|
|
267
|
+
const cssid = this.scopedCssElementId;
|
|
268
|
+
CotomyElement.find(`#${cssid}`).forEach(e => e.remove());
|
|
269
|
+
const element = document.createElement("style");
|
|
270
|
+
const writeCss = css.replace(/\[scope\]/g, `[${this.scopeId}]`);
|
|
271
|
+
const node = document.createTextNode(writeCss);
|
|
272
|
+
element.appendChild(node);
|
|
273
|
+
element.id = cssid;
|
|
274
|
+
const head = CotomyElement.first("head")
|
|
275
|
+
|| new CotomyElement({ html: `<head></head>` }).prependTo(new CotomyElement(document.documentElement));
|
|
276
|
+
head.append(new CotomyElement(element));
|
|
277
|
+
this.removed(() => {
|
|
278
|
+
CotomyElement.find(`#${cssid}`).forEach(e => e.remove());
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
listenLayoutEvents() {
|
|
284
|
+
this.attribute(CotomyElement.LISTEN_LAYOUT_EVENTS_ATTRIBUTE, "");
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
get id() {
|
|
288
|
+
return this.attribute("id");
|
|
289
|
+
}
|
|
290
|
+
generateId(prefix = "__cotomy_elem__") {
|
|
291
|
+
if (!this.id) {
|
|
292
|
+
this.attribute("id", `${prefix}${cuid()}`);
|
|
293
|
+
}
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
get element() {
|
|
297
|
+
return this._element;
|
|
298
|
+
}
|
|
299
|
+
get tagname() {
|
|
300
|
+
return this.element.tagName.toLowerCase();
|
|
301
|
+
}
|
|
302
|
+
is(selector) {
|
|
303
|
+
const selectors = selector.split(/\s+(?![^\[]*\])|(?<=\>)\s+/);
|
|
304
|
+
let element = this.element;
|
|
305
|
+
for (let i = selectors.length - 1; i >= 0; i--) {
|
|
306
|
+
let subSelector = selectors[i].trim();
|
|
307
|
+
let directChild = false;
|
|
308
|
+
if (subSelector.startsWith(">")) {
|
|
309
|
+
directChild = true;
|
|
310
|
+
subSelector = subSelector.slice(1).trim();
|
|
311
|
+
}
|
|
312
|
+
if (!element || !element.matches(subSelector)) {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (directChild) {
|
|
316
|
+
element = element.parentElement;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
if (i > 0) {
|
|
320
|
+
while (element && !element.matches(selectors[i - 1].trim())) {
|
|
321
|
+
element = element.parentElement;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
get empty() {
|
|
329
|
+
const nonEmptyTags = new Set([
|
|
330
|
+
"input", "select", "textarea", "img", "video", "audio", "br", "hr",
|
|
331
|
+
"iframe", "embed", "canvas", "object", "svg", "source", "track", "col",
|
|
332
|
+
"link", "meta", "base"
|
|
333
|
+
]);
|
|
334
|
+
return nonEmptyTags.has(this.tagname)
|
|
335
|
+
|| this._element.hasAttribute("data-cotomy-empty")
|
|
336
|
+
|| this._element.innerHTML.trim() === "";
|
|
337
|
+
}
|
|
338
|
+
get attached() {
|
|
339
|
+
return document.contains(this.element);
|
|
340
|
+
}
|
|
341
|
+
get readonly() {
|
|
342
|
+
if ("readOnly" in this.element) {
|
|
343
|
+
return this.element.readOnly;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
return this.element.hasAttribute("readonly");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
set readonly(readonly) {
|
|
350
|
+
if ("readOnly" in this.element) {
|
|
351
|
+
this.element.readOnly = readonly;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
if (readonly) {
|
|
355
|
+
this.attribute("readonly", "readonly");
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
this.attribute("readonly", null);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
get value() {
|
|
363
|
+
if ("value" in this.element) {
|
|
364
|
+
return this.element.value;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
return this.attribute("data-cotomy-value") ?? "";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
set value(val) {
|
|
371
|
+
if ("value" in this.element) {
|
|
372
|
+
this.element.value = val;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
this.attribute("data-cotomy-value", val);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
get text() {
|
|
379
|
+
return this.element.textContent ?? "";
|
|
380
|
+
}
|
|
381
|
+
set text(text) {
|
|
382
|
+
this.element.textContent = text ?? "";
|
|
383
|
+
}
|
|
384
|
+
get html() {
|
|
385
|
+
return this.element.innerHTML;
|
|
386
|
+
}
|
|
387
|
+
set html(html) {
|
|
388
|
+
this.element.innerHTML = html;
|
|
389
|
+
}
|
|
390
|
+
setFocus() {
|
|
391
|
+
this.element.focus();
|
|
392
|
+
}
|
|
393
|
+
get visible() {
|
|
394
|
+
if (!this.attached) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
if (!this.element.offsetParent && !document.contains(this.element)) {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
const rect = this.element.getBoundingClientRect();
|
|
401
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
402
|
+
const style = this.getComputedStyle();
|
|
403
|
+
return style.display !== "none" && style.visibility !== "hidden" && style.visibility !== "collapse";
|
|
404
|
+
}
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
get enabled() {
|
|
408
|
+
return !(this.element.hasAttribute("disabled") && this.element.getAttribute("disabled") !== null);
|
|
409
|
+
}
|
|
410
|
+
set enabled(value) {
|
|
411
|
+
if (value) {
|
|
412
|
+
this.element.removeAttribute("disabled");
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.element.setAttribute("disabled", "disabled");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
get invalidated() {
|
|
419
|
+
return this.element.hasAttribute("data-cotomy-invalidated");
|
|
420
|
+
}
|
|
421
|
+
remove() {
|
|
422
|
+
if (!this.invalidated) {
|
|
423
|
+
this._element.remove();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
clear() {
|
|
427
|
+
this.find("*").forEach(e => e.remove());
|
|
428
|
+
this.text = "";
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
get width() {
|
|
432
|
+
return this.element.offsetWidth;
|
|
433
|
+
}
|
|
434
|
+
set width(width) {
|
|
435
|
+
let w = width.toString() + "px";
|
|
436
|
+
this.style("width", w);
|
|
437
|
+
}
|
|
438
|
+
get height() {
|
|
439
|
+
return this.element.offsetHeight;
|
|
440
|
+
}
|
|
441
|
+
set height(height) {
|
|
442
|
+
let h = height.toString() + "px";
|
|
443
|
+
this.style("height", h);
|
|
444
|
+
}
|
|
445
|
+
get innerWidth() {
|
|
446
|
+
return this.element.clientWidth;
|
|
447
|
+
}
|
|
448
|
+
get innerHeight() {
|
|
449
|
+
return this.element.clientHeight;
|
|
450
|
+
}
|
|
451
|
+
get outerWidth() {
|
|
452
|
+
const style = this.getComputedStyle();
|
|
453
|
+
const margin = parseFloat(style.marginLeft) + parseFloat(style.marginRight);
|
|
454
|
+
return this.element.offsetWidth + margin;
|
|
455
|
+
}
|
|
456
|
+
get outerHeight() {
|
|
457
|
+
const style = this.getComputedStyle();
|
|
458
|
+
const margin = parseFloat(style.marginTop) + parseFloat(style.marginBottom);
|
|
459
|
+
return this.element.offsetHeight + margin;
|
|
460
|
+
}
|
|
461
|
+
get scrollHeight() {
|
|
462
|
+
return this.element.scrollHeight;
|
|
463
|
+
}
|
|
464
|
+
get scrollWidth() {
|
|
465
|
+
return this.element.scrollWidth;
|
|
466
|
+
}
|
|
467
|
+
get scrollTop() {
|
|
468
|
+
return this.element.scrollTop;
|
|
469
|
+
}
|
|
470
|
+
get position() {
|
|
471
|
+
const rect = this.element.getBoundingClientRect();
|
|
472
|
+
return { top: rect.top, left: rect.left };
|
|
473
|
+
}
|
|
474
|
+
get absolutePosition() {
|
|
475
|
+
const rect = this.element.getBoundingClientRect();
|
|
476
|
+
return { top: rect.top + window.scrollY, left: rect.left + window.scrollX };
|
|
477
|
+
}
|
|
478
|
+
get screenPosition() {
|
|
479
|
+
const rect = this.element.getBoundingClientRect();
|
|
480
|
+
return { top: rect.top, left: rect.left };
|
|
481
|
+
}
|
|
482
|
+
get rect() {
|
|
483
|
+
const rect = this.element.getBoundingClientRect();
|
|
484
|
+
return { top: rect.top, left: rect.left, width: rect.width, height: rect.height };
|
|
485
|
+
}
|
|
486
|
+
get innerRect() {
|
|
487
|
+
const rect = this.element.getBoundingClientRect();
|
|
488
|
+
const style = this.getComputedStyle();
|
|
489
|
+
const padding = {
|
|
490
|
+
top: parseFloat(style.paddingTop),
|
|
491
|
+
right: parseFloat(style.paddingRight),
|
|
492
|
+
bottom: parseFloat(style.paddingBottom),
|
|
493
|
+
left: parseFloat(style.paddingLeft)
|
|
494
|
+
};
|
|
495
|
+
return {
|
|
496
|
+
top: rect.top + padding.top,
|
|
497
|
+
left: rect.left + padding.left,
|
|
498
|
+
width: rect.width - padding.left - padding.right,
|
|
499
|
+
height: rect.height - padding.top - padding.bottom
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
get outerRect() {
|
|
503
|
+
const rect = this.element.getBoundingClientRect();
|
|
504
|
+
const style = this.getComputedStyle();
|
|
505
|
+
const margin = {
|
|
506
|
+
top: parseFloat(style.marginTop),
|
|
507
|
+
right: parseFloat(style.marginRight),
|
|
508
|
+
bottom: parseFloat(style.marginBottom),
|
|
509
|
+
left: parseFloat(style.marginLeft)
|
|
510
|
+
};
|
|
511
|
+
return {
|
|
512
|
+
top: rect.top - margin.top,
|
|
513
|
+
left: rect.left - margin.left,
|
|
514
|
+
width: rect.width + margin.left + margin.right,
|
|
515
|
+
height: rect.height + margin.top + margin.bottom
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
get padding() {
|
|
519
|
+
const style = this.getComputedStyle();
|
|
520
|
+
return {
|
|
521
|
+
top: parseFloat(style.paddingTop),
|
|
522
|
+
right: parseFloat(style.paddingRight),
|
|
523
|
+
bottom: parseFloat(style.paddingBottom),
|
|
524
|
+
left: parseFloat(style.paddingLeft)
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
get margin() {
|
|
528
|
+
const style = this.getComputedStyle();
|
|
529
|
+
return {
|
|
530
|
+
top: parseFloat(style.marginTop),
|
|
531
|
+
right: parseFloat(style.marginRight),
|
|
532
|
+
bottom: parseFloat(style.marginBottom),
|
|
533
|
+
left: parseFloat(style.marginLeft)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
get inViewport() {
|
|
537
|
+
const rect = this.element.getBoundingClientRect();
|
|
538
|
+
return rect.top < window.innerHeight && rect.bottom > 0;
|
|
539
|
+
}
|
|
540
|
+
get isAboveViewport() {
|
|
541
|
+
return this.element.getBoundingClientRect().bottom < 0;
|
|
542
|
+
}
|
|
543
|
+
get isBelowViewport() {
|
|
544
|
+
return this.element.getBoundingClientRect().top > window.innerHeight;
|
|
545
|
+
}
|
|
546
|
+
get isLeftViewport() {
|
|
547
|
+
return this.element.getBoundingClientRect().right < 0;
|
|
548
|
+
}
|
|
549
|
+
get isRightViewport() {
|
|
550
|
+
return this.element.getBoundingClientRect().left > window.innerWidth;
|
|
551
|
+
}
|
|
552
|
+
hasAttribute(name) {
|
|
553
|
+
return this.element.hasAttribute(name);
|
|
554
|
+
}
|
|
555
|
+
attribute(name, value) {
|
|
556
|
+
if (arguments.length === 1) {
|
|
557
|
+
return this.element.hasAttribute(name) ? this.element.getAttribute(name) : undefined;
|
|
558
|
+
}
|
|
559
|
+
else if (value === null) {
|
|
560
|
+
this.element.removeAttribute(name);
|
|
561
|
+
return this;
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
this.element.setAttribute(name, value?.toString() ?? "");
|
|
565
|
+
return this;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
hasClass(name) {
|
|
569
|
+
return this.element.classList.contains(name);
|
|
570
|
+
}
|
|
571
|
+
addClass(name) {
|
|
572
|
+
this.element.classList.add(name);
|
|
573
|
+
return this;
|
|
574
|
+
}
|
|
575
|
+
removeClass(name) {
|
|
576
|
+
this.element.classList.remove(name);
|
|
577
|
+
return this;
|
|
578
|
+
}
|
|
579
|
+
toggleClass(name, force) {
|
|
580
|
+
this.element.classList.toggle(name, force);
|
|
581
|
+
return this;
|
|
582
|
+
}
|
|
583
|
+
style(name, value) {
|
|
584
|
+
if (arguments.length === 1) {
|
|
585
|
+
return this.element.style.getPropertyValue(name);
|
|
586
|
+
}
|
|
587
|
+
else if (value == null) {
|
|
588
|
+
this.element.style.removeProperty(name);
|
|
589
|
+
return this;
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
this.element.style.setProperty(name, value);
|
|
593
|
+
return this;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
getComputedStyle() {
|
|
597
|
+
return window.getComputedStyle(this.element);
|
|
598
|
+
}
|
|
599
|
+
computedStyle(name) {
|
|
600
|
+
return window.getComputedStyle(this.element).getPropertyValue(name);
|
|
601
|
+
}
|
|
602
|
+
get parent() {
|
|
603
|
+
if (this._parentElement == null && this.element.parentElement !== null) {
|
|
604
|
+
this._parentElement = new CotomyElement(this.element.parentElement);
|
|
605
|
+
}
|
|
606
|
+
return this._parentElement ?? CotomyElement.empty();
|
|
607
|
+
}
|
|
608
|
+
get parents() {
|
|
609
|
+
let parents = [];
|
|
610
|
+
let currentElement = this.element.parentElement;
|
|
611
|
+
while (currentElement !== null) {
|
|
612
|
+
parents.push(new CotomyElement(currentElement));
|
|
613
|
+
currentElement = currentElement.parentElement;
|
|
614
|
+
}
|
|
615
|
+
return parents;
|
|
616
|
+
}
|
|
617
|
+
hasChildren(selector = "*") {
|
|
618
|
+
return this.element.querySelector(selector) !== null;
|
|
619
|
+
}
|
|
620
|
+
children(selector = "*", type) {
|
|
621
|
+
const children = Array.from(this.element.querySelectorAll(selector));
|
|
622
|
+
const directChildren = children.filter(child => child.parentElement === this.element);
|
|
623
|
+
const ctor = (type ?? CotomyElement);
|
|
624
|
+
return directChildren.filter((e) => e instanceof HTMLElement).map(e => new ctor(e));
|
|
625
|
+
}
|
|
626
|
+
firstChild(selector = "*", type) {
|
|
627
|
+
const elements = this.children(selector, type);
|
|
628
|
+
return elements.shift() ?? undefined;
|
|
629
|
+
}
|
|
630
|
+
lastChild(selector = "*", type) {
|
|
631
|
+
const elements = this.children(selector, type);
|
|
632
|
+
return elements.pop() ?? undefined;
|
|
633
|
+
}
|
|
634
|
+
closest(selector, type) {
|
|
635
|
+
const closestElement = this.element.closest(selector);
|
|
636
|
+
if (closestElement !== null && closestElement instanceof HTMLElement) {
|
|
637
|
+
const ctor = (type ?? CotomyElement);
|
|
638
|
+
return new ctor(closestElement);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
find(selector, type) {
|
|
645
|
+
const elements = Array.from(this.element.querySelectorAll(selector));
|
|
646
|
+
return elements.map(e => new (type ?? CotomyElement)(e));
|
|
647
|
+
}
|
|
648
|
+
first(selector = "*", type) {
|
|
649
|
+
const elements = this.find(selector, type);
|
|
650
|
+
return elements.shift() ?? undefined;
|
|
651
|
+
}
|
|
652
|
+
contains(selector) {
|
|
653
|
+
return this.find(selector).length > 0;
|
|
654
|
+
}
|
|
655
|
+
prepend(prepend) {
|
|
656
|
+
this._element.prepend(prepend.element);
|
|
657
|
+
return this;
|
|
658
|
+
}
|
|
659
|
+
append(target) {
|
|
660
|
+
this.element.append(target.element);
|
|
661
|
+
return this;
|
|
662
|
+
}
|
|
663
|
+
appendAll(targets) {
|
|
664
|
+
targets.forEach(e => this.append(e));
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
insertBefore(append) {
|
|
668
|
+
this.element.before(append.element);
|
|
669
|
+
return this;
|
|
670
|
+
}
|
|
671
|
+
insertAfter(append) {
|
|
672
|
+
this.element.after(append.element);
|
|
673
|
+
return this;
|
|
674
|
+
}
|
|
675
|
+
appendTo(target) {
|
|
676
|
+
target.element.append(this.element);
|
|
677
|
+
return this;
|
|
678
|
+
}
|
|
679
|
+
prependTo(target) {
|
|
680
|
+
target.element.prepend(this.element);
|
|
681
|
+
return this;
|
|
682
|
+
}
|
|
683
|
+
trigger(event, e) {
|
|
684
|
+
this.element.dispatchEvent(e ?? new Event(event));
|
|
685
|
+
return this;
|
|
686
|
+
}
|
|
687
|
+
on(event, handle, options) {
|
|
688
|
+
const entry = new HandlerEntry(handle, undefined, options);
|
|
689
|
+
EventRegistry.instance.on(event, this, entry);
|
|
690
|
+
return this;
|
|
691
|
+
}
|
|
692
|
+
onChild(event, selector, handle, options) {
|
|
693
|
+
const delegate = (e) => {
|
|
694
|
+
const target = e.target;
|
|
695
|
+
if (target && target.closest(selector)) {
|
|
696
|
+
return handle(e);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
const entry = new HandlerEntry(handle, delegate, options);
|
|
700
|
+
EventRegistry.instance.on(event, this, entry);
|
|
701
|
+
return this;
|
|
702
|
+
}
|
|
703
|
+
once(event, handle, options) {
|
|
704
|
+
const mergedOptions = { ...(options ?? {}), once: true };
|
|
705
|
+
const entry = new HandlerEntry(handle, undefined, mergedOptions);
|
|
706
|
+
this.off(event, handle, mergedOptions);
|
|
707
|
+
EventRegistry.instance.on(event, this, entry);
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
off(event, handle, options) {
|
|
711
|
+
if (handle) {
|
|
712
|
+
const entry = new HandlerEntry(handle, undefined, options);
|
|
713
|
+
EventRegistry.instance.off(event, this, entry);
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
EventRegistry.instance.off(event, this);
|
|
717
|
+
}
|
|
718
|
+
return this;
|
|
719
|
+
}
|
|
720
|
+
click(handle) {
|
|
721
|
+
if (handle) {
|
|
722
|
+
this.element.addEventListener("click", async (e) => await handle(e));
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
this.trigger("click");
|
|
726
|
+
}
|
|
727
|
+
return this;
|
|
728
|
+
}
|
|
729
|
+
dblclick(handle) {
|
|
730
|
+
if (handle) {
|
|
731
|
+
this.element.addEventListener("dblclick", async (e) => await handle(e));
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
this.trigger("dblclick");
|
|
735
|
+
}
|
|
736
|
+
return this;
|
|
737
|
+
}
|
|
738
|
+
mouseover(handle) {
|
|
739
|
+
if (handle) {
|
|
740
|
+
this.element.addEventListener("mouseover", async (e) => await handle(e));
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
this.trigger("mouseover");
|
|
744
|
+
}
|
|
745
|
+
return this;
|
|
746
|
+
}
|
|
747
|
+
mouseout(handle) {
|
|
748
|
+
if (handle) {
|
|
749
|
+
this.element.addEventListener("mouseout", async (e) => await handle(e));
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
this.trigger("mouseout");
|
|
753
|
+
}
|
|
754
|
+
return this;
|
|
755
|
+
}
|
|
756
|
+
mousedown(handle) {
|
|
757
|
+
if (handle) {
|
|
758
|
+
this.element.addEventListener("mousedown", async (e) => await handle(e));
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
this.trigger("mousedown");
|
|
762
|
+
}
|
|
763
|
+
return this;
|
|
764
|
+
}
|
|
765
|
+
mouseup(handle) {
|
|
766
|
+
if (handle) {
|
|
767
|
+
this.element.addEventListener("mouseup", async (e) => await handle(e));
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
this.trigger("mouseup");
|
|
771
|
+
}
|
|
772
|
+
return this;
|
|
773
|
+
}
|
|
774
|
+
mousemove(handle) {
|
|
775
|
+
if (handle) {
|
|
776
|
+
this.element.addEventListener("mousemove", async (e) => await handle(e));
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
this.trigger("mousemove");
|
|
780
|
+
}
|
|
781
|
+
return this;
|
|
782
|
+
}
|
|
783
|
+
mouseenter(handle) {
|
|
784
|
+
if (handle) {
|
|
785
|
+
this.element.addEventListener("mouseenter", async (e) => await handle(e));
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
this.trigger("mouseenter");
|
|
789
|
+
}
|
|
790
|
+
return this;
|
|
791
|
+
}
|
|
792
|
+
mouseleave(handle) {
|
|
793
|
+
if (handle) {
|
|
794
|
+
this.element.addEventListener("mouseleave", async (e) => await handle(e));
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
this.trigger("mouseleave");
|
|
798
|
+
}
|
|
799
|
+
return this;
|
|
800
|
+
}
|
|
801
|
+
dragstart(handle) {
|
|
802
|
+
if (handle) {
|
|
803
|
+
this.element.addEventListener("dragstart", async (e) => await handle(e));
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
this.trigger("dragstart");
|
|
807
|
+
}
|
|
808
|
+
return this;
|
|
809
|
+
}
|
|
810
|
+
dragend(handle) {
|
|
811
|
+
if (handle) {
|
|
812
|
+
this.element.addEventListener("dragend", async (e) => await handle(e));
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
this.trigger("dragend");
|
|
816
|
+
}
|
|
817
|
+
return this;
|
|
818
|
+
}
|
|
819
|
+
dragover(handle) {
|
|
820
|
+
if (handle) {
|
|
821
|
+
this.element.addEventListener("dragover", async (e) => await handle(e));
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
this.trigger("dragover");
|
|
825
|
+
}
|
|
826
|
+
return this;
|
|
827
|
+
}
|
|
828
|
+
dragenter(handle) {
|
|
829
|
+
if (handle) {
|
|
830
|
+
this.element.addEventListener("dragenter", async (e) => await handle(e));
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
this.trigger("dragenter");
|
|
834
|
+
}
|
|
835
|
+
return this;
|
|
836
|
+
}
|
|
837
|
+
dragleave(handle) {
|
|
838
|
+
if (handle) {
|
|
839
|
+
this.element.addEventListener("dragleave", async (e) => await handle(e));
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
this.trigger("dragleave");
|
|
843
|
+
}
|
|
844
|
+
return this;
|
|
845
|
+
}
|
|
846
|
+
drop(handle) {
|
|
847
|
+
if (handle) {
|
|
848
|
+
this.element.addEventListener("drop", async (e) => await handle(e));
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
this.trigger("drop");
|
|
852
|
+
}
|
|
853
|
+
return this;
|
|
854
|
+
}
|
|
855
|
+
drag(handle) {
|
|
856
|
+
if (handle) {
|
|
857
|
+
this.element.addEventListener("drag", async (e) => await handle(e));
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
this.trigger("drag");
|
|
861
|
+
}
|
|
862
|
+
return this;
|
|
863
|
+
}
|
|
864
|
+
removed(handle) {
|
|
865
|
+
this.element.addEventListener("removed", async (e) => await handle(e));
|
|
866
|
+
return this;
|
|
867
|
+
}
|
|
868
|
+
keydown(handle) {
|
|
869
|
+
if (handle) {
|
|
870
|
+
this.element.addEventListener("keydown", async (e) => await handle(e));
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
this.trigger("keydown");
|
|
874
|
+
}
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
877
|
+
keyup(handle) {
|
|
878
|
+
if (handle) {
|
|
879
|
+
this.element.addEventListener("keyup", async (e) => await handle(e));
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
this.trigger("keyup");
|
|
883
|
+
}
|
|
884
|
+
return this;
|
|
885
|
+
}
|
|
886
|
+
keypress(handle) {
|
|
887
|
+
if (handle) {
|
|
888
|
+
this.element.addEventListener("keypress", async (e) => await handle(e));
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
this.trigger("keypress");
|
|
892
|
+
}
|
|
893
|
+
return this;
|
|
894
|
+
}
|
|
895
|
+
change(handle) {
|
|
896
|
+
if (handle) {
|
|
897
|
+
this.element.addEventListener("change", async (e) => await handle(e));
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
this.trigger("change");
|
|
901
|
+
}
|
|
902
|
+
return this;
|
|
903
|
+
}
|
|
904
|
+
input(handle) {
|
|
905
|
+
if (handle) {
|
|
906
|
+
this.element.addEventListener("input", async (e) => await handle(e));
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
this.trigger("input");
|
|
910
|
+
}
|
|
911
|
+
return this;
|
|
912
|
+
}
|
|
913
|
+
static get intersectionObserver() {
|
|
914
|
+
return CotomyElement._intersectionObserver = CotomyElement._intersectionObserver
|
|
915
|
+
?? new IntersectionObserver(entries => {
|
|
916
|
+
entries.filter(entry => entry.isIntersecting).forEach(entry => entry.target.dispatchEvent(new Event("inview")));
|
|
917
|
+
entries.filter(entry => !entry.isIntersecting).forEach(entry => entry.target.dispatchEvent(new Event("outview")));
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
inview(handle) {
|
|
921
|
+
if (handle) {
|
|
922
|
+
CotomyElement.intersectionObserver.observe(this.element);
|
|
923
|
+
this.element.addEventListener("inview", async (e) => await handle(e));
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
this.trigger("inview");
|
|
927
|
+
}
|
|
928
|
+
return this;
|
|
929
|
+
}
|
|
930
|
+
outview(handle) {
|
|
931
|
+
if (handle) {
|
|
932
|
+
CotomyElement.intersectionObserver.observe(this.element);
|
|
933
|
+
this.element.addEventListener("outview", async (e) => await handle(e));
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
this.trigger("outview");
|
|
937
|
+
}
|
|
938
|
+
return this;
|
|
939
|
+
}
|
|
940
|
+
focus(handle) {
|
|
941
|
+
if (handle) {
|
|
942
|
+
this.element.addEventListener("focus", async (e) => await handle(e));
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
this.trigger("focus");
|
|
946
|
+
}
|
|
947
|
+
return this;
|
|
948
|
+
}
|
|
949
|
+
blur(handle) {
|
|
950
|
+
if (handle) {
|
|
951
|
+
this.element.addEventListener("blur", async (e) => await handle(e));
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
this.trigger("blur");
|
|
955
|
+
}
|
|
956
|
+
return this;
|
|
957
|
+
}
|
|
958
|
+
focusin(handle) {
|
|
959
|
+
if (handle) {
|
|
960
|
+
this.element.addEventListener("focusin", async (e) => await handle(e));
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
this.trigger("focusin");
|
|
964
|
+
}
|
|
965
|
+
return this;
|
|
966
|
+
}
|
|
967
|
+
focusout(handle) {
|
|
968
|
+
if (handle) {
|
|
969
|
+
this.element.addEventListener("focusout", async (e) => await handle(e));
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
this.trigger("focusout");
|
|
973
|
+
}
|
|
974
|
+
return this;
|
|
975
|
+
}
|
|
976
|
+
filedrop(handle) {
|
|
977
|
+
this.element.addEventListener("drop", async (e) => {
|
|
978
|
+
e.preventDefault();
|
|
979
|
+
const dt = e.dataTransfer;
|
|
980
|
+
if (dt && dt.files) {
|
|
981
|
+
await handle(Array.from(dt.files));
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
return this;
|
|
985
|
+
}
|
|
986
|
+
resize(handle) {
|
|
987
|
+
this.listenLayoutEvents();
|
|
988
|
+
if (handle) {
|
|
989
|
+
this.element.addEventListener("cotomy:resize", async (e) => await handle(e));
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
this.trigger("cotomy:resize");
|
|
993
|
+
}
|
|
994
|
+
return this;
|
|
995
|
+
}
|
|
996
|
+
scroll(handle) {
|
|
997
|
+
this.listenLayoutEvents();
|
|
998
|
+
if (handle) {
|
|
999
|
+
this.element.addEventListener("cotomy:scroll", async (e) => await handle(e));
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
this.trigger("cotomy:scroll");
|
|
1003
|
+
}
|
|
1004
|
+
return this;
|
|
1005
|
+
}
|
|
1006
|
+
changelayout(handle) {
|
|
1007
|
+
this.listenLayoutEvents();
|
|
1008
|
+
if (handle) {
|
|
1009
|
+
this.element.addEventListener("cotomy:changelayout", async (e) => await handle(e));
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
this.trigger("cotomy:changelayout");
|
|
1013
|
+
}
|
|
1014
|
+
return this;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
CotomyElement.LISTEN_LAYOUT_EVENTS_ATTRIBUTE = "data-cotomy-layout";
|
|
1018
|
+
CotomyElement._intersectionObserver = null;
|
|
1019
|
+
export class CotomyMetaElement extends CotomyElement {
|
|
1020
|
+
static get(name) {
|
|
1021
|
+
return CotomyElement.first(`meta[name="${name}" i]`, CotomyMetaElement)
|
|
1022
|
+
?? CotomyElement.empty(CotomyMetaElement);
|
|
1023
|
+
}
|
|
1024
|
+
get content() {
|
|
1025
|
+
return this.attribute("content") ?? "";
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
export class CotomyWindow {
|
|
1029
|
+
constructor() {
|
|
1030
|
+
this._body = CotomyElement.empty();
|
|
1031
|
+
this._mutationObserver = null;
|
|
1032
|
+
this._reloading = false;
|
|
1033
|
+
this._eventHandlers = {};
|
|
1034
|
+
}
|
|
1035
|
+
static get instance() {
|
|
1036
|
+
return CotomyWindow._instance ?? (CotomyWindow._instance = new CotomyWindow());
|
|
1037
|
+
}
|
|
1038
|
+
get initialized() {
|
|
1039
|
+
return this._body.attached;
|
|
1040
|
+
}
|
|
1041
|
+
initialize() {
|
|
1042
|
+
if (!this.initialized) {
|
|
1043
|
+
if (!document.body) {
|
|
1044
|
+
throw new Error("<body> element not found. DOM may not be ready.");
|
|
1045
|
+
}
|
|
1046
|
+
this._body = CotomyElement.first("body");
|
|
1047
|
+
const changeLayoutEvents = ["resize", "scroll", "orientationchange", "fullscreenchange", "cotomy:ready"];
|
|
1048
|
+
changeLayoutEvents.forEach(e => {
|
|
1049
|
+
window.addEventListener(e, () => {
|
|
1050
|
+
const changeLayoutEvent = new CustomEvent("cotomy:changelayout");
|
|
1051
|
+
window.dispatchEvent(changeLayoutEvent);
|
|
1052
|
+
}, { passive: true });
|
|
1053
|
+
});
|
|
1054
|
+
document.addEventListener("dragover", e => {
|
|
1055
|
+
e.stopPropagation();
|
|
1056
|
+
e.preventDefault();
|
|
1057
|
+
});
|
|
1058
|
+
this.resize(() => {
|
|
1059
|
+
document.querySelectorAll(`[${CotomyElement.LISTEN_LAYOUT_EVENTS_ATTRIBUTE}]`).forEach(e => {
|
|
1060
|
+
e.dispatchEvent(new CustomEvent("cotomy:resize"));
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
this.scroll(() => {
|
|
1064
|
+
document.querySelectorAll(`[${CotomyElement.LISTEN_LAYOUT_EVENTS_ATTRIBUTE}]`).forEach(e => {
|
|
1065
|
+
e.dispatchEvent(new CustomEvent("cotomy:scroll"));
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
this.changeLayout(() => {
|
|
1069
|
+
document.querySelectorAll(`[${CotomyElement.LISTEN_LAYOUT_EVENTS_ATTRIBUTE}]`).forEach(e => {
|
|
1070
|
+
e.dispatchEvent(new CustomEvent("cotomy:changelayout"));
|
|
1071
|
+
});
|
|
1072
|
+
});
|
|
1073
|
+
this._mutationObserver = new MutationObserver(mutations => {
|
|
1074
|
+
mutations.forEach(mutation => {
|
|
1075
|
+
mutation.removedNodes.forEach(node => {
|
|
1076
|
+
if (node instanceof HTMLElement) {
|
|
1077
|
+
const element = new CotomyElement(node);
|
|
1078
|
+
element.trigger("removed");
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
this._mutationObserver.observe(this.body.element, { childList: true, subtree: true });
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
get reloading() {
|
|
1087
|
+
return this._reloading;
|
|
1088
|
+
}
|
|
1089
|
+
reload() {
|
|
1090
|
+
this._reloading = true;
|
|
1091
|
+
location.reload();
|
|
1092
|
+
}
|
|
1093
|
+
get body() {
|
|
1094
|
+
return this._body;
|
|
1095
|
+
}
|
|
1096
|
+
append(e) {
|
|
1097
|
+
this._body.append(e);
|
|
1098
|
+
}
|
|
1099
|
+
moveNext(focused, shift = false) {
|
|
1100
|
+
const selector = "input, a, select, button, textarea";
|
|
1101
|
+
const focusableElements = Array.from(this.body.element.querySelectorAll(selector))
|
|
1102
|
+
.map(e => new CotomyElement(e))
|
|
1103
|
+
.filter(e => e.width > 0 && e.height > 0 && e.visible && e.enabled && !e.hasAttribute("readonly"));
|
|
1104
|
+
const focusedIndex = focusableElements.map(e => e.element).indexOf(focused.element);
|
|
1105
|
+
let nextIndex = focusedIndex + (shift ? -1 : 1);
|
|
1106
|
+
if (nextIndex >= focusableElements.length) {
|
|
1107
|
+
nextIndex = 0;
|
|
1108
|
+
}
|
|
1109
|
+
else if (nextIndex < 0) {
|
|
1110
|
+
nextIndex = focusableElements.length - 1;
|
|
1111
|
+
}
|
|
1112
|
+
if (focusableElements[nextIndex]) {
|
|
1113
|
+
focusableElements[nextIndex].setFocus();
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
trigger(event) {
|
|
1117
|
+
window.dispatchEvent(new Event(event));
|
|
1118
|
+
}
|
|
1119
|
+
on(event, handle) {
|
|
1120
|
+
if (!this._eventHandlers[event])
|
|
1121
|
+
this._eventHandlers[event] = [];
|
|
1122
|
+
this._eventHandlers[event].push(handle);
|
|
1123
|
+
window.addEventListener(event, handle);
|
|
1124
|
+
}
|
|
1125
|
+
off(event, handle) {
|
|
1126
|
+
if (handle) {
|
|
1127
|
+
window.removeEventListener(event, handle);
|
|
1128
|
+
this._eventHandlers[event] = this._eventHandlers[event]?.filter(h => h !== handle) ?? [];
|
|
1129
|
+
}
|
|
1130
|
+
else {
|
|
1131
|
+
for (const h of this._eventHandlers[event] ?? []) {
|
|
1132
|
+
window.removeEventListener(event, h);
|
|
1133
|
+
}
|
|
1134
|
+
delete this._eventHandlers[event];
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
handlers(event) {
|
|
1138
|
+
return this._eventHandlers[event] ?? [];
|
|
1139
|
+
}
|
|
1140
|
+
load(handle) {
|
|
1141
|
+
this.on("load", handle);
|
|
1142
|
+
}
|
|
1143
|
+
ready(handle) {
|
|
1144
|
+
this.on("cotomy:ready", handle);
|
|
1145
|
+
}
|
|
1146
|
+
resize(handle) {
|
|
1147
|
+
if (handle) {
|
|
1148
|
+
this.on("resize", handle);
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
this.trigger("resize");
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
scroll(handle) {
|
|
1155
|
+
if (handle) {
|
|
1156
|
+
this.on("scroll", handle);
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
this.trigger("scroll");
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
changeLayout(handle) {
|
|
1163
|
+
if (handle) {
|
|
1164
|
+
this.on("cotomy:changelayout", handle);
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
this.trigger("cotomy:changelayout");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
pageshow(handle) {
|
|
1171
|
+
if (handle) {
|
|
1172
|
+
this.on("pageshow", handle);
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
this.trigger("pageshow");
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
get scrollTop() {
|
|
1179
|
+
return window.scrollY || document.documentElement.scrollTop;
|
|
1180
|
+
}
|
|
1181
|
+
get scrollLeft() {
|
|
1182
|
+
return window.scrollX || document.documentElement.scrollLeft;
|
|
1183
|
+
}
|
|
1184
|
+
get width() {
|
|
1185
|
+
return window.innerWidth;
|
|
1186
|
+
}
|
|
1187
|
+
get height() {
|
|
1188
|
+
return window.innerHeight;
|
|
1189
|
+
}
|
|
1190
|
+
get documentWidth() {
|
|
1191
|
+
return document.documentElement.scrollWidth;
|
|
1192
|
+
}
|
|
1193
|
+
get documentHeight() {
|
|
1194
|
+
return document.documentElement.scrollHeight;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
CotomyWindow._instance = null;
|
|
1198
|
+
//# sourceMappingURL=view.js.map
|