@globalbrain/sefirot 4.56.0 → 4.58.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 (50) hide show
  1. package/lib/blocks/lens/components/LensFormFilterCondition.vue +2 -1
  2. package/lib/blocks/lens/components/LensFormFilterGroup.vue +1 -1
  3. package/lib/blocks/lens/components/{LensAvatarInput.vue → LensInputAvatar.vue} +5 -3
  4. package/lib/blocks/lens/components/LensInputDropdown.vue +102 -0
  5. package/lib/blocks/lens/components/LensSheetAvatarField.vue +2 -2
  6. package/lib/blocks/lens/components/LensTableEditableCell.vue +5 -4
  7. package/lib/blocks/lens/fields/AvatarField.ts +2 -2
  8. package/lib/blocks/lens/fields/Field.ts +10 -6
  9. package/lib/blocks/lens/filter-inputs/SelectFilterInput.ts +2 -1
  10. package/lib/components/SButton.vue +2 -10
  11. package/lib/components/SControlInputSearch.vue +6 -3
  12. package/lib/components/SDescPill.vue +3 -2
  13. package/lib/components/SDescState.vue +3 -2
  14. package/lib/components/SInputAddon.vue +4 -2
  15. package/lib/components/SInputAsyncDropdown.vue +761 -0
  16. package/lib/components/SInputBase.vue +1 -3
  17. package/lib/components/SInputCheckbox.vue +10 -18
  18. package/lib/components/SInputCheckboxes.vue +16 -33
  19. package/lib/components/SInputDate.vue +10 -20
  20. package/lib/components/SInputDropdown.vue +22 -43
  21. package/lib/components/SInputDropdownItem.vue +10 -27
  22. package/lib/components/SInputFile.vue +10 -18
  23. package/lib/components/SInputFileUpload.vue +12 -34
  24. package/lib/components/SInputFileUploadItem.vue +7 -21
  25. package/lib/components/SInputHMS.vue +6 -7
  26. package/lib/components/SInputImage.vue +11 -16
  27. package/lib/components/SInputNumber.vue +1 -3
  28. package/lib/components/SInputRadio.vue +9 -19
  29. package/lib/components/SInputRadios.vue +18 -40
  30. package/lib/components/SInputSegments.vue +15 -26
  31. package/lib/components/SInputSegmentsOption.vue +7 -7
  32. package/lib/components/SInputSelect.vue +14 -19
  33. package/lib/components/SInputSwitch.vue +10 -19
  34. package/lib/components/SInputSwitches.vue +16 -30
  35. package/lib/components/SInputText.vue +1 -4
  36. package/lib/components/SInputTextarea.vue +0 -2
  37. package/lib/components/SInputYMD.vue +6 -7
  38. package/lib/components/SPagination.vue +1 -1
  39. package/lib/components/SPill.vue +2 -2
  40. package/lib/components/SState.vue +2 -2
  41. package/lib/components/STableCellPill.vue +3 -2
  42. package/lib/components/STableCellState.vue +3 -2
  43. package/lib/composables/Table.ts +8 -15
  44. package/lib/support/Color.ts +4 -0
  45. package/lib/support/InputBase.ts +5 -0
  46. package/lib/support/InputDropdown.ts +15 -0
  47. package/lib/support/InputFileUpload.ts +22 -0
  48. package/lib/support/InputOption.ts +8 -0
  49. package/lib/support/InputText.ts +6 -0
  50. package/package.json +1 -1
@@ -2,10 +2,11 @@
2
2
  import IconTrash from '~icons/ph/trash'
3
3
  import { type Ref, computed, markRaw, ref, watch } from 'vue'
4
4
  import SButton from '../../../components/SButton.vue'
5
- import SInputDropdown, { type Option } from '../../../components/SInputDropdown.vue'
5
+ import SInputDropdown from '../../../components/SInputDropdown.vue'
6
6
  import SSpinner from '../../../components/SSpinner.vue'
7
7
  import { useTrans } from '../../../composables/Lang'
8
8
  import { useValidation } from '../../../composables/Validation'
9
+ import { type Option } from '../../../support/InputDropdown'
9
10
  import { required } from '../../../validation/rules'
10
11
  import { type FieldData } from '../FieldData'
11
12
  import { type FilterOperator, FilterOperatorLabelDict } from '../FilterOperator'
@@ -2,9 +2,9 @@
2
2
  import IconPlusCircle from '~icons/ph/plus-circle'
3
3
  import IconTrash from '~icons/ph/trash'
4
4
  import SButton from '../../../components/SButton.vue'
5
- import { type Option } from '../../../components/SInputDropdown.vue'
6
5
  import SInputSegments from '../../../components/SInputSegments.vue'
7
6
  import { useTrans } from '../../../composables/Lang'
7
+ import { type Option } from '../../../support/InputDropdown'
8
8
  import { type FieldData } from '../FieldData'
9
9
  import LensFormFilterCondition, { type FilterCondition } from './LensFormFilterCondition.vue'
10
10
 
@@ -3,7 +3,7 @@ import SInputImage, { type ImageType, type Size } from '../../../components/SInp
3
3
  import { useTrans } from '../../../composables/Lang'
4
4
  import { type Validatable } from '../../../composables/Validation'
5
5
 
6
- withDefaults(defineProps<{
6
+ export interface Props {
7
7
  label?: string
8
8
  help?: string
9
9
  accept?: string
@@ -11,7 +11,9 @@ withDefaults(defineProps<{
11
11
  size?: Size
12
12
  disabled?: boolean
13
13
  validation?: Validatable
14
- }>(), {
14
+ }
15
+
16
+ withDefaults(defineProps<Props>(), {
15
17
  imageType: 'circle',
16
18
  size: 'small'
17
19
  })
@@ -30,7 +32,7 @@ const { t } = useTrans({
30
32
  <template>
31
33
  <SInputImage
32
34
  v-model="model"
33
- class="LensAvatarInput"
35
+ class="LensInputAvatar"
34
36
  :label
35
37
  :help
36
38
  :accept
@@ -0,0 +1,102 @@
1
+ <script setup lang="ts" generic="T, Multiple extends boolean = false">
2
+ import SInputAsyncDropdown from '../../../components/SInputAsyncDropdown.vue'
3
+ import { useMutation } from '../../../composables/Api'
4
+ import { useLang } from '../../../composables/Lang'
5
+ import { type Option } from '../../../support/InputDropdown'
6
+ import { type LensQuerySettings, type LensQuerySort } from '../LensQuery'
7
+ import { isAuthError } from '../validation/ServerErrors'
8
+
9
+ // A lens-backed search-select that works entirely in the consumer's own model
10
+ // type. It searches the server as the user types (via SInputAsyncDropdown): each
11
+ // result row is turned into a model by `toModel`, and each model is rendered as
12
+ // its dropdown/chip option by `toOption`. The v-model is those models — a `T[]`
13
+ // when `multiple`, otherwise `T | null`.
14
+ //
15
+ // Remaining selection UI props (nullable, disabled, placeholder, size, label,
16
+ // validation, position, closeOnSelect, debounce, …) fall through as attributes.
17
+ export interface Props<T = any, Multiple extends boolean = false> {
18
+ // The lens search endpoint (e.g. `/api/admin/lens/search`).
19
+ endpoint: string
20
+
21
+ // The entity to search.
22
+ entity: string
23
+
24
+ // Columns to fetch for each row, passed to `toModel`.
25
+ select: string[]
26
+
27
+ // Columns matched against the typed query — OR-ed together with `contains`. An
28
+ // empty query fetches the initial (unfiltered) page. Named to match
29
+ // LensCatalog's `queryKeys`.
30
+ queryKeys: string[]
31
+
32
+ // Sort applied to the results (e.g. `[['name', 'asc']]`). Re-read on each fetch,
33
+ // so a locale-dependent sort stays current.
34
+ sort?: LensQuerySort[]
35
+
36
+ // Page size of each search. The server enforces its own perPage ceiling.
37
+ perPage?: number
38
+
39
+ // Per-request settings (e.g. language). Usually unneeded — the server
40
+ // negotiates language from the request headers.
41
+ settings?: LensQuerySettings
42
+
43
+ // Whether multiple models can be selected. Typed `boolean & Multiple` (equal to
44
+ // `Multiple`) so Vue keeps runtime Boolean casting for a bare `multiple` while
45
+ // `Multiple` still drives the arity-conditional model type. See SInputAsyncDropdown.
46
+ multiple?: boolean & Multiple
47
+
48
+ // Build a model from a raw result row.
49
+ toModel: (row: Record<string, any>) => T
50
+
51
+ // Render a model as its dropdown/chip option (`value` is the identity).
52
+ toOption: (item: T) => Option
53
+ }
54
+
55
+ const props = defineProps<Props<T, Multiple>>()
56
+
57
+ // The selected model(s): a `T[]` when `multiple`, otherwise `T | null`.
58
+ const model = defineModel<Multiple extends true ? T[] : T | null>({ required: true })
59
+
60
+ // Don't let lens-config attributes leak onto the child; only the explicitly
61
+ // forwarded selection props (via `$attrs`) should.
62
+ defineOptions({ inheritAttrs: false })
63
+
64
+ // Default the request language to the active app language (like LensCatalog), so
65
+ // server-localized fields come back in the right locale even when HttpConfig /
66
+ // request headers differ; explicit `settings` (incl. `lang`) overrides it.
67
+ const lang = useLang()
68
+
69
+ const { execute } = useMutation((http, query: string) =>
70
+ http.post<{ data: Record<string, any>[] }>(props.endpoint, {
71
+ entity: props.entity,
72
+ select: props.select,
73
+ filters: query
74
+ ? [['$or', props.queryKeys.map((key) => [key, 'contains', query])]]
75
+ : [],
76
+ sort: props.sort ?? [],
77
+ page: 1,
78
+ perPage: props.perPage ?? 25,
79
+ settings: { lang, ...props.settings }
80
+ })
81
+ )
82
+
83
+ // Read every config field through `props` at call time so a reactive change (e.g.
84
+ // a locale-dependent sort or label) takes effect on the next search.
85
+ async function fetch(query: string): Promise<T[]> {
86
+ const res = await execute(query)
87
+ return res.data.map(props.toModel)
88
+ }
89
+ </script>
90
+
91
+ <template>
92
+ <SInputAsyncDropdown
93
+ v-model="model"
94
+ :fetch
95
+ :to-option
96
+ :multiple
97
+ :rethrow="isAuthError"
98
+ v-bind="$attrs"
99
+ >
100
+ <template v-if="$slots.info" #info><slot name="info" /></template>
101
+ </SInputAsyncDropdown>
102
+ </template>
@@ -8,7 +8,7 @@ import { type FieldData } from '../FieldData'
8
8
  import { useLensEdit } from '../composables/LensEdit'
9
9
  import { type AvatarField } from '../fields/AvatarField'
10
10
  import { type Field } from '../fields/Field'
11
- import LensAvatarInput from './LensAvatarInput.vue'
11
+ import LensInputAvatar from './LensInputAvatar.vue'
12
12
 
13
13
  const props = defineProps<{
14
14
  field: Field<FieldData>
@@ -116,7 +116,7 @@ async function onChange(value: File | string | null | undefined) {
116
116
  <SDataListItem v-if="editable">
117
117
  <template #label>{{ field.label() }}</template>
118
118
  <template #value>
119
- <LensAvatarInput
119
+ <LensInputAvatar
120
120
  :model-value="model"
121
121
  :accept="avatarField.accept()"
122
122
  :image-type="avatarField.imageType()"
@@ -4,11 +4,12 @@ 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
6
  import SLink from '../../../components/SLink.vue'
7
- import SPill, { type Mode as PillMode } from '../../../components/SPill.vue'
8
- import SState, { type Mode as StateMode } from '../../../components/SState.vue'
7
+ import SPill from '../../../components/SPill.vue'
8
+ import SState from '../../../components/SState.vue'
9
9
  import { useManualDropdownPosition } from '../../../composables/Dropdown'
10
10
  import { useTrans } from '../../../composables/Lang'
11
11
  import { useValidation } from '../../../composables/Validation'
12
+ import { type ColorMode } from '../../../support/Color'
12
13
  import { day } from '../../../support/Day'
13
14
  import { dispatchEditorKeydown, focusFirstEditable } from '../../../support/Dom'
14
15
  import { type FieldData } from '../FieldData'
@@ -75,7 +76,7 @@ const resolvedCell = computed<any>(() => {
75
76
 
76
77
  // A `pills` cell (e.g. a multi-select with displayAs: 'pills') renders as pills
77
78
  // rather than text, mirroring the read-only `STableCellPills` column.
78
- const displayPills = computed<{ label: string; color?: PillMode }[] | null>(() => {
79
+ const displayPills = computed<{ label: string; color?: ColorMode }[] | null>(() => {
79
80
  const cell = resolvedCell.value
80
81
  return cell && cell.type === 'pills' ? cell.pills : null
81
82
  })
@@ -83,7 +84,7 @@ const displayPills = computed<{ label: string; color?: PillMode }[] | null>(() =
83
84
  // A `state` cell (e.g. a select with displayAs: 'state') renders as a status
84
85
  // badge rather than its bare label, mirroring the read-only `STableCellState`
85
86
  // column.
86
- const displayState = computed<{ label: string; mode?: StateMode } | null>(() => {
87
+ const displayState = computed<{ label: string; mode?: ColorMode } | null>(() => {
87
88
  const cell = resolvedCell.value
88
89
  return cell && cell.type === 'state' ? { label: cell.label, mode: cell.mode } : null
89
90
  })
@@ -3,7 +3,7 @@ import SAvatar from '../../../components/SAvatar.vue'
3
3
  import { type TableCell } from '../../../composables/Table'
4
4
  import { type AvatarFieldData } from '../FieldData'
5
5
  import { type FilterOperator } from '../FilterOperator'
6
- import LensAvatarInput from '../components/LensAvatarInput.vue'
6
+ import LensInputAvatar from '../components/LensInputAvatar.vue'
7
7
  import { type FilterInput } from '../filter-inputs/FilterInput'
8
8
  import { Field } from './Field'
9
9
 
@@ -62,7 +62,7 @@ export class AvatarField extends Field<AvatarFieldData> {
62
62
 
63
63
  override formInputComponent(): any {
64
64
  return this.defineFormInputComponent((props, { emit }) => {
65
- return () => h(LensAvatarInput, {
65
+ return () => h(LensInputAvatar, {
66
66
  'label': this.formInputLabel(),
67
67
  'help': this.help() || undefined,
68
68
  'accept': this.accept(),
@@ -50,9 +50,11 @@ export abstract class Field<T extends FieldData> {
50
50
  * on the current app language.
51
51
  */
52
52
  placeholder(): string | null {
53
- return this.ctx.lang === 'ja'
54
- ? (this.data as any).placeholderJa ?? null
55
- : (this.data as any).placeholderEn ?? null
53
+ return this.ctx.lang === 'ja' && 'placeholderJa' in this.data
54
+ ? this.data.placeholderJa ?? null
55
+ : this.ctx.lang === 'en' && 'placeholderEn' in this.data
56
+ ? this.data.placeholderEn ?? null
57
+ : null
56
58
  }
57
59
 
58
60
  /**
@@ -60,9 +62,11 @@ export abstract class Field<T extends FieldData> {
60
62
  * on the current app language.
61
63
  */
62
64
  help(): string | null {
63
- return this.ctx.lang === 'ja'
64
- ? (this.data as any).helpJa ?? null
65
- : (this.data as any).helpEn ?? null
65
+ return this.ctx.lang === 'ja' && 'helpJa' in this.data
66
+ ? this.data.helpJa ?? null
67
+ : this.ctx.lang === 'en' && 'helpEn' in this.data
68
+ ? this.data.helpEn ?? null
69
+ : null
66
70
  }
67
71
 
68
72
  /**
@@ -1,6 +1,7 @@
1
1
  import { type ValidationArgs } from '@vuelidate/core'
2
2
  import { defineAsyncComponent } from 'vue'
3
- import SInputDropdown, { type Option } from '../../../components/SInputDropdown.vue'
3
+ import SInputDropdown from '../../../components/SInputDropdown.vue'
4
+ import { type Option } from '../../../support/InputDropdown'
4
5
  import { required } from '../../../validation/rules'
5
6
  import { isAuthError } from '../validation/ServerErrors'
6
7
  import { FilterInput } from './FilterInput'
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { type Component, type MaybeRef, computed, unref, useSlots } from 'vue'
3
3
  import { type Position } from '../composables/Tooltip'
4
+ import { type ColorMode } from '../support/Color'
4
5
  import SFragment from './SFragment.vue'
5
6
  import SLink from './SLink.vue'
6
7
  import SSpinner from './SSpinner.vue'
@@ -20,16 +21,7 @@ export type Size =
20
21
 
21
22
  export type Type = 'fill' | 'outline' | 'text'
22
23
 
23
- export type Mode =
24
- | 'default'
25
- | 'mute'
26
- | 'neutral'
27
- | 'white'
28
- | 'black'
29
- | 'info'
30
- | 'success'
31
- | 'warning'
32
- | 'danger'
24
+ export type Mode = ColorMode | 'white' | 'black'
33
25
 
34
26
  export interface Tooltip {
35
27
  tag?: Component | string
@@ -4,9 +4,10 @@ import { type Component, computed } from 'vue'
4
4
  import { useControlSize } from '../composables/Control'
5
5
  import { useTrans } from '../composables/Lang'
6
6
  import { type Validatable } from '../composables/Validation'
7
- import SInputText, { type Align, type TextColor } from './SInputText.vue'
7
+ import { type Align, type TextColor } from '../support/InputText'
8
+ import SInputText from './SInputText.vue'
8
9
 
9
- const props = defineProps<{
10
+ export interface Props {
10
11
  placeholder?: string
11
12
  unitAfter?: Component | string
12
13
  textColor?: TextColor | ((value: string | null) => TextColor)
@@ -16,7 +17,9 @@ const props = defineProps<{
16
17
  modelValue?: string | null
17
18
  displayValue?: string | null
18
19
  validation?: Validatable
19
- }>()
20
+ }
21
+
22
+ const props = defineProps<Props>()
20
23
 
21
24
  defineEmits<{
22
25
  'update:model-value': [value: string | null]
@@ -1,10 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from 'vue'
3
+ import { type ColorMode } from '../support/Color'
3
4
  import SDescEmpty from './SDescEmpty.vue'
4
- import SPill, { type Mode } from './SPill.vue'
5
+ import SPill from './SPill.vue'
5
6
 
6
7
  export interface Pill {
7
- mode?: Mode
8
+ mode?: ColorMode
8
9
  label: string
9
10
  }
10
11
 
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
+ import { type ColorMode } from '../support/Color'
2
3
  import SDescEmpty from './SDescEmpty.vue'
3
- import SState, { type Mode } from './SState.vue'
4
+ import SState from './SState.vue'
4
5
 
5
6
  export interface State {
6
- mode?: Mode
7
+ mode?: ColorMode
7
8
  label: string
8
9
  }
9
10
 
@@ -9,14 +9,16 @@ import {
9
9
  import { useFlyout } from '../composables/Flyout'
10
10
  import SDropdown from './SDropdown.vue'
11
11
 
12
- const props = withDefaults(defineProps<{
12
+ export interface Props {
13
13
  label?: Component | string
14
14
  clickable?: boolean
15
15
  dropdown?: DropdownSection[]
16
16
  dropdownCaret?: boolean
17
17
  dropdownPosition?: 'top' | 'bottom'
18
18
  disabled?: boolean
19
- }>(), {
19
+ }
20
+
21
+ const props = withDefaults(defineProps<Props>(), {
20
22
  clickable: true,
21
23
  dropdown: () => [],
22
24
  dropdownCaret: true