@dooboostore/simple-web-component 1.0.22 → 1.0.24
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 +568 -32
- package/dist/cjs/decorators/applyNode.js +195 -26
- package/dist/cjs/decorators/applyNode.js.map +2 -2
- package/dist/cjs/decorators/attribute.js +21 -3
- package/dist/cjs/decorators/attribute.js.map +2 -2
- package/dist/cjs/decorators/changedAttributeHost.js.map +2 -2
- package/dist/cjs/decorators/classList.js +82 -19
- package/dist/cjs/decorators/classList.js.map +2 -2
- package/dist/cjs/decorators/elementDefine.js +1 -2
- package/dist/cjs/decorators/elementDefine.js.map +2 -2
- package/dist/cjs/decorators/index.js +0 -1
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/lifecycles.js +25 -0
- package/dist/cjs/decorators/lifecycles.js.map +2 -2
- package/dist/cjs/decorators/query.js +77 -2
- package/dist/cjs/decorators/query.js.map +2 -2
- package/dist/cjs/decorators/queryAll.js +77 -2
- package/dist/cjs/decorators/queryAll.js.map +2 -2
- package/dist/cjs/decorators/setAttribute.js +4 -4
- package/dist/cjs/decorators/setAttribute.js.map +2 -2
- package/dist/cjs/decorators/style.js +70 -13
- package/dist/cjs/decorators/style.js.map +2 -2
- package/dist/cjs/index.js +4 -4
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/utils/Utils.js +140 -1
- package/dist/cjs/utils/Utils.js.map +2 -2
- package/dist/esm/decorators/applyNode.js +195 -26
- package/dist/esm/decorators/applyNode.js.map +2 -2
- package/dist/esm/decorators/attribute.js +21 -3
- package/dist/esm/decorators/attribute.js.map +2 -2
- package/dist/esm/decorators/changedAttributeHost.js.map +2 -2
- package/dist/esm/decorators/classList.js +82 -19
- package/dist/esm/decorators/classList.js.map +2 -2
- package/dist/esm/decorators/elementDefine.js +1 -2
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/index.js +0 -1
- package/dist/esm/decorators/index.js.map +2 -2
- package/dist/esm/decorators/lifecycles.js +25 -0
- package/dist/esm/decorators/lifecycles.js.map +2 -2
- package/dist/esm/decorators/query.js +77 -2
- package/dist/esm/decorators/query.js.map +2 -2
- package/dist/esm/decorators/queryAll.js +77 -2
- package/dist/esm/decorators/queryAll.js.map +2 -2
- package/dist/esm/decorators/setAttribute.js +4 -4
- package/dist/esm/decorators/setAttribute.js.map +2 -2
- package/dist/esm/decorators/style.js +70 -13
- package/dist/esm/decorators/style.js.map +2 -2
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/utils/Utils.js +140 -1
- package/dist/esm/utils/Utils.js.map +2 -2
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +678 -168
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/decorators/applyNode.d.ts +107 -2
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/attribute.d.ts +11 -4
- package/dist/types/decorators/attribute.d.ts.map +1 -1
- package/dist/types/decorators/classList.d.ts +22 -10
- package/dist/types/decorators/classList.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/index.d.ts +0 -1
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/lifecycles.d.ts +14 -0
- package/dist/types/decorators/lifecycles.d.ts.map +1 -1
- package/dist/types/decorators/query.d.ts +68 -7
- package/dist/types/decorators/query.d.ts.map +1 -1
- package/dist/types/decorators/queryAll.d.ts +70 -1
- package/dist/types/decorators/queryAll.d.ts.map +1 -1
- package/dist/types/decorators/setAttribute.d.ts.map +1 -1
- package/dist/types/decorators/style.d.ts +37 -3
- package/dist/types/decorators/style.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/Utils.d.ts +49 -0
- package/dist/types/utils/Utils.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +678 -168
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +2 -2
- package/src/decorators/applyNode.ts +319 -37
- package/src/decorators/attribute.ts +44 -11
- package/src/decorators/changedAttributeHost.ts +1 -1
- package/src/decorators/classList.ts +107 -22
- package/src/decorators/elementDefine.ts +1 -2
- package/src/decorators/index.ts +0 -1
- package/src/decorators/lifecycles.ts +40 -0
- package/src/decorators/query.ts +147 -7
- package/src/decorators/queryAll.ts +148 -2
- package/src/decorators/setAttribute.ts +6 -5
- package/src/decorators/style.ts +109 -13
- package/src/index.ts +6 -6
- package/src/utils/Utils.ts +144 -0
- package/dist/cjs/decorators/onConnectedInnerHtml.js +0 -48
- package/dist/cjs/decorators/onConnectedInnerHtml.js.map +0 -7
- package/dist/cjs/utils/TemplateUtils.js +0 -103
- package/dist/cjs/utils/TemplateUtils.js.map +0 -7
- package/dist/esm/decorators/onConnectedInnerHtml.js +0 -29
- package/dist/esm/decorators/onConnectedInnerHtml.js.map +0 -7
- package/dist/esm/utils/TemplateUtils.js +0 -84
- package/dist/esm/utils/TemplateUtils.js.map +0 -7
- package/dist/types/decorators/onConnectedInnerHtml.d.ts +0 -15
- package/dist/types/decorators/onConnectedInnerHtml.d.ts.map +0 -1
- package/dist/types/utils/TemplateUtils.d.ts +0 -54
- package/dist/types/utils/TemplateUtils.d.ts.map +0 -1
- package/src/decorators/onConnectedInnerHtml.ts +0 -42
- package/src/utils/TemplateUtils.ts +0 -83
package/README.md
CHANGED
|
@@ -260,19 +260,26 @@ class StyledCard extends HTMLElement {
|
|
|
260
260
|
|
|
261
261
|
### Attribute & Property Decorators
|
|
262
262
|
|
|
263
|
-
#### 10. `@attributeHost
|
|
264
|
-
Bidirectional attribute binding with automatic type conversion.
|
|
263
|
+
#### 10. `@attributeHost`
|
|
264
|
+
Bidirectional attribute binding with automatic type conversion on `:host` element.
|
|
265
|
+
|
|
266
|
+
All usage patterns support automatic field-name-to-attribute-name conversion:
|
|
267
|
+
- Without parentheses: `@attributeHost` - uses field name as attribute name
|
|
268
|
+
- Empty parentheses: `@attributeHost()` - uses field name as attribute name
|
|
269
|
+
- Options only: `@attributeHost({ connectedInitialize: true })` - uses field name as attribute name
|
|
270
|
+
- Custom name: `@attributeHost('custom-name')` - explicit attribute name
|
|
271
|
+
- Custom name + options: `@attributeHost('custom-name', { type: Boolean })` - both specified
|
|
265
272
|
|
|
266
273
|
```typescript
|
|
267
274
|
@elementDefine('input-field')
|
|
268
275
|
class InputField extends HTMLElement {
|
|
269
|
-
@attributeHost
|
|
276
|
+
@attributeHost
|
|
270
277
|
value: string = '';
|
|
271
278
|
|
|
272
279
|
@attributeHost('disabled')
|
|
273
280
|
disabled: boolean = false;
|
|
274
281
|
|
|
275
|
-
@attributeHost(
|
|
282
|
+
@attributeHost({ type: Number })
|
|
276
283
|
count: number = 0;
|
|
277
284
|
|
|
278
285
|
@onConnectedInnerHtml
|
|
@@ -312,56 +319,119 @@ class ReactiveInput extends HTMLElement {
|
|
|
312
319
|
}
|
|
313
320
|
```
|
|
314
321
|
|
|
315
|
-
#### 12. `@setAttribute(name, options?)`
|
|
316
|
-
Reflect method's return value to a DOM attribute. One-way binding (class → DOM).
|
|
322
|
+
#### 12. `@setAttribute(selector, name, options?)`
|
|
323
|
+
Reflect method's return value to a target element's DOM attribute. One-way binding (class → DOM).
|
|
317
324
|
|
|
318
325
|
```typescript
|
|
319
326
|
@elementDefine('status-badge')
|
|
320
327
|
class StatusBadge extends HTMLElement {
|
|
321
328
|
private status: 'active' | 'inactive' | 'pending' = 'pending';
|
|
329
|
+
private items: any[] = [];
|
|
322
330
|
|
|
323
|
-
// Reflect status to
|
|
324
|
-
@setAttribute('data-status')
|
|
331
|
+
// Reflect status to host element's data-status attribute
|
|
332
|
+
@setAttribute(':host', 'data-status')
|
|
325
333
|
getStatus() {
|
|
326
334
|
return this.status;
|
|
327
335
|
}
|
|
328
336
|
|
|
329
|
-
// Reflect computed data to
|
|
330
|
-
@setAttribute('count')
|
|
337
|
+
// Reflect computed data to specific element
|
|
338
|
+
@setAttribute('#count-display', 'data-count')
|
|
331
339
|
getItemCount() {
|
|
332
|
-
return this.
|
|
340
|
+
return this.items.length.toString();
|
|
333
341
|
}
|
|
334
342
|
|
|
335
|
-
// Reflect to Shadow DOM element
|
|
336
|
-
@setAttribute('aria-label', { root: 'shadow' })
|
|
343
|
+
// Reflect to Shadow DOM element's aria-label
|
|
344
|
+
@setAttribute('.badge-label', 'aria-label', { root: 'shadow' })
|
|
337
345
|
getAriaLabel() {
|
|
338
346
|
return `Status is ${this.status}`;
|
|
339
347
|
}
|
|
340
348
|
|
|
341
|
-
private getItems() {
|
|
342
|
-
return [];
|
|
343
|
-
}
|
|
344
|
-
|
|
345
349
|
@onConnectedInnerHtml
|
|
346
350
|
render() {
|
|
347
351
|
return `
|
|
348
|
-
<div
|
|
349
|
-
Status: ${this.status}
|
|
352
|
+
<div data-status="${this.status}">
|
|
353
|
+
<span class="badge-label">Status: ${this.status}</span>
|
|
354
|
+
<span id="count-display" data-count="${this.items.length}">Items: ${this.items.length}</span>
|
|
350
355
|
</div>
|
|
351
356
|
`;
|
|
352
357
|
}
|
|
353
358
|
|
|
354
|
-
// Change status and update DOM
|
|
359
|
+
// Change status and update DOM attributes
|
|
355
360
|
setStatus(newStatus: typeof this.status) {
|
|
356
361
|
this.status = newStatus;
|
|
357
|
-
this.getStatus();
|
|
362
|
+
this.getStatus(); // Updates :host[data-status]
|
|
363
|
+
this.getItemCount(); // Updates #count-display[data-count]
|
|
364
|
+
this.getAriaLabel(); // Updates .badge-label[aria-label]
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Signature:**
|
|
370
|
+
- `@setAttribute(selector, attributeName, options?)`
|
|
371
|
+
- Returns method's value → reflects to target's attribute
|
|
372
|
+
|
|
373
|
+
**Selectors:**
|
|
374
|
+
- `':host'` - The component itself
|
|
375
|
+
- `'#element-id'` - Element by ID
|
|
376
|
+
- `'.class-name'` - Element by class
|
|
377
|
+
- `selector` - Any CSS selector
|
|
378
|
+
|
|
379
|
+
#### 12b. `@setAttributeHost(name, options?)` - Shorthand for `:host`
|
|
380
|
+
Simplified version of `@setAttribute` that always targets `:host`.
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
@elementDefine('counter')
|
|
384
|
+
class Counter extends HTMLElement {
|
|
385
|
+
private count = 0;
|
|
386
|
+
|
|
387
|
+
// Equivalent to @setAttribute(':host', 'aria-label')
|
|
388
|
+
@setAttributeHost('aria-label')
|
|
389
|
+
getAriaLabel() {
|
|
390
|
+
return `Count is ${this.count}`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@setAttributeHost('data-count')
|
|
394
|
+
getCountValue() {
|
|
395
|
+
return this.count.toString();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
@setAttributeHost('aria-disabled')
|
|
399
|
+
getDisabledState() {
|
|
400
|
+
return (this.count >= 10).toString();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@onConnectedInnerHtml
|
|
404
|
+
render() {
|
|
405
|
+
return `
|
|
406
|
+
<div>
|
|
407
|
+
<p>Count: ${this.count}</p>
|
|
408
|
+
<button swc-on-click="$host.increment()">+</button>
|
|
409
|
+
</div>
|
|
410
|
+
`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
increment() {
|
|
414
|
+
this.count++;
|
|
415
|
+
this.getAriaLabel(); // Updates :host[aria-label]
|
|
416
|
+
this.getCountValue(); // Updates :host[data-count]
|
|
417
|
+
this.getDisabledState(); // Updates :host[aria-disabled]
|
|
358
418
|
}
|
|
359
419
|
}
|
|
360
420
|
```
|
|
361
421
|
|
|
422
|
+
**Usage:**
|
|
423
|
+
```typescript
|
|
424
|
+
// Full form
|
|
425
|
+
@setAttribute(':host', 'data-status')
|
|
426
|
+
// vs
|
|
427
|
+
// Shorthand for :host
|
|
428
|
+
@setAttributeHost('data-status')
|
|
429
|
+
```
|
|
430
|
+
|
|
362
431
|
**Key Differences:**
|
|
363
432
|
- **`@attributeHost`** - Two-way binding: DOM ↔ Class (Bidirectional)
|
|
364
|
-
- **`@setAttribute`** - One-way binding: Class → DOM (
|
|
433
|
+
- **`@setAttribute`** - One-way binding: Class → DOM (Any selector)
|
|
434
|
+
- **`@setAttributeHost`** - One-way binding: Class → DOM (`:host` only)
|
|
365
435
|
- **`@changedAttributeHost`** - Watch DOM attribute changes (Reactive handler)
|
|
366
436
|
|
|
367
437
|
**Options:**
|
|
@@ -425,11 +495,110 @@ class ListRenderer extends HTMLElement {
|
|
|
425
495
|
}
|
|
426
496
|
```
|
|
427
497
|
|
|
498
|
+
#### 14b. Removing Elements via `null` or `undefined`
|
|
499
|
+
Both `@query` and `@queryAll` support element removal by setting the property to `null` or `undefined`. This triggers automatic removal of the matching element(s).
|
|
500
|
+
|
|
501
|
+
```typescript
|
|
502
|
+
@elementDefine('todo-list')
|
|
503
|
+
class TodoList extends HTMLElement {
|
|
504
|
+
@query('.notification') notification?: HTMLElement;
|
|
505
|
+
@queryAll('.todo-item') todoItems?: HTMLElement[];
|
|
506
|
+
|
|
507
|
+
removeNotification() {
|
|
508
|
+
// Setting to null triggers automatic removal
|
|
509
|
+
this.notification = null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
removeTodoItem(id: string) {
|
|
513
|
+
// Find and remove a specific todo item
|
|
514
|
+
const item = this.querySelector(`[data-id="${id}"]`);
|
|
515
|
+
if (item) {
|
|
516
|
+
// Setting @query to null removes the element from DOM
|
|
517
|
+
this.notification = null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
clearAllTodos() {
|
|
522
|
+
// Setting @queryAll to null/undefined removes all matching elements
|
|
523
|
+
this.todoItems = null; // Removes all .todo-item elements
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
@onConnectedInnerHtml
|
|
527
|
+
render() {
|
|
528
|
+
return `
|
|
529
|
+
<div class="notification">New todos available!</div>
|
|
530
|
+
<ul>
|
|
531
|
+
<li class="todo-item" data-id="1">Task 1</li>
|
|
532
|
+
<li class="todo-item" data-id="2">Task 2</li>
|
|
533
|
+
<li class="todo-item" data-id="3">Task 3</li>
|
|
534
|
+
</ul>
|
|
535
|
+
<button swc-on-click="$host.removeNotification()">Clear Notification</button>
|
|
536
|
+
<button swc-on-click="$host.clearAllTodos()">Clear All Tasks</button>
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Removal Rules:**
|
|
543
|
+
- `@query`: Setting to `null` or `undefined` removes the single element
|
|
544
|
+
- `@queryAll`: Setting to `null` or `undefined` removes all matching elements
|
|
545
|
+
- Special selectors (`:host`, `:window`, `:document`, etc.) are **NOT removed** (protected)
|
|
546
|
+
- Only affects Light DOM by default; use `{ root: 'shadow' }` to remove from Shadow DOM
|
|
547
|
+
|
|
548
|
+
**Use Cases:**
|
|
549
|
+
- Close notification banners
|
|
550
|
+
- Clear dynamic list items
|
|
551
|
+
- Remove temporary overlays
|
|
552
|
+
- Clean up modal dialogs
|
|
553
|
+
|
|
554
|
+
#### 15. `@queryHost` - Query :host Element
|
|
555
|
+
Specialized query for the component element itself. Supports automatic or explicit usage.
|
|
556
|
+
|
|
557
|
+
All usage patterns automatically set the element reference:
|
|
558
|
+
- Without parentheses: `@queryHost` self?: HTMLElement;
|
|
559
|
+
- Empty parentheses: `@queryHost()` self?: HTMLElement;
|
|
560
|
+
- With options: `@queryHost({ root: 'shadow' })` self?: HTMLElement;
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
@elementDefine('custom-card')
|
|
564
|
+
class CustomCard extends HTMLElement {
|
|
565
|
+
@queryHost
|
|
566
|
+
self?: HTMLElement;
|
|
567
|
+
|
|
568
|
+
@queryHost()
|
|
569
|
+
selfExplicit?: HTMLElement;
|
|
570
|
+
|
|
571
|
+
printTag() {
|
|
572
|
+
console.log(this.self?.tagName); // Logs: CUSTOM-CARD
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
#### 16. `@queryAllHost` - Query All :host Children
|
|
578
|
+
Returns all children of the component element. Useful for accessing direct children.
|
|
579
|
+
|
|
580
|
+
All usage patterns automatically set the children array:
|
|
581
|
+
- Without parentheses: `@queryAllHost` children?: HTMLElement[];
|
|
582
|
+
- Empty parentheses: `@queryAllHost()` children?: HTMLElement[];
|
|
583
|
+
- With options: `@queryAllHost({ root: 'shadow' })` children?: HTMLElement[];
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
@elementDefine('card-container')
|
|
587
|
+
class CardContainer extends HTMLElement {
|
|
588
|
+
@queryAllHost
|
|
589
|
+
allChildren?: HTMLElement[];
|
|
590
|
+
|
|
591
|
+
getChildCount() {
|
|
592
|
+
return this.allChildren?.length ?? 0;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
428
597
|
---
|
|
429
598
|
|
|
430
599
|
### Event & Messaging Decorators
|
|
431
600
|
|
|
432
|
-
####
|
|
601
|
+
#### 17. `@addEventListener(selector, event, options?)`
|
|
433
602
|
Declarative event binding with automatic delegation.
|
|
434
603
|
|
|
435
604
|
```typescript
|
|
@@ -486,7 +655,7 @@ class TodoApp extends HTMLElement {
|
|
|
486
655
|
- ✅ Parent communication (`:parentHost`, `:appHost`)
|
|
487
656
|
- ✅ Auto-cleanup on `disconnectedCallback`
|
|
488
657
|
|
|
489
|
-
####
|
|
658
|
+
#### 18. `@emitCustomEvent(target, type, options?)`
|
|
490
659
|
Emit custom events to component hierarchy or global targets.
|
|
491
660
|
|
|
492
661
|
```typescript
|
|
@@ -564,38 +733,47 @@ class UserWidget extends HTMLElement {
|
|
|
564
733
|
|
|
565
734
|
### DOM Manipulation Decorators
|
|
566
735
|
|
|
567
|
-
####
|
|
736
|
+
#### 19. `@applyNode(selector, options?)`
|
|
568
737
|
Surgically add, update, or replace DOM nodes at specific positions.
|
|
569
738
|
|
|
570
739
|
```typescript
|
|
740
|
+
import { htmlDivElement, htmlFragment, htmlSpanElement } from '@dooboostore/simple-web-component';
|
|
741
|
+
|
|
571
742
|
@elementDefine('counter-display')
|
|
572
743
|
class CounterDisplay extends HTMLElement {
|
|
573
744
|
private count = 0;
|
|
745
|
+
private errorMessage = '';
|
|
574
746
|
|
|
575
|
-
// Replace all children with new content
|
|
747
|
+
// Replace all children with new content (return Node)
|
|
576
748
|
@applyNode('#display', { position: 'replaceChildren' })
|
|
577
749
|
updateDisplayCount() {
|
|
578
|
-
return
|
|
750
|
+
return htmlSpanElement(`${this.count}`);
|
|
579
751
|
}
|
|
580
752
|
|
|
581
|
-
// Append to container
|
|
753
|
+
// Append to container (return Node)
|
|
582
754
|
@applyNode('#history', { position: 'beforeEnd' })
|
|
583
755
|
addHistoryEntry() {
|
|
584
|
-
return `<p>Count was: ${this.count}</p
|
|
756
|
+
return htmlFragment(`<p>Count was: ${this.count}</p>`);
|
|
585
757
|
}
|
|
586
758
|
|
|
587
|
-
// Set HTML content
|
|
759
|
+
// Set HTML content (return string)
|
|
588
760
|
@applyNode('#status', { position: 'innerHtml' })
|
|
589
761
|
setStatus() {
|
|
590
762
|
return `<strong>Status: Active</strong>`;
|
|
591
763
|
}
|
|
592
764
|
|
|
593
|
-
// Set text content
|
|
765
|
+
// Set text content (return string)
|
|
594
766
|
@applyNode('#counter', { position: 'innerText' })
|
|
595
767
|
updateCounter() {
|
|
596
768
|
return this.count.toString();
|
|
597
769
|
}
|
|
598
770
|
|
|
771
|
+
// Remove element if condition is truthy (return boolean)
|
|
772
|
+
@applyNode('#error-banner', { position: 'remove' })
|
|
773
|
+
dismissError() {
|
|
774
|
+
return this.errorMessage === ''; // Remove if no error
|
|
775
|
+
}
|
|
776
|
+
|
|
599
777
|
@onConnectedInnerHtml
|
|
600
778
|
render() {
|
|
601
779
|
return `
|
|
@@ -604,6 +782,7 @@ class CounterDisplay extends HTMLElement {
|
|
|
604
782
|
<div id="status"></div>
|
|
605
783
|
<button swc-on-click="$host.count++; $host.updateDisplayCount?.()">+</button>
|
|
606
784
|
<div id="history"></div>
|
|
785
|
+
<div id="error-banner" style="display: ${this.errorMessage ? 'block' : 'none'};"></div>
|
|
607
786
|
`;
|
|
608
787
|
}
|
|
609
788
|
}
|
|
@@ -618,12 +797,349 @@ class CounterDisplay extends HTMLElement {
|
|
|
618
797
|
- `'replaceChildren'` - Replace all children
|
|
619
798
|
- `'innerHtml'` - Set `innerHTML`
|
|
620
799
|
- `'innerText'` - Set `innerText`
|
|
800
|
+
- `'remove'` - Remove element if returned value is truthy
|
|
801
|
+
|
|
802
|
+
**Root Option:**
|
|
803
|
+
Control which DOM tree is searched with the `root` option:
|
|
804
|
+
- `'auto'` (default) - Search shadow DOM first, then light DOM
|
|
805
|
+
- `'shadow'` - Search only shadow DOM
|
|
806
|
+
- `'light'` - Search only light DOM
|
|
807
|
+
- `'all'` - Search all ancestors
|
|
808
|
+
|
|
809
|
+
#### Convenience Decorators
|
|
810
|
+
|
|
811
|
+
Shorthand decorators for common position-specific operations:
|
|
812
|
+
|
|
813
|
+
**Position-Specific Variants:**
|
|
814
|
+
|
|
815
|
+
Return **DOM Nodes** for position-based insertion. Use DOM creation functions:
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
import {
|
|
819
|
+
htmlFragment, htmlDivElement, htmlSpanElement,
|
|
820
|
+
createElement, textNode
|
|
821
|
+
} from '@dooboostore/simple-web-component';
|
|
822
|
+
|
|
823
|
+
@elementDefine('my-component')
|
|
824
|
+
class MyComponent extends HTMLElement {
|
|
825
|
+
// Method 1: Using htmlFragment (DocumentFragment)
|
|
826
|
+
@applyBeforeBeginNode('#target') prependTarget() {
|
|
827
|
+
return htmlFragment('<div>Before</div>');
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Method 2: Using htmlDivElement (single element)
|
|
831
|
+
@applyAfterBeginNode('#target') afterBeginTarget() {
|
|
832
|
+
return htmlDivElement('After Begin');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Method 3: Manual createElement (full control)
|
|
836
|
+
@applyBeforeEndNode('#target') beforeEndTarget() {
|
|
837
|
+
return htmlDivElement('Before End');
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
@applyAfterEndNode('#target') afterEndTarget() {
|
|
841
|
+
const el = htmlDivElement('After');
|
|
842
|
+
el.classList.add('highlight');
|
|
843
|
+
return el;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
@applyReplaceNode('#target') replaceTarget() {
|
|
847
|
+
return htmlFragment('<div class="replaced">Replaced</div>');
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Multiple nodes: return array
|
|
851
|
+
@applyReplaceChildrenNode('#target') replaceChildrenTarget() {
|
|
852
|
+
return [
|
|
853
|
+
htmlDivElement('Item 1'),
|
|
854
|
+
htmlDivElement('Item 2'),
|
|
855
|
+
htmlDivElement('Item 3')
|
|
856
|
+
];
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// String return for innerHtml
|
|
860
|
+
@applyInnerHtmlNode('#target') setInnerHtml() {
|
|
861
|
+
return `<b>Bold</b> <i>Italic</i>`;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// String return for innerText
|
|
865
|
+
@applyInnerTextNode('#target') setInnerText() {
|
|
866
|
+
return 'Plain text';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Boolean return for remove
|
|
870
|
+
@applyRemoveNode('#target') removeTarget() {
|
|
871
|
+
return this.shouldHideTarget;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
**SSR Support (with custom Document):**
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
// Browser
|
|
880
|
+
return htmlDivElement('Content'); // Uses global document
|
|
881
|
+
|
|
882
|
+
// SSR (e.g. Node.js with jsdom)
|
|
883
|
+
const ssrDocument = new JSDOM().window.document;
|
|
884
|
+
return htmlDivElement('Content', ssrDocument); // Pass custom doc
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
**Host-Targeted Variants (`:host` selector):**
|
|
888
|
+
|
|
889
|
+
All Host variants support three usage patterns:
|
|
890
|
+
- Without parentheses: `@applyBeforeBeginNodeHost` prependHost() { ... }
|
|
891
|
+
- With empty parentheses: `@applyBeforeBeginNodeHost()` prependHost() { ... }
|
|
892
|
+
- With options: `@applyBeforeBeginNodeHost({ ... })` prependHost() { ... }
|
|
893
|
+
|
|
894
|
+
```typescript
|
|
895
|
+
@elementDefine('my-component')
|
|
896
|
+
class MyComponent extends HTMLElement {
|
|
897
|
+
@applyBeforeBeginNodeHost
|
|
898
|
+
prependHost() {
|
|
899
|
+
return htmlFragment('<div>Before Host</div>');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
@applyAfterBeginNodeHost() afterBeginHost() {
|
|
903
|
+
return htmlSpanElement('After Begin');
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
@applyBeforeEndNodeHost beforeEndHost() {
|
|
907
|
+
const el = htmlDivElement('Before End');
|
|
908
|
+
return el;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
@applyAfterEndNodeHost afterEndHost() {
|
|
912
|
+
return htmlFragment('After Host');
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
@applyReplaceNodeHost replaceHost() {
|
|
916
|
+
return htmlFragment('<div>Replaced Host</div>');
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Multiple children in shadow DOM
|
|
920
|
+
@applyReplaceChildrenNodeHost() replaceChildrenHost() {
|
|
921
|
+
return [
|
|
922
|
+
htmlDivElement('Shadow Child 1'),
|
|
923
|
+
htmlDivElement('Shadow Child 2')
|
|
924
|
+
];
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
@applyInnerHtmlNodeHost setHostInnerHtml() {
|
|
928
|
+
return `<h1>Host Content</h1>`;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
@applyInnerTextNodeHost() setHostInnerText() {
|
|
932
|
+
return 'Host text content';
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
@applyRemoveNodeHost removeHost() {
|
|
936
|
+
return this.isRemoved;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
**Return Types by Position:**
|
|
942
|
+
- `beforeBegin`, `afterBegin`, `beforeEnd`, `afterEnd`: **Node | Node[]**
|
|
943
|
+
- `replace`: **Node**
|
|
944
|
+
- `replaceChildren`: **Node | Node[]**
|
|
945
|
+
- `innerHtml`: **string**
|
|
946
|
+
- `innerText`: **string**
|
|
947
|
+
- `remove`: **boolean** (truthy = remove)
|
|
948
|
+
|
|
949
|
+
**Available DOM Functions:**
|
|
950
|
+
- `htmlFragment(html, doc?)` - Create DocumentFragment from HTML
|
|
951
|
+
- `textNode(text, doc?)` - Create Text node
|
|
952
|
+
- `commentNode(comment, doc?)` - Create Comment node
|
|
953
|
+
- `createElement(tagName, options?, doc?)` - Create any element with attributes
|
|
954
|
+
- `htmlDivElement(html, doc?)` - Create div element
|
|
955
|
+
- `htmlSpanElement(html, doc?)` - Create span element
|
|
956
|
+
- `htmlButtonElement(html, doc?)` - Create button element
|
|
957
|
+
- `htmlAElement(html, href?, doc?)` - Create anchor element
|
|
958
|
+
- `htmlPElement(html, doc?)` - Create paragraph element
|
|
959
|
+
- `htmlLiElement(html, doc?)` - Create list item element
|
|
960
|
+
- `htmlUlElement(doc?)` - Create unordered list
|
|
961
|
+
- `htmlHeadingElement(level, html, doc?)` - Create h1-h6
|
|
962
|
+
- `htmlSectionElement(doc?)` - Create section
|
|
963
|
+
- `htmlArticleElement(doc?)` - Create article
|
|
964
|
+
|
|
965
|
+
**All functions accept optional `doc` parameter for SSR support.**
|
|
966
|
+
|
|
967
|
+
#### 20. `@setStyle(selector, options?)` / `@updateStyle(selector, options?)` / `@removeStyle(selector, options?)`
|
|
968
|
+
|
|
969
|
+
Manipulate CSS styles on elements with full Shadow DOM support.
|
|
970
|
+
|
|
971
|
+
**Core Features:**
|
|
972
|
+
- `@setStyle` - Set inline style (replaces all)
|
|
973
|
+
- `@updateStyle` - Merge with existing styles
|
|
974
|
+
- `@removeStyle` - Remove specific properties
|
|
975
|
+
- `root` option for Shadow DOM / Light DOM targeting
|
|
976
|
+
- Support for multiple elements with `querySelectorAll`
|
|
977
|
+
|
|
978
|
+
```typescript
|
|
979
|
+
@elementDefine('styled-component')
|
|
980
|
+
class StyledComponent extends HTMLElement {
|
|
981
|
+
private isActive = false;
|
|
982
|
+
|
|
983
|
+
@setStyle('.button', { root: 'shadow' })
|
|
984
|
+
setPrimaryButtonStyle() {
|
|
985
|
+
return {
|
|
986
|
+
backgroundColor: '#0066cc',
|
|
987
|
+
color: 'white',
|
|
988
|
+
padding: '10px 20px',
|
|
989
|
+
border: 'none',
|
|
990
|
+
borderRadius: '4px'
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
@updateStyle('.button', { root: 'shadow' })
|
|
995
|
+
highlightButton() {
|
|
996
|
+
return {
|
|
997
|
+
boxShadow: '0 0 10px rgba(0, 102, 204, 0.5)',
|
|
998
|
+
transform: 'scale(1.05)'
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
@removeStyle('.button', { root: 'shadow' })
|
|
1003
|
+
clearHighlight() {
|
|
1004
|
+
return ['boxShadow', 'transform'];
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
@onConnectedInnerHtml({ useShadow: true })
|
|
1008
|
+
render() {
|
|
1009
|
+
return `
|
|
1010
|
+
<style>
|
|
1011
|
+
.button { cursor: pointer; transition: all 0.2s; }
|
|
1012
|
+
</style>
|
|
1013
|
+
<button class="button">Styled</button>
|
|
1014
|
+
`;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
**Host-Targeted Variants:**
|
|
1020
|
+
|
|
1021
|
+
Note: `:host` selector targets the component itself (boundary), so `root` option is ignored.
|
|
1022
|
+
|
|
1023
|
+
All Host variants support three usage patterns:
|
|
1024
|
+
- Without parentheses: `@setStyleHost` setHostStyle() { ... }
|
|
1025
|
+
- With empty parentheses: `@setStyleHost()` setHostStyle() { ... }
|
|
1026
|
+
- With options: `@setStyleHost({ ... })` setHostStyle() { ... }
|
|
1027
|
+
|
|
1028
|
+
```typescript
|
|
1029
|
+
@setStyleHost
|
|
1030
|
+
setHostStyle() {
|
|
1031
|
+
return { display: 'flex', gap: '10px' };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
@updateStyleHost()
|
|
1035
|
+
updateHostStyle() {
|
|
1036
|
+
return { backgroundColor: '#f0f0f0' };
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
@removeStyleHost()
|
|
1040
|
+
clearHostStyle() {
|
|
1041
|
+
return ['backgroundColor'];
|
|
1042
|
+
}
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
---
|
|
1046
|
+
|
|
1047
|
+
#### 21. `@setClassList(selector, options?)` / `@addClassList(selector, options?)` / `@removeClassList(selector, options?)` / `@toggleClassList(selector, options?)` / `@updateClassList(selector, options?)`
|
|
1048
|
+
|
|
1049
|
+
Manipulate CSS classes on elements declaratively with Shadow DOM support.
|
|
1050
|
+
|
|
1051
|
+
**Core Features:**
|
|
1052
|
+
- `@addClassList` - Add classes
|
|
1053
|
+
- `@removeClassList` - Remove classes
|
|
1054
|
+
- `@toggleClassList` - Toggle classes (returns boolean or bool array)
|
|
1055
|
+
- `@setClassList` - Replace all classes
|
|
1056
|
+
- `@updateClassList` - Merge with existing classes
|
|
1057
|
+
- `root` option for DOM layer targeting
|
|
1058
|
+
- Support for multiple elements
|
|
1059
|
+
|
|
1060
|
+
```typescript
|
|
1061
|
+
@elementDefine('tab-component')
|
|
1062
|
+
class TabComponent extends HTMLElement {
|
|
1063
|
+
activeTab = 'home';
|
|
1064
|
+
|
|
1065
|
+
@addClassList('.tab', { root: 'shadow' })
|
|
1066
|
+
addTabClasses() {
|
|
1067
|
+
return ['interactive', 'focusable'];
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
@toggleClassList('.tab', { root: 'shadow' })
|
|
1071
|
+
toggleActiveTab() {
|
|
1072
|
+
return this.activeTab === 'home'; // Returns boolean for all matched elements
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
@removeClassList('.tab-inactive', { root: 'shadow' })
|
|
1076
|
+
removeInactiveClass() {
|
|
1077
|
+
return ['disabled'];
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
@setClassList('.tab-pane', { root: 'shadow' })
|
|
1081
|
+
updateTabPane() {
|
|
1082
|
+
return [`pane-${this.activeTab}`, 'content'];
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
@onConnectedInnerHtml({ useShadow: true })
|
|
1086
|
+
render() {
|
|
1087
|
+
return `
|
|
1088
|
+
<style>
|
|
1089
|
+
.tab { cursor: pointer; padding: 10px; }
|
|
1090
|
+
.tab.active { border-bottom: 2px solid blue; }
|
|
1091
|
+
</style>
|
|
1092
|
+
<div class="tabs">
|
|
1093
|
+
<div class="tab" swc-on-click="$host.activeTab='home'; $host.toggleActiveTab?.();">Home</div>
|
|
1094
|
+
<div class="tab" swc-on-click="$host.activeTab='about'; $host.toggleActiveTab?.();">About</div>
|
|
1095
|
+
</div>
|
|
1096
|
+
<div class="tab-pane">Content here</div>
|
|
1097
|
+
`;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
**Host-Targeted Variants:**
|
|
1103
|
+
|
|
1104
|
+
Note: `:host` selector targets the component itself (boundary), so `root` option is ignored.
|
|
1105
|
+
|
|
1106
|
+
All Host variants support three usage patterns:
|
|
1107
|
+
- Without parentheses: `@setClassListHost` setHostClasses() { ... }
|
|
1108
|
+
- With empty parentheses: `@setClassListHost()` setHostClasses() { ... }
|
|
1109
|
+
- With options: `@setClassListHost({ ... })` setHostClasses() { ... }
|
|
1110
|
+
|
|
1111
|
+
```typescript
|
|
1112
|
+
@addClassListHost
|
|
1113
|
+
addHostClasses() {
|
|
1114
|
+
return ['component', 'interactive'];
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
@removeClassListHost()
|
|
1118
|
+
removeHostClasses() {
|
|
1119
|
+
return ['loading'];
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
@toggleClassListHost()
|
|
1123
|
+
toggleHostClass() {
|
|
1124
|
+
return this.isOpen; // Apply 'open' class if true
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
@setClassListHost
|
|
1128
|
+
setHostClasses() {
|
|
1129
|
+
return this.isOpen ? ['open', 'expanded'] : ['closed'];
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
@updateClassListHost()
|
|
1133
|
+
updateHostClasses() {
|
|
1134
|
+
return ['visible'];
|
|
1135
|
+
}
|
|
1136
|
+
```
|
|
621
1137
|
|
|
622
1138
|
---
|
|
623
1139
|
|
|
624
1140
|
### Dependency Injection & Singleton
|
|
625
1141
|
|
|
626
|
-
####
|
|
1142
|
+
#### 22. `@Sim`
|
|
627
1143
|
Marks a class as a **Singleton** in the `<swc-app>` DI container for **constructor injection**.
|
|
628
1144
|
|
|
629
1145
|
**Two DI Mechanisms:**
|
|
@@ -1270,6 +1786,26 @@ class TextRenderer extends HTMLElement {
|
|
|
1270
1786
|
}
|
|
1271
1787
|
```
|
|
1272
1788
|
|
|
1789
|
+
### 2b. `commentNode` - HTML Comments
|
|
1790
|
+
|
|
1791
|
+
Create HTML comment nodes (useful for debugging, markers, or template documentation).
|
|
1792
|
+
|
|
1793
|
+
```typescript
|
|
1794
|
+
@elementDefine('debug-component')
|
|
1795
|
+
class DebugComponent extends HTMLElement {
|
|
1796
|
+
@appendChild('#debug-area')
|
|
1797
|
+
addDebugComment(message: string) {
|
|
1798
|
+
// Create HTML comment node
|
|
1799
|
+
return commentNode`DEBUG: ${message} at ${new Date().toISOString()}`;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
@onConnectedInnerHtml
|
|
1803
|
+
render() {
|
|
1804
|
+
return `<div id="debug-area"></div>`;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
```
|
|
1808
|
+
|
|
1273
1809
|
### 3. Element Factories (`html***Element`)
|
|
1274
1810
|
|
|
1275
1811
|
Specific factories for common tags. Each returns a **typed element instance**.
|