@jsenv/navi 0.16.50 → 0.16.52
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/dist/jsenv_navi.js +122 -54
- package/dist/jsenv_navi.js.map +8 -6
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -300,7 +300,9 @@ const getSignalType = (value) => {
|
|
|
300
300
|
*
|
|
301
301
|
* @param {any} a - First value to compare
|
|
302
302
|
* @param {any} b - Second value to compare
|
|
303
|
-
* @param {
|
|
303
|
+
* @param {Object} [options={}] - Comparison options
|
|
304
|
+
* @param {Function} [options.keyComparator] - Custom comparator function for object properties and array elements
|
|
305
|
+
* @param {boolean} [options.ignoreArrayOrder=false] - If true, arrays are considered equal regardless of element order
|
|
304
306
|
* @returns {boolean} true if values are deeply equal, false otherwise
|
|
305
307
|
*/
|
|
306
308
|
|
|
@@ -319,7 +321,11 @@ const getSignalType = (value) => {
|
|
|
319
321
|
*/
|
|
320
322
|
const SYMBOL_IDENTITY = Symbol.for("navi_object_identity");
|
|
321
323
|
|
|
322
|
-
const compareTwoJsValues = (
|
|
324
|
+
const compareTwoJsValues = (
|
|
325
|
+
rootA,
|
|
326
|
+
rootB,
|
|
327
|
+
{ keyComparator, ignoreArrayOrder = false } = {},
|
|
328
|
+
) => {
|
|
323
329
|
const seenSet = new Set();
|
|
324
330
|
const compare = (a, b) => {
|
|
325
331
|
if (a === b) {
|
|
@@ -370,6 +376,32 @@ const compareTwoJsValues = (rootA, rootB, { keyComparator } = {}) => {
|
|
|
370
376
|
if (a.length !== b.length) {
|
|
371
377
|
return false;
|
|
372
378
|
}
|
|
379
|
+
if (ignoreArrayOrder) {
|
|
380
|
+
// Unordered array comparison: each element in 'a' must have a match in 'b'
|
|
381
|
+
const usedIndices = new Set();
|
|
382
|
+
for (let i = 0; i < a.length; i++) {
|
|
383
|
+
const aValue = a[i];
|
|
384
|
+
let foundMatch = false;
|
|
385
|
+
|
|
386
|
+
for (let j = 0; j < b.length; j++) {
|
|
387
|
+
if (usedIndices.has(j)) continue; // Already matched with another element
|
|
388
|
+
|
|
389
|
+
const bValue = b[j];
|
|
390
|
+
const comparator = keyComparator || compare;
|
|
391
|
+
if (comparator(aValue, bValue, i, compare)) {
|
|
392
|
+
foundMatch = true;
|
|
393
|
+
usedIndices.add(j);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (!foundMatch) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
// Ordered array comparison (original behavior)
|
|
373
405
|
let i = 0;
|
|
374
406
|
while (i < a.length) {
|
|
375
407
|
const aValue = a[i];
|
|
@@ -2393,11 +2425,17 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
2393
2425
|
if (dynamicDefaultSignal) {
|
|
2394
2426
|
const dynamicValue = dynamicDefaultSignal.peek();
|
|
2395
2427
|
if (dynamicValue === undefined) {
|
|
2396
|
-
return value
|
|
2428
|
+
return !compareTwoJsValues(value, staticDefaultValue, {
|
|
2429
|
+
ignoreArrayOrder: true,
|
|
2430
|
+
});
|
|
2397
2431
|
}
|
|
2398
|
-
return value
|
|
2432
|
+
return !compareTwoJsValues(value, dynamicValue, {
|
|
2433
|
+
ignoreArrayOrder: true,
|
|
2434
|
+
});
|
|
2399
2435
|
}
|
|
2400
|
-
return value
|
|
2436
|
+
return !compareTwoJsValues(value, staticDefaultValue, {
|
|
2437
|
+
ignoreArrayOrder: true,
|
|
2438
|
+
});
|
|
2401
2439
|
};
|
|
2402
2440
|
|
|
2403
2441
|
// Create signal with initial value: use stored value, or undefined to indicate no explicit value
|
|
@@ -2553,6 +2591,21 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
2553
2591
|
}
|
|
2554
2592
|
effect(() => {
|
|
2555
2593
|
const value = preactSignal.value;
|
|
2594
|
+
|
|
2595
|
+
if (dynamicDefaultSignal) {
|
|
2596
|
+
// With dynamic defaults: always persist to preserve user intent
|
|
2597
|
+
// even when value matches dynamic defaults that may change
|
|
2598
|
+
if (value !== undefined) {
|
|
2599
|
+
if (debug) {
|
|
2600
|
+
console.debug(
|
|
2601
|
+
`[stateSignal:${signalIdString}] dynamic default: writing to localStorage "${localStorageKey}"=${value}`,
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
writeIntoLocalStorage(value);
|
|
2605
|
+
}
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
// Static defaults: only persist custom values
|
|
2556
2609
|
if (isCustomValue(value)) {
|
|
2557
2610
|
if (debug) {
|
|
2558
2611
|
console.debug(
|
|
@@ -4517,54 +4570,6 @@ const isProps = (value) => {
|
|
|
4517
4570
|
return value !== null && typeof value === "object";
|
|
4518
4571
|
};
|
|
4519
4572
|
|
|
4520
|
-
/**
|
|
4521
|
-
* Creates a signal that stays synchronized with an external value,
|
|
4522
|
-
* only updating the signal when the value actually changes.
|
|
4523
|
-
*
|
|
4524
|
-
* This hook solves a common reactive UI pattern where:
|
|
4525
|
-
* 1. A signal controls a UI element (like an input field)
|
|
4526
|
-
* 2. The UI element can be modified by user interaction
|
|
4527
|
-
* 3. When the external "source of truth" changes, it should take precedence
|
|
4528
|
-
*
|
|
4529
|
-
* @param {any} value - The external value to sync with (the "source of truth")
|
|
4530
|
-
* @param {any} [initialValue] - Optional initial value for the signal (defaults to value)
|
|
4531
|
-
* @returns {Signal} A signal that tracks the external value but allows temporary local changes
|
|
4532
|
-
*
|
|
4533
|
-
* @example
|
|
4534
|
-
* const FileNameEditor = ({ file }) => {
|
|
4535
|
-
* // Signal stays in sync with file.name, but allows user editing
|
|
4536
|
-
* const nameSignal = useSignalSync(file.name);
|
|
4537
|
-
*
|
|
4538
|
-
* return (
|
|
4539
|
-
* <Editable
|
|
4540
|
-
* valueSignal={nameSignal} // User can edit this
|
|
4541
|
-
* action={renameFileAction} // Saves changes
|
|
4542
|
-
* />
|
|
4543
|
-
* );
|
|
4544
|
-
* };
|
|
4545
|
-
*
|
|
4546
|
-
* // Scenario:
|
|
4547
|
-
* // 1. file.name = "doc.txt", nameSignal.value = "doc.txt"
|
|
4548
|
-
* // 2. User types "report" -> nameSignal.value = "report.txt"
|
|
4549
|
-
* // 3. External update: file.name = "shared-doc.txt"
|
|
4550
|
-
* // 4. Next render: nameSignal.value = "shared-doc.txt" (model wins!)
|
|
4551
|
-
*
|
|
4552
|
-
*/
|
|
4553
|
-
|
|
4554
|
-
const useSignalSync = (value, initialValue = value) => {
|
|
4555
|
-
const signal = useSignal(initialValue);
|
|
4556
|
-
const previousValueRef = useRef(value);
|
|
4557
|
-
|
|
4558
|
-
// Only update signal when external value actually changes
|
|
4559
|
-
// This preserves user input between external changes
|
|
4560
|
-
if (previousValueRef.current !== value) {
|
|
4561
|
-
previousValueRef.current = value;
|
|
4562
|
-
signal.value = value; // Model takes precedence
|
|
4563
|
-
}
|
|
4564
|
-
|
|
4565
|
-
return signal;
|
|
4566
|
-
};
|
|
4567
|
-
|
|
4568
4573
|
const addIntoArray = (array, ...valuesToAdd) => {
|
|
4569
4574
|
if (valuesToAdd.length === 1) {
|
|
4570
4575
|
const [valueToAdd] = valuesToAdd;
|
|
@@ -4627,6 +4632,69 @@ const removeFromArray = (array, ...valuesToRemove) => {
|
|
|
4627
4632
|
return hasRemovedValues ? arrayWithoutTheseValues : array;
|
|
4628
4633
|
};
|
|
4629
4634
|
|
|
4635
|
+
const useArraySignalMembership = (arraySignal, id) => {
|
|
4636
|
+
const array = arraySignal.value;
|
|
4637
|
+
const found = array.includes(id);
|
|
4638
|
+
return [
|
|
4639
|
+
found,
|
|
4640
|
+
(enabled) => {
|
|
4641
|
+
if (enabled) {
|
|
4642
|
+
arraySignal.value = addIntoArray(array, id);
|
|
4643
|
+
} else {
|
|
4644
|
+
arraySignal.value = removeFromArray(array, id);
|
|
4645
|
+
}
|
|
4646
|
+
},
|
|
4647
|
+
];
|
|
4648
|
+
};
|
|
4649
|
+
|
|
4650
|
+
/**
|
|
4651
|
+
* Creates a signal that stays synchronized with an external value,
|
|
4652
|
+
* only updating the signal when the value actually changes.
|
|
4653
|
+
*
|
|
4654
|
+
* This hook solves a common reactive UI pattern where:
|
|
4655
|
+
* 1. A signal controls a UI element (like an input field)
|
|
4656
|
+
* 2. The UI element can be modified by user interaction
|
|
4657
|
+
* 3. When the external "source of truth" changes, it should take precedence
|
|
4658
|
+
*
|
|
4659
|
+
* @param {any} value - The external value to sync with (the "source of truth")
|
|
4660
|
+
* @param {any} [initialValue] - Optional initial value for the signal (defaults to value)
|
|
4661
|
+
* @returns {Signal} A signal that tracks the external value but allows temporary local changes
|
|
4662
|
+
*
|
|
4663
|
+
* @example
|
|
4664
|
+
* const FileNameEditor = ({ file }) => {
|
|
4665
|
+
* // Signal stays in sync with file.name, but allows user editing
|
|
4666
|
+
* const nameSignal = useSignalSync(file.name);
|
|
4667
|
+
*
|
|
4668
|
+
* return (
|
|
4669
|
+
* <Editable
|
|
4670
|
+
* valueSignal={nameSignal} // User can edit this
|
|
4671
|
+
* action={renameFileAction} // Saves changes
|
|
4672
|
+
* />
|
|
4673
|
+
* );
|
|
4674
|
+
* };
|
|
4675
|
+
*
|
|
4676
|
+
* // Scenario:
|
|
4677
|
+
* // 1. file.name = "doc.txt", nameSignal.value = "doc.txt"
|
|
4678
|
+
* // 2. User types "report" -> nameSignal.value = "report.txt"
|
|
4679
|
+
* // 3. External update: file.name = "shared-doc.txt"
|
|
4680
|
+
* // 4. Next render: nameSignal.value = "shared-doc.txt" (model wins!)
|
|
4681
|
+
*
|
|
4682
|
+
*/
|
|
4683
|
+
|
|
4684
|
+
const useSignalSync = (value, initialValue = value) => {
|
|
4685
|
+
const signal = useSignal(initialValue);
|
|
4686
|
+
const previousValueRef = useRef(value);
|
|
4687
|
+
|
|
4688
|
+
// Only update signal when external value actually changes
|
|
4689
|
+
// This preserves user input between external changes
|
|
4690
|
+
if (previousValueRef.current !== value) {
|
|
4691
|
+
previousValueRef.current = value;
|
|
4692
|
+
signal.value = value; // Model takes precedence
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
return signal;
|
|
4696
|
+
};
|
|
4697
|
+
|
|
4630
4698
|
/**
|
|
4631
4699
|
* Picks the best initial value from three options using a simple priority system.
|
|
4632
4700
|
*
|
|
@@ -28456,5 +28524,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
28456
28524
|
})
|
|
28457
28525
|
});
|
|
28458
28526
|
|
|
28459
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, clearAllRoutes, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useCalloutClose, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useMatchingRouteInfo, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
|
|
28527
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, BadgeCount, Box, Button, ButtonCopyToClipboard, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, ConstructionSvg, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, Group, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, Separator, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, clearAllRoutes, compareTwoJsValues, createAction, createAvailableConstraint, createRequestCanceller, createSelectionKeyboardShortcuts, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, requestAction, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useArraySignalMembership, useCalloutClose, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useMatchingRouteInfo, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useTitleLevel, useUrlSearchParam, valueInLocalStorage };
|
|
28460
28528
|
//# sourceMappingURL=jsenv_navi.js.map
|