@jsenv/navi 0.26.7 → 0.26.8
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 +133 -46
- package/dist/jsenv_navi.js.map +20 -10
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -3,7 +3,7 @@ import { isValidElement, createContext, h, options, toChildArray, render, cloneE
|
|
|
3
3
|
import { useErrorBoundary, useLayoutEffect, useEffect, useContext, useMemo, useRef, useState, useCallback, useImperativeHandle, useId } from "preact/hooks";
|
|
4
4
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
5
5
|
import { signal, effect, computed, batch, useSignal } from "@preact/signals";
|
|
6
|
-
import { createIterableWeakSet, getElementSignature, mergeOneStyle,
|
|
6
|
+
import { createIterableWeakSet, getElementSignature, mergeOneStyle, normalizeStyle, createPubSub, mergeTwoStyles, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, findBefore, findAfter, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, allowWheelThrough, resolveCSSColor, createStyleController, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, resolveCSSSize, canInterceptKeys, activeElementSignal, hasCSSSizeUnit, resolveOklchLightness, contrastColor, initFocusGroup, elementIsFocusable, scrollIntoViewScoped, findFocusable, trapScrollInside, trapFocusInside, snapToPixel, dragAfterThreshold, getScrollContainer, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
7
7
|
export { contrastColor } from "@jsenv/dom";
|
|
8
8
|
import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
|
|
9
9
|
import { createValidity } from "@jsenv/validity";
|
|
@@ -6222,15 +6222,6 @@ const withPropsClassName = (baseClassName, classNameFromProps) => {
|
|
|
6222
6222
|
|
|
6223
6223
|
const BoxFlowContext = createContext();
|
|
6224
6224
|
|
|
6225
|
-
const normalizeSpacingStyle = (value, property = "padding") => {
|
|
6226
|
-
const cssValue = SIZE_MAP[value];
|
|
6227
|
-
return cssValue || stringifyStyle(value, property);
|
|
6228
|
-
};
|
|
6229
|
-
const normalizeTypoStyle = (value, property = "fontSize") => {
|
|
6230
|
-
const cssValue = TYPO_SIZE_MAP[value];
|
|
6231
|
-
return cssValue || stringifyStyle(value, property);
|
|
6232
|
-
};
|
|
6233
|
-
|
|
6234
6225
|
const PASS_THROUGH = { name: "pass_through" };
|
|
6235
6226
|
const applyOnCSSProp = (cssStyle) => {
|
|
6236
6227
|
return (value) => {
|
|
@@ -6410,7 +6401,7 @@ const DIMENSION_PROPS = {
|
|
|
6410
6401
|
return { transform: `scaleX(${stringifyStyle(value, "scaleX")})` };
|
|
6411
6402
|
},
|
|
6412
6403
|
scaleY: (value) => {
|
|
6413
|
-
return { transform: `scaleY(${value})` };
|
|
6404
|
+
return { transform: `scaleY(${stringifyStyle(value, "scaleY")})` };
|
|
6414
6405
|
},
|
|
6415
6406
|
scale: (value) => {
|
|
6416
6407
|
if (Array.isArray(value)) {
|
|
@@ -6655,7 +6646,7 @@ const CONTENT_PROPS = {
|
|
|
6655
6646
|
spacing: (value, { boxFlow }) => {
|
|
6656
6647
|
if (isSpacingHandledByFlow(boxFlow)) {
|
|
6657
6648
|
return {
|
|
6658
|
-
gap:
|
|
6649
|
+
gap: stringifySpacingStyle(value, "gap"),
|
|
6659
6650
|
};
|
|
6660
6651
|
}
|
|
6661
6652
|
return undefined;
|
|
@@ -6663,12 +6654,12 @@ const CONTENT_PROPS = {
|
|
|
6663
6654
|
spacingX: (value, { boxFlow }) => {
|
|
6664
6655
|
if (boxFlow === "flex-x" || boxFlow === "inline-flex-x") {
|
|
6665
6656
|
return {
|
|
6666
|
-
gap:
|
|
6657
|
+
gap: stringifySpacingStyle(value, "gap"),
|
|
6667
6658
|
};
|
|
6668
6659
|
}
|
|
6669
6660
|
if (boxFlow === "grid" || boxFlow === "inline-grid") {
|
|
6670
6661
|
return {
|
|
6671
|
-
columnGap:
|
|
6662
|
+
columnGap: stringifySpacingStyle(value, "columnGap"),
|
|
6672
6663
|
};
|
|
6673
6664
|
}
|
|
6674
6665
|
return undefined;
|
|
@@ -6676,12 +6667,12 @@ const CONTENT_PROPS = {
|
|
|
6676
6667
|
spacingY: (value, { boxFlow }) => {
|
|
6677
6668
|
if (boxFlow === "flex-y" || boxFlow === "inline-flex-y") {
|
|
6678
6669
|
return {
|
|
6679
|
-
gap:
|
|
6670
|
+
gap: stringifySpacingStyle(value, "gap"),
|
|
6680
6671
|
};
|
|
6681
6672
|
}
|
|
6682
6673
|
if (boxFlow === "grid" || boxFlow === "inline-grid") {
|
|
6683
6674
|
return {
|
|
6684
|
-
rowGap:
|
|
6675
|
+
rowGap: stringifySpacingStyle(value, "rowGap"),
|
|
6685
6676
|
};
|
|
6686
6677
|
}
|
|
6687
6678
|
return undefined;
|
|
@@ -6773,25 +6764,31 @@ const getStylePropGroup = (name) => {
|
|
|
6773
6764
|
}
|
|
6774
6765
|
return null;
|
|
6775
6766
|
};
|
|
6776
|
-
const
|
|
6767
|
+
const getStringifier = (key) => {
|
|
6777
6768
|
if (key === "borderRadius") {
|
|
6778
|
-
return
|
|
6769
|
+
return stringifySpacingStyle;
|
|
6779
6770
|
}
|
|
6780
6771
|
const group = getStylePropGroup(key);
|
|
6781
6772
|
if (group === "margin" || group === "padding") {
|
|
6782
|
-
return
|
|
6773
|
+
return stringifySpacingStyle;
|
|
6783
6774
|
}
|
|
6784
6775
|
if (group === "typo") {
|
|
6785
|
-
return
|
|
6776
|
+
return stringifyTypoStyle;
|
|
6786
6777
|
}
|
|
6787
|
-
return
|
|
6778
|
+
return stringifyStyle;
|
|
6779
|
+
};
|
|
6780
|
+
const stringifySpacingStyle = (size, property = "padding") => {
|
|
6781
|
+
return normalizeStyle(SIZE_MAP[size] || size, property, "css");
|
|
6782
|
+
};
|
|
6783
|
+
const stringifyTypoStyle = (size, property = "fontSize") => {
|
|
6784
|
+
return normalizeStyle(TYPO_SIZE_MAP[size] || size, property, "css");
|
|
6788
6785
|
};
|
|
6789
|
-
const
|
|
6786
|
+
const stringifyStyle = (
|
|
6790
6787
|
value,
|
|
6791
6788
|
name,
|
|
6792
6789
|
// styleContext, context
|
|
6793
6790
|
) => {
|
|
6794
|
-
return
|
|
6791
|
+
return normalizeStyle(value, name, "css");
|
|
6795
6792
|
};
|
|
6796
6793
|
const getHowToHandleStyleProp = (name) => {
|
|
6797
6794
|
const getStyle = All_PROPS[name];
|
|
@@ -6807,8 +6804,8 @@ const prepareStyleValue = (
|
|
|
6807
6804
|
styleContext,
|
|
6808
6805
|
context,
|
|
6809
6806
|
) => {
|
|
6810
|
-
const
|
|
6811
|
-
const cssValue =
|
|
6807
|
+
const stringifier = getStringifier(name);
|
|
6808
|
+
const cssValue = stringifier(value, name, styleContext, context);
|
|
6812
6809
|
const mergedValue = mergeOneStyle(existingValue, cssValue, name, context);
|
|
6813
6810
|
return mergedValue;
|
|
6814
6811
|
};
|
|
@@ -6848,8 +6845,8 @@ const sizeSpacingKeySet = new Set(Object.keys(SIZE_MAP));
|
|
|
6848
6845
|
const isSizeSpacingKey = (key) => {
|
|
6849
6846
|
return sizeSpacingKeySet.has(key);
|
|
6850
6847
|
};
|
|
6851
|
-
const resolveSpacingSize = (size, property = "padding") => {
|
|
6852
|
-
return
|
|
6848
|
+
const resolveSpacingSize = (size, element, property = "padding") => {
|
|
6849
|
+
return normalizeStyle(SIZE_MAP[size] || size, property, "js", element);
|
|
6853
6850
|
};
|
|
6854
6851
|
|
|
6855
6852
|
const COLOR_KEYWORD_MAP = {
|
|
@@ -21017,7 +21014,7 @@ const applySpacingOnTextChildren = (children, spacing, defaultSpace) => {
|
|
|
21017
21014
|
separator = defaultSpace;
|
|
21018
21015
|
} else if (typeof spacing === "string") {
|
|
21019
21016
|
if (isSizeSpacingKey(spacing)) {
|
|
21020
|
-
const value =
|
|
21017
|
+
const value = stringifySpacingStyle(spacing);
|
|
21021
21018
|
separator = jsx(CustomWidthSpace, {
|
|
21022
21019
|
value: value,
|
|
21023
21020
|
useRealSpaceChar: useRealSpaceChar
|
|
@@ -30731,6 +30728,8 @@ const Popover = props => {
|
|
|
30731
30728
|
positionY,
|
|
30732
30729
|
positionXFixed,
|
|
30733
30730
|
positionYFixed,
|
|
30731
|
+
spacing = 0,
|
|
30732
|
+
viewportSpacing = 0,
|
|
30734
30733
|
...rest
|
|
30735
30734
|
} = props;
|
|
30736
30735
|
const defaultRef = useRef();
|
|
@@ -30762,21 +30761,39 @@ const Popover = props => {
|
|
|
30762
30761
|
width,
|
|
30763
30762
|
height
|
|
30764
30763
|
} = effectiveAnchor.getBoundingClientRect();
|
|
30765
|
-
const
|
|
30766
|
-
|
|
30767
|
-
|
|
30764
|
+
const {
|
|
30765
|
+
left: borderLeft,
|
|
30766
|
+
right: borderRight,
|
|
30767
|
+
top: borderTop,
|
|
30768
|
+
bottom: borderBottom
|
|
30769
|
+
} = getBorderSizes(effectiveAnchor);
|
|
30770
|
+
popoverEl.style.setProperty("--anchor-width", `${snapToPixel(width)}px`);
|
|
30771
|
+
popoverEl.style.setProperty("--anchor-height", `${snapToPixel(height)}px`);
|
|
30772
|
+
popoverEl.style.setProperty("--anchor-inner-width", `${snapToPixel(width - borderLeft - borderRight)}px`);
|
|
30773
|
+
popoverEl.style.setProperty("--anchor-inner-height", `${snapToPixel(height - borderTop - borderBottom)}px`);
|
|
30768
30774
|
const minLeft = 1;
|
|
30769
30775
|
const effectivePositionX = anchor ? positionX : "center";
|
|
30776
|
+
// Remove max-height constraint so pickPositionRelativeTo measures the natural
|
|
30777
|
+
// (unconstrained) height of the popover. This ensures the 60% flip threshold
|
|
30778
|
+
// compares against the real content height, not the already-truncated one.
|
|
30779
|
+
popoverEl.style.removeProperty("--space-available");
|
|
30770
30780
|
const {
|
|
30771
30781
|
left,
|
|
30772
|
-
top
|
|
30782
|
+
top,
|
|
30783
|
+
positionY: finalPositionY,
|
|
30784
|
+
spaceAbove,
|
|
30785
|
+
spaceBelow
|
|
30773
30786
|
} = pickPositionRelativeTo(popoverEl, effectiveAnchor, {
|
|
30774
30787
|
positionX: effectivePositionX,
|
|
30775
30788
|
positionY,
|
|
30776
30789
|
positionXFixed,
|
|
30777
30790
|
positionYFixed,
|
|
30791
|
+
spacing: resolveSpacingSize(spacing),
|
|
30792
|
+
viewportSpacing: resolveSpacingSize(viewportSpacing),
|
|
30778
30793
|
minLeft
|
|
30779
30794
|
});
|
|
30795
|
+
const spaceAvailable = finalPositionY === "above" || finalPositionY === "above-overlap" ? spaceAbove : spaceBelow;
|
|
30796
|
+
popoverEl.style.setProperty("--space-available", `${spaceAvailable}px`);
|
|
30780
30797
|
debugPopup(`positionPopover("${positionEvent.type}") -> left: ${left}, top: ${top}`);
|
|
30781
30798
|
popoverEl.style.top = `${top}px`;
|
|
30782
30799
|
popoverEl.style.left = `${Math.max(left, minLeft)}px`;
|
|
@@ -31046,7 +31063,7 @@ installImportMetaCssBuild(import.meta);const css$f = /* css */`
|
|
|
31046
31063
|
min-width: var(--anchor-width, 0px);
|
|
31047
31064
|
max-width: 95vw;
|
|
31048
31065
|
/* max-height covers the placeholder + list; the list scrolls internally */
|
|
31049
|
-
max-height: 95dvh;
|
|
31066
|
+
max-height: var(--space-available, 95dvh);
|
|
31050
31067
|
margin: 0;
|
|
31051
31068
|
padding: 0;
|
|
31052
31069
|
background: var(--select-background-color);
|
|
@@ -31071,7 +31088,7 @@ installImportMetaCssBuild(import.meta);const css$f = /* css */`
|
|
|
31071
31088
|
/* To make clone same height as original we need to force it because context can impact height */
|
|
31072
31089
|
/* Like siblings with a bigger height in a flex container */
|
|
31073
31090
|
/* We subtract the border sizes as anchor-height includes borders in the dimensions */
|
|
31074
|
-
min-height:
|
|
31091
|
+
min-height: var(--anchor-inner-height);
|
|
31075
31092
|
/* Mirror the trigger's padding so the clone looks identical */
|
|
31076
31093
|
padding-top: var(--x-select-padding-top);
|
|
31077
31094
|
padding-right: var(--x-select-padding-right);
|
|
@@ -31079,7 +31096,6 @@ installImportMetaCssBuild(import.meta);const css$f = /* css */`
|
|
|
31079
31096
|
padding-left: var(--x-select-padding-left);
|
|
31080
31097
|
flex-shrink: 0;
|
|
31081
31098
|
flex-direction: column;
|
|
31082
|
-
align-items: center;
|
|
31083
31099
|
justify-content: center;
|
|
31084
31100
|
gap: var(--navi-s);
|
|
31085
31101
|
order: -1; /* before the list — popover is below the trigger */
|
|
@@ -31122,6 +31138,13 @@ installImportMetaCssBuild(import.meta);const css$f = /* css */`
|
|
|
31122
31138
|
}
|
|
31123
31139
|
|
|
31124
31140
|
&[aria-expanded="true"] {
|
|
31141
|
+
&[navi-popover-mode="overlay"],
|
|
31142
|
+
&[navi-popover-mode="attached"] {
|
|
31143
|
+
/* When sizes uses float AND the border uses border-radius it's possible it's possible to see some pixels
|
|
31144
|
+
of the underlying select borders. We hide them to ensure this cannot happen. */
|
|
31145
|
+
border-color: transparent;
|
|
31146
|
+
}
|
|
31147
|
+
|
|
31125
31148
|
.navi_select_popover {
|
|
31126
31149
|
display: flex;
|
|
31127
31150
|
flex-direction: column;
|
|
@@ -31385,7 +31408,7 @@ const SelectTrigger = () => {
|
|
|
31385
31408
|
});
|
|
31386
31409
|
};
|
|
31387
31410
|
|
|
31388
|
-
// SelectWithPopover — trigger + popover anchored
|
|
31411
|
+
// SelectWithPopover — trigger + popover anchored relative to the trigger.
|
|
31389
31412
|
const SelectWithPopover = props => {
|
|
31390
31413
|
const {
|
|
31391
31414
|
ref,
|
|
@@ -31395,6 +31418,9 @@ const SelectWithPopover = props => {
|
|
|
31395
31418
|
pointerTrap,
|
|
31396
31419
|
scrollTrap = true,
|
|
31397
31420
|
focusTrap = true,
|
|
31421
|
+
popoverMode = "nearby",
|
|
31422
|
+
popoverSpacing = popoverMode === "nearby" ? 5 : 0,
|
|
31423
|
+
viewportSpacing = 10,
|
|
31398
31424
|
...rest
|
|
31399
31425
|
} = props;
|
|
31400
31426
|
const debugFocus = useDebugFocus();
|
|
@@ -31433,8 +31459,13 @@ const SelectWithPopover = props => {
|
|
|
31433
31459
|
});
|
|
31434
31460
|
};
|
|
31435
31461
|
const moveFocusToSelect = e => {
|
|
31462
|
+
if (e.type === "mousedown") {
|
|
31463
|
+
e.preventDefault();
|
|
31464
|
+
debugFocus(formatEventSideEffect(e, `preventDefault and move focus to select`));
|
|
31465
|
+
} else {
|
|
31466
|
+
debugFocus(formatEventSideEffect(e, `move focus to select`));
|
|
31467
|
+
}
|
|
31436
31468
|
const select = ref.current;
|
|
31437
|
-
debugFocus(`moveFocusToSelect("${e.type}")`);
|
|
31438
31469
|
select.focus({
|
|
31439
31470
|
preventScroll: true
|
|
31440
31471
|
});
|
|
@@ -31444,6 +31475,7 @@ const SelectWithPopover = props => {
|
|
|
31444
31475
|
"aria-haspopup": "listbox",
|
|
31445
31476
|
"aria-expanded": expanded,
|
|
31446
31477
|
"aria-controls": popoverId,
|
|
31478
|
+
"navi-popover-mode": popoverMode,
|
|
31447
31479
|
onMouseDown: e => {
|
|
31448
31480
|
if (e.button !== 0) {
|
|
31449
31481
|
return;
|
|
@@ -31549,11 +31581,13 @@ const SelectWithPopover = props => {
|
|
|
31549
31581
|
}
|
|
31550
31582
|
},
|
|
31551
31583
|
positionX: "left-aligned",
|
|
31552
|
-
positionY: "below-overlap",
|
|
31584
|
+
positionY: popoverMode === "nearby" ? "below" : "below-overlap",
|
|
31585
|
+
spacing: popoverSpacing,
|
|
31586
|
+
viewportSpacing: viewportSpacing,
|
|
31553
31587
|
scrollTrap: scrollTrap,
|
|
31554
31588
|
pointerTrap: pointerTrap,
|
|
31555
31589
|
focusTrap: focusTrap,
|
|
31556
|
-
children: [jsx("div", {
|
|
31590
|
+
children: [popoverMode === "attached" ? jsx("div", {
|
|
31557
31591
|
className: "navi_select_anchor_clone",
|
|
31558
31592
|
onMouseDown: e => {
|
|
31559
31593
|
if (e.button !== 0) {
|
|
@@ -31562,7 +31596,7 @@ const SelectWithPopover = props => {
|
|
|
31562
31596
|
requestClose(e);
|
|
31563
31597
|
},
|
|
31564
31598
|
children: props.trigger
|
|
31565
|
-
}), jsx(SelectRequestCloseContext.Provider, {
|
|
31599
|
+
}) : null, jsx(SelectRequestCloseContext.Provider, {
|
|
31566
31600
|
value: requestClose,
|
|
31567
31601
|
children: children
|
|
31568
31602
|
})]
|
|
@@ -31611,8 +31645,14 @@ const SelectWithDialog = props => {
|
|
|
31611
31645
|
});
|
|
31612
31646
|
};
|
|
31613
31647
|
const moveFocusToSelect = e => {
|
|
31614
|
-
|
|
31615
|
-
|
|
31648
|
+
if (e.type === "mousedown") {
|
|
31649
|
+
e.preventDefault();
|
|
31650
|
+
debugFocus(formatEventSideEffect(e, `preventDefault and move focus to select`));
|
|
31651
|
+
} else {
|
|
31652
|
+
debugFocus(formatEventSideEffect(e, `move focus to select`));
|
|
31653
|
+
}
|
|
31654
|
+
const select = ref.current;
|
|
31655
|
+
select.focus({
|
|
31616
31656
|
preventScroll: true
|
|
31617
31657
|
});
|
|
31618
31658
|
};
|
|
@@ -31830,9 +31870,11 @@ const applySearch = (searchText, value) => {
|
|
|
31830
31870
|
|
|
31831
31871
|
// Multi-word OR: split on whitespace, any word matching contributes to the score.
|
|
31832
31872
|
// Items where all words match rank higher than partial matches.
|
|
31833
|
-
|
|
31834
|
-
|
|
31835
|
-
|
|
31873
|
+
// Note: words always has at least 1 element here (searchText is non-empty and
|
|
31874
|
+
// foldedSearch.split filters empty strings). This path also handles the case
|
|
31875
|
+
// where searchText has trailing/leading spaces: the phrase match above tries
|
|
31876
|
+
// the literal (e.g. "tc " in "tc adapter"), and if that fails we fall through
|
|
31877
|
+
// here to try each word individually (e.g. "tc" matches "tca").
|
|
31836
31878
|
const matchRanges = [];
|
|
31837
31879
|
let matchedWordCount = 0;
|
|
31838
31880
|
let anyWordAtStart = false;
|
|
@@ -31865,7 +31907,7 @@ const applySearch = (searchText, value) => {
|
|
|
31865
31907
|
}
|
|
31866
31908
|
}
|
|
31867
31909
|
if (matchedWordCount === 0) {
|
|
31868
|
-
return
|
|
31910
|
+
return tryAcronymMatch(foldedStr, str, searchText);
|
|
31869
31911
|
}
|
|
31870
31912
|
const wordRatio = matchedWordCount / words.length;
|
|
31871
31913
|
let baseScore;
|
|
@@ -31907,8 +31949,53 @@ const SCORE_PHRASE_AT_START = 1;
|
|
|
31907
31949
|
const SCORE_MULTI_WORD_AT_START = 0.75;
|
|
31908
31950
|
const SCORE_AT_WORD_BOUNDARY = 0.625;
|
|
31909
31951
|
const SCORE_MID_WORD = 0.5;
|
|
31952
|
+
const SCORE_ACRONYM = 0.4;
|
|
31910
31953
|
const SCORE_BONUS_CASE_EXACT = 0.125;
|
|
31911
31954
|
|
|
31955
|
+
// Acronym match: each char of searchText (spaces stripped) must be the first
|
|
31956
|
+
// letter of a word in value, in order (greedy subsequence on word-starts).
|
|
31957
|
+
// e.g. "TC" matches "Total Count" highlighting the T and C.
|
|
31958
|
+
const tryAcronymMatch = (foldedStr, str, searchText) => {
|
|
31959
|
+
const acronymChars = foldAccents(searchText).toLowerCase().replace(/\s/g, "");
|
|
31960
|
+
if (acronymChars.length < 2) {
|
|
31961
|
+
// Single-char acronym is too ambiguous — skip.
|
|
31962
|
+
return { match: false, matchScore: 0, matchRanges: [] };
|
|
31963
|
+
}
|
|
31964
|
+
const wordStarts = [];
|
|
31965
|
+
for (let i = 0; i < foldedStr.length; i++) {
|
|
31966
|
+
if (isWordBoundary(foldedStr, i)) {
|
|
31967
|
+
wordStarts.push(i);
|
|
31968
|
+
}
|
|
31969
|
+
}
|
|
31970
|
+
const matchedPositions = [];
|
|
31971
|
+
let wordIdx = 0;
|
|
31972
|
+
const originalAcronym = searchText.replace(/\s/g, "");
|
|
31973
|
+
for (let si = 0; si < acronymChars.length; si++) {
|
|
31974
|
+
const ch = acronymChars[si];
|
|
31975
|
+
let found = false;
|
|
31976
|
+
while (wordIdx < wordStarts.length) {
|
|
31977
|
+
const pos = wordStarts[wordIdx];
|
|
31978
|
+
wordIdx++;
|
|
31979
|
+
if (foldedStr[pos] === ch) {
|
|
31980
|
+
matchedPositions.push(pos);
|
|
31981
|
+
found = true;
|
|
31982
|
+
break;
|
|
31983
|
+
}
|
|
31984
|
+
}
|
|
31985
|
+
if (!found) {
|
|
31986
|
+
return { match: false, matchScore: 0, matchRanges: [] };
|
|
31987
|
+
}
|
|
31988
|
+
}
|
|
31989
|
+
const atStart = matchedPositions[0] === 0;
|
|
31990
|
+
const caseExact = matchedPositions.every(
|
|
31991
|
+
(p, i) => str[p] === originalAcronym[i],
|
|
31992
|
+
);
|
|
31993
|
+
const baseScore = atStart ? SCORE_ACRONYM + 0.05 : SCORE_ACRONYM;
|
|
31994
|
+
const matchScore = baseScore + (caseExact ? SCORE_BONUS_CASE_EXACT : 0);
|
|
31995
|
+
const matchRanges = matchedPositions.map((p) => [p, p + 1]);
|
|
31996
|
+
return { match: true, matchScore, matchRanges };
|
|
31997
|
+
};
|
|
31998
|
+
|
|
31912
31999
|
// LRU cache for pre-computed search info, avoids recomputing foldAccents/toLowerCase
|
|
31913
32000
|
// for the same searchText across all items in a list render.
|
|
31914
32001
|
const searchCache = new Map();
|