@object-ui/components 17.6.0 → 18.0.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 (72) hide show
  1. package/CHANGELOG.md +9618 -0
  2. package/README.md +27 -3
  3. package/dist/custom/RecordTitleChip.d.ts +4 -3
  4. package/dist/custom/button-group.d.ts +1 -1
  5. package/dist/custom/combobox.d.ts +2 -4
  6. package/dist/custom/dialog-viewport-gutter.d.ts +37 -0
  7. package/dist/custom/empty-state-link.d.ts +63 -0
  8. package/dist/custom/empty.d.ts +16 -4
  9. package/dist/custom/field.d.ts +1 -1
  10. package/dist/custom/filter-builder.d.ts +225 -77
  11. package/dist/custom/grouping-editor.d.ts +4 -0
  12. package/dist/custom/horizontal-overflow.d.ts +18 -0
  13. package/dist/custom/index.d.ts +4 -1
  14. package/dist/custom/item.d.ts +1 -1
  15. package/dist/custom/kbd.d.ts +1 -1
  16. package/dist/custom/mobile-dialog-content.d.ts +22 -1
  17. package/dist/custom/navigation-overlay.d.ts +105 -0
  18. package/dist/custom/refresh-indicator.d.ts +8 -2
  19. package/dist/custom/sort-builder.d.ts +8 -0
  20. package/dist/custom/use-horizontal-overflow.d.ts +36 -0
  21. package/dist/custom/view-states.d.ts +74 -1
  22. package/dist/hooks/related-count-store.d.ts +81 -28
  23. package/dist/index.css +345 -361
  24. package/dist/index.d.ts +11 -3
  25. package/dist/index.js +8480 -6628
  26. package/dist/index.umd.cjs +11 -15
  27. package/dist/lib/close-label.d.ts +1 -2
  28. package/dist/lib/date-fns-locale.d.ts +41 -0
  29. package/dist/lib/inline-markdown.d.ts +64 -0
  30. package/dist/lib/lazy-icon.d.ts +32 -1
  31. package/dist/lib/lucide-record-icon-names.d.ts +30 -0
  32. package/dist/lib/utils.d.ts +113 -0
  33. package/dist/renderers/action/action-bar.d.ts +8 -36
  34. package/dist/renderers/action/action-button.d.ts +15 -4
  35. package/dist/renderers/action/action-group.d.ts +5 -4
  36. package/dist/renderers/action/action-icon.d.ts +12 -2
  37. package/dist/renderers/action/action-menu.d.ts +5 -2
  38. package/dist/renderers/action/auto-trigger.d.ts +19 -4
  39. package/dist/renderers/action/disabled-reason.d.ts +49 -0
  40. package/dist/renderers/action/resolve-icon.d.ts +27 -2
  41. package/dist/renderers/action/static-params.d.ts +71 -0
  42. package/dist/renderers/action/visibility-gate.d.ts +11 -3
  43. package/dist/renderers/basic/data-list.d.ts +2 -1
  44. package/dist/renderers/basic/elements.d.ts +2 -0
  45. package/dist/renderers/basic/html-elements.d.ts +7 -3
  46. package/dist/renderers/basic/readProps.d.ts +60 -0
  47. package/dist/renderers/basic/record-picker.d.ts +3 -2
  48. package/dist/renderers/complex/data-table.d.ts +31 -14
  49. package/dist/renderers/complex/dataTableBindDiagnostic.d.ts +187 -0
  50. package/dist/renderers/layout/box.d.ts +8 -0
  51. package/dist/renderers/layout/containers.d.ts +28 -23
  52. package/dist/renderers/layout/page-block-grid.d.ts +29 -0
  53. package/dist/renderers/layout/page-block-span.d.ts +50 -0
  54. package/dist/renderers/layout/page-description-context.d.ts +14 -0
  55. package/dist/renderers/layout/page-print.d.ts +14 -0
  56. package/dist/renderers/layout/page-region-posture.d.ts +82 -0
  57. package/dist/renderers/layout/react-page.d.ts +4 -0
  58. package/dist/renderers/navigation/sidebar.d.ts +31 -0
  59. package/dist/ui/badge.d.ts +1 -1
  60. package/dist/ui/button.d.ts +1 -1
  61. package/dist/ui/calendar.d.ts +2 -1
  62. package/dist/ui/command.d.ts +7 -7
  63. package/dist/ui/index.d.ts +0 -1
  64. package/dist/ui/input-otp.d.ts +2 -2
  65. package/dist/ui/sheet.d.ts +1 -1
  66. package/dist/ui/sidebar.d.ts +2 -1
  67. package/package.json +14 -15
  68. package/dist/SchemaRenderer.d.ts +0 -4
  69. package/dist/custom/export-progress-dialog.d.ts +0 -43
  70. package/dist/hooks/use-export-job.d.ts +0 -34
  71. package/dist/ui/chart.d.ts +0 -56
  72. package/dist/ui/toast.d.ts +0 -22
@@ -1,3 +1,4 @@
1
+ import { FilterBuilderCondition as AuthoredFilterBuilderCondition, FilterGroup as AuthoredFilterGroup } from '@object-ui/types';
1
2
  /**
2
3
  * ObjectUI
3
4
  * Copyright (c) 2024-present ObjectStack Inc.
@@ -16,22 +17,53 @@ import * as React from "react";
16
17
  * and `operator` here is the builder's own dropdown vocabulary
17
18
  * (`FILTER_BUILDER_OPERATORS`), not the spec's `$`-tokens.
18
19
  *
19
- * `@object-ui/types` already renamed its identical concept to
20
- * `FilterBuilderCondition` (objectui#3068); this is the same thing, so it takes
21
- * the same name rather than inventing a second dialect. Note the same name is
22
- * NOT the right answer everywhere: `@object-ui/app-shell`'s `FilterCondition`
23
- * really was the spec's ObjectQL AST and was re-exported (objectui#3169).
20
+ * DERIVED from `@object-ui/types`' `FilterBuilderCondition` (objectui#9306).
21
+ * That declaration is the one name authority for the row; this one takes every
22
+ * member from it (`id`, required since objectui#8415, and `field`) and restates
23
+ * exactly two, each a named extension:
24
+ *
25
+ * - `operator` — {@link FilterBuilderOperator}: the protocol's ids, which is
26
+ * what the authority declares, plus the opt-in `exists` / `notExists` the
27
+ * protocol has no member for (objectui#9559 ruling B keeps them opt-in and
28
+ * unfolded). It was `string`, which let a caller hand the builder an id no
29
+ * dropdown entry can hold.
30
+ * - `value` — REQUIRED and narrowed to the scalar-or-list union this builder
31
+ * edits, where the authority declares `value?: any`. Every exported helper
32
+ * below that takes a row's value (`reshapeFilterValue`, `retypeFilterValue`,
33
+ * `narrowFilterValueToOptions`, …) is typed on this member, so `any` here
34
+ * would switch their checking off in silence.
35
+ *
36
+ * Its own NAME, not the authority's (objectui#6349, batch 12). Under the
37
+ * authority's name this was a second declaration of `FilterBuilderCondition`
38
+ * with a different shape: the two are assignable in neither direction (the
39
+ * operator blocks one way, the value's requiredness the other), so an
40
+ * auto-import picked one of two rows by package. `FilterBuilderCondition` now
41
+ * means the authored row in `@object-ui/types` only, and this specialisation
42
+ * is named for the builder that edits it.
24
43
  */
25
- export interface FilterBuilderCondition {
26
- id: string;
27
- field: string;
28
- operator: string;
44
+ export interface FilterBuilderRow extends Omit<AuthoredFilterBuilderCondition, "operator" | "value"> {
45
+ operator: FilterBuilderOperator;
29
46
  value: string | number | boolean | (string | number | boolean)[];
30
47
  }
31
- export interface FilterGroup {
32
- id: string;
33
- logic: "and" | "or";
34
- conditions: FilterBuilderCondition[];
48
+ /**
49
+ * The group the FilterBuilder edits: one `logic` over FLAT rows.
50
+ *
51
+ * DERIVED from `@object-ui/types`' `FilterGroup` like the row above
52
+ * (objectui#9306), and named apart from it for the same reason
53
+ * (objectui#6349, batch 12): `FilterGroup` means the authored group only. Only
54
+ * `conditions` is restated, to hold THIS package's row; `id` and `logic` are
55
+ * the authority's own. So `id` is OPTIONAL here, as it is there: nothing in
56
+ * this builder reads a group's `id`, `onChange` hands back whatever group the
57
+ * host passed (an id-less group comes back id-less), and only `EMPTY_GROUP`
58
+ * invents one. The `id: string` this package used to declare described a
59
+ * group the builder never guaranteed.
60
+ *
61
+ * `conditions` is flat because the builder is: it draws each entry as one row
62
+ * and has no control that creates a group. The authority retired nested
63
+ * sub-groups in the same change and its zod mirror refuses one by name.
64
+ */
65
+ export interface FilterBuilderRowGroup extends Omit<AuthoredFilterGroup, "conditions"> {
66
+ conditions: FilterBuilderRow[];
35
67
  }
36
68
  export interface FilterBuilderProps {
37
69
  fields?: Array<{
@@ -49,8 +81,8 @@ export interface FilterBuilderProps {
49
81
  /** ID field on the referenced object (defaults to "id") */
50
82
  idField?: string;
51
83
  }>;
52
- value?: FilterGroup;
53
- onChange?: (value: FilterGroup) => void;
84
+ value?: FilterBuilderRowGroup;
85
+ onChange?: (value: FilterBuilderRowGroup) => void;
54
86
  className?: string;
55
87
  showClearAll?: boolean;
56
88
  /**
@@ -69,34 +101,34 @@ declare const defaultOperators: readonly [{
69
101
  readonly value: "equals";
70
102
  readonly label: "Equals";
71
103
  }, {
72
- readonly value: "notEquals";
104
+ readonly value: "not_equals";
73
105
  readonly label: "Does not equal";
74
106
  }, {
75
107
  readonly value: "contains";
76
108
  readonly label: "Contains";
77
109
  }, {
78
- readonly value: "containsCaseInsensitive";
110
+ readonly value: "icontains";
79
111
  readonly label: "Contains (ignore case)";
80
112
  }, {
81
- readonly value: "notContains";
113
+ readonly value: "not_contains";
82
114
  readonly label: "Does not contain";
83
115
  }, {
84
- readonly value: "isEmpty";
116
+ readonly value: "is_empty";
85
117
  readonly label: "Is empty";
86
118
  }, {
87
- readonly value: "isNotEmpty";
119
+ readonly value: "is_not_empty";
88
120
  readonly label: "Is not empty";
89
121
  }, {
90
- readonly value: "greaterThan";
122
+ readonly value: "greater_than";
91
123
  readonly label: "Greater than";
92
124
  }, {
93
- readonly value: "lessThan";
125
+ readonly value: "less_than";
94
126
  readonly label: "Less than";
95
127
  }, {
96
- readonly value: "greaterOrEqual";
128
+ readonly value: "greater_than_or_equal";
97
129
  readonly label: "Greater than or equal";
98
130
  }, {
99
- readonly value: "lessOrEqual";
131
+ readonly value: "less_than_or_equal";
100
132
  readonly label: "Less than or equal";
101
133
  }, {
102
134
  readonly value: "before";
@@ -111,19 +143,19 @@ declare const defaultOperators: readonly [{
111
143
  readonly value: "in";
112
144
  readonly label: "In";
113
145
  }, {
114
- readonly value: "notIn";
146
+ readonly value: "not_in";
115
147
  readonly label: "Not in";
116
148
  }, {
117
- readonly value: "startsWith";
149
+ readonly value: "starts_with";
118
150
  readonly label: "Starts with";
119
151
  }, {
120
- readonly value: "endsWith";
152
+ readonly value: "ends_with";
121
153
  readonly label: "Ends with";
122
154
  }, {
123
- readonly value: "isNull";
155
+ readonly value: "is_null";
124
156
  readonly label: "Is null";
125
157
  }, {
126
- readonly value: "isNotNull";
158
+ readonly value: "is_not_null";
127
159
  readonly label: "Is not null";
128
160
  }, {
129
161
  readonly value: "exists";
@@ -137,30 +169,79 @@ declare const defaultOperators: readonly [{
137
169
  * hold, derived from the operators it actually renders. Includes the opt-in
138
170
  * ids: they are drawable, just not offered unconditionally.
139
171
  *
140
- * Exported because several translation tables map an external vocabulary
141
- * (the spec's `VIEW_FILTER_OPERATORS`, Mongo `$`-tokens) *onto* this one, and
142
- * a value that is not in this list produces a condition row whose operator
143
- * select has nothing to select. Those tables assert against this export
144
- * rather than restating it, so adding an operator here is the only edit
145
- * needed to widen them.
172
+ * Since objectui#9306 these ARE the protocol's ids — the twenty members of the
173
+ * spec's `VIEW_FILTER_OPERATORS`, plus the opt-in `exists` / `notExists` the
174
+ * protocol has no member for — so a table that maps the spec's vocabulary onto
175
+ * this one is an identity over the twenty. It is still exported because those
176
+ * tables, and the parity pins over them, assert against this list rather than
177
+ * restating it: a value that is not in it produces a condition row whose
178
+ * operator select has nothing to select.
179
+ */
180
+ export declare const FILTER_BUILDER_OPERATORS: ("in" | "starts_with" | "ends_with" | "contains" | "equals" | "not_equals" | "greater_than" | "greater_than_or_equal" | "less_than" | "less_than_or_equal" | "before" | "after" | "not_in" | "not_contains" | "icontains" | "between" | "is_null" | "is_not_null" | "is_empty" | "is_not_empty" | "exists" | "notExists")[];
181
+ /**
182
+ * An operator id the FilterBuilder can render: the spec's `ViewFilterOperator`
183
+ * plus the two opt-in existence ids (objectui#9306). A type-level pin in
184
+ * `filter-builder-protocol-ids-9306.test.tsx` holds it EQUAL to that union, so
185
+ * a protocol member added upstream fails to compile there until the dropdown
186
+ * offers it.
146
187
  */
147
- export declare const FILTER_BUILDER_OPERATORS: ("endsWith" | "startsWith" | "in" | "after" | "equals" | "notEquals" | "contains" | "containsCaseInsensitive" | "notContains" | "isEmpty" | "isNotEmpty" | "greaterThan" | "lessThan" | "greaterOrEqual" | "lessOrEqual" | "before" | "between" | "notIn" | "isNull" | "isNotNull" | "exists" | "notExists")[];
148
- /** An operator id the FilterBuilder can render. */
149
188
  export type FilterBuilderOperator = (typeof defaultOperators)[number]['value'];
150
189
  /**
151
- * Operator ids for which this builder renders NO value input — so "no value"
152
- * is the row's FINISHED state, not an unfinished one (objectui#4744).
190
+ * The spelling a stored operator is READ as — the protocol id for every
191
+ * spelling the spec or this builder ever wrote (objectui#9306).
192
+ *
193
+ * The ruling this executes: camelCase is the deprecated alias form
194
+ * (objectui#7993), accepted on read and rewritten on write. So a filter stored
195
+ * as `{ operator: 'greaterOrEqual' }` keeps loading — it is folded here, through
196
+ * the spec's own `normalizeFilterOperator`, onto `greater_than_or_equal` — and
197
+ * the builder holds and emits that canonical id from then on, so the next write
198
+ * the author makes stores it. A lossless conversion on the read side, ⛔ not a
199
+ * second vocabulary: nothing this builder EMITS is ever a camelCase id.
200
+ *
201
+ * An id nothing folds (`exists`, `notExists`, a spelling from no vocabulary)
202
+ * comes back unchanged, exactly as the spec's normalizer returns it: inventing
203
+ * a mapping for a word the protocol does not contain would be a claim, not a
204
+ * repair.
205
+ *
206
+ * ⚠️ One guard on the spec's answer, and only because the builder now STORES
207
+ * what this returns (before objectui#9306 the fold was used for comparison
208
+ * only). `normalizeFilterOperator` is declared to return a string, but it
209
+ * looks the spelling up on a plain object, so an `Object.prototype` key comes
210
+ * back as whatever the prototype holds — `'constructor'` returns the `Object`
211
+ * function, measured on `@objectstack/spec` 17.4.0. Held in a row and written
212
+ * back, that would drop the row's operator on serialisation. A non-string
213
+ * answer is therefore treated as "nothing folds" and the spelling is kept;
214
+ * the defect itself belongs to the spec and is reported there, not patched
215
+ * around here in any other way.
216
+ */
217
+ export declare function normalizeFilterBuilderOperator(operator: string): string;
218
+ /**
219
+ * Operator ids — this dropdown's own ids, which are the protocol's canonical
220
+ * spellings since objectui#9306 — for which this builder renders no value
221
+ * input, so "no value" is the row's FINISHED state, not an unfinished one
222
+ * (objectui#4744).
153
223
  *
154
224
  * This is the source of truth for that distinction, and it lives here because
155
- * this component is the thing that decides it: `needsValueInput` below is
156
- * defined as the complement of this set, so the two cannot say different
157
- * things. Every consumer that has to tell a complete value-less row from a
158
- * half-filled one reads it FROM here rather than restating it:
225
+ * this component is the thing that decides it. But membership here is not the
226
+ * whole question: `needsValueInput` below asks it of the row's spelling AFTER
227
+ * {@link normalizeFilterBuilderOperator} (objectui#9302), so the gate answers
228
+ * "no value" for spellings that are NOT members — the deprecated camelCase ids
229
+ * a stored filter may still carry, and the alias rows the spec publishes for
230
+ * those same operators. A consumer holding a spelling that did not come from
231
+ * this dropdown must therefore fold it before asking, and fold this set's own
232
+ * members through the same normalizer too — the spec's `normalizeFilterOperator`
233
+ * is the identity on them, so that fold costs nothing and keeps the consumer
234
+ * correct whichever vocabulary this set is written in. How much wider the
235
+ * gate's preimage is is NOT restated here: the pin
236
+ * `filter-builder-valueless-canonical-spelling-9302.test.tsx` walks the
237
+ * spec's two published tables and names every row it measures, which is the
238
+ * only form of that answer that moves when those tables do (AGENTS.md #9).
239
+ *
240
+ * Every consumer that has to tell a complete value-less row from a half-filled
241
+ * one reads the membership FROM here rather than restating it:
159
242
  *
160
243
  * - `plugin-list`'s `convertFilterGroupToAST` — what the live grid QUERIES;
161
- * - `app-shell`'s `foldFilterGroupToSpecRules` — what a saved view PERSISTS
162
- * (its `VALUELESS_FILTER_OPERATORS` is this set plus the canonical spec
163
- * spellings, which only that layer sees).
244
+ * - `app-shell`'s `foldFilterGroupToSpecRules` — what a saved view PERSISTS.
164
245
  *
165
246
  * Placement is forced by the dependency graph — `app-shell` depends on
166
247
  * `plugin-list` depends on this package, so this is the only module all three
@@ -168,11 +249,12 @@ export type FilterBuilderOperator = (typeof defaultOperators)[number]['value'];
168
249
  * states is a fact about what this dropdown draws.
169
250
  *
170
251
  * Why it exists at all: each consumer used to keep its own copy, and the
171
- * live-grid copy listed only `isEmpty`/`isNotEmpty`. A fresh row is seeded
172
- * `{ operator: 'equals', value: '' }` and the operator dropdown preserves
173
- * `value`, so picking **Is null** as the first action left `value: ''` — the
174
- * grid read that as an unfinished row, dropped it, and applied NO filter at
175
- * all while the panel showed one. Silent, and every record came back.
252
+ * live-grid copy listed only the two empty-string operators. A fresh row is
253
+ * seeded `{ operator: 'equals', value: '' }` and the operator dropdown
254
+ * preserves `value`, so picking **Is null** as the first action left
255
+ * `value: ''` — the grid read that as an unfinished row, dropped it, and
256
+ * applied NO filter at all while the panel showed one. Silent, and every
257
+ * record came back.
176
258
  *
177
259
  * `exists` / `notExists` stay listed even though `OPT_IN_OPERATORS` withholds
178
260
  * them from most consumers (objectui#4736): the builder still draws them
@@ -193,13 +275,14 @@ export declare const VALUELESS_FILTER_BUILDER_OPERATORS: ReadonlySet<string>;
193
275
  */
194
276
  export type FilterValueArity = "scalar" | "list" | "pair";
195
277
  /**
196
- * Which value shape `operator` takes, in EITHER dialect.
278
+ * Which value shape `operator` takes, whichever spelling it arrives in.
197
279
  *
198
- * The argument is folded through the spec's own `normalizeFilterOperator`
199
- * first, so the builder's camelCase dropdown ids (`notIn`) and the canonical
200
- * spellings a stored rule carries (`not_in`) land on the same answer. That
201
- * fold is the whole point: one vocabulary, declared upstream, consulted here —
202
- * not a second local dialect that has to be kept in sync by hand.
280
+ * The argument is folded through {@link normalizeFilterBuilderOperator} — the
281
+ * spec's own `normalizeFilterOperator` plus the builder's one unfolded legacy
282
+ * id — first, so the canonical id this dropdown emits (`not_in`) and the
283
+ * deprecated alias a stored filter may still carry (`notIn`) land on the same
284
+ * answer. That fold is the whole point: one vocabulary, declared upstream,
285
+ * consulted here — not a second local dialect kept in sync by hand.
203
286
  *
204
287
  * @internal exported for tests
205
288
  */
@@ -224,13 +307,13 @@ export declare function filterValueArity(operator: string): FilterValueArity;
224
307
  *
225
308
  * @internal exported for tests
226
309
  */
227
- export declare function reshapeFilterValue(value: FilterBuilderCondition["value"], nextOperator: string): FilterBuilderCondition["value"];
310
+ export declare function reshapeFilterValue(value: FilterBuilderRow["value"], nextOperator: string): FilterBuilderRow["value"];
228
311
  /**
229
312
  * The operator a row keeps after its FIELD is changed, given the operators the
230
313
  * NEW field offers (objectui#4768).
231
314
  *
232
315
  * Each field type has its own operator bucket, and the buckets are not nested:
233
- * a `select` column offers `in` / `notIn`, a `text` column does not. Changing
316
+ * a `select` column offers `in` / `not_in`, a `text` column does not. Changing
234
317
  * the field used to write `{ field }` alone, so the row's operator survived
235
318
  * into a bucket that no longer contains it — `in` on a text column. The Radix
236
319
  * trigger then had nothing to render (its `SelectValue` matches against the
@@ -243,22 +326,80 @@ export declare function reshapeFilterValue(value: FilterBuilderCondition["value"
243
326
  * entry (`equals` for every bucket this builder draws), and the caller then
244
327
  * re-shapes the value for that operator's family.
245
328
  *
246
- * Membership is decided CANONICALLY, through the spec's own
247
- * `normalizeFilterOperator` — the same fold `filterValueArity` uses. A stored
248
- * rule can reach this builder spelled `not_in` while the dropdown lists the
249
- * alias `notIn`; those are one operator, so a select→lookup switch must not
250
- * silently rewrite the author's operator to `equals` just because the two
251
- * spellings differ. The fold is safe to compare through because it is
252
- * INJECTIVE over this builder's vocabulary — all 22 ids in `defaultOperators`
253
- * normalize to 22 distinct canonical operators, pinned in
254
- * `filter-builder-field-switch-operator.test.tsx` — so no two OFFERED
255
- * operators can ever collapse onto one another.
329
+ * Membership is decided CANONICALLY, through
330
+ * {@link normalizeFilterBuilderOperator} — the same fold `filterValueArity`
331
+ * uses. A caller can hand this helper a row spelled with the deprecated alias
332
+ * `notIn` while the dropdown lists the protocol id `not_in`; those are one
333
+ * operator, so a select→lookup switch must not silently rewrite the author's
334
+ * operator to `equals` just because the two spellings differ. The fold is safe
335
+ * to compare through because it is INJECTIVE over this builder's vocabulary —
336
+ * all 22 ids in `defaultOperators` normalize to 22 distinct canonical
337
+ * operators, pinned in `filter-builder-field-switch-operator.test.tsx` — so no
338
+ * two OFFERED operators can ever collapse onto one another.
256
339
  *
257
340
  * @internal exported for tests
258
341
  */
259
342
  export declare function reconcileOperatorForField(operator: string, offeredOperators: ReadonlyArray<{
260
343
  value: string;
261
344
  }>): string;
345
+ /**
346
+ * The offered id the operator trigger COMPARES against — the last site in this
347
+ * component that read the operator literally instead of through the spec's
348
+ * fold (objectui#7561).
349
+ *
350
+ * Radix matches `SelectValue` against the `SelectItem`s actually MOUNTED, and
351
+ * the mounted ids are this builder's own vocabulary — the protocol's canonical
352
+ * ids since objectui#9306 (they were camelCase when this helper was written). A
353
+ * row can hold the operator under another spelling of the SAME operator and
354
+ * still be perfectly valid:
355
+ *
356
+ * - a deprecated camelCase alias such as `greaterThan`, which a filter saved
357
+ * before objectui#9306 still carries;
358
+ * - the spec's alias table `gt` / `lt` / `eq`, which three schema-catalog
359
+ * entries author today.
360
+ *
361
+ * When this helper was written the mounted id was `greaterThan` and the stored
362
+ * spelling `greater_than`; neither matched the other literally, so the trigger
363
+ * drew BLANK over a row that filtered correctly — the user's own operator,
364
+ * invisible and unreachable.
365
+ * Everywhere the operator's MEANING matters this component already folds first
366
+ * ({@link filterValueArity}, {@link reconcileOperatorForField}); this was the
367
+ * one place it did not, and that omission — not the vocabulary divergence — is
368
+ * what produced the blank.
369
+ *
370
+ * So the comparison is made canonically and the MOUNTED spelling is handed to
371
+ * the trigger. Three things this deliberately does NOT do, each of them a
372
+ * separate ruling (objectui#7561):
373
+ *
374
+ * 1. it does not rewrite `condition.operator` — the row keeps the spelling it
375
+ * arrived with, exactly as {@link reconcileOperatorForField} keeps it, and
376
+ * nothing is written back on render. (Since objectui#9306 the BUILDER
377
+ * folds a row's spelling once, at its read boundary — see
378
+ * {@link normalizeFilterBuilderOperator} — so the rows it renders already
379
+ * hold protocol ids. This helper still folds both sides, because it is
380
+ * exported and its caller's rows need not have come through that
381
+ * boundary.)
382
+ * 2. it does not mount a new `SelectItem`, so the vocabulary the dropdown
383
+ * EMITS is byte-identical — contrast the value select above, where
384
+ * objectui#4874 ruling C mounts the outside-options value as its own
385
+ * option. That answer is right there and wrong here: the value's domain is
386
+ * the author's data, while the operator's domain is a declared vocabulary,
387
+ * and mounting a foreign spelling would admit a second one;
388
+ * 3. it does not widen what any schema ACCEPTS.
389
+ *
390
+ * Safe to compare through because the fold is INJECTIVE over this builder's
391
+ * vocabulary — all 22 ids in `defaultOperators` normalize to 22 distinct
392
+ * canonical operators, pinned in `filter-builder-field-switch-operator.test.tsx`
393
+ * — so no two OFFERED operators can collapse onto one another and the match is
394
+ * unambiguous. An operator no offered id folds onto falls through to the row's
395
+ * own spelling and still draws blank: inventing a label for a word this
396
+ * vocabulary does not contain would be a claim, not a repair.
397
+ *
398
+ * @internal exported for tests
399
+ */
400
+ export declare function mountedOperatorValue(operator: string, offeredOperators: ReadonlyArray<{
401
+ value: string;
402
+ }>): string;
262
403
  /**
263
404
  * Re-TYPE a row's `value` for the field it is being changed TO — the type
264
405
  * question, standing beside {@link reshapeFilterValue}'s shape question
@@ -285,7 +426,7 @@ export declare function reconcileOperatorForField(operator: string, offeredOpera
285
426
  * - `scalar` — converted, or `""`.
286
427
  * - `list` — converted ENTRY BY ENTRY, keeping the ones that carry:
287
428
  * `["42", "acme"]` → `[42]`, and `[]` when none do. Reachable today only
288
- * between the two buckets that offer `in`/`notIn` (`select` ↔ `lookup`),
429
+ * between the two buckets that offer `in`/`not_in` (`select` ↔ `lookup`),
289
430
  * which are both text-family, so the conversion is the identity there; it
290
431
  * is written for the family the operator lands in rather than for today's
291
432
  * buckets, and pinned directly on this helper.
@@ -296,7 +437,7 @@ export declare function reconcileOperatorForField(operator: string, offeredOpera
296
437
  *
297
438
  * @internal exported for tests
298
439
  */
299
- export declare function retypeFilterValue(value: FilterBuilderCondition["value"], fieldType: string | undefined, operator: string): FilterBuilderCondition["value"];
440
+ export declare function retypeFilterValue(value: FilterBuilderRow["value"], fieldType: string | undefined, operator: string): FilterBuilderRow["value"];
300
441
  /** The shape of a `fields` entry that the option-domain helpers below read. */
301
442
  type OptionBearingField = {
302
443
  type?: string;
@@ -344,7 +485,7 @@ type OptionBearingField = {
344
485
  *
345
486
  * @internal exported for tests
346
487
  */
347
- export declare function narrowFilterValueToOptions(value: FilterBuilderCondition["value"], field: OptionBearingField | undefined, operator: string): FilterBuilderCondition["value"];
488
+ export declare function narrowFilterValueToOptions(value: FilterBuilderRow["value"], field: OptionBearingField | undefined, operator: string): FilterBuilderRow["value"];
348
489
  /**
349
490
  * Is this row's value FINISHED for the operator it carries — the arity-aware
350
491
  * reading of "the user has filled this in" (objectstack#8815).
@@ -396,10 +537,17 @@ export declare function narrowFilterValueToOptions(value: FilterBuilderCondition
396
537
  *
397
538
  * @internal exported for tests
398
539
  */
399
- export declare function isFilterValueComplete(operator: string, value: FilterBuilderCondition["value"] | undefined | null): boolean;
540
+ export declare function isFilterValueComplete(operator: string, value: FilterBuilderRow["value"] | undefined | null): boolean;
400
541
  /**
401
- * The operators the dropdown offers for a field of `fieldType`, given the
402
- * opt-in ids this instance was granted.
542
+ * The operators a row on a field of `fieldType` can HOLD, given the opt-in ids
543
+ * this instance was granted — every operator the dropdown can draw for it.
544
+ *
545
+ * What the dropdown OFFERS a fresh choice is this set minus the nullness pair
546
+ * on a type that cannot tell "empty" from "null" (objectui#11810, see
547
+ * {@link offeredOperatorsForRow}). That narrowing is deliberately NOT applied
548
+ * here: callers outside this file (`app-shell`'s dataset read-back,
549
+ * `plugin-list`'s parity pin) ask "can the builder hold this row", and a
550
+ * stored `is_null` on a `select` column still can.
403
551
  *
404
552
  * A pure function rather than a closure so the selection rule — and above all
405
553
  * the {@link OPT_IN_OPERATORS} gate, whose whole job is to keep an operator OFF
@@ -425,9 +573,9 @@ interface LookupValuePickerProps {
425
573
  displayField?: string;
426
574
  idField?: string;
427
575
  };
428
- value: FilterBuilderCondition["value"];
576
+ value: FilterBuilderRow["value"];
429
577
  multiple: boolean;
430
- onChange: (value: FilterBuilderCondition["value"]) => void;
578
+ onChange: (value: FilterBuilderRow["value"]) => void;
431
579
  }
432
580
  declare function LookupValuePicker({ field, value, multiple, onChange }: LookupValuePickerProps): React.JSX.Element;
433
581
  export { FilterBuilder, LookupValuePicker };
@@ -47,5 +47,9 @@ export interface GroupingEditorProps {
47
47
  * Field options are filtered per row so a field can only appear in one level
48
48
  * at a time. The current row's selected field stays in its own dropdown so the
49
49
  * user sees the active selection.
50
+ *
51
+ * The field selector is the shared `Select` primitive, the control the Filter
52
+ * and Sort panels beside this one pick a field with, so the three panels look
53
+ * and behave alike (objectui#11865). It used to be a browser-native `<select>`.
50
54
  */
51
55
  export declare function GroupingEditor({ value, onChange, fieldOptions, maxLevels, className, labels, }: GroupingEditorProps): React.JSX.Element;
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ export interface HorizontalOverflowBoxProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ /**
4
+ * Where the shadow is drawn: `edge` (default) overlays the box's right edge;
5
+ * `pinned` leaves it to the right-pinned cells inside, which carry
6
+ * `PINNED_RIGHT_SEAM_SHADOW_CLASSES` (`./use-horizontal-overflow`).
7
+ */
8
+ seam?: 'edge' | 'pinned';
9
+ /** Classes for the positioned wrapper around the scrolling box. */
10
+ outerClassName?: string;
11
+ }
12
+ /**
13
+ * A horizontally scrolling box with the shared overflow affordance. The
14
+ * scrolling element is the inner `div` (`className`, the ref and every other
15
+ * prop land on it); the edge shadow is its sibling, so it never scrolls away
16
+ * with the content.
17
+ */
18
+ export declare const HorizontalOverflowBox: React.ForwardRefExoticComponent<HorizontalOverflowBoxProps & React.RefAttributes<HTMLDivElement>>;
@@ -24,5 +24,8 @@ export * from './shimmer-skeleton.js';
24
24
  export * from './RecordTitleChip.js';
25
25
  export * from './refresh-indicator.js';
26
26
  export * from './view-states.js';
27
+ export * from './empty-state-link.js';
27
28
  export * from './mobile-dialog-content.js';
28
- export * from './export-progress-dialog.js';
29
+ export * from './dialog-viewport-gutter.js';
30
+ export { HorizontalOverflowBox, type HorizontalOverflowBoxProps } from './horizontal-overflow.js';
31
+ export { PINNED_RIGHT_SEAM_SHADOW_CLASSES } from './use-horizontal-overflow.js';
@@ -18,7 +18,7 @@ declare function Item({ className, variant, size, asChild, ...props }: React.Com
18
18
  asChild?: boolean;
19
19
  }): React.JSX.Element;
20
20
  declare const itemMediaVariants: (props?: ({
21
- variant?: "image" | "default" | "icon" | null | undefined;
21
+ variant?: "default" | "icon" | "image" | null | undefined;
22
22
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
23
23
  declare function ItemMedia({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>): React.JSX.Element;
24
24
  declare function ItemContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
@@ -6,5 +6,5 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  declare function Kbd({ className, ...props }: React.ComponentProps<"kbd">): import("react").JSX.Element;
9
- declare function KbdGroup({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
9
+ declare function KbdGroup({ className, ...props }: React.ComponentProps<"kbd">): import("react").JSX.Element;
10
10
  export { Kbd, KbdGroup };
@@ -43,5 +43,26 @@ type InteractOutsideHandler = NonNullable<React.ComponentPropsWithoutRef<typeof
43
43
  * plain backdrop click with no flyout open.
44
44
  */
45
45
  export declare function usePopperAwareInteractOutside(onInteractOutside?: InteractOutsideHandler): InteractOutsideHandler;
46
- export declare const MobileDialogContent: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
46
+ export type MobileDialogContentProps = React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
47
+ /**
48
+ * Whether the dialog draws its own close (X) button. Defaults to `true`;
49
+ * `false` leaves it out of the DOM entirely (objectui#11061), the same
50
+ * switch upstream Shadcn's `DialogContent` names `showCloseButton`. The
51
+ * dialog stays dismissable without it: Escape and a backdrop click still
52
+ * reach the `Dialog`'s `onOpenChange(false)` through Radix, so a caller that
53
+ * hides the X only has to keep whatever explicit way out its body offers.
54
+ */
55
+ showCloseButton?: boolean;
56
+ };
57
+ export declare const MobileDialogContent: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
58
+ /**
59
+ * Whether the dialog draws its own close (X) button. Defaults to `true`;
60
+ * `false` leaves it out of the DOM entirely (objectui#11061), the same
61
+ * switch upstream Shadcn's `DialogContent` names `showCloseButton`. The
62
+ * dialog stays dismissable without it: Escape and a backdrop click still
63
+ * reach the `Dialog`'s `onOpenChange(false)` through Radix, so a caller that
64
+ * hides the X only has to keep whatever explicit way out its body offers.
65
+ */
66
+ showCloseButton?: boolean;
67
+ } & React.RefAttributes<HTMLDivElement>>;
47
68
  export {};