@dooboostore/dom-render 1.0.99 → 1.0.102
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 +261 -47
- package/components/ComponentBase.d.ts +17 -0
- package/components/ComponentBase.js +135 -77
- package/package.json +3 -3
- package/rawsets/RawSet.js +31 -27
- package/css/parse/index.d.ts +0 -49
- package/css/parse/index.js +0 -498
- package/css/stringify/compiler.d.ts +0 -34
- package/css/stringify/compiler.js +0 -36
- package/css/stringify/compress.d.ts +0 -104
- package/css/stringify/compress.js +0 -147
- package/css/stringify/identity.d.ts +0 -109
- package/css/stringify/identity.js +0 -183
- package/css/stringify/index.d.ts +0 -24
- package/css/stringify/index.js +0 -27
- package/css/stringify/types.d.ts +0 -15
- package/css/stringify/types.js +0 -2
- package/css/types.d.ts +0 -10
- package/css/types.js +0 -2
package/README.MD
CHANGED
|
@@ -6,25 +6,25 @@ DOM-RENDER
|
|
|
6
6
|
* all internal variables are managed by proxy. (DomRenderProxy)
|
|
7
7
|
|
|
8
8
|
# 🚀 Quick start
|
|
9
|
-
```
|
|
10
|
-
|
|
9
|
+
```typescript
|
|
10
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
11
|
+
|
|
12
|
+
class AppData {
|
|
13
|
+
name: string = 'my name is dom-render';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const data = new AppData();
|
|
17
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
11
18
|
```
|
|
12
19
|
```html
|
|
13
20
|
<!DOCTYPE html>
|
|
14
21
|
<html lang="en">
|
|
15
|
-
<head
|
|
22
|
+
<head>
|
|
16
23
|
<meta charset="UTF-8">
|
|
17
24
|
<title>Title</title>
|
|
18
25
|
</head>
|
|
19
26
|
<body id="app">
|
|
20
27
|
${this.name}$
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/dom-render@1.0.86/dist/bundle.js"></script>
|
|
22
|
-
<script>
|
|
23
|
-
let data = {
|
|
24
|
-
name: 'my name is dom-render'
|
|
25
|
-
};
|
|
26
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
27
|
-
</script>
|
|
28
28
|
</body>
|
|
29
29
|
</html>
|
|
30
30
|
```
|
|
@@ -42,14 +42,18 @@ ${this.name}$
|
|
|
42
42
|
<body id="app">
|
|
43
43
|
${this.name}$ <!-- outout: <i>my name is dom-render</i> -->
|
|
44
44
|
#{this.name}# <!-- outout text is italic: my name is dom-render -->
|
|
45
|
-
<script>
|
|
46
|
-
let data = {
|
|
47
|
-
name: '<i>my name is dom-render</i>'
|
|
48
|
-
};
|
|
49
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
50
|
-
</script>
|
|
51
45
|
</body>
|
|
52
46
|
```
|
|
47
|
+
```typescript
|
|
48
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
49
|
+
|
|
50
|
+
class AppData {
|
|
51
|
+
name: string = '<i>my name is dom-render</i>';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = new AppData();
|
|
55
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
56
|
+
```
|
|
53
57
|
</details>
|
|
54
58
|
|
|
55
59
|
# dom-render attributes
|
|
@@ -141,14 +145,18 @@ uuid: rawSetUUID
|
|
|
141
145
|
<body id="app">
|
|
142
146
|
<div dr-if="true">true</div> <!-- render -->
|
|
143
147
|
<div dr-if="this.gender === 'M'">gender: M</div> <!-- No Render -->
|
|
144
|
-
<script>
|
|
145
|
-
let data = {
|
|
146
|
-
gender: 'F',
|
|
147
|
-
};
|
|
148
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
149
|
-
</script>
|
|
150
148
|
</body>
|
|
151
149
|
```
|
|
150
|
+
```typescript
|
|
151
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
152
|
+
|
|
153
|
+
class AppData {
|
|
154
|
+
gender: string = 'F';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const data = new AppData();
|
|
158
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
159
|
+
```
|
|
152
160
|
</details>
|
|
153
161
|
|
|
154
162
|
|
|
@@ -326,6 +334,197 @@ class data {
|
|
|
326
334
|
</details>
|
|
327
335
|
|
|
328
336
|
|
|
337
|
+
## Component Property binding with `@attribute` decorator
|
|
338
|
+
<details>
|
|
339
|
+
<summary>@attribute<strong>🔻(click)</strong></summary>
|
|
340
|
+
|
|
341
|
+
You can bind component properties directly to HTML attributes using the `@attribute` decorator. When the attribute on the custom element changes, the corresponding property in the component class instance is automatically updated.
|
|
342
|
+
|
|
343
|
+
This requires your component to extend `ComponentBase`.
|
|
344
|
+
|
|
345
|
+
### Usage
|
|
346
|
+
|
|
347
|
+
The `@attribute` decorator options:
|
|
348
|
+
|
|
349
|
+
* `name: string`: Binds the property to a specifically named attribute. If not provided, the property name is used.
|
|
350
|
+
* `converter: (value: string | null) => any`: A function to convert the string attribute value to another type.
|
|
351
|
+
|
|
352
|
+
### Example
|
|
353
|
+
|
|
354
|
+
**Component (`MyComponent.ts`)**
|
|
355
|
+
```typescript
|
|
356
|
+
import { ComponentBase, attribute } from '@dooboostore/dom-render';
|
|
357
|
+
|
|
358
|
+
// Assuming MyComponent is registered with the tag 'my-component'
|
|
359
|
+
export class MyComponent extends ComponentBase<{ 'title': string, 'show-icon': string, 'created-at': string }> {
|
|
360
|
+
@attribute
|
|
361
|
+
title: string = 'Default Title';
|
|
362
|
+
|
|
363
|
+
@attribute({
|
|
364
|
+
name: 'show-icon',
|
|
365
|
+
converter: (val) => val === 'true'
|
|
366
|
+
})
|
|
367
|
+
showIcon: boolean; // The converter handles string -> boolean
|
|
368
|
+
|
|
369
|
+
@attribute({
|
|
370
|
+
name: 'created-at',
|
|
371
|
+
converter: (val) => val ? new Date(val) : null
|
|
372
|
+
})
|
|
373
|
+
createdAt: Date | null;
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Template (`index.html`)**
|
|
378
|
+
```html
|
|
379
|
+
<my-component
|
|
380
|
+
title="Hello World!"
|
|
381
|
+
show-icon="true"
|
|
382
|
+
created-at="2025-08-30T12:00:00Z">
|
|
383
|
+
</my-component>
|
|
384
|
+
|
|
385
|
+
<script>
|
|
386
|
+
// After some time, change the attribute
|
|
387
|
+
setTimeout(() => {
|
|
388
|
+
const comp = document.querySelector('my-component');
|
|
389
|
+
comp.setAttribute('title', 'New Title!');
|
|
390
|
+
comp.setAttribute('show-icon', 'false');
|
|
391
|
+
comp.setAttribute('created-at', new Date().toISOString());
|
|
392
|
+
}, 2000);
|
|
393
|
+
</script>
|
|
394
|
+
```
|
|
395
|
+
When attributes change, the `converter` function will be executed before the value is assigned to the property. For `showIcon`, the string `"true"` or `"false"` will be converted to a boolean. For `createdAt`, the date string will be converted to a `Date` object.
|
|
396
|
+
|
|
397
|
+
</details>
|
|
398
|
+
|
|
399
|
+
## Element Query with `@query` decorator
|
|
400
|
+
<details>
|
|
401
|
+
<summary>@query<strong>🔻(click)</strong></summary>
|
|
402
|
+
|
|
403
|
+
You can get a reference to DOM elements within your component's template using the `@query` decorator. It takes a CSS selector as an argument.
|
|
404
|
+
|
|
405
|
+
- If the decorated property is **not an array type**, it returns the **first** matching element (like `querySelector`).
|
|
406
|
+
- If the decorated property **is an array type** (e.g., `HTMLElement[]`), it returns **all** matching elements as an array (like `querySelectorAll`).
|
|
407
|
+
- When applied to a **method**, the method will be called with the queried element(s) as arguments after the component has been rendered. If the method expects a single element, the first match is passed. If it expects an array, all matches are passed.
|
|
408
|
+
|
|
409
|
+
This is useful for directly accessing and manipulating elements you need to interact with from your component's logic.
|
|
410
|
+
|
|
411
|
+
**Note:** This feature relies on TypeScript's decorator metadata (`emitDecoratorMetadata: true` in `tsconfig.json`).
|
|
412
|
+
|
|
413
|
+
### Usage Example
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import { ComponentBase, query } from '@dooboostore/dom-render';
|
|
417
|
+
|
|
418
|
+
// Assuming MyComponent is rendered with the template below
|
|
419
|
+
export class MyComponent extends ComponentBase<{}> {
|
|
420
|
+
// Gets the first element matching the selector
|
|
421
|
+
@query('h3.title')
|
|
422
|
+
private titleEl: HTMLHeadingElement;
|
|
423
|
+
|
|
424
|
+
// Gets all elements matching the selector as an array
|
|
425
|
+
@query('ul.item-list > li')
|
|
426
|
+
private items: HTMLLIElement[];
|
|
427
|
+
|
|
428
|
+
onInitRender() {
|
|
429
|
+
// The elements are available after the component has been rendered.
|
|
430
|
+
if (this.titleEl) {
|
|
431
|
+
this.titleEl.style.color = 'blue';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (this.items && this.items.length > 0) {
|
|
435
|
+
this.items.forEach((item, index) => {
|
|
436
|
+
item.textContent = `Item ${index + 1} (updated)`;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Method query: This method will be called with the first element matching '.my-button'
|
|
442
|
+
@query('.my-button')
|
|
443
|
+
private setupButton(button: HTMLButtonElement) {
|
|
444
|
+
if (button) {
|
|
445
|
+
button.addEventListener('click', () => alert('Button clicked from method query!'));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Method query: This method will be called with an array of all elements matching 'span.item'
|
|
450
|
+
@query('span.item')
|
|
451
|
+
private processItems(spans: HTMLSpanElement[]) {
|
|
452
|
+
spans.forEach(span => {
|
|
453
|
+
span.style.fontWeight = 'bold';
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Template
|
|
460
|
+
```html
|
|
461
|
+
<!-- Template for MyComponent -->
|
|
462
|
+
<div>
|
|
463
|
+
<h3 class="title">Original Title</h3>
|
|
464
|
+
<ul class="item-list">
|
|
465
|
+
<li>Item 1</li>
|
|
466
|
+
<li>Item 2</li>
|
|
467
|
+
<li>Item 3</li>
|
|
468
|
+
</ul>
|
|
469
|
+
<button class="my-button">Click Me</button>
|
|
470
|
+
<p><span class="item">First Span</span> <span class="item">Second Span</span></p>
|
|
471
|
+
</div>
|
|
472
|
+
```
|
|
473
|
+
The `@query` decorator will assign the corresponding element(s) to the decorated property or pass them to the decorated method. The property will be `null`/`undefined` (for single query) or an empty array (for multiple query) if no elements are found or before the component is rendered. For methods, they will only be called if matching elements are found.
|
|
474
|
+
|
|
475
|
+
</details>
|
|
476
|
+
|
|
477
|
+
</details>
|
|
478
|
+
|
|
479
|
+
## DOM Event Binding with `@event` decorator
|
|
480
|
+
<details>
|
|
481
|
+
<summary>@event<strong>🔻(click)</strong></summary>
|
|
482
|
+
|
|
483
|
+
You can bind component methods directly to DOM events on elements within your component's template using the `@event` decorator.
|
|
484
|
+
|
|
485
|
+
The decorator takes an options object with two properties:
|
|
486
|
+
- `query: string`: A CSS selector to find the target element(s).
|
|
487
|
+
- `name: string`: The name of the DOM event to listen for (e.g., `'click'`, `'input'`).
|
|
488
|
+
|
|
489
|
+
The framework will automatically add the event listener when the component is initialized and remove it when the component is destroyed, preventing memory leaks.
|
|
490
|
+
|
|
491
|
+
### Usage Example
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
import { ComponentBase, event } from '@dooboostore/dom-render';
|
|
495
|
+
|
|
496
|
+
// Assuming MyComponent is rendered with the template below
|
|
497
|
+
export class MyComponent extends ComponentBase<{}> {
|
|
498
|
+
|
|
499
|
+
// This method will be called when any element matching 'button.action' is clicked.
|
|
500
|
+
@event({ query: 'button.action', name: 'click' })
|
|
501
|
+
onActionClick(e: MouseEvent) {
|
|
502
|
+
console.log('Action button clicked!', e.target);
|
|
503
|
+
alert('Action!');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// This method will be called for the 'input' event on all text inputs.
|
|
507
|
+
@event({ query: 'input[type="text"]', name: 'input' })
|
|
508
|
+
onTextInput(e: Event) {
|
|
509
|
+
const target = e.target as HTMLInputElement;
|
|
510
|
+
console.log(`Text changed in ${target.id}: ${target.value}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Template
|
|
516
|
+
```html
|
|
517
|
+
<!-- Template for MyComponent -->
|
|
518
|
+
<div>
|
|
519
|
+
<input type="text" id="firstname" placeholder="First Name">
|
|
520
|
+
<input type="text" id="lastname" placeholder="Last Name">
|
|
521
|
+
<button class="action">Submit</button>
|
|
522
|
+
</div>
|
|
523
|
+
```
|
|
524
|
+
In this example, when the "Submit" button is clicked, the `onActionClick` method is executed. When the user types in either of the text inputs, the `onTextInput` method is executed for each keystroke.
|
|
525
|
+
|
|
526
|
+
</details>
|
|
527
|
+
|
|
329
528
|
## nearThis
|
|
330
529
|
```html
|
|
331
530
|
<div dr-this="@this@.dictionary"> (${@nearThis@.name}$) ${$nearThis.name}$ </div>
|
|
@@ -465,16 +664,22 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
|
|
|
465
664
|
age: <input name="age">
|
|
466
665
|
<button type="submit">submit</button>
|
|
467
666
|
</form>
|
|
468
|
-
<script>
|
|
469
|
-
let data = {
|
|
470
|
-
form: {},
|
|
471
|
-
submit() {
|
|
472
|
-
console.log(this.form);
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
476
|
-
</script>
|
|
477
667
|
</body>
|
|
668
|
+
```
|
|
669
|
+
```typescript
|
|
670
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
671
|
+
|
|
672
|
+
class FormData {
|
|
673
|
+
form: any = {};
|
|
674
|
+
|
|
675
|
+
submit() {
|
|
676
|
+
console.log(this.form);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const data = new FormData();
|
|
681
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
682
|
+
```
|
|
478
683
|
<!-- 💥 submit call -->
|
|
479
684
|
<!-- console: {name: 'name data', age: 'age data'} -->
|
|
480
685
|
```
|
|
@@ -490,24 +695,33 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
|
|
|
490
695
|
age: <input name="age">
|
|
491
696
|
<button type="submit">submit</button>
|
|
492
697
|
</form>
|
|
493
|
-
<script>
|
|
494
|
-
const form = new FormValidator();
|
|
495
|
-
form.name = new NotEmptyValidator();
|
|
496
|
-
form.age = new NotEmptyValidator();
|
|
497
|
-
let data = {
|
|
498
|
-
form,
|
|
499
|
-
submit() {
|
|
500
|
-
if (this.form.valid()){
|
|
501
|
-
console.log('valid');
|
|
502
|
-
} else {
|
|
503
|
-
console.log('inValid');
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
508
|
-
</script>
|
|
509
698
|
</body>
|
|
510
699
|
```
|
|
700
|
+
```typescript
|
|
701
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
702
|
+
import { FormValidator, NotEmptyValidator } from '@dooboostore/dom-render/validator';
|
|
703
|
+
|
|
704
|
+
class FormDataWithValidator {
|
|
705
|
+
form: FormValidator = new FormValidator();
|
|
706
|
+
|
|
707
|
+
constructor() {
|
|
708
|
+
this.form.name = new NotEmptyValidator();
|
|
709
|
+
this.form.age = new NotEmptyValidator();
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
submit() {
|
|
713
|
+
if (this.form.valid()){
|
|
714
|
+
console.log('valid');
|
|
715
|
+
} else {
|
|
716
|
+
console.log('inValid');
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const data = new FormDataWithValidator();
|
|
722
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
723
|
+
```
|
|
724
|
+
```
|
|
511
725
|
validator
|
|
512
726
|
- Validator (abstract)
|
|
513
727
|
- ValidatorArray (abstract)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
1
2
|
import { OnChangeAttrRender, OtherData } from '../lifecycle/OnChangeAttrRender';
|
|
2
3
|
import { OnCreateRenderData, OnCreateRenderDataParams } from '../lifecycle/OnCreateRenderData';
|
|
3
4
|
import { RawSet } from '../rawsets/RawSet';
|
|
@@ -8,14 +9,29 @@ import { OnDestroyRender, OnDestroyRenderParams } from '../lifecycle/OnDestroyRe
|
|
|
8
9
|
import { OnDrThisUnBind } from '../lifecycle/dr-this/OnDrThisUnBind';
|
|
9
10
|
import { OnDrThisBind } from '../lifecycle/dr-this/OnDrThisBind';
|
|
10
11
|
import * as Render from '../rawsets/Render';
|
|
12
|
+
export declare const ATTRIBUTE_METADATA_KEY: unique symbol;
|
|
13
|
+
export declare const QUERY_METADATA_KEY: unique symbol;
|
|
14
|
+
export declare const EVENT_METADATA_KEY: unique symbol;
|
|
15
|
+
export declare function attribute(options?: {
|
|
16
|
+
name?: string;
|
|
17
|
+
converter?: (value: string | null) => any;
|
|
18
|
+
}): PropertyDecorator;
|
|
19
|
+
export declare function query(selector: string): (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => void;
|
|
20
|
+
export declare function event(options: {
|
|
21
|
+
query: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}): MethodDecorator;
|
|
11
24
|
export declare abstract class ComponentBase<T> implements OnChangeAttrRender, OnCreateRenderData, OnCreatedThisChild, OnDrThisBind, OnDrThisUnBind, OnInitRender, OnDestroyRender {
|
|
12
25
|
private config?;
|
|
13
26
|
private _rawSet?;
|
|
14
27
|
private _children;
|
|
15
28
|
private _render;
|
|
16
29
|
private _attribute?;
|
|
30
|
+
private _boundEventListeners;
|
|
17
31
|
get render(): Render.Render | undefined;
|
|
18
32
|
get rawSet(): RawSet | undefined;
|
|
33
|
+
private _queryElements;
|
|
34
|
+
private _cleanupEventListeners;
|
|
19
35
|
getAttribute<K extends keyof T>(name: K, attribute?: T): T[K] | null;
|
|
20
36
|
hasAttribute(name: keyof T): boolean;
|
|
21
37
|
equalsAttributeName(n: string, name: keyof T): boolean;
|
|
@@ -29,6 +45,7 @@ export declare abstract class ComponentBase<T> implements OnChangeAttrRender, On
|
|
|
29
45
|
constructor(config?: {
|
|
30
46
|
onlyParentType: ConstructorType<any>[] | ConstructorType<any>;
|
|
31
47
|
});
|
|
48
|
+
private setRawSet;
|
|
32
49
|
setAttribute(attribute: T): void;
|
|
33
50
|
onCreatedThisChild(child: any, data: OnCreateRenderDataParams): void;
|
|
34
51
|
onCreateRenderData(data: OnCreateRenderDataParams): void;
|