pixelize-design-library 2.4.2-beta.63 → 2.4.2-beta.65

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +130 -8
  2. package/COMPONENT-GOTCHAS.md +63 -7
  3. package/DESIGN-GUIDE.md +8 -3
  4. package/dist/Components/Button/Button.styles.js +7 -2
  5. package/dist/Components/CopyButton/CopyButton.d.ts +3 -1
  6. package/dist/Components/CopyButton/CopyButton.js +2 -2
  7. package/dist/Components/Drawer/Drawer.d.ts +1 -1
  8. package/dist/Components/Drawer/Drawer.js +6 -2
  9. package/dist/Components/Drawer/DrawerProps.d.ts +4 -0
  10. package/dist/Components/Dropdown/DropDown.js +2 -9
  11. package/dist/Components/FilePreview/FilePreview.js +2 -1
  12. package/dist/Components/FileUploader/FileUploader.js +4 -0
  13. package/dist/Components/FileUploader/FileUploaderProps.d.ts +1 -1
  14. package/dist/Components/FormDrawer/FormDrawer.d.ts +1 -1
  15. package/dist/Components/FormDrawer/FormDrawer.js +3 -3
  16. package/dist/Components/FormDrawer/FormDrawerProps.d.ts +2 -0
  17. package/dist/Components/Input/TextInput.styles.js +10 -6
  18. package/dist/Components/KanbanBoard/KanbanBoard.js +6 -5
  19. package/dist/Components/Modal/dialogNaming.d.ts +8 -0
  20. package/dist/Components/Modal/dialogNaming.js +21 -0
  21. package/dist/Components/NavLauncher/LauncherTabs.js +2 -2
  22. package/dist/Components/NavLauncher/NavLauncher.d.ts +1 -1
  23. package/dist/Components/NavLauncher/NavLauncher.js +257 -47
  24. package/dist/Components/NavLauncher/NavLauncherProps.d.ts +79 -3
  25. package/dist/Components/NavLauncher/ScopeBar.d.ts +14 -0
  26. package/dist/Components/NavLauncher/ScopeBar.js +137 -0
  27. package/dist/Components/NavLauncher/engine/distance.d.ts +4 -0
  28. package/dist/Components/NavLauncher/engine/distance.js +41 -0
  29. package/dist/Components/NavLauncher/engine/engine.d.ts +84 -0
  30. package/dist/Components/NavLauncher/engine/engine.js +471 -0
  31. package/dist/Components/NavLauncher/engine/fold.d.ts +15 -0
  32. package/dist/Components/NavLauncher/engine/fold.js +83 -0
  33. package/dist/Components/NavLauncher/engine/index.d.ts +21 -0
  34. package/dist/Components/NavLauncher/engine/index.js +17 -0
  35. package/dist/Components/NavLauncher/launcherParts.d.ts +2 -0
  36. package/dist/Components/NavLauncher/launcherParts.js +11 -7
  37. package/dist/Components/NavLauncher/match.d.ts +13 -2
  38. package/dist/Components/NavLauncher/match.js +26 -12
  39. package/dist/Components/NavLauncher/searchLayout.d.ts +60 -0
  40. package/dist/Components/NavLauncher/searchLayout.js +108 -0
  41. package/dist/Components/NavLauncher/spotlight.d.ts +39 -0
  42. package/dist/Components/NavLauncher/spotlight.js +202 -0
  43. package/dist/Components/NavLauncher/spotlightParts.d.ts +49 -0
  44. package/dist/Components/NavLauncher/spotlightParts.js +108 -0
  45. package/dist/Components/NavigationBar/NavigationBar.js +2 -1
  46. package/dist/Components/OrgSwitcher/OrgSwitcher.js +2 -4
  47. package/dist/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +6 -5
  48. package/dist/Components/ScrollToTop/ScrollToTop.js +4 -3
  49. package/dist/Components/SearchSelect/SearchSelect.js +4 -16
  50. package/dist/Components/Select/SelectPopup.js +1 -3
  51. package/dist/Components/Select/optionRowInk.d.ts +12 -0
  52. package/dist/Components/Select/optionRowInk.js +10 -1
  53. package/dist/Components/SelectSearch/SelectSearch.js +1 -10
  54. package/dist/Components/Table/Table.js +3 -2
  55. package/dist/Components/Table/TableProps.d.ts +1 -1
  56. package/dist/Components/Table/components/TableBody.js +9 -1
  57. package/dist/Components/Table/components/TableHeader.js +9 -2
  58. package/dist/Components/Table/components/TableLoading.d.ts +4 -1
  59. package/dist/Components/Table/components/TableLoading.js +13 -6
  60. package/dist/Components/Table/filters/CompactSelect.js +1 -10
  61. package/dist/Components/Table/variants/TableVariantContext.d.ts +1 -1
  62. package/dist/Components/Table/variants/TableVariantContext.js +3 -3
  63. package/dist/Components/WizardDialog/WizardDialog.d.ts +1 -1
  64. package/dist/Components/WizardDialog/WizardDialog.js +15 -3
  65. package/dist/Components/WizardDialog/WizardDialogProps.d.ts +5 -3
  66. package/dist/Components/WorkspaceWindow/WorkspaceTrayPreview.js +4 -0
  67. package/dist/Components/WorkspaceWindow/WorkspaceWindow.d.ts +2 -2
  68. package/dist/Components/WorkspaceWindow/WorkspaceWindow.js +26 -18
  69. package/dist/Components/WorkspaceWindow/WorkspaceWindowProps.d.ts +38 -8
  70. package/dist/Theme/chakra/Menu.styles.d.ts +8 -0
  71. package/dist/Theme/chakra/Menu.styles.js +7 -0
  72. package/dist/Theme/chakra/componentStyles.d.ts +8 -0
  73. package/dist/Theme/chakra/componentStyles.js +2 -2
  74. package/dist/Theme/chakra/ringShadow.d.ts +3 -0
  75. package/dist/Theme/chakra/ringShadow.js +7 -0
  76. package/dist/Theme/tokens/builders/accentText.d.ts +2 -0
  77. package/dist/Theme/tokens/builders/accentText.js +7 -1
  78. package/dist/Theme/tokens/builders/buildDarkPalette.d.ts +1 -1
  79. package/dist/Theme/tokens/builders/buildDarkPalette.js +5 -5
  80. package/dist/Theme/tokens/builders/filledSurface.d.ts +5 -1
  81. package/dist/Theme/tokens/builders/filledSurface.js +17 -4
  82. package/dist/Theme/tokens/builders/planeStep.d.ts +1 -0
  83. package/dist/Theme/tokens/builders/planeStep.js +5 -4
  84. package/dist/Utils/table.js +2 -2
  85. package/dist/esm/Components/Button/Button.styles.js +8 -3
  86. package/dist/esm/Components/CopyButton/CopyButton.js +2 -2
  87. package/dist/esm/Components/Drawer/Drawer.js +6 -2
  88. package/dist/esm/Components/Dropdown/DropDown.js +2 -9
  89. package/dist/esm/Components/FilePreview/FilePreview.js +3 -2
  90. package/dist/esm/Components/FileUploader/FileUploader.js +4 -0
  91. package/dist/esm/Components/FormDrawer/FormDrawer.js +3 -3
  92. package/dist/esm/Components/Input/TextInput.styles.js +10 -6
  93. package/dist/esm/Components/KanbanBoard/KanbanBoard.js +6 -5
  94. package/dist/esm/Components/Modal/dialogNaming.js +18 -0
  95. package/dist/esm/Components/NavLauncher/LauncherTabs.js +2 -2
  96. package/dist/esm/Components/NavLauncher/NavLauncher.js +259 -49
  97. package/dist/esm/Components/NavLauncher/ScopeBar.js +134 -0
  98. package/dist/esm/Components/NavLauncher/engine/distance.js +37 -0
  99. package/dist/esm/Components/NavLauncher/engine/engine.js +458 -0
  100. package/dist/esm/Components/NavLauncher/engine/fold.js +74 -0
  101. package/dist/esm/Components/NavLauncher/engine/index.js +14 -0
  102. package/dist/esm/Components/NavLauncher/launcherParts.js +10 -6
  103. package/dist/esm/Components/NavLauncher/match.js +25 -12
  104. package/dist/esm/Components/NavLauncher/searchLayout.js +100 -0
  105. package/dist/esm/Components/NavLauncher/spotlight.js +194 -0
  106. package/dist/esm/Components/NavLauncher/spotlightParts.js +95 -0
  107. package/dist/esm/Components/NavigationBar/NavigationBar.js +2 -1
  108. package/dist/esm/Components/OrgSwitcher/OrgSwitcher.js +2 -4
  109. package/dist/esm/Components/ProfilePhotoViewer/ProfilePhotoViewer.js +7 -6
  110. package/dist/esm/Components/ScrollToTop/ScrollToTop.js +5 -4
  111. package/dist/esm/Components/SearchSelect/SearchSelect.js +6 -18
  112. package/dist/esm/Components/Select/SelectPopup.js +2 -4
  113. package/dist/esm/Components/Select/optionRowInk.js +7 -0
  114. package/dist/esm/Components/SelectSearch/SelectSearch.js +2 -11
  115. package/dist/esm/Components/Table/Table.js +4 -3
  116. package/dist/esm/Components/Table/components/TableBody.js +10 -2
  117. package/dist/esm/Components/Table/components/TableHeader.js +10 -3
  118. package/dist/esm/Components/Table/components/TableLoading.js +10 -6
  119. package/dist/esm/Components/Table/filters/CompactSelect.js +2 -11
  120. package/dist/esm/Components/Table/variants/TableVariantContext.js +3 -3
  121. package/dist/esm/Components/WizardDialog/WizardDialog.js +15 -3
  122. package/dist/esm/Components/WorkspaceWindow/WorkspaceTrayPreview.js +4 -0
  123. package/dist/esm/Components/WorkspaceWindow/WorkspaceWindow.js +28 -20
  124. package/dist/esm/Theme/chakra/Menu.styles.js +7 -0
  125. package/dist/esm/Theme/chakra/componentStyles.js +2 -2
  126. package/dist/esm/Theme/chakra/ringShadow.js +3 -0
  127. package/dist/esm/Theme/tokens/builders/accentText.js +5 -0
  128. package/dist/esm/Theme/tokens/builders/buildDarkPalette.js +5 -5
  129. package/dist/esm/Theme/tokens/builders/filledSurface.js +18 -5
  130. package/dist/esm/Theme/tokens/builders/planeStep.js +3 -3
  131. package/dist/esm/Utils/table.js +2 -2
  132. package/dist/esm/index.js +6 -5
  133. package/dist/index.d.ts +10 -8
  134. package/dist/index.js +14 -6
  135. package/package.json +1 -1
@@ -0,0 +1,134 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Flex } from "@chakra-ui/react";
3
+ import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { useCallback, useEffect, useRef, useState, } from "react";
5
+ import { focusVisibleStyles } from "../../Theme/chakra/focusRing.styles.js";
6
+ import { reducedMotionSx, usePrefersReducedMotion, } from "../../Theme/chakra/motion.js";
7
+ import { AA_NON_TEXT, liftToContrast, } from "../../Theme/tokens/builders/outlineRung.js";
8
+ import { listRowBackgrounds } from "../../Theme/tokens/builders/rowStates.js";
9
+ import { transitionFor } from "../../Theme/tokens/primitives/motion.js";
10
+ import { useCustomTheme } from "../../Theme/useCustomTheme.js";
11
+ const EDGE = 36;
12
+ const MAX_COUNT = 99;
13
+ const STEPS = {
14
+ ArrowRight: 1,
15
+ ArrowDown: 1,
16
+ ArrowLeft: -1,
17
+ ArrowUp: -1,
18
+ };
19
+ const HIDDEN_SCROLLBAR = {
20
+ scrollbarWidth: "none",
21
+ "&::-webkit-scrollbar": { display: "none" },
22
+ };
23
+ const NO_EDGES = { start: false, end: false };
24
+ const fadeMask = ({ start, end }) => start || end
25
+ ? `linear-gradient(to right, ${start ? "transparent" : "black"}, black ${EDGE}px, black calc(100% - ${EDGE}px), ${end ? "transparent" : "black"})`
26
+ : "none";
27
+ function PageArrow({ side, onPage, }) {
28
+ const { colors } = useCustomTheme();
29
+ const Icon = side === "start" ? ChevronLeft : ChevronRight;
30
+ return (_jsx(Box, { as: "button", type: "button", tabIndex: -1, "aria-hidden": true, "data-testid": `launcher-scopes-${side}`, onMouseDown: (event) => event.preventDefault(), onClick: onPage, position: "absolute", top: "50%", left: side === "start" ? 0 : undefined, right: side === "end" ? 0 : undefined, transform: "translateY(-50%)", display: "inline-flex", alignItems: "center", justifyContent: "center", boxSize: "28px", borderRadius: "full", borderWidth: "1px", borderColor: colors.boxborder[200], bg: colors.surfaceRaised, boxShadow: `0 1px 3px ${colors.boxShadow.default}`, color: colors.textMuted, cursor: "pointer", _hover: { color: colors.text[900] }, sx: { "@media (hover: none)": { display: "none" } }, children: _jsx(Icon, { size: 16, "aria-hidden": true }) }));
31
+ }
32
+ export function ScopeBar({ scopes, value, onChange, label, countLabel, }) {
33
+ const { colors } = useCustomTheme();
34
+ const stripRef = useRef(null);
35
+ const chipRefs = useRef([]);
36
+ const [edges, setEdges] = useState(NO_EDGES);
37
+ const reduced = usePrefersReducedMotion();
38
+ const selected = Math.max(0, scopes.findIndex((scope) => scope.id === value));
39
+ const layoutKey = scopes.map((s) => { var _a; return `${s.id}:${(_a = s.count) !== null && _a !== void 0 ? _a : ""}`; }).join("|");
40
+ const edge = liftToContrast(colors.primary[500], colors.surfaceRaised, AA_NON_TEXT);
41
+ const measure = useCallback(() => {
42
+ const strip = stripRef.current;
43
+ if (!strip)
44
+ return;
45
+ const start = strip.scrollLeft > 1;
46
+ const end = strip.scrollLeft + strip.clientWidth < strip.scrollWidth - 1;
47
+ setEdges((prev) => prev.start === start && prev.end === end ? prev : { start, end });
48
+ }, []);
49
+ useEffect(() => {
50
+ const strip = stripRef.current;
51
+ if (!strip || typeof ResizeObserver === "undefined")
52
+ return undefined;
53
+ const observer = new ResizeObserver(measure);
54
+ observer.observe(strip);
55
+ if (strip.firstElementChild)
56
+ observer.observe(strip.firstElementChild);
57
+ return () => observer.disconnect();
58
+ }, [measure]);
59
+ useEffect(() => {
60
+ const strip = stripRef.current;
61
+ const chip = chipRefs.current[selected];
62
+ if (strip && chip) {
63
+ const from = chip.offsetLeft - EDGE;
64
+ const to = chip.offsetLeft + chip.offsetWidth + EDGE - strip.clientWidth;
65
+ if (from < strip.scrollLeft)
66
+ strip.scrollLeft = Math.max(0, from);
67
+ else if (to > strip.scrollLeft)
68
+ strip.scrollLeft = to;
69
+ }
70
+ measure();
71
+ }, [selected, layoutKey, measure]);
72
+ const onKeyDown = (event) => {
73
+ var _a;
74
+ if (event.altKey || event.ctrlKey || event.metaKey)
75
+ return;
76
+ const step = STEPS[event.key];
77
+ const last = scopes.length - 1;
78
+ const next = step !== undefined
79
+ ? (selected + step + scopes.length) % scopes.length
80
+ : event.key === "Home"
81
+ ? 0
82
+ : event.key === "End"
83
+ ? last
84
+ : -1;
85
+ if (next < 0)
86
+ return;
87
+ event.preventDefault();
88
+ (_a = chipRefs.current[next]) === null || _a === void 0 ? void 0 : _a.focus();
89
+ onChange(scopes[next].id);
90
+ };
91
+ const onWheel = (event) => {
92
+ const strip = event.currentTarget;
93
+ if (Math.abs(event.deltaY) <= Math.abs(event.deltaX))
94
+ return;
95
+ if (strip.scrollWidth > strip.clientWidth)
96
+ strip.scrollLeft += event.deltaY;
97
+ };
98
+ const page = (direction) => {
99
+ const strip = stripRef.current;
100
+ if (!strip)
101
+ return;
102
+ const step = Math.max(strip.clientWidth - EDGE * 2, EDGE);
103
+ const max = Math.max(0, strip.scrollWidth - strip.clientWidth);
104
+ const left = Math.min(max, Math.max(0, strip.scrollLeft + direction * step));
105
+ if (reduced || typeof strip.scrollTo !== "function")
106
+ strip.scrollLeft = left;
107
+ else
108
+ strip.scrollTo({ left, behavior: "smooth" });
109
+ };
110
+ const mask = fadeMask(edges);
111
+ return (_jsxs(Box, { position: "relative", minW: 0, flexShrink: 1, children: [_jsx(Box, { ref: stripRef, "data-testid": "launcher-scopes", overflowX: "auto", overflowY: "hidden", onScroll: measure, onWheel: onWheel, sx: { ...HIDDEN_SCROLLBAR, maskImage: mask, WebkitMaskImage: mask }, children: _jsx(Flex, { role: "radiogroup", "aria-label": label, onKeyDown: onKeyDown, gap: "6px", w: "max-content", p: "5px", children: scopes.map((scope, index) => {
112
+ var _a, _b;
113
+ const isSelected = index === selected;
114
+ const hasCount = scope.count !== undefined;
115
+ return (_jsxs(Box, { as: "button", type: "button", ref: (node) => {
116
+ chipRefs.current[index] = node;
117
+ }, role: "radio", "aria-checked": isSelected, "aria-label": hasCount
118
+ ? countLabel(scope.label, (_a = scope.count) !== null && _a !== void 0 ? _a : 0)
119
+ : undefined, tabIndex: isSelected ? 0 : -1, onClick: () => onChange(scope.id), display: "inline-flex", alignItems: "center", gap: "6px", flexShrink: 0, h: "32px", pl: "12px", pr: hasCount ? "6px" : "12px", borderRadius: "full", borderWidth: "1px", borderColor: isSelected ? edge : colors.boxborder[200], bg: isSelected ? colors.primary[50] : colors.surfaceRaised, color: isSelected ? colors.accentText : colors.textMuted, fontSize: "xs-plus", fontWeight: isSelected ? 600 : 500, whiteSpace: "nowrap", cursor: "pointer", transition: transitionFor([
120
+ "background-color",
121
+ "border-color",
122
+ "color",
123
+ ]), _hover: isSelected
124
+ ? undefined
125
+ : {
126
+ bg: listRowBackgrounds(colors).active,
127
+ color: colors.text[900],
128
+ }, sx: { ...focusVisibleStyles(colors), ...reducedMotionSx() }, children: [scope.label, hasCount ? (_jsx(Box, { as: "span", minW: "20px", h: "20px", px: "6px", borderRadius: "full", bg: isSelected
129
+ ? colors.surfaceRaised
130
+ : colors.backgroundColor.subtle, fontSize: "11.5px", fontWeight: 600, display: "inline-flex", alignItems: "center", justifyContent: "center", sx: { fontVariantNumeric: "tabular-nums" }, children: ((_b = scope.count) !== null && _b !== void 0 ? _b : 0) > MAX_COUNT
131
+ ? `${MAX_COUNT}+`
132
+ : scope.count })) : null] }, scope.id));
133
+ }) }) }), edges.start ? _jsx(PageArrow, { side: "start", onPage: () => page(-1) }) : null, edges.end ? _jsx(PageArrow, { side: "end", onPage: () => page(1) }) : null] }));
134
+ }
@@ -0,0 +1,37 @@
1
+ /** Damerau-Levenshtein (optimal string alignment) distance; returns `max + 1` once it exceeds `max`. */
2
+ export const editDistance = (a, b, max) => {
3
+ if (a === b)
4
+ return 0;
5
+ const la = a.length;
6
+ const lb = b.length;
7
+ if (Math.abs(la - lb) > max)
8
+ return max + 1;
9
+ if (la === 0 || lb === 0)
10
+ return Math.max(la, lb);
11
+ let before = new Int32Array(lb + 1);
12
+ let prev = new Int32Array(lb + 1);
13
+ let cur = new Int32Array(lb + 1);
14
+ for (let j = 0; j <= lb; j += 1)
15
+ prev[j] = j;
16
+ for (let i = 1; i <= la; i += 1) {
17
+ cur[0] = i;
18
+ let rowMin = i;
19
+ for (let j = 1; j <= lb; j += 1) {
20
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
21
+ let value = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
22
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
23
+ value = Math.min(value, before[j - 2] + 1);
24
+ }
25
+ cur[j] = value;
26
+ if (value < rowMin)
27
+ rowMin = value;
28
+ }
29
+ if (rowMin > max)
30
+ return max + 1;
31
+ const spare = before;
32
+ before = prev;
33
+ prev = cur;
34
+ cur = spare;
35
+ }
36
+ return Math.min(prev[lb], max + 1);
37
+ };
@@ -0,0 +1,458 @@
1
+ import { editDistance } from "./distance.js";
2
+ import { foldText, foldWithMap, hasMarks, letters, splitWords, tokenize, } from "./fold.js";
3
+ const KIND_SCORE = {
4
+ exact: 100,
5
+ prefix: 88,
6
+ word: 78,
7
+ "word-prefix": 68,
8
+ initialism: 60,
9
+ substring: 46,
10
+ fuzzy: 34,
11
+ subsequence: 16,
12
+ };
13
+ const COMPLETENESS = 6;
14
+ const SYNONYM_FACTOR = 0.92;
15
+ const STEM_PENALTY = 1;
16
+ const MAX_ALTERNATIVES = 12;
17
+ const UNBOUNDED_SLACK = 32;
18
+ const TAMIL_FINAL_MA = "ம்";
19
+ const WORD_RE = /[\p{L}\p{N}\p{M}]+/gu;
20
+ const charBit = (code) => {
21
+ if (code >= 97 && code <= 122)
22
+ return 1 << (code - 97);
23
+ return code >= 48 && code <= 57 ? 1 << 26 : 1 << (27 + (code % 4));
24
+ };
25
+ const maskOf = (text) => {
26
+ let mask = 0;
27
+ for (let i = 0; i < text.length; i += 1)
28
+ mask |= charBit(text.charCodeAt(i));
29
+ return mask;
30
+ };
31
+ const missingChars = (token, field) => {
32
+ let rest = token & ~field;
33
+ let count = 0;
34
+ for (; rest; count += 1)
35
+ rest &= rest - 1;
36
+ return count;
37
+ };
38
+ const maxEdits = (size) => (size >= 8 ? 2 : 1);
39
+ const writtenSize = (text, marked) => marked ? letters(text).length : text.length;
40
+ const indexField = ({ name, text, weight = 1, }) => {
41
+ var _a;
42
+ const folded = foldText(text).trim();
43
+ const words = [];
44
+ const starts = [];
45
+ for (const match of folded.matchAll(WORD_RE)) {
46
+ words.push(match[0]);
47
+ starts.push((_a = match.index) !== null && _a !== void 0 ? _a : 0);
48
+ }
49
+ const letterWords = hasMarks(folded) ? words.map(letters) : null;
50
+ return {
51
+ name,
52
+ text,
53
+ weight,
54
+ folded,
55
+ compact: words.join(""),
56
+ words,
57
+ starts,
58
+ initials: (letterWords !== null && letterWords !== void 0 ? letterWords : words).map((w) => w[0]).join(""),
59
+ mask: maskOf(folded),
60
+ letterWords,
61
+ };
62
+ };
63
+ export const createSearchIndex = (documents) => {
64
+ const indexed = documents.map((doc, index) => {
65
+ const fields = doc.fields.filter((f) => f.text).map(indexField);
66
+ return {
67
+ id: doc.id,
68
+ index,
69
+ fields,
70
+ mask: fields.reduce((mask, f) => mask | f.mask, 0),
71
+ };
72
+ });
73
+ return { size: indexed.length, documents: indexed };
74
+ };
75
+ const startsLikely = (token, word) => word[0][0] === token[0][0] || (word[0] === token[1] && word[1] === token[0]);
76
+ const fuzzyScore = (token, words) => {
77
+ const max = maxEdits(token.length);
78
+ let best = 0;
79
+ for (let w = 0; w < words.length; w += 1) {
80
+ const word = words[w];
81
+ if (!startsLikely(token, word))
82
+ continue;
83
+ if (Math.abs(word.length - token.length) <= max) {
84
+ const d = editDistance(token, word, max);
85
+ if (d <= max)
86
+ best = Math.max(best, KIND_SCORE.fuzzy - 8 * (d - 1));
87
+ }
88
+ if (token.length < 5 || best >= KIND_SCORE.fuzzy)
89
+ continue;
90
+ for (let len = token.length - 1; len <= token.length + 1; len += 1) {
91
+ if (len >= word.length)
92
+ break;
93
+ const d = editDistance(token, word.slice(0, len), max);
94
+ if (d <= max)
95
+ best = Math.max(best, KIND_SCORE.fuzzy - 4 - 8 * (d - 1));
96
+ }
97
+ }
98
+ return best;
99
+ };
100
+ const subsequenceOf = (need, have) => {
101
+ if (have.length <= need.length || have[0] !== need[0])
102
+ return null;
103
+ const positions = [0];
104
+ for (let i = 1, at = 1; i < need.length; i += 1) {
105
+ const next = have.indexOf(need[i], at);
106
+ if (next < 0)
107
+ return null;
108
+ positions.push(next);
109
+ at = next + 1;
110
+ }
111
+ return positions;
112
+ };
113
+ const offsetIn = (have, index) => typeof have === "string" ? index : have.slice(0, index).join("").length;
114
+ const subsequenceAt = (token, words, starts, marked) => {
115
+ const need = marked ? letters(token) : token;
116
+ for (let k = 0; k < words.length; k += 1) {
117
+ const have = marked ? letters(words[k]) : words[k];
118
+ const positions = subsequenceOf(need, have);
119
+ if (positions)
120
+ return positions.map((p) => starts[k] + offsetIn(have, p));
121
+ }
122
+ return null;
123
+ };
124
+ const fuzzyOnly = (token, field, floor) => {
125
+ var _a, _b;
126
+ if (KIND_SCORE.fuzzy < floor)
127
+ return null;
128
+ const score = fuzzyScore((_a = token.letters) !== null && _a !== void 0 ? _a : token.text, (_b = field.letterWords) !== null && _b !== void 0 ? _b : field.words);
129
+ return score > 0 ? { kind: "fuzzy", base: score } : null;
130
+ };
131
+ const matchToken = (tokenInfo, field, floor) => {
132
+ var _a, _b;
133
+ const { text: token, mask, edits, slack } = tokenInfo;
134
+ const missing = missingChars(mask, field.mask);
135
+ if (missing > 0)
136
+ return missing <= slack ? fuzzyOnly(tokenInfo, field, floor) : null;
137
+ const ratio = (COMPLETENESS * token.length) / Math.max(field.folded.length, token.length);
138
+ if (field.folded === token)
139
+ return { kind: "exact", base: KIND_SCORE.exact + COMPLETENESS };
140
+ if (field.folded.startsWith(token))
141
+ return { kind: "prefix", base: KIND_SCORE.prefix + ratio };
142
+ if (KIND_SCORE.word + COMPLETENESS < floor)
143
+ return null;
144
+ let isWordPrefix = false;
145
+ const { words } = field;
146
+ for (let w = 0; w < words.length; w += 1) {
147
+ if (words[w] === token)
148
+ return { kind: "word", base: KIND_SCORE.word + ratio };
149
+ if (words[w].startsWith(token))
150
+ isWordPrefix = true;
151
+ }
152
+ if (isWordPrefix)
153
+ return { kind: "word-prefix", base: KIND_SCORE["word-prefix"] + ratio };
154
+ if (token.length < 2 || KIND_SCORE.initialism < floor)
155
+ return null;
156
+ if (field.words.length >= 2 && field.initials.startsWith(token)) {
157
+ return { kind: "initialism", base: KIND_SCORE.initialism };
158
+ }
159
+ if (field.folded.includes(token) || field.compact.includes(token)) {
160
+ return { kind: "substring", base: KIND_SCORE.substring };
161
+ }
162
+ if (tokenInfo.exact)
163
+ return null;
164
+ if (edits > 0) {
165
+ const match = fuzzyOnly(tokenInfo, field, floor);
166
+ if (match)
167
+ return match;
168
+ }
169
+ if (KIND_SCORE.subsequence < floor)
170
+ return null;
171
+ const need = (_a = tokenInfo.letters) !== null && _a !== void 0 ? _a : token;
172
+ const lists = (_b = field.letterWords) !== null && _b !== void 0 ? _b : field.words;
173
+ return lists.some((have) => subsequenceOf(need, have))
174
+ ? { kind: "subsequence", base: KIND_SCORE.subsequence }
175
+ : null;
176
+ };
177
+ const reachable = (token, doc) => missingChars(token.mask, doc.mask) <= token.slack ||
178
+ (token.stem !== null && missingChars(token.stem.mask, doc.mask) === 0);
179
+ const scoreTokens = (doc, alt) => {
180
+ const { tokens } = alt;
181
+ const { fields } = doc;
182
+ for (let t = 0; t < tokens.length; t += 1) {
183
+ if (!reachable(tokens[t], doc))
184
+ return null;
185
+ }
186
+ let total = 0;
187
+ const matches = [];
188
+ for (let t = 0; t < tokens.length; t += 1) {
189
+ const token = tokens[t];
190
+ let best = 0;
191
+ let found = null;
192
+ for (let f = 0; f < fields.length; f += 1) {
193
+ const field = fields[f];
194
+ for (let form = token, penalty = 0; form; form = form.stem, penalty = STEM_PENALTY) {
195
+ const match = matchToken(form, field, best / field.weight + penalty);
196
+ if (!match)
197
+ continue;
198
+ const score = (match.base - penalty) * field.weight;
199
+ if (score > best) {
200
+ best = score;
201
+ found = {
202
+ token: token.text,
203
+ field: field.name,
204
+ text: field.text,
205
+ kind: match.kind,
206
+ };
207
+ }
208
+ }
209
+ }
210
+ if (!found)
211
+ return null;
212
+ total += best;
213
+ matches.push(found);
214
+ }
215
+ let score = total / tokens.length;
216
+ if (tokens.length > 1) {
217
+ const { phrase } = alt;
218
+ for (let f = 0; f < fields.length; f += 1) {
219
+ const field = fields[f];
220
+ if (field.weight < 1)
221
+ continue;
222
+ if (field.folded === phrase)
223
+ score += 20;
224
+ else if (field.folded.startsWith(phrase))
225
+ score += 10;
226
+ else if (field.folded.includes(phrase))
227
+ score += 5;
228
+ }
229
+ }
230
+ return { score, matches };
231
+ };
232
+ const compiled = new WeakMap();
233
+ const synonymGroups = (synonyms) => {
234
+ const cached = compiled.get(synonyms);
235
+ if (cached)
236
+ return cached;
237
+ const groups = Object.entries(synonyms).map(([term, phrases]) => [term, ...phrases].map(tokenize).filter((tokens) => tokens.length > 0));
238
+ compiled.set(synonyms, groups);
239
+ return groups;
240
+ };
241
+ const findRun = (tokens, run) => {
242
+ for (let at = 0; at + run.length <= tokens.length; at += 1) {
243
+ if (run.every((t, i) => tokens[at + i] === t))
244
+ return at;
245
+ }
246
+ return -1;
247
+ };
248
+ const alternatives = (tokens, synonyms) => {
249
+ const out = [{ tokens }];
250
+ if (!synonyms || tokens.length === 0)
251
+ return out;
252
+ const seen = new Set([tokens.join(" ")]);
253
+ for (const group of synonymGroups(synonyms)) {
254
+ for (const member of group) {
255
+ const at = findRun(tokens, member);
256
+ if (at < 0)
257
+ continue;
258
+ for (const other of group) {
259
+ if (other === member)
260
+ continue;
261
+ const next = [
262
+ ...tokens.slice(0, at),
263
+ ...other,
264
+ ...tokens.slice(at + member.length),
265
+ ];
266
+ const key = next.join(" ");
267
+ if (seen.has(key))
268
+ continue;
269
+ seen.add(key);
270
+ out.push({ tokens: Array.from(new Set(next)), via: other.join(" ") });
271
+ if (out.length >= MAX_ALTERNATIVES)
272
+ return out;
273
+ }
274
+ }
275
+ }
276
+ return out;
277
+ };
278
+ const stemOf = (text) => text.endsWith(TAMIL_FINAL_MA) && letters(text).length > 2
279
+ ? text.slice(0, -TAMIL_FINAL_MA.length)
280
+ : null;
281
+ const toToken = (text, fuzzy) => {
282
+ const written = hasMarks(text) ? letters(text) : null;
283
+ const size = written ? written.length : text.length;
284
+ const edits = fuzzy && size >= 4 ? maxEdits(size) : 0;
285
+ const stem = stemOf(text);
286
+ return {
287
+ text,
288
+ mask: maskOf(text),
289
+ edits,
290
+ slack: written && edits > 0 ? UNBOUNDED_SLACK : edits,
291
+ letters: written,
292
+ exact: false,
293
+ stem: stem === null
294
+ ? null
295
+ : {
296
+ text: stem,
297
+ mask: maskOf(stem),
298
+ edits: 0,
299
+ slack: 0,
300
+ letters: null,
301
+ exact: true,
302
+ stem: null,
303
+ },
304
+ };
305
+ };
306
+ const compileQuery = (tokens, options) => alternatives(tokens, options.synonyms).map(({ tokens: words, via }) => ({
307
+ tokens: words.map((text) => toToken(text, options.fuzzy !== false)),
308
+ phrase: words.join(" "),
309
+ via,
310
+ }));
311
+ export const searchIndex = (index, query, options = {}) => {
312
+ var _a, _b, _c;
313
+ const tokens = tokenize(query);
314
+ if (tokens.length === 0)
315
+ return [];
316
+ const alts = compileQuery(tokens, options);
317
+ const hits = [];
318
+ const { documents } = index;
319
+ for (let d = 0; d < documents.length; d += 1) {
320
+ const doc = documents[d];
321
+ let best = null;
322
+ for (let a = 0; a < alts.length; a += 1) {
323
+ const alt = alts[a];
324
+ const result = scoreTokens(doc, alt);
325
+ if (!result)
326
+ continue;
327
+ const score = alt.via ? result.score * SYNONYM_FACTOR : result.score;
328
+ if (!best || score > best.score) {
329
+ best = {
330
+ id: doc.id,
331
+ index: doc.index,
332
+ score,
333
+ matches: result.matches,
334
+ synonym: alt.via,
335
+ };
336
+ }
337
+ }
338
+ if (!best)
339
+ continue;
340
+ best.score += (_b = (_a = options.boosts) === null || _a === void 0 ? void 0 : _a[doc.id]) !== null && _b !== void 0 ? _b : 0;
341
+ hits.push(best);
342
+ }
343
+ hits.sort((a, b) => b.score - a.score || a.index - b.index);
344
+ const floor = hits.length ? hits[0].score * ((_c = options.relativeCutoff) !== null && _c !== void 0 ? _c : 0) : 0;
345
+ const kept = floor > 0 ? hits.filter((hit) => hit.score >= floor) : hits;
346
+ return options.limit === undefined ? kept : kept.slice(0, options.limit);
347
+ };
348
+ export const searchDocuments = (documents, query, options) => searchIndex(createSearchIndex(documents), query, options);
349
+ /** Compiles `query` once; every query word must hit some text of the row. */
350
+ export const createMatcher = (query, options = {}) => {
351
+ const tokens = tokenize(query);
352
+ if (tokens.length === 0)
353
+ return () => true;
354
+ const alts = compileQuery(tokens, options);
355
+ return (...texts) => {
356
+ const fields = texts
357
+ .filter((text) => Boolean(text))
358
+ .map((text) => indexField({ name: "text", text }));
359
+ const doc = {
360
+ id: "",
361
+ index: 0,
362
+ fields,
363
+ mask: fields.reduce((mask, f) => mask | f.mask, 0),
364
+ };
365
+ return alts.some((alt) => scoreTokens(doc, alt) !== null);
366
+ };
367
+ };
368
+ /** One-off form of `createMatcher`: does `query` match the row made of `texts`? */
369
+ export const matchesQuery = (query, ...texts) => createMatcher(query)(...texts);
370
+ const markEnd = (text, end) => {
371
+ let at = end;
372
+ while (at < text.length && /\p{M}/u.test(text[at]))
373
+ at += 1;
374
+ return at;
375
+ };
376
+ const tokenRanges = (token, folded, words, starts, fuzzy) => {
377
+ var _a;
378
+ const ranges = [];
379
+ if (token.length >= 2) {
380
+ for (let at = folded.indexOf(token); at !== -1; at = folded.indexOf(token, at + token.length)) {
381
+ ranges.push([at, at + token.length]);
382
+ }
383
+ }
384
+ else {
385
+ words.forEach((word, i) => {
386
+ if (word.startsWith(token))
387
+ ranges.push([starts[i], starts[i] + 1]);
388
+ });
389
+ }
390
+ if (ranges.length || token.length < 2)
391
+ return ranges;
392
+ const marked = hasMarks(token) || hasMarks(folded);
393
+ const firsts = words.map((w) => (marked ? letters(w)[0] : w[0]));
394
+ if (words.length >= 2 && firsts.join("").startsWith(token)) {
395
+ return Array.from(marked ? letters(token) : token, (_, i) => [starts[i], starts[i] + firsts[i].length]);
396
+ }
397
+ if (fuzzy && writtenSize(token, marked) >= 4) {
398
+ words.forEach((word, i) => {
399
+ if (fuzzyScore(marked ? letters(token) : token, [
400
+ marked ? letters(word) : word,
401
+ ]) > 0)
402
+ ranges.push([starts[i], starts[i] + word.length]);
403
+ });
404
+ if (ranges.length)
405
+ return ranges;
406
+ }
407
+ return ((_a = subsequenceAt(token, words, starts, marked)) !== null && _a !== void 0 ? _a : []).map((p) => [p, p + 1]);
408
+ };
409
+ /** Character ranges of `text` a query matched, in original-text offsets, merged and sorted. */
410
+ export const highlightRanges = (text, query, options = {}) => {
411
+ var _a;
412
+ const tokens = Array.from(new Set(alternatives(tokenize(query), options.synonyms)
413
+ .flatMap((a) => a.tokens)
414
+ .flatMap((token) => {
415
+ const stem = stemOf(token);
416
+ return stem ? [token, stem] : [token];
417
+ })));
418
+ if (tokens.length === 0 || !text)
419
+ return [];
420
+ const { folded, map } = foldWithMap(text);
421
+ const words = [];
422
+ const starts = [];
423
+ for (const match of folded.matchAll(WORD_RE)) {
424
+ words.push(match[0]);
425
+ starts.push((_a = match.index) !== null && _a !== void 0 ? _a : 0);
426
+ }
427
+ const ranges = tokens
428
+ .flatMap((token) => tokenRanges(token, folded, words, starts, options.fuzzy !== false))
429
+ .sort((a, b) => a[0] - b[0]);
430
+ const merged = [];
431
+ for (const [start, end] of ranges) {
432
+ const last = merged[merged.length - 1];
433
+ if (last && start <= last[1])
434
+ last[1] = Math.max(last[1], end);
435
+ else
436
+ merged.push([start, end]);
437
+ }
438
+ return merged.map(([start, end]) => {
439
+ var _a;
440
+ const lastChar = map[end - 1];
441
+ const width = ((_a = text.codePointAt(lastChar)) !== null && _a !== void 0 ? _a : 0) > 0xffff ? 2 : 1;
442
+ return [map[start], markEnd(text, lastChar + width)];
443
+ });
444
+ };
445
+ /** Additive ranking points from usage: pinned flat, frequent and recent decaying by position. */
446
+ export const usageBoosts = (usage) => {
447
+ const out = {};
448
+ const add = (ids, weight, decays) => ids === null || ids === void 0 ? void 0 : ids.forEach((id, i) => {
449
+ var _a;
450
+ const factor = decays ? Math.max(0.25, 1 - i / ids.length) : 1;
451
+ out[id] = ((_a = out[id]) !== null && _a !== void 0 ? _a : 0) + weight * factor;
452
+ });
453
+ add(usage.pinnedIds, 3, false);
454
+ add(usage.frequentIds, 4, true);
455
+ add(usage.recentIds, 2, true);
456
+ return out;
457
+ };
458
+ export { editDistance, foldText, splitWords, tokenize };