@plait/core 0.52.0-next.0 → 0.53.0

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.
Files changed (39) hide show
  1. package/constants/cursor.d.ts +1 -0
  2. package/esm2022/board/board.component.mjs +3 -3
  3. package/esm2022/constants/cursor.mjs +2 -1
  4. package/esm2022/interfaces/board.mjs +1 -1
  5. package/esm2022/plugins/create-board.mjs +3 -2
  6. package/esm2022/plugins/with-moving.mjs +36 -17
  7. package/esm2022/plugins/with-related-fragment.mjs +20 -0
  8. package/esm2022/plugins/with-selection.mjs +32 -98
  9. package/esm2022/public-api.mjs +2 -1
  10. package/esm2022/transforms/group.mjs +47 -0
  11. package/esm2022/transforms/index.mjs +2 -1
  12. package/esm2022/utils/angle.mjs +75 -0
  13. package/esm2022/utils/debug.mjs +76 -0
  14. package/esm2022/utils/element.mjs +17 -5
  15. package/esm2022/utils/group.mjs +79 -84
  16. package/esm2022/utils/helper.mjs +10 -1
  17. package/esm2022/utils/index.mjs +2 -1
  18. package/esm2022/utils/math.mjs +11 -1
  19. package/esm2022/utils/selection.mjs +89 -3
  20. package/esm2022/utils/touch.mjs +15 -4
  21. package/fesm2022/plait-core.mjs +1331 -1072
  22. package/fesm2022/plait-core.mjs.map +1 -1
  23. package/interfaces/board.d.ts +1 -0
  24. package/package.json +1 -1
  25. package/plugins/with-moving.d.ts +1 -1
  26. package/plugins/with-related-fragment.d.ts +2 -0
  27. package/public-api.d.ts +1 -0
  28. package/transforms/group.d.ts +7 -0
  29. package/transforms/index.d.ts +1 -0
  30. package/utils/angle.d.ts +13 -0
  31. package/utils/debug.d.ts +13 -0
  32. package/utils/group.d.ts +15 -15
  33. package/utils/helper.d.ts +2 -0
  34. package/utils/index.d.ts +1 -0
  35. package/utils/math.d.ts +8 -0
  36. package/utils/selection.d.ts +6 -3
  37. package/utils/touch.d.ts +1 -1
  38. package/esm2022/plugins/with-group.mjs +0 -27
  39. package/plugins/with-group.d.ts +0 -2
@@ -493,6 +493,7 @@ var CursorClass;
493
493
  (function (CursorClass) {
494
494
  CursorClass["crosshair"] = "crosshair";
495
495
  })(CursorClass || (CursorClass = {}));
496
+ const RESIZE_CURSORS = [ResizeCursorClass.ns, ResizeCursorClass.nesw, ResizeCursorClass.ew, ResizeCursorClass.nwse];
496
497
 
497
498
  const ATTACHED_ELEMENT_CLASS_NAME = 'plait-board-attached';
498
499
  const ACTIVE_STROKE_WIDTH = 1;
@@ -952,6 +953,16 @@ function toDomPrecision(v) {
952
953
  function toFixed(v) {
953
954
  return +v.toFixed(2);
954
955
  }
956
+ /**
957
+ * Whether two numbers numbers a and b are approximately equal.
958
+ *
959
+ * @param a - The first point.
960
+ * @param b - The second point.
961
+ * @public
962
+ */
963
+ function approximately(a, b, precision = 0.000001) {
964
+ return Math.abs(a - b) <= precision;
965
+ }
955
966
 
956
967
  function isInPlaitBoard(board, x, y) {
957
968
  const plaitBoardElement = PlaitBoard.getBoardContainer(board);
@@ -1030,6 +1041,15 @@ const RgbaToHEX = (Rgb, opacity) => {
1030
1041
  function isContextmenu(event) {
1031
1042
  return event.button === 2;
1032
1043
  }
1044
+ function uniqueById(elements) {
1045
+ const uniqueMap = new Map();
1046
+ elements.forEach(item => {
1047
+ if (!uniqueMap.has(item.id)) {
1048
+ uniqueMap.set(item.id, item);
1049
+ }
1050
+ });
1051
+ return Array.from(uniqueMap.values());
1052
+ }
1033
1053
 
1034
1054
  /**
1035
1055
  * Check whether to merge an operation into the previous operation.
@@ -2151,8 +2171,16 @@ const isPreventTouchMove = (board) => {
2151
2171
  return !!BOARD_TO_TOUCH_REF.get(board);
2152
2172
  };
2153
2173
  const preventTouchMove = (board, event, state) => {
2154
- if (state && (event.target instanceof HTMLElement || event.target instanceof SVGElement)) {
2155
- BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
2174
+ const hostElement = PlaitBoard.getElementHost(board);
2175
+ const activeHostElement = PlaitBoard.getElementActiveHost(board);
2176
+ if (state) {
2177
+ if ((event.target instanceof HTMLElement || event.target instanceof SVGElement) &&
2178
+ (hostElement.contains(event.target) || activeHostElement.contains(event.target))) {
2179
+ BOARD_TO_TOUCH_REF.set(board, { state, target: event.target instanceof SVGElement ? event.target : undefined });
2180
+ }
2181
+ else {
2182
+ BOARD_TO_TOUCH_REF.set(board, { state, target: undefined });
2183
+ }
2156
2184
  }
2157
2185
  else {
2158
2186
  const ref = BOARD_TO_TOUCH_REF.get(board);
@@ -2170,7 +2198,10 @@ const preventTouchMove = (board, event, state) => {
2170
2198
  */
2171
2199
  const handleTouchTarget = (board) => {
2172
2200
  const touchRef = BOARD_TO_TOUCH_REF.get(board);
2173
- if (touchRef && touchRef.target && !touchRef.target.contains(PlaitBoard.getElementActiveHost(board))) {
2201
+ if (touchRef &&
2202
+ touchRef.target &&
2203
+ !PlaitBoard.getElementHost(board).contains(touchRef.target) &&
2204
+ !PlaitBoard.getElementActiveHost(board).contains(touchRef.target)) {
2174
2205
  touchRef.target.style.opacity = '0';
2175
2206
  const host = createG();
2176
2207
  host.appendChild(touchRef.target);
@@ -2180,1057 +2211,1262 @@ const handleTouchTarget = (board) => {
2180
2211
  }
2181
2212
  };
2182
2213
 
2183
- const PlaitGroupElement = {
2184
- isGroup: (value) => {
2185
- return value.type === 'group';
2214
+ const rotatePoints = (points, centerPoint, angle) => {
2215
+ if (!angle) {
2216
+ angle = 0;
2217
+ }
2218
+ if (Array.isArray(points) && typeof points[0] === 'number') {
2219
+ return rotate(points[0], points[1], centerPoint[0], centerPoint[1], angle);
2220
+ }
2221
+ else {
2222
+ return points.map(point => {
2223
+ return rotate(point[0], point[1], centerPoint[0], centerPoint[1], angle);
2224
+ });
2186
2225
  }
2187
2226
  };
2188
-
2189
- const Viewport = {
2190
- isViewport: (value) => {
2191
- return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
2227
+ const getSelectionAngle = (elements) => {
2228
+ let angle = elements[0]?.angle || 0;
2229
+ elements.forEach(item => {
2230
+ if (item.angle !== angle && !approximately((item.angle % (Math.PI / 2)) - (angle % (Math.PI / 2)), 0)) {
2231
+ angle = 0;
2232
+ }
2233
+ });
2234
+ return angle;
2235
+ };
2236
+ const hasSameAngle = (elements) => {
2237
+ return !!getSelectionAngle(elements);
2238
+ };
2239
+ const getRotatedBoundingRectangle = (rectanglesCornerPoints, angle) => {
2240
+ let rectanglesFromOrigin = [];
2241
+ for (let i = 0; i < rectanglesCornerPoints.length; i++) {
2242
+ const cornerPoints = rectanglesCornerPoints[i];
2243
+ const invertCornerPointsFromOrigin = rotatePoints(cornerPoints, [0, 0], -angle);
2244
+ rectanglesFromOrigin.push(RectangleClient.getRectangleByPoints(invertCornerPointsFromOrigin));
2245
+ }
2246
+ const selectionRectangleFromOrigin = RectangleClient.getBoundingRectangle(rectanglesFromOrigin);
2247
+ const selectionCornerPoints = RectangleClient.getCornerPoints(selectionRectangleFromOrigin);
2248
+ const cornerPointsFromOrigin = rotatePoints(selectionCornerPoints, [0, 0], angle);
2249
+ const centerPoint = RectangleClient.getCenterPoint(RectangleClient.getRectangleByPoints(cornerPointsFromOrigin));
2250
+ return RectangleClient.getRectangleByPoints(rotatePoints(cornerPointsFromOrigin, centerPoint, -angle));
2251
+ };
2252
+ const getOffsetAfterRotate = (rectangle, rotateCenterPoint, angle) => {
2253
+ const targetCenterPoint = RectangleClient.getCenterPoint(rectangle);
2254
+ const [rotatedCenterPoint] = rotatePoints([targetCenterPoint], rotateCenterPoint, angle);
2255
+ const offsetX = rotatedCenterPoint[0] - targetCenterPoint[0];
2256
+ const offsetY = rotatedCenterPoint[1] - targetCenterPoint[1];
2257
+ return { offsetX, offsetY };
2258
+ };
2259
+ const rotatedDataPoints = (points, rotateCenterPoint, angle) => {
2260
+ const { offsetX, offsetY } = getOffsetAfterRotate(RectangleClient.getRectangleByPoints(points), rotateCenterPoint, angle);
2261
+ return points.map(p => [p[0] + offsetX, p[1] + offsetY]);
2262
+ };
2263
+ const hasValidAngle = (node) => {
2264
+ return node.angle && node.angle !== 0;
2265
+ };
2266
+ const rotatePointsByElement = (points, element) => {
2267
+ if (hasValidAngle(element)) {
2268
+ let rectangle = RectangleClient.getRectangleByPoints(element.points);
2269
+ const centerPoint = RectangleClient.getCenterPoint(rectangle);
2270
+ return rotatePoints(points, centerPoint, element.angle);
2271
+ }
2272
+ else {
2273
+ return null;
2274
+ }
2275
+ };
2276
+ const rotateAntiPointsByElement = (points, element) => {
2277
+ if (hasValidAngle(element)) {
2278
+ let rectangle = RectangleClient.getRectangleByPoints(element.points);
2279
+ const centerPoint = RectangleClient.getCenterPoint(rectangle);
2280
+ return rotatePoints(points, centerPoint, -element.angle);
2281
+ }
2282
+ else {
2283
+ return null;
2192
2284
  }
2193
2285
  };
2194
2286
 
2195
- const Path = {
2196
- /**
2197
- * Get a list of ancestor paths for a given path.
2198
- *
2199
- * The paths are sorted from shallowest to deepest ancestor. However, if the
2200
- * `reverse: true` option is passed, they are reversed.
2201
- */
2202
- ancestors(path, options = {}) {
2203
- const { reverse = false } = options;
2204
- let paths = Path.levels(path, options);
2205
- if (reverse) {
2206
- paths = paths.slice(1);
2207
- }
2208
- else {
2209
- paths = paths.slice(0, -1);
2210
- }
2211
- return paths;
2212
- },
2213
- /**
2214
- * Get a list of paths at every level down to a path. Note: this is the same
2215
- * as `Path.ancestors`, but including the path itself.
2216
- *
2217
- * The paths are sorted from shallowest to deepest. However, if the `reverse:
2218
- * true` option is passed, they are reversed.
2219
- */
2220
- levels(path, options = {}) {
2221
- const { reverse = false } = options;
2222
- const list = [];
2223
- for (let i = 0; i <= path.length; i++) {
2224
- list.push(path.slice(0, i));
2287
+ function isSelectionMoving(board) {
2288
+ return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
2289
+ }
2290
+ function setSelectionMoving(board) {
2291
+ PlaitBoard.getBoardContainer(board).classList.add('selection-moving');
2292
+ BOARD_TO_IS_SELECTION_MOVING.set(board, true);
2293
+ setDragging(board, true);
2294
+ }
2295
+ function clearSelectionMoving(board) {
2296
+ PlaitBoard.getBoardContainer(board).classList.remove('selection-moving');
2297
+ BOARD_TO_IS_SELECTION_MOVING.delete(board);
2298
+ setDragging(board, false);
2299
+ }
2300
+ function isHandleSelection(board) {
2301
+ const options = board.getPluginOptions(PlaitPluginKey.withSelection);
2302
+ return board.pointer !== PlaitPointerType.hand && !options.isDisabledSelect && !PlaitBoard.isReadonly(board);
2303
+ }
2304
+ function isSetSelectionOperation(board) {
2305
+ return !!board.operations.find(value => value.type === 'set_selection');
2306
+ }
2307
+ function getTemporaryElements(board) {
2308
+ const ref = BOARD_TO_TEMPORARY_ELEMENTS.get(board);
2309
+ if (ref) {
2310
+ return ref.elements;
2311
+ }
2312
+ else {
2313
+ return undefined;
2314
+ }
2315
+ }
2316
+ function getTemporaryRef(board) {
2317
+ return BOARD_TO_TEMPORARY_ELEMENTS.get(board);
2318
+ }
2319
+ function deleteTemporaryElements(board) {
2320
+ BOARD_TO_TEMPORARY_ELEMENTS.delete(board);
2321
+ }
2322
+ function createSelectionRectangleG(board) {
2323
+ const elements = getSelectedElements(board);
2324
+ const rectangle = getRectangleByElements(board, elements, false);
2325
+ if (rectangle.width > 0 && rectangle.height > 0 && elements.length > 1) {
2326
+ const selectionRectangleG = drawRectangle(board, RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH), {
2327
+ stroke: SELECTION_BORDER_COLOR,
2328
+ strokeWidth: ACTIVE_STROKE_WIDTH,
2329
+ fillStyle: 'solid'
2330
+ });
2331
+ selectionRectangleG.classList.add(SELECTION_RECTANGLE_CLASS_NAME);
2332
+ PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
2333
+ const angle = getSelectionAngle(elements);
2334
+ if (angle) {
2335
+ setAngleForG(selectionRectangleG, RectangleClient.getCenterPoint(rectangle), angle);
2225
2336
  }
2226
- if (reverse) {
2227
- list.reverse();
2337
+ return selectionRectangleG;
2338
+ }
2339
+ return null;
2340
+ }
2341
+ function setSelectedElementsWithGroup(board, elements, isShift) {
2342
+ if (!board.selection) {
2343
+ return;
2344
+ }
2345
+ const selectedElements = getSelectedElements(board);
2346
+ if (!Selection.isCollapsed(board.selection)) {
2347
+ let newElements = [...selectedElements];
2348
+ elements.forEach(item => {
2349
+ if (!item.groupId) {
2350
+ newElements.push(item);
2351
+ }
2352
+ else {
2353
+ newElements.push(...getElementsInGroupByElement(board, item));
2354
+ }
2355
+ });
2356
+ cacheSelectedElements(board, uniqueById(newElements));
2357
+ return;
2358
+ }
2359
+ if (Selection.isCollapsed(board.selection)) {
2360
+ const hitElement = elements[0];
2361
+ const hitElementGroups = getGroupByElement(board, hitElement, true);
2362
+ if (hitElementGroups.length) {
2363
+ const elementsInHighestGroup = getElementsInGroup(board, hitElementGroups[hitElementGroups.length - 1], true) || [];
2364
+ const isSelectGroupElement = selectedElements.some(element => elementsInHighestGroup.map(item => item.id).includes(element.id));
2365
+ if (isShift) {
2366
+ cacheSelectedElementsWithGroupOnShift(board, elements, isSelectGroupElement, elementsInHighestGroup);
2367
+ }
2368
+ else {
2369
+ cacheSelectedElementsWithGroup(board, elements, isSelectGroupElement, hitElementGroups);
2370
+ }
2228
2371
  }
2229
- return list;
2230
- },
2231
- parent(path) {
2232
- if (path.length === 0) {
2233
- throw new Error(`Cannot get the parent path of the root path [${path}].`);
2372
+ }
2373
+ }
2374
+ function cacheSelectedElementsWithGroupOnShift(board, elements, isSelectGroupElement, elementsInHighestGroup) {
2375
+ const selectedElements = getSelectedElements(board);
2376
+ let newElements = [...selectedElements];
2377
+ const hitElement = elements[0];
2378
+ let pendingElements = [];
2379
+ if (!isSelectGroupElement) {
2380
+ pendingElements = elementsInHighestGroup;
2381
+ }
2382
+ else {
2383
+ const isHitSelectedElement = selectedElements.some(item => item.id === hitElement.id);
2384
+ const selectedElementsInGroup = elementsInHighestGroup.filter(item => selectedElements.includes(item));
2385
+ if (isHitSelectedElement) {
2386
+ pendingElements = selectedElementsInGroup.filter(item => item.id !== hitElement.id);
2234
2387
  }
2235
- return path.slice(0, -1);
2236
- },
2237
- next(path) {
2238
- if (path.length === 0) {
2239
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
2388
+ else {
2389
+ pendingElements.push(...selectedElementsInGroup, ...elements);
2240
2390
  }
2241
- const last = path[path.length - 1];
2242
- return path.slice(0, -1).concat(last + 1);
2243
- },
2244
- hasPrevious(path) {
2245
- return path[path.length - 1] > 0;
2246
- },
2247
- previous(path) {
2248
- if (path.length === 0) {
2249
- throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
2391
+ }
2392
+ elementsInHighestGroup.forEach(element => {
2393
+ if (newElements.includes(element)) {
2394
+ newElements.splice(newElements.indexOf(element), 1);
2250
2395
  }
2251
- const last = path[path.length - 1];
2252
- return path.slice(0, -1).concat(last - 1);
2253
- },
2254
- /**
2255
- * Check if a path is an ancestor of another.
2256
- */
2257
- isAncestor(path, another) {
2258
- return path.length < another.length && Path.compare(path, another) === 0;
2259
- },
2260
- /**
2261
- * Compare a path to another, returning an integer indicating whether the path
2262
- * was before, at, or after the other.
2263
- *
2264
- * Note: Two paths of unequal length can still receive a `0` result if one is
2265
- * directly above or below the other. If you want exact matching, use
2266
- * [[Path.equals]] instead.
2267
- */
2268
- compare(path, another) {
2269
- const min = Math.min(path.length, another.length);
2270
- for (let i = 0; i < min; i++) {
2271
- if (path[i] < another[i])
2272
- return -1;
2273
- if (path[i] > another[i])
2274
- return 1;
2396
+ });
2397
+ if (pendingElements.length) {
2398
+ newElements.push(...pendingElements);
2399
+ }
2400
+ cacheSelectedElements(board, uniqueById(newElements));
2401
+ }
2402
+ function cacheSelectedElementsWithGroup(board, elements, isSelectGroupElement, hitElementGroups) {
2403
+ let newElements = [...elements];
2404
+ const selectedGroups = filterSelectedGroups(board, hitElementGroups);
2405
+ if (selectedGroups.length > 0) {
2406
+ if (selectedGroups.length > 1) {
2407
+ newElements = getAllElementsInGroup(board, selectedGroups[selectedGroups.length - 2], true);
2275
2408
  }
2276
- return 0;
2277
- },
2278
- /**
2279
- * Check if a path is exactly equal to another.
2280
- */
2281
- equals(path, another) {
2282
- return path.length === another.length && path.every((n, i) => n === another[i]);
2283
- },
2284
- /**
2285
- * Check if a path ends before one of the indexes in another.
2286
- */
2287
- endsBefore(path, another) {
2288
- const i = path.length - 1;
2289
- const as = path.slice(0, i);
2290
- const bs = another.slice(0, i);
2291
- const av = path[i];
2292
- const bv = another[i];
2293
- return Path.equals(as, bs) && av < bv;
2294
- },
2295
- /**
2296
- * Check if a path is a sibling of another.
2297
- */
2298
- isSibling(path, another) {
2299
- if (path.length !== another.length) {
2300
- return false;
2409
+ }
2410
+ else {
2411
+ const elementsInGroup = getAllElementsInGroup(board, hitElementGroups[hitElementGroups.length - 1], true);
2412
+ if (!isSelectGroupElement) {
2413
+ newElements = elementsInGroup;
2301
2414
  }
2302
- const as = path.slice(0, -1);
2303
- const bs = another.slice(0, -1);
2304
- const al = path[path.length - 1];
2305
- const bl = another[another.length - 1];
2306
- return al !== bl && Path.equals(as, bs);
2307
- },
2308
- transform(path, operation) {
2309
- return produce(path, p => {
2310
- // PERF: Exit early if the operation is guaranteed not to have an effect.
2311
- if (!path || path?.length === 0) {
2312
- return;
2415
+ }
2416
+ cacheSelectedElements(board, uniqueById(newElements));
2417
+ }
2418
+
2419
+ const getElementsInGroup = (board, group, recursion, includeGroup) => {
2420
+ let result = [];
2421
+ const elements = board.children.filter(value => value.groupId === group.id);
2422
+ if (recursion) {
2423
+ elements.forEach(item => {
2424
+ if (PlaitGroupElement.isGroup(item)) {
2425
+ if (includeGroup) {
2426
+ result.push(item);
2427
+ }
2428
+ result.push(...getElementsInGroup(board, item, recursion, includeGroup));
2313
2429
  }
2314
- if (p === null) {
2315
- return null;
2430
+ else {
2431
+ result.push(item);
2316
2432
  }
2317
- switch (operation.type) {
2318
- case 'insert_node': {
2319
- const { path: op } = operation;
2320
- if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
2321
- p[op.length - 1] += 1;
2322
- }
2323
- break;
2324
- }
2325
- case 'remove_node': {
2326
- const { path: op } = operation;
2327
- if (Path.equals(op, p) || Path.isAncestor(op, p)) {
2328
- return null;
2329
- }
2330
- else if (Path.endsBefore(op, p)) {
2331
- p[op.length - 1] -= 1;
2332
- }
2333
- break;
2334
- }
2335
- case 'move_node': {
2336
- const { path: op, newPath: onp } = operation;
2337
- // If the old and new path are the same, it's a no-op.
2338
- if (Path.equals(op, onp)) {
2339
- return;
2340
- }
2341
- if (Path.isAncestor(op, p) || Path.equals(op, p)) {
2342
- const copy = onp.slice();
2343
- // op.length <= onp.length is different for slate
2344
- // resolve drag from [0, 0] to [0, 3] issue
2345
- if (Path.endsBefore(op, onp) && op.length <= onp.length) {
2346
- copy[op.length - 1] -= 1;
2347
- }
2348
- return copy.concat(p.slice(op.length));
2349
- }
2350
- else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
2351
- if (Path.endsBefore(op, p)) {
2352
- p[op.length - 1] -= 1;
2353
- }
2354
- else {
2355
- p[op.length - 1] += 1;
2356
- }
2357
- }
2358
- else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
2359
- if (Path.endsBefore(op, p)) {
2360
- p[op.length - 1] -= 1;
2361
- }
2362
- p[onp.length - 1] += 1;
2363
- }
2364
- else if (Path.endsBefore(op, p)) {
2365
- if (Path.equals(onp, p)) {
2366
- p[onp.length - 1] += 1;
2367
- }
2368
- p[op.length - 1] -= 1;
2369
- }
2370
- break;
2371
- }
2372
- }
2373
- return p;
2374
2433
  });
2375
2434
  }
2435
+ else {
2436
+ result = includeGroup ? elements : elements.filter(item => !PlaitGroupElement.isGroup(item));
2437
+ }
2438
+ return result;
2376
2439
  };
2377
-
2378
- const PlaitNode = {
2379
- parent: (board, path) => {
2380
- const parentPath = Path.parent(path);
2381
- const p = PlaitNode.get(board, parentPath);
2382
- return p;
2383
- },
2384
- /**
2385
- * Return a generator of all the ancestor nodes above a specific path.
2386
- *
2387
- * By default the order is top-down, from highest to lowest ancestor in
2388
- * the tree, but you can pass the `reverse: true` option to go bottom-up.
2389
- */
2390
- *parents(root, path, options = {}) {
2391
- for (const p of Path.ancestors(path, options)) {
2392
- const n = PlaitNode.get(root, p);
2393
- yield n;
2394
- }
2395
- },
2396
- get(root, path) {
2397
- let node = root;
2398
- for (let i = 0; i < path.length; i++) {
2399
- const p = path[i];
2400
- if (!node || !node.children || !node.children[p]) {
2401
- throw new Error(`Cannot find a descendant at path [${path}]`);
2402
- }
2403
- node = node.children[p];
2440
+ const getAllElementsInGroup = (board, group, recursion, includeGroup) => {
2441
+ const elementsInGroup = getElementsInGroup(board, group, recursion, includeGroup);
2442
+ const result = [];
2443
+ elementsInGroup.forEach(element => {
2444
+ depthFirstRecursion(element, node => {
2445
+ result.push(node);
2446
+ }, () => true);
2447
+ });
2448
+ return result;
2449
+ };
2450
+ const getRectangleByGroup = (board, group, recursion) => {
2451
+ const elementsInGroup = getAllElementsInGroup(board, group, recursion);
2452
+ return getRectangleByElements(board, elementsInGroup, false);
2453
+ };
2454
+ const getGroupByElement = (board, element, recursion, source) => {
2455
+ const group = (source || board.children).find(item => item.id === element?.groupId);
2456
+ if (!group) {
2457
+ return recursion ? [] : null;
2458
+ }
2459
+ if (recursion) {
2460
+ const groups = [group];
2461
+ const grandGroups = getGroupByElement(board, group, recursion, source);
2462
+ if (grandGroups.length) {
2463
+ groups.push(...grandGroups);
2404
2464
  }
2405
- return node;
2406
- },
2407
- last(board, path) {
2408
- let n = PlaitNode.get(board, path);
2409
- while (n && n.children && n.children.length > 0) {
2410
- const i = n.children.length - 1;
2411
- n = n.children[i];
2465
+ return groups;
2466
+ }
2467
+ else {
2468
+ return group;
2469
+ }
2470
+ };
2471
+ const getHighestGroup = (board, element) => {
2472
+ const hitElementGroups = getGroupByElement(board, element, true);
2473
+ if (hitElementGroups.length) {
2474
+ return hitElementGroups[hitElementGroups.length - 1];
2475
+ }
2476
+ return null;
2477
+ };
2478
+ const getElementsInGroupByElement = (board, element) => {
2479
+ const highestGroup = getHighestGroup(board, element);
2480
+ if (highestGroup) {
2481
+ return getAllElementsInGroup(board, highestGroup, true);
2482
+ }
2483
+ else {
2484
+ return [element];
2485
+ }
2486
+ };
2487
+ const isSelectedElementOrGroup = (board, element, elements) => {
2488
+ const selectedElements = elements || getSelectedElements(board);
2489
+ if (PlaitGroupElement.isGroup(element)) {
2490
+ return isSelectedAllElementsInGroup(board, element, elements);
2491
+ }
2492
+ return selectedElements.map(item => item.id).includes(element.id);
2493
+ };
2494
+ const isSelectedAllElementsInGroup = (board, group, elements) => {
2495
+ const selectedElements = elements || getSelectedElements(board);
2496
+ const elementsInGroup = getElementsInGroup(board, group, true);
2497
+ return elementsInGroup.every(item => selectedElements.map(element => element.id).includes(item.id));
2498
+ };
2499
+ const filterSelectedGroups = (board, groups, elements) => {
2500
+ const selectedGroups = [];
2501
+ groups.forEach(item => {
2502
+ if (isSelectedElementOrGroup(board, item, elements)) {
2503
+ selectedGroups.push(item);
2412
2504
  }
2413
- return n;
2505
+ });
2506
+ return selectedGroups;
2507
+ };
2508
+ const getSelectedGroups = (board, elements) => {
2509
+ const highestSelectedGroups = getHighestSelectedGroups(board, elements);
2510
+ const groups = [];
2511
+ highestSelectedGroups.forEach(item => {
2512
+ groups.push(item);
2513
+ const elementsInGroup = getElementsInGroup(board, item, true, true);
2514
+ groups.push(...elementsInGroup.filter(item => PlaitGroupElement.isGroup(item)));
2515
+ });
2516
+ return groups;
2517
+ };
2518
+ const getHighestSelectedGroup = (board, element, elements) => {
2519
+ const hitElementGroups = getGroupByElement(board, element, true, elements);
2520
+ const selectedGroups = filterSelectedGroups(board, hitElementGroups, elements);
2521
+ if (selectedGroups.length) {
2522
+ return selectedGroups[selectedGroups.length - 1];
2414
2523
  }
2524
+ return null;
2415
2525
  };
2416
-
2417
- const applyToDraft = (board, selection, viewport, theme, op) => {
2418
- switch (op.type) {
2419
- case 'insert_node': {
2420
- const { path, node } = op;
2421
- const parent = PlaitNode.parent(board, path);
2422
- const index = path[path.length - 1];
2423
- if (!parent.children || index > parent.children.length) {
2424
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
2526
+ const getHighestSelectedGroups = (board, elements) => {
2527
+ let result = [];
2528
+ const selectedElements = elements || getSelectedElements(board);
2529
+ selectedElements.forEach(item => {
2530
+ if (item.groupId) {
2531
+ const group = getHighestSelectedGroup(board, item, elements);
2532
+ if (group && !result.includes(group)) {
2533
+ result.push(group);
2425
2534
  }
2426
- parent.children.splice(index, 0, node);
2427
- break;
2428
2535
  }
2429
- case 'remove_node': {
2430
- const { path } = op;
2431
- const parent = PlaitNode.parent(board, path);
2432
- const index = path[path.length - 1];
2433
- if (!parent.children || index > parent.children.length) {
2434
- throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
2435
- }
2436
- parent.children.splice(index, 1);
2437
- break;
2536
+ });
2537
+ return result;
2538
+ };
2539
+ const getSelectedIsolatedElements = (board, elements) => {
2540
+ let result = [];
2541
+ const selectedElements = elements || getSelectedElements(board);
2542
+ selectedElements
2543
+ .filter(item => !PlaitGroupElement.isGroup(item))
2544
+ .forEach(item => {
2545
+ if (!item.groupId) {
2546
+ result.push(item);
2438
2547
  }
2439
- case 'move_node': {
2440
- const { path, newPath } = op;
2441
- if (Path.isAncestor(path, newPath)) {
2442
- throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
2548
+ else {
2549
+ const group = getHighestSelectedGroup(board, item, elements);
2550
+ if (!group) {
2551
+ result.push(item);
2443
2552
  }
2444
- const node = PlaitNode.get(board, path);
2445
- const parent = PlaitNode.parent(board, path);
2446
- const index = path[path.length - 1];
2447
- // This is tricky, but since the `path` and `newPath` both refer to
2448
- // the same snapshot in time, there's a mismatch. After either
2449
- // removing the original position, the second step's path can be out
2450
- // of date. So instead of using the `op.newPath` directly, we
2451
- // transform `op.path` to ascertain what the `newPath` would be after
2452
- // the operation was applied.
2453
- parent.children?.splice(index, 1);
2454
- const truePath = Path.transform(path, op);
2455
- const newParent = PlaitNode.get(board, Path.parent(truePath));
2456
- const newIndex = truePath[truePath.length - 1];
2457
- newParent.children?.splice(newIndex, 0, node);
2458
- break;
2459
2553
  }
2460
- case 'set_node': {
2461
- const { path, properties, newProperties } = op;
2462
- if (path.length === 0) {
2463
- throw new Error(`Cannot set properties on the root node!`);
2464
- }
2465
- const node = PlaitNode.get(board, path);
2466
- for (const key in newProperties) {
2467
- const value = newProperties[key];
2468
- if (value == null) {
2469
- delete node[key];
2470
- }
2471
- else {
2472
- node[key] = value;
2473
- }
2474
- }
2475
- // properties that were previously defined, but are now missing, must be deleted
2476
- for (const key in properties) {
2477
- if (!newProperties.hasOwnProperty(key)) {
2478
- delete node[key];
2479
- }
2480
- }
2481
- break;
2554
+ });
2555
+ return result;
2556
+ };
2557
+ const getSelectedIsolatedElementsCanAddToGroup = (board, elements) => {
2558
+ const selectedIsolatedElements = getSelectedIsolatedElements(board, elements);
2559
+ return selectedIsolatedElements.filter(item => board.canAddToGroup(item));
2560
+ };
2561
+ const getHighestSelectedElements = (board, elements) => {
2562
+ return [...getHighestSelectedGroups(board, elements), ...getSelectedIsolatedElements(board, elements)];
2563
+ };
2564
+ const createGroupRectangleG = (board, elements) => {
2565
+ const selectedElements = getSelectedElements(board);
2566
+ const groupRectangleG = createG();
2567
+ const isMoving = isSelectionMoving(board);
2568
+ elements.forEach(item => {
2569
+ const isRender = (!selectedElements.includes(item) && !isMoving) || isMoving;
2570
+ if (item.groupId && isRender) {
2571
+ const elements = getElementsInGroupByElement(board, item);
2572
+ const rectangle = getRectangleByElements(board, elements, false);
2573
+ groupRectangleG.append(drawRectangle(board, rectangle, {
2574
+ stroke: SELECTION_BORDER_COLOR,
2575
+ strokeWidth: ACTIVE_STROKE_WIDTH,
2576
+ strokeLineDash: [5]
2577
+ }));
2482
2578
  }
2483
- case 'set_viewport': {
2484
- const { newProperties } = op;
2485
- if (newProperties == null) {
2486
- viewport = newProperties;
2487
- }
2488
- else {
2489
- if (viewport == null) {
2490
- if (!Viewport.isViewport(newProperties)) {
2491
- throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
2492
- }
2493
- viewport = { ...newProperties };
2494
- }
2495
- for (const key in newProperties) {
2496
- const value = newProperties[key];
2497
- if (value == null) {
2498
- delete viewport[key];
2499
- }
2500
- else {
2501
- viewport[key] = value;
2502
- }
2503
- }
2504
- }
2505
- break;
2579
+ });
2580
+ return groupRectangleG;
2581
+ };
2582
+ const createGroup = (groupId) => {
2583
+ return groupId
2584
+ ? {
2585
+ id: idCreator(),
2586
+ type: 'group',
2587
+ groupId
2506
2588
  }
2507
- case 'set_selection': {
2508
- const { newProperties } = op;
2509
- if (newProperties == null) {
2510
- selection = newProperties;
2511
- }
2512
- else {
2513
- if (selection === null) {
2514
- selection = op.newProperties;
2515
- }
2516
- else {
2517
- selection = newProperties;
2518
- }
2519
- }
2520
- break;
2589
+ : {
2590
+ id: idCreator(),
2591
+ type: 'group'
2592
+ };
2593
+ };
2594
+ const nonGroupInHighestSelectedElements = (elements) => {
2595
+ return elements.every(item => !item.groupId);
2596
+ };
2597
+ const hasSelectedElementsInSameGroup = (elements) => {
2598
+ return elements.every(item => item.groupId && item.groupId === elements[0].groupId);
2599
+ };
2600
+ const canAddGroup = (board, elements) => {
2601
+ const highestSelectedElements = getHighestSelectedElements(board, elements);
2602
+ const rootElements = highestSelectedElements.filter(item => board.canAddToGroup(item));
2603
+ if (rootElements.length > 1) {
2604
+ return nonGroupInHighestSelectedElements(rootElements) || hasSelectedElementsInSameGroup(rootElements);
2605
+ }
2606
+ return false;
2607
+ };
2608
+ const canRemoveGroup = (board, elements) => {
2609
+ const selectedGroups = getHighestSelectedGroups(board, elements);
2610
+ const selectedElements = elements || getSelectedElements(board);
2611
+ return selectedElements.length > 0 && selectedGroups.length > 0;
2612
+ };
2613
+
2614
+ const PlaitElement = {
2615
+ isRootElement(value) {
2616
+ const parent = NODE_TO_PARENT.get(value);
2617
+ if (parent && PlaitBoard.isBoard(parent)) {
2618
+ return true;
2521
2619
  }
2522
- case 'set_theme': {
2523
- const { newProperties } = op;
2524
- theme = newProperties;
2525
- break;
2620
+ else {
2621
+ return false;
2526
2622
  }
2623
+ },
2624
+ getComponent(value) {
2625
+ return ELEMENT_TO_COMPONENT.get(value);
2527
2626
  }
2528
- return { selection, viewport, theme };
2529
2627
  };
2530
- const GeneralTransforms = {
2628
+
2629
+ const Path = {
2531
2630
  /**
2532
- * Transform the board by an operation.
2631
+ * Get a list of ancestor paths for a given path.
2632
+ *
2633
+ * The paths are sorted from shallowest to deepest ancestor. However, if the
2634
+ * `reverse: true` option is passed, they are reversed.
2533
2635
  */
2534
- transform(board, op) {
2535
- board.children = createDraft(board.children);
2536
- let viewport = board.viewport && createDraft(board.viewport);
2537
- let selection = board.selection && createDraft(board.selection);
2538
- let theme = board.theme && createDraft(board.theme);
2539
- try {
2540
- const state = applyToDraft(board, selection, viewport, theme, op);
2541
- viewport = state.viewport;
2542
- selection = state.selection;
2543
- theme = state.theme;
2636
+ ancestors(path, options = {}) {
2637
+ const { reverse = false } = options;
2638
+ let paths = Path.levels(path, options);
2639
+ if (reverse) {
2640
+ paths = paths.slice(1);
2544
2641
  }
2545
- finally {
2546
- board.children = finishDraft(board.children);
2547
- if (selection) {
2548
- board.selection = isDraft(selection) ? finishDraft(selection) : selection;
2642
+ else {
2643
+ paths = paths.slice(0, -1);
2644
+ }
2645
+ return paths;
2646
+ },
2647
+ /**
2648
+ * Get a list of paths at every level down to a path. Note: this is the same
2649
+ * as `Path.ancestors`, but including the path itself.
2650
+ *
2651
+ * The paths are sorted from shallowest to deepest. However, if the `reverse:
2652
+ * true` option is passed, they are reversed.
2653
+ */
2654
+ levels(path, options = {}) {
2655
+ const { reverse = false } = options;
2656
+ const list = [];
2657
+ for (let i = 0; i <= path.length; i++) {
2658
+ list.push(path.slice(0, i));
2659
+ }
2660
+ if (reverse) {
2661
+ list.reverse();
2662
+ }
2663
+ return list;
2664
+ },
2665
+ parent(path) {
2666
+ if (path.length === 0) {
2667
+ throw new Error(`Cannot get the parent path of the root path [${path}].`);
2668
+ }
2669
+ return path.slice(0, -1);
2670
+ },
2671
+ next(path) {
2672
+ if (path.length === 0) {
2673
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no next index.`);
2674
+ }
2675
+ const last = path[path.length - 1];
2676
+ return path.slice(0, -1).concat(last + 1);
2677
+ },
2678
+ hasPrevious(path) {
2679
+ return path[path.length - 1] > 0;
2680
+ },
2681
+ previous(path) {
2682
+ if (path.length === 0) {
2683
+ throw new Error(`Cannot get the next path of a root path [${path}], because it has no previous index.`);
2684
+ }
2685
+ const last = path[path.length - 1];
2686
+ return path.slice(0, -1).concat(last - 1);
2687
+ },
2688
+ /**
2689
+ * Check if a path is an ancestor of another.
2690
+ */
2691
+ isAncestor(path, another) {
2692
+ return path.length < another.length && Path.compare(path, another) === 0;
2693
+ },
2694
+ /**
2695
+ * Compare a path to another, returning an integer indicating whether the path
2696
+ * was before, at, or after the other.
2697
+ *
2698
+ * Note: Two paths of unequal length can still receive a `0` result if one is
2699
+ * directly above or below the other. If you want exact matching, use
2700
+ * [[Path.equals]] instead.
2701
+ */
2702
+ compare(path, another) {
2703
+ const min = Math.min(path.length, another.length);
2704
+ for (let i = 0; i < min; i++) {
2705
+ if (path[i] < another[i])
2706
+ return -1;
2707
+ if (path[i] > another[i])
2708
+ return 1;
2709
+ }
2710
+ return 0;
2711
+ },
2712
+ /**
2713
+ * Check if a path is exactly equal to another.
2714
+ */
2715
+ equals(path, another) {
2716
+ return path.length === another.length && path.every((n, i) => n === another[i]);
2717
+ },
2718
+ /**
2719
+ * Check if a path ends before one of the indexes in another.
2720
+ */
2721
+ endsBefore(path, another) {
2722
+ const i = path.length - 1;
2723
+ const as = path.slice(0, i);
2724
+ const bs = another.slice(0, i);
2725
+ const av = path[i];
2726
+ const bv = another[i];
2727
+ return Path.equals(as, bs) && av < bv;
2728
+ },
2729
+ /**
2730
+ * Check if a path is a sibling of another.
2731
+ */
2732
+ isSibling(path, another) {
2733
+ if (path.length !== another.length) {
2734
+ return false;
2735
+ }
2736
+ const as = path.slice(0, -1);
2737
+ const bs = another.slice(0, -1);
2738
+ const al = path[path.length - 1];
2739
+ const bl = another[another.length - 1];
2740
+ return al !== bl && Path.equals(as, bs);
2741
+ },
2742
+ transform(path, operation) {
2743
+ return produce(path, p => {
2744
+ // PERF: Exit early if the operation is guaranteed not to have an effect.
2745
+ if (!path || path?.length === 0) {
2746
+ return;
2549
2747
  }
2550
- else {
2551
- board.selection = null;
2748
+ if (p === null) {
2749
+ return null;
2552
2750
  }
2553
- board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
2554
- board.theme = isDraft(theme) ? finishDraft(theme) : theme;
2555
- }
2556
- }
2557
- };
2558
-
2559
- function insertNode(board, node, path) {
2560
- const operation = { type: 'insert_node', node, path };
2561
- board.apply(operation);
2562
- }
2563
- function setNode(board, props, path) {
2564
- const properties = {};
2565
- const newProperties = {};
2566
- const node = PlaitNode.get(board, path);
2567
- for (const k in props) {
2568
- if (node[k] !== props[k]) {
2569
- if (node.hasOwnProperty(k)) {
2570
- properties[k] = node[k];
2751
+ switch (operation.type) {
2752
+ case 'insert_node': {
2753
+ const { path: op } = operation;
2754
+ if (Path.equals(op, p) || Path.endsBefore(op, p) || Path.isAncestor(op, p)) {
2755
+ p[op.length - 1] += 1;
2756
+ }
2757
+ break;
2758
+ }
2759
+ case 'remove_node': {
2760
+ const { path: op } = operation;
2761
+ if (Path.equals(op, p) || Path.isAncestor(op, p)) {
2762
+ return null;
2763
+ }
2764
+ else if (Path.endsBefore(op, p)) {
2765
+ p[op.length - 1] -= 1;
2766
+ }
2767
+ break;
2768
+ }
2769
+ case 'move_node': {
2770
+ const { path: op, newPath: onp } = operation;
2771
+ // If the old and new path are the same, it's a no-op.
2772
+ if (Path.equals(op, onp)) {
2773
+ return;
2774
+ }
2775
+ if (Path.isAncestor(op, p) || Path.equals(op, p)) {
2776
+ const copy = onp.slice();
2777
+ // op.length <= onp.length is different for slate
2778
+ // resolve drag from [0, 0] to [0, 3] issue
2779
+ if (Path.endsBefore(op, onp) && op.length <= onp.length) {
2780
+ copy[op.length - 1] -= 1;
2781
+ }
2782
+ return copy.concat(p.slice(op.length));
2783
+ }
2784
+ else if (Path.isSibling(op, onp) && (Path.isAncestor(onp, p) || Path.equals(onp, p))) {
2785
+ if (Path.endsBefore(op, p)) {
2786
+ p[op.length - 1] -= 1;
2787
+ }
2788
+ else {
2789
+ p[op.length - 1] += 1;
2790
+ }
2791
+ }
2792
+ else if (Path.endsBefore(onp, p) || Path.equals(onp, p) || Path.isAncestor(onp, p)) {
2793
+ if (Path.endsBefore(op, p)) {
2794
+ p[op.length - 1] -= 1;
2795
+ }
2796
+ p[onp.length - 1] += 1;
2797
+ }
2798
+ else if (Path.endsBefore(op, p)) {
2799
+ if (Path.equals(onp, p)) {
2800
+ p[onp.length - 1] += 1;
2801
+ }
2802
+ p[op.length - 1] -= 1;
2803
+ }
2804
+ break;
2805
+ }
2571
2806
  }
2572
- if (props[k] != null)
2573
- newProperties[k] = props[k];
2574
- }
2575
- }
2576
- const operation = { type: 'set_node', properties, newProperties, path };
2577
- board.apply(operation);
2578
- }
2579
- function removeNode(board, path) {
2580
- const node = PlaitNode.get(board, path);
2581
- const operation = { type: 'remove_node', path, node };
2582
- board.apply(operation);
2583
- }
2584
- function moveNode(board, path, newPath) {
2585
- const operation = { type: 'move_node', path, newPath };
2586
- board.apply(operation);
2587
- }
2588
- const NodeTransforms = {
2589
- insertNode,
2590
- setNode,
2591
- removeNode,
2592
- moveNode
2593
- };
2594
-
2595
- function setSelection(board, selection) {
2596
- const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
2597
- board.apply(operation);
2598
- }
2599
- const SelectionTransforms = {
2600
- setSelection,
2601
- addSelectionWithTemporaryElements
2602
- };
2603
- function addSelectionWithTemporaryElements(board, elements) {
2604
- const timeoutId = setTimeout(() => {
2605
- setSelection(board, { anchor: [0, 0], focus: [0, 0] });
2606
- }, 0);
2607
- let ref = getTemporaryRef(board);
2608
- if (ref) {
2609
- clearTimeout(ref.timeoutId);
2610
- const currentElements = ref.elements;
2611
- ref.elements.push(...elements.filter(element => !currentElements.includes(element)));
2612
- ref.timeoutId = timeoutId;
2613
- }
2614
- else {
2615
- BOARD_TO_TEMPORARY_ELEMENTS.set(board, { timeoutId, elements });
2807
+ return p;
2808
+ });
2616
2809
  }
2617
- }
2618
-
2619
- const removeElements = (board, elements) => {
2620
- elements
2621
- .map(element => {
2622
- const path = PlaitBoard.findPath(board, element);
2623
- const ref = board.pathRef(path);
2624
- return () => {
2625
- removeNode(board, ref.current);
2626
- ref.unref();
2627
- removeSelectedElement(board, element, true);
2628
- };
2629
- })
2630
- .forEach(action => {
2631
- action();
2632
- });
2633
- };
2634
- const CoreTransforms = {
2635
- removeElements
2636
2810
  };
2637
2811
 
2638
- const Transforms = {
2639
- ...GeneralTransforms,
2640
- ...ViewportTransforms$1,
2641
- ...SelectionTransforms,
2642
- ...NodeTransforms
2643
- };
2644
-
2645
- function isSelectionMoving(board) {
2646
- return !!BOARD_TO_IS_SELECTION_MOVING.get(board);
2647
- }
2648
- function setSelectionMoving(board) {
2649
- PlaitBoard.getBoardContainer(board).classList.add('selection-moving');
2650
- BOARD_TO_IS_SELECTION_MOVING.set(board, true);
2651
- setDragging(board, true);
2652
- }
2653
- function clearSelectionMoving(board) {
2654
- PlaitBoard.getBoardContainer(board).classList.remove('selection-moving');
2655
- BOARD_TO_IS_SELECTION_MOVING.delete(board);
2656
- setDragging(board, false);
2657
- }
2658
- function isHandleSelection(board) {
2659
- const options = board.getPluginOptions(PlaitPluginKey.withSelection);
2660
- return board.pointer !== PlaitPointerType.hand && !options.isDisabledSelect && !PlaitBoard.isReadonly(board);
2661
- }
2662
- function isSetSelectionOperation(board) {
2663
- return !!board.operations.find(value => value.type === 'set_selection');
2664
- }
2665
- function getTemporaryElements(board) {
2666
- const ref = BOARD_TO_TEMPORARY_ELEMENTS.get(board);
2667
- if (ref) {
2668
- return ref.elements;
2669
- }
2670
- else {
2671
- return undefined;
2672
- }
2673
- }
2674
- function getTemporaryRef(board) {
2675
- return BOARD_TO_TEMPORARY_ELEMENTS.get(board);
2676
- }
2677
- function deleteTemporaryElements(board) {
2678
- BOARD_TO_TEMPORARY_ELEMENTS.delete(board);
2679
- }
2680
- function createSelectionRectangleG(board) {
2681
- const elements = getSelectedElements(board);
2682
- const rectangle = getRectangleByElements(board, elements, false);
2683
- if (rectangle.width > 0 && rectangle.height > 0 && elements.length > 1) {
2684
- const selectionRectangleG = drawRectangle(board, RectangleClient.inflate(rectangle, ACTIVE_STROKE_WIDTH), {
2685
- stroke: SELECTION_BORDER_COLOR,
2686
- strokeWidth: ACTIVE_STROKE_WIDTH,
2687
- fillStyle: 'solid'
2688
- });
2689
- selectionRectangleG.classList.add(SELECTION_RECTANGLE_CLASS_NAME);
2690
- PlaitBoard.getElementActiveHost(board).append(selectionRectangleG);
2691
- return selectionRectangleG;
2812
+ const PlaitNode = {
2813
+ parent: (board, path) => {
2814
+ const parentPath = Path.parent(path);
2815
+ const p = PlaitNode.get(board, parentPath);
2816
+ return p;
2817
+ },
2818
+ /**
2819
+ * Return a generator of all the ancestor nodes above a specific path.
2820
+ *
2821
+ * By default the order is top-down, from highest to lowest ancestor in
2822
+ * the tree, but you can pass the `reverse: true` option to go bottom-up.
2823
+ */
2824
+ *parents(root, path, options = {}) {
2825
+ for (const p of Path.ancestors(path, options)) {
2826
+ const n = PlaitNode.get(root, p);
2827
+ yield n;
2828
+ }
2829
+ },
2830
+ get(root, path) {
2831
+ let node = root;
2832
+ for (let i = 0; i < path.length; i++) {
2833
+ const p = path[i];
2834
+ if (!node || !node.children || !node.children[p]) {
2835
+ throw new Error(`Cannot find a descendant at path [${path}]`);
2836
+ }
2837
+ node = node.children[p];
2838
+ }
2839
+ return node;
2840
+ },
2841
+ last(board, path) {
2842
+ let n = PlaitNode.get(board, path);
2843
+ while (n && n.children && n.children.length > 0) {
2844
+ const i = n.children.length - 1;
2845
+ n = n.children[i];
2846
+ }
2847
+ return n;
2692
2848
  }
2693
- return null;
2694
- }
2849
+ };
2695
2850
 
2696
- const getElementsInGroup = (board, group, recursion, includeGroup) => {
2697
- let result = [];
2698
- const elements = board.children.filter(value => value.groupId === group.id);
2699
- if (recursion) {
2700
- elements.forEach(item => {
2701
- if (PlaitGroupElement.isGroup(item)) {
2702
- if (includeGroup) {
2703
- result.push(item);
2704
- }
2705
- result.push(...getElementsInGroup(board, item, recursion));
2851
+ const isSetViewportOperation = (value) => {
2852
+ return value.type === 'set_viewport';
2853
+ };
2854
+ const inverse = (op) => {
2855
+ switch (op.type) {
2856
+ case 'insert_node': {
2857
+ return { ...op, type: 'remove_node' };
2858
+ }
2859
+ case 'remove_node': {
2860
+ return { ...op, type: 'insert_node' };
2861
+ }
2862
+ case 'move_node': {
2863
+ const { newPath, path } = op;
2864
+ // PERF: in this case the move operation is a no-op anyways.
2865
+ if (Path.equals(newPath, path)) {
2866
+ return op;
2867
+ }
2868
+ // when operation path is [0,0] -> [0,2], should exec Path.transform to get [0,1] -> [0,0]
2869
+ // shoud not return [0,2] -> [0,0] #WIK-8981
2870
+ // if (Path.isSibling(path, newPath)) {
2871
+ // return { ...op, path: newPath, newPath: path };
2872
+ // }
2873
+ // If the move does not happen within a single parent it is possible
2874
+ // for the move to impact the true path to the location where the node
2875
+ // was removed from and where it was inserted. We have to adjust for this
2876
+ // and find the original path. We can accomplish this (only in non-sibling)
2877
+ // moves by looking at the impact of the move operation on the node
2878
+ // after the original move path.
2879
+ const inversePath = Path.transform(path, op);
2880
+ const inverseNewPath = Path.transform(Path.next(path), op);
2881
+ return { ...op, path: inversePath, newPath: inverseNewPath };
2882
+ }
2883
+ case 'set_node': {
2884
+ const { properties, newProperties } = op;
2885
+ return { ...op, properties: newProperties, newProperties: properties };
2886
+ }
2887
+ case 'set_selection': {
2888
+ const { properties, newProperties } = op;
2889
+ if (properties == null) {
2890
+ return {
2891
+ ...op,
2892
+ properties: newProperties,
2893
+ newProperties: null
2894
+ };
2895
+ }
2896
+ else if (newProperties == null) {
2897
+ return {
2898
+ ...op,
2899
+ properties: null,
2900
+ newProperties: properties
2901
+ };
2706
2902
  }
2707
2903
  else {
2708
- result.push(item);
2904
+ return { ...op, properties: newProperties, newProperties: properties };
2709
2905
  }
2710
- });
2711
- }
2712
- else {
2713
- result = includeGroup ? elements : elements.filter(item => !PlaitGroupElement.isGroup(item));
2906
+ }
2907
+ case 'set_viewport': {
2908
+ const { properties, newProperties } = op;
2909
+ if (properties == null) {
2910
+ return {
2911
+ ...op,
2912
+ properties: newProperties,
2913
+ newProperties: newProperties
2914
+ };
2915
+ }
2916
+ else if (newProperties == null) {
2917
+ return {
2918
+ ...op,
2919
+ properties: properties,
2920
+ newProperties: properties
2921
+ };
2922
+ }
2923
+ else {
2924
+ return { ...op, properties: newProperties, newProperties: properties };
2925
+ }
2926
+ }
2927
+ case 'set_theme': {
2928
+ const { properties, newProperties } = op;
2929
+ return { ...op, properties: newProperties, newProperties: properties };
2930
+ }
2714
2931
  }
2715
- return result;
2716
2932
  };
2717
- const getRectangleByGroup = (board, group, recursion) => {
2718
- const elementsInGroup = getElementsInGroup(board, group, recursion);
2719
- return getRectangleByElements(board, elementsInGroup, false);
2933
+ const PlaitOperation = {
2934
+ isSetViewportOperation,
2935
+ inverse
2720
2936
  };
2721
- const getGroupByElement = (board, element, recursion) => {
2722
- const group = board.children.find(item => item.id === element?.groupId);
2723
- if (!group) {
2724
- return recursion ? [] : null;
2725
- }
2726
- if (recursion) {
2727
- const groups = [group];
2728
- const grandGroups = getGroupByElement(board, group, recursion);
2729
- if (grandGroups.length) {
2730
- groups.push(...grandGroups);
2731
- }
2732
- return groups;
2733
- }
2734
- else {
2735
- return group;
2937
+
2938
+ const Point = {
2939
+ isEquals(point, otherPoint) {
2940
+ return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
2941
+ },
2942
+ isHorizontal(point, otherPoint, tolerance = 0) {
2943
+ return point && otherPoint && Point.isOverHorizontal([point, otherPoint], tolerance);
2944
+ },
2945
+ isOverHorizontal(points, tolerance = 0) {
2946
+ return points.every(point => Math.abs(point[1] - points[0][1]) <= tolerance);
2947
+ },
2948
+ isVertical(point, otherPoint, tolerance = 0) {
2949
+ return point && otherPoint && Point.isOverVertical([point, otherPoint], tolerance);
2950
+ },
2951
+ isOverVertical(points, tolerance = 0) {
2952
+ return points.every(point => Math.abs(point[0] - points[0][0]) <= tolerance);
2953
+ },
2954
+ isAlign(points, tolerance = 0) {
2955
+ return Point.isOverHorizontal(points, tolerance) || Point.isOverVertical(points, tolerance);
2956
+ },
2957
+ getOffsetX(point1, point2) {
2958
+ return point2[0] - point1[0];
2959
+ },
2960
+ getOffsetY(point1, point2) {
2961
+ return point2[1] - point1[1];
2736
2962
  }
2737
2963
  };
2738
- const getHighestGroup = (board, element) => {
2739
- const groups = getGroupByElement(board, element, true);
2740
- if (groups.length) {
2741
- return groups[groups.length - 1];
2964
+
2965
+ const Viewport = {
2966
+ isViewport: (value) => {
2967
+ return !isNullOrUndefined(value.zoom) && !isNullOrUndefined(value.viewBackgroundColor);
2742
2968
  }
2743
- return null;
2744
2969
  };
2745
- const getElementsInGroupByElement = (board, element) => {
2746
- const highestGroup = getHighestGroup(board, element);
2747
- if (highestGroup) {
2748
- return getElementsInGroup(board, highestGroup, true);
2749
- }
2750
- else {
2751
- return [element];
2752
- }
2970
+
2971
+ const SAVING = new WeakMap();
2972
+ const MERGING = new WeakMap();
2973
+
2974
+ var ThemeColorMode;
2975
+ (function (ThemeColorMode) {
2976
+ ThemeColorMode["default"] = "default";
2977
+ ThemeColorMode["colorful"] = "colorful";
2978
+ ThemeColorMode["soft"] = "soft";
2979
+ ThemeColorMode["retro"] = "retro";
2980
+ ThemeColorMode["dark"] = "dark";
2981
+ ThemeColorMode["starry"] = "starry";
2982
+ })(ThemeColorMode || (ThemeColorMode = {}));
2983
+ const DefaultThemeColor = {
2984
+ mode: ThemeColorMode.default,
2985
+ boardBackground: '#ffffff',
2986
+ textColor: '#333333'
2753
2987
  };
2754
- const isSelectedElementOrGroup = (board, element) => {
2755
- const selectedElements = getSelectedElements(board);
2756
- if (PlaitGroupElement.isGroup(element)) {
2757
- return isSelectedAllElementsInGroup(board, element);
2758
- }
2759
- return selectedElements.includes(element);
2988
+ const ColorfulThemeColor = {
2989
+ mode: ThemeColorMode.colorful,
2990
+ boardBackground: '#ffffff',
2991
+ textColor: '#333333'
2760
2992
  };
2761
- const isSelectedAllElementsInGroup = (board, group) => {
2762
- const selectedElements = getSelectedElements(board);
2763
- const elementsInGroup = getElementsInGroup(board, group, true);
2764
- return elementsInGroup.every(item => selectedElements.includes(item));
2993
+ const SoftThemeColor = {
2994
+ mode: ThemeColorMode.soft,
2995
+ boardBackground: '#f5f5f5',
2996
+ textColor: '#333333'
2765
2997
  };
2766
- const getSelectedGroups = (board, groups) => {
2767
- const selectedGroups = [];
2768
- groups.forEach(item => {
2769
- if (isSelectedElementOrGroup(board, item)) {
2770
- selectedGroups.push(item);
2771
- }
2772
- });
2773
- return selectedGroups;
2998
+ const RetroThemeColor = {
2999
+ mode: ThemeColorMode.retro,
3000
+ boardBackground: '#f9f8ed',
3001
+ textColor: '#333333'
2774
3002
  };
2775
- const getHighestSelectedGroup = (board, element) => {
2776
- const groups = getGroupByElement(board, element, true);
2777
- const selectedGroups = getSelectedGroups(board, groups);
2778
- if (selectedGroups.length) {
2779
- return selectedGroups[selectedGroups.length - 1];
2780
- }
2781
- return null;
3003
+ const DarkThemeColor = {
3004
+ mode: ThemeColorMode.dark,
3005
+ boardBackground: '#141414',
3006
+ textColor: '#FFFFFF'
2782
3007
  };
2783
- const getHighestSelectedGroups = (board) => {
2784
- let result = [];
2785
- const selectedElements = getSelectedElements(board);
2786
- selectedElements.forEach(item => {
2787
- if (item.groupId) {
2788
- const group = getHighestSelectedGroup(board, item);
2789
- if (group && !result.includes(group)) {
2790
- result.push(group);
2791
- }
2792
- }
2793
- });
2794
- return result;
3008
+ const StarryThemeColor = {
3009
+ mode: ThemeColorMode.starry,
3010
+ boardBackground: '#0d2537',
3011
+ textColor: '#FFFFFF'
2795
3012
  };
2796
- const getSelectedIsolatedElements = (board) => {
2797
- let result = [];
2798
- const selectedElements = getSelectedElements(board);
2799
- selectedElements.forEach(item => {
2800
- if (!item.groupId) {
2801
- result.push(item);
3013
+ const ThemeColors = [
3014
+ DefaultThemeColor,
3015
+ ColorfulThemeColor,
3016
+ SoftThemeColor,
3017
+ RetroThemeColor,
3018
+ DarkThemeColor,
3019
+ StarryThemeColor
3020
+ ];
3021
+
3022
+ var Direction;
3023
+ (function (Direction) {
3024
+ Direction["left"] = "left";
3025
+ Direction["top"] = "top";
3026
+ Direction["right"] = "right";
3027
+ Direction["bottom"] = "bottom";
3028
+ })(Direction || (Direction = {}));
3029
+
3030
+ const PlaitGroupElement = {
3031
+ isGroup: (value) => {
3032
+ return value.type === 'group';
3033
+ }
3034
+ };
3035
+
3036
+ function getRectangleByElements(board, elements, recursion) {
3037
+ const rectanglesCornerPoints = [];
3038
+ const callback = (node) => {
3039
+ const nodeRectangle = board.getRectangle(node);
3040
+ if (nodeRectangle) {
3041
+ const cornerPoints = RectangleClient.getCornerPoints(nodeRectangle);
3042
+ const rotatedCornerPoints = rotatePoints(cornerPoints, RectangleClient.getCenterPoint(nodeRectangle), node.angle || 0);
3043
+ rectanglesCornerPoints.push(rotatedCornerPoints);
2802
3044
  }
2803
3045
  else {
2804
- const group = getHighestSelectedGroup(board, item);
2805
- if (!group) {
2806
- result.push(item);
2807
- }
3046
+ console.error(`can not get rectangle of element:`, node);
2808
3047
  }
2809
- });
2810
- return result;
2811
- };
2812
- const getHighestSelectedElements = (board) => {
2813
- return [...getHighestSelectedGroups(board), ...getSelectedIsolatedElements(board)];
2814
- };
2815
- const createGroupRectangleG = (board, elements) => {
2816
- const selectedElements = getSelectedElements(board);
2817
- const groupRectangleG = createG();
2818
- const isMoving = isSelectionMoving(board);
2819
- elements.forEach(item => {
2820
- const isRender = (!selectedElements.includes(item) && !isMoving) || isMoving;
2821
- if (item.groupId && isRender) {
2822
- const elements = getElementsInGroupByElement(board, item);
2823
- const rectangle = getRectangleByElements(board, elements, false);
2824
- groupRectangleG.append(drawRectangle(board, rectangle, {
2825
- stroke: SELECTION_BORDER_COLOR,
2826
- strokeWidth: ACTIVE_STROKE_WIDTH,
2827
- strokeLineDash: [5]
2828
- }));
3048
+ };
3049
+ elements.forEach(element => {
3050
+ if (recursion) {
3051
+ depthFirstRecursion(element, node => callback(node), node => board.isRecursion(node));
3052
+ }
3053
+ else {
3054
+ callback(element);
2829
3055
  }
2830
3056
  });
2831
- return groupRectangleG;
2832
- };
2833
- const createGroup = () => {
2834
- return {
2835
- id: idCreator(),
2836
- type: 'group'
2837
- };
2838
- };
2839
- const nonGroupInHighestSelectedElements = (elements) => {
2840
- return elements.every(item => !item.groupId);
2841
- };
2842
- const hasSelectedElementsInSameGroup = (elements) => {
2843
- return elements.every(item => item.groupId && item.groupId === elements[0].groupId);
2844
- };
2845
- const canAddGroup = (highestSelectedElements) => {
2846
- if (highestSelectedElements.length > 1) {
2847
- return nonGroupInHighestSelectedElements(highestSelectedElements) || hasSelectedElementsInSameGroup(highestSelectedElements);
2848
- }
2849
- return false;
2850
- };
2851
- const addGroup = (board) => {
2852
- const selectedGroups = getHighestSelectedGroups(board);
2853
- const selectedIsolatedElements = getSelectedIsolatedElements(board);
2854
- const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
2855
- const group = createGroup();
2856
- if (canAddGroup(highestSelectedElements)) {
2857
- highestSelectedElements.forEach(item => {
2858
- const path = PlaitBoard.findPath(board, item);
2859
- Transforms.setNode(board, { groupId: group.id }, path);
2860
- });
2861
- if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
2862
- const newGroupId = selectedIsolatedElements[0].groupId;
2863
- Transforms.insertNode(board, {
2864
- ...group,
2865
- groupId: newGroupId
2866
- }, [board.children.length]);
3057
+ if (rectanglesCornerPoints.length > 0) {
3058
+ if (hasSameAngle(elements)) {
3059
+ const angle = getSelectionAngle(elements);
3060
+ return getRotatedBoundingRectangle(rectanglesCornerPoints, angle);
2867
3061
  }
2868
3062
  else {
2869
- Transforms.insertNode(board, group, [board.children.length]);
3063
+ const flatCornerPoints = rectanglesCornerPoints.reduce((acc, val) => {
3064
+ return acc.concat(val);
3065
+ }, []);
3066
+ return RectangleClient.getRectangleByPoints(flatCornerPoints);
2870
3067
  }
2871
3068
  }
2872
- };
2873
- const canRemoveGroup = (board, selectedGroups) => {
2874
- const selectedElements = getSelectedElements(board);
2875
- return selectedElements.length > 0 && selectedGroups.length > 0;
2876
- };
2877
- const removeGroup = (board) => {
2878
- const selectedGroups = getHighestSelectedGroups(board);
2879
- if (canRemoveGroup(board, selectedGroups)) {
2880
- selectedGroups.map(group => {
2881
- const elementsInGroup = findElements(board, {
2882
- match: item => item.groupId === group.id,
2883
- recursion: () => false
2884
- });
2885
- elementsInGroup.forEach(item => {
2886
- const path = PlaitBoard.findPath(board, item);
2887
- Transforms.setNode(board, { groupId: group.groupId || undefined }, path);
2888
- });
2889
- const groupPath = PlaitBoard.findPath(board, group);
2890
- Transforms.removeNode(board, groupPath);
2891
- });
3069
+ else {
3070
+ return {
3071
+ x: 0,
3072
+ y: 0,
3073
+ width: 0,
3074
+ height: 0
3075
+ };
2892
3076
  }
2893
- };
2894
-
2895
- const PlaitElement = {
2896
- isRootElement(value) {
2897
- const parent = NODE_TO_PARENT.get(value);
2898
- if (parent && PlaitBoard.isBoard(parent)) {
3077
+ }
3078
+ function getBoardRectangle(board) {
3079
+ return getRectangleByElements(board, board.children, true);
3080
+ }
3081
+ function getElementById(board, id, dataSource) {
3082
+ if (!dataSource) {
3083
+ dataSource = findElements(board, { match: element => true, recursion: element => true });
3084
+ }
3085
+ let element = dataSource.find(element => element.id === id);
3086
+ return element;
3087
+ }
3088
+ function findElements(board, options) {
3089
+ let elements = [];
3090
+ const isReverse = options.isReverse ?? true;
3091
+ depthFirstRecursion(board, node => {
3092
+ if (!PlaitBoard.isBoard(node) && options.match(node)) {
3093
+ elements.push(node);
3094
+ }
3095
+ }, (value) => {
3096
+ if (PlaitBoard.isBoard(value)) {
2899
3097
  return true;
2900
3098
  }
2901
3099
  else {
2902
- return false;
3100
+ return getIsRecursionFunc(board)(value) && options.recursion(value);
3101
+ }
3102
+ }, isReverse);
3103
+ return elements;
3104
+ }
3105
+
3106
+ const PlaitBoard = {
3107
+ isBoard(value) {
3108
+ const cachedIsBoard = IS_BOARD_CACHE.get(value);
3109
+ if (cachedIsBoard !== undefined) {
3110
+ return cachedIsBoard;
2903
3111
  }
3112
+ const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
3113
+ IS_BOARD_CACHE.set(value, isBoard);
3114
+ return isBoard;
2904
3115
  },
2905
- getComponent(value) {
2906
- return ELEMENT_TO_COMPONENT.get(value);
3116
+ findPath(board, node) {
3117
+ const path = [];
3118
+ let child = node;
3119
+ while (true) {
3120
+ const parent = NODE_TO_PARENT.get(child);
3121
+ if (parent == null) {
3122
+ if (PlaitBoard.isBoard(child)) {
3123
+ return path;
3124
+ }
3125
+ else {
3126
+ break;
3127
+ }
3128
+ }
3129
+ const i = NODE_TO_INDEX.get(child);
3130
+ if (i == null) {
3131
+ break;
3132
+ }
3133
+ path.unshift(i);
3134
+ child = parent;
3135
+ }
3136
+ throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
3137
+ },
3138
+ getHost(board) {
3139
+ return BOARD_TO_HOST.get(board);
3140
+ },
3141
+ getElementHost(board) {
3142
+ return BOARD_TO_ELEMENT_HOST.get(board)?.host;
3143
+ },
3144
+ getElementUpperHost(board) {
3145
+ return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
3146
+ },
3147
+ getElementActiveHost(board) {
3148
+ return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
3149
+ },
3150
+ getRoughSVG(board) {
3151
+ return BOARD_TO_ROUGH_SVG.get(board);
3152
+ },
3153
+ getComponent(board) {
3154
+ return BOARD_TO_COMPONENT.get(board);
3155
+ },
3156
+ getBoardContainer(board) {
3157
+ return BOARD_TO_ELEMENT_HOST.get(board)?.container;
3158
+ },
3159
+ getRectangle(board) {
3160
+ return getRectangleByElements(board, board.children, true);
3161
+ },
3162
+ getViewportContainer(board) {
3163
+ return BOARD_TO_ELEMENT_HOST.get(board)?.viewportContainer;
3164
+ },
3165
+ isFocus(board) {
3166
+ return !!board.selection;
3167
+ },
3168
+ isReadonly(board) {
3169
+ return board.options.readonly;
3170
+ },
3171
+ hasBeenTextEditing(board) {
3172
+ return !!IS_TEXT_EDITABLE.get(board);
3173
+ },
3174
+ getPointer(board) {
3175
+ return board.pointer;
3176
+ },
3177
+ isPointer(board, pointer) {
3178
+ return board.pointer === pointer;
3179
+ },
3180
+ isInPointer(board, pointers) {
3181
+ const point = board.pointer;
3182
+ return pointers.includes(point);
3183
+ },
3184
+ getMovingPointInBoard(board) {
3185
+ return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
3186
+ },
3187
+ isMovingPointInBoard(board) {
3188
+ const point = BOARD_TO_MOVING_POINT.get(board);
3189
+ const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
3190
+ if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
3191
+ return true;
3192
+ }
3193
+ return false;
3194
+ },
3195
+ getThemeColors(board) {
3196
+ return (board.options.themeColors || ThemeColors);
2907
3197
  }
2908
3198
  };
2909
3199
 
2910
- const isSetViewportOperation = (value) => {
2911
- return value.type === 'set_viewport';
2912
- };
2913
- const inverse = (op) => {
3200
+ const applyToDraft = (board, selection, viewport, theme, op) => {
2914
3201
  switch (op.type) {
2915
3202
  case 'insert_node': {
2916
- return { ...op, type: 'remove_node' };
3203
+ const { path, node } = op;
3204
+ const parent = PlaitNode.parent(board, path);
3205
+ const index = path[path.length - 1];
3206
+ if (!parent.children || index > parent.children.length) {
3207
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
3208
+ }
3209
+ parent.children.splice(index, 0, node);
3210
+ break;
2917
3211
  }
2918
3212
  case 'remove_node': {
2919
- return { ...op, type: 'insert_node' };
3213
+ const { path } = op;
3214
+ const parent = PlaitNode.parent(board, path);
3215
+ const index = path[path.length - 1];
3216
+ if (!parent.children || index > parent.children.length) {
3217
+ throw new Error(`Cannot apply an "insert_node" operation at path [${path}] because the destination is past the end of the node.`);
3218
+ }
3219
+ parent.children.splice(index, 1);
3220
+ break;
2920
3221
  }
2921
3222
  case 'move_node': {
2922
- const { newPath, path } = op;
2923
- // PERF: in this case the move operation is a no-op anyways.
2924
- if (Path.equals(newPath, path)) {
2925
- return op;
3223
+ const { path, newPath } = op;
3224
+ if (Path.isAncestor(path, newPath)) {
3225
+ throw new Error(`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`);
2926
3226
  }
2927
- // when operation path is [0,0] -> [0,2], should exec Path.transform to get [0,1] -> [0,0]
2928
- // shoud not return [0,2] -> [0,0] #WIK-8981
2929
- // if (Path.isSibling(path, newPath)) {
2930
- // return { ...op, path: newPath, newPath: path };
2931
- // }
2932
- // If the move does not happen within a single parent it is possible
2933
- // for the move to impact the true path to the location where the node
2934
- // was removed from and where it was inserted. We have to adjust for this
2935
- // and find the original path. We can accomplish this (only in non-sibling)
2936
- // moves by looking at the impact of the move operation on the node
2937
- // after the original move path.
2938
- const inversePath = Path.transform(path, op);
2939
- const inverseNewPath = Path.transform(Path.next(path), op);
2940
- return { ...op, path: inversePath, newPath: inverseNewPath };
3227
+ const node = PlaitNode.get(board, path);
3228
+ const parent = PlaitNode.parent(board, path);
3229
+ const index = path[path.length - 1];
3230
+ // This is tricky, but since the `path` and `newPath` both refer to
3231
+ // the same snapshot in time, there's a mismatch. After either
3232
+ // removing the original position, the second step's path can be out
3233
+ // of date. So instead of using the `op.newPath` directly, we
3234
+ // transform `op.path` to ascertain what the `newPath` would be after
3235
+ // the operation was applied.
3236
+ parent.children?.splice(index, 1);
3237
+ const truePath = Path.transform(path, op);
3238
+ const newParent = PlaitNode.get(board, Path.parent(truePath));
3239
+ const newIndex = truePath[truePath.length - 1];
3240
+ newParent.children?.splice(newIndex, 0, node);
3241
+ break;
2941
3242
  }
2942
3243
  case 'set_node': {
2943
- const { properties, newProperties } = op;
2944
- return { ...op, properties: newProperties, newProperties: properties };
2945
- }
2946
- case 'set_selection': {
2947
- const { properties, newProperties } = op;
2948
- if (properties == null) {
2949
- return {
2950
- ...op,
2951
- properties: newProperties,
2952
- newProperties: null
2953
- };
3244
+ const { path, properties, newProperties } = op;
3245
+ if (path.length === 0) {
3246
+ throw new Error(`Cannot set properties on the root node!`);
2954
3247
  }
2955
- else if (newProperties == null) {
2956
- return {
2957
- ...op,
2958
- properties: null,
2959
- newProperties: properties
2960
- };
3248
+ const node = PlaitNode.get(board, path);
3249
+ for (const key in newProperties) {
3250
+ const value = newProperties[key];
3251
+ if (value == null) {
3252
+ delete node[key];
3253
+ }
3254
+ else {
3255
+ node[key] = value;
3256
+ }
2961
3257
  }
2962
- else {
2963
- return { ...op, properties: newProperties, newProperties: properties };
3258
+ // properties that were previously defined, but are now missing, must be deleted
3259
+ for (const key in properties) {
3260
+ if (!newProperties.hasOwnProperty(key)) {
3261
+ delete node[key];
3262
+ }
2964
3263
  }
3264
+ break;
2965
3265
  }
2966
3266
  case 'set_viewport': {
2967
- const { properties, newProperties } = op;
2968
- if (properties == null) {
2969
- return {
2970
- ...op,
2971
- properties: newProperties,
2972
- newProperties: newProperties
2973
- };
3267
+ const { newProperties } = op;
3268
+ if (newProperties == null) {
3269
+ viewport = newProperties;
2974
3270
  }
2975
- else if (newProperties == null) {
2976
- return {
2977
- ...op,
2978
- properties: properties,
2979
- newProperties: properties
2980
- };
3271
+ else {
3272
+ if (viewport == null) {
3273
+ if (!Viewport.isViewport(newProperties)) {
3274
+ throw new Error(`Cannot apply an incomplete "set_viewport" operation properties ${JSON.stringify(newProperties)} when there is no current viewport.`);
3275
+ }
3276
+ viewport = { ...newProperties };
3277
+ }
3278
+ for (const key in newProperties) {
3279
+ const value = newProperties[key];
3280
+ if (value == null) {
3281
+ delete viewport[key];
3282
+ }
3283
+ else {
3284
+ viewport[key] = value;
3285
+ }
3286
+ }
3287
+ }
3288
+ break;
3289
+ }
3290
+ case 'set_selection': {
3291
+ const { newProperties } = op;
3292
+ if (newProperties == null) {
3293
+ selection = newProperties;
2981
3294
  }
2982
3295
  else {
2983
- return { ...op, properties: newProperties, newProperties: properties };
3296
+ if (selection === null) {
3297
+ selection = op.newProperties;
3298
+ }
3299
+ else {
3300
+ selection = newProperties;
3301
+ }
2984
3302
  }
3303
+ break;
2985
3304
  }
2986
3305
  case 'set_theme': {
2987
- const { properties, newProperties } = op;
2988
- return { ...op, properties: newProperties, newProperties: properties };
3306
+ const { newProperties } = op;
3307
+ theme = newProperties;
3308
+ break;
2989
3309
  }
2990
3310
  }
3311
+ return { selection, viewport, theme };
2991
3312
  };
2992
- const PlaitOperation = {
2993
- isSetViewportOperation,
2994
- inverse
3313
+ const GeneralTransforms = {
3314
+ /**
3315
+ * Transform the board by an operation.
3316
+ */
3317
+ transform(board, op) {
3318
+ board.children = createDraft(board.children);
3319
+ let viewport = board.viewport && createDraft(board.viewport);
3320
+ let selection = board.selection && createDraft(board.selection);
3321
+ let theme = board.theme && createDraft(board.theme);
3322
+ try {
3323
+ const state = applyToDraft(board, selection, viewport, theme, op);
3324
+ viewport = state.viewport;
3325
+ selection = state.selection;
3326
+ theme = state.theme;
3327
+ }
3328
+ finally {
3329
+ board.children = finishDraft(board.children);
3330
+ if (selection) {
3331
+ board.selection = isDraft(selection) ? finishDraft(selection) : selection;
3332
+ }
3333
+ else {
3334
+ board.selection = null;
3335
+ }
3336
+ board.viewport = isDraft(viewport) ? finishDraft(viewport) : viewport;
3337
+ board.theme = isDraft(theme) ? finishDraft(theme) : theme;
3338
+ }
3339
+ }
2995
3340
  };
2996
3341
 
2997
- const Point = {
2998
- isEquals(point, otherPoint) {
2999
- return point && otherPoint && point[0] === otherPoint[0] && point[1] === otherPoint[1];
3000
- },
3001
- isHorizontal(point, otherPoint, tolerance = 0) {
3002
- return point && otherPoint && Point.isOverHorizontal([point, otherPoint], tolerance);
3003
- },
3004
- isOverHorizontal(points, tolerance = 0) {
3005
- return points.every(point => Math.abs(point[1] - points[0][1]) <= tolerance);
3006
- },
3007
- isVertical(point, otherPoint, tolerance = 0) {
3008
- return point && otherPoint && Point.isOverVertical([point, otherPoint], tolerance);
3009
- },
3010
- isOverVertical(points, tolerance = 0) {
3011
- return points.every(point => Math.abs(point[0] - points[0][0]) <= tolerance);
3012
- },
3013
- isAlign(points, tolerance = 0) {
3014
- return Point.isOverHorizontal(points, tolerance) || Point.isOverVertical(points, tolerance);
3015
- },
3016
- getOffsetX(point1, point2) {
3017
- return point2[0] - point1[0];
3018
- },
3019
- getOffsetY(point1, point2) {
3020
- return point2[1] - point1[1];
3342
+ function insertNode(board, node, path) {
3343
+ const operation = { type: 'insert_node', node, path };
3344
+ board.apply(operation);
3345
+ }
3346
+ function setNode(board, props, path) {
3347
+ const properties = {};
3348
+ const newProperties = {};
3349
+ const node = PlaitNode.get(board, path);
3350
+ for (const k in props) {
3351
+ if (node[k] !== props[k]) {
3352
+ if (node.hasOwnProperty(k)) {
3353
+ properties[k] = node[k];
3354
+ }
3355
+ if (props[k] != null)
3356
+ newProperties[k] = props[k];
3357
+ }
3021
3358
  }
3359
+ const operation = { type: 'set_node', properties, newProperties, path };
3360
+ board.apply(operation);
3361
+ }
3362
+ function removeNode(board, path) {
3363
+ const node = PlaitNode.get(board, path);
3364
+ const operation = { type: 'remove_node', path, node };
3365
+ board.apply(operation);
3366
+ }
3367
+ function moveNode(board, path, newPath) {
3368
+ const operation = { type: 'move_node', path, newPath };
3369
+ board.apply(operation);
3370
+ }
3371
+ const NodeTransforms = {
3372
+ insertNode,
3373
+ setNode,
3374
+ removeNode,
3375
+ moveNode
3022
3376
  };
3023
3377
 
3024
- const SAVING = new WeakMap();
3025
- const MERGING = new WeakMap();
3026
-
3027
- var ThemeColorMode;
3028
- (function (ThemeColorMode) {
3029
- ThemeColorMode["default"] = "default";
3030
- ThemeColorMode["colorful"] = "colorful";
3031
- ThemeColorMode["soft"] = "soft";
3032
- ThemeColorMode["retro"] = "retro";
3033
- ThemeColorMode["dark"] = "dark";
3034
- ThemeColorMode["starry"] = "starry";
3035
- })(ThemeColorMode || (ThemeColorMode = {}));
3036
- const DefaultThemeColor = {
3037
- mode: ThemeColorMode.default,
3038
- boardBackground: '#ffffff',
3039
- textColor: '#333333'
3040
- };
3041
- const ColorfulThemeColor = {
3042
- mode: ThemeColorMode.colorful,
3043
- boardBackground: '#ffffff',
3044
- textColor: '#333333'
3045
- };
3046
- const SoftThemeColor = {
3047
- mode: ThemeColorMode.soft,
3048
- boardBackground: '#f5f5f5',
3049
- textColor: '#333333'
3050
- };
3051
- const RetroThemeColor = {
3052
- mode: ThemeColorMode.retro,
3053
- boardBackground: '#f9f8ed',
3054
- textColor: '#333333'
3378
+ function setSelection(board, selection) {
3379
+ const operation = { type: 'set_selection', properties: board.selection, newProperties: selection };
3380
+ board.apply(operation);
3381
+ }
3382
+ const SelectionTransforms = {
3383
+ setSelection,
3384
+ addSelectionWithTemporaryElements
3055
3385
  };
3056
- const DarkThemeColor = {
3057
- mode: ThemeColorMode.dark,
3058
- boardBackground: '#141414',
3059
- textColor: '#FFFFFF'
3386
+ function addSelectionWithTemporaryElements(board, elements) {
3387
+ const timeoutId = setTimeout(() => {
3388
+ setSelection(board, { anchor: [0, 0], focus: [0, 0] });
3389
+ }, 0);
3390
+ let ref = getTemporaryRef(board);
3391
+ if (ref) {
3392
+ clearTimeout(ref.timeoutId);
3393
+ const currentElements = ref.elements;
3394
+ ref.elements.push(...elements.filter(element => !currentElements.includes(element)));
3395
+ ref.timeoutId = timeoutId;
3396
+ }
3397
+ else {
3398
+ BOARD_TO_TEMPORARY_ELEMENTS.set(board, { timeoutId, elements });
3399
+ }
3400
+ }
3401
+
3402
+ const removeElements = (board, elements) => {
3403
+ elements
3404
+ .map(element => {
3405
+ const path = PlaitBoard.findPath(board, element);
3406
+ const ref = board.pathRef(path);
3407
+ return () => {
3408
+ removeNode(board, ref.current);
3409
+ ref.unref();
3410
+ removeSelectedElement(board, element, true);
3411
+ };
3412
+ })
3413
+ .forEach(action => {
3414
+ action();
3415
+ });
3060
3416
  };
3061
- const StarryThemeColor = {
3062
- mode: ThemeColorMode.starry,
3063
- boardBackground: '#0d2537',
3064
- textColor: '#FFFFFF'
3417
+ const CoreTransforms = {
3418
+ removeElements
3065
3419
  };
3066
- const ThemeColors = [
3067
- DefaultThemeColor,
3068
- ColorfulThemeColor,
3069
- SoftThemeColor,
3070
- RetroThemeColor,
3071
- DarkThemeColor,
3072
- StarryThemeColor
3073
- ];
3074
-
3075
- var Direction;
3076
- (function (Direction) {
3077
- Direction["left"] = "left";
3078
- Direction["top"] = "top";
3079
- Direction["right"] = "right";
3080
- Direction["bottom"] = "bottom";
3081
- })(Direction || (Direction = {}));
3082
3420
 
3083
- function getRectangleByElements(board, elements, recursion) {
3084
- const rectangles = [];
3085
- const callback = (node) => {
3086
- const nodeRectangle = board.getRectangle(node);
3087
- if (nodeRectangle) {
3088
- rectangles.push(nodeRectangle);
3089
- }
3090
- else {
3091
- console.error(`can not get rectangle of element:`, node);
3092
- }
3093
- };
3094
- elements.forEach(element => {
3095
- if (recursion) {
3096
- depthFirstRecursion(element, node => callback(node), node => board.isRecursion(node));
3421
+ const addGroup = (board, elements) => {
3422
+ const selectedGroups = getHighestSelectedGroups(board, elements);
3423
+ const selectedIsolatedElements = getSelectedIsolatedElementsCanAddToGroup(board);
3424
+ const highestSelectedElements = [...selectedGroups, ...selectedIsolatedElements];
3425
+ const group = createGroup();
3426
+ if (canAddGroup(board)) {
3427
+ highestSelectedElements.forEach(item => {
3428
+ const path = PlaitBoard.findPath(board, item);
3429
+ NodeTransforms.setNode(board, { groupId: group.id }, path);
3430
+ });
3431
+ if (hasSelectedElementsInSameGroup(highestSelectedElements)) {
3432
+ const newGroupId = selectedIsolatedElements[0].groupId;
3433
+ NodeTransforms.insertNode(board, {
3434
+ ...group,
3435
+ groupId: newGroupId
3436
+ }, [board.children.length]);
3097
3437
  }
3098
3438
  else {
3099
- callback(element);
3439
+ NodeTransforms.insertNode(board, group, [board.children.length]);
3100
3440
  }
3101
- });
3102
- if (rectangles.length > 0) {
3103
- return RectangleClient.getBoundingRectangle(rectangles);
3104
- }
3105
- else {
3106
- return {
3107
- x: 0,
3108
- y: 0,
3109
- width: 0,
3110
- height: 0
3111
- };
3112
3441
  }
3113
- }
3114
- function getBoardRectangle(board) {
3115
- return getRectangleByElements(board, board.children, true);
3116
- }
3117
- function getElementById(board, id, dataSource) {
3118
- if (!dataSource) {
3119
- dataSource = findElements(board, { match: element => true, recursion: element => true });
3442
+ };
3443
+ const removeGroup = (board, elements) => {
3444
+ const selectedGroups = getHighestSelectedGroups(board, elements);
3445
+ if (canRemoveGroup(board)) {
3446
+ selectedGroups.map(group => {
3447
+ const elementsInGroup = findElements(board, {
3448
+ match: item => item.groupId === group.id,
3449
+ recursion: () => false
3450
+ });
3451
+ elementsInGroup.forEach(item => {
3452
+ const path = PlaitBoard.findPath(board, item);
3453
+ NodeTransforms.setNode(board, { groupId: group.groupId || undefined }, path);
3454
+ });
3455
+ const groupPath = PlaitBoard.findPath(board, group);
3456
+ NodeTransforms.removeNode(board, groupPath);
3457
+ });
3120
3458
  }
3121
- let element = dataSource.find(element => element.id === id);
3122
- return element;
3123
- }
3124
- function findElements(board, options) {
3125
- let elements = [];
3126
- const isReverse = options.isReverse ?? true;
3127
- depthFirstRecursion(board, node => {
3128
- if (!PlaitBoard.isBoard(node) && options.match(node)) {
3129
- elements.push(node);
3130
- }
3131
- }, (value) => {
3132
- if (PlaitBoard.isBoard(value)) {
3133
- return true;
3134
- }
3135
- else {
3136
- return getIsRecursionFunc(board)(value) && options.recursion(value);
3137
- }
3138
- }, isReverse);
3139
- return elements;
3140
- }
3459
+ };
3460
+ const GroupTransforms = {
3461
+ addGroup,
3462
+ removeGroup
3463
+ };
3141
3464
 
3142
- const PlaitBoard = {
3143
- isBoard(value) {
3144
- const cachedIsBoard = IS_BOARD_CACHE.get(value);
3145
- if (cachedIsBoard !== undefined) {
3146
- return cachedIsBoard;
3147
- }
3148
- const isBoard = typeof value.onChange === 'function' && typeof value.apply === 'function';
3149
- IS_BOARD_CACHE.set(value, isBoard);
3150
- return isBoard;
3151
- },
3152
- findPath(board, node) {
3153
- const path = [];
3154
- let child = node;
3155
- while (true) {
3156
- const parent = NODE_TO_PARENT.get(child);
3157
- if (parent == null) {
3158
- if (PlaitBoard.isBoard(child)) {
3159
- return path;
3160
- }
3161
- else {
3162
- break;
3163
- }
3164
- }
3165
- const i = NODE_TO_INDEX.get(child);
3166
- if (i == null) {
3167
- break;
3168
- }
3169
- path.unshift(i);
3170
- child = parent;
3171
- }
3172
- throw new Error(`Unable to find the path for Plait node: ${JSON.stringify(node)}`);
3173
- },
3174
- getHost(board) {
3175
- return BOARD_TO_HOST.get(board);
3176
- },
3177
- getElementHost(board) {
3178
- return BOARD_TO_ELEMENT_HOST.get(board)?.host;
3179
- },
3180
- getElementUpperHost(board) {
3181
- return BOARD_TO_ELEMENT_HOST.get(board)?.upperHost;
3182
- },
3183
- getElementActiveHost(board) {
3184
- return BOARD_TO_ELEMENT_HOST.get(board)?.activeHost;
3185
- },
3186
- getRoughSVG(board) {
3187
- return BOARD_TO_ROUGH_SVG.get(board);
3188
- },
3189
- getComponent(board) {
3190
- return BOARD_TO_COMPONENT.get(board);
3191
- },
3192
- getBoardContainer(board) {
3193
- return BOARD_TO_ELEMENT_HOST.get(board)?.container;
3194
- },
3195
- getRectangle(board) {
3196
- return getRectangleByElements(board, board.children, true);
3197
- },
3198
- getViewportContainer(board) {
3199
- return BOARD_TO_ELEMENT_HOST.get(board)?.viewportContainer;
3200
- },
3201
- isFocus(board) {
3202
- return !!board.selection;
3203
- },
3204
- isReadonly(board) {
3205
- return board.options.readonly;
3206
- },
3207
- hasBeenTextEditing(board) {
3208
- return !!IS_TEXT_EDITABLE.get(board);
3209
- },
3210
- getPointer(board) {
3211
- return board.pointer;
3212
- },
3213
- isPointer(board, pointer) {
3214
- return board.pointer === pointer;
3215
- },
3216
- isInPointer(board, pointers) {
3217
- const point = board.pointer;
3218
- return pointers.includes(point);
3219
- },
3220
- getMovingPointInBoard(board) {
3221
- return BOARD_TO_MOVING_POINT_IN_BOARD.get(board);
3222
- },
3223
- isMovingPointInBoard(board) {
3224
- const point = BOARD_TO_MOVING_POINT.get(board);
3225
- const rect = PlaitBoard.getBoardContainer(board).getBoundingClientRect();
3226
- if (point && distanceBetweenPointAndRectangle(point[0], point[1], rect) === 0) {
3227
- return true;
3228
- }
3229
- return false;
3230
- },
3231
- getThemeColors(board) {
3232
- return (board.options.themeColors || ThemeColors);
3233
- }
3465
+ const Transforms = {
3466
+ ...GeneralTransforms,
3467
+ ...ViewportTransforms$1,
3468
+ ...SelectionTransforms,
3469
+ ...NodeTransforms
3234
3470
  };
3235
3471
 
3236
3472
  const PathRef = {
@@ -3351,7 +3587,8 @@ function createBoard(children, options) {
3351
3587
  pointerLeave: pointer => { },
3352
3588
  globalPointerMove: pointer => { },
3353
3589
  globalPointerUp: pointer => { },
3354
- isImageBindingAllowed: (element) => false
3590
+ isImageBindingAllowed: (element) => false,
3591
+ canAddToGroup: (element) => true
3355
3592
  };
3356
3593
  return board;
3357
3594
  }
@@ -3628,112 +3865,46 @@ function withSelection(board) {
3628
3865
  selectionRectangleG?.remove();
3629
3866
  }
3630
3867
  const temporaryElements = getTemporaryElements(board);
3631
- let elements = temporaryElements ? temporaryElements : getHitElementsBySelection(board);
3632
- if (!options.isMultiple && elements.length > 1) {
3633
- elements = [elements[0]];
3868
+ if (temporaryElements) {
3869
+ cacheSelectedElements(board, [...temporaryElements]);
3634
3870
  }
3635
- const isHitElementWithGroup = elements.some(item => item.groupId);
3636
- if (isShift) {
3637
- const newSelectedElements = [...getSelectedElements(board)];
3638
- if (board.selection && Selection.isCollapsed(board.selection)) {
3639
- if (isHitElementWithGroup) {
3640
- let pendingElements = [...elements];
3641
- const hitElement = elements[0];
3642
- const groups = getGroupByElement(board, hitElement, true);
3643
- const selectedGroups = getSelectedGroups(board, groups);
3644
- const elementsInHighestGroup = getElementsInGroup(board, groups[groups.length - 1], true);
3645
- if (selectedGroups.length > 0) {
3646
- if (selectedGroups.length > 1) {
3647
- pendingElements = getElementsInGroup(board, selectedGroups[selectedGroups.length - 2], true);
3648
- }
3649
- }
3650
- else {
3651
- if (!newSelectedElements.includes(hitElement)) {
3652
- const selectedElementsInGroup = elementsInHighestGroup.filter(item => newSelectedElements.includes(item));
3653
- // When partially selected elements belong to a group,
3654
- // only select those elements along with the hit elements.
3655
- if (selectedElementsInGroup.length) {
3656
- pendingElements.push(...selectedElementsInGroup);
3871
+ else {
3872
+ let elements = getHitElementsBySelection(board);
3873
+ if (!options.isMultiple && elements.length > 1) {
3874
+ elements = [elements[0]];
3875
+ }
3876
+ const isHitElementWithGroup = elements.some(item => item.groupId);
3877
+ const selectedElements = getSelectedElements(board);
3878
+ if (isHitElementWithGroup) {
3879
+ setSelectedElementsWithGroup(board, elements, isShift);
3880
+ }
3881
+ else {
3882
+ if (isShift) {
3883
+ const newElements = [...selectedElements];
3884
+ if (board.selection && Selection.isCollapsed(board.selection)) {
3885
+ elements.forEach(element => {
3886
+ if (newElements.includes(element)) {
3887
+ newElements.splice(newElements.indexOf(element), 1);
3657
3888
  }
3658
3889
  else {
3659
- pendingElements = elementsInHighestGroup;
3890
+ newElements.push(element);
3660
3891
  }
3661
- }
3662
- else {
3663
- pendingElements = [];
3664
- }
3892
+ });
3893
+ cacheSelectedElements(board, newElements);
3665
3894
  }
3666
- elementsInHighestGroup.forEach(element => {
3667
- if (newSelectedElements.includes(element)) {
3668
- newSelectedElements.splice(newSelectedElements.indexOf(element), 1);
3669
- }
3670
- });
3671
- if (pendingElements.length) {
3672
- newSelectedElements.push(...pendingElements);
3673
- }
3674
- }
3675
- else {
3676
- elements.forEach(element => {
3677
- if (newSelectedElements.includes(element)) {
3678
- newSelectedElements.splice(newSelectedElements.indexOf(element), 1);
3679
- }
3680
- else {
3681
- newSelectedElements.push(element);
3682
- }
3683
- });
3684
- }
3685
- cacheSelectedElements(board, newSelectedElements);
3686
- }
3687
- else {
3688
- let newElements = [...elements];
3689
- if (isHitElementWithGroup) {
3690
- elements.forEach(item => {
3691
- if (!item.groupId) {
3692
- newElements.push(item);
3693
- }
3694
- else {
3695
- newElements.push(...getElementsInGroupByElement(board, item));
3696
- }
3697
- });
3698
- }
3699
- newElements.forEach(element => {
3700
- if (!newSelectedElements.includes(element)) {
3701
- newSelectedElements.push(element);
3895
+ else {
3896
+ elements.forEach(element => {
3897
+ if (!newElements.includes(element)) {
3898
+ newElements.push(element);
3899
+ }
3900
+ });
3901
+ cacheSelectedElements(board, [...newElements]);
3702
3902
  }
3703
- });
3704
- cacheSelectedElements(board, newSelectedElements);
3705
- }
3706
- }
3707
- else {
3708
- let newSelectedElements = [...elements];
3709
- if (isHitElementWithGroup) {
3710
- const isCollapsed = Selection.isCollapsed(board.selection);
3711
- if (!isCollapsed) {
3712
- newSelectedElements = [];
3713
- elements.forEach(item => {
3714
- if (!item.groupId) {
3715
- newSelectedElements.push(item);
3716
- }
3717
- else {
3718
- newSelectedElements.push(...getElementsInGroupByElement(board, item));
3719
- }
3720
- });
3721
3903
  }
3722
3904
  else {
3723
- const hitElement = elements[0];
3724
- const groups = getGroupByElement(board, hitElement, true);
3725
- const selectedGroups = getSelectedGroups(board, groups);
3726
- if (selectedGroups.length > 0) {
3727
- if (selectedGroups.length > 1) {
3728
- newSelectedElements = getElementsInGroup(board, selectedGroups[selectedGroups.length - 2], true);
3729
- }
3730
- }
3731
- else {
3732
- newSelectedElements = getElementsInGroup(board, groups[groups.length - 1], true);
3733
- }
3905
+ cacheSelectedElements(board, [...elements]);
3734
3906
  }
3735
3907
  }
3736
- cacheSelectedElements(board, newSelectedElements);
3737
3908
  }
3738
3909
  const newElements = getSelectedElements(board);
3739
3910
  previousSelectedElements = newElements;
@@ -4185,6 +4356,9 @@ function withMoving(board) {
4185
4356
  let activeElements = [];
4186
4357
  let alignG = null;
4187
4358
  let activeElementsRectangle = null;
4359
+ let selectedTargetElements = null;
4360
+ let hitTargetElement = undefined;
4361
+ let isHitSelectedTarget = undefined;
4188
4362
  board.pointerDown = (event) => {
4189
4363
  if (PlaitBoard.isReadonly(board) ||
4190
4364
  !PlaitBoard.isPointer(board, PlaitPointerType.selection) ||
@@ -4194,24 +4368,31 @@ function withMoving(board) {
4194
4368
  return;
4195
4369
  }
4196
4370
  const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4197
- const targetElements = getTargetElements(board);
4198
- const targetRectangle = targetElements.length > 0 && getRectangleByElements(board, targetElements, false);
4199
- const isInTargetRectangle = targetRectangle && RectangleClient.isPointInRectangle(targetRectangle, point);
4200
- if (isInTargetRectangle) {
4371
+ hitTargetElement = getHitElementByPoint(board, point, el => board.isMovable(el));
4372
+ selectedTargetElements = getSelectedTargetElements(board);
4373
+ isHitSelectedTarget = hitTargetElement && selectedTargetElements.includes(hitTargetElement);
4374
+ if (hitTargetElement && isHitSelectedTarget) {
4201
4375
  startPoint = point;
4202
- activeElements = targetElements;
4376
+ activeElements = selectedTargetElements;
4377
+ activeElementsRectangle = getRectangleByElements(board, activeElements, true);
4203
4378
  preventTouchMove(board, event, true);
4379
+ }
4380
+ else if (hitTargetElement) {
4381
+ startPoint = point;
4382
+ activeElements = getElementsInGroupByElement(board, hitTargetElement);
4204
4383
  activeElementsRectangle = getRectangleByElements(board, activeElements, true);
4384
+ preventTouchMove(board, event, true);
4205
4385
  }
4206
4386
  else {
4207
- const targetElement = getHitElementByPoint(board, point, el => board.isMovable(el));
4208
- if (targetElement) {
4387
+ // 只有判定用户未击中元素之后才可以验证用户是否击中了已选元素所在的空白区域
4388
+ // Only after it is determined that the user has not hit the element can it be verified whether the user hit the blank area where the selected element is located.
4389
+ const targetRectangle = selectedTargetElements.length > 0 && getRectangleByElements(board, selectedTargetElements, false);
4390
+ const isHitInTargetRectangle = targetRectangle && RectangleClient.isPointInRectangle(targetRectangle, point);
4391
+ if (isHitInTargetRectangle) {
4209
4392
  startPoint = point;
4210
- activeElements = getElementsInGroupByElement(board, targetElement);
4211
- if (targetElements.length > 0) {
4212
- addSelectionWithTemporaryElements(board, []);
4213
- }
4214
- activeElementsRectangle = getRectangleByElements(board, activeElements, true);
4393
+ activeElements = selectedTargetElements;
4394
+ activeElementsRectangle = targetRectangle;
4395
+ preventTouchMove(board, event, true);
4215
4396
  }
4216
4397
  }
4217
4398
  pointerDown(event);
@@ -4227,6 +4408,12 @@ function withMoving(board) {
4227
4408
  offsetY = endPoint[1] - startPoint[1];
4228
4409
  const distance = distanceBetweenPointAndPoint(...endPoint, ...startPoint);
4229
4410
  if (distance > PRESS_AND_MOVE_BUFFER || getMovingElements(board).length > 0) {
4411
+ if (hitTargetElement && !isHitSelectedTarget && selectedTargetElements && selectedTargetElements.length > 0) {
4412
+ addSelectionWithTemporaryElements(board, []);
4413
+ hitTargetElement = undefined;
4414
+ selectedTargetElements = null;
4415
+ isHitSelectedTarget = undefined;
4416
+ }
4230
4417
  throttleRAF(board, 'with-moving', () => {
4231
4418
  if (!activeElementsRectangle) {
4232
4419
  return;
@@ -4267,6 +4454,9 @@ function withMoving(board) {
4267
4454
  };
4268
4455
  board.globalPointerUp = event => {
4269
4456
  isPreventDefault = false;
4457
+ hitTargetElement = undefined;
4458
+ selectedTargetElements = null;
4459
+ isHitSelectedTarget = undefined;
4270
4460
  if (startPoint) {
4271
4461
  cancelMove(board);
4272
4462
  }
@@ -4315,7 +4505,7 @@ function withArrowMoving(board) {
4315
4505
  break;
4316
4506
  }
4317
4507
  }
4318
- const targetElements = getTargetElements(board);
4508
+ const targetElements = getSelectedTargetElements(board);
4319
4509
  throttleRAF(board, 'with-arrow-moving', () => {
4320
4510
  updatePoints(board, targetElements, offset[0], offset[1]);
4321
4511
  });
@@ -4328,7 +4518,7 @@ function withArrowMoving(board) {
4328
4518
  };
4329
4519
  return board;
4330
4520
  }
4331
- function getTargetElements(board) {
4521
+ function getSelectedTargetElements(board) {
4332
4522
  const selectedElements = getSelectedElements(board);
4333
4523
  const movableElements = board.children.filter(item => board.isMovable(item));
4334
4524
  const targetElements = selectedElements.filter(element => {
@@ -4339,7 +4529,7 @@ function getTargetElements(board) {
4339
4529
  return targetElements;
4340
4530
  }
4341
4531
  function updatePoints(board, targetElements, offsetX, offsetY) {
4342
- const validElements = targetElements.filter(element => board.children.findIndex(item => item.id === element.id) > -1);
4532
+ const validElements = targetElements.filter(element => !PlaitGroupElement.isGroup(element) && board.children.findIndex(item => item.id === element.id) > -1);
4343
4533
  const currentElements = validElements.map(element => {
4344
4534
  const points = element.points || [];
4345
4535
  const newPoints = points.map(p => [p[0] + offsetX, p[1] + offsetY]);
@@ -4671,27 +4861,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.2.4", ngImpor
4671
4861
  type: Input
4672
4862
  }] } });
4673
4863
 
4674
- function withGroup(board) {
4675
- const { pointerMove, globalPointerUp } = board;
4676
- let groupRectangleG;
4677
- board.pointerMove = (event) => {
4678
- groupRectangleG?.remove();
4679
- const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
4680
- let selection = { anchor: point, focus: point };
4681
- if (board.selection && !Selection.isCollapsed(board.selection)) {
4682
- selection = board.selection;
4864
+ function withRelatedFragment(board) {
4865
+ const { setFragment } = board;
4866
+ board.setFragment = (data, clipboardContext, rectangle, type) => {
4867
+ const relatedFragment = board.getRelatedFragment([]);
4868
+ if (!clipboardContext) {
4869
+ clipboardContext = createClipboardContext(WritableClipboardType.elements, relatedFragment, '');
4683
4870
  }
4684
- const hitElements = getHitElementsBySelection(board, selection);
4685
- if (hitElements.length) {
4686
- groupRectangleG = createGroupRectangleG(board, hitElements);
4687
- groupRectangleG && PlaitBoard.getElementActiveHost(board).append(groupRectangleG);
4871
+ else {
4872
+ clipboardContext = addClipboardContext(clipboardContext, {
4873
+ text: '',
4874
+ type: WritableClipboardType.elements,
4875
+ data: relatedFragment
4876
+ });
4688
4877
  }
4689
- pointerMove(event);
4690
- };
4691
- board.globalPointerUp = (event) => {
4692
- groupRectangleG?.remove();
4693
- groupRectangleG = null;
4694
- globalPointerUp(event);
4878
+ setFragment(data, clipboardContext, rectangle, type);
4695
4879
  };
4696
4880
  return board;
4697
4881
  }
@@ -4821,7 +5005,7 @@ class PlaitBoardComponent {
4821
5005
  initializeViewportOffset(this.board);
4822
5006
  }
4823
5007
  initializePlugins() {
4824
- let board = withHotkey(withHandPointer(withHistory(withSelection(withGroup(withMoving(withBoard(withViewport(withOptions(createBoard(this.plaitValue, this.plaitOptions))))))))));
5008
+ let board = withRelatedFragment(withHotkey(withHandPointer(withHistory(withSelection(withMoving(withBoard(withViewport(withOptions(createBoard(this.plaitValue, this.plaitOptions))))))))));
4825
5009
  this.plaitPlugins.forEach(plugin => {
4826
5010
  board = plugin(board);
4827
5011
  });
@@ -5263,6 +5447,81 @@ function createModModifierKeys() {
5263
5447
  return modifiers;
5264
5448
  }
5265
5449
 
5450
+ const TEMPORARY_G = new Map();
5451
+ const getTemporaryGArray = (debugKey) => {
5452
+ return TEMPORARY_G.get(debugKey) || [];
5453
+ };
5454
+ const setTemporaryGArray = (debugKey, gArray) => {
5455
+ TEMPORARY_G.set(debugKey, gArray);
5456
+ };
5457
+ class DebugGenerator {
5458
+ constructor(debugKey) {
5459
+ this.debugKey = debugKey;
5460
+ }
5461
+ isDebug() {
5462
+ return isDebug(this.debugKey);
5463
+ }
5464
+ clear() {
5465
+ if (!this.isDebug()) {
5466
+ return;
5467
+ }
5468
+ const gArray = getTemporaryGArray(this.debugKey);
5469
+ setTemporaryGArray(this.debugKey, []);
5470
+ gArray.forEach(g => g.remove());
5471
+ }
5472
+ drawPolygon(board, points, options) {
5473
+ if (!isDebug(this.debugKey)) {
5474
+ return;
5475
+ }
5476
+ const polygonG = PlaitBoard.getRoughSVG(board).polygon(points, options || { stroke: 'red' });
5477
+ PlaitBoard.getElementActiveHost(board).append(polygonG);
5478
+ const gArray = getTemporaryGArray(this.debugKey);
5479
+ gArray.push(polygonG);
5480
+ setTemporaryGArray(this.debugKey, gArray);
5481
+ return polygonG;
5482
+ }
5483
+ drawRectangle(board, data, options) {
5484
+ if (!isDebug(this.debugKey)) {
5485
+ return;
5486
+ }
5487
+ let rectangle;
5488
+ if (data instanceof Array) {
5489
+ rectangle = RectangleClient.getRectangleByPoints(data);
5490
+ }
5491
+ else {
5492
+ rectangle = data;
5493
+ }
5494
+ const rectangleG = PlaitBoard.getRoughSVG(board).rectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height, options || { stroke: 'red' });
5495
+ PlaitBoard.getElementActiveHost(board).append(rectangleG);
5496
+ const gArray = getTemporaryGArray(this.debugKey);
5497
+ gArray.push(rectangleG);
5498
+ setTemporaryGArray(this.debugKey, gArray);
5499
+ return rectangleG;
5500
+ }
5501
+ drawCircles(board, points, diameter = 0, isCumulativeDiameter = false, options) {
5502
+ if (!isDebug(this.debugKey)) {
5503
+ return;
5504
+ }
5505
+ const result = [];
5506
+ points.forEach((p, i) => {
5507
+ const circle = PlaitBoard.getRoughSVG(board).circle(p[0], p[1], isCumulativeDiameter ? diameter * (i + 1) : diameter, Object.assign({}, { stroke: 'red', fill: 'red', fillStyle: 'solid' }, options || {}));
5508
+ PlaitBoard.getElementActiveHost(board).append(circle);
5509
+ const gArray = getTemporaryGArray(this.debugKey);
5510
+ gArray.push(circle);
5511
+ result.push(circle);
5512
+ setTemporaryGArray(this.debugKey, gArray);
5513
+ });
5514
+ return result;
5515
+ }
5516
+ }
5517
+ const createDebugGenerator = (debugKey) => {
5518
+ return new DebugGenerator(debugKey);
5519
+ };
5520
+ const isDebug = (key) => {
5521
+ const defaultKey = 'debug:plait';
5522
+ return localStorage.getItem(key || defaultKey) === 'true';
5523
+ };
5524
+
5266
5525
  /*
5267
5526
  * Public API Surface of plait
5268
5527
  */
@@ -5271,5 +5530,5 @@ function createModModifierKeys() {
5271
5530
  * Generated bundle index. Do not edit.
5272
5531
  */
5273
5532
 
5274
- export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, H, HIT_DISTANCE_BUFFER, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitChildrenElementComponent, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitGroupElement, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addGroup, addSelectedElement, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, calcNewViewBox, canAddGroup, canRemoveGroup, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createFakeEvent, createForeignObject, createG, createGroup, createGroupRectangleG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionRectangleG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, findElements, getBoardRectangle, getClipboardData, getClipboardFromHtml, getDataTransferClipboard, getDataTransferClipboardText, getElementById, getElementHostBBox, getElementsInGroup, getElementsInGroupByElement, getEllipseTangentSlope, getGroupByElement, getHighestGroup, getHighestSelectedElements, getHighestSelectedGroup, getHighestSelectedGroups, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByElements, getRectangleByGroup, getSelectedElements, getSelectedGroups, getSelectedIsolatedElements, getTargetElements, getTemporaryElements, getTemporaryRef, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hasSelectedElementsInSameGroup, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isContextmenu, isDOMElement, isDOMNode, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedAllElementsInGroup, isSelectedElement, isSelectedElementOrGroup, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, nonGroupInHighestSelectedElements, normalizePoint, preventTouchMove, removeGroup, removeMovingElements, removeSelectedElement, rotate, scrollToRectangle, setAngleForG, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
5533
+ export { A, ACTIVE_MOVING_CLASS_NAME, ACTIVE_STROKE_WIDTH, ALT, APOSTROPHE, ATTACHED_ELEMENT_CLASS_NAME, AT_SIGN, B, BACKSLASH, BACKSPACE, BOARD_TO_AFTER_CHANGE, BOARD_TO_COMPONENT, BOARD_TO_ELEMENT_HOST, BOARD_TO_HOST, BOARD_TO_IS_SELECTION_MOVING, BOARD_TO_MOVING_ELEMENT, BOARD_TO_MOVING_POINT, BOARD_TO_MOVING_POINT_IN_BOARD, BOARD_TO_ON_CHANGE, BOARD_TO_ROUGH_SVG, BOARD_TO_SELECTED_ELEMENT, BOARD_TO_TEMPORARY_ELEMENTS, BOARD_TO_TOUCH_REF, BOARD_TO_VIEWPORT_ORIGINATION, BoardTransforms, C, CAPS_LOCK, CLOSE_SQUARE_BRACKET, COMMA, CONTEXT_MENU, CONTROL, ColorfulThemeColor, CoreTransforms, CursorClass, D, DASH, DELETE, DOWN_ARROW, DarkThemeColor, DebugGenerator, DefaultThemeColor, Direction, E, EIGHT, ELEMENT_TO_COMPONENT, END, ENTER, EQUALS, ESCAPE, F, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, FF_EQUALS, FF_MINUS, FF_MUTE, FF_SEMICOLON, FF_VOLUME_DOWN, FF_VOLUME_UP, FIRST_MEDIA, FIVE, FLUSHING, FOUR, G, GroupTransforms, H, HIT_DISTANCE_BUFFER, HOME, HOST_CLASS_NAME, I, INSERT, IS_APPLE, IS_BOARD_CACHE, IS_CHROME, IS_CHROME_LEGACY, IS_DRAGGING, IS_EDGE_LEGACY, IS_FIREFOX, IS_IOS, IS_MAC, IS_SAFARI, IS_TEXT_EDITABLE, J, K, L, LAST_MEDIA, LEFT_ARROW, M, MAC_ENTER, MAC_META, MAC_WK_CMD_LEFT, MAC_WK_CMD_RIGHT, MAX_RADIUS, MERGING, META, MUTE, N, NINE, NODE_TO_INDEX, NODE_TO_PARENT, NS, NUMPAD_DIVIDE, NUMPAD_EIGHT, NUMPAD_FIVE, NUMPAD_FOUR, NUMPAD_MINUS, NUMPAD_MULTIPLY, NUMPAD_NINE, NUMPAD_ONE, NUMPAD_PERIOD, NUMPAD_PLUS, NUMPAD_SEVEN, NUMPAD_SIX, NUMPAD_THREE, NUMPAD_TWO, NUMPAD_ZERO, NUM_CENTER, NUM_LOCK, O, ONE, OPEN_SQUARE_BRACKET, P, PAGE_DOWN, PAGE_UP, PATH_REFS, PAUSE, PERIOD, PLUS_SIGN, POINTER_BUTTON, PRESS_AND_MOVE_BUFFER, PRINT_SCREEN, Path, PlaitBoard, PlaitBoardComponent, PlaitChildrenElementComponent, PlaitContextService, PlaitElement, PlaitElementComponent, PlaitGroupElement, PlaitHistoryBoard, PlaitIslandBaseComponent, PlaitIslandPopoverBaseComponent, PlaitNode, PlaitOperation, PlaitPluginElementComponent, PlaitPluginKey, PlaitPointerType, Point, Q, QUESTION_MARK, R, RESIZE_CURSORS, RIGHT_ARROW, RectangleClient, ResizeCursorClass, RetroThemeColor, RgbaToHEX, S, SAVING, SCROLL_BAR_WIDTH, SCROLL_LOCK, SELECTION_BORDER_COLOR, SELECTION_FILL_COLOR, SELECTION_RECTANGLE_CLASS_NAME, SEMICOLON, SEVEN, SHIFT, SINGLE_QUOTE, SIX, SLASH, SPACE, Selection, SoftThemeColor, StarryThemeColor, T, TAB, THREE, TILDE, TWO, ThemeColorMode, ThemeColors, Transforms, U, UP_ARROW, V, VOLUME_DOWN, VOLUME_UP, Viewport, W, WritableClipboardType, X, Y, Z, ZERO, addClipboardContext, addSelectedElement, approximately, arrowPoints, buildPlaitHtml, cacheMovingElements, cacheSelectedElements, cacheSelectedElementsWithGroup, cacheSelectedElementsWithGroupOnShift, calcNewViewBox, canAddGroup, canRemoveGroup, catmullRomFitting, clampZoomLevel, clearNodeWeakMap, clearSelectedElement, clearSelectionMoving, clearViewportOrigination, createClipboardContext, createDebugGenerator, createFakeEvent, createForeignObject, createG, createGroup, createGroupRectangleG, createKeyboardEvent, createMask, createModModifierKeys, createMouseEvent, createPath, createPointerEvent, createRect, createSVG, createSelectionRectangleG, createTestingBoard, createText, createTouchEvent, debounce, deleteTemporaryElements, depthFirstRecursion, distanceBetweenPointAndPoint, distanceBetweenPointAndRectangle, distanceBetweenPointAndSegment, distanceBetweenPointAndSegments, downloadImage, drawArrow, drawBezierPath, drawCircle, drawLine, drawLinearPath, drawRectangle, drawRoundRectangle, fakeNodeWeakMap, filterSelectedGroups, findElements, getAllElementsInGroup, getBoardRectangle, getClipboardData, getClipboardFromHtml, getDataTransferClipboard, getDataTransferClipboardText, getElementById, getElementHostBBox, getElementsInGroup, getElementsInGroupByElement, getEllipseTangentSlope, getGroupByElement, getHighestGroup, getHighestSelectedElements, getHighestSelectedGroup, getHighestSelectedGroups, getHitElementByPoint, getHitElementsBySelection, getHitSelectedElements, getIsRecursionFunc, getMovingElements, getNearestPointBetweenPointAndSegment, getNearestPointBetweenPointAndSegments, getOffsetAfterRotate, getProbablySupportsClipboardRead, getProbablySupportsClipboardWrite, getProbablySupportsClipboardWriteText, getRealScrollBarWidth, getRectangleByElements, getRectangleByGroup, getRotatedBoundingRectangle, getSelectedElements, getSelectedGroups, getSelectedIsolatedElements, getSelectedIsolatedElementsCanAddToGroup, getSelectedTargetElements, getSelectionAngle, getTemporaryElements, getTemporaryRef, getVectorFromPointAndSlope, getViewBox, getViewBoxCenterPoint, getViewportContainerRect, getViewportOrigination, handleTouchTarget, hasBeforeContextChange, hasInputOrTextareaTarget, hasOnBoardChange, hasOnContextChanged, hasSameAngle, hasSelectedElementsInSameGroup, hasValidAngle, hotkeys, idCreator, initializeViewBox, initializeViewportContainer, initializeViewportOffset, inverse, isContextmenu, isDOMElement, isDOMNode, isDebug, isDragging, isFromScrolling, isFromViewportChange, isHandleSelection, isInPlaitBoard, isLineHitLine, isMainPointer, isMovingElements, isNullOrUndefined, isPointInEllipse, isPointInPolygon, isPointInRoundRectangle, isPolylineHitRectangle, isPreventTouchMove, isSecondaryPointer, isSelectedAllElementsInGroup, isSelectedElement, isSelectedElementOrGroup, isSelectionMoving, isSetSelectionOperation, isSetViewportOperation, nonGroupInHighestSelectedElements, normalizePoint, preventTouchMove, removeMovingElements, removeSelectedElement, rotate, rotateAntiPointsByElement, rotatePoints, rotatePointsByElement, rotatedDataPoints, scrollToRectangle, setAngleForG, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setDragging, setIsFromScrolling, setIsFromViewportChange, setPathStrokeLinecap, setSVGViewBox, setSelectedElementsWithGroup, setSelectionMoving, setStrokeLinecap, shouldClear, shouldMerge, shouldSave, stripHtml, temporaryDisableSelection, throttleRAF, toDomPrecision, toFixed, toHostPoint, toHostPointFromViewBoxPoint, toImage, toScreenPointFromHostPoint, toViewBoxPoint, toViewBoxPoints, uniqueById, updateForeignObject, updateForeignObjectWidth, updatePoints, updateViewportByScrolling, updateViewportContainerScroll, updateViewportOffset, updateViewportOrigination, withArrowMoving, withMoving, withOptions, withSelection };
5275
5534
  //# sourceMappingURL=plait-core.mjs.map