@reopt-ai/opt-ui 1.6.0 → 1.8.0

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 (75) hide show
  1. package/COMPONENT_CATALOG.md +96 -13
  2. package/dist/app.css +24 -0
  3. package/dist/core/index.cjs +3 -3
  4. package/dist/core/index.d.cts +1 -1
  5. package/dist/core/index.d.ts +1 -1
  6. package/dist/core/index.js +2 -2
  7. package/dist/docs/02-components/01-core.md +1 -1
  8. package/dist/docs/02-components/02-visuals.md +1 -1
  9. package/dist/docs/02-components/03-shells.md +105 -12
  10. package/dist/docs/02-components/04-surfaces.md +1 -1
  11. package/dist/docs/02-components/index.md +4 -4
  12. package/dist/docs/05-migration/01-breaking-changes.md +18 -1
  13. package/dist/field-sidebar-5KYVlpSs.d.ts +1041 -0
  14. package/dist/field-sidebar-5KYVlpSs.d.ts.map +1 -0
  15. package/dist/field-sidebar-BP0iWkoV.d.cts +1041 -0
  16. package/dist/field-sidebar-BP0iWkoV.d.cts.map +1 -0
  17. package/dist/{workflow-canvas-BGBAcjf-.js → field-sidebar-BYD_UIjG.js} +1909 -52
  18. package/dist/field-sidebar-BYD_UIjG.js.map +1 -0
  19. package/dist/{workflow-canvas-3Jeevkgy.cjs → field-sidebar-Bqfp6dgg.cjs} +2037 -48
  20. package/dist/id-registry.cjs +41 -1
  21. package/dist/id-registry.d.cts +1 -1
  22. package/dist/id-registry.d.cts.map +1 -1
  23. package/dist/id-registry.d.ts +1 -1
  24. package/dist/id-registry.d.ts.map +1 -1
  25. package/dist/id-registry.js +41 -1
  26. package/dist/id-registry.js.map +1 -1
  27. package/dist/id-registry.json +85 -0
  28. package/dist/index.cjs +142 -56
  29. package/dist/index.d.cts +149 -80
  30. package/dist/index.d.cts.map +1 -1
  31. package/dist/index.d.ts +149 -80
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +73 -11
  34. package/dist/index.js.map +1 -1
  35. package/dist/meta.cjs +292 -0
  36. package/dist/meta.d.cts.map +1 -1
  37. package/dist/meta.d.ts.map +1 -1
  38. package/dist/meta.js +292 -0
  39. package/dist/meta.js.map +1 -1
  40. package/dist/{pagination-By7Ozv3U.js → pagination-DWbvACuN.js} +93 -31
  41. package/dist/pagination-DWbvACuN.js.map +1 -0
  42. package/dist/{pagination-Uv3CnGJz.cjs → pagination-DjfxBcjX.cjs} +98 -36
  43. package/dist/query.cjs +386 -0
  44. package/dist/query.d.cts +141 -0
  45. package/dist/query.d.cts.map +1 -0
  46. package/dist/query.d.ts +141 -0
  47. package/dist/query.d.ts.map +1 -0
  48. package/dist/query.js +371 -0
  49. package/dist/query.js.map +1 -0
  50. package/dist/{shader-surface-BWhO2xkk.js → shader-surface-BUPZ0P-n.js} +22 -83
  51. package/dist/shader-surface-BUPZ0P-n.js.map +1 -0
  52. package/dist/{shader-surface-DIVWtpFl.d.ts → shader-surface-BfTP9c1Q.d.ts} +139 -139
  53. package/dist/shader-surface-BfTP9c1Q.d.ts.map +1 -0
  54. package/dist/{shader-surface-BK1GMt5_.d.cts → shader-surface-DRrcLrWf.d.cts} +139 -139
  55. package/dist/shader-surface-DRrcLrWf.d.cts.map +1 -0
  56. package/dist/{shader-surface-DxLLfrE6.cjs → shader-surface-Fav5K9UC.cjs} +20 -87
  57. package/dist/shells/index.cjs +31 -24
  58. package/dist/shells/index.d.cts +2 -2
  59. package/dist/shells/index.d.ts +2 -2
  60. package/dist/shells/index.js +3 -2
  61. package/dist/theme/presets/default.css +2 -2
  62. package/dist/theme/presets/minimal.css +2 -2
  63. package/dist/theme/presets/mono-dark.css +2 -2
  64. package/dist/theme/presets/natural.css +1 -1
  65. package/dist/theme/presets/pro.css +2 -2
  66. package/package.json +17 -6
  67. package/dist/pagination-By7Ozv3U.js.map +0 -1
  68. package/dist/shader-surface-BK1GMt5_.d.cts.map +0 -1
  69. package/dist/shader-surface-BWhO2xkk.js.map +0 -1
  70. package/dist/shader-surface-DIVWtpFl.d.ts.map +0 -1
  71. package/dist/workflow-canvas-BGBAcjf-.js.map +0 -1
  72. package/dist/workflow-canvas-Bar_O2SQ.d.ts +0 -494
  73. package/dist/workflow-canvas-Bar_O2SQ.d.ts.map +0 -1
  74. package/dist/workflow-canvas-DghDRtHn.d.cts +0 -494
  75. package/dist/workflow-canvas-DghDRtHn.d.cts.map +0 -1
@@ -0,0 +1,141 @@
1
+ //#region src/lib/query/query-ast.d.ts
2
+ /**
3
+ * A search query, as a value.
4
+ *
5
+ * Free-text search and a row of filter pickers answer different halves of the
6
+ * same question, and neither can express the other: a picker cannot say "not
7
+ * this", and a text box cannot say "this field, exactly". A parsed query is one
8
+ * thing that does both, and — because it is a value rather than a widget's
9
+ * internal state — it survives a URL, a saved view, and the trip to a server
10
+ * that has to turn it into a database filter.
11
+ *
12
+ * The grammar follows the one analytics tools have converged on (Elastic's
13
+ * `EuiSearchBar`, Kibana, GitHub's issue search), minus the parts that only
14
+ * make sense against a Lucene index:
15
+ *
16
+ * error a term anywhere in the row
17
+ * "connection reset" a phrase, kept together
18
+ * -health a term that must not appear
19
+ * path:/checkout a field equal to a value
20
+ * path:(/a or /b) a field equal to any of several values
21
+ * -status:200 a field that must not equal a value
22
+ * status>=500 a comparison
23
+ * path:*checkout* the field contains this somewhere
24
+ * utm_source:* the field is present at all
25
+ *
26
+ * Clauses combine with AND; `or` only groups values inside one field. That is
27
+ * a deliberate ceiling: arbitrary boolean nesting reads worse than it queries,
28
+ * and every backend this targets can express the restricted form directly.
29
+ */
30
+ /** How a field clause compares. */
31
+ type QueryOperator = "eq" | "contains" | "gt" | "gte" | "lt" | "lte" | "exists";
32
+ /** A bare or quoted word matched against the whole row. */
33
+ interface QueryTermClause {
34
+ type: "term";
35
+ value: string;
36
+ negated: boolean;
37
+ }
38
+ /** A constraint on one named field. Several values read as "any of". */
39
+ interface QueryFieldClause {
40
+ type: "field";
41
+ field: string;
42
+ operator: QueryOperator;
43
+ values: string[];
44
+ negated: boolean;
45
+ }
46
+ type QueryClause = QueryTermClause | QueryFieldClause;
47
+ /** A parsed query: clauses combined with AND. */
48
+ interface Query {
49
+ clauses: QueryClause[];
50
+ }
51
+ declare const EMPTY_QUERY: Query;
52
+ /** Whether a query constrains anything at all. */
53
+ declare function isEmptyQuery(query: Query | null | undefined): boolean;
54
+ /** Add a clause unless an identical one is already there. */
55
+ declare function addClause(query: Query, clause: QueryClause): Query;
56
+ /**
57
+ * Constrain a field to a value — what a cell's "filter for this" action does.
58
+ *
59
+ * Applying the opposite of a constraint already present replaces it rather than
60
+ * adding it: `status:200` then "filter out 200" should read as one changed
61
+ * mind, not as a query that can never match.
62
+ */
63
+ declare function withFieldValue(query: Query, field: string, value: string, options?: {
64
+ negated?: boolean;
65
+ operator?: QueryOperator;
66
+ }): Query;
67
+ /** Drop every clause naming `field`. */
68
+ declare function withoutField(query: Query, field: string): Query;
69
+ /** Drop the clause at `index`, for a removable chip. */
70
+ declare function removeClause(query: Query, index: number): Query;
71
+ /** Every field the query names, in the order they first appear. */
72
+ declare function queryFields(query: Query): string[];
73
+ /** The values a field is constrained to, if any — for showing a filter as active. */
74
+ declare function fieldValues(query: Query, field: string): string[];
75
+ /** What a "filter for / filter out" control on a value asks for. */
76
+ interface QueryCellAction {
77
+ field: string;
78
+ value: string;
79
+ intent: "include" | "exclude";
80
+ }
81
+ /**
82
+ * Fold a cell action into the query.
83
+ *
84
+ * The pair of buttons on a log cell is the fastest path from "I noticed this
85
+ * value" to a filtered view, and the reason it works is that the result is a
86
+ * query the reader can then see, edit and share — not a hidden filter stack
87
+ * with its own rules.
88
+ */
89
+ declare function applyCellAction(query: Query, action: QueryCellAction): Query;
90
+ //#endregion
91
+ //#region src/lib/query/query-parse.d.ts
92
+ /** A part of the input that could not be read as intended. */
93
+ interface QueryDiagnostic {
94
+ /** Character offset into the original string. */
95
+ at: number;
96
+ text: string;
97
+ message: string;
98
+ }
99
+ interface ParsedQuery {
100
+ query: Query;
101
+ diagnostics: QueryDiagnostic[];
102
+ /** The final clause is still being typed — the caller can complete it. */
103
+ trailing: {
104
+ field: string;
105
+ operator: QueryOperator;
106
+ value: string;
107
+ } | null;
108
+ }
109
+ /** Parse, keeping whatever could not be understood as a plain term. */
110
+ declare function parseQueryWithDiagnostics(input: string): ParsedQuery;
111
+ /** Parse, discarding diagnostics. */
112
+ declare function parseQuery(input: string): Query;
113
+ //#endregion
114
+ //#region src/lib/query/query-format.d.ts
115
+ /** Quote a value that would otherwise re-parse as something else. */
116
+ declare function quoteValue(value: string): string;
117
+ /** One clause as text. */
118
+ declare function formatClause(clause: QueryClause): string;
119
+ /** The whole query as text. */
120
+ declare function formatQuery(query: Query): string;
121
+ /** A short human label for a clause chip. */
122
+ declare function describeClause(clause: QueryClause): string;
123
+ //#endregion
124
+ //#region src/lib/query/query-match.d.ts
125
+ /** How a row exposes its values. Missing fields return `undefined`. */
126
+ type FieldReader = (field: string) => unknown;
127
+ /**
128
+ * Whether one row satisfies every clause.
129
+ *
130
+ * `searchable` is lazy because most queries are field constraints, and
131
+ * flattening a row to text for a term match nobody asked for is the expensive
132
+ * part of filtering a live tail.
133
+ */
134
+ declare function matchesQuery(query: Query, read: FieldReader, searchable: () => string): boolean;
135
+ /** Convenience for plain objects, including one level of nested properties. */
136
+ declare function matchesQueryObject(query: Query, row: Record<string, unknown>, options?: {
137
+ nested?: string;
138
+ }): boolean;
139
+ //#endregion
140
+ export { EMPTY_QUERY, type FieldReader, type ParsedQuery, type Query, type QueryCellAction, type QueryClause, type QueryDiagnostic, type QueryFieldClause, type QueryOperator, type QueryTermClause, addClause, applyCellAction, describeClause, fieldValues, formatClause, formatQuery, isEmptyQuery, matchesQuery, matchesQueryObject, parseQuery, parseQueryWithDiagnostics, queryFields, quoteValue, removeClause, withFieldValue, withoutField };
141
+ //# sourceMappingURL=query.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.cts","names":[],"sources":["../src/lib/query/query-ast.ts","../src/lib/query/query-parse.ts","../src/lib/query/query-format.ts","../src/lib/query/query-match.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8BY;;UAUK;EACf;EACA;EACA;;;UAIe;EACf;EACA;EACA,UAAU;EACV;EACA;;KAGU,cAAc,kBAAkB;;UAG3B;EACf,SAAS;;cAGE,aAAa;;iBAGV,aAAa,OAAO;;iBAyBpB,UAAU,OAAO,OAAO,QAAQ,cAAc;;;;;;;;iBAc9C,eACd,OAAO,OACP,eACA,eACA;EAAW;EAAmB,WAAW;IACxC;;iBAsBa,aAAa,OAAO,OAAO,gBAAgB;;iBAS3C,aAAa,OAAO,OAAO,gBAAgB;;iBAK3C,YAAY,OAAO;;iBAWnB,YAAY,OAAO,OAAO;;UAazB;EACf;EACA;EACA;;;;;;;;;;iBAWc,gBAAgB,OAAO,OAAO,QAAQ,kBAAkB;;;;UCjKvD;;EAEf;EACA;EACA;;UAGe;EACf,OAAO;EACP,aAAa;;EAEb;IAAY;IAAe,UAAU;IAAe;;;;iBAoHtC,0BAA0B,gBAAgB;;iBAoG1C,WAAW,gBAAgB;;;;iBCrO3B,WAAW;;iBAOX,aAAa,QAAQ;;iBAgBrB,YAAY,OAAO;;iBAKnB,eAAe,QAAQ;;;;KCjC3B,eAAe;;;;;;;;iBA8DX,aACd,OAAO,OACP,MAAM,aACN;;iBASc,mBACd,OAAO,OACP,KAAK,yBACL;EAAW"}
@@ -0,0 +1,141 @@
1
+ //#region src/lib/query/query-ast.d.ts
2
+ /**
3
+ * A search query, as a value.
4
+ *
5
+ * Free-text search and a row of filter pickers answer different halves of the
6
+ * same question, and neither can express the other: a picker cannot say "not
7
+ * this", and a text box cannot say "this field, exactly". A parsed query is one
8
+ * thing that does both, and — because it is a value rather than a widget's
9
+ * internal state — it survives a URL, a saved view, and the trip to a server
10
+ * that has to turn it into a database filter.
11
+ *
12
+ * The grammar follows the one analytics tools have converged on (Elastic's
13
+ * `EuiSearchBar`, Kibana, GitHub's issue search), minus the parts that only
14
+ * make sense against a Lucene index:
15
+ *
16
+ * error a term anywhere in the row
17
+ * "connection reset" a phrase, kept together
18
+ * -health a term that must not appear
19
+ * path:/checkout a field equal to a value
20
+ * path:(/a or /b) a field equal to any of several values
21
+ * -status:200 a field that must not equal a value
22
+ * status>=500 a comparison
23
+ * path:*checkout* the field contains this somewhere
24
+ * utm_source:* the field is present at all
25
+ *
26
+ * Clauses combine with AND; `or` only groups values inside one field. That is
27
+ * a deliberate ceiling: arbitrary boolean nesting reads worse than it queries,
28
+ * and every backend this targets can express the restricted form directly.
29
+ */
30
+ /** How a field clause compares. */
31
+ type QueryOperator = "eq" | "contains" | "gt" | "gte" | "lt" | "lte" | "exists";
32
+ /** A bare or quoted word matched against the whole row. */
33
+ interface QueryTermClause {
34
+ type: "term";
35
+ value: string;
36
+ negated: boolean;
37
+ }
38
+ /** A constraint on one named field. Several values read as "any of". */
39
+ interface QueryFieldClause {
40
+ type: "field";
41
+ field: string;
42
+ operator: QueryOperator;
43
+ values: string[];
44
+ negated: boolean;
45
+ }
46
+ type QueryClause = QueryTermClause | QueryFieldClause;
47
+ /** A parsed query: clauses combined with AND. */
48
+ interface Query {
49
+ clauses: QueryClause[];
50
+ }
51
+ declare const EMPTY_QUERY: Query;
52
+ /** Whether a query constrains anything at all. */
53
+ declare function isEmptyQuery(query: Query | null | undefined): boolean;
54
+ /** Add a clause unless an identical one is already there. */
55
+ declare function addClause(query: Query, clause: QueryClause): Query;
56
+ /**
57
+ * Constrain a field to a value — what a cell's "filter for this" action does.
58
+ *
59
+ * Applying the opposite of a constraint already present replaces it rather than
60
+ * adding it: `status:200` then "filter out 200" should read as one changed
61
+ * mind, not as a query that can never match.
62
+ */
63
+ declare function withFieldValue(query: Query, field: string, value: string, options?: {
64
+ negated?: boolean;
65
+ operator?: QueryOperator;
66
+ }): Query;
67
+ /** Drop every clause naming `field`. */
68
+ declare function withoutField(query: Query, field: string): Query;
69
+ /** Drop the clause at `index`, for a removable chip. */
70
+ declare function removeClause(query: Query, index: number): Query;
71
+ /** Every field the query names, in the order they first appear. */
72
+ declare function queryFields(query: Query): string[];
73
+ /** The values a field is constrained to, if any — for showing a filter as active. */
74
+ declare function fieldValues(query: Query, field: string): string[];
75
+ /** What a "filter for / filter out" control on a value asks for. */
76
+ interface QueryCellAction {
77
+ field: string;
78
+ value: string;
79
+ intent: "include" | "exclude";
80
+ }
81
+ /**
82
+ * Fold a cell action into the query.
83
+ *
84
+ * The pair of buttons on a log cell is the fastest path from "I noticed this
85
+ * value" to a filtered view, and the reason it works is that the result is a
86
+ * query the reader can then see, edit and share — not a hidden filter stack
87
+ * with its own rules.
88
+ */
89
+ declare function applyCellAction(query: Query, action: QueryCellAction): Query;
90
+ //#endregion
91
+ //#region src/lib/query/query-parse.d.ts
92
+ /** A part of the input that could not be read as intended. */
93
+ interface QueryDiagnostic {
94
+ /** Character offset into the original string. */
95
+ at: number;
96
+ text: string;
97
+ message: string;
98
+ }
99
+ interface ParsedQuery {
100
+ query: Query;
101
+ diagnostics: QueryDiagnostic[];
102
+ /** The final clause is still being typed — the caller can complete it. */
103
+ trailing: {
104
+ field: string;
105
+ operator: QueryOperator;
106
+ value: string;
107
+ } | null;
108
+ }
109
+ /** Parse, keeping whatever could not be understood as a plain term. */
110
+ declare function parseQueryWithDiagnostics(input: string): ParsedQuery;
111
+ /** Parse, discarding diagnostics. */
112
+ declare function parseQuery(input: string): Query;
113
+ //#endregion
114
+ //#region src/lib/query/query-format.d.ts
115
+ /** Quote a value that would otherwise re-parse as something else. */
116
+ declare function quoteValue(value: string): string;
117
+ /** One clause as text. */
118
+ declare function formatClause(clause: QueryClause): string;
119
+ /** The whole query as text. */
120
+ declare function formatQuery(query: Query): string;
121
+ /** A short human label for a clause chip. */
122
+ declare function describeClause(clause: QueryClause): string;
123
+ //#endregion
124
+ //#region src/lib/query/query-match.d.ts
125
+ /** How a row exposes its values. Missing fields return `undefined`. */
126
+ type FieldReader = (field: string) => unknown;
127
+ /**
128
+ * Whether one row satisfies every clause.
129
+ *
130
+ * `searchable` is lazy because most queries are field constraints, and
131
+ * flattening a row to text for a term match nobody asked for is the expensive
132
+ * part of filtering a live tail.
133
+ */
134
+ declare function matchesQuery(query: Query, read: FieldReader, searchable: () => string): boolean;
135
+ /** Convenience for plain objects, including one level of nested properties. */
136
+ declare function matchesQueryObject(query: Query, row: Record<string, unknown>, options?: {
137
+ nested?: string;
138
+ }): boolean;
139
+ //#endregion
140
+ export { EMPTY_QUERY, type FieldReader, type ParsedQuery, type Query, type QueryCellAction, type QueryClause, type QueryDiagnostic, type QueryFieldClause, type QueryOperator, type QueryTermClause, addClause, applyCellAction, describeClause, fieldValues, formatClause, formatQuery, isEmptyQuery, matchesQuery, matchesQueryObject, parseQuery, parseQueryWithDiagnostics, queryFields, quoteValue, removeClause, withFieldValue, withoutField };
141
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","names":[],"sources":["../src/lib/query/query-ast.ts","../src/lib/query/query-parse.ts","../src/lib/query/query-format.ts","../src/lib/query/query-match.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8BY;;UAUK;EACf;EACA;EACA;;;UAIe;EACf;EACA;EACA,UAAU;EACV;EACA;;KAGU,cAAc,kBAAkB;;UAG3B;EACf,SAAS;;cAGE,aAAa;;iBAGV,aAAa,OAAO;;iBAyBpB,UAAU,OAAO,OAAO,QAAQ,cAAc;;;;;;;;iBAc9C,eACd,OAAO,OACP,eACA,eACA;EAAW;EAAmB,WAAW;IACxC;;iBAsBa,aAAa,OAAO,OAAO,gBAAgB;;iBAS3C,aAAa,OAAO,OAAO,gBAAgB;;iBAK3C,YAAY,OAAO;;iBAWnB,YAAY,OAAO,OAAO;;UAazB;EACf;EACA;EACA;;;;;;;;;;iBAWc,gBAAgB,OAAO,OAAO,QAAQ,kBAAkB;;;;UCjKvD;;EAEf;EACA;EACA;;UAGe;EACf,OAAO;EACP,aAAa;;EAEb;IAAY;IAAe,UAAU;IAAe;;;;iBAoHtC,0BAA0B,gBAAgB;;iBAoG1C,WAAW,gBAAgB;;;;iBCrO3B,WAAW;;iBAOX,aAAa,QAAQ;;iBAgBrB,YAAY,OAAO;;iBAKnB,eAAe,QAAQ;;;;KCjC3B,eAAe;;;;;;;;iBA8DX,aACd,OAAO,OACP,MAAM,aACN;;iBASc,mBACd,OAAO,OACP,KAAK,yBACL;EAAW"}
package/dist/query.js ADDED
@@ -0,0 +1,371 @@
1
+ //#region src/lib/query/query-ast.ts
2
+ const EMPTY_QUERY = { clauses: [] };
3
+ /** Whether a query constrains anything at all. */
4
+ function isEmptyQuery(query) {
5
+ return !query || query.clauses.length === 0;
6
+ }
7
+ function sameClause(a, b) {
8
+ if (a.type !== b.type) return false;
9
+ if (a.type === "term" && b.type === "term") return a.value === b.value && a.negated === b.negated;
10
+ const fieldA = a;
11
+ const fieldB = b;
12
+ return fieldA.field === fieldB.field && fieldA.operator === fieldB.operator && fieldA.negated === fieldB.negated && fieldA.values.length === fieldB.values.length && fieldA.values.every((value, index) => value === fieldB.values[index]);
13
+ }
14
+ /** Add a clause unless an identical one is already there. */
15
+ function addClause(query, clause) {
16
+ if (query.clauses.some((existing) => sameClause(existing, clause))) return query;
17
+ return { clauses: [...query.clauses, clause] };
18
+ }
19
+ /**
20
+ * Constrain a field to a value — what a cell's "filter for this" action does.
21
+ *
22
+ * Applying the opposite of a constraint already present replaces it rather than
23
+ * adding it: `status:200` then "filter out 200" should read as one changed
24
+ * mind, not as a query that can never match.
25
+ */
26
+ function withFieldValue(query, field, value, options = {}) {
27
+ var _options$negated, _options$operator;
28
+ const negated = (_options$negated = options.negated) !== null && _options$negated !== void 0 ? _options$negated : false;
29
+ const operator = (_options$operator = options.operator) !== null && _options$operator !== void 0 ? _options$operator : "eq";
30
+ return { clauses: [...query.clauses.filter((clause) => !(clause.type === "field" && clause.field === field && clause.operator === operator && clause.values.length === 1 && clause.values[0] === value)), {
31
+ type: "field",
32
+ field,
33
+ operator,
34
+ values: [value],
35
+ negated
36
+ }] };
37
+ }
38
+ /** Drop every clause naming `field`. */
39
+ function withoutField(query, field) {
40
+ return { clauses: query.clauses.filter((clause) => clause.type !== "field" || clause.field !== field) };
41
+ }
42
+ /** Drop the clause at `index`, for a removable chip. */
43
+ function removeClause(query, index) {
44
+ return { clauses: query.clauses.filter((_, at) => at !== index) };
45
+ }
46
+ /** Every field the query names, in the order they first appear. */
47
+ function queryFields(query) {
48
+ const seen = [];
49
+ for (const clause of query.clauses) if (clause.type === "field" && !seen.includes(clause.field)) seen.push(clause.field);
50
+ return seen;
51
+ }
52
+ /** The values a field is constrained to, if any — for showing a filter as active. */
53
+ function fieldValues(query, field) {
54
+ return query.clauses.flatMap((clause) => clause.type === "field" && clause.field === field && !clause.negated ? clause.values : []);
55
+ }
56
+ /**
57
+ * Fold a cell action into the query.
58
+ *
59
+ * The pair of buttons on a log cell is the fastest path from "I noticed this
60
+ * value" to a filtered view, and the reason it works is that the result is a
61
+ * query the reader can then see, edit and share — not a hidden filter stack
62
+ * with its own rules.
63
+ */
64
+ function applyCellAction(query, action) {
65
+ return withFieldValue(query, action.field, action.value, { negated: action.intent === "exclude" });
66
+ }
67
+ //#endregion
68
+ //#region src/lib/query/query-parse.ts
69
+ const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
70
+ /**
71
+ * Split on whitespace, but keep quoted runs and parenthesised groups whole.
72
+ *
73
+ * An unterminated quote or group swallows the rest of the line rather than
74
+ * erroring: the reader is mid-word, and the alternative is the query flickering
75
+ * between two different meanings as they type the closing character.
76
+ */
77
+ function tokenize(input) {
78
+ const tokens = [];
79
+ let index = 0;
80
+ while (index < input.length) {
81
+ while (index < input.length && /\s/.test(input[index])) index += 1;
82
+ if (index >= input.length) break;
83
+ const start = index;
84
+ let text = "";
85
+ let quoted = false;
86
+ let atTokenStart = true;
87
+ let depth = 0;
88
+ while (index < input.length) {
89
+ const char = input[index];
90
+ if (char === "\"") {
91
+ if (atTokenStart) quoted = true;
92
+ atTokenStart = false;
93
+ index += 1;
94
+ while (index < input.length && input[index] !== "\"") {
95
+ text += input[index];
96
+ index += 1;
97
+ }
98
+ index += 1;
99
+ continue;
100
+ }
101
+ if (char === "(") depth += 1;
102
+ if (char === ")") depth = Math.max(0, depth - 1);
103
+ if (/\s/.test(char) && depth === 0) break;
104
+ atTokenStart = false;
105
+ text += char;
106
+ index += 1;
107
+ }
108
+ if (text.length > 0 || quoted) tokens.push({
109
+ text,
110
+ at: start,
111
+ quoted
112
+ });
113
+ }
114
+ return tokens;
115
+ }
116
+ /** `(a or b)` → `["a", "b"]`; anything else is a single value. */
117
+ function readValues(raw) {
118
+ const trimmed = raw.trim();
119
+ if (!trimmed.startsWith("(")) return [stripQuotes(trimmed)];
120
+ return trimmed.replace(/^\(/, "").replace(/\)$/, "").split(/\s+or\s+/i).map((part) => stripQuotes(part.trim())).filter((part) => part.length > 0);
121
+ }
122
+ function stripQuotes(value) {
123
+ if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
124
+ return value.replace(/^"/, "");
125
+ }
126
+ const OPERATORS = [
127
+ {
128
+ symbol: ">=",
129
+ operator: "gte"
130
+ },
131
+ {
132
+ symbol: "<=",
133
+ operator: "lte"
134
+ },
135
+ {
136
+ symbol: ":",
137
+ operator: "eq"
138
+ },
139
+ {
140
+ symbol: ">",
141
+ operator: "gt"
142
+ },
143
+ {
144
+ symbol: "<",
145
+ operator: "lt"
146
+ }
147
+ ];
148
+ /** Where a field operator starts in the token, or `-1`. */
149
+ function findOperator(text) {
150
+ let best = null;
151
+ for (const { symbol, operator } of OPERATORS) {
152
+ const index = text.indexOf(symbol);
153
+ if (index <= 0) continue;
154
+ if (!best || index < best.index || index === best.index && symbol.length > best.symbol.length) best = {
155
+ index,
156
+ symbol,
157
+ operator
158
+ };
159
+ }
160
+ return best;
161
+ }
162
+ /** Parse, keeping whatever could not be understood as a plain term. */
163
+ function parseQueryWithDiagnostics(input) {
164
+ const diagnostics = [];
165
+ const clauses = [];
166
+ let trailing = null;
167
+ for (const token of tokenize(input)) {
168
+ let text = token.text;
169
+ let negated = false;
170
+ if (!token.quoted && text.startsWith("-") && text.length > 1) {
171
+ negated = true;
172
+ text = text.slice(1);
173
+ }
174
+ const operator = token.quoted ? null : findOperator(text);
175
+ if (!operator) {
176
+ if (text.length > 0) clauses.push({
177
+ type: "term",
178
+ value: text,
179
+ negated
180
+ });
181
+ continue;
182
+ }
183
+ const field = text.slice(0, operator.index);
184
+ const rawValue = text.slice(operator.index + operator.symbol.length);
185
+ if (!FIELD_NAME.test(field)) {
186
+ clauses.push({
187
+ type: "term",
188
+ value: text,
189
+ negated
190
+ });
191
+ continue;
192
+ }
193
+ if (rawValue === "") {
194
+ trailing = {
195
+ field,
196
+ operator: operator.operator,
197
+ value: ""
198
+ };
199
+ continue;
200
+ }
201
+ if (rawValue === "*" && operator.operator === "eq") {
202
+ clauses.push({
203
+ type: "field",
204
+ field,
205
+ operator: "exists",
206
+ values: [],
207
+ negated
208
+ });
209
+ continue;
210
+ }
211
+ if (operator.operator === "eq" && rawValue.length > 2 && rawValue.startsWith("*") && rawValue.endsWith("*")) {
212
+ clauses.push({
213
+ type: "field",
214
+ field,
215
+ operator: "contains",
216
+ values: [stripQuotes(rawValue.slice(1, -1))],
217
+ negated
218
+ });
219
+ continue;
220
+ }
221
+ const values = readValues(rawValue);
222
+ if (values.length === 0) {
223
+ diagnostics.push({
224
+ at: token.at,
225
+ text: token.text,
226
+ message: "Empty value group"
227
+ });
228
+ continue;
229
+ }
230
+ if (operator.operator !== "eq" && values.length > 1) diagnostics.push({
231
+ at: token.at,
232
+ text: token.text,
233
+ message: "A comparison takes a single value"
234
+ });
235
+ const clause = {
236
+ type: "field",
237
+ field,
238
+ operator: operator.operator,
239
+ values: operator.operator === "eq" ? values : values.slice(0, 1),
240
+ negated
241
+ };
242
+ clauses.push(clause);
243
+ if (!rawValue.startsWith("(")) {
244
+ var _values$;
245
+ trailing = {
246
+ field,
247
+ operator: operator.operator,
248
+ value: (_values$ = values[0]) !== null && _values$ !== void 0 ? _values$ : ""
249
+ };
250
+ }
251
+ }
252
+ return {
253
+ query: { clauses },
254
+ diagnostics,
255
+ trailing
256
+ };
257
+ }
258
+ /** Parse, discarding diagnostics. */
259
+ function parseQuery(input) {
260
+ return parseQueryWithDiagnostics(input).query;
261
+ }
262
+ //#endregion
263
+ //#region src/lib/query/query-format.ts
264
+ const OPERATOR_SYMBOL = {
265
+ eq: ":",
266
+ contains: ":",
267
+ gt: ">",
268
+ gte: ">=",
269
+ lt: "<",
270
+ lte: "<=",
271
+ exists: ":"
272
+ };
273
+ /** Quote a value that would otherwise re-parse as something else. */
274
+ function quoteValue(value) {
275
+ return /[\s"()]/.test(value) || value === "" ? `"${value.replace(/"/g, "")}"` : value;
276
+ }
277
+ /** One clause as text. */
278
+ function formatClause(clause) {
279
+ var _clause$values$2;
280
+ const prefix = clause.negated ? "-" : "";
281
+ if (clause.type === "term") return `${prefix}${quoteValue(clause.value)}`;
282
+ if (clause.operator === "exists") return `${prefix}${clause.field}:*`;
283
+ if (clause.operator === "contains") {
284
+ var _clause$values$;
285
+ return `${prefix}${clause.field}:*${(_clause$values$ = clause.values[0]) !== null && _clause$values$ !== void 0 ? _clause$values$ : ""}*`;
286
+ }
287
+ const symbol = OPERATOR_SYMBOL[clause.operator];
288
+ if (clause.values.length > 1) return `${prefix}${clause.field}${symbol}(${clause.values.map(quoteValue).join(" or ")})`;
289
+ return `${prefix}${clause.field}${symbol}${quoteValue((_clause$values$2 = clause.values[0]) !== null && _clause$values$2 !== void 0 ? _clause$values$2 : "")}`;
290
+ }
291
+ /** The whole query as text. */
292
+ function formatQuery(query) {
293
+ return query.clauses.map(formatClause).join(" ");
294
+ }
295
+ /** A short human label for a clause chip. */
296
+ function describeClause(clause) {
297
+ if (clause.type === "term") return clause.negated ? `not "${clause.value}"` : `"${clause.value}"`;
298
+ if (clause.operator === "exists") return clause.negated ? `${clause.field} missing` : `${clause.field} present`;
299
+ if (clause.operator === "contains") {
300
+ var _clause$values$3;
301
+ return `${clause.negated ? "not " : ""}${clause.field} ⊃ ${(_clause$values$3 = clause.values[0]) !== null && _clause$values$3 !== void 0 ? _clause$values$3 : ""}`;
302
+ }
303
+ const values = clause.values.join(", ");
304
+ const symbol = OPERATOR_SYMBOL[clause.operator];
305
+ return `${clause.negated ? "not " : ""}${clause.field} ${symbol === ":" ? "=" : symbol} ${values}`;
306
+ }
307
+ //#endregion
308
+ //#region src/lib/query/query-match.ts
309
+ function toText(value) {
310
+ if (value === null || value === void 0) return "";
311
+ if (typeof value === "string") return value;
312
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
313
+ return JSON.stringify(value);
314
+ }
315
+ function compare(left, right) {
316
+ const leftNumber = typeof left === "number" ? left : Number(toText(left));
317
+ const rightNumber = Number(right);
318
+ if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) return leftNumber - rightNumber;
319
+ return toText(left).localeCompare(right);
320
+ }
321
+ function matchesClause(clause, read, searchable) {
322
+ if (clause.type === "term") return searchable().toLowerCase().includes(clause.value.toLowerCase());
323
+ const value = read(clause.field);
324
+ switch (clause.operator) {
325
+ case "exists": return value !== void 0 && value !== null && value !== "";
326
+ case "eq": return clause.values.some((candidate) => toText(value).toLowerCase() === candidate.toLowerCase());
327
+ case "contains": return clause.values.some((candidate) => toText(value).toLowerCase().includes(candidate.toLowerCase()));
328
+ case "gt":
329
+ var _clause$values$;
330
+ return compare(value, (_clause$values$ = clause.values[0]) !== null && _clause$values$ !== void 0 ? _clause$values$ : "") > 0;
331
+ case "gte":
332
+ var _clause$values$2;
333
+ return compare(value, (_clause$values$2 = clause.values[0]) !== null && _clause$values$2 !== void 0 ? _clause$values$2 : "") >= 0;
334
+ case "lt":
335
+ var _clause$values$3;
336
+ return compare(value, (_clause$values$3 = clause.values[0]) !== null && _clause$values$3 !== void 0 ? _clause$values$3 : "") < 0;
337
+ case "lte":
338
+ var _clause$values$4;
339
+ return compare(value, (_clause$values$4 = clause.values[0]) !== null && _clause$values$4 !== void 0 ? _clause$values$4 : "") <= 0;
340
+ default: return true;
341
+ }
342
+ }
343
+ /**
344
+ * Whether one row satisfies every clause.
345
+ *
346
+ * `searchable` is lazy because most queries are field constraints, and
347
+ * flattening a row to text for a term match nobody asked for is the expensive
348
+ * part of filtering a live tail.
349
+ */
350
+ function matchesQuery(query, read, searchable) {
351
+ return query.clauses.every((clause) => {
352
+ const matched = matchesClause(clause, read, searchable);
353
+ return clause.negated ? !matched : matched;
354
+ });
355
+ }
356
+ /** Convenience for plain objects, including one level of nested properties. */
357
+ function matchesQueryObject(query, row, options = {}) {
358
+ const nested = options.nested ? row[options.nested] : void 0;
359
+ const read = (field) => field in row ? row[field] : nested === null || nested === void 0 ? void 0 : nested[field];
360
+ let cached = null;
361
+ const searchable = () => {
362
+ var _cached;
363
+ (_cached = cached) !== null && _cached !== void 0 || (cached = [...Object.values(row).filter((value) => typeof value !== "object"), ...Object.values(nested !== null && nested !== void 0 ? nested : {})].map(toText).join(" "));
364
+ return cached;
365
+ };
366
+ return matchesQuery(query, read, searchable);
367
+ }
368
+ //#endregion
369
+ export { EMPTY_QUERY, addClause, applyCellAction, describeClause, fieldValues, formatClause, formatQuery, isEmptyQuery, matchesQuery, matchesQueryObject, parseQuery, parseQueryWithDiagnostics, queryFields, quoteValue, removeClause, withFieldValue, withoutField };
370
+
371
+ //# sourceMappingURL=query.js.map