@fastkit/vui 0.7.46 → 0.7.49

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.
@@ -0,0 +1,58 @@
1
+ .v-busy-image {
2
+ position: relative;
3
+ display: inline-block;
4
+ max-width: 100%;
5
+ vertical-align: bottom;
6
+
7
+ &--clickable {
8
+ cursor: pointer;
9
+ }
10
+
11
+ &--cover {
12
+ background-repeat: no-repeat;
13
+ background-position: center;
14
+ background-size: cover;
15
+ }
16
+
17
+ &--loading::before {
18
+ position: absolute;
19
+ top: 0;
20
+ right: 0;
21
+ bottom: 0;
22
+ left: 0;
23
+ display: block;
24
+ content: '';
25
+ background: rgba(0, 0, 0, 0.1);
26
+ animation: v-tiny-image-blink normal 1.5s infinite ease-in-out;
27
+ }
28
+
29
+ &__node {
30
+ position: relative;
31
+ display: inline-block;
32
+ max-width: 100%;
33
+ vertical-align: bottom;
34
+ }
35
+
36
+ &__slot {
37
+ position: absolute;
38
+ top: 0;
39
+ left: 0;
40
+ width: 100%;
41
+ height: 100%;
42
+ pointer-events: none;
43
+ }
44
+ }
45
+
46
+ @keyframes v-tiny-image-blink {
47
+ 0% {
48
+ opacity: 1;
49
+ }
50
+
51
+ 50% {
52
+ opacity: 0;
53
+ }
54
+
55
+ 100% {
56
+ opacity: 1;
57
+ }
58
+ }
@@ -0,0 +1,278 @@
1
+ import './VBusyImage.scss';
2
+
3
+ import {
4
+ defineComponent,
5
+ HTMLAttributes,
6
+ ImgHTMLAttributes,
7
+ PropType,
8
+ computed,
9
+ ref,
10
+ watch,
11
+ onMounted,
12
+ onBeforeUnmount,
13
+ CSSProperties,
14
+ } from 'vue';
15
+ import { IN_WINDOW, loadImage } from '@fastkit/helpers';
16
+ import { defineSlotsProps } from '@fastkit/vue-utils';
17
+
18
+ export declare type ImgHTMLAttributesPropOptions = {
19
+ [K in keyof ImgHTMLAttributes]-?: PropType<ImgHTMLAttributes[K]>;
20
+ };
21
+
22
+ const IMAGE_ATTRS = [
23
+ 'alt',
24
+ 'crossorigin',
25
+ 'decoding',
26
+ 'sizes',
27
+ 'src',
28
+ 'srcset',
29
+ 'usemap',
30
+ ] as const;
31
+
32
+ export function splitAttrs(attrs: ImgHTMLAttributes): {
33
+ el: HTMLAttributes;
34
+ img: ImgHTMLAttributes;
35
+ } {
36
+ const el: HTMLAttributes = { ...attrs };
37
+ const img: ImgHTMLAttributes = {};
38
+
39
+ for (const ATTR of IMAGE_ATTRS) {
40
+ const value = attrs[ATTR];
41
+ if (value != null) {
42
+ img[ATTR] = value as any;
43
+ }
44
+ }
45
+
46
+ return {
47
+ el,
48
+ img,
49
+ };
50
+ }
51
+
52
+ export type BusyImageLoadState = 'pending' | 'loading' | 'loaded' | 'error';
53
+
54
+ export type BusyImageSizeValue = number | string;
55
+
56
+ const NUMBERISH_PROP = [Number, String] as PropType<BusyImageSizeValue>;
57
+
58
+ const DEFAULT_HEIGHT = 150;
59
+
60
+ const resolveBusyImageSizeValue = (
61
+ source?: BusyImageSizeValue,
62
+ ): string | undefined => {
63
+ if (source == null) return;
64
+ return isNaN(source as number) ? String(source) : `${source}px`;
65
+ };
66
+
67
+ export interface BusyImageRect {
68
+ width?: string;
69
+ height?: string;
70
+ }
71
+
72
+ export interface BusyImageRects {
73
+ placeholder: BusyImageRect;
74
+ main: BusyImageRect;
75
+ }
76
+
77
+ /**
78
+ * 画像表示用コンポーネント
79
+ * <img /> タグのように振る舞うが、ローディング表示や、表示サイズの調整など、ちょっといい感じにやるためのコンポーネント
80
+ */
81
+ export const VBusyImage = defineComponent({
82
+ name: 'VBusyImage',
83
+ props: {
84
+ ...(undefined as unknown as ImgHTMLAttributesPropOptions),
85
+ aspectRatio: String,
86
+ width: NUMBERISH_PROP,
87
+ height: NUMBERISH_PROP,
88
+ cover: Boolean,
89
+ ...defineSlotsProps<{
90
+ default: void;
91
+ }>(),
92
+ },
93
+ emits: {
94
+ load: (ev: Event) => true,
95
+ error: (ev: Event) => true,
96
+ },
97
+ setup(props, ctx) {
98
+ const loadStateRef = ref<BusyImageLoadState>(
99
+ props.cover && isAvairableSrc(ctx.attrs.src as any)
100
+ ? 'loading'
101
+ : 'pending',
102
+ );
103
+ const attrsRef = computed(() => splitAttrs(ctx.attrs));
104
+ const isPendingRef = computed(() => loadStateRef.value === 'pending');
105
+ const isLoadingRef = computed(() => loadStateRef.value === 'loading');
106
+ const isLoadedRef = computed(() => loadStateRef.value === 'loaded');
107
+ const isErrorRef = computed(() => loadStateRef.value === 'error');
108
+ const classesRef = computed(() => [
109
+ `v-busy-image--${loadStateRef.value}`,
110
+ {
111
+ 'v-busy-image--cover': props.cover,
112
+ 'v-busy-image--clickable': typeof ctx.attrs.onClick === 'function',
113
+ },
114
+ ]);
115
+ const nodeRef = ref<HTMLImageElement>();
116
+ const coverImageRef = ref<HTMLImageElement>();
117
+ const rectsRef = computed<BusyImageRects>(() => {
118
+ const placeholder: BusyImageRect = {};
119
+ const main: BusyImageRect = {};
120
+ const { cover } = props;
121
+ let { width, height } = props;
122
+
123
+ if (height == null && cover) {
124
+ height = DEFAULT_HEIGHT;
125
+ }
126
+
127
+ if (width == null && cover) {
128
+ width = height;
129
+ }
130
+
131
+ width = resolveBusyImageSizeValue(width);
132
+ height = resolveBusyImageSizeValue(height);
133
+
134
+ main.width = width;
135
+ main.height = height;
136
+
137
+ placeholder.width = width;
138
+ placeholder.height = height;
139
+
140
+ if (placeholder.height == null) {
141
+ placeholder.height = resolveBusyImageSizeValue(DEFAULT_HEIGHT);
142
+ }
143
+
144
+ if (placeholder.width == null) {
145
+ placeholder.width = placeholder.height;
146
+ }
147
+
148
+ return {
149
+ placeholder,
150
+ main,
151
+ };
152
+ });
153
+
154
+ const currentRectRef = computed<BusyImageRect>(() =>
155
+ isLoadedRef.value ? rectsRef.value.main : rectsRef.value.placeholder,
156
+ );
157
+
158
+ const stylesRef = computed<CSSProperties>(() => {
159
+ const { value: coverImage } = coverImageRef;
160
+ const coverSrc = coverImage && coverImage.src;
161
+ const styles: CSSProperties = {
162
+ ...currentRectRef.value,
163
+ };
164
+ if (coverSrc) {
165
+ styles.backgroundImage = `url(${coverSrc})`;
166
+ }
167
+ return styles;
168
+ });
169
+
170
+ let booted = false;
171
+
172
+ watch(
173
+ () => ctx.attrs.src as string | null | undefined,
174
+ (value) => {
175
+ coverImageRef.value = undefined;
176
+ if (isAvairableSrc(value)) {
177
+ loadStateRef.value = 'loading';
178
+ if (props.cover) {
179
+ loadImage(value)
180
+ .then((image) => {
181
+ coverImageRef.value = image;
182
+ setManualState('load');
183
+ })
184
+ .catch((err) => {
185
+ setManualState('error');
186
+ });
187
+ }
188
+ } else {
189
+ loadStateRef.value = 'pending';
190
+ }
191
+ },
192
+ { immediate: IN_WINDOW },
193
+ );
194
+
195
+ const setManualState = (loadType: 'load' | 'error') => {
196
+ loadStateRef.value = loadType === 'load' ? 'loaded' : 'error';
197
+
198
+ const ev = new Event(loadType, {
199
+ bubbles: false,
200
+ cancelable: false,
201
+ composed: false,
202
+ });
203
+
204
+ ctx.emit(loadType as any, ev);
205
+ };
206
+
207
+ onMounted(() => {
208
+ if (booted || props.cover) return;
209
+
210
+ const image = nodeRef.value;
211
+ if (!image) return;
212
+
213
+ const loadType = imageIsReady(image);
214
+
215
+ if (loadType) {
216
+ setManualState(loadType);
217
+ }
218
+ });
219
+
220
+ onBeforeUnmount(() => {
221
+ coverImageRef.value = undefined;
222
+ });
223
+
224
+ const handleLoad = (ev: Event) => {
225
+ booted = true;
226
+ loadStateRef.value = 'loaded';
227
+ ctx.emit('load', ev);
228
+ };
229
+
230
+ const handleError = (ev: Event) => {
231
+ booted = true;
232
+ loadStateRef.value = 'error';
233
+ ctx.emit('error', ev);
234
+ };
235
+
236
+ ctx.expose({
237
+ state: loadStateRef,
238
+ isPending: isPendingRef,
239
+ isLoading: isLoadingRef,
240
+ isLoaded: isLoadedRef,
241
+ isError: isErrorRef,
242
+ });
243
+
244
+ return () => {
245
+ const { el: elAttrs, img: imgAttrs } = attrsRef.value;
246
+ const styles = stylesRef.value;
247
+ const children = ctx.slots.default && ctx.slots.default();
248
+
249
+ return (
250
+ <span
251
+ class={['v-busy-image', classesRef.value]}
252
+ {...elAttrs}
253
+ style={styles}>
254
+ {!props.cover && (
255
+ <img
256
+ key="img"
257
+ ref={nodeRef}
258
+ class="v-busy-image__node"
259
+ {...imgAttrs}
260
+ onLoad={handleLoad}
261
+ onError={handleError}
262
+ />
263
+ )}
264
+ {!!children && <span class="v-busy-image__slot">{children}</span>}
265
+ </span>
266
+ );
267
+ };
268
+ },
269
+ });
270
+
271
+ function imageIsReady(image: HTMLImageElement): 'load' | 'error' | false {
272
+ if (!image.complete) return false;
273
+ return image.naturalWidth + image.naturalHeight > 0 ? 'load' : 'error';
274
+ }
275
+
276
+ function isAvairableSrc(src: string | null | undefined): src is string {
277
+ return typeof src === 'string' && src !== '';
278
+ }
@@ -0,0 +1 @@
1
+ export * from './VBusyImage';
@@ -110,10 +110,11 @@
110
110
  align-items: center;
111
111
  justify-content: center;
112
112
  justify-self: stretch;
113
+ pointer-events: none;
113
114
  }
114
115
 
115
116
  &--loading &__content {
116
- pointer-events: none;
117
+ // pointer-events: none;
117
118
  opacity: 0;
118
119
  }
119
120
 
Binary file
@@ -0,0 +1,78 @@
1
+ @use '../../styles/core.scss';
2
+
3
+ .v-chip {
4
+ @include core.button-reset();
5
+
6
+ $chip-sizes: xs, sm, md, lg, xl;
7
+
8
+ --chip-radius: calc(var(--chip-height) / 2);
9
+
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ height: var(--chip-height);
14
+ padding: 0 var(--chip-padding);
15
+ overflow: hidden;
16
+ font-size: var(--chip-font-size);
17
+ font-weight: var(--chip-font-weight);
18
+ line-height: 1;
19
+ text-align: center;
20
+ white-space: nowrap;
21
+ vertical-align: bottom;
22
+ cursor: default;
23
+ border-style: solid;
24
+ border-width: 1px;
25
+ border-radius: var(--chip-radius);
26
+
27
+ --shadow-color: rgba(0, 0, 0, 0) !important; // @TODO layer supports
28
+ --focusShadow-color: rgba(0, 0, 0, 0) !important; // @TODO layer supports
29
+
30
+ &--clickable {
31
+ cursor: pointer;
32
+ }
33
+
34
+ @each $size in $chip-sizes {
35
+ &--#{$size} {
36
+ --chip-height: var(--chip-height-#{$size});
37
+ --chip-font-size: var(--chip-font-size-#{$size});
38
+ }
39
+ }
40
+
41
+ &--label {
42
+ --chip-radius: 4px;
43
+ }
44
+
45
+ // &[disabled] {
46
+ // pointer-events: none;
47
+
48
+ // &::before {
49
+ // content: none;
50
+ // }
51
+ // }
52
+ // // noop
53
+
54
+ // img,
55
+ // &__image {
56
+ // display: inline-flex;
57
+ // width: inherit !important;
58
+ // height: inherit !important;
59
+ // border-radius: inherit;
60
+ // object-fit: cover;
61
+ // }
62
+
63
+ &__icon {
64
+ &--start {
65
+ margin-right: calc(var(--chip-padding) * 0.5);
66
+ margin-left: calc(var(--chip-padding) * -0.5);
67
+ }
68
+
69
+ &--end {
70
+ margin-right: calc(var(--chip-padding) * -0.5);
71
+ margin-left: calc(var(--chip-padding) * 0.5);
72
+ }
73
+ }
74
+
75
+ .v-avatar {
76
+ --avatar-size: 14px;
77
+ }
78
+ }
@@ -0,0 +1,102 @@
1
+ import './VChip.scss';
2
+ import { defineComponent, computed, PropType, VNodeChild } from 'vue';
3
+ import {
4
+ navigationableInheritProps,
5
+ useNavigationable,
6
+ renderSlotOrEmpty,
7
+ } from '@fastkit/vue-utils';
8
+ import { colorSchemeProps, useColorClasses } from '@fastkit/vue-color-scheme';
9
+ import { useVui } from '../../injections';
10
+ import type { IconName } from '../VIcon';
11
+ import { VIcon } from '../VIcon';
12
+
13
+ export const CHIP_SIZES = ['xs', 'sm', 'md', 'lg', 'xl'] as const;
14
+
15
+ export type ChipSize = typeof CHIP_SIZES[number];
16
+
17
+ export type VChipIcon = () => VNodeChild;
18
+
19
+ export type RawVChipIcon = IconName | VChipIcon;
20
+
21
+ export function createChipProps() {
22
+ return {
23
+ ...colorSchemeProps(),
24
+ ...navigationableInheritProps,
25
+ size: {
26
+ type: String as PropType<ChipSize>,
27
+ default: 'md',
28
+ },
29
+ startIcon: [String, Function] as PropType<RawVChipIcon>,
30
+ endIcon: [String, Function] as PropType<RawVChipIcon>,
31
+ label: Boolean,
32
+ disabled: Boolean,
33
+ };
34
+ }
35
+
36
+ function resolveRawVChipIcon(
37
+ raw: RawVChipIcon | undefined,
38
+ type: 'start' | 'end',
39
+ ): VChipIcon | undefined {
40
+ if (!raw) return;
41
+ if (typeof raw === 'function') {
42
+ return () => {
43
+ const child = raw();
44
+ if (child == null) return;
45
+ return (
46
+ <span class={['v-chip__icon', `v-chip__icon--${type}`]}>{child}</span>
47
+ );
48
+ };
49
+ }
50
+ return () => (
51
+ <VIcon class={['v-chip__icon', `v-chip__icon--${type}`]} name={raw} />
52
+ );
53
+ }
54
+
55
+ export const VChip = defineComponent({
56
+ name: 'VChip',
57
+ inheritAttrs: false,
58
+ props: createChipProps(),
59
+ setup(props, ctx) {
60
+ const vui = useVui();
61
+ const defaults = vui.setting('buttonDefault');
62
+ const color = useColorClasses({
63
+ color: () => props.color || defaults.color,
64
+ variant: () => props.variant || defaults.variant,
65
+ });
66
+ const startIcon = computed(() => {
67
+ const _icon = resolveRawVChipIcon(props.startIcon, 'start');
68
+ return _icon && _icon();
69
+ });
70
+ const endIcon = computed(() => {
71
+ const _icon = resolveRawVChipIcon(props.endIcon, 'end');
72
+ return _icon && _icon();
73
+ });
74
+ const navigationable = useNavigationable(ctx, {
75
+ clickableClassName: () => 'v-chip--clickable',
76
+ linkFallbackTag: 'div',
77
+ });
78
+
79
+ const classes = computed(() => {
80
+ const { size } = props;
81
+ return [
82
+ {
83
+ 'v-chip--label': props.label,
84
+ },
85
+ typeof size === 'string' && `v-chip--${size}`,
86
+ ];
87
+ });
88
+
89
+ return () => {
90
+ const { Tag, attrs } = navigationable.value;
91
+ return (
92
+ <Tag
93
+ {...attrs}
94
+ class={['v-chip', classes.value, color.colorClasses.value]}>
95
+ {startIcon.value}
96
+ <span class="v-chip__content">{renderSlotOrEmpty(ctx.slots)}</span>
97
+ {endIcon.value}
98
+ </Tag>
99
+ );
100
+ };
101
+ },
102
+ });
@@ -0,0 +1 @@
1
+ export * from './VChip';
@@ -123,7 +123,7 @@ export const VFormControl = defineComponent({
123
123
  {getRequiredChip()}
124
124
  {hinttip && (
125
125
  <VTooltip
126
- top
126
+ y="top"
127
127
  openOnHover={_hinttipDelay !== 'click'}
128
128
  openDelay={
129
129
  typeof _hinttipDelay === 'number'
@@ -0,0 +1,31 @@
1
+ .v-skelton-loader-bone {
2
+ position: relative;
3
+ overflow: hidden;
4
+ cursor: progress;
5
+ background: rgba(0, 0, 0, 0.12);
6
+ border-radius: inherit;
7
+
8
+ &::after {
9
+ position: absolute;
10
+ top: 0;
11
+ right: 0;
12
+ left: 0;
13
+ z-index: 1;
14
+ height: 100%;
15
+ content: '';
16
+ background: linear-gradient(
17
+ 90deg,
18
+ hsla(0deg, 0%, 100%, 0),
19
+ hsla(0deg, 0%, 100%, 0.3),
20
+ hsla(0deg, 0%, 100%, 0)
21
+ );
22
+ transform: translateX(-100%);
23
+ animation: v-skelton-loader-bone-loading 1.5s infinite;
24
+ }
25
+ }
26
+
27
+ @keyframes v-skelton-loader-bone-loading {
28
+ 100% {
29
+ transform: translateX(100%);
30
+ }
31
+ }
@@ -0,0 +1,25 @@
1
+ import './VSkeltonLoaderBone.scss';
2
+
3
+ import { defineComponent } from 'vue';
4
+ import { renderSlotOrEmpty } from '@fastkit/vue-utils';
5
+
6
+ export const VSkeltonLoaderBone = defineComponent({
7
+ name: 'VSkeltonLoaderBone',
8
+ inheritAttrs: false,
9
+ props: {
10
+ tag: {
11
+ type: String,
12
+ default: 'div',
13
+ },
14
+ },
15
+ setup(props, ctx) {
16
+ return () => {
17
+ const TagName = props.tag as 'div';
18
+ return (
19
+ <TagName {...ctx.attrs} class="v-skelton-loader-bone">
20
+ {renderSlotOrEmpty(ctx.slots)}
21
+ </TagName>
22
+ );
23
+ };
24
+ },
25
+ });
@@ -0,0 +1 @@
1
+ export * from './VSkeltonLoaderBone';
@@ -99,10 +99,10 @@ export const VWysiwygEditor = defineComponent({
99
99
  watch(
100
100
  () => props.modelValue,
101
101
  (modelValue) => {
102
- editor.value &&
103
- editor.value.commands.setContent(
104
- modelValue == null ? '' : modelValue,
105
- );
102
+ const $editor = editor.value;
103
+ if (!$editor) return;
104
+ $editor.commands.setContent(modelValue == null ? '' : modelValue);
105
+ textRef.value = $editor.getText();
106
106
  },
107
107
  );
108
108
 
@@ -120,6 +120,7 @@ export const VWysiwygEditor = defineComponent({
120
120
  onUpdate: (ev) => {
121
121
  inputControl.value = ev.editor.getHTML();
122
122
  textRef.value = ev.editor.getText();
123
+ console.log(textRef.value);
123
124
  },
124
125
  autofocus: props.autofocus,
125
126
  content: props.modelValue,
@@ -1,7 +1,11 @@
1
1
  export * from './loading';
2
2
  export * from './kits';
3
+ export * from './VSkeltonLoader';
4
+ export * from './VBusyImage';
3
5
  export * from './VGrid';
4
6
  export * from './VPaper';
7
+ export * from './VAvatar';
8
+ export * from './VChip';
5
9
  export * from './VCard';
6
10
  export * from './VIcon';
7
11
  export * from './VButton';
@@ -30,6 +30,9 @@
30
30
  --typo-base-height: 1.5;
31
31
  --typo-base-spacing: 0em;
32
32
 
33
+ // medium
34
+ --typo-medium-weight: 500;
35
+
33
36
  // h1
34
37
  --typo-h1-size: 2.5rem;
35
38
  --typo-h1-weight: 500;
@@ -123,6 +126,22 @@
123
126
  // --typo-overline-height: 2.66;
124
127
  // --typo-overline-spacing: 0.08333em;
125
128
 
129
+ // =============================================
130
+ // chips
131
+ // =============================================
132
+ --chip-padding: 0.875em;
133
+ --chip-font-weight: var(--typo-base-weight);
134
+ --chip-height-xs: 16px;
135
+ --chip-font-size-xs: 10px;
136
+ --chip-height-sm: 24px;
137
+ --chip-font-size-sm: 12px;
138
+ --chip-height-md: 32px;
139
+ --chip-font-size-md: 14px;
140
+ --chip-height-lg: 54px;
141
+ --chip-font-size-lg: 16px;
142
+ --chip-height-xl: 66px;
143
+ --chip-font-size-xl: 18px;
144
+
126
145
  // =============================================
127
146
  // controls
128
147
  // =============================================