@reactive-web-components/rwc 2.51.8 → 2.51.10

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.
Files changed (38) hide show
  1. package/README.md +1302 -0
  2. package/package.json +32 -5
  3. package/main.d.ts +0 -0
  4. package/reactive-web-component.C6ivwJzW.js +0 -916
  5. package/reactive-web-component.D9B4LRRS.umd.cjs +0 -2
  6. package/shared/constants/constants.d.ts +0 -2
  7. package/shared/index.d.ts +0 -2
  8. package/shared/types/base-element.d.ts +0 -1
  9. package/shared/types/element.d.ts +0 -191
  10. package/shared/types/index.d.ts +0 -3
  11. package/shared/types/list.type.d.ts +0 -7
  12. package/shared/utils/helpers.d.ts +0 -7
  13. package/shared/utils/html-decorators/html-property.d.ts +0 -6
  14. package/shared/utils/html-decorators/index.d.ts +0 -1
  15. package/shared/utils/html-elements/__tests__/element.test.d.ts +0 -1
  16. package/shared/utils/html-elements/base-element.d.ts +0 -33
  17. package/shared/utils/html-elements/custom-element.d.ts +0 -11
  18. package/shared/utils/html-elements/element-helper.d.ts +0 -21
  19. package/shared/utils/html-elements/element.d.ts +0 -84
  20. package/shared/utils/html-elements/index.d.ts +0 -4
  21. package/shared/utils/html-fabric/custom-fabric.d.ts +0 -3
  22. package/shared/utils/html-fabric/fabric.d.ts +0 -67
  23. package/shared/utils/html-fabric/fn-component.d.ts +0 -5
  24. package/shared/utils/html-fabric/index.d.ts +0 -3
  25. package/shared/utils/index.d.ts +0 -5
  26. package/shared/utils/routing/router.d.ts +0 -10
  27. package/shared/utils/signal/helpers.types.d.ts +0 -3
  28. package/shared/utils/signal/index.d.ts +0 -3
  29. package/shared/utils/signal/signal.d.ts +0 -18
  30. package/shared/utils/signal/signal.type.d.ts +0 -18
  31. package/shared/utils/signal/utils.d.ts +0 -10
  32. package/test-components/examples/button/button.d.ts +0 -12
  33. package/test-components/examples/counter.d.ts +0 -15
  34. package/test-components/examples/test-list/dynamic-items-test.d.ts +0 -17
  35. package/test-components/examples/test-list/example.list.d.ts +0 -26
  36. package/test-components/examples/useCustomComponent.d.ts +0 -21
  37. package/test-components/examples/when.d.ts +0 -6
  38. package/test-components/tab-bar.d.ts +0 -23
package/README.md ADDED
@@ -0,0 +1,1302 @@
1
+ # Reactive Web Components (RWC) Library Documentation
2
+
3
+ ---
4
+
5
+ ## Table of Contents
6
+ 1. [Introduction](#introduction)
7
+ 2. [Core Concepts](#core-concepts)
8
+ - [Signals](#signals)
9
+ - [Signal Methods: set, update, forceSet](#signals)
10
+ - [Effects](#effects)
11
+ - [Reactive Strings (rs)](#reactive-strings-rs)
12
+ - [createSignal](#createsignal)
13
+ - [Signal Utilities](#signal-utilities)
14
+ - [Function as Child Content](#function-as-child-content-dynamic-lists-and-conditional-render)
15
+ 3. [Components](#components)
16
+ - [Creating a Component](#creating-a-component)
17
+ - [Lifecycle](#lifecycle)
18
+ - [Events](#events)
19
+ - [Context (providers/injects)](#context-providersinjects)
20
+ - [Class Components and Decorators](#class-components-and-decorators)
21
+ - [Functional Components](#functional-components)
22
+ 4. [Elements and Templates](#elements-and-templates)
23
+ - [HTML Element Factory](#html-element-factory)
24
+ - [Element Configuration: ComponentInitConfig](#element-configuration-componentinitconfig)
25
+ - [Custom Components: useCustomComponent](#custom-components-usecustomcomponent)
26
+ - [Slot Templates](#slot-templates)
27
+ - [Function as Child Content (Dynamic Lists and Conditional Render)](#function-as-child-content-dynamic-lists-and-conditional-render)
28
+ - [Efficient List Rendering with getList](#efficient-list-rendering-with-getlist)
29
+ - [Conditional Rendering with when](#conditional-rendering-with-when)
30
+ - [Conditional Display with show](#conditional-display-with-show)
31
+ 5. [Examples](#examples)
32
+ 6. [Recommendations and Best Practices](#recommendations-and-best-practices)
33
+ 7. [Conclusion](#conclusion)
34
+
35
+ ---
36
+
37
+ ## Introduction
38
+ RWC is a modern library for creating reactive web components with declarative syntax and strict typing. It allows you to build complex UIs with minimal code and maximum reactivity.
39
+
40
+ ## Core Concepts
41
+
42
+ ### Signals
43
+ A signal is a reactive wrapper around a value. All states, properties, contexts, and injections in components are implemented through signals.
44
+
45
+ **Type:**
46
+ ```typescript
47
+ interface ReactiveSignal<T> {
48
+ (): T;
49
+ oldValue: Readonly<T>;
50
+ initValue: Readonly<T>;
51
+ set(value: T): void;
52
+ forceSet(value: T): void;
53
+ setCompareFn(compare: (oldValue: T, newValue: T) => boolean): ReactiveSignal<T>;
54
+ update(cb: (v: Readonly<T>) => T): void;
55
+ clearSubscribers(): void;
56
+ peek(): Readonly<T>;
57
+ pipe<R>(fn: (value: T) => R): ReactiveSignal<UnwrapSignal<R>>;
58
+ }
59
+ ```
60
+
61
+ **Examples:**
62
+ ```typescript
63
+ const count = signal(0);
64
+ count(); // get value
65
+ count.set(1); // set value
66
+ count.update(v => v + 1); // update via function
67
+ count.forceSet(1);
68
+
69
+ // Reactive usage
70
+ const doubled = signal(0);
71
+ effect(() => {
72
+ doubled.set(count() * 2);
73
+ });
74
+
75
+ // Additional methods
76
+ count.setCompareFn((oldV, newV) => Math.abs(newV - oldV) >= 1); // custom comparison
77
+ count.peek(); // safe read without subscription
78
+ count.clearSubscribers(); // clear effect subscribers
79
+
80
+ // pipe — create a derived signal
81
+ const doubled2 = count.pipe(v => v * 2);
82
+ effect(() => console.log('x2:', doubled2()));
83
+ ```
84
+
85
+ **When to use forceSet:**
86
+ - If you need to manually trigger subscriber updates, even if the signal value hasn't changed (e.g., for force-updating UI or side effects).
87
+
88
+ **Edge case:**
89
+ ```typescript
90
+ const arr = signal([1,2,3]);
91
+ arr.update(a => [...a, 4]); // reactively adds element
92
+ ```
93
+
94
+ ### Effects
95
+ An effect is a function that automatically subscribes to all signals used inside it. Effects are used for side actions (logging, synchronization, event emission, etc.).
96
+
97
+ **Example:**
98
+ ```typescript
99
+ effect(() => {
100
+ console.log('Count:', count());
101
+ });
102
+ ```
103
+
104
+ **Best practice:**
105
+ - Don't modify signals inside an effect unless required (to avoid infinite loops).
106
+
107
+ ### Reactive Strings (rs)
108
+ Allows creating reactive strings based on signals and other values.
109
+
110
+ **Example:**
111
+ ```typescript
112
+ const name = signal('John');
113
+ const greeting = rs`Hello, ${name}!`;
114
+ console.log(greeting()); // "Hello, John!"
115
+ name.set('Jane');
116
+ console.log(greeting()); // "Hello, Jane!"
117
+ ```
118
+
119
+ **Edge case:**
120
+ ```typescript
121
+ const a = signal('A');
122
+ const b = signal('B');
123
+ const combined = rs`${a}-${b}`;
124
+ a.set('X'); // combined() === 'X-B'
125
+ ```
126
+
127
+ ### createSignal
128
+ Allows creating a signal whose value is computed based on a function or async value. The difference from signal is support for async sources and automatic updates when dependencies change.
129
+
130
+ **Typing:**
131
+ ```typescript
132
+ function createSignal<T extends Promise<any> | (() => any), I = ...>(cb: T, initializeValue?: I): ReactiveSignal<...>
133
+ ```
134
+
135
+ **Main use cases:**
136
+
137
+ 1. **To get a property from a signal:**
138
+ ```typescript
139
+ const user = signal({ name: 'John', age: 30 });
140
+ const userName = createSignal(() => user().name);
141
+ // userName() returns 'John'
142
+ user.set({ name: 'Jane', age: 31 });
143
+ // userName() automatically updates and returns 'Jane'
144
+ ```
145
+
146
+ 2. **To compute a new value based on another signal:**
147
+ ```typescript
148
+ const count = signal(0);
149
+ const doubled = createSignal(() => count() * 2);
150
+ count.set(5); // doubled() automatically updates and returns 10
151
+ ```
152
+
153
+ 3. **For working with async data:**
154
+ ```typescript
155
+ const userId = signal(1);
156
+ const userData = createSignal(
157
+ () => fetch(`/api/users/${userId()}`).then(r => r.json()),
158
+ { name: '', loading: true } // initial value
159
+ );
160
+ ```
161
+
162
+ **Examples from codebase:**
163
+ ```typescript
164
+ // Converting numeric index to human-readable number
165
+ div({ classList: ['tab-header'] }, rs`current tab: ${createSignal(() => this.activeTab() + 1)}`);
166
+ ```
167
+
168
+ **Best practice:**
169
+ - Use createSignal for computed values instead of effect+signal combination
170
+ - For async data, always specify an initial value (fallback)
171
+ - The function passed to createSignal should be pure (no side effects)
172
+
173
+ ### Signal Utilities
174
+
175
+ RWC provides additional utilities for working with signals that simplify complex use cases.
176
+
177
+ #### bindReactiveSignals
178
+
179
+ Creates two-way binding between two reactive signals. Changes in one signal are automatically synchronized with the other.
180
+
181
+ ```typescript
182
+ import { bindReactiveSignals, signal } from '@shared/utils';
183
+
184
+ const signalA = signal('Hello');
185
+ const signalB = signal('World');
186
+
187
+ // Create two-way binding
188
+ bindReactiveSignals(signalA, signalB);
189
+
190
+ signalA.set('Hello'); // signalB automatically becomes 'Hello'
191
+ signalB.set('World'); // signalA automatically becomes 'World'
192
+ ```
193
+
194
+ #### forkJoin
195
+
196
+ Combines multiple signals into one that updates only when all source signals receive new values.
197
+
198
+ ```typescript
199
+ import { forkJoin, signal } from '@shared/utils';
200
+
201
+ const name = signal('John');
202
+ const age = signal(25);
203
+ const city = signal('Moscow');
204
+
205
+ const userData = forkJoin(name, age, city);
206
+ // userData() returns ['John', 25, 'Moscow']
207
+
208
+ name.set('Jane'); // userData doesn't update
209
+ age.set(30); // userData doesn't update
210
+ city.set('SPB'); // userData updates to ['Jane', 30, 'SPB']
211
+ ```
212
+
213
+ **Application:**
214
+ - Synchronizing related data
215
+ - Creating composite objects from multiple sources
216
+ - Waiting for all dependencies to update before executing actions
217
+
218
+ ### Function as Child Content (recommended style for dynamic lists and conditional render)
219
+
220
+ Functions passed as child content to `el` or `customEl` are automatically converted to reactive content. This allows convenient creation of dynamic content that will update when dependent signals change. The content function receives context (a reference to its component) as the first argument.
221
+
222
+ **Example: dynamic list with context**
223
+ ```typescript
224
+ const items = signal(['Item 1', 'Item 2']);
225
+ div(
226
+ ul(
227
+ (self) => {
228
+ console.log('self!!!', self); // self - component reference
229
+ return items().map(item => li(item));
230
+ }
231
+ )
232
+ )
233
+ // When items changes, the entire list will be re-rendered
234
+ items.set(['Item 1', 'Item 2', 'Item 3']);
235
+ ```
236
+
237
+ **Example: conditional render with context**
238
+ ```typescript
239
+ div(
240
+ (self) => {
241
+ console.log('self!!!', self);
242
+ return when(signal(true), () => button('test-when-signal'));
243
+ }
244
+ )
245
+ ```
246
+
247
+ **Best practice:**
248
+ - For dynamic rendering, use functions as child content instead of signalComponent
249
+ - For simple cases (text, attributes), use rs or other reactive primitives
250
+ - For complex lists with conditional logic, use functions as child content
251
+ - Use context (self) to access component properties and methods inside the content function
252
+
253
+ ## Components
254
+
255
+ ### Creating a Component
256
+
257
+ To declare a component, use classes with decorators. This provides strict typing, support for reactive props, events, providers, injections, and lifecycle hooks.
258
+
259
+ **Inside a component, it's recommended to use element factory functions** (`div`, `button`, `input`, etc.) from the factory (`@shared/utils/html-fabric/fabric`). This ensures strict typing, autocomplete, and consistent code style.
260
+
261
+
262
+ #### Example: Class Component with props and event
263
+
264
+ ```typescript
265
+ @component('test-decorator-component')
266
+ export class TestDecoratorComponent extends BaseElement {
267
+ @property()
268
+ testProp = signal<string>('Hello from Decorator!');
269
+
270
+ @event()
271
+ onCustomEvent = newEventEmitter<string>();
272
+
273
+ render() {
274
+ this.onCustomEvent('test value');
275
+ return div(rs`Title: ${this.testProp()}`);
276
+ }
277
+ }
278
+ export const TestDecoratorComponentComp = useCustomComponent(TestDecoratorComponent);
279
+ ```
280
+
281
+ #### Brief on parameters:
282
+
283
+ - **@property** — a signal field that automatically syncs with an attribute.
284
+ - **@event** — an event field that emits custom events.
285
+ - **render** — a method that returns the component template.
286
+ - **@component** — registers a custom element with the given selector.
287
+
288
+ ---
289
+
290
+ **Best practice:**
291
+ - All props/state/providers/injects — only signals (`ReactiveSignal<T>`)
292
+ - All events — only through `EventEmitter<T>`
293
+ - Use attributes to pass props
294
+
295
+ ### Lifecycle
296
+
297
+ **Available hooks:**
298
+ - `onInit`, `onBeforeRender`, `onAfterRender`, `onConnected`, `onDisconnected`, `onAttributeChanged`
299
+
300
+ **Example:**
301
+ ```typescript
302
+ @component('logger-component')
303
+ export class LoggerComponent extends BaseElement {
304
+ connectedCallback() {
305
+ super.connectedCallback?.();
306
+ console.log('connected');
307
+ }
308
+ disconnectedCallback() {
309
+ super.disconnectedCallback?.();
310
+ console.log('disconnected');
311
+ }
312
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
313
+ super.attributeChangedCallback?.(name, oldValue, newValue);
314
+ console.log(name, oldValue, newValue);
315
+ }
316
+ render() {
317
+ return div(rs`Logger`);
318
+ }
319
+ }
320
+ export const LoggerComponentComp = useCustomComponent(LoggerComponent);
321
+ ```
322
+
323
+ ### Events
324
+
325
+ **Type:**
326
+ ```typescript
327
+ interface EventEmitter<T> {
328
+ (value: T | ReactiveSignal<T>): void; // can pass a signal — event will emit reactively
329
+ oldValue: null;
330
+ }
331
+ ```
332
+
333
+ **Example:**
334
+ ```typescript
335
+ @component('counter')
336
+ export class Counter extends BaseElement {
337
+ @property()
338
+ count = signal(0);
339
+
340
+ @event()
341
+ onCountChange = newEventEmitter<number>();
342
+
343
+ render() {
344
+ return button({
345
+ listeners: {
346
+ click: () => {
347
+ this.count.update(v => v + 1);
348
+ // one-time emit with value
349
+ this.onCountChange(this.count());
350
+ // or reactive emit: on subsequent count changes, event will emit automatically
351
+ // this.onCountChange(this.count);
352
+ }
353
+ }
354
+ }, rs`Count: ${this.count()}`);
355
+ }
356
+ }
357
+ export const CounterComp = useCustomComponent(Counter);
358
+ ```
359
+
360
+ ### Context (providers/injects)
361
+
362
+ **Example:**
363
+ ```typescript
364
+ const ThemeContext = 'theme';
365
+
366
+ @component('theme-provider')
367
+ export class ThemeProvider extends BaseElement {
368
+ providers = { [ThemeContext]: signal('dark') };
369
+ render() {
370
+ return div(slot({ attributes: { name: 'tab-item' } }));
371
+ }
372
+ }
373
+
374
+ @component('theme-consumer')
375
+ export class ThemeConsumer extends BaseElement {
376
+ theme = this.inject<string>(ThemeContext); // Get context signal once outside render
377
+ render() {
378
+ return div(rs`Theme: ${this.theme}`);
379
+ }
380
+ }
381
+
382
+ @component('app-root')
383
+ export class AppRoot extends BaseElement {
384
+ render() {
385
+ return useCustomComponent(ThemeProvider)(
386
+ useCustomComponent(ThemeConsumer)
387
+ );
388
+ }
389
+ }
390
+ ```
391
+
392
+ ### Class Components and Decorators
393
+
394
+ RWC supports declarative component declaration using classes and TypeScript decorators. This allows using a familiar OOP approach, strict typing, and autocomplete.
395
+
396
+ #### Main Decorators
397
+
398
+ - `@component('component-name')` — registers a custom element with the given selector.
399
+ - `@property()` — marks a class field as a reactive property (based on signal). Automatically syncs with the eponymous attribute (kebab-case).
400
+ - `@event()` — marks a class field as an event (EventEmitter). Allows convenient event emission outward.
401
+
402
+ #### Class Component Example
403
+
404
+ ```typescript
405
+ @component('test-decorator-component')
406
+ export class TestDecoratorComponent extends BaseElement {
407
+ @property()
408
+ testProp = signal<number>(1);
409
+
410
+ @event()
411
+ testEvent = newEventEmitter<number>();
412
+
413
+ private count = 0;
414
+
415
+ render() {
416
+ return div({ listeners: { click: () => this.testEvent(++this.count) } }, rs`test ${this.testProp()}`);
417
+ }
418
+ }
419
+ export const TestDecoratorComponentComp = useCustomComponent(TestDecoratorComponent);
420
+ ```
421
+
422
+ #### How It Works
423
+
424
+ - All fields with `@property()` must be signals (`signal<T>()`). Changing the signal value automatically updates DOM and attributes.
425
+ - All fields with `@event()` must be created via `newEventEmitter<T>()`. Calling such a field emits a custom DOM event.
426
+ - The `render()` method returns the component template.
427
+ - The class must extend `BaseElement`.
428
+
429
+ #### Features
430
+
431
+ - Class and functional components can be used together.
432
+ - All reactivity and typing benefits are preserved.
433
+ - Decorators are implemented in `@shared/utils/html-decorators/html-property.ts` and exported through `@shared/utils/html-decorators`.
434
+
435
+ ### Functional Components
436
+
437
+ RWC supports creating functional components using `createComponent`. This is an alternative approach to class components that may be more convenient for simple cases.
438
+
439
+ #### createComponent
440
+
441
+ Creates a functional component that accepts props and returns an element configuration.
442
+
443
+ ```typescript
444
+ import { createComponent } from '@shared/utils/html-fabric/fn-component';
445
+ import { div, button } from '@shared/utils/html-fabric/fabric';
446
+ import { signal } from '@shared/utils';
447
+
448
+ interface ButtonProps {
449
+ text: string;
450
+ onClick: () => void;
451
+ disabled?: boolean;
452
+ }
453
+
454
+ const Button = createComponent<ButtonProps>((props) => {
455
+ return button({
456
+ attributes: { disabled: props.disabled },
457
+ listeners: { click: props.onClick }
458
+ }, props.text);
459
+ });
460
+
461
+ // Usage
462
+ const count = signal(0);
463
+ const MyButton = Button({
464
+ text: 'Increment',
465
+ onClick: () => count.set(count() + 1),
466
+ disabled: false
467
+ });
468
+ ```
469
+
470
+ **Advantages of functional components:**
471
+ - Simpler syntax for simple cases
472
+ - Automatic support for `classList` and `reactiveClassList` via props
473
+ - Better performance for stateless components
474
+ - Convenience for creating reusable UI elements
475
+
476
+ **When to use:**
477
+ - Simple components without complex logic
478
+ - UI elements that only accept props
479
+ - Reusable components (buttons, inputs, cards)
480
+
481
+ ## Elements and Templates
482
+
483
+ ### HTML Element Factory
484
+
485
+ To create HTML elements, use factory functions (`div`, `button`, `input`, etc.) from `@shared/utils/html-fabric/fabric`. This ensures strict typing, autocomplete, and consistent style.
486
+
487
+ ```typescript
488
+ import { div, button, ul, li, input, slot } from '@shared/utils/html-fabric/fabric';
489
+
490
+ // Examples:
491
+ div('Hello, world!')
492
+ div({ classList: ['container'] },
493
+ button({ listeners: { click: onClick } }, "Click me"),
494
+ ul(
495
+ li('Item 1'),
496
+ li('Item 2')
497
+ )
498
+ )
499
+ ```
500
+ - First argument — config object or content directly.
501
+ - All standard HTML tags are available through corresponding factories.
502
+
503
+ #### Element Configuration: ComponentInitConfig
504
+
505
+ To set properties, attributes, classes, events, and effects for elements and components, a special config object type is used — `ComponentInitConfig<T>`. It supports both standard and shorthand notation.
506
+
507
+ **Typing:**
508
+ ```typescript
509
+ export type ComponentInitConfig<T extends ExtraHTMLElement> = Partial<{
510
+ classList: ConfigClassList;
511
+ style: ConfigStyle;
512
+ attributes: ConfigAttribute<T>;
513
+ customAttributes: ConfigCustomAttribute;
514
+ reactiveClassList: ConfigReactiveClassList;
515
+ children: ConfigChildren;
516
+ effects: ConfigEffect<T>;
517
+ listeners: ConfigListeners<T>;
518
+ customListeners: ConfigCustomListeners<T>;
519
+ }> & Partial<{
520
+ [key in AttrSignal<T> as `.${key}`]?: AttributeValue<T, key>;
521
+ } & {
522
+ [K in keyof HTMLElementEventMap as `@${string & K}`]?: ComponentEventListener<T, HTMLElementEventMap[K]>;
523
+ } & {
524
+ [K in EventKeys<T> as `@${string & K}`]?: CustomEventListener<CustomEventValue<T[K]>, T>;
525
+ } & {
526
+ [key in `$${string}`]: EffectCallback<T>;
527
+ }>
528
+ ```
529
+
530
+ #### Main Features
531
+
532
+ - **classList** — array of classes (strings or functions/signals)
533
+ - **style** — CSS styles object; supports both regular properties and CSS Custom Properties (`--var`), values can be functions/signals
534
+ - **attributes** — object with HTML attributes
535
+ - **customAttributes** — object with custom attributes
536
+ - **reactiveClassList** — array of reactive classes
537
+ - **children** — child elements/content
538
+ - **effects** — array of effects (functions called when element is created)
539
+ - **listeners** — object with DOM event handlers
540
+ - **customListeners** — object with custom event handlers (e.g., `route-change`)
541
+
542
+ ##### Shorthand Notation
543
+
544
+ - `.attributeName` — quick attribute/property assignment
545
+ - `@eventName` — quick event handler assignment (DOM or custom)
546
+ - `$` — quick effect assignment
547
+
548
+ ---
549
+
550
+ #### Usage Examples
551
+
552
+ **1. Standard config**
553
+ ```typescript
554
+ div({
555
+ classList: ['container', () => isActive() ? 'active' : ''],
556
+ attributes: { id: 'main', tabIndex: 0 },
557
+ listeners: {
558
+ click: (e) => console.log('clicked', e)
559
+ },
560
+ effects: [
561
+ (el) => console.log('created', el)
562
+ ]
563
+ }, 'Content')
564
+ ```
565
+
566
+ **2. Shorthand notation**
567
+ ```typescript
568
+ div({
569
+ '.id': 'main',
570
+ '.tabIndex': 0,
571
+ '.class': 'container',
572
+ '@click': (e) => console.log('clicked', e),
573
+ '$': (el) => console.log('created', el)
574
+ }, 'Content')
575
+ ```
576
+
577
+ **2.1. Styles (static / reactive / custom properties)**
578
+ ```typescript
579
+ const primaryColor = signal('#0d6efd');
580
+ div({
581
+ style: {
582
+ color: 'white',
583
+ backgroundColor: () => primaryColor(),
584
+ '--gap': '8px', // CSS Custom Property
585
+ marginTop: () => '12px' // reactive value
586
+ }
587
+ }, 'Styles via config.style')
588
+ ```
589
+
590
+ **3. Usage with components**
591
+ ```typescript
592
+ MyComponentComp({
593
+ '.count': countSignal, // reactive prop
594
+ '@onCountChange': (value) => console.log('count changed', value)
595
+ })
596
+ ```
597
+
598
+ **3.1. Custom events via customListeners**
599
+ ```typescript
600
+ div({
601
+ customListeners: {
602
+ 'route-change': (e, self) => {
603
+ console.log('Route change:', e.detail);
604
+ }
605
+ }
606
+ })
607
+ ```
608
+
609
+ **4. Reactive classes via classList**
610
+ ```typescript
611
+ div(
612
+ classList`static-class ${() => isActive() ? 'active' : ''}`,
613
+ 'Content'
614
+ )
615
+ ```
616
+
617
+ **5. Reactive classes via reactiveClassList**
618
+ ```typescript
619
+ const isRed = signal(false);
620
+ const isBold = signal(true);
621
+ div({
622
+ reactiveClassList: {
623
+ 'red': isRed,
624
+ 'bold': isBold
625
+ }
626
+ }, 'Text with reactive classes');
627
+ ```
628
+
629
+ **6. Child elements**
630
+ ```typescript
631
+ div(
632
+ { classList: ['container'] },
633
+ span('Text'),
634
+ button('Click me')
635
+ )
636
+ ```
637
+
638
+ ---
639
+
640
+ **Best practice:**
641
+ Use shorthand notation for brevity, and standard notation for complex cases or IDE autocomplete.
642
+
643
+ ### Custom Components: useCustomComponent
644
+
645
+ To create and use custom components, use the `useCustomComponent` function from `@shared/utils/html-fabric/custom-fabric`.
646
+
647
+ **Recommended style 1:** Using the `@component` decorator
648
+ 1. Declare the component class with the `@component` decorator.
649
+ 2. Call `useCustomComponent` below the class, assign the result to a constant, and export it (the class itself doesn't need to be exported).
650
+
651
+ ```typescript
652
+ import { component, event, property } from '@shared/utils/html-decorators';
653
+ import { BaseElement } from '@shared/utils/html-elements/element';
654
+ import { useCustomComponent } from '@shared/utils/html-fabric/custom-fabric';
655
+ import { div } from '@shared/utils/html-fabric/fabric';
656
+
657
+ @component('my-component')
658
+ class MyComponent extends BaseElement {
659
+ render() {
660
+ return div('Hello from custom component!');
661
+ }
662
+ }
663
+ export const MyComponentComp = useCustomComponent(MyComponent);
664
+ ```
665
+
666
+ **Recommended style 2:** Passing selector directly to `useCustomComponent`
667
+ 1. Declare the component class **without** the `@component` decorator.
668
+ 2. Call `useCustomComponent` with the component class and selector as the second argument.
669
+
670
+ ```typescript
671
+ import { event, property } from '@shared/utils/html-decorators';
672
+ import { BaseElement } from '@shared/utils/html-elements/element';
673
+ import { useCustomComponent } from '@shared/utils/html-fabric/custom-fabric';
674
+ import { div } from '@shared/utils/html-fabric/fabric';
675
+
676
+ class MyComponent extends BaseElement {
677
+ render() {
678
+ return div('Hello from custom component!');
679
+ }
680
+ }
681
+ export const MyComponentComp = useCustomComponent(MyComponent, 'my-component');
682
+ ```
683
+
684
+ In the second approach, the `@component` decorator is called inside `useCustomComponent` when a selector is passed. This simplifies the component code.
685
+
686
+ **Usage in other components:**
687
+ ```typescript
688
+ div(
689
+ MyComponentComp({ attributes: { someProp: 'value' } },
690
+ 'Nested content'
691
+ )
692
+ )
693
+ ```
694
+
695
+ ### Slot Templates
696
+
697
+ `slotTemplate` is a powerful mechanism for passing custom templates into a component. It's an analog of "render props" or "scoped slots" from other frameworks. It allows a child component to receive templates from a parent component and render them with slot-specific context passed.
698
+
699
+ This is useful when a component should manage logic but delegate rendering of part of its content to external code.
700
+
701
+ #### How It Works
702
+
703
+ 1. **In the component (template provider):**
704
+ - Define a `slotTemplate` property using `defineSlotTemplate<T>()`.
705
+ - `T` is a type describing available templates. Keys are template names, values are functions that will render the template. Arguments of these functions are the context passed from the component.
706
+ - In the `render` method, the component calls these templates, passing context to them.
707
+
708
+ 2. **When using the component (template consumer):**
709
+ - Call the `.setSlotTemplate()` method on the component instance.
710
+ - Pass an object with template implementations to `.setSlotTemplate()`.
711
+
712
+ #### Example
713
+
714
+ Let's say we have a list component that renders items, but we want to allow users of this component to customize how each item looks.
715
+
716
+ **1. Creating the component (`example-list.ts`)**
717
+
718
+ ```typescript
719
+ // src/components/example-list.ts
720
+ import { BaseElement, component, defineSlotTemplate, div, getList, property, signal, useCustomComponent } from "@shared/utils";
721
+ import { ComponentConfig } from "@shared/types";
722
+
723
+ @component('example-list')
724
+ export class ExampleListComponent extends BaseElement {
725
+ // Define available templates and their context
726
+ public slotTemplate = defineSlotTemplate<{
727
+ // Template for list item, receives the item itself in context
728
+ item: (slotCtx: { id: number, name: string }) => ComponentConfig<any> | null,
729
+ // Template for index, receives the number in context
730
+ indexTemplate: (slotCtx: number) => ComponentConfig<any>
731
+ }>()
732
+
733
+ @property()
734
+ items = signal<{ id: number, name: string }[]>([])
735
+
736
+ render() {
737
+ // Use getList for efficient rendering
738
+ return div(getList(
739
+ this.items,
740
+ (item) => item.id,
741
+ (item, index) => div(
742
+ // Render 'item' template if provided, otherwise - standard view
743
+ this.slotTemplate.item?.(item) || div(item.name),
744
+ // Render 'indexTemplate' template if provided
745
+ this.slotTemplate.indexTemplate?.(index) || div()
746
+ )
747
+ ));
748
+ }
749
+ }
750
+ export const ExampleList = useCustomComponent(ExampleListComponent);
751
+ ```
752
+
753
+ **2. Using the component**
754
+
755
+ ```typescript
756
+ // src/components/app.ts
757
+ import { ExampleList } from './example-list';
758
+
759
+ const allItems = [
760
+ { id: 1, name: 'First' },
761
+ { id: 2, name: 'Second' },
762
+ { id: 3, name: 'Third' },
763
+ ];
764
+
765
+ @component('my-app')
766
+ export class App extends BaseElement {
767
+ render() {
768
+ return div(
769
+ ExampleList({ '.items': allItems })
770
+ // Pass custom templates
771
+ .setSlotTemplate({
772
+ // Custom render for item
773
+ item: (itemCtx) => div(`Item: ${itemCtx.name} (id: ${itemCtx.id})`),
774
+ // Custom render for even indices
775
+ indexTemplate: indexCtx => indexCtx % 2 === 0
776
+ ? div(`Even index: ${indexCtx}`)
777
+ : null,
778
+ })
779
+ );
780
+ }
781
+ }
782
+ ```
783
+
784
+ #### Key Points:
785
+
786
+ - `defineSlotTemplate` creates a typed object for templates.
787
+ - The `.setSlotTemplate()` method allows passing template implementations to the component.
788
+ - Context (`slotCtx`) is passed from the component to the template function, providing flexibility.
789
+ - You can define fallback rendering if a template wasn't provided, using `||`.
790
+
791
+ ### Function as Child Content (Dynamic Lists and Conditional Render)
792
+
793
+ Functions passed as child content to factories (`div`, `ul`, etc.) are automatically converted to reactive content.
794
+
795
+ **Example: dynamic list**
796
+ ```typescript
797
+ const items = signal(['Item 1', 'Item 2']);
798
+ div(
799
+ ul(
800
+ () => items().map(item => li(item))
801
+ )
802
+ )
803
+ // When items changes, the entire list will be re-rendered
804
+ items.set(['Item 1', 'Item 2', 'Item 3']);
805
+ ```
806
+
807
+ ### Efficient List Rendering with getList
808
+
809
+ For performance optimization when working with lists, it's recommended to use the `getList` function. It allows efficiently updating only changed list elements instead of re-rendering the entire list.
810
+
811
+ **Signature:**
812
+ ```typescript
813
+ getList<I extends Record<string, any>, K extends keyof I>(
814
+ items: ReactiveSignal<I[]>,
815
+ keyFn: (item: I) => I[K] | string,
816
+ cb: (item: I, index: number, items: I[]) => ComponentConfig<any>
817
+ ): ComponentConfig<HTMLDivElement>
818
+ ```
819
+
820
+ **Parameters:**
821
+ - `items` - reactive signal with array of elements
822
+ - `keyFn` - function returning a unique key for each element (supports `string` or element field `I[K]`)
823
+ - `cb` - element rendering function, accepting the element, its index, and the entire current `items` array
824
+
825
+ **Usage example:**
826
+ ```typescript
827
+ @component('example-list')
828
+ class ExampleList extends BaseElement {
829
+ items = signal<{ id: number, name: string }[]>([
830
+ { id: 1, name: 'Item 1' },
831
+ { id: 2, name: 'Item 2' },
832
+ { id: 3, name: 'Item 3' },
833
+ ])
834
+
835
+ render() {
836
+ return div(
837
+ // Regular list rendering (re-renders entire list)
838
+ div(() => this.items().map(item => div(item.name))),
839
+
840
+ // Efficient rendering with getList (updates only changed elements)
841
+ div(getList(
842
+ this.items,
843
+ (item) => item.id, // key — item id
844
+ (item, index, items) => div(`${index + 1}. ${item.name}`) // index and entire array available
845
+ ))
846
+ )
847
+ }
848
+ }
849
+ ```
850
+
851
+ **Advantages of using getList:**
852
+ 1. Optimized performance — only changed elements are updated
853
+ 2. Preserving list element state
854
+ 3. Efficient work with large lists
855
+ 4. Automatic updates when data changes
856
+
857
+ **Implementation details:**
858
+ - Uses `data-key` to bind DOM nodes to data elements (key comes from `keyFn`).
859
+ - Each key has its own signal stored; changing the signal value forces update of the corresponding DOM node.
860
+ - Element changes are determined by comparison: `JSON.stringify(currItem) !== JSON.stringify(oldItems[index])`.
861
+ - Nodes whose keys are missing from the new list are removed from DOM, and their cache (signals/components/effects) is cleared.
862
+ - DOM node order is synchronized with key order in the current data array.
863
+ - Render effects are created once per key and cached in `currRegisteredEffects`.
864
+ - Effect initialization is deferred via `Promise.resolve().then(...)` for correct DOM insertion at the right position.
865
+ - Keys are normalized to string for consistent matching.
866
+
867
+ **Best practices:**
868
+ - Keys should be unique and stable between re-renders.
869
+ - Avoid deep/large objects if performance-sensitive: comparison via `JSON.stringify` can be expensive.
870
+ - Ensure immutable element updates so changes are detected correctly.
871
+ - If a specific order is needed, form it at the data level before rendering (e.g., sort the array before passing to `getList`).
872
+
873
+ **Best practice:**
874
+ - Always use unique keys for list elements
875
+ - Use `getList` for dynamic lists, especially with frequent updates
876
+ - For simple static lists, you can use a regular map
877
+
878
+ ### Complex Component Example
879
+
880
+ ```typescript
881
+ import { component, event, property } from '@shared/utils/html-decorators';
882
+ import { BaseElement } from '@shared/utils/html-elements/element';
883
+ import { useCustomComponent } from '@shared/utils/html-fabric/custom-fabric';
884
+ import { div, input } from '@shared/utils/html-fabric/fabric';
885
+ import { signal } from '@shared/utils/html-elements/signal';
886
+
887
+ @component('tab-bar-test-item')
888
+ class TabBarTestItem extends BaseElement {
889
+ render() {
890
+ return div(
891
+ 'tab-bar-test-item 3',
892
+ input()
893
+ );
894
+ }
895
+ }
896
+ export const TabBarTestItemComp = useCustomComponent(TabBarTestItem);
897
+
898
+ @component('tab-bar-test')
899
+ class TabBarTest extends BaseElement {
900
+ activeTabNumber = signal(0);
901
+ items = signal<string[]>(['test1', 'test2', 'test3']);
902
+ render() {
903
+ const isHidden = signal(false);
904
+ return div(
905
+ this.items,
906
+ () => this.items().map(e => div(e)),
907
+ div(
908
+ { classList: [() => isHidden() ? 'test1' : 'test2'] },
909
+ '!!!test classList!!!'
910
+ ),
911
+ TabBarTestItemComp(
912
+ {},
913
+ div('test1'),
914
+ div('test2'),
915
+ div(TabBarTestItemComp()),
916
+ div(
917
+ div(
918
+ div()
919
+ )
920
+ ),
921
+ div(TabBarTestItemComp()),
922
+ TabBarTestItemComp()
923
+ )
924
+ );
925
+ }
926
+ }
927
+ export const TabBarTestComp = useCustomComponent(TabBarTest);
928
+ ```
929
+
930
+ ### Conditional Rendering with when
931
+
932
+ For conditional rendering, use the `when` function from the factory. It supports both static and reactive conditions.
933
+
934
+ ```typescript
935
+ import { when } from '@shared/utils/html-fabric/fabric';
936
+ import { div, span } from '@shared/utils/html-fabric/fabric';
937
+ import { signal } from '@shared/utils/html-elements/signal';
938
+
939
+ // Static condition
940
+ const isVisible = true;
941
+ div(
942
+ when(isVisible,
943
+ () => span('Shown'),
944
+ () => span('Hidden')
945
+ )
946
+ )
947
+
948
+ // Reactive condition
949
+ const isVisibleSignal = signal(true);
950
+ div(
951
+ when(isVisibleSignal,
952
+ () => span('Shown'),
953
+ () => span('Hidden')
954
+ )
955
+ )
956
+
957
+ // Conditional rendering with function
958
+ const items = signal(['Item 1', 'Item 2']);
959
+ div(
960
+ when(
961
+ () => items().length > 0,
962
+ () => ul(
963
+ ...items().map(item => li(item))
964
+ ),
965
+ () => div('No items')
966
+ )
967
+ )
968
+ ```
969
+
970
+ - `when` automatically determines condition type (boolean, signal, or function)
971
+ - Supports optional elseContent
972
+ - Use for any conditional rendering instead of manual if/ternary or deprecated rxRenderIf/renderIf
973
+ - Accepts functions of type `CompFuncContent` as rendering arguments (functions returning `ComponentContent` or array `ComponentContent[]`)
974
+
975
+ ### Conditional Display with show
976
+
977
+ To control element visibility without removing them from DOM, use the `show` function. Unlike `when`, which completely adds/removes elements, `show` controls display via CSS `display` property.
978
+
979
+ ```typescript
980
+ // Static condition
981
+ const isVisible = true;
982
+ div(
983
+ show(isVisible, () => span('Content'))
984
+ )
985
+
986
+ // Reactive condition
987
+ const isVisibleSignal = signal(true);
988
+ div(
989
+ show(isVisibleSignal, () => span('Reactive content'))
990
+ )
991
+
992
+ // Condition via function
993
+ const itemCount = signal(5);
994
+ div(
995
+ show(() => itemCount() > 0, () => span('Items exist'))
996
+ )
997
+ ```
998
+
999
+ **Differences between `when` and `show`:**
1000
+
1001
+ - **`when`** — completely removes/adds elements from DOM. More efficient for heavy components that are rarely shown.
1002
+ - **`show`** — hides/shows elements via `display: none/contents`. More efficient for frequent visibility toggling, preserves element state.
1003
+
1004
+ **When to use `show`:**
1005
+ - For frequent visibility toggling (e.g., dropdown menus, modals)
1006
+ - When you need to preserve element state when hidden
1007
+ - For simple show/hide cases without alternative content
1008
+
1009
+ ## Recommendations and Best Practices
1010
+
1011
+ ### Architectural Principles
1012
+
1013
+ 1. **Separation of Concerns**: Use class components for complex logic, functional — for simple UI elements
1014
+ 2. **Reactivity**: All states should be signals for automatic UI updates
1015
+ 3. **Typing**: Use strict typing for all props, events, and contexts
1016
+ 4. **Performance**: Apply `getList` for large lists, `show` for frequent visibility toggles
1017
+
1018
+ ### Usage Patterns
1019
+
1020
+ #### Component Composition
1021
+ ```typescript
1022
+ // Good: composition of simple components
1023
+ const UserCard = createComponent<UserProps>((props) =>
1024
+ div({ classList: ['user-card'] },
1025
+ UserAvatar({ src: props.avatar }),
1026
+ UserInfo({ name: props.name, email: props.email })
1027
+ )
1028
+ );
1029
+
1030
+ // Bad: one large component with all logic
1031
+ const ComplexUserCard = createComponent<AllProps>((props) => {
1032
+ // 200+ lines of code
1033
+ });
1034
+ ```
1035
+
1036
+ #### State Management
1037
+ ```typescript
1038
+ // Good: local state in component
1039
+ class UserProfile extends BaseElement {
1040
+ @property()
1041
+ isEditing = signal(false);
1042
+
1043
+ render() {
1044
+ return when(this.isEditing,
1045
+ () => UserEditForm(),
1046
+ () => UserDisplay()
1047
+ );
1048
+ }
1049
+ }
1050
+
1051
+ // Good: global state via context
1052
+ const ThemeContext = 'theme';
1053
+ class ThemeProvider extends BaseElement {
1054
+ providers = { [ThemeContext]: signal('dark') };
1055
+ }
1056
+ ```
1057
+
1058
+ ## Examples
1059
+
1060
+ #### Inserting Unsafe HTML (unsafeHtml)
1061
+ ```typescript
1062
+ // Render string as HTML. Use only for trusted content!
1063
+ const html = signal('<b>bold</b> and <i>italic</i>');
1064
+ div(
1065
+ unsafeHtml(html)
1066
+ )
1067
+
1068
+ // static string
1069
+ div(unsafeHtml('<span style="color:red">red</span>'))
1070
+ ```
1071
+
1072
+ ### Basic Component with props and event
1073
+ ```typescript
1074
+ import { component, event, property } from '@shared/utils/html-decorators';
1075
+ import { BaseElement } from '@shared/utils/html-elements/element';
1076
+ import { rs, signal } from '@shared/utils/html-elements/signal';
1077
+ import { newEventEmitter } from '@shared/utils';
1078
+
1079
+ @component('test-decorator-component')
1080
+ export class TestDecoratorComponent extends BaseElement {
1081
+ @property()
1082
+ testProp = signal<string>('Hello from Decorator!');
1083
+
1084
+ @event()
1085
+ onCustomEvent = newEventEmitter<string>();
1086
+
1087
+ render() {
1088
+ this.onCustomEvent('test value');
1089
+ return div(rs`Title: ${this.testProp()}`);
1090
+ }
1091
+ }
1092
+ export const TestDecoratorComponentComp = useCustomComponent(TestDecoratorComponent);
1093
+ ```
1094
+
1095
+ #### Dynamic List via Function as Child Content
1096
+ ```typescript
1097
+ const items = signal(['Item 1', 'Item 2']);
1098
+ div(
1099
+ () => ul(
1100
+ ...items().map(item => li(item))
1101
+ )
1102
+ )
1103
+ // When items changes, the entire list will be re-rendered
1104
+ items.set(['Item 1', 'Item 2', 'Item 3']);
1105
+ ```
1106
+
1107
+ #### Reactive Array Display
1108
+ ```typescript
1109
+ const items = signal(['A', 'B', 'C']);
1110
+ div(() => items().join(','));
1111
+ ```
1112
+
1113
+ #### Example: Tab Header
1114
+ ```typescript
1115
+ div({ classList: ['tab-header'] }, rs`current tab: ${createSignal(() => this.activeTab() + 1)}`)
1116
+ ```
1117
+
1118
+ #### Example: Component with props
1119
+ ```typescript
1120
+ class TestDecoratorComponent extends BaseElement {
1121
+ @property()
1122
+ testProp = signal<string>('Hello from Decorator!');
1123
+ @event()
1124
+ onCustomEvent = newEventEmitter<string>();
1125
+ render() {
1126
+ this.onCustomEvent('test value');
1127
+ return div(rs`Title: ${this.testProp()}`);
1128
+ }
1129
+ }
1130
+ export const TestDecoratorComponentComp = useCustomComponent(TestDecoratorComponent);
1131
+ ```
1132
+
1133
+ #### Example: Component with Logging
1134
+ ```typescript
1135
+ class LoggerComponent extends BaseElement {
1136
+ connectedCallback() {
1137
+ super.connectedCallback?.();
1138
+ console.log('connected');
1139
+ }
1140
+ disconnectedCallback() {
1141
+ super.disconnectedCallback?.();
1142
+ console.log('disconnected');
1143
+ }
1144
+ attributeChangedCallback(name: string, oldValue: string, newValue: string) {
1145
+ super.attributeChangedCallback?.(name, oldValue, newValue);
1146
+ console.log(name, oldValue, newValue);
1147
+ }
1148
+ render() {
1149
+ return div(rs`Logger`);
1150
+ }
1151
+ }
1152
+ export const LoggerComponentComp = useCustomComponent(LoggerComponent);
1153
+ ```
1154
+
1155
+ #### Example: Button with Signal
1156
+ ```typescript
1157
+ class Counter extends BaseElement {
1158
+ @property()
1159
+ count = signal(0);
1160
+ @event()
1161
+ onCountChange = newEventEmitter<number>();
1162
+ render() {
1163
+ return button({
1164
+ listeners: {
1165
+ click: () => {
1166
+ this.count.update(v => v + 1);
1167
+ this.onCountChange(this.count());
1168
+ }
1169
+ }
1170
+ }, rs`Count: ${this.count()}`);
1171
+ }
1172
+ }
1173
+ export const CounterComp = useCustomComponent(Counter);
1174
+ ```
1175
+
1176
+ #### Example: Slot
1177
+ ```typescript
1178
+ div(slot({ attributes: { name: 'tab-item' } }))
1179
+ ```
1180
+
1181
+ #### Example: Using Context
1182
+ ```typescript
1183
+ class ThemeConsumer extends BaseElement {
1184
+ theme = this.inject<string>(ThemeContext); // Get context signal once outside render
1185
+ render() {
1186
+ return div(rs`Theme: ${this.theme}`);
1187
+ }
1188
+ }
1189
+ export const ThemeConsumerComp = useCustomComponent(ThemeConsumer);
1190
+ ```
1191
+
1192
+ #### Example: Nested Components
1193
+ ```typescript
1194
+ div(
1195
+ ThemeProviderComp(
1196
+ ThemeConsumerComp()
1197
+ )
1198
+ )
1199
+ ```
1200
+
1201
+ #### Example: Functional Component
1202
+ ```typescript
1203
+ import { createComponent } from '@shared/utils/html-fabric/fn-component';
1204
+ import { button } from '@shared/utils/html-fabric/fabric';
1205
+
1206
+ interface CounterProps {
1207
+ initialValue?: number;
1208
+ step?: number;
1209
+ }
1210
+
1211
+ const Counter = createComponent<CounterProps>((props) => {
1212
+ const count = signal(props.initialValue || 0);
1213
+
1214
+ return button({
1215
+ listeners: {
1216
+ click: () => count.set(count() + (props.step || 1))
1217
+ }
1218
+ }, () => `Counter: ${count()}`);
1219
+ });
1220
+
1221
+ // Usage
1222
+ const MyCounter = Counter({
1223
+ initialValue: 10,
1224
+ step: 5
1225
+ });
1226
+ ```
1227
+
1228
+ #### Example: Working with Signal Utilities
1229
+ ```typescript
1230
+ import { bindReactiveSignals, forkJoin, signal } from '@shared/utils';
1231
+
1232
+ // Two-way binding
1233
+ const inputValue = signal('');
1234
+ const displayValue = signal('');
1235
+ bindReactiveSignals(inputValue, displayValue);
1236
+
1237
+ // Combining signals
1238
+ const name = signal('John');
1239
+ const age = signal(25);
1240
+ const userInfo = forkJoin(name, age);
1241
+ // userInfo() returns ['John', 25] only when both signals update
1242
+ ```
1243
+
1244
+ #### Example: Event Handling
1245
+ ```typescript
1246
+ class TestDecoratorComponent extends BaseElement {
1247
+ @property()
1248
+ testProp = signal<number>(1);
1249
+ @event()
1250
+ testEvent = newEventEmitter<number>();
1251
+ private count = 0;
1252
+ render() {
1253
+ return div({ listeners: { click: () => this.testEvent(++this.count) } }, rs`test ${this.testProp()}`);
1254
+ }
1255
+ }
1256
+ export const TestDecoratorComponentComp = useCustomComponent(TestDecoratorComponent);
1257
+ ```
1258
+
1259
+ ### Additional Utilities
1260
+
1261
+ #### Using the `classList` Function
1262
+
1263
+ For convenient assignment of dynamic and static classes in element config, you can use the `classList` function. It allows combining string values and functions (e.g., signals) that return a class string. This is especially useful for reactive class management.
1264
+
1265
+ **Signature:**
1266
+ ```typescript
1267
+ classList(strings: TemplateStringsArray, ...args: (() => string)[]): { classList: (string | (() => string))[] }
1268
+ ```
1269
+
1270
+ **Example of static and dynamic classes:**
1271
+ ```typescript
1272
+ const isActive = signal(false);
1273
+ div(
1274
+ classList`my-static-class ${() => isActive() ? 'active' : ''}`,
1275
+ 'Content'
1276
+ )
1277
+ // When isActive changes, the 'active' class will be added or removed automatically
1278
+ ```
1279
+
1280
+ **Additionally:**
1281
+ - As a function inside `classList`, you can pass a **signal** that returns a class string:
1282
+
1283
+ ```typescript
1284
+ const dynamicClass = signal('my-dynamic-class');
1285
+ div(
1286
+ classList`static-class ${dynamicClass}`,
1287
+ 'Content'
1288
+ )
1289
+ // When dynamicClass changes, the class will automatically update
1290
+ ```
1291
+
1292
+ - You can also pass a **function that returns a signal**:
1293
+
1294
+ ```typescript
1295
+ const getClassSignal = () => someSignal;
1296
+ div(
1297
+ classList`test-class ${getClassSignal}`,
1298
+ 'Content'
1299
+ )
1300
+ // The class will reactively change when the signal value returned by the function changes
1301
+ ```
1302
+