chore: refine create-edge and tests
@ -83,7 +83,6 @@
|
||||
"insert-css": "^2.0.0",
|
||||
"stats-js": "^1.0.1",
|
||||
"tslib": "^2.5.0",
|
||||
"uuid": "^9.0.0",
|
||||
"typedoc-plugin-markdown": "^3.16.0",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import EventEmitter from '@antv/event-emitter';
|
||||
import { AABB, Canvas, DisplayObject, PointLike } from '@antv/g';
|
||||
import { AABB, Canvas, Cursor, DisplayObject, PointLike } from '@antv/g';
|
||||
import { GraphChange, ID } from '@antv/graphlib';
|
||||
import {
|
||||
clone,
|
||||
@ -1763,6 +1763,15 @@ export default class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
|
||||
return this.interactionController.getMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cursor. But the cursor in item's style has higher priority.
|
||||
* @param cursor
|
||||
*/
|
||||
public setCursor(cursor: Cursor) {
|
||||
this.canvas.setCursor(cursor);
|
||||
this.transientCanvas.setCursor(cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add behavior(s) to mode(s).
|
||||
* @param behaviors behavior names or configs
|
||||
|
@ -1,15 +1,14 @@
|
||||
import type { ID, IG6GraphEvent, EdgeModel } from '../../types';
|
||||
import type { IG6GraphEvent } from '../../types';
|
||||
import { warn } from '../../util/warn';
|
||||
import { generateEdgeID } from '../../util/item';
|
||||
import { Behavior } from '../../types/behavior';
|
||||
import { EdgeDisplayModelData } from '../../types/edge';
|
||||
|
||||
const KEYBOARD_TRIGGERS = ['shift', 'ctrl', 'control', 'alt', 'meta'] as const;
|
||||
const EVENT_TRIGGERS = ['click', 'drag'] as const;
|
||||
|
||||
enum Events {
|
||||
BEFORE_ADDING_EDGE = 'beforeaddingedge',
|
||||
AFTER_ADDING_EDGE = 'afteraddingedge',
|
||||
}
|
||||
const VIRTUAL_EDGE_ID = 'g6-create-edge-virtual-edge';
|
||||
const DUMMY_NODE_ID = 'g6-create-edge-dummy-node';
|
||||
|
||||
// type Trigger = (typeof EVENT_TRIGGERS)[number];
|
||||
|
||||
@ -29,7 +28,19 @@ interface CreateEdgeOptions {
|
||||
/**
|
||||
* Config of the created edge.
|
||||
*/
|
||||
edgeConfig: any;
|
||||
edgeConfig: EdgeDisplayModelData;
|
||||
/**
|
||||
* The event name to trigger after creating the virtual edge.
|
||||
*/
|
||||
createVirtualEventName?: string;
|
||||
/**
|
||||
* The event name to trigger after creating the actual edge.
|
||||
*/
|
||||
createActualEventName?: string;
|
||||
/**
|
||||
* The event name to trigger after canceling the behavior.
|
||||
*/
|
||||
cancelCreateEventName?: string;
|
||||
/**
|
||||
* Whether allow the behavior happen on the current item.
|
||||
*/
|
||||
@ -49,7 +60,7 @@ const DEFAULT_OPTIONS: CreateEdgeOptions = {
|
||||
edgeConfig: {},
|
||||
};
|
||||
|
||||
export default class CreateEdge extends Behavior {
|
||||
export class CreateEdge extends Behavior {
|
||||
isKeyDown = false;
|
||||
addingEdge = null;
|
||||
dummyNode = null;
|
||||
@ -90,7 +101,7 @@ export default class CreateEdge extends Behavior {
|
||||
trigger === CLICK_NAME
|
||||
? {
|
||||
'node:click': this.handleCreateEdge,
|
||||
mousemove: this.updateEndPoint,
|
||||
pointermove: this.updateEndPoint,
|
||||
'edge:click': this.cancelCreating,
|
||||
'canvas:click': this.cancelCreating,
|
||||
'combo:click': this.handleCreateEdge,
|
||||
@ -99,9 +110,7 @@ export default class CreateEdge extends Behavior {
|
||||
'node:dragstart': this.handleCreateEdge,
|
||||
'combo:dragstart': this.handleCreateEdge,
|
||||
drag: this.updateEndPoint,
|
||||
'node:drop': this.handleCreateEdge,
|
||||
'combo:drop': this.handleCreateEdge,
|
||||
dragend: this.onDragEnd,
|
||||
drop: this.onDrop,
|
||||
};
|
||||
|
||||
const keyboardEvents = key
|
||||
@ -118,6 +127,10 @@ export default class CreateEdge extends Behavior {
|
||||
};
|
||||
|
||||
handleCreateEdge = (e: IG6GraphEvent) => {
|
||||
if (this.options.key && !this.isKeyDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.shouldEnd(e)) {
|
||||
return;
|
||||
}
|
||||
@ -125,88 +138,98 @@ export default class CreateEdge extends Behavior {
|
||||
const { graph, options, addingEdge } = this;
|
||||
const currentNodeId = e.itemId;
|
||||
|
||||
const edgeConfig = options.edgeConfig;
|
||||
const isDragTrigger = options.trigger === 'drag';
|
||||
const { edgeConfig, createVirtualEventName, createActualEventName } =
|
||||
options;
|
||||
|
||||
if (addingEdge) {
|
||||
const updateConfig = {
|
||||
id: addingEdge.id,
|
||||
// create edge end, add the actual edge to graph and remove the virtual edge and node
|
||||
graph.addData('edge', {
|
||||
id: generateEdgeID(addingEdge.source, currentNodeId),
|
||||
source: addingEdge.source,
|
||||
target: currentNodeId,
|
||||
data: {
|
||||
type: currentNodeId === addingEdge.source ? 'loop' : edgeConfig.type,
|
||||
...edgeConfig,
|
||||
type:
|
||||
currentNodeId === addingEdge.source ? 'loop-edge' : edgeConfig.type,
|
||||
},
|
||||
};
|
||||
|
||||
graph.emit(Events.BEFORE_ADDING_EDGE);
|
||||
graph.updateData('edge', updateConfig);
|
||||
graph.emit(Events.AFTER_ADDING_EDGE, { edge: addingEdge });
|
||||
if (!isDragTrigger) {
|
||||
// this.cancelCreating();
|
||||
this.addingEdge = null;
|
||||
});
|
||||
if (createActualEventName) {
|
||||
graph.emit(createActualEventName, { edge: addingEdge });
|
||||
}
|
||||
this.cancelCreating();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDragTrigger) {
|
||||
this.dummyNode = graph.addData('node', {
|
||||
id: 'dummy',
|
||||
data: {
|
||||
// type: 'circle-node',
|
||||
r: 1,
|
||||
label: '',
|
||||
this.dummyNode = graph.addData('node', {
|
||||
id: DUMMY_NODE_ID,
|
||||
data: {
|
||||
x: e.canvas.x,
|
||||
y: e.canvas.y,
|
||||
keyShape: {
|
||||
opacity: 0,
|
||||
interactive: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
labelShape: {
|
||||
opacity: 0,
|
||||
},
|
||||
anchorPoints: [[0.5, 0.5]],
|
||||
},
|
||||
});
|
||||
this.addingEdge = graph.addData('edge', {
|
||||
id: generateEdgeID(currentNodeId, currentNodeId),
|
||||
id: VIRTUAL_EDGE_ID,
|
||||
source: currentNodeId,
|
||||
target: isDragTrigger ? 'dummy' : currentNodeId,
|
||||
target: DUMMY_NODE_ID,
|
||||
data: {
|
||||
...edgeConfig,
|
||||
},
|
||||
} as EdgeModel);
|
||||
});
|
||||
if (createVirtualEventName) {
|
||||
graph.emit(createVirtualEventName, { edge: this.addingEdge });
|
||||
}
|
||||
};
|
||||
|
||||
onDragEnd = (e: IG6GraphEvent) => {
|
||||
onDrop = async (e: IG6GraphEvent) => {
|
||||
const { addingEdge, options, graph } = this;
|
||||
|
||||
const { edgeConfig, key } = options;
|
||||
const { edgeConfig, key, createActualEventName } = options;
|
||||
if (key && !this.isKeyDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (addingEdge) {
|
||||
if (!addingEdge) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { itemId, itemType } = e;
|
||||
const elements = await this.graph.canvas.document.elementsFromPoint(
|
||||
e.canvas.x,
|
||||
e.canvas.y,
|
||||
);
|
||||
const currentIds = elements
|
||||
// @ts-ignore TODO: G type
|
||||
.map((ele) => ele.parentNode.getAttribute?.('data-item-id'))
|
||||
.filter((id) => id !== undefined && !DUMMY_NODE_ID !== id);
|
||||
const dropId = currentIds.find(
|
||||
(id) => this.graph.getComboData(id) || this.graph.getNodeData(id),
|
||||
);
|
||||
|
||||
if (
|
||||
!itemId ||
|
||||
itemId === addingEdge.source ||
|
||||
itemType !== 'node' ||
|
||||
itemId === 'dummy'
|
||||
) {
|
||||
if (!dropId) {
|
||||
this.cancelCreating();
|
||||
return;
|
||||
}
|
||||
|
||||
const updateConfig = {
|
||||
id: addingEdge.id,
|
||||
graph.addData('edge', {
|
||||
id: generateEdgeID(addingEdge.source, dropId),
|
||||
source: addingEdge.source,
|
||||
target: itemId,
|
||||
target: dropId,
|
||||
data: {
|
||||
type: itemId === addingEdge.source ? 'loop' : edgeConfig.type,
|
||||
...edgeConfig,
|
||||
type: dropId === addingEdge.source ? 'loop-edge' : edgeConfig.type,
|
||||
},
|
||||
};
|
||||
|
||||
graph.emit(Events.BEFORE_ADDING_EDGE);
|
||||
graph.updateData('edge', updateConfig);
|
||||
graph.emit(Events.AFTER_ADDING_EDGE, { edge: addingEdge });
|
||||
});
|
||||
if (createActualEventName) {
|
||||
graph.emit(createActualEventName, { edge: addingEdge });
|
||||
}
|
||||
this.cancelCreating();
|
||||
};
|
||||
|
||||
@ -231,15 +254,24 @@ export default class CreateEdge extends Behavior {
|
||||
graph.updatePosition('node', {
|
||||
id: targetId,
|
||||
data: {
|
||||
x: e.offset.x,
|
||||
y: e.offset.y,
|
||||
x: e.canvas.x,
|
||||
y: e.canvas.y,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
cancelCreating = () => {
|
||||
this.removeAddingEdge();
|
||||
this.removeDummyNode();
|
||||
if (this.addingEdge) {
|
||||
this.graph.removeData('edge', VIRTUAL_EDGE_ID);
|
||||
this.addingEdge = null;
|
||||
}
|
||||
if (this.dummyNode) {
|
||||
this.graph.removeData('node', DUMMY_NODE_ID);
|
||||
this.dummyNode = null;
|
||||
}
|
||||
if (this.options.cancelCreateEventName) {
|
||||
this.graph.emit(this.options.cancelCreateEventName, {});
|
||||
}
|
||||
};
|
||||
|
||||
onKeyDown = (e: KeyboardEvent) => {
|
||||
@ -260,18 +292,4 @@ export default class CreateEdge extends Behavior {
|
||||
}
|
||||
this.isKeyDown = false;
|
||||
};
|
||||
|
||||
removeAddingEdge() {
|
||||
if (this.addingEdge) {
|
||||
this.graph.removeData('edge', this.addingEdge.id);
|
||||
this.addingEdge = null;
|
||||
}
|
||||
}
|
||||
|
||||
removeDummyNode() {
|
||||
if (this.dummyNode) {
|
||||
this.graph.removeData('node', this.dummyNode.id);
|
||||
this.dummyNode = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +110,7 @@ const stdLib = {
|
||||
},
|
||||
edges: {
|
||||
'line-edge': LineEdge,
|
||||
'loop-edge': LoopEdge,
|
||||
},
|
||||
combos: {
|
||||
'circle-combo': CircleCombo,
|
||||
@ -248,6 +249,7 @@ const Extensions = {
|
||||
CollapseExpandCombo,
|
||||
DragNode,
|
||||
DragCombo,
|
||||
CreateEdge,
|
||||
//plugins
|
||||
BasePlugin,
|
||||
History,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import EventEmitter from '@antv/event-emitter';
|
||||
import { AABB, Canvas, DisplayObject, PointLike } from '@antv/g';
|
||||
import { AABB, Canvas, Cursor, DisplayObject, PointLike } from '@antv/g';
|
||||
import { ID } from '@antv/graphlib';
|
||||
import { Command } from '../stdlib/plugin/history/command';
|
||||
import { Hooks } from '../types/hook';
|
||||
@ -591,6 +591,11 @@ export interface IGraph<
|
||||
* @group Interaction
|
||||
*/
|
||||
getMode: () => string;
|
||||
/**
|
||||
* Set the cursor. But the cursor in item's style has higher priority.
|
||||
* @param cursor
|
||||
*/
|
||||
setCursor: (cursor: Cursor) => void;
|
||||
/**
|
||||
* Add behavior(s) to mode(s).
|
||||
* @param behaviors behavior names or configs
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ID } from '@antv/graphlib';
|
||||
import { Group } from '@antv/g';
|
||||
import { uniqueId } from '@antv/util';
|
||||
import { IGraph } from '../types';
|
||||
import Combo from '../item/combo';
|
||||
import Edge from '../item/edge';
|
||||
import Node from '../item/node';
|
||||
import { GraphCore } from '../types/data';
|
||||
import { getCombinedBoundsByItem } from './shape';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
/**
|
||||
* Find the edges whose source and target are both in the ids.
|
||||
@ -144,5 +144,5 @@ export const upsertTransientItem = (
|
||||
* @returns
|
||||
*/
|
||||
export function generateEdgeID(source: ID, target: ID) {
|
||||
return [source, target, uuidv4()].join('->');
|
||||
return [source, target, uniqueId()].join('->');
|
||||
}
|
||||
|
@ -1,8 +1,20 @@
|
||||
import G6 from '../../../src/index';
|
||||
import { extend, Graph, Extensions } from '../../../src/index';
|
||||
import { TestCaseContext } from '../interface';
|
||||
|
||||
export default (context: TestCaseContext) => {
|
||||
return new G6.Graph({
|
||||
export default (context: TestCaseContext, options) => {
|
||||
const createEdgeOptions = options || {
|
||||
trigger: 'click',
|
||||
edgeConfig: { keyShape: { stroke: '#f00' } },
|
||||
createVirtualEventName: 'begincreate',
|
||||
cancelCreateEventName: 'cancelcreate',
|
||||
};
|
||||
const ExtGraph = extend(Graph, {
|
||||
behaviors: {
|
||||
'create-edge': Extensions.CreateEdge,
|
||||
'brush-select': Extensions.BrushSelect,
|
||||
},
|
||||
});
|
||||
const graph = new ExtGraph({
|
||||
...context,
|
||||
layout: {
|
||||
type: 'grid',
|
||||
@ -33,7 +45,24 @@ export default (context: TestCaseContext) => {
|
||||
],
|
||||
},
|
||||
modes: {
|
||||
default: [{ type: 'create-edge', trigger: 'click' }],
|
||||
default: [
|
||||
{
|
||||
type: 'brush-select',
|
||||
eventName: 'afterbrush',
|
||||
},
|
||||
{
|
||||
type: 'create-edge',
|
||||
...createEdgeOptions,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
graph.on('begincreate', (e) => {
|
||||
graph.setCursor('crosshair');
|
||||
});
|
||||
graph.on('cancelcreate', (e) => {
|
||||
graph.setCursor('default');
|
||||
});
|
||||
return graph;
|
||||
};
|
||||
|
192
packages/g6/tests/integration/behaviors-create-edge.spec.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import { resetEntityCounter } from '@antv/g';
|
||||
import { createContext } from './utils';
|
||||
import createEdge from '../demo/behaviors/create-edge';
|
||||
import './utils/useSnapshotMatchers';
|
||||
|
||||
describe('Create edge behavior', () => {
|
||||
beforeEach(() => {
|
||||
/**
|
||||
* SVG Snapshot testing will generate a unique id for each element.
|
||||
* Reset to 0 to keep snapshot consistent.
|
||||
*/
|
||||
resetEntityCounter();
|
||||
});
|
||||
|
||||
it('trigger click should be rendered correctly with Canvas2D', (done) => {
|
||||
const dir = `${__dirname}/snapshots/canvas/behaviors`;
|
||||
const { backgroundCanvas, canvas, transientCanvas, container } =
|
||||
createContext('canvas', 500, 500);
|
||||
|
||||
const graph = createEdge(
|
||||
{
|
||||
container,
|
||||
backgroundCanvas,
|
||||
canvas,
|
||||
transientCanvas,
|
||||
width: 500,
|
||||
height: 500,
|
||||
},
|
||||
{
|
||||
trigger: 'click',
|
||||
edgeConfig: { keyShape: { stroke: '#f00' } },
|
||||
createVirtualEventName: 'begincreate',
|
||||
cancelCreateEventName: 'cancelcreate',
|
||||
},
|
||||
);
|
||||
|
||||
graph.on('afterlayout', async () => {
|
||||
graph.emit('node:click', {
|
||||
itemId: 'node5',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
graph.emit('pointermove', { canvas: { x: 100, y: 100 } });
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-click-begin',
|
||||
);
|
||||
|
||||
graph.emit('node:click', {
|
||||
itemId: 'node2',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-click-finish',
|
||||
);
|
||||
|
||||
graph.emit('node:click', {
|
||||
itemId: 'node5',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
graph.emit('node:click', {
|
||||
itemId: 'node5',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-click-loop',
|
||||
);
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('trigger drag should be rendered correctly with Canvas2D', (done) => {
|
||||
const dir = `${__dirname}/snapshots/canvas/behaviors`;
|
||||
const { backgroundCanvas, canvas, transientCanvas, container } =
|
||||
createContext('canvas', 500, 500);
|
||||
|
||||
const graph = createEdge(
|
||||
{
|
||||
container,
|
||||
backgroundCanvas,
|
||||
canvas,
|
||||
transientCanvas,
|
||||
width: 500,
|
||||
height: 500,
|
||||
},
|
||||
{
|
||||
trigger: 'drag',
|
||||
edgeConfig: { keyShape: { stroke: '#f00' } },
|
||||
},
|
||||
);
|
||||
|
||||
graph.on('afterlayout', async () => {
|
||||
graph.emit('node:dragstart', {
|
||||
itemId: 'node5',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
graph.emit('drag', { canvas: { x: 100, y: 100 } });
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-drag-begin',
|
||||
);
|
||||
|
||||
const nodeModel = graph.getNodeData('node2');
|
||||
graph.emit('drop', {
|
||||
itemId: 'node2',
|
||||
itemType: 'node',
|
||||
canvas: { x: nodeModel?.data.x, y: nodeModel?.data.y },
|
||||
});
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-drag-finish',
|
||||
);
|
||||
|
||||
graph.emit('node:dragstart', {
|
||||
itemId: 'node5',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
graph.emit('drag', { canvas: { x: 100, y: 100 } });
|
||||
const node5Model = graph.getNodeData('node5');
|
||||
graph.emit('drop', {
|
||||
itemId: 'node5',
|
||||
itemType: 'node',
|
||||
canvas: { x: node5Model?.data.x, y: node5Model?.data.y },
|
||||
});
|
||||
await expect(canvas).toMatchCanvasSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-drag-loop',
|
||||
);
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be rendered correctly with SVG', (done) => {
|
||||
const dir = `${__dirname}/snapshots/svg/behaviors`;
|
||||
const { backgroundCanvas, canvas, transientCanvas, container } =
|
||||
createContext('svg', 500, 500);
|
||||
|
||||
const graph = createEdge(
|
||||
{
|
||||
container,
|
||||
backgroundCanvas,
|
||||
canvas,
|
||||
transientCanvas,
|
||||
width: 500,
|
||||
height: 500,
|
||||
},
|
||||
{
|
||||
trigger: 'click',
|
||||
edgeConfig: { keyShape: { stroke: '#f00' } },
|
||||
createVirtualEventName: 'begincreate',
|
||||
cancelCreateEventName: 'cancelcreate',
|
||||
},
|
||||
);
|
||||
|
||||
graph.on('afterlayout', async () => {
|
||||
graph.emit('node:click', {
|
||||
itemId: 'node5',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
graph.emit('pointermove', { canvas: { x: 100, y: 100 } });
|
||||
await expect(canvas).toMatchSVGSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-click-begin',
|
||||
);
|
||||
|
||||
graph.emit('node:click', {
|
||||
itemId: 'node2',
|
||||
itemType: 'node',
|
||||
canvas: { x: 100, y: 100 },
|
||||
});
|
||||
await expect(canvas).toMatchSVGSnapshot(
|
||||
dir,
|
||||
'behaviors-create-edge-click-finish',
|
||||
);
|
||||
|
||||
graph.destroy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.0 KiB |
@ -116,6 +116,9 @@ importers:
|
||||
typescript:
|
||||
specifier: ^5.1.6
|
||||
version: 5.1.6
|
||||
uuid:
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.0
|
||||
devDependencies:
|
||||
'@rollup/plugin-terser':
|
||||
specifier: ^0.4.3
|
||||
@ -40178,6 +40181,11 @@ packages:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
hasBin: true
|
||||
|
||||
/uuid@9.0.0:
|
||||
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/uvu@0.5.6:
|
||||
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
|
||||
engines: {node: '>=8'}
|
||||
|