@globalbrain/sefirot 4.41.2 → 4.42.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.
@@ -61,7 +61,18 @@ export interface Props {
61
61
  // Whether to hide the condition blocks.
62
62
  hideConditions?: boolean
63
63
 
64
- // Field name to be used as index field for selection.
64
+ // Field name to be used as the row identifier. When set, the field is
65
+ // automatically included in the request `select` so every row carries
66
+ // its value, and the corresponding column is hidden from the rendered
67
+ // table — *unless* the caller explicitly listed it in `select`, in
68
+ // which case the caller's intent wins and the column is shown
69
+ // normally. The value remains accessible via `record[indexField]` in
70
+ // `cell-clicked` events and through the `selected` model regardless.
71
+ //
72
+ // If the caller's `select` is empty or `null` (i.e. "use server
73
+ // defaults"), the index field is **not** auto-appended — that would
74
+ // narrow the result to just that single column on backends that treat
75
+ // an empty `select` as "use defaults".
65
76
  indexField?: string
66
77
 
67
78
  // Fields that are clickable to emit `cell-clicked` event when clicked.
@@ -125,6 +136,11 @@ const hasInitialResults = ref(false)
125
136
  let prevFetchInput: LensQuery | null = null
126
137
  let prevFetchResult: LensResult | null = null
127
138
 
139
+ // `_select` carries the caller's intent. The index field is preserved
140
+ // here if it was listed explicitly so the corresponding column gets
141
+ // rendered, and is only added to the request payload separately (see
142
+ // `withIndexField`). When `_select` is empty, the auto-fetched index
143
+ // field is stripped out of the table's column list further down.
128
144
  const _select = ref(props.select ?? [])
129
145
  const _selectable = ref(props.selectable ?? props.select ?? [])
130
146
 
@@ -152,7 +168,7 @@ const perPage = ref(100)
152
168
  const { data: result, execute: refresh, loading } = useQuery(async (http) => {
153
169
  const input = {
154
170
  entity: props.entity ?? '__no_entity__',
155
- select: _select.value,
171
+ select: withIndexField(_select.value),
156
172
  filters: createInputFilters(queryFilter.value, _filters.value),
157
173
  sort: _sort.value.length > 0 ? _sort.value : defaultSort.value ?? [],
158
174
  page: page.value,
@@ -231,19 +247,55 @@ const tableMaxHeight = computed(() => {
231
247
  return `--table-max-height: calc(${props.height} - ${controlHeight} - ${conditionBlocksHeight.value} - ${columnsHeight} - ${footerHeight})`
232
248
  })
233
249
 
234
- // Initial setup when the result is loaded for the first time.
250
+ // Initial setup when the result is loaded for the first time. When the
251
+ // caller didn't pass a `select`, we initialise from the response, but
252
+ // we strip out the index field — anything pulled in by `withIndexField`
253
+ // on the way out shouldn't pretend to be caller-declared on the way
254
+ // back in.
235
255
  watch(result, (res) => {
236
256
  if (!hasInitialResults.value && res!.data.length > 0) {
237
257
  hasInitialResults.value = true
238
258
  }
239
259
  if (_select.value.length === 0) {
240
- _select.value = res!.query.select
260
+ _select.value = withoutIndexField(res!.query.select)
241
261
  }
242
262
  if (_selectable.value.length === 0) {
243
- _selectable.value = res!.query.select
263
+ _selectable.value = withoutIndexField(res!.query.select)
244
264
  }
245
265
  }, { once: true })
246
266
 
267
+ // Columns to render in the table. We always defer to `_select` (caller
268
+ // or user intent) once we have it; before the first response we use the
269
+ // raw response select with the index field filtered out so an
270
+ // auto-fetched index field doesn't accidentally appear as a column.
271
+ const tableSelect = computed(() => {
272
+ if (_select.value.length > 0) {
273
+ return _select.value
274
+ }
275
+ return withoutIndexField(result.value?.query.select ?? [])
276
+ })
277
+
278
+ // The `indexField` is appended to the request `select` so the server
279
+ // returns its value on every row, but it is kept out of the internal
280
+ // `_select` / `_selectable` state so the caller-facing concept of
281
+ // "selected columns" stays clean (the index field is a row identifier,
282
+ // not a column the user picked). The corresponding column is also
283
+ // hidden from the rendered table by `LensTable`.
284
+ //
285
+ // When the caller has no concrete select list, the index field is
286
+ // *not* added either — leaving the request empty lets the server use
287
+ // its own defaults. See `indexField` prop docs above.
288
+ function withIndexField(fields: string[]): string[] {
289
+ if (fields.length === 0) { return [] }
290
+ if (!props.indexField || fields.includes(props.indexField)) { return [...fields] }
291
+ return [...fields, props.indexField]
292
+ }
293
+
294
+ function withoutIndexField(fields: string[]): string[] {
295
+ if (!props.indexField) { return [...fields] }
296
+ return fields.filter((f) => f !== props.indexField)
297
+ }
298
+
247
299
  // Create lens filters option by combining query (free search) filters,
248
300
  // user selected filters, and fixed filters.
249
301
  function createInputFilters(queryFilters: any[], filters: any[]) {
@@ -313,6 +365,8 @@ function onResetSorts() {
313
365
  }
314
366
 
315
367
  function onViewUpdated(newSelect: string[], newSelectable: string[], overrides: Record<string, Partial<FieldData>>) {
368
+ // Treat updates from the view form as deliberate user intent: if the
369
+ // user picked the index field on purpose, surface its column.
316
370
  _select.value = newSelect
317
371
  _selectable.value = newSelectable
318
372
  _overrides.value = overrides
@@ -423,6 +477,7 @@ defineExpose({
423
477
  :result
424
478
  :loading
425
479
  :overrides="_overrides"
480
+ :select="tableSelect"
426
481
  :index-field
427
482
  :selected
428
483
  :clickable-fields
@@ -14,6 +14,11 @@ const props = defineProps<{
14
14
  result?: LensResult
15
15
  overrides?: Record<string, Partial<FieldData>>
16
16
  loading: boolean
17
+ // The list of field keys to actually render as columns, in order.
18
+ // Falls back to `result.query.select` if not provided. The catalog
19
+ // uses this to control whether an auto-fetched `indexField` shows up
20
+ // as a column.
21
+ select?: string[]
17
22
  selected?: any[]
18
23
  indexField?: string
19
24
  }>()
@@ -29,8 +34,10 @@ const fieldFactory = useFieldFactory()
29
34
 
30
35
  const records = computed(() => props.result?.data ?? [])
31
36
 
37
+ const columnKeys = computed(() => props.select ?? props.result?.query.select ?? [])
38
+
32
39
  const orders = computed(() => [
33
- ...(props.result?.query.select ?? []),
40
+ ...columnKeys.value,
34
41
  '__last_empty__'
35
42
  ])
36
43
 
@@ -48,9 +55,9 @@ const columns = computedAsync(async () => {
48
55
  }
49
56
  }
50
57
 
51
- // Build the lest of columns based on selected fields.
52
- for (const i in r.query.select) {
53
- const key = r.query.select[i]
58
+ // Build the list of columns based on the resolved column key list.
59
+ for (const i in columnKeys.value) {
60
+ const key = columnKeys.value[i]
54
61
 
55
62
  const _fieldData = cloneDeep(r.fields[key])
56
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
- "version": "4.41.2",
3
+ "version": "4.42.0",
4
4
  "description": "Vue Components for Global Brain Design System.",
5
5
  "keywords": [
6
6
  "components",