merge: 최신 main을 암행부 명령 요약 변경에 통합
This commit is contained in:
@@ -9,15 +9,27 @@ type TouchDragOptions = {
|
|||||||
targetYRatio?: number;
|
targetYRatio?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pointIn = async (locator: Locator, yRatio = 0.5): Promise<TouchPoint> => {
|
const pointInStable = async (locator: Locator, yRatio = 0.5): Promise<TouchPoint> => {
|
||||||
const box = await locator.boundingBox();
|
let previous: TouchPoint | null = null;
|
||||||
if (!box) {
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||||
throw new Error('Touch drag target has no visible bounding box');
|
await locator.evaluate(
|
||||||
|
() => new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve())))
|
||||||
|
);
|
||||||
|
const box = await locator.boundingBox();
|
||||||
|
if (!box) {
|
||||||
|
throw new Error('Touch drag target has no visible bounding box');
|
||||||
|
}
|
||||||
|
const point = {
|
||||||
|
x: box.x + box.width / 2,
|
||||||
|
y: box.y + box.height * yRatio,
|
||||||
|
};
|
||||||
|
if (previous && Math.abs(previous.x - point.x) < 0.25 && Math.abs(previous.y - point.y) < 0.25) {
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
previous = point;
|
||||||
}
|
}
|
||||||
return {
|
if (!previous) throw new Error('Touch drag target did not produce a stable point');
|
||||||
x: box.x + box.width / 2,
|
return previous;
|
||||||
y: box.y + box.height * yRatio,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const touchDrag = async (
|
export const touchDrag = async (
|
||||||
@@ -26,25 +38,66 @@ export const touchDrag = async (
|
|||||||
target: Locator,
|
target: Locator,
|
||||||
options: TouchDragOptions = {}
|
options: TouchDragOptions = {}
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
await source.scrollIntoViewIfNeeded();
|
|
||||||
await target.scrollIntoViewIfNeeded();
|
|
||||||
const from = await pointIn(source);
|
|
||||||
const to = await pointIn(target, options.targetYRatio);
|
|
||||||
const cdp = await page.context().newCDPSession(page);
|
const cdp = await page.context().newCDPSession(page);
|
||||||
await page.evaluate(() => {
|
let from: TouchPoint | null = null;
|
||||||
document.documentElement.removeAttribute('data-playwright-touch-trusted');
|
let to: TouchPoint | null = null;
|
||||||
document.addEventListener(
|
|
||||||
'touchstart',
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||||
(event) => document.documentElement.setAttribute('data-playwright-touch-trusted', String(event.isTrusted)),
|
await source.scrollIntoViewIfNeeded();
|
||||||
{ capture: true, once: true }
|
await target.scrollIntoViewIfNeeded();
|
||||||
);
|
from = await pointInStable(source);
|
||||||
});
|
to = await pointInStable(target, options.targetYRatio);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
for (const element of document.querySelectorAll('[data-playwright-touch-source]')) {
|
||||||
|
element.removeAttribute('data-playwright-touch-source');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await source.evaluate((element) => element.setAttribute('data-playwright-touch-source', ''));
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.documentElement.removeAttribute('data-playwright-touch-trusted');
|
||||||
|
document.documentElement.removeAttribute('data-playwright-touch-source-hit');
|
||||||
|
document.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
(event) => {
|
||||||
|
const sourceElement = document.querySelector('[data-playwright-touch-source]');
|
||||||
|
document.documentElement.setAttribute('data-playwright-touch-trusted', String(event.isTrusted));
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
'data-playwright-touch-source-hit',
|
||||||
|
String(event.target instanceof Node && sourceElement?.contains(event.target))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ capture: true, once: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await cdp.send('Input.dispatchTouchEvent', {
|
||||||
|
type: 'touchStart',
|
||||||
|
touchPoints: [{ ...from, id: 0, radiusX: 1, radiusY: 1, force: 1 }],
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
const startState = await page.evaluate(() => ({
|
||||||
|
trusted: document.documentElement.getAttribute('data-playwright-touch-trusted') === 'true',
|
||||||
|
sourceHit: document.documentElement.getAttribute('data-playwright-touch-source-hit') === 'true',
|
||||||
|
}));
|
||||||
|
if (!startState.trusted) {
|
||||||
|
throw new Error('Chromium did not dispatch a trusted touchstart event');
|
||||||
|
}
|
||||||
|
if (startState.sourceHit) break;
|
||||||
|
|
||||||
|
await cdp.send('Input.dispatchTouchEvent', { type: 'touchCancel', touchPoints: [] });
|
||||||
|
await page.evaluate(() => {
|
||||||
|
for (const element of document.querySelectorAll('[data-playwright-touch-source]')) {
|
||||||
|
element.removeAttribute('data-playwright-touch-source');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
from = null;
|
||||||
|
to = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!from || !to) {
|
||||||
|
throw new Error('Trusted touchstart did not land on the requested drag source');
|
||||||
|
}
|
||||||
|
|
||||||
await cdp.send('Input.dispatchTouchEvent', {
|
|
||||||
type: 'touchStart',
|
|
||||||
touchPoints: [{ ...from, id: 0, radiusX: 1, radiusY: 1, force: 1 }],
|
|
||||||
});
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
const dispatchMove = async (ratio: number) => {
|
const dispatchMove = async (ratio: number) => {
|
||||||
await cdp.send('Input.dispatchTouchEvent', {
|
await cdp.send('Input.dispatchTouchEvent', {
|
||||||
type: 'touchMove',
|
type: 'touchMove',
|
||||||
@@ -68,10 +121,9 @@ export const touchDrag = async (
|
|||||||
}
|
}
|
||||||
await page.waitForTimeout(50);
|
await page.waitForTimeout(50);
|
||||||
await cdp.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
|
await cdp.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] });
|
||||||
const trusted = await page.evaluate(
|
await page.evaluate(() => {
|
||||||
() => document.documentElement.getAttribute('data-playwright-touch-trusted') === 'true'
|
for (const element of document.querySelectorAll('[data-playwright-touch-source]')) {
|
||||||
);
|
element.removeAttribute('data-playwright-touch-source');
|
||||||
if (!trusted) {
|
}
|
||||||
throw new Error('Chromium did not dispatch a trusted touchstart event');
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.26",
|
"vue": "^3.5.26",
|
||||||
"vuedraggable-es": "4.1.1",
|
"vue-draggable-plus": "0.6.1",
|
||||||
"vue-router": "^4.6.4",
|
"vue-router": "^4.6.4",
|
||||||
"zod": "^4.3.5"
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
import { defineComponent, h, type PropType, type SlotsType, type VNode } from 'vue';
|
import { cloneVNode, computed, defineComponent, h, ref, type PropType, type SlotsType, type VNode } from 'vue';
|
||||||
import VueDraggable from 'vuedraggable-es';
|
import { useDraggable, type DraggableEvent, type UseDraggableOptions } from 'vue-draggable-plus';
|
||||||
|
|
||||||
|
const SORTABLE_ITEM_ATTRIBUTE = 'data-sortable-string-list-item';
|
||||||
|
|
||||||
|
// SortableJS allows only one active drag, so this preserves the exact string across grouped lists.
|
||||||
|
let activeStringDrag: { value: string } | null = null;
|
||||||
|
|
||||||
|
const restoreItem = (event: DraggableEvent<string>) => {
|
||||||
|
if (event.oldIndex === undefined) return;
|
||||||
|
event.item.remove();
|
||||||
|
event.from.insertBefore(event.item, event.from.children[event.oldIndex] ?? null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveItem = (list: string[], from: number, to: number): string[] => {
|
||||||
|
if (from === to) return list;
|
||||||
|
const next = [...list];
|
||||||
|
const [item] = next.splice(from, 1);
|
||||||
|
if (item === undefined) return list;
|
||||||
|
next.splice(to, 0, item);
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SortableStringList',
|
name: 'SortableStringList',
|
||||||
@@ -18,26 +38,77 @@ export default defineComponent({
|
|||||||
default: 'div',
|
default: 'div',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: {
|
||||||
|
'update:list': (list: string[]) => Array.isArray(list),
|
||||||
|
},
|
||||||
slots: Object as SlotsType<{
|
slots: Object as SlotsType<{
|
||||||
header?: () => VNode[];
|
header?: () => VNode[];
|
||||||
item: (props: { element: string; index: number }) => VNode[];
|
item: (props: { element: string; index: number }) => VNode[];
|
||||||
}>,
|
}>,
|
||||||
setup(props, { attrs, slots }) {
|
setup(props, { attrs, emit, slots }) {
|
||||||
return () =>
|
const root = ref<HTMLElement | null>(null);
|
||||||
h(
|
let initialChildren: ChildNode[] | null = null;
|
||||||
VueDraggable,
|
|
||||||
{
|
const options = computed<UseDraggableOptions<string>>(() => ({
|
||||||
...attrs,
|
group: props.group,
|
||||||
list: props.list,
|
draggable: `[${SORTABLE_ITEM_ATTRIBUTE}]`,
|
||||||
group: props.group,
|
dataIdAttr: SORTABLE_ITEM_ATTRIBUTE,
|
||||||
itemKey: (item: string) => item,
|
onStart: (event) => {
|
||||||
tag: props.tag,
|
initialChildren = Array.from(event.from.childNodes);
|
||||||
},
|
if (event.oldDraggableIndex === undefined) {
|
||||||
{
|
activeStringDrag = null;
|
||||||
header: () => slots.header?.(),
|
return;
|
||||||
item: ({ element, index }: { element: string; index: number }) =>
|
|
||||||
slots.item({ element, index }),
|
|
||||||
}
|
}
|
||||||
|
const value = props.list[event.oldDraggableIndex];
|
||||||
|
activeStringDrag = value === undefined ? null : { value };
|
||||||
|
},
|
||||||
|
onUpdate: (event) => {
|
||||||
|
restoreItem(event);
|
||||||
|
if (event.oldDraggableIndex === undefined || event.newDraggableIndex === undefined) return;
|
||||||
|
emit('update:list', moveItem(props.list, event.oldDraggableIndex, event.newDraggableIndex));
|
||||||
|
},
|
||||||
|
onRemove: (event) => {
|
||||||
|
restoreItem(event);
|
||||||
|
if (event.pullMode === 'clone') {
|
||||||
|
event.clone.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.oldDraggableIndex === undefined) return;
|
||||||
|
const next = [...props.list];
|
||||||
|
next.splice(event.oldDraggableIndex, 1);
|
||||||
|
emit('update:list', next);
|
||||||
|
},
|
||||||
|
onAdd: (event) => {
|
||||||
|
event.item.remove();
|
||||||
|
if (event.newDraggableIndex === undefined) return;
|
||||||
|
const value = activeStringDrag?.value ?? event.item.getAttribute(SORTABLE_ITEM_ATTRIBUTE);
|
||||||
|
if (value === null) return;
|
||||||
|
const next = [...props.list];
|
||||||
|
next.splice(event.newDraggableIndex, 0, value);
|
||||||
|
emit('update:list', next);
|
||||||
|
},
|
||||||
|
onEnd: (event) => {
|
||||||
|
if (event.from === event.to && event.oldIndex === event.newIndex && initialChildren) {
|
||||||
|
for (const child of initialChildren) event.from.append(child);
|
||||||
|
}
|
||||||
|
initialChildren = null;
|
||||||
|
activeStringDrag = null;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
useDraggable(root, options);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const header = slots.header?.() ?? [];
|
||||||
|
const items = props.list.flatMap((element, index) =>
|
||||||
|
slots.item({ element, index }).map((node) =>
|
||||||
|
cloneVNode(node, {
|
||||||
|
key: element,
|
||||||
|
[SORTABLE_ITEM_ATTRIBUTE]: element,
|
||||||
|
})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
return h(props.tag, { ...attrs, ref: root }, [...header, ...items]);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -691,7 +691,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<p>항목을 끌어 놓거나 위·아래 버튼으로 상대 순서를 바꿉니다.</p>
|
<p>항목을 끌어 놓거나 위·아래 버튼으로 상대 순서를 바꿉니다.</p>
|
||||||
<SortableStringList
|
<SortableStringList
|
||||||
:list="mobileLayoutOrder"
|
v-model:list="mobileLayoutOrder"
|
||||||
tag="ol"
|
tag="ol"
|
||||||
class="mobile-layout-list"
|
class="mobile-layout-list"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ const submitPriority = async (section: PrioritySectionKey) => {
|
|||||||
<div class="priority-column">
|
<div class="priority-column">
|
||||||
<div class="sub_bar legacy-bg2">비활성</div>
|
<div class="sub_bar legacy-bg2">비활성</div>
|
||||||
<SortableStringList
|
<SortableStringList
|
||||||
:list="panel.state.inactive"
|
v-model:list="panel.state.inactive"
|
||||||
:group="`npc-priority-${panel.key}`"
|
:group="`npc-priority-${panel.key}`"
|
||||||
tag="div"
|
tag="div"
|
||||||
class="priority-list"
|
class="priority-list"
|
||||||
@@ -477,7 +477,7 @@ const submitPriority = async (section: PrioritySectionKey) => {
|
|||||||
<div class="priority-column">
|
<div class="priority-column">
|
||||||
<div class="sub_bar legacy-bg2">활성</div>
|
<div class="sub_bar legacy-bg2">활성</div>
|
||||||
<SortableStringList
|
<SortableStringList
|
||||||
:list="panel.state.active"
|
v-model:list="panel.state.active"
|
||||||
:group="`npc-priority-${panel.key}`"
|
:group="`npc-priority-${panel.key}`"
|
||||||
tag="div"
|
tag="div"
|
||||||
class="priority-list"
|
class="priority-list"
|
||||||
|
|||||||
Generated
+23
-14
@@ -210,12 +210,12 @@ importers:
|
|||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.26
|
specifier: ^3.5.26
|
||||||
version: 3.5.41(typescript@6.0.3)
|
version: 3.5.41(typescript@6.0.3)
|
||||||
|
vue-draggable-plus:
|
||||||
|
specifier: 0.6.1
|
||||||
|
version: 0.6.1(@types/sortablejs@1.15.9)
|
||||||
vue-router:
|
vue-router:
|
||||||
specifier: ^4.6.4
|
specifier: ^4.6.4
|
||||||
version: 4.6.4(vue@3.5.41(typescript@6.0.3))
|
version: 4.6.4(vue@3.5.41(typescript@6.0.3))
|
||||||
vuedraggable-es:
|
|
||||||
specifier: 4.1.1
|
|
||||||
version: 4.1.1(vue@3.5.41(typescript@6.0.3))
|
|
||||||
zod:
|
zod:
|
||||||
specifier: ^4.3.5
|
specifier: ^4.3.5
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
@@ -2257,6 +2257,9 @@ packages:
|
|||||||
'@types/sanitize-html@2.16.1':
|
'@types/sanitize-html@2.16.1':
|
||||||
resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==}
|
resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==}
|
||||||
|
|
||||||
|
'@types/sortablejs@1.15.9':
|
||||||
|
resolution: {integrity: sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==}
|
||||||
|
|
||||||
'@types/unist@3.0.3':
|
'@types/unist@3.0.3':
|
||||||
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
|
||||||
|
|
||||||
@@ -4776,6 +4779,15 @@ packages:
|
|||||||
vscode-uri@3.1.0:
|
vscode-uri@3.1.0:
|
||||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||||
|
|
||||||
|
vue-draggable-plus@0.6.1:
|
||||||
|
resolution: {integrity: sha512-FbtQ/fuoixiOfTZzG3yoPl4JAo9HJXRHmBQZFB9x2NYCh6pq0TomHf7g5MUmpaDYv+LU2n6BPq2YN9sBO+FbIg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/sortablejs': ^1.15.0
|
||||||
|
'@vue/composition-api': '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@vue/composition-api':
|
||||||
|
optional: true
|
||||||
|
|
||||||
vue-eslint-parser@10.4.1:
|
vue-eslint-parser@10.4.1:
|
||||||
resolution: {integrity: sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==}
|
resolution: {integrity: sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -4801,11 +4813,6 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
vuedraggable-es@4.1.1:
|
|
||||||
resolution: {integrity: sha512-F35pjSwC8HS/lnaOd+B59nYR4FZmwuhWAzccK9xftRuWds8SU1TZh5myKVM86j5dFOI7S26O64Kwe7LUHnXjlA==}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.2.31
|
|
||||||
|
|
||||||
w3c-keyname@2.2.8:
|
w3c-keyname@2.2.8:
|
||||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||||
|
|
||||||
@@ -6247,6 +6254,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
htmlparser2: 10.1.0
|
htmlparser2: 10.1.0
|
||||||
|
|
||||||
|
'@types/sortablejs@1.15.9': {}
|
||||||
|
|
||||||
'@types/unist@3.0.3': {}
|
'@types/unist@3.0.3': {}
|
||||||
|
|
||||||
'@types/web-bluetooth@0.0.21': {}
|
'@types/web-bluetooth@0.0.21': {}
|
||||||
@@ -8446,7 +8455,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
atomic-sleep: 1.0.0
|
atomic-sleep: 1.0.0
|
||||||
|
|
||||||
sortablejs@1.14.0: {}
|
sortablejs@1.14.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
@@ -8784,6 +8794,10 @@ snapshots:
|
|||||||
|
|
||||||
vscode-uri@3.1.0: {}
|
vscode-uri@3.1.0: {}
|
||||||
|
|
||||||
|
vue-draggable-plus@0.6.1(@types/sortablejs@1.15.9):
|
||||||
|
dependencies:
|
||||||
|
'@types/sortablejs': 1.15.9
|
||||||
|
|
||||||
vue-eslint-parser@10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
|
vue-eslint-parser@10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3(supports-color@7.2.0)
|
debug: 4.4.3(supports-color@7.2.0)
|
||||||
@@ -8817,11 +8831,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 6.0.3
|
typescript: 6.0.3
|
||||||
|
|
||||||
vuedraggable-es@4.1.1(vue@3.5.41(typescript@6.0.3)):
|
|
||||||
dependencies:
|
|
||||||
sortablejs: 1.14.0
|
|
||||||
vue: 3.5.41(typescript@6.0.3)
|
|
||||||
|
|
||||||
w3c-keyname@2.2.8: {}
|
w3c-keyname@2.2.8: {}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
|
|||||||
Reference in New Issue
Block a user