@globalbrain/sefirot 2.49.0 → 3.1.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 (34) hide show
  1. package/lib/components/SActionList.vue +23 -0
  2. package/lib/components/SActionListItem.vue +64 -0
  3. package/lib/components/SAvatar.vue +1 -1
  4. package/lib/components/SButton.vue +378 -392
  5. package/lib/components/SCard.vue +5 -5
  6. package/lib/components/SCardHeader.vue +1 -1
  7. package/lib/components/SCardHeaderTitle.vue +4 -4
  8. package/lib/components/SContent.vue +2 -2
  9. package/lib/components/SDescItem.vue +1 -1
  10. package/lib/components/SDescLink.vue +2 -2
  11. package/lib/components/SDescText.vue +2 -2
  12. package/lib/components/SDropdown.vue +1 -1
  13. package/lib/components/SDropdownSectionFilter.vue +8 -9
  14. package/lib/components/SDropdownSectionMenu.vue +1 -1
  15. package/lib/components/SHeadLead.vue +2 -2
  16. package/lib/components/SHeadTitle.vue +4 -4
  17. package/lib/components/SInputBase.vue +5 -5
  18. package/lib/components/SInputCheckbox.vue +5 -5
  19. package/lib/components/SInputImage.vue +239 -0
  20. package/lib/components/SInputRadio.vue +4 -4
  21. package/lib/components/SSheet.vue +1 -1
  22. package/lib/components/SState.vue +7 -7
  23. package/lib/components/STableCell.vue +1 -1
  24. package/lib/components/STableColumn.vue +1 -1
  25. package/lib/components/STableHeader.vue +1 -1
  26. package/lib/components/STableHeaderActionItem.vue +1 -1
  27. package/lib/composables/Image.ts +46 -0
  28. package/lib/composables/Table.ts +6 -5
  29. package/lib/styles/base.css +1 -1
  30. package/lib/styles/bootstrap.css +1 -0
  31. package/lib/styles/variables-deprecated.css +336 -0
  32. package/lib/styles/variables.css +582 -578
  33. package/lib/support/Utils.ts +4 -0
  34. package/package.json +1 -1
@@ -35,11 +35,11 @@ const classes = computed(() => [
35
35
  background-color: var(--c-gutter);
36
36
  }
37
37
 
38
- .SCard.neutral { border-color: var(--c-divider-2); }
39
- .SCard.info { border-color: var(--c-info-border); }
40
- .SCard.success { border-color: var(--c-success-border); }
41
- .SCard.warning { border-color: var(--c-warning-border); }
42
- .SCard.danger { border-color: var(--c-danger-border); }
38
+ .SCard.neutral { border-color: var(--c-divider); }
39
+ .SCard.info { border-color: var(--c-border-info-1); }
40
+ .SCard.success { border-color: var(--c-border-success-1); }
41
+ .SCard.warning { border-color: var(--c-border-warning-1); }
42
+ .SCard.danger { border-color: var(--c-border-danger-1); }
43
43
 
44
44
  .SCard.collapsed {
45
45
  height: 48px;
@@ -10,6 +10,6 @@
10
10
  align-items: center;
11
11
  border-radius: 5px 5px 0 0;
12
12
  height: 48px;
13
- background-color: var(--c-bg-soft);
13
+ background-color: var(--c-bg-elv-4);
14
14
  }
15
15
  </style>
@@ -22,8 +22,8 @@ defineProps<{
22
22
  }
23
23
 
24
24
  .SCardHeaderTitle.neutral { color: var(--c-text-1); }
25
- .SCardHeaderTitle.info { color: var(--c-info-text); }
26
- .SCardHeaderTitle.success { color: var(--c-success-text); }
27
- .SCardHeaderTitle.warning { color: var(--c-warning-text); }
28
- .SCardHeaderTitle.danger { color: var(--c-danger-text); }
25
+ .SCardHeaderTitle.info { color: var(--c-text-info-1); }
26
+ .SCardHeaderTitle.success { color: var(--c-text-success-1); }
27
+ .SCardHeaderTitle.warning { color: var(--c-text-warning-1); }
28
+ .SCardHeaderTitle.danger { color: var(--c-text-danger-1); }
29
29
  </style>
@@ -24,11 +24,11 @@
24
24
  }
25
25
 
26
26
  .SContent :deep(a) {
27
- color: var(--c-info-text);
27
+ color: var(--c-text-info-1);
28
28
  transition: color 0.25s;
29
29
 
30
30
  &:hover {
31
- color: var(--c-info-text-dark);
31
+ color: var(--c-text-info-2);
32
32
  }
33
33
  }
34
34
 
@@ -32,7 +32,7 @@ const labelWidth = computed(() => {
32
32
  }
33
33
 
34
34
  .SDesc.divider > .SDescItem {
35
- border-bottom: 1px dashed var(--c-divider-1);
35
+ border-bottom: 1px dashed var(--c-divider);
36
36
  padding-bottom: 7px;
37
37
  }
38
38
  </style>
@@ -41,11 +41,11 @@ const link = computed(() => {
41
41
  white-space: nowrap;
42
42
  overflow: hidden;
43
43
  text-overflow: ellipsis;
44
- color: var(--c-info-text);
44
+ color: var(--c-text-info-1);
45
45
  transition: color 0.25s;
46
46
 
47
47
  &:hover {
48
- color: var(--c-info-text-dark);
48
+ color: var(--c-text-info-2);
49
49
  }
50
50
  }
51
51
  </style>
@@ -53,11 +53,11 @@ const lineClamp = computed(() => props.lineClamp ?? 'none')
53
53
  }
54
54
 
55
55
  .value :deep(a) {
56
- color: var(--c-info-text);
56
+ color: var(--c-text-info-1);
57
57
  transition: color 0.25s;
58
58
 
59
59
  &:hover {
60
- color: var(--c-info-text-dark);
60
+ color: var(--c-text-info-2);
61
61
  }
62
62
  }
63
63
  </style>
@@ -19,7 +19,7 @@ defineProps<{
19
19
 
20
20
  <style scoped lang="postcss">
21
21
  .SDropdown {
22
- border: 1px solid var(--c-divider-2);
22
+ border: 1px solid var(--c-divider);
23
23
  border-radius: 6px;
24
24
  min-width: 256px;
25
25
  max-height: 384px;
@@ -105,11 +105,10 @@ function handleClick(option: DropdownSectionFilterOption, value: string | number
105
105
  z-index: 10;
106
106
  border-bottom: 1px solid var(--c-gutter);
107
107
  padding: 8px;
108
- background-color: var(--c-bg-elv-up);
109
108
  }
110
109
 
111
110
  .input {
112
- border: 1px solid var(--c-divider-1);
111
+ border: 1px solid var(--c-divider);
113
112
  border-radius: 6px;
114
113
  padding: 0 8px;
115
114
  width: 100%;
@@ -141,7 +140,7 @@ function handleClick(option: DropdownSectionFilterOption, value: string | number
141
140
 
142
141
  &:hover,
143
142
  &:focus {
144
- background-color: var(--c-mute);
143
+ background-color: var(--c-bg-mute-1);
145
144
  }
146
145
  }
147
146
 
@@ -155,7 +154,7 @@ function handleClick(option: DropdownSectionFilterOption, value: string | number
155
154
  display: flex;
156
155
  justify-content: center;
157
156
  align-items: center;
158
- border: 1px solid var(--c-divider-1);
157
+ border: 1px solid var(--c-border-mute-1);
159
158
  border-radius: 4px;
160
159
  width: 16px;
161
160
  height: 16px;
@@ -163,8 +162,8 @@ function handleClick(option: DropdownSectionFilterOption, value: string | number
163
162
  transition: border-color 0.1s, background-color 0.1s;
164
163
 
165
164
  .button.active & {
166
- border-color: var(--c-info-text);
167
- background-color: var(--c-info);
165
+ border-color: var(--c-fg-info-1);
166
+ background-color: var(--c-fg-info-1);
168
167
  }
169
168
  }
170
169
 
@@ -184,7 +183,7 @@ function handleClick(option: DropdownSectionFilterOption, value: string | number
184
183
  .radio {
185
184
  position: relative;
186
185
  flex-shrink: 0;
187
- border: 1px solid var(--c-divider-1);
186
+ border: 1px solid var(--c-border-mute-1);
188
187
  border-radius: 50%;
189
188
  margin-top: 8px;
190
189
  margin-bottom: 8px;
@@ -194,7 +193,7 @@ function handleClick(option: DropdownSectionFilterOption, value: string | number
194
193
  transition: border-color 0.25s;
195
194
 
196
195
  .button.active & {
197
- border-color: var(--c-info-light);
196
+ border-color: var(--c-border-info-1);
198
197
  }
199
198
  }
200
199
 
@@ -209,7 +208,7 @@ function handleClick(option: DropdownSectionFilterOption, value: string | number
209
208
  align-items: center;
210
209
  border-radius: 50%;
211
210
  width: 100%;
212
- background-color: var(--c-info-light);
211
+ background-color: var(--c-fg-info-1);
213
212
  opacity: 0;
214
213
  transform: scale(0);
215
214
  transition: opacity 0.25s, transform 0.1s;
@@ -38,7 +38,7 @@ defineProps<{
38
38
  transition: color 0.25s, background-color 0.25s;
39
39
 
40
40
  &:hover:not(:disabled) {
41
- background-color: var(--c-mute);
41
+ background-color: var(--c-bg-mute-1);
42
42
  }
43
43
 
44
44
  &:disabled {
@@ -12,11 +12,11 @@
12
12
  }
13
13
 
14
14
  .SHeadLead :deep(a) {
15
- color: var(--c-info-text);
15
+ color: var(--c-text-info-1);
16
16
  transition: color 0.25s;
17
17
 
18
18
  &:hover {
19
- color: var(--c-info-text-dark);
19
+ color: var(--c-text-info-2);
20
20
  }
21
21
  }
22
22
  </style>
@@ -21,8 +21,8 @@ defineProps<{
21
21
  }
22
22
 
23
23
  .SHeadTitle.neutral { color: var(--c-text-1); }
24
- .SHeadTitle.info { color: var(--c-info-text); }
25
- .SHeadTitle.success { color: var(--c-success-text); }
26
- .SHeadTitle.warning { color: var(--c-warning-text); }
27
- .SHeadTitle.danger { color: var(--c-danger-text); }
24
+ .SHeadTitle.info { color: var(--c-text-info-1); }
25
+ .SHeadTitle.success { color: var(--c-text-success-1); }
26
+ .SHeadTitle.warning { color: var(--c-text-warning-1); }
27
+ .SHeadTitle.danger { color: var(--c-text-danger-1); }
28
28
  </style>
@@ -132,7 +132,7 @@ function getErrorMsg(validation: Validatable) {
132
132
 
133
133
  &:hover, &:focus, &:focus-within {
134
134
  .label-info {
135
- color: var(--c-info-text);
135
+ color: var(--c-text-info-1);
136
136
  }
137
137
  }
138
138
 
@@ -191,10 +191,10 @@ function getErrorMsg(validation: Validatable) {
191
191
 
192
192
  &.neutral { color: var(--c-text-1); }
193
193
  &.mute { color: var(--c-text-2); }
194
- &.info { color: var(--c-info-text); }
195
- &.success { color: var(--c-success-text); }
196
- &.warning { color: var(--c-warning-text); }
197
- &.danger { color: var(--c-danger-text); }
194
+ &.info { color: var(--c-text-info-1); }
195
+ &.success { color: var(--c-text-success-1); }
196
+ &.warning { color: var(--c-text-warning-1); }
197
+ &.danger { color: var(--c-text-danger-1); }
198
198
  }
199
199
 
200
200
  .check-icon {
@@ -109,15 +109,15 @@ function onClick() {
109
109
 
110
110
  &:hover {
111
111
  .box {
112
- border-color: var(--c-info-light);
112
+ border-color: var(--c-border-info-1);
113
113
  }
114
114
  }
115
115
  }
116
116
 
117
117
  .input.on {
118
118
  .box {
119
- border-color: var(--c-info-light);
120
- background-color: var(--c-info-light);
119
+ border-color: var(--c-fg-info-1);
120
+ background-color: var(--c-fg-info-1);
121
121
  }
122
122
 
123
123
  .check {
@@ -128,7 +128,7 @@ function onClick() {
128
128
 
129
129
  .box {
130
130
  position: relative;
131
- border: 1px solid var(--c-divider-1);
131
+ border: 1px solid var(--c-border-mute-1);
132
132
  border-radius: 4px;
133
133
  width: 16px;
134
134
  height: 16px;
@@ -151,7 +151,7 @@ function onClick() {
151
151
  .check-icon {
152
152
  width: 10px;
153
153
  height: 10px;
154
- color: var(--c-white);
154
+ color: var(--c-white-1);
155
155
  }
156
156
 
157
157
  .text {
@@ -0,0 +1,239 @@
1
+ <script setup lang="ts">
2
+ import { type IconifyIcon } from '@iconify/vue/dist/offline'
3
+ import IconImage from '@iconify-icons/ph/image-bold'
4
+ import { computed, ref } from 'vue'
5
+ import { useImageSrcFromFile } from '../composables/Image'
6
+ import { type Validatable } from '../composables/Validation'
7
+ import SButton from './SButton.vue'
8
+ import SIcon from './SIcon.vue'
9
+ import SInputBase from './SInputBase.vue'
10
+
11
+ export type Size = 'mini' | 'small' | 'medium'
12
+ export type CheckColor = 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
13
+ export type ImageType = 'rectangle' | 'circle'
14
+
15
+ const props = withDefaults(defineProps<{
16
+ size?: Size
17
+ label?: string
18
+ info?: string
19
+ note?: string
20
+ help?: string
21
+ checkIcon?: IconifyIcon
22
+ checkText?: string
23
+ checkColor?: CheckColor
24
+ imageType?: ImageType
25
+ imageWidth?: string
26
+ imageAspectRatio?: string
27
+ selectText?: string
28
+ removeText?: string
29
+ accept?: string
30
+ nullable?: boolean
31
+ disabled?: boolean
32
+ value?: File | string | null
33
+ modelValue?: File | string | null
34
+ hideError?: boolean
35
+ validation?: Validatable
36
+ }>(), {
37
+ imageType: 'rectangle',
38
+ imageWidth: '96px',
39
+ imageAspectRatio: '1 / 1',
40
+ selectText: 'Select image',
41
+ removeText: 'Remove image',
42
+ nullable: true
43
+ })
44
+
45
+ const emit = defineEmits<{
46
+ (e: 'update:model-value', value: File | null): void
47
+ (e: 'change', value: File | null): void
48
+ }>()
49
+
50
+ const fileInput = ref<HTMLInputElement | null>(null)
51
+
52
+ const _value = computed(() => {
53
+ return (props.modelValue !== undefined)
54
+ ? props.modelValue
55
+ : props.value !== undefined ? props.value : null
56
+ })
57
+
58
+ const { src: imageSrc } = useImageSrcFromFile(_value)
59
+
60
+ function openFileSelect() {
61
+ fileInput.value!.click()
62
+ }
63
+
64
+ function onFileSelect(e: Event) {
65
+ const file = ((e.target as HTMLInputElement).files ?? [])[0]
66
+
67
+ emit('update:model-value', file ?? null)
68
+ emit('change', file ?? null)
69
+
70
+ file && props.validation?.$touch()
71
+ }
72
+
73
+ function onFileDelete() {
74
+ fileInput.value!.value = ''
75
+
76
+ emit('update:model-value', null)
77
+ emit('change', null)
78
+ }
79
+ </script>
80
+
81
+ <template>
82
+ <SInputBase
83
+ class="SInputImage"
84
+ :class="[size]"
85
+ :label="label"
86
+ :note="note"
87
+ :info="info"
88
+ :check-icon="checkIcon"
89
+ :check-text="checkText"
90
+ :check-color="checkColor"
91
+ :hide-error="hideError"
92
+ :validation="validation"
93
+ >
94
+ <template #default>
95
+ <input
96
+ ref="fileInput"
97
+ class="file-input"
98
+ type="file"
99
+ :accept="accept"
100
+ @change="onFileSelect"
101
+ >
102
+
103
+ <div class="container">
104
+ <div class="image" :class="[imageType]">
105
+ <div v-if="imageSrc" class="image-fill">
106
+ <img class="image-fill-src" :src="imageSrc">
107
+ </div>
108
+ <div v-else class="image-empty">
109
+ <div class="image-empty-inner">
110
+ <SIcon class="image-empty-icon" :icon="IconImage" />
111
+ </div>
112
+ </div>
113
+ </div>
114
+ <div class="control">
115
+ <div class="actions">
116
+ <SButton
117
+ size="small"
118
+ :label="selectText"
119
+ :disabled="disabled"
120
+ @click="openFileSelect"
121
+ />
122
+ <SButton
123
+ v-if="nullable && imageSrc"
124
+ size="small"
125
+ mode="danger"
126
+ :label="removeText"
127
+ :disabled="disabled"
128
+ @click="onFileDelete"
129
+ />
130
+ </div>
131
+ <p v-if="help" class="help">
132
+ {{ help }}
133
+ </p>
134
+ </div>
135
+ </div>
136
+ </template>
137
+
138
+ <template v-if="$slots.info" #info>
139
+ <slot name="info" />
140
+ </template>
141
+ </SInputBase>
142
+ </template>
143
+
144
+ <style scoped lang="postcss">
145
+ .file-input {
146
+ display: none;
147
+ }
148
+
149
+ .container {
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 12px;
153
+ padding-top: 4px;
154
+
155
+ @media (min-width: 640px) {
156
+ flex-direction: row;
157
+ align-items: center;
158
+ gap: 24px;
159
+ }
160
+ }
161
+
162
+ .image {
163
+ display: flex;
164
+ flex-shrink: 0;
165
+ width: var(--input-image-width, v-bind(imageWidth));
166
+ aspect-ratio: v-bind(imageAspectRatio);
167
+ overflow: hidden;
168
+ }
169
+
170
+ .image-fill {
171
+ display: flex;
172
+ flex-shrink: 0;
173
+ width: 100%;
174
+ height: 100%;
175
+ overflow: hidden;
176
+ }
177
+
178
+ .image-fill-src {
179
+ width: 100%;
180
+ height: 100%;
181
+ object-fit: cover;
182
+ }
183
+
184
+ .image-empty {
185
+ display: flex;
186
+ border: 1px solid var(--c-border-mute-1);
187
+ padding: 8px;
188
+ width: 100%;
189
+ height: 100%;
190
+ }
191
+
192
+ .image-empty-inner {
193
+ display: flex;
194
+ justify-content: center;
195
+ align-items: center;
196
+ border: 1px dashed var(--c-border-mute-1);
197
+ width: 100%;
198
+ height: 100%;
199
+ }
200
+
201
+ .image-empty-icon {
202
+ width: 24px;
203
+ height: 24px;
204
+ color: var(--c-text-3);
205
+ }
206
+
207
+ .control {
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 8px;
211
+ }
212
+
213
+ .actions {
214
+ display: flex;
215
+ gap: 8px;
216
+ }
217
+
218
+ .help {
219
+ margin: 0;
220
+ line-height: 20px;
221
+ font-size: 12px;
222
+ color: var(--c-text-2);
223
+ }
224
+
225
+ .image.rectangle .image-fill,
226
+ .image.rectangle .image-empty {
227
+ border-radius: 6px;
228
+ }
229
+
230
+ .image.rectangle .image-empty-inner {
231
+ border-radius: 3px;
232
+ }
233
+
234
+ .image.circle .image-fill,
235
+ .image.circle .image-empty,
236
+ .image.circle .image-empty-inner {
237
+ border-radius: 50%;
238
+ }
239
+ </style>
@@ -89,14 +89,14 @@ function onClick() {
89
89
 
90
90
  &:hover {
91
91
  .box {
92
- border-color: var(--c-info-light);
92
+ border-color: var(--c-border-info-1);
93
93
  }
94
94
  }
95
95
  }
96
96
 
97
97
  .input.on {
98
98
  .box {
99
- border-color: var(--c-info-light);
99
+ border-color: var(--c-fg-info-1);
100
100
  }
101
101
 
102
102
  .check {
@@ -107,7 +107,7 @@ function onClick() {
107
107
 
108
108
  .box {
109
109
  position: relative;
110
- border: 1px solid var(--c-divider-1);
110
+ border: 1px solid var(--c-border-mute-1);
111
111
  border-radius: 50%;
112
112
  width: 16px;
113
113
  height: 16px;
@@ -126,7 +126,7 @@ function onClick() {
126
126
  align-items: center;
127
127
  border-radius: 50%;
128
128
  width: 100%;
129
- background-color: var(--c-info-light);
129
+ background-color: var(--c-fg-info-1);
130
130
  opacity: 0;
131
131
  transform: scale(0);
132
132
  transition: opacity 0.25s, transform 0.1s;
@@ -45,7 +45,7 @@ defineEmits<{
45
45
  box-shadow: var(--shadow-depth-5);
46
46
 
47
47
  .dark & {
48
- background-color: var(--c-bg-soft);
48
+ background-color: var(--c-bg-elv-4);
49
49
  }
50
50
  }
51
51
  }
@@ -28,11 +28,11 @@ const classes = computed(() => [
28
28
  .SState {
29
29
  display: inline-flex;
30
30
  align-items: center;
31
- border: 1px solid var(--c-divider-2);
31
+ border: 1px solid var(--c-border-mute-1);
32
32
  border-radius: 6px;
33
33
  font-weight: 500;
34
34
  white-space: nowrap;
35
- background-color: var(--c-mute);
35
+ background-color: var(--c-bg-mute-1);
36
36
  }
37
37
 
38
38
  .SState.mini {
@@ -105,23 +105,23 @@ const classes = computed(() => [
105
105
 
106
106
  .SState.mute {
107
107
  .label { color: var(--c-text-2); }
108
- .indicator { background-color: var(--c-mute-darker); }
108
+ .indicator { background-color: var(--c-fg-gray-1); }
109
109
  }
110
110
 
111
111
  .SState.info {
112
- .indicator { background-color: var(--c-info-text); }
112
+ .indicator { background-color: var(--c-fg-info-1); }
113
113
  }
114
114
 
115
115
  .SState.success {
116
- .indicator { background-color: var(--c-success-text); }
116
+ .indicator { background-color: var(--c-fg-success-1); }
117
117
  }
118
118
 
119
119
  .SState.warning {
120
- .indicator { background-color: var(--c-warning-text); }
120
+ .indicator { background-color: var(--c-fg-warning-1); }
121
121
  }
122
122
 
123
123
  .SState.danger {
124
- .indicator { background-color: var(--c-danger-text); }
124
+ .indicator { background-color: var(--c-fg-danger-1); }
125
125
  }
126
126
 
127
127
  .label {
@@ -126,7 +126,7 @@ const computedCell = computed<TableCell | undefined>(() =>
126
126
 
127
127
  <style scoped lang="postcss">
128
128
  .STableCell {
129
- background-color: var(--c-bg-elv-up);
129
+ background-color: var(--c-bg-elv-3);
130
130
  transition: background-color 0.1s;
131
131
  overflow: hidden;
132
132
 
@@ -148,7 +148,7 @@ function stopDialogPositionListener() {
148
148
 
149
149
  <style scoped lang="postcss">
150
150
  .STableColumn {
151
- background-color: var(--c-bg-soft);
151
+ background-color: var(--c-bg-elv-4);
152
152
 
153
153
  &.has-header {
154
154
  border-top: 1px solid var(--c-divider-2);
@@ -60,7 +60,7 @@ const resetAction = computed(() => {
60
60
  border-radius: calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px) 0 0;
61
61
  padding-right: var(--table-padding-right);
62
62
  padding-left: var(--table-padding-left);
63
- background-color: var(--c-bg-soft);
63
+ background-color: var(--c-bg-elv-4);
64
64
 
65
65
  &.borderless {
66
66
  border-radius: 0;
@@ -4,7 +4,7 @@ import SButton from './SButton.vue'
4
4
 
5
5
  withDefaults(defineProps<TableHeaderAction>(), {
6
6
  show: true,
7
- mode: 'mute',
7
+ mode: 'default',
8
8
  labelMode: 'neutral'
9
9
  })
10
10
  </script>
@@ -0,0 +1,46 @@
1
+ import { type MaybeRefOrGetter, type Ref, ref, toValue, watchEffect } from 'vue'
2
+ import { isFile, isString } from '../support/Utils'
3
+
4
+ export interface ImageSrcFromFile {
5
+ src: Ref<string | null>
6
+ }
7
+
8
+ /**
9
+ * Get renderable image src from the given file. If a string is given, it will
10
+ * be returned as is. It is useful when you want to render an image from a
11
+ * remote URL.
12
+ */
13
+ export function useImageSrcFromFile(
14
+ file: MaybeRefOrGetter<File | string | null>
15
+ ): ImageSrcFromFile {
16
+ const reader = new FileReader()
17
+ const src = ref<string | null>(null)
18
+
19
+ reader.onload = function () {
20
+ src.value = this.result?.toString() ?? null
21
+ }
22
+
23
+ watchEffect(() => {
24
+ read()
25
+ })
26
+
27
+ function read(): void {
28
+ const f = toValue(file)
29
+
30
+ if (isFile(f)) {
31
+ reader.readAsDataURL(f)
32
+ return
33
+ }
34
+
35
+ if (isString(f)) {
36
+ src.value = f
37
+ return
38
+ }
39
+
40
+ src.value = null
41
+ }
42
+
43
+ return {
44
+ src
45
+ }
46
+ }