@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 +122 -6
- package/dist/main.d.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +3 -3
- package/package.json +13 -13
- package/src/main.ts +1 -1
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 {
|
|
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: {
|
|
455
|
+
on: {FETCH: {target: 'loading'}},
|
|
401
456
|
},
|
|
402
457
|
loading: {
|
|
403
458
|
on: {
|
|
404
|
-
SUCCESS: {
|
|
459
|
+
SUCCESS: {target: 'success', assigners: [({context}) => ({...context, retries: 0})]},
|
|
405
460
|
ERROR: [
|
|
406
461
|
// Guard evaluates condition; first matching transition branch is chosen
|
|
407
|
-
{
|
|
408
|
-
|
|
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: {
|
|
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
|
package/dist/main.d.ts.map
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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=
|
|
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';\
|
|
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,
|
|
9
|
-
"debugId": "
|
|
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.
|
|
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.
|
|
25
|
-
"@alwatr/
|
|
26
|
-
"@alwatr/
|
|
27
|
-
"@alwatr/
|
|
28
|
-
"@alwatr/
|
|
29
|
-
"@alwatr/
|
|
30
|
-
"@alwatr/
|
|
31
|
-
"@alwatr/
|
|
32
|
-
"@alwatr/
|
|
33
|
-
"@alwatr/
|
|
34
|
-
"@alwatr/
|
|
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": "
|
|
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';
|