summaryrefslogtreecommitdiff
path: root/packages/client/src/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'packages/client/src/widgets')
-rw-r--r--packages/client/src/widgets/activity.vue12
-rw-r--r--packages/client/src/widgets/aichan.vue31
-rw-r--r--packages/client/src/widgets/calendar.vue25
-rw-r--r--packages/client/src/widgets/federation.vue16
-rw-r--r--packages/client/src/widgets/index.ts4
-rw-r--r--packages/client/src/widgets/instance-cloud.vue76
-rw-r--r--packages/client/src/widgets/online-users.vue12
-rw-r--r--packages/client/src/widgets/rss-ticker.vue144
-rw-r--r--packages/client/src/widgets/rss.vue26
-rw-r--r--packages/client/src/widgets/slideshow.vue15
-rw-r--r--packages/client/src/widgets/trends.vue12
-rw-r--r--packages/client/src/widgets/widget.ts5
12 files changed, 298 insertions, 80 deletions
diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue
index 7fb9f5894c..7252d65403 100644
--- a/packages/client/src/widgets/activity.vue
+++ b/packages/client/src/widgets/activity.vue
@@ -1,6 +1,6 @@
<template>
<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" class="mkw-activity">
- <template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template>
+ <template #header><i class="fas fa-chart-simple"></i>{{ $ts._widgets.activity }}</template>
<template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template>
<div>
@@ -15,12 +15,12 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
-import * as os from '@/os';
-import MkContainer from '@/components/ui/container.vue';
import XCalendar from './activity.calendar.vue';
import XChart from './activity.chart.vue';
+import { GetFormResultType } from '@/scripts/form';
+import * as os from '@/os';
+import MkContainer from '@/components/ui/container.vue';
import { $i } from '@/account';
const name = 'activity';
@@ -67,7 +67,7 @@ const toggleView = () => {
save();
};
-os.api('charts/user/notes', {
+os.apiGet('charts/user/notes', {
userId: $i.id,
span: 'day',
limit: 7 * 21,
@@ -76,7 +76,7 @@ os.api('charts/user/notes', {
total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i],
notes: res.diffs.normal[i],
replies: res.diffs.reply[i],
- renotes: res.diffs.renote[i]
+ renotes: res.diffs.renote[i],
}));
fetching.value = false;
});
diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue
index cdd367cc84..828490fd9c 100644
--- a/packages/client/src/widgets/aichan.vue
+++ b/packages/client/src/widgets/aichan.vue
@@ -6,8 +6,8 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
const name = 'ai';
@@ -38,22 +38,23 @@ const touched = () => {
//if (this.live2d) this.live2d.changeExpression('gurugurume');
};
-onMounted(() => {
- const onMousemove = (ev: MouseEvent) => {
- const iframeRect = live2d.value.getBoundingClientRect();
- live2d.value.contentWindow.postMessage({
- type: 'moveCursor',
- body: {
- x: ev.clientX - iframeRect.left,
- y: ev.clientY - iframeRect.top,
- }
- }, '*');
- };
+const onMousemove = (ev: MouseEvent) => {
+ const iframeRect = live2d.value.getBoundingClientRect();
+ live2d.value.contentWindow.postMessage({
+ type: 'moveCursor',
+ body: {
+ x: ev.clientX - iframeRect.left,
+ y: ev.clientY - iframeRect.top,
+ },
+ }, '*');
+};
+onMounted(() => {
window.addEventListener('mousemove', onMousemove, { passive: true });
- onUnmounted(() => {
- window.removeEventListener('mousemove', onMousemove);
- });
+});
+
+onUnmounted(() => {
+ window.removeEventListener('mousemove', onMousemove);
});
defineExpose<WidgetComponentExpose>({
diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue
index 2a2b035541..3a0dc8970c 100644
--- a/packages/client/src/widgets/calendar.vue
+++ b/packages/client/src/widgets/calendar.vue
@@ -34,9 +34,10 @@
<script lang="ts" setup>
import { onUnmounted, ref } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
import { i18n } from '@/i18n';
+import { useInterval } from '@/scripts/use-interval';
const name = 'calendar';
@@ -85,28 +86,26 @@ const tick = () => {
i18n.ts._weekday.wednesday,
i18n.ts._weekday.thursday,
i18n.ts._weekday.friday,
- i18n.ts._weekday.saturday
+ i18n.ts._weekday.saturday,
][now.getDay()];
- const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime();
- const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/;
+ const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime();
+ const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/;
const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime();
const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime();
- const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime();
- const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime();
+ const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime();
+ const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime();
- dayP.value = dayNumer / dayDenom * 100;
+ dayP.value = dayNumer / dayDenom * 100;
monthP.value = monthNumer / monthDenom * 100;
- yearP.value = yearNumer / yearDenom * 100;
+ yearP.value = yearNumer / yearDenom * 100;
isHoliday.value = now.getDay() === 0 || now.getDay() === 6;
};
-tick();
-
-const intervalId = window.setInterval(tick, 1000);
-onUnmounted(() => {
- window.clearInterval(intervalId);
+useInterval(tick, 1000, {
+ immediate: true,
+ afterMounted: false,
});
defineExpose<WidgetComponentExpose>({
diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue
index a3862077bb..ac87cdac2e 100644
--- a/packages/client/src/widgets/federation.vue
+++ b/packages/client/src/widgets/federation.vue
@@ -20,11 +20,12 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
import MkContainer from '@/components/ui/container.vue';
import MkMiniChart from '@/components/mini-chart.vue';
import * as os from '@/os';
+import { useInterval } from '@/scripts/use-interval';
const name = 'federation';
@@ -56,20 +57,17 @@ const fetching = ref(true);
const fetch = async () => {
const fetchedInstances = await os.api('federation/instances', {
sort: '+lastCommunicatedAt',
- limit: 5
+ limit: 5,
});
- const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.api('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
+ const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.apiGet('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
instances.value = fetchedInstances;
charts.value = fetchedCharts;
fetching.value = false;
};
-onMounted(() => {
- fetch();
- const intervalId = window.setInterval(fetch, 1000 * 60);
- onUnmounted(() => {
- window.clearInterval(intervalId);
- });
+useInterval(fetch, 1000 * 60, {
+ immediate: true,
+ afterMounted: true,
});
defineExpose<WidgetComponentExpose>({
diff --git a/packages/client/src/widgets/index.ts b/packages/client/src/widgets/index.ts
index 51a82af080..baf6acd23d 100644
--- a/packages/client/src/widgets/index.ts
+++ b/packages/client/src/widgets/index.ts
@@ -6,6 +6,7 @@ export default function(app: App) {
app.component('MkwTimeline', defineAsyncComponent(() => import('./timeline.vue')));
app.component('MkwCalendar', defineAsyncComponent(() => import('./calendar.vue')));
app.component('MkwRss', defineAsyncComponent(() => import('./rss.vue')));
+ app.component('MkwRssTicker', defineAsyncComponent(() => import('./rss-ticker.vue')));
app.component('MkwTrends', defineAsyncComponent(() => import('./trends.vue')));
app.component('MkwClock', defineAsyncComponent(() => import('./clock.vue')));
app.component('MkwActivity', defineAsyncComponent(() => import('./activity.vue')));
@@ -17,6 +18,7 @@ export default function(app: App) {
app.component('MkwServerMetric', defineAsyncComponent(() => import('./server-metric/index.vue')));
app.component('MkwOnlineUsers', defineAsyncComponent(() => import('./online-users.vue')));
app.component('MkwJobQueue', defineAsyncComponent(() => import('./job-queue.vue')));
+ app.component('MkwInstanceCloud', defineAsyncComponent(() => import('./instance-cloud.vue')));
app.component('MkwButton', defineAsyncComponent(() => import('./button.vue')));
app.component('MkwAiscript', defineAsyncComponent(() => import('./aiscript.vue')));
app.component('MkwAichan', defineAsyncComponent(() => import('./aichan.vue')));
@@ -28,12 +30,14 @@ export const widgets = [
'timeline',
'calendar',
'rss',
+ 'rssTicker',
'trends',
'clock',
'activity',
'photos',
'digitalClock',
'federation',
+ 'instanceCloud',
'postForm',
'slideshow',
'serverMetric',
diff --git a/packages/client/src/widgets/instance-cloud.vue b/packages/client/src/widgets/instance-cloud.vue
new file mode 100644
index 0000000000..597ce0e824
--- /dev/null
+++ b/packages/client/src/widgets/instance-cloud.vue
@@ -0,0 +1,76 @@
+<template>
+<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-instance-cloud">
+ <div class="">
+ <MkTagCloud v-if="activeInstances">
+ <li v-for="instance in activeInstances" :key="instance.id">
+ <a @click.prevent="onInstanceClick(instance)">
+ <img style="width: 32px;" :src="instance.iconUrl">
+ </a>
+ </li>
+ </MkTagCloud>
+ </div>
+</MkContainer>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
+import MkContainer from '@/components/ui/container.vue';
+import MkTagCloud from '@/components/tag-cloud.vue';
+import * as os from '@/os';
+import { useInterval } from '@/scripts/use-interval';
+
+const name = 'instanceCloud';
+
+const widgetPropsDef = {
+ transparent: {
+ type: 'boolean' as const,
+ default: false,
+ },
+};
+
+type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
+
+// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
+//const props = defineProps<WidgetComponentProps<WidgetProps>>();
+//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+
+const { widgetProps, configure } = useWidgetPropsManager(name,
+ widgetPropsDef,
+ props,
+ emit,
+);
+
+let cloud = $ref<InstanceType<typeof MkTagCloud> | null>();
+let activeInstances = $shallowRef(null);
+
+function onInstanceClick(i) {
+ os.pageWindow(`/instance-info/${i.host}`);
+}
+
+useInterval(() => {
+ os.api('federation/instances', {
+ sort: '+lastCommunicatedAt',
+ limit: 25,
+ }).then(res => {
+ activeInstances = res;
+ if (cloud) cloud.update();
+ });
+}, 1000 * 60 * 3, {
+ immediate: true,
+ afterMounted: true,
+});
+
+defineExpose<WidgetComponentExpose>({
+ name,
+ configure,
+ id: props.widget ? props.widget.id : null,
+});
+</script>
+
+<style lang="scss" scoped>
+
+</style>
diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue
index eb3184fe9d..4122a82657 100644
--- a/packages/client/src/widgets/online-users.vue
+++ b/packages/client/src/widgets/online-users.vue
@@ -8,9 +8,10 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
import * as os from '@/os';
+import { useInterval } from '@/scripts/use-interval';
const name = 'onlineUsers';
@@ -43,12 +44,9 @@ const tick = () => {
});
};
-onMounted(() => {
- tick();
- const intervalId = window.setInterval(tick, 1000 * 15);
- onUnmounted(() => {
- window.clearInterval(intervalId);
- });
+useInterval(tick, 1000 * 15, {
+ immediate: true,
+ afterMounted: true,
});
defineExpose<WidgetComponentExpose>({
diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue
new file mode 100644
index 0000000000..06995bc865
--- /dev/null
+++ b/packages/client/src/widgets/rss-ticker.vue
@@ -0,0 +1,144 @@
+<template>
+<MkContainer :naked="widgetProps.transparent" :show-header="widgetProps.showHeader" class="mkw-rss-ticker">
+ <template #header><i class="fas fa-rss-square"></i>RSS</template>
+ <template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template>
+
+ <div class="ekmkgxbk">
+ <MkLoading v-if="fetching"/>
+ <div v-else class="feed">
+ <transition name="change" mode="default">
+ <MarqueeText :key="key" :duration="widgetProps.duration" :reverse="widgetProps.reverse">
+ <span v-for="item in items" class="item">
+ <a class="link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span class="divider"></span>
+ </span>
+ </MarqueeText>
+ </transition>
+ </div>
+ </div>
+</MkContainer>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, watch } from 'vue';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import MarqueeText from '@/components/marquee.vue';
+import { GetFormResultType } from '@/scripts/form';
+import * as os from '@/os';
+import MkContainer from '@/components/ui/container.vue';
+import { useInterval } from '@/scripts/use-interval';
+
+const name = 'rssTicker';
+
+const widgetPropsDef = {
+ url: {
+ type: 'string' as const,
+ default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
+ },
+ refreshIntervalSec: {
+ type: 'number' as const,
+ default: 60,
+ },
+ duration: {
+ type: 'range' as const,
+ default: 70,
+ step: 1,
+ min: 5,
+ max: 200,
+ },
+ reverse: {
+ type: 'boolean' as const,
+ default: false,
+ },
+ showHeader: {
+ type: 'boolean' as const,
+ default: false,
+ },
+ transparent: {
+ type: 'boolean' as const,
+ default: false,
+ },
+};
+
+type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
+
+// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
+//const props = defineProps<WidgetComponentProps<WidgetProps>>();
+//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+
+const { widgetProps, configure } = useWidgetPropsManager(name,
+ widgetPropsDef,
+ props,
+ emit,
+);
+
+const items = ref([]);
+const fetching = ref(true);
+let key = $ref(0);
+
+const tick = () => {
+ fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
+ res.json().then(feed => {
+ items.value = feed.items;
+ fetching.value = false;
+ key++;
+ });
+ });
+};
+
+watch(() => widgetProps.url, tick);
+
+useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
+ immediate: true,
+ afterMounted: true,
+});
+
+defineExpose<WidgetComponentExpose>({
+ name,
+ configure,
+ id: props.widget ? props.widget.id : null,
+});
+</script>
+
+<style lang="scss" scoped>
+.change-enter-active, .change-leave-active {
+ position: absolute;
+ top: 0;
+ transition: all 1s ease;
+}
+.change-enter-from {
+ opacity: 0;
+ transform: translateY(-100%);
+}
+.change-leave-to {
+ opacity: 0;
+ transform: translateY(100%);
+}
+
+.ekmkgxbk {
+ > .feed {
+ --height: 42px;
+ padding: 0;
+ font-size: 0.9em;
+ line-height: var(--height);
+ height: var(--height);
+ contain: strict;
+
+ ::v-deep(.item) {
+ display: inline-flex;
+ align-items: center;
+ vertical-align: bottom;
+ color: var(--fg);
+
+ > .divider {
+ display: inline-block;
+ width: 0.5px;
+ height: 16px;
+ margin: 0 1em;
+ background: var(--divider);
+ }
+ }
+ }
+}
+</style>
diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue
index fc65f11813..72f6249820 100644
--- a/packages/client/src/widgets/rss.vue
+++ b/packages/client/src/widgets/rss.vue
@@ -6,7 +6,7 @@
<div class="ekmkgxbj">
<MkLoading v-if="fetching"/>
<div v-else class="feed">
- <a v-for="item in items" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
+ <a v-for="item in items" class="item" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
</div>
</div>
</MkContainer>
@@ -14,22 +14,23 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
import * as os from '@/os';
import MkContainer from '@/components/ui/container.vue';
+import { useInterval } from '@/scripts/use-interval';
const name = 'rss';
const widgetPropsDef = {
- showHeader: {
- type: 'boolean' as const,
- default: true,
- },
url: {
type: 'string' as const,
default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews',
},
+ showHeader: {
+ type: 'boolean' as const,
+ default: true,
+ },
};
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
@@ -50,7 +51,7 @@ const items = ref([]);
const fetching = ref(true);
const tick = () => {
- fetch(`https://api.rss2json.com/v1/api.json?rss_url=${widgetProps.url}`, {}).then(res => {
+ fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => {
res.json().then(feed => {
items.value = feed.items;
fetching.value = false;
@@ -60,12 +61,9 @@ const tick = () => {
watch(() => widgetProps.url, tick);
-onMounted(() => {
- tick();
- const intervalId = window.setInterval(tick, 60000);
- onUnmounted(() => {
- window.clearInterval(intervalId);
- });
+useInterval(tick, 60000, {
+ immediate: true,
+ afterMounted: true,
});
defineExpose<WidgetComponentExpose>({
@@ -81,7 +79,7 @@ defineExpose<WidgetComponentExpose>({
padding: 0;
font-size: 0.9em;
- > a {
+ > .item {
display: block;
padding: 8px 16px;
color: var(--fg);
diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue
index fd78edbe40..c286312161 100644
--- a/packages/client/src/widgets/slideshow.vue
+++ b/packages/client/src/widgets/slideshow.vue
@@ -13,9 +13,10 @@
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
import * as os from '@/os';
+import { useInterval } from '@/scripts/use-interval';
const name = 'slideshow';
@@ -75,7 +76,7 @@ const fetch = () => {
os.api('drive/files', {
folderId: widgetProps.folderId,
type: 'image/*',
- limit: 100
+ limit: 100,
}).then(res => {
images.value = res;
fetching.value = false;
@@ -96,15 +97,15 @@ const choose = () => {
});
};
+useInterval(change, 10000, {
+ immediate: false,
+ afterMounted: true,
+});
+
onMounted(() => {
if (widgetProps.folderId != null) {
fetch();
}
-
- const intervalId = window.setInterval(change, 10000);
- onUnmounted(() => {
- window.clearInterval(intervalId);
- });
});
defineExpose<WidgetComponentExpose>({
diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue
index 9680f1c892..0f34ea6341 100644
--- a/packages/client/src/widgets/trends.vue
+++ b/packages/client/src/widgets/trends.vue
@@ -19,11 +19,12 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue';
-import { GetFormResultType } from '@/scripts/form';
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
import MkContainer from '@/components/ui/container.vue';
import MkMiniChart from '@/components/mini-chart.vue';
import * as os from '@/os';
+import { useInterval } from '@/scripts/use-interval';
const name = 'hashtags';
@@ -58,12 +59,9 @@ const fetch = () => {
});
};
-onMounted(() => {
- fetch();
- const intervalId = window.setInterval(fetch, 1000 * 60);
- onUnmounted(() => {
- window.clearInterval(intervalId);
- });
+useInterval(fetch, 1000 * 60, {
+ immediate: true,
+ afterMounted: true,
});
defineExpose<WidgetComponentExpose>({
diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts
index 9626d01619..9fdfe7f3e1 100644
--- a/packages/client/src/widgets/widget.ts
+++ b/packages/client/src/widgets/widget.ts
@@ -36,8 +36,9 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default:
const mergeProps = () => {
for (const prop of Object.keys(propsDef)) {
- if (widgetProps.hasOwnProperty(prop)) continue;
- widgetProps[prop] = propsDef[prop].default;
+ if (typeof widgetProps[prop] === 'undefined') {
+ widgetProps[prop] = propsDef[prop].default;
+ }
}
};
watch(widgetProps, () => {