summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/MkSelect.vue
diff options
context:
space:
mode:
authorJulia <julia@insertdomain.name>2025-05-29 00:07:38 +0000
committerJulia <julia@insertdomain.name>2025-05-29 00:07:38 +0000
commit6b554c178b81f13f83a69b19d44b72b282a0c119 (patch)
treef5537f1a56323a4dd57ba150b3cb84a2d8b5dc63 /packages/frontend/src/components/MkSelect.vue
parentmerge: Security fixes (!970) (diff)
parentbump version for release (diff)
downloadsharkey-6b554c178b81f13f83a69b19d44b72b282a0c119.tar.gz
sharkey-6b554c178b81f13f83a69b19d44b72b282a0c119.tar.bz2
sharkey-6b554c178b81f13f83a69b19d44b72b282a0c119.zip
merge: release 2025.4.2 (!1051)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1051 Approved-by: Hazelnoot <acomputerdog@gmail.com> Approved-by: Marie <github@yuugi.dev> Approved-by: Julia <julia@insertdomain.name>
Diffstat (limited to 'packages/frontend/src/components/MkSelect.vue')
-rw-r--r--packages/frontend/src/components/MkSelect.vue133
1 files changed, 102 insertions, 31 deletions
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 79a56b68a8..cf4e4eda74 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -40,11 +40,29 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue';
+import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue';
import { useInterval } from '@@/js/use-interval.js';
+import type { VNode, VNodeChild } from 'vue';
import type { MenuItem } from '@/types/menu.js';
import * as os from '@/os.js';
+type ItemOption = {
+ type?: 'option';
+ value: string | number | null;
+ label: string;
+};
+
+type ItemGroup = {
+ type: 'group';
+ label: string;
+ items: ItemOption[];
+};
+
+export type MkSelectItem = ItemOption | ItemGroup;
+
+// TODO: itemsをslot内のoptionで指定する用法は廃止する(props.itemsを必須化する)
+// see: https://github.com/misskey-dev/misskey/issues/15558
+
const props = defineProps<{
modelValue: string | number | null;
required?: boolean;
@@ -55,6 +73,7 @@ const props = defineProps<{
inline?: boolean;
small?: boolean;
large?: boolean;
+ items?: MkSelectItem[];
}>();
const emit = defineEmits<{
@@ -106,7 +125,30 @@ onMounted(() => {
});
});
-watch(modelValue, () => {
+watch([modelValue, () => props.items], () => {
+ if (props.items) {
+ let found: ItemOption | null = null;
+ for (const item of props.items) {
+ if (item.type === 'group') {
+ for (const option of item.items) {
+ if (option.value === modelValue.value) {
+ found = option;
+ break;
+ }
+ }
+ } else {
+ if (item.value === modelValue.value) {
+ found = item;
+ break;
+ }
+ }
+ }
+ if (found) {
+ currentValueText.value = found.label;
+ }
+ return;
+ }
+
const scanOptions = (options: VNodeChild[]) => {
for (const vnode of options) {
if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
@@ -129,7 +171,7 @@ watch(modelValue, () => {
};
scanOptions(slots.default!());
-}, { immediate: true });
+}, { immediate: true, deep: true });
function show() {
if (opening.value) return;
@@ -138,41 +180,70 @@ function show() {
opening.value = true;
const menu: MenuItem[] = [];
- let options = slots.default!();
- const pushOption = (option: VNode) => {
- menu.push({
- text: option.children as string,
- active: computed(() => modelValue.value === option.props?.value),
- action: () => {
- emit('update:modelValue', option.props?.value);
- },
- });
- };
-
- const scanOptions = (options: VNodeChild[]) => {
- for (const vnode of options) {
- if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
- if (vnode.type === 'optgroup') {
- const optgroup = vnode;
+ if (props.items) {
+ for (const item of props.items) {
+ if (item.type === 'group') {
menu.push({
type: 'label',
- text: optgroup.props?.label,
+ text: item.label,
});
- if (Array.isArray(optgroup.children)) scanOptions(optgroup.children);
- } else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある
- const fragment = vnode;
- if (Array.isArray(fragment.children)) scanOptions(fragment.children);
- } else if (vnode.props == null) { // v-if で条件が false のときにこうなる
- // nop?
+ for (const option of item.items) {
+ menu.push({
+ text: option.label,
+ active: computed(() => modelValue.value === option.value),
+ action: () => {
+ emit('update:modelValue', option.value);
+ },
+ });
+ }
} else {
- const option = vnode;
- pushOption(option);
+ menu.push({
+ text: item.label,
+ active: computed(() => modelValue.value === item.value),
+ action: () => {
+ emit('update:modelValue', item.value);
+ },
+ });
}
}
- };
+ } else {
+ let options = slots.default!();
+
+ const pushOption = (option: VNode) => {
+ menu.push({
+ text: option.children as string,
+ active: computed(() => modelValue.value === option.props?.value),
+ action: () => {
+ emit('update:modelValue', option.props?.value);
+ },
+ });
+ };
- scanOptions(options);
+ const scanOptions = (options: VNodeChild[]) => {
+ for (const vnode of options) {
+ if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
+ if (vnode.type === 'optgroup') {
+ const optgroup = vnode;
+ menu.push({
+ type: 'label',
+ text: optgroup.props?.label,
+ });
+ if (Array.isArray(optgroup.children)) scanOptions(optgroup.children);
+ } else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある
+ const fragment = vnode;
+ if (Array.isArray(fragment.children)) scanOptions(fragment.children);
+ } else if (vnode.props == null) { // v-if で条件が false のときにこうなる
+ // nop?
+ } else {
+ const option = vnode;
+ pushOption(option);
+ }
+ }
+ };
+
+ scanOptions(options);
+ }
os.popupMenu(menu, container.value, {
width: container.value?.offsetWidth,
@@ -197,7 +268,7 @@ function show() {
.caption {
font-size: 0.85em;
padding: 8px 0 0 0;
- color: var(--MI_THEME-fgTransparentWeak);
+ color: color(from var(--MI_THEME-fg) srgb r g b / 0.75);
&:empty {
display: none;