@dooboostore/dom-parser 1.0.1 → 1.0.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/dist/cjs/DomParser.js +33 -12
- package/dist/cjs/DomParser.js.map +2 -2
- package/dist/cjs/node/DocumentBase.js +4 -0
- package/dist/cjs/node/DocumentBase.js.map +2 -2
- package/dist/cjs/node/elements/Element.js.map +1 -1
- package/dist/cjs/node/elements/ElementBase.js +12 -2
- package/dist/cjs/node/elements/ElementBase.js.map +2 -2
- package/dist/cjs/node/elements/HTMLElement.js.map +1 -1
- package/dist/cjs/node/elements/HTMLElementBase.js +154 -2
- package/dist/cjs/node/elements/HTMLElementBase.js.map +2 -2
- package/dist/cjs/window/WindowBase.js +128 -7
- package/dist/cjs/window/WindowBase.js.map +2 -2
- package/dist/esm/DomParser.js +33 -12
- package/dist/esm/DomParser.js.map +2 -2
- package/dist/esm/node/DocumentBase.js +4 -0
- package/dist/esm/node/DocumentBase.js.map +2 -2
- package/dist/esm/node/elements/ElementBase.js +12 -2
- package/dist/esm/node/elements/ElementBase.js.map +2 -2
- package/dist/esm/node/elements/HTMLElementBase.js +154 -2
- package/dist/esm/node/elements/HTMLElementBase.js.map +2 -2
- package/dist/esm/window/WindowBase.js +128 -7
- package/dist/esm/window/WindowBase.js.map +2 -2
- package/dist/esm-bundle/dooboostore-dom-parser.esm.js +504 -195
- package/dist/esm-bundle/dooboostore-dom-parser.esm.js.map +3 -3
- package/dist/types/DomParser.d.ts +4 -0
- package/dist/types/DomParser.d.ts.map +1 -1
- package/dist/types/node/DocumentBase.d.ts +2 -0
- package/dist/types/node/DocumentBase.d.ts.map +1 -1
- package/dist/types/node/elements/Element.d.ts +1 -0
- package/dist/types/node/elements/Element.d.ts.map +1 -1
- package/dist/types/node/elements/ElementBase.d.ts +2 -2
- package/dist/types/node/elements/ElementBase.d.ts.map +1 -1
- package/dist/types/node/elements/HTMLElement.d.ts +32 -1
- package/dist/types/node/elements/HTMLElement.d.ts.map +1 -1
- package/dist/types/node/elements/HTMLElementBase.d.ts +11 -2
- package/dist/types/node/elements/HTMLElementBase.d.ts.map +1 -1
- package/dist/types/window/WindowBase.d.ts +12 -2
- package/dist/types/window/WindowBase.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-dom-parser.umd.js +504 -195
- package/dist/umd-bundle/dooboostore-dom-parser.umd.js.map +3 -3
- package/package.json +1 -1
- package/src/DomParser.ts +457 -436
- package/src/node/DocumentBase.ts +7 -2
- package/src/node/elements/Element.ts +24 -23
- package/src/node/elements/ElementBase.ts +50 -41
- package/src/node/elements/HTMLElement.ts +36 -1
- package/src/node/elements/HTMLElementBase.ts +191 -5
- package/src/window/WindowBase.ts +1128 -919
package/src/node/DocumentBase.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {ParentNodeBase} from './ParentNodeBase';
|
|
2
2
|
import {DOCUMENT_NODE, Node} from './Node';
|
|
3
3
|
import {Document, ElementCreationOptions, DocumentReadyState, DocumentVisibilityState, ImportNodeOptions, CaretPositionFromPointOptions, StartViewTransitionOptions, ViewTransitionUpdateCallback} from './Document';
|
|
4
|
+
import {Location} from '../window/Window';
|
|
4
5
|
import {Comment} from './Comment';
|
|
5
6
|
import {Text} from './Text';
|
|
6
7
|
import {DocumentFragment} from './DocumentFragment';
|
|
@@ -65,7 +66,7 @@ export class DocumentBase extends ParentNodeBase implements Document {
|
|
|
65
66
|
readonly links: HTMLCollectionOf<any> = new HTMLCollectionOf([]);
|
|
66
67
|
|
|
67
68
|
// Location property - simple implementation for Document
|
|
68
|
-
private _location:
|
|
69
|
+
private _location: Location = {
|
|
69
70
|
href: 'about:blank',
|
|
70
71
|
protocol: 'about:',
|
|
71
72
|
host: '',
|
|
@@ -78,9 +79,13 @@ export class DocumentBase extends ParentNodeBase implements Document {
|
|
|
78
79
|
assign: (url: string) => { this._location.href = url; },
|
|
79
80
|
replace: (url: string) => { this._location.href = url; },
|
|
80
81
|
reload: () => {},
|
|
82
|
+
// @ts-ignore
|
|
81
83
|
toString: () => this._location.href
|
|
82
84
|
};
|
|
83
|
-
|
|
85
|
+
|
|
86
|
+
setLocation(location: Location): void {
|
|
87
|
+
this._location = location;
|
|
88
|
+
}
|
|
84
89
|
get location(): any {
|
|
85
90
|
return this._location;
|
|
86
91
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Node } from '../Node';
|
|
2
2
|
import { ChildNode } from '../ChildNode';
|
|
3
3
|
import { ParentNode } from '../ParentNode';
|
|
4
|
-
import {HTMLCollectionOf} from '../collection/HTMLCollectionOf';
|
|
5
|
-
import {HTMLElement} from './HTMLElement';
|
|
6
|
-
import {SVGElement} from './SVGElement';
|
|
7
|
-
import {MathMLElement} from './MathMLElement';
|
|
8
|
-
import {HTMLElementTagNameMap, SVGElementTagNameMap, MathMLElementTagNameMap} from '../index';
|
|
4
|
+
import { HTMLCollectionOf } from '../collection/HTMLCollectionOf';
|
|
5
|
+
import { HTMLElement } from './HTMLElement';
|
|
6
|
+
import { SVGElement } from './SVGElement';
|
|
7
|
+
import { MathMLElement } from './MathMLElement';
|
|
8
|
+
import { HTMLElementTagNameMap, SVGElementTagNameMap, MathMLElementTagNameMap } from '../index';
|
|
9
9
|
// Forward declarations for types that will be implemented later
|
|
10
10
|
export interface NamedNodeMap {
|
|
11
11
|
readonly length: number;
|
|
@@ -38,6 +38,7 @@ export interface DOMRect {
|
|
|
38
38
|
readonly width: number;
|
|
39
39
|
readonly x: number;
|
|
40
40
|
readonly y: number;
|
|
41
|
+
toJSON(): any;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export interface DOMRectList {
|
|
@@ -235,24 +236,24 @@ export interface Event {
|
|
|
235
236
|
stopImmediatePropagation(): void;
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
export interface UIEvent extends Event {}
|
|
239
|
-
export interface MouseEvent extends UIEvent {}
|
|
240
|
-
export interface KeyboardEvent extends UIEvent {}
|
|
241
|
-
export interface FocusEvent extends UIEvent {}
|
|
242
|
-
export interface InputEvent extends UIEvent {}
|
|
243
|
-
export interface WheelEvent extends MouseEvent {}
|
|
244
|
-
export interface PointerEvent extends MouseEvent {}
|
|
245
|
-
export interface TouchEvent extends UIEvent {}
|
|
246
|
-
export interface DragEvent extends MouseEvent {}
|
|
247
|
-
export interface ClipboardEvent extends Event {}
|
|
248
|
-
export interface AnimationEvent extends Event {}
|
|
249
|
-
export interface TransitionEvent extends Event {}
|
|
250
|
-
export interface CompositionEvent extends UIEvent {}
|
|
251
|
-
export interface FormDataEvent extends Event {}
|
|
252
|
-
export interface ProgressEvent extends Event {}
|
|
253
|
-
export interface SecurityPolicyViolationEvent extends Event {}
|
|
254
|
-
export interface SubmitEvent extends Event {}
|
|
255
|
-
export interface ErrorEvent extends Event {}
|
|
239
|
+
export interface UIEvent extends Event { }
|
|
240
|
+
export interface MouseEvent extends UIEvent { }
|
|
241
|
+
export interface KeyboardEvent extends UIEvent { }
|
|
242
|
+
export interface FocusEvent extends UIEvent { }
|
|
243
|
+
export interface InputEvent extends UIEvent { }
|
|
244
|
+
export interface WheelEvent extends MouseEvent { }
|
|
245
|
+
export interface PointerEvent extends MouseEvent { }
|
|
246
|
+
export interface TouchEvent extends UIEvent { }
|
|
247
|
+
export interface DragEvent extends MouseEvent { }
|
|
248
|
+
export interface ClipboardEvent extends Event { }
|
|
249
|
+
export interface AnimationEvent extends Event { }
|
|
250
|
+
export interface TransitionEvent extends Event { }
|
|
251
|
+
export interface CompositionEvent extends UIEvent { }
|
|
252
|
+
export interface FormDataEvent extends Event { }
|
|
253
|
+
export interface ProgressEvent extends Event { }
|
|
254
|
+
export interface SecurityPolicyViolationEvent extends Event { }
|
|
255
|
+
export interface SubmitEvent extends Event { }
|
|
256
|
+
export interface ErrorEvent extends Event { }
|
|
256
257
|
|
|
257
258
|
export interface EventTarget {
|
|
258
259
|
addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
|
|
@@ -3,7 +3,7 @@ import { ChildNodeBase } from '../ChildNodeBase';
|
|
|
3
3
|
import { HTMLCollection } from "../collection";
|
|
4
4
|
import { Text } from "../Text";
|
|
5
5
|
import { ElementFactory } from "../../factory/ElementFactory";
|
|
6
|
-
import { Element, DOMTokenList, Attr, NamedNodeMap } from './Element';
|
|
6
|
+
import { Element, DOMTokenList, Attr, NamedNodeMap, DOMRect } from './Element';
|
|
7
7
|
import { ELEMENT_NODE, TEXT_NODE, ATTRIBUTE_NODE } from '../Node';
|
|
8
8
|
import { CSSSelector } from '../../utils/CSSSelector';
|
|
9
9
|
import { HTMLElementTagNameMap } from "./index";
|
|
@@ -121,10 +121,9 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
121
121
|
*/
|
|
122
122
|
private generateChildElementHTML(element: any): string {
|
|
123
123
|
const tagName = element.tagName.toLowerCase();
|
|
124
|
-
|
|
125
124
|
// Get attributes
|
|
126
125
|
const attrs = Array.from(element._attributes?.entries() || [])
|
|
127
|
-
.map(([name, value]: [string, string]) => value === '' ? ` ${name}` : ` ${name}="${value.replace(/"/g, '"')}"`)
|
|
126
|
+
.map(([name, value]: [string, string]) => value === '' ? ` ${name}` : ` ${name}="${String(value).replace(/"/g, '"')}"`)
|
|
128
127
|
.join('');
|
|
129
128
|
|
|
130
129
|
// Check if it's a self-closing tag
|
|
@@ -154,13 +153,13 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
154
153
|
*/
|
|
155
154
|
private parseAndAppendHTML(html: string): void {
|
|
156
155
|
const ElementFactory = require('../../factory/ElementFactory').ElementFactory;
|
|
157
|
-
|
|
156
|
+
|
|
158
157
|
let i = 0;
|
|
159
158
|
const length = html.length;
|
|
160
|
-
|
|
159
|
+
|
|
161
160
|
while (i < length) {
|
|
162
161
|
const nextTagStart = html.indexOf('<', i);
|
|
163
|
-
|
|
162
|
+
|
|
164
163
|
// Handle text content before next tag
|
|
165
164
|
if (nextTagStart === -1) {
|
|
166
165
|
// No more tags, rest is text
|
|
@@ -184,15 +183,15 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
184
183
|
this.appendChild(textNode);
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
|
-
|
|
186
|
+
|
|
188
187
|
i = nextTagStart;
|
|
189
|
-
|
|
188
|
+
|
|
190
189
|
// Parse the tag - find the real tag end, considering quoted attributes
|
|
191
190
|
const tagEnd = this.findTagEnd(html, i);
|
|
192
191
|
if (tagEnd === -1) break;
|
|
193
|
-
|
|
192
|
+
|
|
194
193
|
const tagContent = html.substring(i + 1, tagEnd);
|
|
195
|
-
|
|
194
|
+
|
|
196
195
|
// Handle comments
|
|
197
196
|
if (tagContent.startsWith('!--')) {
|
|
198
197
|
const commentEnd = html.indexOf('-->', i);
|
|
@@ -205,20 +204,20 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
205
204
|
continue;
|
|
206
205
|
}
|
|
207
206
|
}
|
|
208
|
-
|
|
207
|
+
|
|
209
208
|
// Handle self-closing tags
|
|
210
209
|
if (tagContent.endsWith('/')) {
|
|
211
210
|
const parts = tagContent.slice(0, -1).trim().split(/\s+/);
|
|
212
211
|
const tagName = parts[0];
|
|
213
212
|
const attributeString = tagContent.slice(tagName.length, -1).trim();
|
|
214
|
-
|
|
213
|
+
|
|
215
214
|
const element = ElementFactory.createElement(tagName, this._ownerDocument);
|
|
216
215
|
this.parseAttributes(element, attributeString);
|
|
217
216
|
this.appendChild(element);
|
|
218
217
|
i = tagEnd + 1;
|
|
219
218
|
continue;
|
|
220
219
|
}
|
|
221
|
-
|
|
220
|
+
|
|
222
221
|
// Handle closing tags - if they don't have matching opening tags, treat as text
|
|
223
222
|
if (tagContent.startsWith('/')) {
|
|
224
223
|
// For now, treat unmatched closing tags as text content
|
|
@@ -229,47 +228,47 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
229
228
|
i = tagEnd + 1;
|
|
230
229
|
continue;
|
|
231
230
|
}
|
|
232
|
-
|
|
231
|
+
|
|
233
232
|
// Handle opening tags
|
|
234
233
|
const parts = tagContent.split(/\s+/);
|
|
235
234
|
const tagName = parts[0];
|
|
236
235
|
const attributeString = tagContent.slice(tagName.length).trim();
|
|
237
|
-
|
|
236
|
+
|
|
238
237
|
// Special handling for style and script tags
|
|
239
238
|
if (tagName === 'style' || tagName === 'script') {
|
|
240
239
|
const closingTag = `</${tagName}>`;
|
|
241
240
|
const closingTagIndex = html.indexOf(closingTag, tagEnd + 1);
|
|
242
|
-
|
|
241
|
+
|
|
243
242
|
if (closingTagIndex !== -1) {
|
|
244
243
|
const element = ElementFactory.createElement(tagName, this._ownerDocument);
|
|
245
244
|
this.parseAttributes(element, attributeString);
|
|
246
|
-
|
|
245
|
+
|
|
247
246
|
const content = html.substring(tagEnd + 1, closingTagIndex);
|
|
248
247
|
if (content) {
|
|
249
248
|
const { TextBase } = require('../TextBase');
|
|
250
249
|
const textNode = new TextBase(content, this._ownerDocument);
|
|
251
250
|
element.appendChild(textNode);
|
|
252
251
|
}
|
|
253
|
-
|
|
252
|
+
|
|
254
253
|
this.appendChild(element);
|
|
255
254
|
i = closingTagIndex + closingTag.length;
|
|
256
255
|
continue;
|
|
257
256
|
}
|
|
258
257
|
}
|
|
259
|
-
|
|
258
|
+
|
|
260
259
|
// Handle regular opening tags with content
|
|
261
260
|
const closingTag = `</${tagName}>`;
|
|
262
261
|
const closingTagIndex = this.findMatchingClosingTag(html, tagName, tagEnd + 1);
|
|
263
|
-
|
|
262
|
+
|
|
264
263
|
if (closingTagIndex !== -1) {
|
|
265
264
|
const element = ElementFactory.createElement(tagName, this._ownerDocument);
|
|
266
265
|
this.parseAttributes(element, attributeString);
|
|
267
|
-
|
|
266
|
+
|
|
268
267
|
const content = html.substring(tagEnd + 1, closingTagIndex);
|
|
269
268
|
if (content.trim()) {
|
|
270
269
|
element.innerHTML = content;
|
|
271
270
|
}
|
|
272
|
-
|
|
271
|
+
|
|
273
272
|
this.appendChild(element);
|
|
274
273
|
i = closingTagIndex + closingTag.length;
|
|
275
274
|
} else {
|
|
@@ -281,7 +280,7 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
281
280
|
}
|
|
282
281
|
}
|
|
283
282
|
}
|
|
284
|
-
|
|
283
|
+
|
|
285
284
|
/**
|
|
286
285
|
* Find the real end of a tag, considering quoted attributes
|
|
287
286
|
*/
|
|
@@ -289,10 +288,10 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
289
288
|
let i = startIndex + 1; // Skip the '<'
|
|
290
289
|
let inQuotes = false;
|
|
291
290
|
let quoteChar = '';
|
|
292
|
-
|
|
291
|
+
|
|
293
292
|
while (i < html.length) {
|
|
294
293
|
const char = html[i];
|
|
295
|
-
|
|
294
|
+
|
|
296
295
|
if (!inQuotes) {
|
|
297
296
|
if (char === '"' || char === "'") {
|
|
298
297
|
inQuotes = true;
|
|
@@ -306,10 +305,10 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
306
305
|
quoteChar = '';
|
|
307
306
|
}
|
|
308
307
|
}
|
|
309
|
-
|
|
308
|
+
|
|
310
309
|
i++;
|
|
311
310
|
}
|
|
312
|
-
|
|
311
|
+
|
|
313
312
|
return -1; // No closing '>' found
|
|
314
313
|
}
|
|
315
314
|
|
|
@@ -330,15 +329,15 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
330
329
|
const closeTag = `</${tagName}>`;
|
|
331
330
|
let depth = 1;
|
|
332
331
|
let i = startIndex;
|
|
333
|
-
|
|
332
|
+
|
|
334
333
|
while (i < html.length && depth > 0) {
|
|
335
334
|
const nextOpen = html.indexOf(openTag, i);
|
|
336
335
|
const nextClose = html.indexOf(closeTag, i);
|
|
337
|
-
|
|
336
|
+
|
|
338
337
|
if (nextClose === -1) {
|
|
339
338
|
return -1; // No closing tag found
|
|
340
339
|
}
|
|
341
|
-
|
|
340
|
+
|
|
342
341
|
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
343
342
|
// Found another opening tag before the closing tag
|
|
344
343
|
// Make sure it's a complete tag (not just a substring)
|
|
@@ -356,7 +355,7 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
356
355
|
i = nextClose + closeTag.length;
|
|
357
356
|
}
|
|
358
357
|
}
|
|
359
|
-
|
|
358
|
+
|
|
360
359
|
return -1;
|
|
361
360
|
}
|
|
362
361
|
|
|
@@ -408,18 +407,18 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
408
407
|
|
|
409
408
|
if (position < length) {
|
|
410
409
|
const quote = attributeString[position];
|
|
411
|
-
|
|
410
|
+
|
|
412
411
|
if (quote === '"' || quote === "'") {
|
|
413
412
|
// Quoted value - find matching closing quote
|
|
414
413
|
position++; // Skip opening quote
|
|
415
414
|
const valueStart = position;
|
|
416
|
-
|
|
415
|
+
|
|
417
416
|
while (position < length && attributeString[position] !== quote) {
|
|
418
417
|
position++;
|
|
419
418
|
}
|
|
420
|
-
|
|
419
|
+
|
|
421
420
|
value = attributeString.substring(valueStart, position);
|
|
422
|
-
|
|
421
|
+
|
|
423
422
|
if (position < length && attributeString[position] === quote) {
|
|
424
423
|
position++; // Skip closing quote
|
|
425
424
|
}
|
|
@@ -436,7 +435,7 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
436
435
|
|
|
437
436
|
// Decode HTML entities in attribute values
|
|
438
437
|
value = this.decodeHTMLEntities(value);
|
|
439
|
-
|
|
438
|
+
|
|
440
439
|
element.setAttribute(name, value);
|
|
441
440
|
}
|
|
442
441
|
}
|
|
@@ -488,7 +487,7 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
488
487
|
if (entityMap[entity]) {
|
|
489
488
|
return entityMap[entity];
|
|
490
489
|
}
|
|
491
|
-
|
|
490
|
+
|
|
492
491
|
// Handle numeric entities like ' "
|
|
493
492
|
if (entity.startsWith('&#') && entity.endsWith(';')) {
|
|
494
493
|
const numStr = entity.slice(2, -1);
|
|
@@ -497,7 +496,7 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
497
496
|
return String.fromCharCode(num);
|
|
498
497
|
}
|
|
499
498
|
}
|
|
500
|
-
|
|
499
|
+
|
|
501
500
|
// Handle hex entities like '
|
|
502
501
|
if (entity.startsWith('&#x') && entity.endsWith(';')) {
|
|
503
502
|
const hexStr = entity.slice(3, -1);
|
|
@@ -506,7 +505,7 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
506
505
|
return String.fromCharCode(num);
|
|
507
506
|
}
|
|
508
507
|
}
|
|
509
|
-
|
|
508
|
+
|
|
510
509
|
// Return original if not recognized
|
|
511
510
|
return entity;
|
|
512
511
|
});
|
|
@@ -708,8 +707,18 @@ export abstract class ElementBase extends ParentNodeBase implements Element {
|
|
|
708
707
|
return this.getAttributeNode(localName);
|
|
709
708
|
}
|
|
710
709
|
|
|
711
|
-
getBoundingClientRect():
|
|
712
|
-
|
|
710
|
+
getBoundingClientRect(): DOMRect {
|
|
711
|
+
return {
|
|
712
|
+
bottom: 0,
|
|
713
|
+
height: 0,
|
|
714
|
+
left: 0,
|
|
715
|
+
right: 0,
|
|
716
|
+
top: 0,
|
|
717
|
+
width: 0,
|
|
718
|
+
x: 0,
|
|
719
|
+
y: 0,
|
|
720
|
+
toJSON: () => ({})
|
|
721
|
+
};
|
|
713
722
|
}
|
|
714
723
|
|
|
715
724
|
getClientRects(): any {
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
import { Element } from './Element';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ElementCSSInlineStyle)
|
|
5
|
+
*/
|
|
6
|
+
export interface ElementCSSInlineStyle {
|
|
7
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/attributeStyleMap) */
|
|
8
|
+
readonly attributeStyleMap: StylePropertyMap;
|
|
9
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/style) */
|
|
10
|
+
style: CSSStyleDeclaration | string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface StylePropertyMap {
|
|
14
|
+
append(property: string, ...values: (string | any)[]): void;
|
|
15
|
+
clear(): void;
|
|
16
|
+
delete(property: string): void;
|
|
17
|
+
set(property: string, ...values: (string | any)[]): void;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
|
|
3
21
|
/**
|
|
4
22
|
* The **`HTMLElement`** interface represents any HTML element.
|
|
5
23
|
*
|
|
6
24
|
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement)
|
|
7
25
|
*/
|
|
8
|
-
export interface HTMLElement extends Element {
|
|
26
|
+
export interface HTMLElement extends Element, ElementCSSInlineStyle {
|
|
9
27
|
/**
|
|
10
28
|
* Returns the value of element's title content attribute. Can be set to change it.
|
|
11
29
|
*
|
|
@@ -102,4 +120,21 @@ export interface HTMLElement extends Element {
|
|
|
102
120
|
// 기본 타입들
|
|
103
121
|
export interface FocusOptions {
|
|
104
122
|
preventScroll?: boolean;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface CSSStyleDeclaration {
|
|
126
|
+
cssText: string;
|
|
127
|
+
length: number;
|
|
128
|
+
parentRule: any;
|
|
129
|
+
getPropertyPriority(property: string): string;
|
|
130
|
+
getPropertyValue(property: string): string;
|
|
131
|
+
item(index: number): string;
|
|
132
|
+
removeProperty(property: string): string;
|
|
133
|
+
setProperty(property: string, value: string | null, priority?: string): void;
|
|
134
|
+
[index: number]: string;
|
|
135
|
+
[key: string]: any;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface DOMStringMap {
|
|
139
|
+
[key: string]: string | undefined;
|
|
105
140
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ElementBase } from './ElementBase';
|
|
2
|
-
import { HTMLElement, FocusOptions } from './HTMLElement';
|
|
2
|
+
import { HTMLElement, FocusOptions, CSSStyleDeclaration, DOMStringMap, ElementCSSInlineStyle, StylePropertyMap } from './HTMLElement';
|
|
3
3
|
import { Text } from '../Text';
|
|
4
4
|
import { ElementFactory } from '../../factory';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Base implementation for all HTML elements
|
|
8
8
|
*/
|
|
9
|
-
export abstract class HTMLElementBase extends ElementBase implements HTMLElement {
|
|
9
|
+
export abstract class HTMLElementBase extends ElementBase implements HTMLElement, ElementCSSInlineStyle {
|
|
10
10
|
private _title: string = '';
|
|
11
11
|
private _lang: string = '';
|
|
12
12
|
private _dir: string = '';
|
|
@@ -124,9 +124,6 @@ export abstract class HTMLElementBase extends ElementBase implements HTMLElement
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
focus(options?: FocusOptions): void {
|
|
127
|
-
// Set focus state using data attribute
|
|
128
|
-
this.setAttribute('data-focused', 'true');
|
|
129
|
-
console.log(`Focused on ${this.tagName} element`);
|
|
130
127
|
}
|
|
131
128
|
|
|
132
129
|
blur(): void {
|
|
@@ -195,4 +192,193 @@ export abstract class HTMLElementBase extends ElementBase implements HTMLElement
|
|
|
195
192
|
break;
|
|
196
193
|
}
|
|
197
194
|
}
|
|
195
|
+
|
|
196
|
+
// New implementations for style, dataset, nonce
|
|
197
|
+
private _style: CSSStyleDeclaration | null = null;
|
|
198
|
+
private _dataset: DOMStringMap | null = null;
|
|
199
|
+
|
|
200
|
+
get style(): CSSStyleDeclaration {
|
|
201
|
+
if (!this._style) {
|
|
202
|
+
this._style = new Proxy(new CSSStyleDeclarationImpl(this), {
|
|
203
|
+
get: (target, prop: string | symbol) => {
|
|
204
|
+
if (typeof prop === 'string' && !(prop in target)) {
|
|
205
|
+
// Convert camelCase to kebab-case for CSS properties
|
|
206
|
+
const cssProp = prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
|
207
|
+
return target.getPropertyValue(cssProp);
|
|
208
|
+
}
|
|
209
|
+
return (target as any)[prop];
|
|
210
|
+
},
|
|
211
|
+
set: (target, prop: string | symbol, value: any) => {
|
|
212
|
+
if (typeof prop === 'string' && !(prop in target)) {
|
|
213
|
+
// Convert camelCase to kebab-case for CSS properties
|
|
214
|
+
const cssProp = prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
|
215
|
+
target.setProperty(cssProp, String(value));
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
(target as any)[prop] = value;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
}) as unknown as CSSStyleDeclaration;
|
|
222
|
+
}
|
|
223
|
+
return this._style;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
set style(value: string | CSSStyleDeclaration) {
|
|
227
|
+
if (typeof value === 'string') {
|
|
228
|
+
this.style.cssText = value;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private _attributeStyleMap: StylePropertyMap | null = null;
|
|
233
|
+
|
|
234
|
+
get attributeStyleMap(): StylePropertyMap {
|
|
235
|
+
if (!this._attributeStyleMap) {
|
|
236
|
+
this._attributeStyleMap = new StylePropertyMapImpl(this);
|
|
237
|
+
}
|
|
238
|
+
return this._attributeStyleMap;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
get dataset(): DOMStringMap {
|
|
242
|
+
if (!this._dataset) {
|
|
243
|
+
this._dataset = new Proxy({}, {
|
|
244
|
+
get: (target, prop: string | symbol) => {
|
|
245
|
+
if (typeof prop === 'string') {
|
|
246
|
+
// Convert camelCase to kebab-case: fooBar -> data-foo-bar
|
|
247
|
+
const attrName = 'data-' + prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
|
248
|
+
return this.getAttribute(attrName);
|
|
249
|
+
}
|
|
250
|
+
return undefined;
|
|
251
|
+
},
|
|
252
|
+
set: (target, prop: string | symbol, value: any) => {
|
|
253
|
+
if (typeof prop === 'string') {
|
|
254
|
+
// Convert camelCase to kebab-case: fooBar -> data-foo-bar
|
|
255
|
+
const attrName = 'data-' + prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
|
256
|
+
this.setAttribute(attrName, String(value));
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
},
|
|
261
|
+
deleteProperty: (target, prop: string | symbol) => {
|
|
262
|
+
if (typeof prop === 'string') {
|
|
263
|
+
const attrName = 'data-' + prop.replace(/[A-Z]/g, m => `-${m.toLowerCase()}`);
|
|
264
|
+
this.removeAttribute(attrName);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return this._dataset;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get nonce(): string {
|
|
275
|
+
return this.getAttribute('nonce') || '';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
set nonce(value: string) {
|
|
279
|
+
this.setAttribute('nonce', value);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// CSSStyleDeclaration implementation
|
|
284
|
+
class CSSStyleDeclarationImpl {
|
|
285
|
+
[key: string]: any;
|
|
286
|
+
|
|
287
|
+
constructor(private element: HTMLElementBase) { }
|
|
288
|
+
|
|
289
|
+
parentRule: any = null;
|
|
290
|
+
|
|
291
|
+
getPropertyPriority(property: string): string {
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
get cssText(): string {
|
|
296
|
+
return this.element.getAttribute('style') || '';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
set cssText(value: string) {
|
|
300
|
+
this.element.setAttribute('style', value);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
get length(): number {
|
|
304
|
+
return this.parseStyle(this.cssText).size;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getPropertyValue(property: string): string {
|
|
308
|
+
const style = this.parseStyle(this.cssText);
|
|
309
|
+
return style.get(property) || '';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
setProperty(property: string, value: string | null, priority: string = ''): void {
|
|
313
|
+
const style = this.parseStyle(this.cssText);
|
|
314
|
+
if (value === null || value === '') {
|
|
315
|
+
style.delete(property);
|
|
316
|
+
} else {
|
|
317
|
+
style.set(property, value); // TODO: handle priority
|
|
318
|
+
}
|
|
319
|
+
this.cssText = this.serializeStyle(style);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
removeProperty(property: string): string {
|
|
323
|
+
const style = this.parseStyle(this.cssText);
|
|
324
|
+
const value = style.get(property) || '';
|
|
325
|
+
style.delete(property);
|
|
326
|
+
this.cssText = this.serializeStyle(style);
|
|
327
|
+
return value;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
item(index: number): string {
|
|
331
|
+
const style = this.parseStyle(this.cssText);
|
|
332
|
+
return Array.from(style.keys())[index] || '';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private parseStyle(cssText: string): Map<string, string> {
|
|
336
|
+
const style = new Map<string, string>();
|
|
337
|
+
if (!cssText) return style;
|
|
338
|
+
|
|
339
|
+
cssText.split(';').forEach(declaration => {
|
|
340
|
+
const part = declaration.trim();
|
|
341
|
+
if (!part) return;
|
|
342
|
+
|
|
343
|
+
const colonIndex = part.indexOf(':');
|
|
344
|
+
if (colonIndex !== -1) {
|
|
345
|
+
const property = part.substring(0, colonIndex).trim();
|
|
346
|
+
const value = part.substring(colonIndex + 1).trim();
|
|
347
|
+
if (property && value) {
|
|
348
|
+
style.set(property, value);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
return style;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private serializeStyle(style: Map<string, string>): string {
|
|
356
|
+
return Array.from(style.entries())
|
|
357
|
+
.map(([property, value]) => `${property}: ${value}`)
|
|
358
|
+
.join('; ');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// StylePropertyMap implementation
|
|
363
|
+
class StylePropertyMapImpl implements StylePropertyMap {
|
|
364
|
+
[key: string]: any;
|
|
365
|
+
|
|
366
|
+
constructor(private element: HTMLElementBase) { }
|
|
367
|
+
|
|
368
|
+
set(property: string, ...values: (string | any)[]): void {
|
|
369
|
+
const value = values[0];
|
|
370
|
+
this.element.style.setProperty(property, String(value));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
append(property: string, ...values: (string | any)[]): void {
|
|
374
|
+
this.set(property, ...values);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
delete(property: string): void {
|
|
378
|
+
this.element.style.removeProperty(property);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
clear(): void {
|
|
382
|
+
this.element.style.cssText = '';
|
|
383
|
+
}
|
|
198
384
|
}
|