@istar-ts/core 0.15.0-beta.0 → 0.15.0-beta.1
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.
- package/dist/index.cjs +142 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +141 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -812,6 +812,146 @@ function actorBounds(model, actorId, metamodel = metamodelOf(model), defaultSize
|
|
|
812
812
|
height: maxY - minY
|
|
813
813
|
};
|
|
814
814
|
}
|
|
815
|
+
/** Space kept between a suggested box and any existing element (`suggestPosition`). */
|
|
816
|
+
const SUGGEST_MARGIN = 20;
|
|
817
|
+
/** Step between candidate positions on the grid `suggestPosition` scans. */
|
|
818
|
+
const SUGGEST_STEP = 40;
|
|
819
|
+
/** How many rings out `suggestPosition` scans before falling back to `guaranteedFreePoint`. */
|
|
820
|
+
const SUGGEST_MAX_RING = 25;
|
|
821
|
+
function boxesOverlap(a, b) {
|
|
822
|
+
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
823
|
+
}
|
|
824
|
+
function padded(box, margin) {
|
|
825
|
+
return {
|
|
826
|
+
x: box.x - margin,
|
|
827
|
+
y: box.y - margin,
|
|
828
|
+
width: box.width + margin * 2,
|
|
829
|
+
height: box.height + margin * 2
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function firstFreeCandidate(candidates, size, obstacles, fallback) {
|
|
833
|
+
for (const point of candidates) {
|
|
834
|
+
const box = {
|
|
835
|
+
x: point.x,
|
|
836
|
+
y: point.y,
|
|
837
|
+
width: size.width,
|
|
838
|
+
height: size.height
|
|
839
|
+
};
|
|
840
|
+
if (!obstacles.some((obstacle) => boxesOverlap(padded(box, 20), obstacle))) return point;
|
|
841
|
+
}
|
|
842
|
+
return fallback;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* A point guaranteed to overlap no obstacle, however dense the model: past the right edge of
|
|
846
|
+
* every obstacle's box, so no obstacle can reach it regardless of its y (`suggestPosition`'s
|
|
847
|
+
* last-resort fallback, once every ring candidate is taken).
|
|
848
|
+
*/
|
|
849
|
+
function guaranteedFreePoint(obstacles, referenceY) {
|
|
850
|
+
if (obstacles.length === 0) return {
|
|
851
|
+
x: 0,
|
|
852
|
+
y: referenceY
|
|
853
|
+
};
|
|
854
|
+
return {
|
|
855
|
+
x: Math.max(...obstacles.map((box) => box.x + box.width)) + 20,
|
|
856
|
+
y: referenceY
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
/** Candidate positions in expanding square rings around `center`, in a fixed scan order. */
|
|
860
|
+
function ringCandidates(center, maxRing) {
|
|
861
|
+
const points = [center];
|
|
862
|
+
for (let ring = 1; ring <= maxRing; ring++) {
|
|
863
|
+
const offset = ring * SUGGEST_STEP;
|
|
864
|
+
for (let dx = -offset; dx <= offset; dx += SUGGEST_STEP) {
|
|
865
|
+
points.push({
|
|
866
|
+
x: center.x + dx,
|
|
867
|
+
y: center.y - offset
|
|
868
|
+
});
|
|
869
|
+
points.push({
|
|
870
|
+
x: center.x + dx,
|
|
871
|
+
y: center.y + offset
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
for (let dy = -offset + SUGGEST_STEP; dy <= offset - SUGGEST_STEP; dy += SUGGEST_STEP) {
|
|
875
|
+
points.push({
|
|
876
|
+
x: center.x - offset,
|
|
877
|
+
y: center.y + dy
|
|
878
|
+
});
|
|
879
|
+
points.push({
|
|
880
|
+
x: center.x + offset,
|
|
881
|
+
y: center.y + dy
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return points;
|
|
886
|
+
}
|
|
887
|
+
function modelBoxes(model, defaultSizeOf) {
|
|
888
|
+
const boxes = [];
|
|
889
|
+
for (const element of model.elements.values()) {
|
|
890
|
+
const base = defaultSizeOf(element.kind);
|
|
891
|
+
const width = element.display?.width ?? base.width;
|
|
892
|
+
const height = element.display?.height ?? base.height;
|
|
893
|
+
boxes.push({
|
|
894
|
+
x: element.x,
|
|
895
|
+
y: element.y,
|
|
896
|
+
width,
|
|
897
|
+
height
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
return boxes;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* A free spot for a new element of `kind`, overlapping no existing element (sizes from the
|
|
904
|
+
* metamodel, or `defaultSizeOf` for a host with its own size overrides, as in `actorBounds`).
|
|
905
|
+
*
|
|
906
|
+
* Scans a deterministic, fixed-order set of candidates — rings around `near` when given, else
|
|
907
|
+
* the container's interior, else rings around the origin — and returns the first whose padded
|
|
908
|
+
* box (`SUGGEST_MARGIN`) overlaps nothing; same input always yields the same output. However
|
|
909
|
+
* dense the model, the result never overlaps: once every ring candidate is taken, it falls back
|
|
910
|
+
* to a point past the right edge of every obstacle (`guaranteedFreePoint`).
|
|
911
|
+
*
|
|
912
|
+
* Inside `containerId`, the candidate stays within the actor's current boundary (`actorBounds`)
|
|
913
|
+
* when there is room, or goes just past its last child when the boundary is full — the boundary
|
|
914
|
+
* grows from there, same as any other inner element (`actorBounds`).
|
|
915
|
+
*/
|
|
916
|
+
function suggestPosition(model, options) {
|
|
917
|
+
const metamodel = options.metamodel ?? metamodelOf(model);
|
|
918
|
+
const defaultSizeOf = options.defaultSizeOf ?? ((kind) => metamodelSizeOf(metamodel, kind));
|
|
919
|
+
const size = defaultSizeOf(options.kind);
|
|
920
|
+
const obstacles = modelBoxes(model, defaultSizeOf);
|
|
921
|
+
const near = typeof options.near === "string" ? model.elements.get(options.near) : options.near;
|
|
922
|
+
const nearPoint = near ? "kind" in near ? {
|
|
923
|
+
x: near.x,
|
|
924
|
+
y: near.y
|
|
925
|
+
} : near : void 0;
|
|
926
|
+
if (options.containerId) {
|
|
927
|
+
const bounds = actorBounds(model, options.containerId, metamodel, defaultSizeOf);
|
|
928
|
+
const candidates = ringCandidates(nearPoint ?? {
|
|
929
|
+
x: bounds.x + 10,
|
|
930
|
+
y: bounds.y + 10
|
|
931
|
+
}, SUGGEST_MAX_RING).filter((point) => point.x >= bounds.x + 10 && point.y >= bounds.y + 10 && point.x + size.width <= bounds.x + bounds.width - 10 && point.y + size.height <= bounds.y + bounds.height - 10);
|
|
932
|
+
if (candidates.length > 0) {
|
|
933
|
+
const sentinel = {
|
|
934
|
+
x: NaN,
|
|
935
|
+
y: NaN
|
|
936
|
+
};
|
|
937
|
+
const found = firstFreeCandidate(candidates, size, obstacles, sentinel);
|
|
938
|
+
if (found !== sentinel) return found;
|
|
939
|
+
}
|
|
940
|
+
const pastLastChild = {
|
|
941
|
+
x: bounds.x + bounds.width + 10,
|
|
942
|
+
y: bounds.y + 10
|
|
943
|
+
};
|
|
944
|
+
return firstFreeCandidate(ringCandidates(pastLastChild, SUGGEST_MAX_RING), size, obstacles, guaranteedFreePoint(obstacles, pastLastChild.y));
|
|
945
|
+
}
|
|
946
|
+
const start = nearPoint ?? (obstacles.length > 0 ? {
|
|
947
|
+
x: Math.max(...obstacles.map((box) => box.x + box.width)) + 20,
|
|
948
|
+
y: 0
|
|
949
|
+
} : {
|
|
950
|
+
x: 0,
|
|
951
|
+
y: 0
|
|
952
|
+
});
|
|
953
|
+
return firstFreeCandidate(ringCandidates(start, SUGGEST_MAX_RING), size, obstacles, guaranteedFreePoint(obstacles, start.y));
|
|
954
|
+
}
|
|
815
955
|
//#endregion
|
|
816
956
|
//#region src/constraints.ts
|
|
817
957
|
const OK = { ok: true };
|
|
@@ -3184,6 +3324,7 @@ exports.NODE_LINK_KINDS = NODE_LINK_KINDS;
|
|
|
3184
3324
|
exports.PISTAR_TOOL = PISTAR_TOOL;
|
|
3185
3325
|
exports.PistarParseError = PistarParseError;
|
|
3186
3326
|
exports.PistarWriteError = PistarWriteError;
|
|
3327
|
+
exports.SUGGEST_MARGIN = SUGGEST_MARGIN;
|
|
3187
3328
|
exports.actorBounds = actorBounds;
|
|
3188
3329
|
exports.addDependency = addDependency;
|
|
3189
3330
|
exports.addElement = addElement;
|
|
@@ -3262,6 +3403,7 @@ exports.removeLeg = removeLeg;
|
|
|
3262
3403
|
exports.setHostMetamodel = setHostMetamodel;
|
|
3263
3404
|
exports.shortKindName = shortKindName;
|
|
3264
3405
|
exports.string = string;
|
|
3406
|
+
exports.suggestPosition = suggestPosition;
|
|
3265
3407
|
exports.targetsLinks = targetsLinks;
|
|
3266
3408
|
exports.toPistar = toPistar;
|
|
3267
3409
|
exports.toPistarObject = toPistarObject;
|