@elaraai/e3-ui-components 1.0.61 → 1.0.63

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.
@@ -0,0 +1,84 @@
1
+ import { EastTypeValue } from '@elaraai/east';
2
+ /** Where a query landed in the collection's canonical key order. */
3
+ export interface DatasetKeyMatchRange {
4
+ /** Whether any row matched. */
5
+ found: boolean;
6
+ /** First matched row (for a miss, the query's insertion row). */
7
+ row: number;
8
+ /** Number of matched rows. */
9
+ count: number;
10
+ }
11
+ /**
12
+ * A key query in wire form: a whole-key `.east` literal (`key`), a String
13
+ * prefix (`prefix`), or — for struct keys — exact leading-field literals
14
+ * (`fields`, declaration order) optionally followed by a `prefix` on the
15
+ * next (String) field. Every form addresses one contiguous row range in
16
+ * the canonical key order.
17
+ */
18
+ export type DatasetKeyQuery = {
19
+ key: string;
20
+ } | {
21
+ prefix: string;
22
+ } | {
23
+ fields: string[];
24
+ prefix?: string;
25
+ };
26
+ export interface DatasetKeySearchProps {
27
+ /** The searched collection's Dict key / Set element type. */
28
+ keyType: EastTypeValue;
29
+ /** Locates a query; literals are canonical `.east` text of
30
+ * already-validated values. */
31
+ onFind: (query: DatasetKeyQuery) => Promise<DatasetKeyMatchRange>;
32
+ /** Labels rows `[row, row + limit)` for the popup, in row order. */
33
+ onListRange: (row: number, limit: number) => Promise<string[]>;
34
+ /** Jumps the host tree to a global root row. */
35
+ onJump: (row: number) => void;
36
+ /** Clears the host's jump (and its held row highlight) when the query
37
+ * is cleared. */
38
+ onClear?: (() => void) | undefined;
39
+ }
40
+ /** A parsed search input: a wire query, or the hint to show instead. */
41
+ export type ParsedKeyInput = {
42
+ kind: 'query';
43
+ query: DatasetKeyQuery;
44
+ } | {
45
+ kind: 'hint';
46
+ hint: string;
47
+ };
48
+ /**
49
+ * Turns typed search text into a wire query, or a hint when it cannot
50
+ * parse. Struct keys: `(` opens a whole-key `.east` literal; otherwise
51
+ * comma-separated leading field values — exact for all but the last
52
+ * segment (unquoted String segments need no quotes), the last a prefix on
53
+ * a String field or an exact value otherwise, and a trailing comma
54
+ * narrows to the leading exact fields.
55
+ *
56
+ * @param keyType - the collection's Dict key / Set element type
57
+ * @param text - the raw search input
58
+ * @returns the query to send, or the hint to display
59
+ */
60
+ export declare function parseKeyInput(keyType: EastTypeValue, text: string): ParsedKeyInput;
61
+ /**
62
+ * Builds the monotone lower/upper row predicates of a RANGE query over
63
+ * keys in canonical East order — the client-side mirror of the server's
64
+ * fence-search predicates, for inline (already-decoded) previews. The
65
+ * range is `[first row where lower holds, first row where upper holds)`.
66
+ *
67
+ * @param keyType - the collection's Dict key / Set element type
68
+ * @param query - the wire query
69
+ * @returns the predicate pair, or `null` for a whole-key literal query
70
+ * (an exact lookup, not a range)
71
+ */
72
+ export declare function keyRangePredicates(keyType: EastTypeValue, query: DatasetKeyQuery): {
73
+ lower: (k: unknown) => boolean;
74
+ upper: (k: unknown) => boolean;
75
+ } | null;
76
+ /**
77
+ * Renders the dataset key search control.
78
+ *
79
+ * @param props - see {@link DatasetKeySearchProps}
80
+ * @returns the search combobox with its match count, range navigation and
81
+ * clear affordance
82
+ */
83
+ export declare const DatasetKeySearch: import('react').NamedExoticComponent<DatasetKeySearchProps>;
84
+ //# sourceMappingURL=DatasetKeySearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatasetKeySearch.d.ts","sourceRoot":"","sources":["../../src/components/DatasetKeySearch.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAoCH,OAAO,EAAmE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAQpH,oEAAoE;AACpE,MAAM,WAAW,oBAAoB;IACjC,+BAA+B;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GACrB;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GACf;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C,MAAM,WAAW,qBAAqB;IAClC,6DAA6D;IAC7D,OAAO,EAAE,aAAa,CAAC;IACvB;oCACgC;IAChC,MAAM,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAClE,oEAAoE;IACpE,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,gDAAgD;IAChD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B;sBACkB;IAClB,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;CACtC;AAwDD,wEAAwE;AACxE,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CA2ClF;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,GAC3E;IAAE,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;CAAE,GAClE,IAAI,CAgDT;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,6DA0J3B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"DatasetPreview.d.ts","sourceRoot":"","sources":["../../src/components/DatasetPreview.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAkB7D,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mDAAmD;IACnD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;+EAC2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,2CAYzF;AAED,eAAO,MAAM,cAAc,2DAiJzB,CAAC"}
1
+ {"version":3,"file":"DatasetPreview.d.ts","sourceRoot":"","sources":["../../src/components/DatasetPreview.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAmB7D,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mDAAmD;IACnD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;+EAC2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,2CAYzF;AAED,eAAO,MAAM,cAAc,2DAoMzB,CAAC"}
@@ -1,5 +1,8 @@
1
1
  import { RequestOptions } from '@elaraai/e3-api-client';
2
2
  import { EastTypeValue } from '@elaraai/east';
3
+ /** Drops loaded pages far from the window `[firstPage, lastPage]`, keeping
4
+ * the window itself and then the nearest pages up to the retention cap. */
5
+ export declare function pruneRetainedPages<T>(pages: ReadonlyMap<number, T>, firstPage: number, lastPage: number, max: number): ReadonlyMap<number, T>;
3
6
  export interface PagedDatasetPreviewProps {
4
7
  apiUrl: string;
5
8
  repo: string;
@@ -1 +1 @@
1
- {"version":3,"file":"PagedDatasetPreview.d.ts","sourceRoot":"","sources":["../../src/components/PagedDatasetPreview.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAwC,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAezF,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,EAAE,aAAa,CAAC;IACpB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oEAAoE;IACpE,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC;;;6DAGyD;IACzD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAiCD,eAAO,MAAM,mBAAmB,gEAyIgH,CAAC"}
1
+ {"version":3,"file":"PagedDatasetPreview.d.ts","sourceRoot":"","sources":["../../src/components/PagedDatasetPreview.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAwC,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAyBzF;4EAC4E;AAC5E,wBAAgB,kBAAkB,CAAC,CAAC,EAChC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,EAC7B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACZ,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CASxB;AAED,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,EAAE,aAAa,CAAC;IACpB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oEAAoE;IACpE,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC;;;6DAGyD;IACzD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAiCD,eAAO,MAAM,mBAAmB,gEAwLgH,CAAC"}
package/dist/index.cjs CHANGED
@@ -64006,6 +64006,271 @@ class ErrorBoundary extends React.Component {
64006
64006
  return this.props.children;
64007
64007
  }
64008
64008
  }
64009
+ const FIND_DEBOUNCE_MS = 250;
64010
+ const POPUP_LIMIT = 20;
64011
+ function structFields(keyType) {
64012
+ if (keyType.type !== "Struct") return null;
64013
+ const fields = keyType.value;
64014
+ return fields.length > 0 ? fields : null;
64015
+ }
64016
+ function splitTopLevel(text) {
64017
+ const parts = [];
64018
+ let current = "";
64019
+ let inString = false;
64020
+ let depth = 0;
64021
+ for (let i = 0; i < text.length; i++) {
64022
+ const ch = text[i];
64023
+ if (inString) {
64024
+ current += ch;
64025
+ if (ch === "\\" && i + 1 < text.length) {
64026
+ current += text[i + 1];
64027
+ i++;
64028
+ } else if (ch === '"') {
64029
+ inString = false;
64030
+ }
64031
+ continue;
64032
+ }
64033
+ if (ch === '"') {
64034
+ inString = true;
64035
+ current += ch;
64036
+ } else if (ch === "(" || ch === "[" || ch === "{") {
64037
+ depth++;
64038
+ current += ch;
64039
+ } else if (ch === ")" || ch === "]" || ch === "}") {
64040
+ depth--;
64041
+ current += ch;
64042
+ } else if (ch === "," && depth === 0) {
64043
+ parts.push(current);
64044
+ current = "";
64045
+ } else {
64046
+ current += ch;
64047
+ }
64048
+ }
64049
+ parts.push(current);
64050
+ return parts;
64051
+ }
64052
+ function keySignature(keyType) {
64053
+ const fields = structFields(keyType);
64054
+ if (fields === null) return keyType.type;
64055
+ return `(${fields.map((f2) => `${f2.name}: ${f2.type.type}`).join(", ")})`;
64056
+ }
64057
+ function parseKeyInput(keyType, text) {
64058
+ const fields = structFields(keyType);
64059
+ if (fields === null) {
64060
+ if (keyType.type === "String") return { kind: "query", query: { prefix: text } };
64061
+ const parsed = east.parseFor(keyType)(text);
64062
+ if (!parsed.success) return { kind: "hint", hint: `Key is ${keyType.type}` };
64063
+ return { kind: "query", query: { key: east.printFor(keyType)(parsed.value) } };
64064
+ }
64065
+ if (text.trimStart().startsWith("(")) {
64066
+ const parsed = east.parseFor(keyType)(text);
64067
+ if (!parsed.success) return { kind: "hint", hint: `Key is ${keySignature(keyType)}` };
64068
+ return { kind: "query", query: { key: east.printFor(keyType)(parsed.value) } };
64069
+ }
64070
+ const segments = splitTopLevel(text);
64071
+ if (segments.length > fields.length) {
64072
+ return { kind: "hint", hint: `Key is ${keySignature(keyType)}` };
64073
+ }
64074
+ const exact = [];
64075
+ for (let i = 0; i < segments.length; i++) {
64076
+ const field = fields[i];
64077
+ const raw = segments[i].trim();
64078
+ const isLast = i === segments.length - 1;
64079
+ if (isLast && raw === "" && i > 0) {
64080
+ return { kind: "query", query: { fields: exact } };
64081
+ }
64082
+ if (field.type.type === "String" && !raw.startsWith('"')) {
64083
+ if (isLast) {
64084
+ return exact.length === 0 ? { kind: "query", query: { prefix: raw } } : { kind: "query", query: { fields: exact, prefix: raw } };
64085
+ }
64086
+ exact.push(east.printFor(east.StringType)(raw));
64087
+ continue;
64088
+ }
64089
+ const parsed = east.parseFor(field.type)(raw);
64090
+ if (!parsed.success) {
64091
+ return { kind: "hint", hint: `${field.name} is ${field.type.type} — key is ${keySignature(keyType)}` };
64092
+ }
64093
+ exact.push(east.printFor(field.type)(parsed.value));
64094
+ }
64095
+ return { kind: "query", query: { fields: exact } };
64096
+ }
64097
+ const MATCH_NOTHING = { lower: () => false, upper: () => false };
64098
+ function keyRangePredicates(keyType, query) {
64099
+ if ("key" in query) return null;
64100
+ if (keyType.type === "Struct") {
64101
+ const meta3 = keyType.value;
64102
+ const literals = "fields" in query ? query.fields : [];
64103
+ if (literals.length > meta3.length) return MATCH_NOTHING;
64104
+ const values = [];
64105
+ for (let j2 = 0; j2 < literals.length; j2++) {
64106
+ const parsed = east.parseFor(meta3[j2].type)(literals[j2]);
64107
+ if (!parsed.success) return MATCH_NOTHING;
64108
+ values.push(parsed.value);
64109
+ }
64110
+ const cmps = meta3.map((f2) => east.compareFor(f2.type));
64111
+ const lead = (k2) => {
64112
+ for (let j2 = 0; j2 < values.length; j2++) {
64113
+ const c2 = cmps[j2](k2[meta3[j2].name], values[j2]);
64114
+ if (c2 !== 0) return c2;
64115
+ }
64116
+ return 0;
64117
+ };
64118
+ const prefix22 = query.prefix;
64119
+ if (prefix22 === void 0) {
64120
+ return { lower: (k2) => lead(k2) >= 0, upper: (k2) => lead(k2) > 0 };
64121
+ }
64122
+ const prefixIdx = values.length;
64123
+ if (prefixIdx >= meta3.length || meta3[prefixIdx].type.type !== "String") return MATCH_NOTHING;
64124
+ const prefixName = meta3[prefixIdx].name;
64125
+ const prefixCmp = cmps[prefixIdx];
64126
+ return {
64127
+ lower: (k2) => {
64128
+ const c2 = lead(k2);
64129
+ return c2 !== 0 ? c2 > 0 : prefixCmp(k2[prefixName], prefix22) >= 0;
64130
+ },
64131
+ upper: (k2) => {
64132
+ const c2 = lead(k2);
64133
+ if (c2 !== 0) return c2 > 0;
64134
+ const field = k2[prefixName];
64135
+ return prefixCmp(field, prefix22) > 0 && !field.startsWith(prefix22);
64136
+ }
64137
+ };
64138
+ }
64139
+ if (!("prefix" in query) || query.prefix === void 0 || keyType.type !== "String") return MATCH_NOTHING;
64140
+ const prefix2 = query.prefix;
64141
+ const cmp = east.compareFor(keyType);
64142
+ return {
64143
+ lower: (k2) => cmp(k2, prefix2) >= 0,
64144
+ upper: (k2) => cmp(k2, prefix2) > 0 && !k2.startsWith(prefix2)
64145
+ };
64146
+ }
64147
+ const DatasetKeySearch = React.memo(function DatasetKeySearch2({ keyType, onFind, onListRange, onJump, onClear }) {
64148
+ const [range, setRange] = React.useState(null);
64149
+ const [items, setItems] = React.useState([]);
64150
+ const [activeIdx, setActiveIdx] = React.useState(-1);
64151
+ const [hint, setHint] = React.useState(null);
64152
+ const [resetSeq, setResetSeq] = React.useState(0);
64153
+ const debounceRef = React.useRef(void 0);
64154
+ const findSeqRef = React.useRef(0);
64155
+ React.useEffect(() => () => clearTimeout(debounceRef.current), []);
64156
+ const runFind = React.useCallback((query) => {
64157
+ const seq = ++findSeqRef.current;
64158
+ void (async () => {
64159
+ try {
64160
+ const result = await onFind(query);
64161
+ if (seq !== findSeqRef.current) return;
64162
+ setRange(result);
64163
+ setActiveIdx(-1);
64164
+ if (result.found) {
64165
+ const labels = await onListRange(result.row, Math.min(POPUP_LIMIT, result.count));
64166
+ if (seq !== findSeqRef.current) return;
64167
+ setItems(labels.map((label, i) => ({ row: result.row + i, label })));
64168
+ } else {
64169
+ setItems([]);
64170
+ }
64171
+ } catch {
64172
+ if (seq !== findSeqRef.current) return;
64173
+ setRange(null);
64174
+ setItems([]);
64175
+ }
64176
+ })();
64177
+ }, [onFind, onListRange]);
64178
+ const resetQueryState = React.useCallback(() => {
64179
+ clearTimeout(debounceRef.current);
64180
+ findSeqRef.current++;
64181
+ setRange(null);
64182
+ setItems([]);
64183
+ setActiveIdx(-1);
64184
+ setHint(null);
64185
+ }, []);
64186
+ const handleInput = React.useCallback((text) => {
64187
+ clearTimeout(debounceRef.current);
64188
+ if (text === "") {
64189
+ resetQueryState();
64190
+ onClear == null ? void 0 : onClear();
64191
+ return;
64192
+ }
64193
+ const parsed = parseKeyInput(keyType, text);
64194
+ if (parsed.kind === "hint") {
64195
+ findSeqRef.current++;
64196
+ setRange(null);
64197
+ setItems([]);
64198
+ setActiveIdx(-1);
64199
+ setHint(parsed.hint);
64200
+ return;
64201
+ }
64202
+ setHint(null);
64203
+ debounceRef.current = setTimeout(() => runFind(parsed.query), FIND_DEBOUNCE_MS);
64204
+ }, [keyType, runFind, resetQueryState, onClear]);
64205
+ const clearSearch = React.useCallback(() => {
64206
+ resetQueryState();
64207
+ setResetSeq((s2) => s2 + 1);
64208
+ onClear == null ? void 0 : onClear();
64209
+ }, [resetQueryState, onClear]);
64210
+ const commit = React.useCallback((row) => {
64211
+ onJump(row);
64212
+ if (range !== null) setActiveIdx(row - range.row);
64213
+ }, [onJump, range]);
64214
+ const step3 = React.useCallback((delta) => {
64215
+ if (range === null || !range.found) return;
64216
+ const next2 = Math.max(0, Math.min(range.count - 1, (activeIdx === -1 ? 0 : activeIdx) + delta));
64217
+ commit(range.row + next2);
64218
+ }, [range, activeIdx, commit]);
64219
+ const onKeyDownCapture = React.useCallback((e3) => {
64220
+ if (e3.key !== "Enter" || range === null || !range.found) return;
64221
+ if (document.querySelector('[data-scope="combobox"][data-part="content"] [data-highlighted]') !== null) return;
64222
+ commit(range.row + Math.max(0, activeIdx));
64223
+ }, [range, activeIdx, commit]);
64224
+ const placeholder = React.useMemo(() => {
64225
+ const fields = structFields(keyType);
64226
+ if (fields !== null) return `Search keys (${fields.map((f2) => f2.name).join(", ")})`;
64227
+ return keyType.type === "String" ? "Search keys" : `Find key (${keyType.type})`;
64228
+ }, [keyType]);
64229
+ const payload = React.useMemo(() => ({
64230
+ value: east.none,
64231
+ items: items.map((it) => ({ value: String(it.row), label: it.label, disabled: east.none })),
64232
+ placeholder: east.some(placeholder),
64233
+ multiple: east.none,
64234
+ disabled: east.none,
64235
+ allowCustomValue: east.some(true),
64236
+ onChange: east.some((v3) => commit(Number(v3))),
64237
+ onChangeMultiple: east.none,
64238
+ onInputValueChange: east.some(handleInput),
64239
+ onOpenChange: east.none,
64240
+ style: east.some({ size: east.some(east.variant("xs", null)), color: east.none, background: east.none, borderColor: east.none })
64241
+ }), [items, placeholder, commit, handleInput]);
64242
+ const status = hint !== null ? hint : range === null ? null : !range.found ? "No matches" : activeIdx === -1 ? `${range.count.toLocaleString()} ${range.count === 1 ? "match" : "matches"}` : `${(activeIdx + 1).toLocaleString()} of ${range.count.toLocaleString()}`;
64243
+ const canStep = range !== null && range.found && range.count > 1;
64244
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { gap: 1, align: "center", minW: "0", onKeyDownCapture, "data-part": "dataset-key-search", children: [
64245
+ /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { width: `min(${placeholder.length + 10}ch, 40rem)`, minW: "12rem", flexShrink: 1, children: /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraCombobox, { value: payload, selectionBehavior: "preserve" }, resetSeq) }),
64246
+ status !== null && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", whiteSpace: "nowrap", children: status }),
64247
+ canStep && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
64248
+ /* @__PURE__ */ jsxRuntime.jsx(
64249
+ react.IconButton,
64250
+ {
64251
+ "aria-label": "Previous match",
64252
+ size: "2xs",
64253
+ variant: "ghost",
64254
+ disabled: activeIdx <= 0,
64255
+ onClick: () => step3(-1),
64256
+ children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronUp })
64257
+ }
64258
+ ),
64259
+ /* @__PURE__ */ jsxRuntime.jsx(
64260
+ react.IconButton,
64261
+ {
64262
+ "aria-label": "Next match",
64263
+ size: "2xs",
64264
+ variant: "ghost",
64265
+ disabled: activeIdx >= range.count - 1,
64266
+ onClick: () => step3(1),
64267
+ children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronDown })
64268
+ }
64269
+ )
64270
+ ] }),
64271
+ (range !== null || hint !== null) && /* @__PURE__ */ jsxRuntime.jsx(react.IconButton, { "aria-label": "Clear search", size: "2xs", variant: "ghost", onClick: clearSearch, children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faXmark }) })
64272
+ ] });
64273
+ });
64009
64274
  function pagingDebug(...args) {
64010
64275
  try {
64011
64276
  if (typeof localStorage === "undefined") return;
@@ -64017,6 +64282,17 @@ function pagingDebug(...args) {
64017
64282
  console.info("[e3-paging]", ...args);
64018
64283
  }
64019
64284
  const PAGE_SIZE = 500;
64285
+ const MAX_RETAINED_PAGES = 8;
64286
+ function pruneRetainedPages(pages, firstPage, lastPage, max5) {
64287
+ if (pages.size <= max5) return pages;
64288
+ const distance2 = (p2) => p2 < firstPage ? firstPage - p2 : p2 > lastPage ? p2 - lastPage : 0;
64289
+ const keep = [...pages.keys()].sort((a2, b2) => distance2(a2) - distance2(b2) || a2 - b2).slice(0, max5);
64290
+ const kept = /* @__PURE__ */ new Map();
64291
+ for (const p2 of keep.sort((a2, b2) => a2 - b2)) {
64292
+ kept.set(p2, pages.get(p2));
64293
+ }
64294
+ return kept;
64295
+ }
64020
64296
  function pageRows(type, decoded, offset) {
64021
64297
  if (type.type === "Array") {
64022
64298
  const elemType2 = type.value;
@@ -64060,12 +64336,14 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64060
64336
  const [totals, setTotals] = React.useState(null);
64061
64337
  const [loadingCount, setLoadingCount] = React.useState(0);
64062
64338
  const [error3, setError] = React.useState(null);
64339
+ const [jumpRow, setJumpRow] = React.useState(void 0);
64063
64340
  const inflightRef = React.useRef(/* @__PURE__ */ new Set());
64064
64341
  React.useEffect(() => {
64065
64342
  pagingDebug(`reset (path=${path2}, hash=${hash2.slice(0, 8)})`);
64066
64343
  setPages(/* @__PURE__ */ new Map());
64067
64344
  setTotals(null);
64068
64345
  setError(null);
64346
+ setJumpRow(void 0);
64069
64347
  inflightRef.current.clear();
64070
64348
  }, [path2, hash2]);
64071
64349
  const loadPage = React.useCallback((pageIdx) => {
@@ -64073,13 +64351,13 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64073
64351
  inflightRef.current.add(pageIdx);
64074
64352
  setLoadingCount((n2) => n2 + 1);
64075
64353
  pagingDebug(`fetch p${pageIdx} (offset=${pageIdx * PAGE_SIZE}, limit=${PAGE_SIZE}, hash=${hash2.slice(0, 8)})`);
64076
- const pathParts = path2.split(".").filter(Boolean).map((v3) => east.variant("field", v3));
64354
+ const pathParts2 = path2.split(".").filter(Boolean).map((v3) => east.variant("field", v3));
64077
64355
  const reqOpts = requestOptions ?? { token: null };
64078
64356
  queryClient.fetchQuery({
64079
64357
  queryKey: ["datasetPage", apiUrl, repo, workspace, path2, hash2, `p${pageIdx}`],
64080
64358
  // Hash-pinned: the URL is a pure function of the bytes, so any
64081
64359
  // HTTP cache between here and the server can hold it immutably.
64082
- queryFn: () => e3ApiClient.datasetGetPage(apiUrl, repo, workspace, pathParts, { offset: pageIdx * PAGE_SIZE, limit: PAGE_SIZE, hash: hash2 }, reqOpts),
64360
+ queryFn: () => e3ApiClient.datasetGetPage(apiUrl, repo, workspace, pathParts2, { offset: pageIdx * PAGE_SIZE, limit: PAGE_SIZE, hash: hash2 }, reqOpts),
64083
64361
  staleTime: Infinity
64084
64362
  // pages of one content hash are immutable
64085
64363
  }).then((page) => {
@@ -64116,8 +64394,35 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64116
64394
  if (!pages.has(p2)) missing.push(p2);
64117
64395
  }
64118
64396
  pagingDebug(`need rows [${startRow}, ${endRow}) → pages ${first2}..${last2}, missing=[${missing.join(",")}], inflight=[${[...inflightRef.current].join(",")}]`);
64397
+ setPages((prev2) => pruneRetainedPages(prev2, first2, last2, MAX_RETAINED_PAGES));
64119
64398
  for (const p2 of missing) loadPage(p2);
64120
64399
  }, [pages, loadPage]);
64400
+ const keyType = React.useMemo(() => type.type === "Dict" ? type.value.key : type.type === "Set" ? type.value : null, [type]);
64401
+ const pathParts = React.useMemo(() => path2.split(".").filter(Boolean).map((v3) => east.variant("field", v3)), [path2]);
64402
+ const onFindKey = React.useCallback(async (query) => {
64403
+ const reqOpts = requestOptions ?? { token: null };
64404
+ const queryTag = JSON.stringify(query);
64405
+ const result = await queryClient.fetchQuery({
64406
+ queryKey: ["datasetFind", apiUrl, repo, workspace, path2, hash2, queryTag],
64407
+ queryFn: () => e3ApiClient.datasetFindKey(apiUrl, repo, workspace, pathParts, { ...query, hash: hash2 }, reqOpts),
64408
+ staleTime: Infinity
64409
+ // hash-pinned: one content hash, one answer
64410
+ });
64411
+ pagingDebug(`find ${queryTag}: found=${result.found} row=${result.row} count=${result.count}`);
64412
+ return result;
64413
+ }, [queryClient, apiUrl, repo, workspace, path2, hash2, pathParts, requestOptions]);
64414
+ const onListRange = React.useCallback(async (row, limit2) => {
64415
+ const reqOpts = requestOptions ?? { token: null };
64416
+ const page = await queryClient.fetchQuery({
64417
+ queryKey: ["datasetPage", apiUrl, repo, workspace, path2, hash2, `w${row}:${limit2}`],
64418
+ queryFn: () => e3ApiClient.datasetGetPage(apiUrl, repo, workspace, pathParts, { offset: row, limit: limit2, hash: hash2 }, reqOpts),
64419
+ staleTime: Infinity
64420
+ });
64421
+ const decoded = east.decodeBeast2For(type)(page.data);
64422
+ const stringKeys = keyType !== null && keyType.type === "String";
64423
+ const keys = type.type === "Dict" ? [...decoded.keys()] : [...decoded.values()];
64424
+ return keys.map((k2) => stringKeys ? k2 : eastUi.ValueTree.keyLabel(keyType, k2));
64425
+ }, [queryClient, apiUrl, repo, workspace, path2, hash2, pathParts, requestOptions, type, keyType]);
64121
64426
  const treeValue = React.useMemo(() => ({
64122
64427
  root: eastUi.ValueTree.materialize(type, emptyOf(type)),
64123
64428
  onEdit: east.none,
@@ -64130,8 +64435,9 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64130
64435
  totalRows: totals.elements,
64131
64436
  pageSize: PAGE_SIZE,
64132
64437
  pages,
64133
- onNeedRows
64134
- }, [totals, pages, onNeedRows]);
64438
+ onNeedRows,
64439
+ scrollToRow: jumpRow
64440
+ }, [totals, pages, onNeedRows, jumpRow]);
64135
64441
  if (error3 !== null) {
64136
64442
  if (error3 instanceof e3ApiClient.ApiError && error3.code === "dataset_too_large") {
64137
64443
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { height: "100%", direction: "column", align: "center", justify: "center", gap: 3, p: 6, children: [
@@ -64156,6 +64462,16 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64156
64462
  formatSize(totals.bytes > 0 ? totals.bytes : sizeBytes)
64157
64463
  ] }),
64158
64464
  loadingCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "Loading…" }),
64465
+ keyType !== null && /* @__PURE__ */ jsxRuntime.jsx(
64466
+ DatasetKeySearch,
64467
+ {
64468
+ keyType,
64469
+ onFind: onFindKey,
64470
+ onListRange,
64471
+ onJump: setJumpRow,
64472
+ onClear: () => setJumpRow(void 0)
64473
+ }
64474
+ ),
64159
64475
  /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { flex: 1, justify: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: onDownload }) })
64160
64476
  ] }),
64161
64477
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, minHeight: 0, overflow: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraValueTree, { value: treeValue, storageKey: `${path2}:page`, paging }) })
@@ -64227,6 +64543,37 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64227
64543
  await setMutation.mutateAsync({ path: treePath, data: data4 });
64228
64544
  await queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
64229
64545
  }, [apiUrl, repo, workspace, path2, type, setMutation, queryClient]);
64546
+ const keyType = React.useMemo(() => type === void 0 ? null : type.type === "Dict" ? type.value.key : type.type === "Set" ? type.value : null, [type]);
64547
+ const inlineKeys = React.useMemo(() => decoded === void 0 || type === void 0 ? null : type.type === "Dict" ? [...decoded.keys()] : type.type === "Set" ? [...decoded.values()] : null, [type, decoded]);
64548
+ const [jumpRow, setJumpRow] = React.useState(void 0);
64549
+ React.useEffect(() => {
64550
+ setJumpRow(void 0);
64551
+ }, [decoded]);
64552
+ const onFindInline = React.useCallback(async (query) => {
64553
+ if (inlineKeys === null || keyType === null) return { found: false, row: 0, count: 0 };
64554
+ const range = keyRangePredicates(keyType, query);
64555
+ if (range === null) {
64556
+ const parsed = east.parseFor(keyType)(query.key);
64557
+ if (!parsed.success) return { found: false, row: 0, count: 0 };
64558
+ const cmp = east.compareFor(keyType);
64559
+ for (let i = 0; i < inlineKeys.length; i++) {
64560
+ const order = cmp(inlineKeys[i], parsed.value);
64561
+ if (order === 0) return { found: true, row: i, count: 1 };
64562
+ if (order > 0) return { found: false, row: i, count: 0 };
64563
+ }
64564
+ return { found: false, row: inlineKeys.length, count: 0 };
64565
+ }
64566
+ let row = inlineKeys.findIndex(range.lower);
64567
+ if (row === -1) row = inlineKeys.length;
64568
+ let count2 = 0;
64569
+ while (row + count2 < inlineKeys.length && !range.upper(inlineKeys[row + count2])) count2++;
64570
+ return { found: count2 > 0, row, count: count2 };
64571
+ }, [inlineKeys, keyType]);
64572
+ const onListInline = React.useCallback(async (row, limit2) => {
64573
+ if (inlineKeys === null || keyType === null) return [];
64574
+ const stringKeys = keyType.type === "String";
64575
+ return inlineKeys.slice(row, row + limit2).map((k2) => stringKeys ? k2 : eastUi.ValueTree.keyLabel(keyType, k2));
64576
+ }, [inlineKeys, keyType]);
64230
64577
  const treeValue = React.useMemo(() => {
64231
64578
  if (type === void 0 || decoded === void 0) return null;
64232
64579
  const root2 = eastUi.ValueTree.materialize(type, decoded);
@@ -64281,14 +64628,26 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64281
64628
  const count = type === void 0 || decoded === void 0 ? null : type.type === "Array" ? decoded.length : type.type === "Set" || type.type === "Dict" ? decoded.size : null;
64282
64629
  const countText = count === null ? "" : `${count.toLocaleString()} ${(type == null ? void 0 : type.type) === "Dict" ? "entries" : "items"} · `;
64283
64630
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64284
- /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { px: 4, py: 2, justify: "flex-end", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64285
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "fg.muted", mr: 2, alignSelf: "center", children: [
64286
- countText,
64287
- formatSize(sizeBytes)
64288
- ] }),
64289
- /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: download })
64631
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { px: 4, py: 2, gap: 2, align: "center", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64632
+ keyType !== null && inlineKeys !== null && /* @__PURE__ */ jsxRuntime.jsx(
64633
+ DatasetKeySearch,
64634
+ {
64635
+ keyType,
64636
+ onFind: onFindInline,
64637
+ onListRange: onListInline,
64638
+ onJump: setJumpRow,
64639
+ onClear: () => setJumpRow(void 0)
64640
+ }
64641
+ ),
64642
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { flex: 1, justify: "flex-end", align: "center", gap: 2, children: [
64643
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "fg.muted", children: [
64644
+ countText,
64645
+ formatSize(sizeBytes)
64646
+ ] }),
64647
+ /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: download })
64648
+ ] })
64290
64649
  ] }),
64291
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, minHeight: 0, overflow: "hidden", children: treeValue !== null && /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraValueTree, { value: treeValue, storageKey: path2 ?? "value" }) })
64650
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, minHeight: 0, overflow: "hidden", children: treeValue !== null && /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraValueTree, { value: treeValue, storageKey: path2 ?? "value", scrollToRow: jumpRow }) })
64292
64651
  ] });
64293
64652
  });
64294
64653
  const InputPreview = React.memo(function InputPreview2({
@@ -65279,6 +65638,7 @@ function EastValueViewer({ type, value }) {
65279
65638
  exports.BindPlatform = BindPlatform;
65280
65639
  exports.BindRuntime = BindRuntime;
65281
65640
  exports.DataTaskPreview = DataTaskPreview;
65641
+ exports.DatasetKeySearch = DatasetKeySearch;
65282
65642
  exports.DatasetPreview = DatasetPreview;
65283
65643
  exports.E3Provider = E3Provider;
65284
65644
  exports.EastChakraDecisionJournal = EastChakraDecisionJournal;