@jsenv/navi 0.18.19 → 0.18.21

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.
@@ -630,109 +630,6 @@ const stringifyForDisplay = (
630
630
  return String(value);
631
631
  };
632
632
 
633
- /**
634
- * Reactively runs an action whenever the params derived from signals change.
635
- *
636
- * @param {object} action - The action to run.
637
- * @param {Function} deriveActionParamsFromSignals - A function that reads signals and returns
638
- * the params to pass to the action. It is re-evaluated automatically whenever a signal it
639
- * read changes. Return `false`/`null`/`undefined` to skip running the action.
640
- * @param {object} [options]
641
- * @param {number} [options.debounce] - When set, the action is only run once the derived params
642
- * have been stable for this many milliseconds. Useful to avoid firing a backend call on every
643
- * keystroke: set `debounce: 500` and the request is sent only after the user stops interacting
644
- * with the filters for 500 ms.
645
- *
646
- * Example — auto-refresh a result list while the user tweaks filters:
647
- * ```js
648
- * actionRunEffect(searchAction, () => ({
649
- * query: querySignal.value,
650
- * page: pageSignal.value,
651
- * }), { debounce: 500 });
652
- * ```
653
- * The action will not fire while the user is actively changing filters; it fires once
654
- * they pause for half a second.
655
- */
656
- const actionRunEffect = (
657
- action,
658
- deriveActionParamsFromSignals,
659
- { debounce, meta } = {},
660
- ) => {
661
- let lastTruthyParams;
662
- let actionParamsSignal = computed(() => {
663
- const params = deriveActionParamsFromSignals();
664
- action.debug(
665
- `Derived params for action "${action}": ${stringifyForDisplay(params)}`,
666
- );
667
- if (!params) {
668
- // normalize falsy values to undefined so that any falsy value ends up in the same state of "don't run the action"
669
- return undefined;
670
- }
671
- if (params && typeof params.then === "function") {
672
- {
673
- console.warn(
674
- `actionRunEffect second arg is returning a promise. This is not supported, the function should be sync and return params to give to the action`,
675
- );
676
- }
677
- }
678
- if (lastTruthyParams === undefined) {
679
- lastTruthyParams = params;
680
- }
681
- return params;
682
- });
683
- if (debounce) {
684
- actionParamsSignal = debounceSignal(actionParamsSignal, {
685
- delay: debounce,
686
- });
687
- }
688
-
689
- const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
690
- syncParams: debounce ? actionParamsSignal.flush : undefined,
691
- onChange: (actionTarget, actionTargetPrevious, { explicitRunIntent }) => {
692
- if (explicitRunIntent) {
693
- // The caller already issued an explicit run/rerun/prerun/reset/abort —
694
- // don't attempt to also auto-run from the params change to avoid double-runs.
695
- action.debug(
696
- `"${actionTarget}": explicit run intent detected -> skipping auto-run from params change`,
697
- );
698
- return;
699
- }
700
- if (!actionTargetPrevious && actionTarget) {
701
- // first run
702
- if (!actionTarget.params) {
703
- // falsy params, don't run
704
- return;
705
- }
706
- actionTarget.run({ reason: "truthy params first run" });
707
- return;
708
- }
709
-
710
- if (
711
- actionTargetPrevious &&
712
- !actionTargetPrevious.isPrerun &&
713
- actionTarget
714
- ) {
715
- // params changed
716
- if (!actionTarget.params) {
717
- // falsy params, don't run
718
- actionTargetPrevious.abort("abortOnFalsyParams");
719
- return;
720
- }
721
- if (compareTwoJsValues(lastTruthyParams, actionTarget.params)) {
722
- actionTarget.run({ reason: "params restored to last truthy value" });
723
- } else {
724
- actionTarget.rerun({ reason: "params modified" });
725
- }
726
- }
727
- },
728
- meta,
729
- });
730
- if (actionParamsSignal.peek()) {
731
- actionRunnedByThisEffect.run({ reason: "initial truthy params" });
732
- }
733
- return actionRunnedByThisEffect;
734
- };
735
-
736
633
  /**
737
634
  * jsenv/navi - createJsValueWeakMap
738
635
  *
@@ -1478,6 +1375,7 @@ const createAction = (callback, rootOptions = {}) => {
1478
1375
  keepOldData = false,
1479
1376
  meta = {},
1480
1377
 
1378
+ outputSignal,
1481
1379
  completeSideEffect,
1482
1380
  } = options;
1483
1381
  if (!Object.hasOwn(options, "params")) {
@@ -1896,6 +1794,9 @@ const createAction = (callback, rootOptions = {}) => {
1896
1794
  valueSignal.value = value;
1897
1795
  runningStateSignal.value = COMPLETED;
1898
1796
  const data = dataSignal.value;
1797
+ if (outputSignal) {
1798
+ outputSignal.value = data;
1799
+ }
1899
1800
  onComplete?.(data, action);
1900
1801
  completeSideEffect?.(action);
1901
1802
  });
@@ -2012,6 +1913,9 @@ const createAction = (callback, rootOptions = {}) => {
2012
1913
  if (!keepOldData) {
2013
1914
  valueSignal.value = valueInitial;
2014
1915
  }
1916
+ if (outputSignal) {
1917
+ outputSignal.value = null;
1918
+ }
2015
1919
  isPrerunSignal.value = true;
2016
1920
  runningStateSignal.value = IDLE;
2017
1921
  });
@@ -2386,6 +2290,110 @@ const COMPLETED_ACTION = createAction(() => undefined, {
2386
2290
  });
2387
2291
  getActionPrivateProperties(COMPLETED_ACTION).performRun({});
2388
2292
 
2293
+ /**
2294
+ * Reactively runs an action whenever the params derived from signals change.
2295
+ *
2296
+ * @param {object} action - The action to run.
2297
+ * @param {Function} deriveActionParamsFromSignals - A function that reads signals and returns
2298
+ * the params to pass to the action. It is re-evaluated automatically whenever a signal it
2299
+ * read changes. Return `false`/`null`/`undefined` to skip running the action.
2300
+ * @param {object} [options]
2301
+ * @param {number} [options.debounce] - When set, the action is only run once the derived params
2302
+ * have been stable for this many milliseconds. Useful to avoid firing a backend call on every
2303
+ * keystroke: set `debounce: 500` and the request is sent only after the user stops interacting
2304
+ * with the filters for 500 ms.
2305
+ *
2306
+ * Example — auto-refresh a result list while the user tweaks filters:
2307
+ * ```js
2308
+ * actionRunEffect(searchAction, () => ({
2309
+ * query: querySignal.value,
2310
+ * page: pageSignal.value,
2311
+ * }), { debounce: 500 });
2312
+ * ```
2313
+ * The action will not fire while the user is actively changing filters; it fires once
2314
+ * they pause for half a second.
2315
+ */
2316
+ const actionRunEffect = (
2317
+ action,
2318
+ deriveActionParamsFromSignals,
2319
+ { debounce, meta } = {},
2320
+ ) => {
2321
+ if (typeof action === "function") action = createAction(action);
2322
+ let lastTruthyParams;
2323
+ let actionParamsSignal = computed(() => {
2324
+ const params = deriveActionParamsFromSignals();
2325
+ action.debug(
2326
+ `Derived params for action "${action}": ${stringifyForDisplay(params)}`,
2327
+ );
2328
+ if (!params) {
2329
+ // normalize falsy values to undefined so that any falsy value ends up in the same state of "don't run the action"
2330
+ return undefined;
2331
+ }
2332
+ if (params && typeof params.then === "function") {
2333
+ {
2334
+ console.warn(
2335
+ `actionRunEffect second arg is returning a promise. This is not supported, the function should be sync and return params to give to the action`,
2336
+ );
2337
+ }
2338
+ }
2339
+ if (lastTruthyParams === undefined) {
2340
+ lastTruthyParams = params;
2341
+ }
2342
+ return params;
2343
+ });
2344
+ if (debounce) {
2345
+ actionParamsSignal = debounceSignal(actionParamsSignal, {
2346
+ delay: debounce,
2347
+ });
2348
+ }
2349
+
2350
+ const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
2351
+ syncParams: debounce ? actionParamsSignal.flush : undefined,
2352
+ onChange: (actionTarget, actionTargetPrevious, { explicitRunIntent }) => {
2353
+ if (explicitRunIntent) {
2354
+ // The caller already issued an explicit run/rerun/prerun/reset/abort —
2355
+ // don't attempt to also auto-run from the params change to avoid double-runs.
2356
+ action.debug(
2357
+ `"${actionTarget}": explicit run intent detected -> skipping auto-run from params change`,
2358
+ );
2359
+ return;
2360
+ }
2361
+ if (!actionTargetPrevious && actionTarget) {
2362
+ // first run
2363
+ if (!actionTarget.params) {
2364
+ // falsy params, don't run
2365
+ return;
2366
+ }
2367
+ actionTarget.run({ reason: "truthy params first run" });
2368
+ return;
2369
+ }
2370
+
2371
+ if (
2372
+ actionTargetPrevious &&
2373
+ !actionTargetPrevious.isPrerun &&
2374
+ actionTarget
2375
+ ) {
2376
+ // params changed
2377
+ if (!actionTarget.params) {
2378
+ // falsy params, don't run
2379
+ actionTargetPrevious.abort("abortOnFalsyParams");
2380
+ return;
2381
+ }
2382
+ if (compareTwoJsValues(lastTruthyParams, actionTarget.params)) {
2383
+ actionTarget.run({ reason: "params restored to last truthy value" });
2384
+ } else {
2385
+ actionTarget.rerun({ reason: "params modified" });
2386
+ }
2387
+ }
2388
+ },
2389
+ meta,
2390
+ });
2391
+ if (actionParamsSignal.peek()) {
2392
+ actionRunnedByThisEffect.run({ reason: "initial truthy params" });
2393
+ }
2394
+ return actionRunnedByThisEffect;
2395
+ };
2396
+
2389
2397
  const useActionData = (action) => {
2390
2398
  if (!action) {
2391
2399
  return undefined;