@rowsncolumns/ui 16.0.3 → 16.0.5
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/cjs/dialog/index.d.ts.map +1 -1
- package/dist/cjs/dialog/index.js +45 -3
- package/dist/cjs/editor-pm/mentions-plugin.d.ts +3 -3
- package/dist/cjs/editor-pm/mentions-plugin.d.ts.map +1 -1
- package/dist/cjs/editor-pm/mentions-plugin.js +11 -0
- package/dist/cjs/editor-pm/prosemirror-editor.d.ts +4 -4
- package/dist/cjs/editor-pm/prosemirror-editor.d.ts.map +1 -1
- package/dist/cjs/editor-pm/prosemirror-editor.js +95 -35
- package/dist/cjs/editor-pm/use-parameter-suggestions.d.ts.map +1 -1
- package/dist/cjs/editor-pm/use-parameter-suggestions.js +27 -9
- package/dist/cjs/hooks/use-dropdown.d.ts +12 -5
- package/dist/cjs/hooks/use-dropdown.d.ts.map +1 -1
- package/dist/cjs/hooks/use-dropdown.js +72 -45
- package/dist/esm/dialog/index.d.ts.map +1 -1
- package/dist/esm/dialog/index.js +33 -2
- package/dist/esm/editor-pm/mentions-plugin.d.ts +3 -3
- package/dist/esm/editor-pm/mentions-plugin.d.ts.map +1 -1
- package/dist/esm/editor-pm/mentions-plugin.js +11 -0
- package/dist/esm/editor-pm/prosemirror-editor.d.ts +4 -4
- package/dist/esm/editor-pm/prosemirror-editor.d.ts.map +1 -1
- package/dist/esm/editor-pm/prosemirror-editor.js +86 -34
- package/dist/esm/editor-pm/use-parameter-suggestions.d.ts.map +1 -1
- package/dist/esm/editor-pm/use-parameter-suggestions.js +27 -9
- package/dist/esm/hooks/use-dropdown.d.ts +12 -5
- package/dist/esm/hooks/use-dropdown.d.ts.map +1 -1
- package/dist/esm/hooks/use-dropdown.js +68 -43
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-dropdown.d.ts","sourceRoot":"","sources":["../../../hooks/use-dropdown.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-dropdown.d.ts","sourceRoot":"","sources":["../../../hooks/use-dropdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAExD,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,KAAK,gBAAgB,CAAC,CAAC,IAAI;IACzB,KAAK,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,CAAC,EAAE,CAAC;IACrB,gFAAgF;IAChF,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC;CACnC,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,UAAU,EACnD,GAAG,KAAK,CAAC,CAAC,CAAC,EACX,GAAG,KAAK,CAAC,CAAC,CAAC,eAQZ,CAAC;AAuDF,eAAO,MAAM,qBAAqB,GAAI,OAAO,MAAM,WACtB,CAAC;AAK9B,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,UAAU,EAAE,gIAU/C,gBAAgB,CAAC,CAAC,CAAC;;;mBA8Jd,KAAK,CAAC,aAAa,CAAC,cAAc,CAAC;sBAd/B,CAAC;;;sBA2EgC,MAAM;;;;CAoBlD,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.ts";
|
|
3
1
|
import { matchSorter } from "match-sorter";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.ts";
|
|
4
4
|
export const sortByUnderscore = (a, b) => {
|
|
5
5
|
const aUnderscore = String(a?.title)?.startsWith("_");
|
|
6
6
|
const bUnderscore = String(b?.title)?.startsWith("_");
|
|
@@ -58,34 +58,50 @@ const reorderByPriority = (list) => {
|
|
|
58
58
|
];
|
|
59
59
|
};
|
|
60
60
|
export const sanitizeDropdownQuery = (value) => value.replace(/["'`]/g, "");
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
const areSameItems = (a, b) => a === b || (a.length === b.length && a.every((item, i) => item === b[i]));
|
|
62
|
+
export const useDropdown = ({ initialHighlightedIndex, initialQuery, onChange, items = [], isNewValue, limit = 10, showAllIfEmpty, itemsWhenEmpty, isItemSelected, }) => {
|
|
63
63
|
const [query, setQuery] = useState(initialQuery ?? "");
|
|
64
64
|
const [highlightedIndex, setHighlightedIndex] = useState(initialHighlightedIndex);
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// The item set the menu was dismissed (Escape) for. The menu is open exactly
|
|
66
|
+
// when it has items and was not dismissed for that set; any change to the
|
|
67
|
+
// set (a keystroke re-filtering it) or an explicit `openMenu` lifts the
|
|
68
|
+
// dismissal.
|
|
69
|
+
const [dismissedItems, setDismissedItems] = useState(null);
|
|
67
70
|
// Refs
|
|
68
71
|
const isDirty = useRef(isNewValue);
|
|
69
|
-
const initialQueryRef = useRef(
|
|
72
|
+
const initialQueryRef = useRef(initialQuery ?? "");
|
|
70
73
|
const highlightedIndexRef = useRef(undefined);
|
|
71
74
|
const queryRef = useRef(undefined);
|
|
72
|
-
const filteredItemsRef = useRef(undefined);
|
|
73
75
|
useIsomorphicLayoutEffect(() => {
|
|
74
76
|
queryRef.current = query;
|
|
75
77
|
}, [query]);
|
|
76
78
|
useIsomorphicLayoutEffect(() => {
|
|
77
79
|
highlightedIndexRef.current = highlightedIndex;
|
|
78
80
|
}, [highlightedIndex]);
|
|
79
|
-
|
|
81
|
+
// Re-seed for a new editing session. The hosting editor stays mounted
|
|
82
|
+
// (hidden) between sessions, so the query must follow `initialQuery` when a
|
|
83
|
+
// new session starts — otherwise the previous session's filter narrows the
|
|
84
|
+
// next dropdown open.
|
|
85
|
+
//
|
|
86
|
+
// Derived during render (React re-runs the component before committing),
|
|
87
|
+
// deliberately NOT from an effect. The in-cell editor passes the live cell
|
|
88
|
+
// text as `initialQuery`, so an effect here ran after every keystroke and
|
|
89
|
+
// scheduled a second render from the commit phase; together with the
|
|
90
|
+
// open/close effect below that left a render pending at the tail of every
|
|
91
|
+
// commit, which in a typing burst never let React's nested-update counter
|
|
92
|
+
// reset and threw "Maximum update depth exceeded" (Minified React error
|
|
93
|
+
// #185) — dropping the keystroke whose update threw.
|
|
94
|
+
const [seed, setSeed] = useState(() => ({ initialQuery, isNewValue }));
|
|
95
|
+
if (seed.initialQuery !== initialQuery || seed.isNewValue !== isNewValue) {
|
|
96
|
+
setSeed({ initialQuery, isNewValue });
|
|
80
97
|
initialQueryRef.current = initialQuery ?? "";
|
|
81
98
|
isDirty.current = Boolean(isNewValue);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}, [initialQuery, isNewValue]);
|
|
99
|
+
if (query !== (initialQuery ?? "")) {
|
|
100
|
+
setQuery(initialQuery ?? "");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
87
103
|
// Filtered items
|
|
88
|
-
const
|
|
104
|
+
const computedFilteredItems = useMemo(() => {
|
|
89
105
|
if (!query) {
|
|
90
106
|
if (itemsWhenEmpty?.length) {
|
|
91
107
|
return reorderByPriority([...itemsWhenEmpty]);
|
|
@@ -121,10 +137,43 @@ export const useDropdown = ({ initialOpen, initialHighlightedIndex, initialQuery
|
|
|
121
137
|
baseSort: dropdownBaseSort,
|
|
122
138
|
});
|
|
123
139
|
return reorderByPriority(sorted).slice(0, limit);
|
|
124
|
-
}, [items, query, limit,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
}, [items, query, limit, showAllIfEmpty, itemsWhenEmpty]);
|
|
141
|
+
// Identity follows content: the open state and the highlight bookkeeping
|
|
142
|
+
// below key off "the item set changed", and a caller re-creating `items`
|
|
143
|
+
// per render must not read as a change (it would reopen a dismissed menu
|
|
144
|
+
// on every render and never let the render-phase derivations settle).
|
|
145
|
+
const filteredItemsRef = useRef(computedFilteredItems);
|
|
146
|
+
const filteredItems = areSameItems(filteredItemsRef.current, computedFilteredItems)
|
|
147
|
+
? filteredItemsRef.current
|
|
148
|
+
: computedFilteredItems;
|
|
149
|
+
filteredItemsRef.current = filteredItems;
|
|
150
|
+
const isOpen = filteredItems.length > 0 && dismissedItems !== filteredItems;
|
|
151
|
+
const openMenu = useCallback(() => setDismissedItems(null), []);
|
|
152
|
+
const closeMenu = useCallback(() => setDismissedItems(filteredItemsRef.current), []);
|
|
153
|
+
// Highlight bookkeeping for a changed query / item set, derived during
|
|
154
|
+
// render for the same reason as the re-seed above (an effect-scheduled
|
|
155
|
+
// setState per keystroke): clearing the query drops the highlight, and a
|
|
156
|
+
// pristine session pre-selects the item matching the current value (e.g. a
|
|
157
|
+
// data-validation dropdown highlighting the cell's current entry — Excel
|
|
158
|
+
// behavior); once the user types, the filter owns the highlight. The
|
|
159
|
+
// pre-select wins over the clear, as the former effect ordering did.
|
|
160
|
+
const [highlightSync, setHighlightSync] = useState(null);
|
|
161
|
+
if (highlightSync === null ||
|
|
162
|
+
highlightSync.query !== query ||
|
|
163
|
+
highlightSync.filteredItems !== filteredItems ||
|
|
164
|
+
highlightSync.isItemSelected !== isItemSelected) {
|
|
165
|
+
setHighlightSync({ query, filteredItems, isItemSelected });
|
|
166
|
+
let nextHighlightedIndex = highlightedIndex;
|
|
167
|
+
if (!query) {
|
|
168
|
+
nextHighlightedIndex = -1;
|
|
169
|
+
}
|
|
170
|
+
if (isItemSelected && !isDirty.current) {
|
|
171
|
+
nextHighlightedIndex = filteredItems.findIndex(isItemSelected);
|
|
172
|
+
}
|
|
173
|
+
if (nextHighlightedIndex !== highlightedIndex) {
|
|
174
|
+
setHighlightedIndex(nextHighlightedIndex);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
128
177
|
// User selects a value from the dropdown
|
|
129
178
|
const onSelect = useCallback((value) => {
|
|
130
179
|
onChange?.(value);
|
|
@@ -190,30 +239,6 @@ export const useDropdown = ({ initialOpen, initialHighlightedIndex, initialQuery
|
|
|
190
239
|
}
|
|
191
240
|
}
|
|
192
241
|
}, [isOpen, openMenu, onSelect, closeMenu]);
|
|
193
|
-
useEffect(() => {
|
|
194
|
-
if (filteredItems.length) {
|
|
195
|
-
openMenu();
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
closeMenu();
|
|
199
|
-
}
|
|
200
|
-
// If user clears the
|
|
201
|
-
if (!query) {
|
|
202
|
-
setHighlightedIndex(-1);
|
|
203
|
-
}
|
|
204
|
-
}, [query, filteredItems, closeMenu, openMenu]);
|
|
205
|
-
// Pre-select the item matching the current value (e.g. a data-validation
|
|
206
|
-
// dropdown highlighting the cell's current entry — Excel behavior). Runs
|
|
207
|
-
// only while the session is pristine (`isDirty` flips on the first typed
|
|
208
|
-
// change, handing the highlight to the filter). Declared AFTER the reset
|
|
209
|
-
// effect above so the seed wins the mount-time ordering when the query is
|
|
210
|
-
// empty (a blank cell selecting the list's empty/text-prefix entry).
|
|
211
|
-
useEffect(() => {
|
|
212
|
-
if (!isItemSelected || isDirty.current) {
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
setHighlightedIndex(filteredItems.findIndex(isItemSelected));
|
|
216
|
-
}, [filteredItems, isItemSelected]);
|
|
217
242
|
const handleSetQuery = useCallback((value) => {
|
|
218
243
|
setQuery(value);
|
|
219
244
|
setHighlightedIndex(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rowsncolumns/ui",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.5",
|
|
4
4
|
"author": "Vinay M",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@radix-ui/react-dialog": "^1.1.14",
|
|
35
|
-
"@radix-ui/react-dropdown-menu": "
|
|
35
|
+
"@radix-ui/react-dropdown-menu": "2.1.16",
|
|
36
36
|
"@radix-ui/react-popover": "^1.1.14",
|
|
37
37
|
"@radix-ui/react-progress": "^1.1.7",
|
|
38
38
|
"@radix-ui/react-select": "^2.2.5",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@rowsncolumns/functions": ">=15.0.2",
|
|
64
64
|
"@rowsncolumns/grid": ">=13.2.35",
|
|
65
65
|
"@rowsncolumns/icons": ">=7.2.33",
|
|
66
|
-
"@rowsncolumns/utils": ">=9.5.
|
|
66
|
+
"@rowsncolumns/utils": ">=9.5.2",
|
|
67
67
|
"@types/lodash": "^4.17.17",
|
|
68
68
|
"react": "^19.1.0",
|
|
69
69
|
"react-dom": "^19.1.0"
|