@globalbrain/sefirot 4.49.0 → 4.50.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.
@@ -146,7 +146,7 @@ export interface DecimalFieldData extends FieldDataBase {
146
146
 
147
147
  export interface SelectFieldData extends FieldDataBase {
148
148
  type: 'select'
149
- displayAs: 'text' | 'state'
149
+ displayAs: 'text' | 'state' | 'pills'
150
150
  inputAs: 'dropdown' | 'radio'
151
151
  placeholderEn: string | null
152
152
  placeholderJa: string | null
@@ -3,6 +3,8 @@ import IconPencilSimple from '~icons/ph/pencil-simple'
3
3
  import { useElementBounding } from '@vueuse/core'
4
4
  import { computed, nextTick, onUnmounted, ref, shallowRef, watch } from 'vue'
5
5
  import SButton from '../../../components/SButton.vue'
6
+ import SPill, { type Mode as PillMode } from '../../../components/SPill.vue'
7
+ import SState, { type Mode as StateMode } from '../../../components/SState.vue'
6
8
  import { useManualDropdownPosition } from '../../../composables/Dropdown'
7
9
  import { useTrans } from '../../../composables/Lang'
8
10
  import { useValidation } from '../../../composables/Validation'
@@ -54,21 +56,37 @@ onUnmounted(() => {
54
56
 
55
57
  // Reuse the field's own table-cell rendering for the display value so the
56
58
  // label mapping (e.g. select option labels) matches the read-only columns.
57
- // Falls back to a plain representation for non-text displays.
58
- const displayValue = computed(() => {
59
+ const resolvedCell = computed<any>(() => {
59
60
  try {
60
61
  const cell = props.field.tableColumn().cell
61
- const resolved: any = typeof cell === 'function' ? cell(props.value, props.record) : cell
62
- if (resolved && (resolved.type === 'text' || resolved.type === 'number')) {
63
- return resolved.value ?? ''
64
- }
65
- // `state` cells (e.g. a select with displayAs: 'state') carry the localized
66
- // label, not the raw payload — show that rather than falling through.
67
- if (resolved && resolved.type === 'state') {
68
- return resolved.label ?? ''
69
- }
62
+ return typeof cell === 'function' ? cell(props.value, props.record) : cell
70
63
  } catch {
71
- // Field types without a text cell fall through to the generic display.
64
+ // Field types without a usable cell fall through to the generic display.
65
+ return null
66
+ }
67
+ })
68
+
69
+ // A `pills` cell (e.g. a multi-select with displayAs: 'pills') renders as pills
70
+ // rather than text, mirroring the read-only `STableCellPills` column.
71
+ const displayPills = computed<{ label: string; color?: PillMode }[] | null>(() => {
72
+ const cell = resolvedCell.value
73
+ return cell && cell.type === 'pills' ? cell.pills : null
74
+ })
75
+
76
+ // A `state` cell (e.g. a select with displayAs: 'state') renders as a status
77
+ // badge rather than its bare label, mirroring the read-only `STableCellState`
78
+ // column.
79
+ const displayState = computed<{ label: string; mode?: StateMode } | null>(() => {
80
+ const cell = resolvedCell.value
81
+ return cell && cell.type === 'state' ? { label: cell.label, mode: cell.mode } : null
82
+ })
83
+
84
+ // Falls back to a plain representation for non-text displays (pills and state
85
+ // cells are rendered as their own components in the template above).
86
+ const displayValue = computed(() => {
87
+ const resolved: any = resolvedCell.value
88
+ if (resolved && (resolved.type === 'text' || resolved.type === 'number')) {
89
+ return resolved.value ?? ''
72
90
  }
73
91
 
74
92
  const v = props.value
@@ -184,7 +202,22 @@ function isTextLikeInput(target: EventTarget | null): boolean {
184
202
 
185
203
  <template>
186
204
  <div ref="anchor" class="LensTableEditableCell" :class="{ editing }">
187
- <span class="value">{{ displayValue }}</span>
205
+ <div v-if="displayPills" class="pills">
206
+ <SPill
207
+ v-for="(pill, i) in displayPills"
208
+ :key="i"
209
+ size="mini"
210
+ :mode="pill.color"
211
+ :label="pill.label"
212
+ />
213
+ </div>
214
+ <SState
215
+ v-else-if="displayState"
216
+ size="mini"
217
+ :mode="displayState.mode"
218
+ :label="displayState.label"
219
+ />
220
+ <span v-else class="value">{{ displayValue }}</span>
188
221
  <button
189
222
  class="edit"
190
223
  type="button"
@@ -233,6 +266,15 @@ function isTextLikeInput(target: EventTarget | null): boolean {
233
266
  text-overflow: ellipsis;
234
267
  }
235
268
 
269
+ .pills {
270
+ display: flex;
271
+ flex-wrap: nowrap;
272
+ align-items: center;
273
+ gap: 4px;
274
+ min-width: 0;
275
+ overflow: hidden;
276
+ }
277
+
236
278
  .edit {
237
279
  position: absolute;
238
280
  top: 50%;
@@ -1,5 +1,6 @@
1
1
  import { xor } from 'lodash-es'
2
2
  import { h } from 'vue'
3
+ import SDescPill from '../../../components/SDescPill.vue'
3
4
  import SInputCheckboxes from '../../../components/SInputCheckboxes.vue'
4
5
  import SInputDropdown from '../../../components/SInputDropdown.vue'
5
6
  import SInputRadios from '../../../components/SInputRadios.vue'
@@ -38,6 +39,8 @@ export class SelectField extends Field<SelectFieldData> {
38
39
  switch (this.data.displayAs) {
39
40
  case 'state':
40
41
  return this.tableCellState(v, _r)
42
+ case 'pills':
43
+ return this.tableCellPills(v, _r)
41
44
  case 'text':
42
45
  return this.tableCellText(v, _r)
43
46
  default:
@@ -45,6 +48,18 @@ export class SelectField extends Field<SelectFieldData> {
45
48
  }
46
49
  }
47
50
 
51
+ protected tableCellPills(v: any, _r: any): TableCell {
52
+ v = Array.isArray(v) ? v : [v]
53
+
54
+ return {
55
+ type: 'pills',
56
+ pills: this.optionsForValues(v).map((o) => ({
57
+ label: this.labelForOption(o),
58
+ color: o.mode
59
+ }))
60
+ }
61
+ }
62
+
48
63
  protected tableCellText(v: any, _r: any): TableCell {
49
64
  v = Array.isArray(v) ? v : [v]
50
65
 
@@ -95,6 +110,8 @@ export class SelectField extends Field<SelectFieldData> {
95
110
  switch (this.data.displayAs) {
96
111
  case 'state':
97
112
  return this.renderDataListItemValueForState(value)
113
+ case 'pills':
114
+ return this.renderDataListItemValueForPills(value)
98
115
  case 'text':
99
116
  return this.renderDataListItemValueForText(value)
100
117
  default:
@@ -103,6 +120,17 @@ export class SelectField extends Field<SelectFieldData> {
103
120
  })
104
121
  }
105
122
 
123
+ protected renderDataListItemValueForPills(value: any): any {
124
+ value = Array.isArray(value) ? value : [value]
125
+
126
+ return h(SDescPill, {
127
+ pill: this.optionsForValues(value).map((o) => ({
128
+ label: this.labelForOption(o),
129
+ mode: o.mode
130
+ }))
131
+ })
132
+ }
133
+
106
134
  protected renderDataListItemValueForState(value: any): any {
107
135
  if (this.data.multiple) {
108
136
  throw new Error('Displaying select field as state with multiple option is not supported.')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
- "version": "4.49.0",
3
+ "version": "4.50.0",
4
4
  "description": "Vue Components for Global Brain Design System.",
5
5
  "keywords": [
6
6
  "components",