sygnal 2.9.4 → 3.0.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 +685 -220
- package/dist/index.cjs.js +258 -266
- package/dist/index.esm.js +258 -266
- package/dist/jsx.cjs.js +4 -4
- package/dist/jsx.esm.js +4 -4
- package/dist/sygnal.min.js +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -3144,10 +3144,11 @@ var _default$2 = debounce$1.default = debounce;
|
|
|
3144
3144
|
const ENVIRONMENT = ((typeof window != 'undefined' && window) || (process && process.env)) || {};
|
|
3145
3145
|
|
|
3146
3146
|
|
|
3147
|
-
const
|
|
3148
|
-
const
|
|
3149
|
-
const
|
|
3150
|
-
const
|
|
3147
|
+
const BOOTSTRAP_ACTION = 'BOOTSTRAP';
|
|
3148
|
+
const INITIALIZE_ACTION = 'INITIALIZE';
|
|
3149
|
+
const HYDRATE_ACTION = 'HYDRATE';
|
|
3150
|
+
const PARENT_SINK_NAME = 'PARENT';
|
|
3151
|
+
const CHILD_SOURCE_NAME = 'CHILD';
|
|
3151
3152
|
|
|
3152
3153
|
|
|
3153
3154
|
let COMPONENT_COUNT = 0;
|
|
@@ -3201,12 +3202,10 @@ class Component {
|
|
|
3201
3202
|
// name
|
|
3202
3203
|
// sources
|
|
3203
3204
|
// intent
|
|
3204
|
-
// request
|
|
3205
3205
|
// model
|
|
3206
3206
|
// context
|
|
3207
|
-
// response
|
|
3208
3207
|
// view
|
|
3209
|
-
//
|
|
3208
|
+
// peers
|
|
3210
3209
|
// components
|
|
3211
3210
|
// initialState
|
|
3212
3211
|
// calculated
|
|
@@ -3222,15 +3221,16 @@ class Component {
|
|
|
3222
3221
|
// action$
|
|
3223
3222
|
// model$
|
|
3224
3223
|
// context$
|
|
3225
|
-
//
|
|
3226
|
-
//
|
|
3227
|
-
// children$
|
|
3224
|
+
// peers$
|
|
3225
|
+
// childSources
|
|
3228
3226
|
// vdom$
|
|
3229
3227
|
// currentState
|
|
3228
|
+
// currentProps
|
|
3229
|
+
// currentChildren
|
|
3230
3230
|
// currentContext
|
|
3231
3231
|
// subComponentSink$
|
|
3232
|
-
// unmountRequest$
|
|
3233
|
-
// unmount()
|
|
3232
|
+
// unmountRequest$ <- TODO
|
|
3233
|
+
// unmount() <- TODO
|
|
3234
3234
|
// _debug
|
|
3235
3235
|
|
|
3236
3236
|
// [ INSTANTIATED STREAM OPERATOR ]
|
|
@@ -3239,18 +3239,17 @@ class Component {
|
|
|
3239
3239
|
// [ OUTPUT ]
|
|
3240
3240
|
// sinks
|
|
3241
3241
|
|
|
3242
|
-
constructor({ name='NO NAME', sources, intent,
|
|
3242
|
+
constructor({ name='NO NAME', sources, intent, model, context, response, view, peers={}, components={}, initialState, calculated, storeCalculatedInState=true, DOMSourceName='DOM', stateSourceName='STATE', requestSourceName='HTTP', debug=false }) {
|
|
3243
3243
|
if (!sources || !isObj(sources)) throw new Error('Missing or invalid sources')
|
|
3244
3244
|
|
|
3245
3245
|
this.name = name;
|
|
3246
3246
|
this.sources = sources;
|
|
3247
3247
|
this.intent = intent;
|
|
3248
|
-
this.request = request;
|
|
3249
3248
|
this.model = model;
|
|
3250
3249
|
this.context = context;
|
|
3251
3250
|
this.response = response;
|
|
3252
3251
|
this.view = view;
|
|
3253
|
-
this.
|
|
3252
|
+
this.peers = peers;
|
|
3254
3253
|
this.components = components;
|
|
3255
3254
|
this.initialState = initialState;
|
|
3256
3255
|
this.calculated = calculated;
|
|
@@ -3273,6 +3272,23 @@ class Component {
|
|
|
3273
3272
|
}));
|
|
3274
3273
|
}
|
|
3275
3274
|
|
|
3275
|
+
const props$ = sources.props$;
|
|
3276
|
+
if (props$) {
|
|
3277
|
+
this.sources.props$ = props$.map(val => {
|
|
3278
|
+
this.currentProps = val;
|
|
3279
|
+
return val
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
const children$ = sources.children$;
|
|
3284
|
+
if (children$) {
|
|
3285
|
+
this.sources.children$ = children$.map(val => {
|
|
3286
|
+
this.currentChildren = val;
|
|
3287
|
+
return val
|
|
3288
|
+
});
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
|
|
3276
3292
|
// Ensure that the root component has an intent and model
|
|
3277
3293
|
// This is necessary to ensure that the component tree's state sink is subscribed to
|
|
3278
3294
|
if (!this.isSubComponent && typeof this.intent === 'undefined' && typeof this.model === 'undefined') {
|
|
@@ -3288,14 +3304,13 @@ class Component {
|
|
|
3288
3304
|
this.addCalculated = this.createMemoizedAddCalculated();
|
|
3289
3305
|
this.log = makeLog(`${componentNumber} | ${name}`);
|
|
3290
3306
|
|
|
3307
|
+
this.initChildSources$();
|
|
3291
3308
|
this.initIntent$();
|
|
3292
3309
|
this.initAction$();
|
|
3293
|
-
this.initResponse$();
|
|
3294
3310
|
this.initState();
|
|
3295
3311
|
this.initContext();
|
|
3296
3312
|
this.initModel$();
|
|
3297
|
-
this.
|
|
3298
|
-
this.initChildren$();
|
|
3313
|
+
this.initPeers$();
|
|
3299
3314
|
this.initSubComponentSink$();
|
|
3300
3315
|
this.initSubComponentsRendered$();
|
|
3301
3316
|
this.initVdom$();
|
|
@@ -3307,7 +3322,7 @@ class Component {
|
|
|
3307
3322
|
}
|
|
3308
3323
|
|
|
3309
3324
|
get debug() {
|
|
3310
|
-
return this._debug || (ENVIRONMENT.
|
|
3325
|
+
return this._debug || (ENVIRONMENT.SYGNAL_DEBUG === 'true' || ENVIRONMENT.SYGNAL_DEBUG === true)
|
|
3311
3326
|
}
|
|
3312
3327
|
|
|
3313
3328
|
initIntent$() {
|
|
@@ -3344,7 +3359,7 @@ class Component {
|
|
|
3344
3359
|
|
|
3345
3360
|
const action$ = ((runner instanceof Stream$1) ? runner : (runner.apply && runner(this.sources) || xs$1.never()));
|
|
3346
3361
|
const bootstrap$ = xs$1.of({ type: BOOTSTRAP_ACTION }).compose(_default$4(10));
|
|
3347
|
-
const wrapped$ = _default$3(bootstrap$, action$)
|
|
3362
|
+
const wrapped$ = this.model[BOOTSTRAP_ACTION] ? _default$3(bootstrap$, action$) : action$;
|
|
3348
3363
|
|
|
3349
3364
|
|
|
3350
3365
|
let initialApiData;
|
|
@@ -3361,92 +3376,6 @@ class Component {
|
|
|
3361
3376
|
.compose(this.log(({ type }) => `<${ type }> Action triggered`));
|
|
3362
3377
|
}
|
|
3363
3378
|
|
|
3364
|
-
initResponse$() {
|
|
3365
|
-
if (typeof this.request == 'undefined') {
|
|
3366
|
-
return
|
|
3367
|
-
} else if (!isObj(this.request)) {
|
|
3368
|
-
throw new Error('The request parameter must be an object')
|
|
3369
|
-
}
|
|
3370
|
-
|
|
3371
|
-
const router$ = this.sources[this.requestSourceName];
|
|
3372
|
-
const methods = Object.entries(this.request);
|
|
3373
|
-
|
|
3374
|
-
const wrapped = methods.reduce((acc, [method, routes]) => {
|
|
3375
|
-
const _method = method.toLowerCase();
|
|
3376
|
-
if (typeof router$[_method] != 'function') {
|
|
3377
|
-
throw new Error('Invalid method in request object:', method)
|
|
3378
|
-
}
|
|
3379
|
-
const entries = Object.entries(routes);
|
|
3380
|
-
const mapped = entries.reduce((acc, [route, action]) => {
|
|
3381
|
-
const routeString = `[${_method.toUpperCase()}]:${route || 'none'}`;
|
|
3382
|
-
const actionType = typeof action;
|
|
3383
|
-
if (actionType === 'undefined') {
|
|
3384
|
-
throw new Error(`Action for '${ route }' route in request object not specified`)
|
|
3385
|
-
} else if (actionType !== 'string' && actionType !== 'function') {
|
|
3386
|
-
throw new Error(`Invalid action for '${ route }' route: expecting string or function`)
|
|
3387
|
-
}
|
|
3388
|
-
const actionString = (actionType === 'function') ? '[ FUNCTION ]' : `< ${ action } >`;
|
|
3389
|
-
console.log(`[${ this.name }] Adding ${ this.requestSourceName } route:`, _method.toUpperCase(), `'${ route }' <${ actionString }>`);
|
|
3390
|
-
const route$ = router$[_method](route)
|
|
3391
|
-
.compose(_default$5((a, b) => a.id == b.id))
|
|
3392
|
-
.map(req => {
|
|
3393
|
-
if (!req || !req.id) {
|
|
3394
|
-
throw new Error(`No id found in request: ${ routeString }`)
|
|
3395
|
-
}
|
|
3396
|
-
try {
|
|
3397
|
-
const _reqId = req.id;
|
|
3398
|
-
const params = req.params;
|
|
3399
|
-
const body = req.body;
|
|
3400
|
-
const cookies = req.cookies;
|
|
3401
|
-
const type = (actionType === 'function') ? 'FUNCTION' : action;
|
|
3402
|
-
const data = { params, body, cookies, req };
|
|
3403
|
-
const obj = { type, data: body, req, _reqId, _action: type };
|
|
3404
|
-
|
|
3405
|
-
const timestamp = (new Date()).toISOString();
|
|
3406
|
-
const ip = req.get ? req.get('host') : '0.0.0.0';
|
|
3407
|
-
|
|
3408
|
-
console.log(`${ timestamp } ${ ip } ${ req.method } ${ req.url }`);
|
|
3409
|
-
|
|
3410
|
-
if (this.debug) {
|
|
3411
|
-
this.action$.setDebugListener({next: ({ type }) => this.log(`[${ this.name }] Action from ${ this.requestSourceName } request: <${ type }>`, true)});
|
|
3412
|
-
}
|
|
3413
|
-
|
|
3414
|
-
if (actionType === 'function') {
|
|
3415
|
-
const enhancedState = this.addCalculated(this.currentState);
|
|
3416
|
-
const result = action(enhancedState, req);
|
|
3417
|
-
return xs$1.of({ ...obj, data: result })
|
|
3418
|
-
} else {
|
|
3419
|
-
this.action$.shamefullySendNext(obj);
|
|
3420
|
-
|
|
3421
|
-
const sourceEntries = Object.entries(this.sources);
|
|
3422
|
-
const responses = sourceEntries.reduce((acc, [name, source]) => {
|
|
3423
|
-
if (!source || typeof source[REQUEST_SELECTOR_METHOD] != 'function') return acc
|
|
3424
|
-
const selected$ = source[REQUEST_SELECTOR_METHOD](_reqId);
|
|
3425
|
-
return [ ...acc, selected$ ]
|
|
3426
|
-
}, []);
|
|
3427
|
-
return xs$1.merge(...responses)
|
|
3428
|
-
}
|
|
3429
|
-
} catch(err) {
|
|
3430
|
-
console.error(err);
|
|
3431
|
-
}
|
|
3432
|
-
}).flatten();
|
|
3433
|
-
return [ ...acc, route$ ]
|
|
3434
|
-
}, []);
|
|
3435
|
-
const mapped$ = xs$1.merge(...mapped);
|
|
3436
|
-
return [ ...acc, mapped$ ]
|
|
3437
|
-
}, []);
|
|
3438
|
-
|
|
3439
|
-
this.response$ = xs$1.merge(...wrapped)
|
|
3440
|
-
.compose(this.log(res => {
|
|
3441
|
-
if (res._action) return `[${ this.requestSourceName }] response data received for Action: <${ res._action }>`
|
|
3442
|
-
return `[${ this.requestSourceName }] response data received from FUNCTION`
|
|
3443
|
-
}));
|
|
3444
|
-
|
|
3445
|
-
if (typeof this.response != 'undefined' && typeof this.response$ == 'undefined') {
|
|
3446
|
-
throw new Error('Cannot have a response parameter without a request parameter')
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
3379
|
initState() {
|
|
3451
3380
|
if (this.model != undefined) {
|
|
3452
3381
|
if (this.model[INITIALIZE_ACTION] === undefined) {
|
|
@@ -3544,15 +3473,18 @@ class Component {
|
|
|
3544
3473
|
sinkEntries.forEach((entry) => {
|
|
3545
3474
|
const [sink, reducer] = entry;
|
|
3546
3475
|
|
|
3547
|
-
const isStateSink
|
|
3476
|
+
const isStateSink = (sink === this.stateSourceName);
|
|
3477
|
+
const isParentSink = (sink === PARENT_SINK_NAME);
|
|
3548
3478
|
|
|
3549
3479
|
const on = isStateSink ? onState() : onNormal();
|
|
3550
|
-
const on$ = on(action, reducer);
|
|
3480
|
+
const on$ = isParentSink ? on(action, reducer).map(value => ({ name: this.name, value })) : on(action, reducer);
|
|
3551
3481
|
|
|
3552
3482
|
const wrapped$ = on$
|
|
3553
3483
|
.compose(this.log(data => {
|
|
3554
3484
|
if (isStateSink) {
|
|
3555
3485
|
return `<${ action }> State reducer added`
|
|
3486
|
+
} else if (isParentSink) {
|
|
3487
|
+
return `<${ action }> Data sent to parent component: ${ JSON.stringify(data.value).replaceAll('"', '') }`
|
|
3556
3488
|
} else {
|
|
3557
3489
|
const extra = data && (data.type || data.command || data.name || data.key || (Array.isArray(data) && 'Array') || data);
|
|
3558
3490
|
return `<${ action }> Data sent to [${ sink }]: ${ JSON.stringify(extra).replaceAll('"', '') }`
|
|
@@ -3576,51 +3508,7 @@ class Component {
|
|
|
3576
3508
|
this.model$ = model$;
|
|
3577
3509
|
}
|
|
3578
3510
|
|
|
3579
|
-
|
|
3580
|
-
const responseType = typeof this.response;
|
|
3581
|
-
if (responseType != 'function' && responseType != 'undefined') {
|
|
3582
|
-
throw new Error('The response parameter must be a function')
|
|
3583
|
-
}
|
|
3584
|
-
|
|
3585
|
-
if (responseType == 'undefined') {
|
|
3586
|
-
if (this.response$) {
|
|
3587
|
-
this.response$.subscribe({
|
|
3588
|
-
next: this.log(({ _reqId, _action }) => `Unhandled response for request: ${ _action } ${ _reqId }`)
|
|
3589
|
-
});
|
|
3590
|
-
}
|
|
3591
|
-
this.sendResponse$ = xs$1.never();
|
|
3592
|
-
return
|
|
3593
|
-
}
|
|
3594
|
-
|
|
3595
|
-
const selectable = {
|
|
3596
|
-
select: (actions) => {
|
|
3597
|
-
if (typeof actions == 'undefined') return this.response$
|
|
3598
|
-
if (!Array.isArray(actions)) actions = [actions];
|
|
3599
|
-
return this.response$.filter(({_action}) => (actions.length > 0) ? (_action === 'FUNCTION' || actions.includes(_action)) : true)
|
|
3600
|
-
}
|
|
3601
|
-
};
|
|
3602
|
-
|
|
3603
|
-
const out = this.response(selectable);
|
|
3604
|
-
if (!isObj(out)) {
|
|
3605
|
-
throw new Error('The response function must return an object')
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3608
|
-
const entries = Object.entries(out);
|
|
3609
|
-
const out$ = entries.reduce((acc, [command, response$]) => {
|
|
3610
|
-
const mapped$ = response$.map(({ _reqId, _action, data }) => {
|
|
3611
|
-
if (!_reqId) {
|
|
3612
|
-
throw new Error(`No request id found for response for: ${ command }`)
|
|
3613
|
-
}
|
|
3614
|
-
return { _reqId, _action, command, data }
|
|
3615
|
-
});
|
|
3616
|
-
return [ ...acc, mapped$ ]
|
|
3617
|
-
}, []);
|
|
3618
|
-
|
|
3619
|
-
this.sendResponse$ = xs$1.merge(...out$)
|
|
3620
|
-
.compose(this.log(({ _reqId, _action }) => `[${ this.requestSourceName }] response sent for: <${ _action }>`));
|
|
3621
|
-
}
|
|
3622
|
-
|
|
3623
|
-
initChildren$() {
|
|
3511
|
+
initPeers$() {
|
|
3624
3512
|
const initial = this.sourceNames.reduce((acc, name) => {
|
|
3625
3513
|
if (name == this.DOMSourceName) {
|
|
3626
3514
|
acc[name] = {};
|
|
@@ -3630,19 +3518,46 @@ class Component {
|
|
|
3630
3518
|
return acc
|
|
3631
3519
|
}, {});
|
|
3632
3520
|
|
|
3633
|
-
this.
|
|
3634
|
-
const
|
|
3521
|
+
this.peers$ = Object.entries(this.peers).reduce((acc, [peerName, peerFactory]) => {
|
|
3522
|
+
const peer$ = peerFactory(this.sources);
|
|
3635
3523
|
this.sourceNames.forEach(source => {
|
|
3636
3524
|
if (source == this.DOMSourceName) {
|
|
3637
|
-
acc[source][
|
|
3525
|
+
acc[source][peerName] = peer$[source];
|
|
3638
3526
|
} else {
|
|
3639
|
-
acc[source].push(
|
|
3527
|
+
acc[source].push(peer$[source]);
|
|
3640
3528
|
}
|
|
3641
3529
|
});
|
|
3642
3530
|
return acc
|
|
3643
3531
|
}, initial);
|
|
3644
3532
|
}
|
|
3645
3533
|
|
|
3534
|
+
initChildSources$() {
|
|
3535
|
+
let newSourcesNext;
|
|
3536
|
+
const childSources$ = xs$1.create({
|
|
3537
|
+
start: listener => {
|
|
3538
|
+
newSourcesNext = listener.next.bind(listener);
|
|
3539
|
+
},
|
|
3540
|
+
stop: _ => {
|
|
3541
|
+
|
|
3542
|
+
}
|
|
3543
|
+
}).map(sources => xs$1.merge(...sources)).flatten();
|
|
3544
|
+
|
|
3545
|
+
// childSources$.subscribe({ next: _ => _})
|
|
3546
|
+
|
|
3547
|
+
this.sources[CHILD_SOURCE_NAME] = {
|
|
3548
|
+
select: (name) => {
|
|
3549
|
+
const all$ = childSources$;
|
|
3550
|
+
const filtered$ = name ? all$.filter(entry => entry.name === name) : all$;
|
|
3551
|
+
const unwrapped$ = filtered$.map(entry => entry.value);
|
|
3552
|
+
return unwrapped$
|
|
3553
|
+
}
|
|
3554
|
+
};
|
|
3555
|
+
|
|
3556
|
+
this.newChildSources = (sources) => {
|
|
3557
|
+
if (typeof newSourcesNext === 'function') newSourcesNext(sources);
|
|
3558
|
+
};
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3646
3561
|
initSubComponentSink$() {
|
|
3647
3562
|
const subComponentSink$ = xs$1.create({
|
|
3648
3563
|
start: listener => {
|
|
@@ -3689,17 +3604,17 @@ class Component {
|
|
|
3689
3604
|
initSinks() {
|
|
3690
3605
|
this.sinks = this.sourceNames.reduce((acc, name) => {
|
|
3691
3606
|
if (name == this.DOMSourceName) return acc
|
|
3692
|
-
const subComponentSink$ = this.subComponentSink$ ? this.subComponentSink$.map(sinks => sinks[name]).filter(sink => !!sink).flatten() : xs$1.never();
|
|
3607
|
+
const subComponentSink$ = (this.subComponentSink$ && name !== PARENT_SINK_NAME) ? this.subComponentSink$.map(sinks => sinks[name]).filter(sink => !!sink).flatten() : xs$1.never();
|
|
3693
3608
|
if (name === this.stateSourceName) {
|
|
3694
|
-
acc[name] = xs$1.merge((this.model$[name] || xs$1.never()), subComponentSink$, this.sources[this.stateSourceName].stream.filter(_ => false), ...this.
|
|
3609
|
+
acc[name] = xs$1.merge((this.model$[name] || xs$1.never()), subComponentSink$, this.sources[this.stateSourceName].stream.filter(_ => false), ...(this.peers$[name] || []));
|
|
3695
3610
|
} else {
|
|
3696
|
-
acc[name] = xs$1.merge((this.model$[name] || xs$1.never()), subComponentSink$, ...this.
|
|
3611
|
+
acc[name] = xs$1.merge((this.model$[name] || xs$1.never()), subComponentSink$, ...(this.peers$[name] || []));
|
|
3697
3612
|
}
|
|
3698
3613
|
return acc
|
|
3699
3614
|
}, {});
|
|
3700
3615
|
|
|
3701
|
-
this.sinks[this.DOMSourceName]
|
|
3702
|
-
this.sinks[this.
|
|
3616
|
+
this.sinks[this.DOMSourceName] = this.vdom$;
|
|
3617
|
+
this.sinks[PARENT_SINK_NAME] = this.model$[PARENT_SINK_NAME] || xs$1.never();
|
|
3703
3618
|
}
|
|
3704
3619
|
|
|
3705
3620
|
makeOnAction(action$, isStateSink=true, rootAction$) {
|
|
@@ -3712,33 +3627,30 @@ class Component {
|
|
|
3712
3627
|
returnStream$ = filtered$.map(action => {
|
|
3713
3628
|
const next = (type, data, delay=10) => {
|
|
3714
3629
|
if (typeof delay !== 'number') throw new Error(`[${ this.name } ] Invalid delay value provided to next() function in model action '${ name }'. Must be a number in ms.`)
|
|
3715
|
-
const _reqId = action._reqId || (action.req && action.req.id);
|
|
3716
|
-
const _data = _reqId ? (isObj(data) ? { ...data, _reqId, _action: name } : { data, _reqId, _action: name }) : data;
|
|
3717
3630
|
// put the "next" action request at the end of the event loop so the "current" action completes first
|
|
3718
3631
|
setTimeout(() => {
|
|
3719
3632
|
// push the "next" action request into the action$ stream
|
|
3720
|
-
rootAction$.shamefullySendNext({ type, data
|
|
3633
|
+
rootAction$.shamefullySendNext({ type, data });
|
|
3721
3634
|
}, delay);
|
|
3722
3635
|
this.log(`<${ name }> Triggered a next() action: <${ type }> ${ delay }ms delay`, true);
|
|
3723
3636
|
};
|
|
3724
3637
|
|
|
3638
|
+
const extra = { props: this.currentProps, children: this.currentChildren, context: this.currentContext };
|
|
3639
|
+
|
|
3725
3640
|
let data = action.data;
|
|
3726
|
-
if (data && data.data && data._reqId) data = data.data;
|
|
3727
3641
|
if (isStateSink) {
|
|
3728
3642
|
return (state) => {
|
|
3729
3643
|
const _state = this.isSubComponent ? this.currentState : state;
|
|
3730
3644
|
const enhancedState = this.addCalculated(_state);
|
|
3731
|
-
const newState = reducer(enhancedState, data, next,
|
|
3645
|
+
const newState = reducer(enhancedState, data, next, extra);
|
|
3732
3646
|
if (newState == ABORT) return _state
|
|
3733
3647
|
return this.cleanupCalculated(newState)
|
|
3734
3648
|
}
|
|
3735
3649
|
} else {
|
|
3736
3650
|
const enhancedState = this.addCalculated(this.currentState);
|
|
3737
|
-
const reduced = reducer(enhancedState, data, next,
|
|
3651
|
+
const reduced = reducer(enhancedState, data, next, extra);
|
|
3738
3652
|
const type = typeof reduced;
|
|
3739
|
-
|
|
3740
|
-
if (['string', 'number', 'boolean', 'function'].includes(type)) return reduced
|
|
3741
|
-
if (isObj(reduced)) return { ...reduced, _reqId, _action: name }
|
|
3653
|
+
if (isObj(reduced) || ['string', 'number', 'boolean', 'function'].includes(type)) return reduced
|
|
3742
3654
|
if (type == 'undefined') {
|
|
3743
3655
|
console.warn(`'undefined' value sent to ${ name }`);
|
|
3744
3656
|
return reduced
|
|
@@ -3767,23 +3679,15 @@ class Component {
|
|
|
3767
3679
|
return lastResult
|
|
3768
3680
|
}
|
|
3769
3681
|
if (!isObj(this.calculated)) throw new Error(`'calculated' parameter must be an object mapping calculated state field named to functions`)
|
|
3770
|
-
|
|
3771
|
-
|
|
3682
|
+
|
|
3683
|
+
const calculated = this.getCalculatedValues(state);
|
|
3684
|
+
if (!calculated) {
|
|
3772
3685
|
lastState = state;
|
|
3773
3686
|
lastResult = state;
|
|
3774
3687
|
return state
|
|
3775
3688
|
}
|
|
3776
|
-
const calculated = entries.reduce((acc, [field, fn]) => {
|
|
3777
|
-
if (typeof fn !== 'function') throw new Error(`Missing or invalid calculator function for calculated field '${ field }`)
|
|
3778
|
-
try {
|
|
3779
|
-
acc[field] = fn(state);
|
|
3780
|
-
} catch(e) {
|
|
3781
|
-
console.warn(`Calculated field '${ field }' threw an error during calculation: ${ e.message }`);
|
|
3782
|
-
}
|
|
3783
|
-
return acc
|
|
3784
|
-
}, {});
|
|
3785
3689
|
|
|
3786
|
-
const newState = { ...state, ...calculated
|
|
3690
|
+
const newState = { ...state, ...calculated };
|
|
3787
3691
|
|
|
3788
3692
|
lastState = state;
|
|
3789
3693
|
lastResult = newState;
|
|
@@ -3792,12 +3696,27 @@ class Component {
|
|
|
3792
3696
|
}
|
|
3793
3697
|
}
|
|
3794
3698
|
|
|
3699
|
+
getCalculatedValues(state) {
|
|
3700
|
+
const entries = Object.entries(this.calculated || {});
|
|
3701
|
+
if (entries.length === 0) {
|
|
3702
|
+
return
|
|
3703
|
+
}
|
|
3704
|
+
return entries.reduce((acc, [field, fn]) => {
|
|
3705
|
+
if (typeof fn !== 'function') throw new Error(`Missing or invalid calculator function for calculated field '${ field }`)
|
|
3706
|
+
try {
|
|
3707
|
+
acc[field] = fn(state);
|
|
3708
|
+
} catch(e) {
|
|
3709
|
+
console.warn(`Calculated field '${ field }' threw an error during calculation: ${ e.message }`);
|
|
3710
|
+
}
|
|
3711
|
+
return acc
|
|
3712
|
+
}, {})
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3795
3715
|
cleanupCalculated(incomingState) {
|
|
3796
3716
|
if (!incomingState || !isObj(incomingState) || Array.isArray(incomingState)) return incomingState
|
|
3797
3717
|
const state = this.storeCalculatedInState ? this.addCalculated(incomingState) : incomingState;
|
|
3798
|
-
const {
|
|
3799
|
-
|
|
3800
|
-
if (!this.calculated) return copy
|
|
3718
|
+
const copy = { ...state };
|
|
3719
|
+
if (!this.calculated || this.storeCalculatedInState) return copy
|
|
3801
3720
|
const keys = Object.keys(this.calculated);
|
|
3802
3721
|
keys.forEach(key => {
|
|
3803
3722
|
if (this.initialState && typeof this.initialState[key] !== 'undefined') {
|
|
@@ -3811,7 +3730,7 @@ class Component {
|
|
|
3811
3730
|
|
|
3812
3731
|
collectRenderParameters() {
|
|
3813
3732
|
const state = this.sources[this.stateSourceName];
|
|
3814
|
-
const renderParams = { ...this.
|
|
3733
|
+
const renderParams = { ...this.peers$[this.DOMSourceName] };
|
|
3815
3734
|
|
|
3816
3735
|
const enhancedState = state && state.isolateSource(state, { get: state => this.addCalculated(state) });
|
|
3817
3736
|
const stateStream = (enhancedState && enhancedState.stream) || xs$1.never();
|
|
@@ -3820,15 +3739,15 @@ class Component {
|
|
|
3820
3739
|
renderParams.state = stateStream.compose(_default$5(objIsEqual));
|
|
3821
3740
|
|
|
3822
3741
|
if (this.sources.props$) {
|
|
3823
|
-
renderParams.
|
|
3742
|
+
renderParams.props = this.sources.props$.compose(_default$5(propsIsEqual));
|
|
3824
3743
|
}
|
|
3825
3744
|
|
|
3826
3745
|
if (this.sources.children$) {
|
|
3827
|
-
renderParams.
|
|
3746
|
+
renderParams.children = this.sources.children$.compose(_default$5(objIsEqual));
|
|
3828
3747
|
}
|
|
3829
3748
|
|
|
3830
3749
|
if (this.context$) {
|
|
3831
|
-
renderParams.
|
|
3750
|
+
renderParams.context = this.context$.compose(_default$5(objIsEqual));
|
|
3832
3751
|
}
|
|
3833
3752
|
|
|
3834
3753
|
const names = [];
|
|
@@ -3845,10 +3764,10 @@ class Component {
|
|
|
3845
3764
|
.map(arr => {
|
|
3846
3765
|
const params = names.reduce((acc, name, index) => {
|
|
3847
3766
|
acc[name] = arr[index];
|
|
3848
|
-
if (name === 'state')
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3767
|
+
if (name === 'state') {
|
|
3768
|
+
acc[this.stateSourceName] = arr[index];
|
|
3769
|
+
acc.calculated = (arr[index] && this.getCalculatedValues(arr[index])) || {};
|
|
3770
|
+
}
|
|
3852
3771
|
return acc
|
|
3853
3772
|
}, {});
|
|
3854
3773
|
return params
|
|
@@ -3870,6 +3789,7 @@ class Component {
|
|
|
3870
3789
|
}
|
|
3871
3790
|
|
|
3872
3791
|
const sinkArrsByType = {};
|
|
3792
|
+
const childSources = [];
|
|
3873
3793
|
let newInstanceCount = 0;
|
|
3874
3794
|
|
|
3875
3795
|
const newComponents = entries.reduce((acc, [id, el]) => {
|
|
@@ -3881,9 +3801,13 @@ class Component {
|
|
|
3881
3801
|
const isSwitchable = data.isSwitchable || false;
|
|
3882
3802
|
|
|
3883
3803
|
const addSinks = (sinks) => {
|
|
3884
|
-
Object.entries(sinks).
|
|
3804
|
+
Object.entries(sinks).forEach(([name, stream]) => {
|
|
3885
3805
|
sinkArrsByType[name] ||= [];
|
|
3886
|
-
if (name
|
|
3806
|
+
if (name === PARENT_SINK_NAME) {
|
|
3807
|
+
childSources.push(stream);
|
|
3808
|
+
} else if (name !== this.DOMSourceName) {
|
|
3809
|
+
sinkArrsByType[name].push(stream);
|
|
3810
|
+
}
|
|
3887
3811
|
});
|
|
3888
3812
|
};
|
|
3889
3813
|
|
|
@@ -3930,6 +3854,8 @@ class Component {
|
|
|
3930
3854
|
}, {});
|
|
3931
3855
|
|
|
3932
3856
|
this.newSubComponentSinks(mergedSinksByType);
|
|
3857
|
+
this.newChildSources(childSources);
|
|
3858
|
+
|
|
3933
3859
|
|
|
3934
3860
|
if (newInstanceCount > 0) this.log(`New sub components instantiated: ${ newInstanceCount }`, true);
|
|
3935
3861
|
|
|
@@ -3953,18 +3879,30 @@ class Component {
|
|
|
3953
3879
|
instantiateCollection(el, props$, children$) {
|
|
3954
3880
|
const data = el.data;
|
|
3955
3881
|
const props = data.props || {};
|
|
3956
|
-
|
|
3957
|
-
let
|
|
3882
|
+
let filter = typeof props.filter === 'function' ? props.filter : undefined;
|
|
3883
|
+
let sort = sortFunctionFromProp(props.sort);
|
|
3958
3884
|
|
|
3959
|
-
const
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3885
|
+
const arrayOperators = {
|
|
3886
|
+
filter,
|
|
3887
|
+
sort
|
|
3888
|
+
};
|
|
3889
|
+
|
|
3890
|
+
const state$ = xs$1.combine(this.sources[this.stateSourceName].stream.startWith(this.currentState), props$.startWith(props))
|
|
3891
|
+
// this debounce is important. it forces state and prop updates to happen at the same time
|
|
3892
|
+
// without this, changes to sort or filter won't happen properly
|
|
3893
|
+
.compose(_default$2(1))
|
|
3894
|
+
.map(([state, props]) => {
|
|
3895
|
+
if (props.filter !== arrayOperators.filter) {
|
|
3896
|
+
arrayOperators.filter = typeof props.filter === 'function' ? props.filter : undefined;
|
|
3897
|
+
}
|
|
3898
|
+
if (props.sort !== arrayOperators.sort) {
|
|
3899
|
+
arrayOperators.sort = sortFunctionFromProp(props.sort);
|
|
3963
3900
|
}
|
|
3964
|
-
|
|
3901
|
+
|
|
3902
|
+
return isObj(state) ? this.addCalculated(state) : state
|
|
3965
3903
|
});
|
|
3966
3904
|
|
|
3967
|
-
const stateSource = new StateSource(
|
|
3905
|
+
const stateSource = new StateSource(state$);
|
|
3968
3906
|
const stateField = props.from;
|
|
3969
3907
|
const collectionOf = props.of;
|
|
3970
3908
|
const idField = props.idfield || 'id';
|
|
@@ -3976,10 +3914,10 @@ class Component {
|
|
|
3976
3914
|
if (collectionOf.isSygnalComponent) {
|
|
3977
3915
|
factory = collectionOf;
|
|
3978
3916
|
} else {
|
|
3979
|
-
const name =
|
|
3917
|
+
const name = collectionOf.componentName || collectionOf.label || collectionOf.name || 'FUNCTION_COMPONENT';
|
|
3980
3918
|
const view = collectionOf;
|
|
3981
|
-
const { model, intent, context,
|
|
3982
|
-
const options = { name, view, model, intent, context,
|
|
3919
|
+
const { model, intent, context, peers, components, initialState, calculated, storeCalculatedInState, DOMSourceName, stateSourceName, debug } = collectionOf;
|
|
3920
|
+
const options = { name, view, model, intent, context, peers, components, initialState, calculated, storeCalculatedInState, DOMSourceName, stateSourceName, debug };
|
|
3983
3921
|
factory = component(options);
|
|
3984
3922
|
}
|
|
3985
3923
|
} else if (this.components[collectionOf]) {
|
|
@@ -3988,22 +3926,17 @@ class Component {
|
|
|
3988
3926
|
throw new Error(`[${this.name}] Invalid 'of' propery in collection: ${ collectionOf }`)
|
|
3989
3927
|
}
|
|
3990
3928
|
|
|
3991
|
-
const sanitizeItems = item => {
|
|
3992
|
-
if (isObj(item)) {
|
|
3993
|
-
const { __props, __children, __context, ...sanitized } = item;
|
|
3994
|
-
return sanitized
|
|
3995
|
-
} else {
|
|
3996
|
-
return item
|
|
3997
|
-
}
|
|
3998
|
-
};
|
|
3999
|
-
|
|
4000
3929
|
const fieldLense = {
|
|
4001
3930
|
get: state => {
|
|
4002
|
-
const { __props, __children } = state;
|
|
4003
3931
|
if (!Array.isArray(state[stateField])) return []
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
3932
|
+
const items = state[stateField];
|
|
3933
|
+
const filtered = typeof arrayOperators.filter === 'function' ? items.filter(arrayOperators.filter) : items;
|
|
3934
|
+
const sorted = typeof arrayOperators.sort ? filtered.sort(arrayOperators.sort) : filtered;
|
|
3935
|
+
const mapped = sorted.map((item, index) => {
|
|
3936
|
+
return (isObj(item)) ? { ...item, [idField]: item[idField] || index } : { value: item, [idField]: index }
|
|
3937
|
+
});
|
|
3938
|
+
|
|
3939
|
+
return mapped
|
|
4007
3940
|
},
|
|
4008
3941
|
set: (oldState, newState) => {
|
|
4009
3942
|
if (this.calculated && stateField in this.calculated) {
|
|
@@ -4011,15 +3944,15 @@ class Component {
|
|
|
4011
3944
|
return oldState
|
|
4012
3945
|
}
|
|
4013
3946
|
const updated = [];
|
|
4014
|
-
for (const oldItem of oldState[stateField].map((item, index) => ({ __primitive: true, value: item, [idField]: index }))) {
|
|
4015
|
-
if (!filter(oldItem)) {
|
|
3947
|
+
for (const oldItem of oldState[stateField].map((item, index) => (isObj(item) ? { ...item, [idField]: item[idField] || index } : { __primitive: true, value: item, [idField]: index }))) {
|
|
3948
|
+
if (typeof arrayOperators.filter === 'function' && !arrayOperators.filter(oldItem)) {
|
|
4016
3949
|
updated.push(oldItem.__primitive ? oldItem.value : oldItem);
|
|
4017
3950
|
} else {
|
|
4018
|
-
const newItem = newState.find(
|
|
3951
|
+
const newItem = newState.find(item => item[idField] === oldItem[idField]);
|
|
4019
3952
|
if (typeof newItem !== 'undefined') updated.push(oldItem.__primitive ? newItem.value : newItem);
|
|
4020
3953
|
}
|
|
4021
3954
|
}
|
|
4022
|
-
return { ...oldState, [stateField]:
|
|
3955
|
+
return { ...oldState, [stateField]: updated }
|
|
4023
3956
|
}
|
|
4024
3957
|
};
|
|
4025
3958
|
|
|
@@ -4074,7 +4007,7 @@ class Component {
|
|
|
4074
4007
|
lense = undefined;
|
|
4075
4008
|
}
|
|
4076
4009
|
|
|
4077
|
-
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context
|
|
4010
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context$, PARENT: null };
|
|
4078
4011
|
const sink$ = collection(factory, lense, { container: null })(sources);
|
|
4079
4012
|
if (!isObj(sink$)) {
|
|
4080
4013
|
throw new Error('Invalid sinks returned from component factory of collection element')
|
|
@@ -4085,40 +4018,31 @@ class Component {
|
|
|
4085
4018
|
instantiateSwitchable(el, props$, children$) {
|
|
4086
4019
|
const data = el.data;
|
|
4087
4020
|
const props = data.props || {};
|
|
4088
|
-
el.children || [];
|
|
4089
4021
|
|
|
4090
|
-
const
|
|
4091
|
-
.map((
|
|
4092
|
-
return isObj(state) ?
|
|
4022
|
+
const state$ = this.sources[this.stateSourceName].stream.startWith(this.currentState)
|
|
4023
|
+
.map((state) => {
|
|
4024
|
+
return isObj(state) ? this.addCalculated(state) : state
|
|
4093
4025
|
});
|
|
4094
4026
|
|
|
4095
|
-
const stateSource
|
|
4096
|
-
const stateField
|
|
4027
|
+
const stateSource = new StateSource(state$);
|
|
4028
|
+
const stateField = props.state;
|
|
4097
4029
|
let lense;
|
|
4098
4030
|
|
|
4099
4031
|
const fieldLense = {
|
|
4100
|
-
get: state =>
|
|
4101
|
-
const { __props, __children } = state;
|
|
4102
|
-
return (isObj(state[stateField])) ? { ...state[stateField], __props, __children, __context: this.currentContext } : { value: state[stateField], __props, __children, __context: this.currentContext }
|
|
4103
|
-
},
|
|
4032
|
+
get: state => state[stateField],
|
|
4104
4033
|
set: (oldState, newState) => {
|
|
4105
4034
|
if (this.calculated && stateField in this.calculated) {
|
|
4106
4035
|
console.warn(`Switchable sub-component of ${ this.name } attempted to update state on a calculated field '${ stateField }': Update ignored`);
|
|
4107
4036
|
return oldState
|
|
4108
4037
|
}
|
|
4109
4038
|
if (!isObj(newState) || Array.isArray(newState)) return { ...oldState, [stateField]: newState }
|
|
4110
|
-
|
|
4111
|
-
return { ...oldState, [stateField]: sanitized }
|
|
4039
|
+
return { ...oldState, [stateField]: newState }
|
|
4112
4040
|
}
|
|
4113
4041
|
};
|
|
4114
4042
|
|
|
4115
4043
|
const baseLense = {
|
|
4116
4044
|
get: state => state,
|
|
4117
|
-
set: (oldState, newState) =>
|
|
4118
|
-
if (!isObj(newState) || Array.isArray(newState)) return newState
|
|
4119
|
-
const { __props, __children, __context, ...sanitized } = newState;
|
|
4120
|
-
return sanitized
|
|
4121
|
-
}
|
|
4045
|
+
set: (oldState, newState) => newState
|
|
4122
4046
|
};
|
|
4123
4047
|
|
|
4124
4048
|
if (typeof stateField === 'undefined') {
|
|
@@ -4142,10 +4066,10 @@ class Component {
|
|
|
4142
4066
|
keys.forEach(key => {
|
|
4143
4067
|
const current = switchableComponents[key];
|
|
4144
4068
|
if (!current.isSygnalComponent) {
|
|
4145
|
-
const name =
|
|
4069
|
+
const name = current.componentName || current.label || current.name || 'FUNCTION_COMPONENT';
|
|
4146
4070
|
const view = current;
|
|
4147
|
-
const { model, intent, context,
|
|
4148
|
-
const options = { name, view, model, intent, context,
|
|
4071
|
+
const { model, intent, context, peers, components, initialState, calculated, storeCalculatedInState, DOMSourceName, stateSourceName, debug } = current;
|
|
4072
|
+
const options = { name, view, model, intent, context, peers, components, initialState, calculated, storeCalculatedInState, DOMSourceName, stateSourceName, debug };
|
|
4149
4073
|
switchableComponents[key] = component(options);
|
|
4150
4074
|
}
|
|
4151
4075
|
});
|
|
@@ -4164,14 +4088,13 @@ class Component {
|
|
|
4164
4088
|
const componentName = el.sel;
|
|
4165
4089
|
const data = el.data;
|
|
4166
4090
|
const props = data.props || {};
|
|
4167
|
-
el.children || [];
|
|
4168
4091
|
|
|
4169
|
-
const
|
|
4170
|
-
.map((
|
|
4171
|
-
return isObj(state) ?
|
|
4092
|
+
const state$ = this.sources[this.stateSourceName].stream.startWith(this.currentState)
|
|
4093
|
+
.map((state) => {
|
|
4094
|
+
return isObj(state) ? this.addCalculated(state) : state
|
|
4172
4095
|
});
|
|
4173
4096
|
|
|
4174
|
-
const stateSource = new StateSource(
|
|
4097
|
+
const stateSource = new StateSource(state$);
|
|
4175
4098
|
const stateField = props.state;
|
|
4176
4099
|
|
|
4177
4100
|
if (typeof props.sygnalFactory !== 'function' && isObj(props.sygnalOptions)) {
|
|
@@ -4187,10 +4110,7 @@ class Component {
|
|
|
4187
4110
|
let lense;
|
|
4188
4111
|
|
|
4189
4112
|
const fieldLense = {
|
|
4190
|
-
get: state =>
|
|
4191
|
-
const { __props, __children } = state;
|
|
4192
|
-
return isObj(state[stateField]) ? { ...state[stateField], __props, __children, __context: this.currentContext } : { value: state[stateField], __props, __children, __context: this.currentContext }
|
|
4193
|
-
},
|
|
4113
|
+
get: state => state[stateField],
|
|
4194
4114
|
set: (oldState, newState) => {
|
|
4195
4115
|
if (this.calculated && stateField in this.calculated) {
|
|
4196
4116
|
console.warn(`Sub-component of ${ this.name } attempted to update state on a calculated field '${ stateField }': Update ignored`);
|
|
@@ -4202,11 +4122,7 @@ class Component {
|
|
|
4202
4122
|
|
|
4203
4123
|
const baseLense = {
|
|
4204
4124
|
get: state => state,
|
|
4205
|
-
set: (oldState, newState) =>
|
|
4206
|
-
if (!isObj(newState) || Array.isArray(newState)) return newState
|
|
4207
|
-
const { __props, __children, __context, ...sanitized } = newState;
|
|
4208
|
-
return sanitized
|
|
4209
|
-
}
|
|
4125
|
+
set: (oldState, newState) => newState
|
|
4210
4126
|
};
|
|
4211
4127
|
|
|
4212
4128
|
if (typeof stateField === 'undefined') {
|
|
@@ -4418,9 +4334,11 @@ function deepCopyVdom(obj) {
|
|
|
4418
4334
|
return { ...obj, children: Array.isArray(obj.children) ? obj.children.map(deepCopyVdom) : undefined, data: obj.data && { ...obj.data, componentsInjected: false } }
|
|
4419
4335
|
}
|
|
4420
4336
|
|
|
4421
|
-
function
|
|
4422
|
-
|
|
4423
|
-
|
|
4337
|
+
function propsIsEqual(obj1, obj2) {
|
|
4338
|
+
return objIsEqual(sanitizeObject(obj1))
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
function objIsEqual(obj1, obj2, maxDepth = 5, depth = 0) {
|
|
4424
4342
|
// Base case: if the current depth exceeds maxDepth, return true
|
|
4425
4343
|
if (depth > maxDepth) {
|
|
4426
4344
|
return false;
|
|
@@ -4473,7 +4391,7 @@ function objIsEqual(objA, objB, maxDepth = 5, depth = 0) {
|
|
|
4473
4391
|
|
|
4474
4392
|
function sanitizeObject(obj) {
|
|
4475
4393
|
if (!isObj(obj)) return obj
|
|
4476
|
-
const {state, of, from,
|
|
4394
|
+
const {state, of, from, filter, ...sanitized} = obj;
|
|
4477
4395
|
return sanitized
|
|
4478
4396
|
}
|
|
4479
4397
|
|
|
@@ -4481,6 +4399,80 @@ function isObj(obj) {
|
|
|
4481
4399
|
return typeof obj === 'object' && obj !== null && !Array.isArray(obj)
|
|
4482
4400
|
}
|
|
4483
4401
|
|
|
4402
|
+
function __baseSort(a, b, ascending=true) {
|
|
4403
|
+
const direction = ascending ? 1 : -1;
|
|
4404
|
+
switch(true) {
|
|
4405
|
+
case a > b: return 1 * direction
|
|
4406
|
+
case a < b: return -1 * direction
|
|
4407
|
+
default: return 0
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
function __sortFunctionFromObj(item) {
|
|
4412
|
+
const entries = Object.entries(item);
|
|
4413
|
+
if (entries.length > 1) {
|
|
4414
|
+
console.error('Sort objects can only have one key:', item);
|
|
4415
|
+
return undefined
|
|
4416
|
+
}
|
|
4417
|
+
const entry = entries[0];
|
|
4418
|
+
const [field, directionRaw] = entry;
|
|
4419
|
+
if (!['string', 'number'].includes(typeof directionRaw)) {
|
|
4420
|
+
console.error('Sort object properties must be a string or number:', item);
|
|
4421
|
+
return undefined
|
|
4422
|
+
}
|
|
4423
|
+
let ascending = true;
|
|
4424
|
+
if (typeof directionRaw === 'string') {
|
|
4425
|
+
if (!['asc', 'dec'].includes(directionRaw.toLowerCase())) {
|
|
4426
|
+
console.error('Sort object string values must be asc or dec:', item);
|
|
4427
|
+
return undefined
|
|
4428
|
+
}
|
|
4429
|
+
ascending = directionRaw.toLowerCase() === 'asc';
|
|
4430
|
+
}
|
|
4431
|
+
if (typeof directionRaw === 'number') {
|
|
4432
|
+
if (directionRaw !== 1 && directionRaw !== -1) {
|
|
4433
|
+
console.error('Sort object number values must be 1 or -1:', item);
|
|
4434
|
+
return undefined
|
|
4435
|
+
}
|
|
4436
|
+
ascending = directionRaw === 1;
|
|
4437
|
+
}
|
|
4438
|
+
return (a, b) => __baseSort(a[field], b[field], ascending)
|
|
4439
|
+
}
|
|
4440
|
+
|
|
4441
|
+
function sortFunctionFromProp(sortProp) {
|
|
4442
|
+
if (!sortProp) return undefined
|
|
4443
|
+
const propType = typeof sortProp;
|
|
4444
|
+
// if function do nothing
|
|
4445
|
+
if (propType === 'function') return sortProp
|
|
4446
|
+
if (propType === 'string') {
|
|
4447
|
+
// if passed either 'asc' or 'dec' sort on the entire item
|
|
4448
|
+
if (sortProp.toLowerCase() === 'asc' || sortProp.toLowerCase() === 'dec') {
|
|
4449
|
+
const ascending = sortProp.toLowerCase() === 'asc';
|
|
4450
|
+
return (a, b) => __baseSort(a, b, ascending)
|
|
4451
|
+
}
|
|
4452
|
+
// assume it's a field/property name, and sort it ascending
|
|
4453
|
+
const field = sortProp;
|
|
4454
|
+
return (a, b) => __baseSort(a[field], b[field], true)
|
|
4455
|
+
} else if (Array.isArray(sortProp)) {
|
|
4456
|
+
const sorters = sortProp.map(item => {
|
|
4457
|
+
if (typeof item === 'function') return item
|
|
4458
|
+
if (typeof item === 'string' && !['asc', 'dec'].includes(item.toLowerCase())) return (a, b) => __baseSort(a[item], b[item], true)
|
|
4459
|
+
if (isObj(item)) {
|
|
4460
|
+
return __sortFunctionFromObj(item)
|
|
4461
|
+
}
|
|
4462
|
+
});
|
|
4463
|
+
|
|
4464
|
+
return (a, b) => sorters.filter(sorter => typeof sorter === 'function').reduce((comparisonSoFar, currentSorter) => {
|
|
4465
|
+
if (comparisonSoFar !== 0) return comparisonSoFar
|
|
4466
|
+
return currentSorter(a, b)
|
|
4467
|
+
}, 0)
|
|
4468
|
+
} else if (isObj(sortProp)) {
|
|
4469
|
+
return __sortFunctionFromObj(sortProp)
|
|
4470
|
+
} else {
|
|
4471
|
+
console.error('Invalid sort option (ignoring):', item);
|
|
4472
|
+
return undefined
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4484
4476
|
function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
4485
4477
|
const {
|
|
4486
4478
|
selector: selectorProperty = 'category',
|
|
@@ -4694,10 +4686,10 @@ function logDriver(out$) {
|
|
|
4694
4686
|
function run(app, drivers={}, options={}) {
|
|
4695
4687
|
const { mountPoint='#root', fragments=true } = options;
|
|
4696
4688
|
if (!app.isSygnalComponent) {
|
|
4697
|
-
const name = app.name || "FUNCTIONAL_COMPONENT";
|
|
4689
|
+
const name = app.name || app.componentName || app.label || "FUNCTIONAL_COMPONENT";
|
|
4698
4690
|
const view = app;
|
|
4699
|
-
const { model, intent, context,
|
|
4700
|
-
const options = { name, view, model, intent, context,
|
|
4691
|
+
const { model, intent, context, peers, components, initialState, calculated, storeCalculatedInState, DOMSourceName, stateSourceName, debug } = app;
|
|
4692
|
+
const options = { name, view, model, intent, context, peers, components, initialState, calculated, storeCalculatedInState, DOMSourceName, stateSourceName, debug };
|
|
4701
4693
|
|
|
4702
4694
|
app = component(options);
|
|
4703
4695
|
}
|