@alwatr/flux 9.28.0 → 9.29.0

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 CHANGED
@@ -18,6 +18,7 @@ Born from years of building production PWAs and inspired by the best ideas from
18
18
  - **Fine-grained reactivity** via Signals (no Virtual DOM overhead)
19
19
  - **Global event delegation** for O(1) boot time (inspired by Qwik's Resumability)
20
20
  - **Declarative DOM directives** for clean, maintainable UI code
21
+ - **Declarative reactive DOM data binding** via Bind (surgical view model updates)
21
22
  - **Type-safe action bus** with zero runtime overhead
22
23
  - **Persistent state management** with automatic localStorage/sessionStorage sync
23
24
 
@@ -377,6 +378,58 @@ createEffect({
377
378
  });
378
379
  ```
379
380
 
381
+ ### 🔗 **Declarative DOM Data Binding (Bind)**
382
+
383
+ Flux aggregates `@alwatr/bind` to provide a declarative, low-overhead way to bind elements directly to reactive view models. This enables surgical updates to text, values, and attributes without virtual DOM diffing or full-component re-renders.
384
+
385
+ ```typescript
386
+ import {service_binding, createStateSignal} from '@alwatr/flux';
387
+
388
+ // 1. Create domain state signal
389
+ const userSignal = createStateSignal({
390
+ name: 'user',
391
+ initialValue: {firstName: 'Ali', lastName: 'Mihandoost', cart: []},
392
+ });
393
+
394
+ // 2. Project domain state to flat view model representation
395
+ service_binding.createViewModel('user', userSignal, (u) => ({
396
+ firstName: u.firstName,
397
+ fullName: `${u.firstName} ${u.lastName}`,
398
+ cartIsEmpty: u.cart.length === 0,
399
+ }));
400
+ ```
401
+
402
+ In your HTML, bind properties using declarative attributes:
403
+
404
+ ```html
405
+ <!-- Text Content Binding -->
406
+ <h2 bind-text="user.fullName">Loading...</h2>
407
+
408
+ <!-- Value Binding with active cursor position preservation guard -->
409
+ <input
410
+ type="text"
411
+ bind-value="user.firstName"
412
+ on-input="ui_edit_name:$value"
413
+ />
414
+
415
+ <!-- Attribute Binding (boolean presence toggling / nullish removal) -->
416
+ <button bind-attrib="disabled=user.cartIsEmpty">Checkout</button>
417
+
418
+ <!-- Lazy Binding (evaluated only when element enters viewport) -->
419
+ <div
420
+ bind-text="user.fullName"
421
+ lazy-bind
422
+ ></div>
423
+ ```
424
+
425
+ #### 🌟 Key Advantages of Bind
426
+
427
+ - **Cursor Preservation**: Writing to input values directly in other frameworks often resets the cursor caret to the end of the text. `@alwatr/bind` compares values and only writes to the DOM if the value changed, preserving typing cursor position perfectly.
428
+ - **Presence-Aware Attributes**: Binds attributes intelligently. A `boolean` value toggles attribute presence, a `nullish` value removes the attribute, and any other value sets the attribute as a string.
429
+ - **Lazy Initialization**: Placing `lazy-bind` on elements defers signal subscription and DOM updates until the element physically intersects the viewport, optimizing rendering performance on large pages.
430
+
431
+ ---
432
+
380
433
  ### 🤖 **Finite State Machine (FSM) & Actor Model**
381
434
 
382
435
  Flux natively aggregates `@alwatr/fsm` to eliminate ad-hoc state variables, boolean flags, and race conditions. Instead of writing unpredictable, disjointed spaghetti code, you model your application's lifecycle as a **declarative, type-safe statechart**.
@@ -388,7 +441,9 @@ import type {StateMachineConfig} from '@alwatr/flux';
388
441
  // 1. Define strict Union Types for compile-time safety
389
442
  type FetchState = 'idle' | 'loading' | 'success' | 'failed';
390
443
  type FetchEvent = {type: 'FETCH'} | {type: 'SUCCESS'} | {type: 'ERROR'};
391
- interface FetchContext { retries: number }
444
+ interface FetchContext {
445
+ retries: number;
446
+ }
392
447
 
393
448
  // 2. Configure the machine declaratively
394
449
  const fetchConfig: StateMachineConfig<FetchState, FetchEvent, FetchContext> = {
@@ -397,21 +452,25 @@ const fetchConfig: StateMachineConfig<FetchState, FetchEvent, FetchContext> = {
397
452
  context: {retries: 0},
398
453
  states: {
399
454
  idle: {
400
- on: { FETCH: { target: 'loading' } },
455
+ on: {FETCH: {target: 'loading'}},
401
456
  },
402
457
  loading: {
403
458
  on: {
404
- SUCCESS: { target: 'success', assigners: [({context}) => ({...context, retries: 0})] },
459
+ SUCCESS: {target: 'success', assigners: [({context}) => ({...context, retries: 0})]},
405
460
  ERROR: [
406
461
  // Guard evaluates condition; first matching transition branch is chosen
407
- { target: 'loading', guard: ({context}) => context.retries < 3, assigners: [({context}) => ({...context, retries: context.retries + 1})] },
408
- { target: 'failed' }
462
+ {
463
+ target: 'loading',
464
+ guard: ({context}) => context.retries < 3,
465
+ assigners: [({context}) => ({...context, retries: context.retries + 1})],
466
+ },
467
+ {target: 'failed'},
409
468
  ],
410
469
  },
411
470
  },
412
471
  success: {},
413
472
  failed: {
414
- on: { FETCH: { target: 'loading', assigners: [({context}) => ({...context, retries: 0})] } },
473
+ on: {FETCH: {target: 'loading', assigners: [({context}) => ({...context, retries: 0})]}},
415
474
  },
416
475
  },
417
476
  };
@@ -442,6 +501,7 @@ fetchService.dispatch({type: 'FETCH'});
442
501
  Flux implements a **strict layered architecture** where each layer has a single responsibility. It supports both standard Unidirectional Data Flow (UDF) and the decentralized **Actor Model** (where components/features behave as isolated micro-services powered by FSMs).
443
502
 
444
503
  ### 1. Unidirectional Data Flow (UDF) Layering
504
+
445
505
  ```
446
506
  ┌───────────────────────────────────────────────────────────┐
447
507
  │ VIEW LAYER │
@@ -1058,6 +1118,62 @@ class FormDirective extends Directive {
1058
1118
 
1059
1119
  ---
1060
1120
 
1121
+ ### Bind (Declarative Data Binding)
1122
+
1123
+ #### `setupBindDirectives()`
1124
+
1125
+ Registers and bootstraps all `@alwatr/bind` attribute directives on application boot.
1126
+
1127
+ ```typescript
1128
+ import {setupBindDirectives} from '@alwatr/flux';
1129
+
1130
+ setupBindDirectives();
1131
+ ```
1132
+
1133
+ #### `service_binding`
1134
+
1135
+ The registry singleton managing presentation view models and mapping subscriptions.
1136
+
1137
+ ##### `service_binding.createViewModel(namespace, sourceSignal, project)`
1138
+
1139
+ Registers a ViewModel namespaces mapping a domain signal state `S` to a flat presentation record `T`.
1140
+
1141
+ ```typescript
1142
+ import {service_binding} from '@alwatr/flux';
1143
+
1144
+ service_binding.createViewModel('user', userSignal, (u) => ({
1145
+ fullName: `${u.firstName} ${u.lastName}`,
1146
+ cartIsEmpty: u.cart.length === 0,
1147
+ }));
1148
+ ```
1149
+
1150
+ ##### `service_binding.getViewModel(namespace)`
1151
+
1152
+ Retrieves a registered ViewModel's computed signal. Returns `null` if the namespace is not registered.
1153
+
1154
+ ##### `service_binding.removeViewModel(namespace)`
1155
+
1156
+ Destroys the ViewModel's computed signal and removes the namespace from the active registry.
1157
+
1158
+ #### Directives HTML Syntax
1159
+
1160
+ - **`bind-text="namespace.prop"`**
1161
+ Updates the element's `textContent` to the property value. Maps nullish values (`null`/`undefined`) to `''`.
1162
+
1163
+ - **`bind-value="namespace.prop"`**
1164
+ Updates input element values safely. Skips DOM writing if the element's value is already equal to the bound value to prevent caret cursor jumping in active text inputs.
1165
+
1166
+ - **`bind-attrib="attr1=namespace.prop1; attr2=namespace.prop2"`**
1167
+ Updates target DOM attributes.
1168
+ - A boolean value toggles the attribute's presence.
1169
+ - A nullish value (`null` or `undefined`) removes the attribute.
1170
+ - All other values are coerced to strings.
1171
+
1172
+ - **`lazy-bind`**
1173
+ When added to any element with the above bindings, defers initialization and signal subscription until the element physically intersects the viewport.
1174
+
1175
+ ---
1176
+
1061
1177
  ### Page Ready
1062
1178
 
1063
1179
  #### `onPageReady(pageId, handler)`
package/dist/main.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from '@alwatr/signal';
2
2
  export * from '@alwatr/action';
3
+ export * from '@alwatr/bind';
3
4
  export * from '@alwatr/directive';
4
5
  export * from '@alwatr/embedded-data';
5
6
  export * from '@alwatr/fsm';
@@ -9,5 +10,4 @@ export * from '@alwatr/local-storage';
9
10
  export * from '@alwatr/session-storage';
10
11
  export * from '@alwatr/page-ready';
11
12
  export * from './lit-html.js';
12
- export type * from '@alwatr/type-helper';
13
13
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAGA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,mBAAmB,qBAAqB,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAGA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC"}
package/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
- /* 📦 @alwatr/flux v9.28.0 */
2
- export*from"@alwatr/signal";export*from"@alwatr/action";export*from"@alwatr/directive";export*from"@alwatr/embedded-data";export*from"@alwatr/fsm";export*from"@alwatr/keyboard-shortcut";export*from"@alwatr/render-state";export*from"@alwatr/local-storage";export*from"@alwatr/session-storage";export*from"@alwatr/page-ready";import{html as e,svg as p,mathml as a,render as f,noChange as t,nothing as x}from"lit-html";import{unsafeSVG as m}from"lit-html/directives/unsafe-svg.js";import{ifDefined as i}from"lit-html/directives/if-defined.js";import{cache as s}from"lit-html/directives/cache.js";import{classMap as b}from"lit-html/directives/class-map.js";import{when as d}from"lit-html/directives/when.js";import{repeat as I}from"lit-html/directives/repeat.js";export{d as when,m as unsafeSVG,p as svg,I as repeat,f as render,x as nothing,t as noChange,a as mathml,i as ifDefined,e as html,b as classMap,s as cache};
1
+ /* 📦 @alwatr/flux v9.29.0 */
2
+ export*from"@alwatr/signal";export*from"@alwatr/action";export*from"@alwatr/bind";export*from"@alwatr/directive";export*from"@alwatr/embedded-data";export*from"@alwatr/fsm";export*from"@alwatr/keyboard-shortcut";export*from"@alwatr/render-state";export*from"@alwatr/local-storage";export*from"@alwatr/session-storage";export*from"@alwatr/page-ready";import{html as e,svg as f,mathml as p,render as x,noChange as a,nothing as n}from"lit-html";import{unsafeSVG as m}from"lit-html/directives/unsafe-svg.js";import{ifDefined as i}from"lit-html/directives/if-defined.js";import{cache as b}from"lit-html/directives/cache.js";import{classMap as d}from"lit-html/directives/class-map.js";import{when as I}from"lit-html/directives/when.js";import{repeat as k}from"lit-html/directives/repeat.js";export{I as when,m as unsafeSVG,f as svg,k as repeat,x as render,n as nothing,a as noChange,p as mathml,i as ifDefined,e as html,d as classMap,b as cache};
3
3
 
4
- //# debugId=F1AB5B823C55946064756E2164756E21
4
+ //# debugId=09E68C2A4C1329DF64756E2164756E21
5
5
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -2,10 +2,10 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/main.ts", "../src/lit-html.ts"],
4
4
  "sourcesContent": [
5
- "// UI and reactive bundle — signals, actions, directives, and client-side storage.\n// This package aggregates all UI-layer nanolibs for convenient single-import usage.\n\nexport * from '@alwatr/signal';\nexport * from '@alwatr/action';\nexport * from '@alwatr/directive';\nexport * from '@alwatr/embedded-data';\nexport * from '@alwatr/fsm';\nexport * from '@alwatr/keyboard-shortcut';\nexport * from '@alwatr/render-state';\nexport * from '@alwatr/local-storage';\nexport * from '@alwatr/session-storage';\nexport * from '@alwatr/page-ready';\nexport * from './lit-html.js';\nexport type * from '@alwatr/type-helper';\n",
5
+ "// UI and reactive bundle — signals, actions, directives, and client-side storage.\n// This package aggregates all UI-layer nanolibs for convenient single-import usage.\n\nexport * from '@alwatr/signal';\nexport * from '@alwatr/action';\nexport * from '@alwatr/bind';\nexport * from '@alwatr/directive';\nexport * from '@alwatr/embedded-data';\nexport * from '@alwatr/fsm';\nexport * from '@alwatr/keyboard-shortcut';\nexport * from '@alwatr/render-state';\nexport * from '@alwatr/local-storage';\nexport * from '@alwatr/session-storage';\nexport * from '@alwatr/page-ready';\nexport * from './lit-html.js';\n",
6
6
  "/**\n * Curated re-exports from `lit-html` for use within `@alwatr/flux`.\n *\n * Only the subset of `lit-html` APIs that are commonly needed in a Flux-based\n * application is exported here. This keeps the public surface minimal and\n * avoids pulling in advanced directive utilities that most consumers never use.\n *\n * **Exported APIs:**\n * - `html` — tagged template literal that produces a `TemplateResult`\n * - `render` — renders a `TemplateResult` into a DOM container\n * - `noChange` — sentinel that tells lit-html to leave the current part value unchanged\n * - `nothing` — sentinel that renders nothing (removes the node/attribute)\n * - `ifDefined` — renders a value only when it is not `undefined`\n * - `cache` — caches rendered templates to avoid re-parsing on state changes\n * - `classMap` — efficiently sets/removes CSS classes from an object map\n * - `when` — conditional rendering helper (`when(condition, trueCase, falseCase)`)\n *\n * @example\n * ```typescript\n * import {html, render, classMap, when} from '@alwatr/flux';\n *\n * const template = (isActive: boolean) => html`\n * <div class=${classMap({active: isActive, hidden: !isActive})}>\n * ${when(isActive, () => html`<span>Active</span>`, () => html`<span>Inactive</span>`)}\n * </div>\n * `;\n *\n * render(template(true), document.getElementById('app')!);\n * ```\n */\nexport {\n html,\n svg,\n mathml,\n render,\n noChange,\n nothing,\n type TemplateResult,\n type HTMLTemplateResult,\n type SVGTemplateResult,\n type MathMLTemplateResult,\n} from 'lit-html';\nexport {unsafeSVG} from 'lit-html/directives/unsafe-svg.js';\nexport {ifDefined} from 'lit-html/directives/if-defined.js';\nexport {cache} from 'lit-html/directives/cache.js';\nexport {classMap, type ClassInfo} from 'lit-html/directives/class-map.js';\nexport {when} from 'lit-html/directives/when.js';\nexport {repeat, type RepeatDirectiveFn, type KeyFn, type ItemTemplate} from 'lit-html/directives/repeat.js';\n"
7
7
  ],
8
- "mappings": ";AAGA,4BACA,4BACA,+BACA,mCACA,yBACA,uCACA,kCACA,mCACA,qCACA,gCCkBA,eACE,SACA,YACA,YACA,cACA,aACA,iBAMF,oBAAQ,0CACR,oBAAQ,0CACR,gBAAQ,qCACR,mBAAQ,yCACR,eAAQ,oCACR,iBAAQ",
9
- "debugId": "F1AB5B823C55946064756E2164756E21",
8
+ "mappings": ";AAGA,4BACA,4BACA,0BACA,+BACA,mCACA,yBACA,uCACA,kCACA,mCACA,qCACA,gCCiBA,eACE,SACA,YACA,YACA,cACA,aACA,iBAMF,oBAAQ,0CACR,oBAAQ,0CACR,gBAAQ,qCACR,mBAAQ,yCACR,eAAQ,oCACR,iBAAQ",
9
+ "debugId": "09E68C2A4C1329DF64756E2164756E21",
10
10
  "names": []
11
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alwatr/flux",
3
- "version": "9.28.0",
3
+ "version": "9.29.0",
4
4
  "description": "UI and reactive library bundle for ECMAScript (JavaScript/TypeScript) projects — signals, actions, directives, and storage.",
5
5
  "license": "MPL-2.0",
6
6
  "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
@@ -21,17 +21,17 @@
21
21
  },
22
22
  "sideEffects": false,
23
23
  "dependencies": {
24
- "@alwatr/action": "9.28.0",
25
- "@alwatr/directive": "9.26.0",
26
- "@alwatr/embedded-data": "9.25.0",
27
- "@alwatr/fsm": "9.28.0",
28
- "@alwatr/keyboard-shortcut": "9.28.0",
29
- "@alwatr/local-storage": "9.25.0",
30
- "@alwatr/page-ready": "9.27.0",
31
- "@alwatr/render-state": "9.25.0",
32
- "@alwatr/session-storage": "9.25.0",
33
- "@alwatr/signal": "9.26.0",
34
- "@alwatr/type-helper": "9.14.0",
24
+ "@alwatr/action": "9.29.0",
25
+ "@alwatr/bind": "9.29.0",
26
+ "@alwatr/directive": "9.29.0",
27
+ "@alwatr/embedded-data": "9.29.0",
28
+ "@alwatr/fsm": "9.29.0",
29
+ "@alwatr/keyboard-shortcut": "9.29.0",
30
+ "@alwatr/local-storage": "9.29.0",
31
+ "@alwatr/page-ready": "9.29.0",
32
+ "@alwatr/render-state": "9.29.0",
33
+ "@alwatr/session-storage": "9.29.0",
34
+ "@alwatr/signal": "9.29.0",
35
35
  "lit-html": "^3.3.3"
36
36
  },
37
37
  "devDependencies": {
@@ -85,5 +85,5 @@
85
85
  "ui",
86
86
  "unidirectional-data-flow"
87
87
  ],
88
- "gitHead": "d60f1d986e5ff44f871d38231f1f2be60833140b"
88
+ "gitHead": "2f80c615d90e038dca634a2dfba8d900d3df6248"
89
89
  }
package/src/main.ts CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  export * from '@alwatr/signal';
5
5
  export * from '@alwatr/action';
6
+ export * from '@alwatr/bind';
6
7
  export * from '@alwatr/directive';
7
8
  export * from '@alwatr/embedded-data';
8
9
  export * from '@alwatr/fsm';
@@ -12,4 +13,3 @@ export * from '@alwatr/local-storage';
12
13
  export * from '@alwatr/session-storage';
13
14
  export * from '@alwatr/page-ready';
14
15
  export * from './lit-html.js';
15
- export type * from '@alwatr/type-helper';