kritzel-stencil 0.3.23 → 0.3.25
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/cjs/index.cjs.js +1 -1
- package/dist/cjs/kritzel-active-users_45.cjs.entry.js +55 -36
- package/dist/cjs/{schema.constants-Lr_zNkox.js → schema.constants-Np7j2Gz4.js} +44 -4
- package/dist/collection/classes/handlers/selection.handler.js +15 -2
- package/dist/collection/classes/managers/anchor.manager.js +13 -2
- package/dist/collection/classes/managers/clipboard.manager.js +7 -0
- package/dist/collection/classes/objects/selection-group.class.js +17 -0
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +46 -34
- package/dist/collection/constants/version.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/kritzel-controls.js +1 -1
- package/dist/components/kritzel-editor.js +1 -1
- package/dist/components/kritzel-engine.js +1 -1
- package/dist/components/kritzel-settings.js +1 -1
- package/dist/components/kritzel-tool-config.js +1 -1
- package/dist/components/p-6_95PFq4.js +9 -0
- package/dist/components/{p-CPMpf9ku.js → p-B5XGjTLd.js} +1 -1
- package/dist/components/{p-BYveczcb.js → p-DF0S4FqL.js} +1 -1
- package/dist/components/{p-DJ3KI725.js → p-DNDt6O6A.js} +1 -1
- package/dist/components/{p-DTqMF94b.js → p-Dw-t1qQc.js} +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/kritzel-active-users_45.entry.js +55 -36
- package/dist/esm/{schema.constants-DMPBDrZh.js → schema.constants-1SDhlkfS.js} +44 -4
- package/dist/stencil/index.esm.js +1 -1
- package/dist/stencil/{p-DMPBDrZh.js → p-1SDhlkfS.js} +1 -1
- package/dist/stencil/{p-27d41f6f.entry.js → p-7ba7df62.entry.js} +2 -2
- package/dist/stencil/stencil.esm.js +1 -1
- package/dist/types/classes/managers/anchor.manager.d.ts +4 -1
- package/dist/types/classes/objects/selection-group.class.d.ts +6 -0
- package/dist/types/constants/version.d.ts +1 -1
- package/package.json +1 -1
- package/dist/components/p-C48bv_0f.js +0 -9
package/dist/cjs/index.cjs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var index = require('./index-Xav9JFHg.js');
|
|
4
|
-
var schema_constants = require('./schema.constants-
|
|
4
|
+
var schema_constants = require('./schema.constants-Np7j2Gz4.js');
|
|
5
5
|
var Y = require('yjs');
|
|
6
6
|
require('y-indexeddb');
|
|
7
7
|
require('y-websocket');
|
|
@@ -24157,6 +24157,7 @@ class KritzelClipboardManager {
|
|
|
24157
24157
|
*/
|
|
24158
24158
|
regenerateIdsAndRemapAnchors(topLevel) {
|
|
24159
24159
|
const all = [];
|
|
24160
|
+
const hasOnlyTopLevelLines = topLevel.length > 0 && topLevel.every(object => schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelLine'));
|
|
24160
24161
|
const collect = (objects) => {
|
|
24161
24162
|
for (const object of objects) {
|
|
24162
24163
|
all.push(object);
|
|
@@ -24181,9 +24182,15 @@ class KritzelClipboardManager {
|
|
|
24181
24182
|
if (object.startAnchor && idMap.has(object.startAnchor.objectId)) {
|
|
24182
24183
|
object.startAnchor = { objectId: idMap.get(object.startAnchor.objectId) };
|
|
24183
24184
|
}
|
|
24185
|
+
else if (hasOnlyTopLevelLines) {
|
|
24186
|
+
object.startAnchor = undefined;
|
|
24187
|
+
}
|
|
24184
24188
|
if (object.endAnchor && idMap.has(object.endAnchor.objectId)) {
|
|
24185
24189
|
object.endAnchor = { objectId: idMap.get(object.endAnchor.objectId) };
|
|
24186
24190
|
}
|
|
24191
|
+
else if (hasOnlyTopLevelLines) {
|
|
24192
|
+
object.endAnchor = undefined;
|
|
24193
|
+
}
|
|
24187
24194
|
}
|
|
24188
24195
|
}
|
|
24189
24196
|
}
|
|
@@ -29412,7 +29419,11 @@ const KritzelEngine = class {
|
|
|
29412
29419
|
transform: object?.transformationMatrix,
|
|
29413
29420
|
transformOrigin: 'top left',
|
|
29414
29421
|
position: 'absolute',
|
|
29415
|
-
|
|
29422
|
+
// The object wrapper establishes its own stacking context (transform + z-index),
|
|
29423
|
+
// so the selection-line handles inside it cannot escape above the snap/anchor
|
|
29424
|
+
// layers (z-index 9999/9998) rendered later at the origin level. Lift the
|
|
29425
|
+
// selected line's wrapper above those layers so its handles always paint on top.
|
|
29426
|
+
zIndex: this.core.displaySelectionLineUI(object) ? '10001' : object.zIndex.toString(),
|
|
29416
29427
|
pointerEvents: getObjectWrapperPointerEvents(object),
|
|
29417
29428
|
} }, schema_constants.KritzelClassHelper.isInstanceOf(object, 'KritzelPath') && (index.h("svg", { ref: el => el && object.mount(el), xmlns: "http://www.w3.org/2000/svg", style: {
|
|
29418
29429
|
height: object?.totalHeight + 'px',
|
|
@@ -29616,10 +29627,19 @@ const KritzelEngine = class {
|
|
|
29616
29627
|
}
|
|
29617
29628
|
const selectionBorderColor = remoteUserColor ?? (schema_constants.KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme) || 'var(--kritzel-selection-border-color, #007AFF)');
|
|
29618
29629
|
const selectionHandleStrokeColor = remoteUserColor ?? 'var(--kritzel-selection-handle-stroke-color, #007AFF)';
|
|
29630
|
+
const lineObject = object;
|
|
29631
|
+
const activeSnapCandidate = this.core.store.state.snapCandidate;
|
|
29632
|
+
const isStartHandleSnapped = lineObject.startAnchor != null;
|
|
29633
|
+
const isEndHandleSnapped = lineObject.endAnchor != null;
|
|
29634
|
+
const isStartHandleSnapPreview = activeSnapCandidate?.endpoint === 'start';
|
|
29635
|
+
const isEndHandleSnapPreview = activeSnapCandidate?.endpoint === 'end';
|
|
29636
|
+
const isStartHandleHighlighted = isStartHandleSnapped || isStartHandleSnapPreview;
|
|
29637
|
+
const isEndHandleHighlighted = isEndHandleSnapped || isEndHandleSnapPreview;
|
|
29638
|
+
const selectionOverlayZIndex = this.core.displaySelectionLineUI(object) ? '10000' : (object.zIndex + 1).toString();
|
|
29619
29639
|
const handleScale = object.scale ?? 1;
|
|
29620
29640
|
const { canRenderResizeHandles, canRenderRotationHandle } = this.getSelectionHandleCapabilities(object);
|
|
29621
29641
|
return (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
|
|
29622
|
-
zIndex:
|
|
29642
|
+
zIndex: selectionOverlayZIndex,
|
|
29623
29643
|
height: object?.totalHeight.toString(),
|
|
29624
29644
|
width: object?.totalWidth.toString(),
|
|
29625
29645
|
left: '0',
|
|
@@ -29710,7 +29730,7 @@ const KritzelEngine = class {
|
|
|
29710
29730
|
strokeLinecap: 'round',
|
|
29711
29731
|
fill: 'none',
|
|
29712
29732
|
} })), !this.isSelecting && !isRemoteSelection && (index.h("g", { class: "selection-line-handles", style: { pointerEvents: 'auto' } }, index.h("circle", { class: "selection-line-handle start", cx: object.startX - object.x, cy: object.startY - object.y, r: `${((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, style: {
|
|
29713
|
-
fill: 'var(--kritzel-selection-handle-color, #000000)',
|
|
29733
|
+
fill: isStartHandleHighlighted ? selectionHandleStrokeColor : 'var(--kritzel-selection-handle-color, #000000)',
|
|
29714
29734
|
stroke: selectionHandleStrokeColor,
|
|
29715
29735
|
strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
|
|
29716
29736
|
paintOrder: 'fill',
|
|
@@ -29734,7 +29754,7 @@ const KritzelEngine = class {
|
|
|
29734
29754
|
fill: 'transparent',
|
|
29735
29755
|
paintOrder: 'fill',
|
|
29736
29756
|
} }), index.h("circle", { class: "selection-line-handle end", cx: object.endX - object.x, cy: object.endY - object.y, r: `${((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, style: {
|
|
29737
|
-
fill: 'var(--kritzel-selection-handle-color, #000000)',
|
|
29757
|
+
fill: isEndHandleHighlighted ? selectionHandleStrokeColor : 'var(--kritzel-selection-handle-color, #000000)',
|
|
29738
29758
|
stroke: selectionHandleStrokeColor,
|
|
29739
29759
|
strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
|
|
29740
29760
|
paintOrder: 'fill',
|
|
@@ -29744,6 +29764,34 @@ const KritzelEngine = class {
|
|
|
29744
29764
|
} })))))));
|
|
29745
29765
|
})()));
|
|
29746
29766
|
}), (() => {
|
|
29767
|
+
const data = this.core.anchorManager.getSnapIndicatorRenderData();
|
|
29768
|
+
if (!data)
|
|
29769
|
+
return null;
|
|
29770
|
+
return (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", class: "snap-indicator", style: {
|
|
29771
|
+
position: 'absolute',
|
|
29772
|
+
left: '0',
|
|
29773
|
+
top: '0',
|
|
29774
|
+
width: '1px',
|
|
29775
|
+
height: '1px',
|
|
29776
|
+
pointerEvents: 'none',
|
|
29777
|
+
zIndex: '9999',
|
|
29778
|
+
overflow: 'visible',
|
|
29779
|
+
} }, index.h("g", null, data.snapLinePath ? (index.h("path", { d: data.snapLinePath, fill: "none", style: {
|
|
29780
|
+
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
29781
|
+
strokeWidth: data.lineStrokeWidth,
|
|
29782
|
+
strokeDasharray: data.dashArray,
|
|
29783
|
+
strokeLinecap: 'round',
|
|
29784
|
+
} })) : (data.edgeX !== undefined &&
|
|
29785
|
+
data.edgeY !== undefined && (index.h("line", { x1: data.edgeX, y1: data.edgeY, x2: data.centerX, y2: data.centerY, style: {
|
|
29786
|
+
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
29787
|
+
strokeWidth: data.lineStrokeWidth,
|
|
29788
|
+
strokeDasharray: data.dashArray,
|
|
29789
|
+
strokeLinecap: 'round',
|
|
29790
|
+
} }))), index.h("circle", { cx: data.centerX, cy: data.centerY, r: data.indicatorRadius, style: {
|
|
29791
|
+
fill: 'var(--kritzel-snap-indicator-fill, rgba(59, 130, 246, 0.3))',
|
|
29792
|
+
strokeWidth: data.indicatorStrokeWidth,
|
|
29793
|
+
} }))));
|
|
29794
|
+
})(), (() => {
|
|
29747
29795
|
const data = this.core.anchorManager.getAnchorLinesRenderData();
|
|
29748
29796
|
if (!data)
|
|
29749
29797
|
return null;
|
|
@@ -29768,7 +29816,7 @@ const KritzelEngine = class {
|
|
|
29768
29816
|
strokeDasharray: data.dashArray,
|
|
29769
29817
|
strokeLinecap: 'round',
|
|
29770
29818
|
} })), index.h("circle", { cx: data.startAnchorViz.centerX, cy: data.startAnchorViz.centerY, r: data.indicatorRadius, style: {
|
|
29771
|
-
fill: 'var(--kritzel-snap-indicator-fill, rgba(
|
|
29819
|
+
fill: 'var(--kritzel-snap-indicator-fill, rgba(59, 130, 246, 0.3))',
|
|
29772
29820
|
stroke: 'var(--kritzel-snap-indicator-stroke, #007bff)',
|
|
29773
29821
|
strokeWidth: data.indicatorStrokeWidth,
|
|
29774
29822
|
} }))), data.endAnchorViz && (index.h("g", { class: "anchor-line-end" }, data.endAnchorViz.pathD ? (index.h("path", { d: data.endAnchorViz.pathD, style: {
|
|
@@ -29787,35 +29835,6 @@ const KritzelEngine = class {
|
|
|
29787
29835
|
stroke: 'var(--kritzel-snap-indicator-stroke, #007bff)',
|
|
29788
29836
|
strokeWidth: data.indicatorStrokeWidth,
|
|
29789
29837
|
} })))));
|
|
29790
|
-
})(), (() => {
|
|
29791
|
-
const data = this.core.anchorManager.getSnapIndicatorRenderData();
|
|
29792
|
-
if (!data)
|
|
29793
|
-
return null;
|
|
29794
|
-
return (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", class: "snap-indicator", style: {
|
|
29795
|
-
position: 'absolute',
|
|
29796
|
-
left: '0',
|
|
29797
|
-
top: '0',
|
|
29798
|
-
width: '1px',
|
|
29799
|
-
height: '1px',
|
|
29800
|
-
pointerEvents: 'none',
|
|
29801
|
-
zIndex: '9999',
|
|
29802
|
-
overflow: 'visible',
|
|
29803
|
-
} }, index.h("g", null, data.snapLinePath ? (index.h("path", { d: data.snapLinePath, fill: "none", style: {
|
|
29804
|
-
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
29805
|
-
strokeWidth: data.lineStrokeWidth,
|
|
29806
|
-
strokeDasharray: data.dashArray,
|
|
29807
|
-
strokeLinecap: 'round',
|
|
29808
|
-
} })) : (data.edgeX !== undefined &&
|
|
29809
|
-
data.edgeY !== undefined && (index.h("line", { x1: data.edgeX, y1: data.edgeY, x2: data.centerX, y2: data.centerY, style: {
|
|
29810
|
-
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
29811
|
-
strokeWidth: data.lineStrokeWidth,
|
|
29812
|
-
strokeDasharray: data.dashArray,
|
|
29813
|
-
strokeLinecap: 'round',
|
|
29814
|
-
} }))), index.h("circle", { cx: data.centerX, cy: data.centerY, r: data.indicatorRadius, style: {
|
|
29815
|
-
fill: 'var(--kritzel-snap-indicator-fill, rgba(59, 130, 246, 0.3))',
|
|
29816
|
-
stroke: 'var(--kritzel-snap-indicator-stroke, #007bff)',
|
|
29817
|
-
strokeWidth: data.indicatorStrokeWidth,
|
|
29818
|
-
} }))));
|
|
29819
29838
|
})()), this.core.store.state.isContextMenuVisible && (index.h("kritzel-context-menu", { class: "context-menu", ref: el => (this.contextMenuElement = el ?? null), items: this.core.store.state.contextMenuItems, objects: this.core.store.selectionGroup?.objects || [], style: {
|
|
29820
29839
|
position: 'absolute',
|
|
29821
29840
|
left: `${this.core.store.state.contextMenuX}px`,
|
|
@@ -31227,7 +31246,7 @@ const KritzelPortal = class {
|
|
|
31227
31246
|
* This file is auto-generated by the version bump scripts.
|
|
31228
31247
|
* Do not modify manually.
|
|
31229
31248
|
*/
|
|
31230
|
-
const KRITZEL_VERSION = '0.3.
|
|
31249
|
+
const KRITZEL_VERSION = '0.3.25';
|
|
31231
31250
|
|
|
31232
31251
|
const kritzelSettingsCss = () => `:host{display:contents}kritzel-dialog{--kritzel-dialog-body-padding:0;--kritzel-dialog-width-large:800px;--kritzel-dialog-height-large:500px}.footer-button{padding:8px 16px;border-radius:6px;cursor:pointer;font-size:14px}.cancel-button{border:1px solid #ebebeb;background:#fff;color:inherit}.cancel-button:hover{background:#f5f5f5}.settings-content{padding:0}.settings-content h3{margin:0 0 16px 0;font-size:18px;font-weight:600;color:var(--kritzel-settings-content-heading-color, #333333)}.settings-content p{margin:0;font-size:14px;color:var(--kritzel-settings-content-text-color, #666666);line-height:1.5}.settings-group{display:flex;flex-direction:column;gap:24px}.settings-item{display:flex;flex-direction:column;gap:8px}.settings-row{display:flex;align-items:center;justify-content:space-between;gap:16px}.settings-label{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.settings-description{font-size:12px;color:var(--kritzel-settings-description-color, #888888);margin:0;line-height:1.4}.shortcuts-list{display:flex;flex-direction:column;gap:24px}.shortcuts-category{display:flex;flex-direction:column;gap:8px}.shortcuts-category-title{font-size:14px;font-weight:600;color:var(--kritzel-settings-label-color, #333333);margin:0 0 4px 0}.shortcuts-group{display:flex;flex-direction:column;gap:4px}.shortcut-item{display:flex;justify-content:space-between;align-items:center;padding:6px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-item-bg, rgba(0, 0, 0, 0.02))}.shortcut-label{font-size:14px;color:var(--kritzel-settings-content-text-color, #666666)}.shortcut-key{font-family:monospace;font-size:12px;padding:2px 8px;border-radius:4px;background:var(--kritzel-settings-shortcut-key-bg, #f0f0f0);color:var(--kritzel-settings-shortcut-key-color, #333333);border:1px solid var(--kritzel-settings-shortcut-key-border, #ddd)}`;
|
|
31233
31252
|
|
|
@@ -20863,11 +20863,27 @@ class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
20863
20863
|
/**
|
|
20864
20864
|
* Sets the isSelected flag to false for all objects in the selection group.
|
|
20865
20865
|
* Used when clearing or changing the selection state.
|
|
20866
|
+
*
|
|
20867
|
+
* For KritzelGroup members, also clears the isSelected flag on all nested
|
|
20868
|
+
* descendants. A descendant can retain a lingering isSelected from the marquee
|
|
20869
|
+
* preview before it was resolved to its parent group during selection, which
|
|
20870
|
+
* would otherwise leave that object selected after the group is deselected and
|
|
20871
|
+
* cause the whole group to be re-selected on the next interaction.
|
|
20866
20872
|
*/
|
|
20867
20873
|
deselectAllChildren() {
|
|
20868
20874
|
const children = this.objects;
|
|
20875
|
+
const descendantIds = new Set();
|
|
20869
20876
|
for (const obj of children) {
|
|
20870
20877
|
obj.isSelected = false;
|
|
20878
|
+
if (KritzelClassHelper.isInstanceOf(obj, 'KritzelGroup')) {
|
|
20879
|
+
KritzelGroup.collectDescendantIds(obj, descendantIds);
|
|
20880
|
+
}
|
|
20881
|
+
}
|
|
20882
|
+
if (descendantIds.size > 0) {
|
|
20883
|
+
const descendants = this._core.store.objects.filter(o => descendantIds.has(o.id));
|
|
20884
|
+
for (const descendant of descendants) {
|
|
20885
|
+
descendant.isSelected = false;
|
|
20886
|
+
}
|
|
20871
20887
|
}
|
|
20872
20888
|
}
|
|
20873
20889
|
/**
|
|
@@ -23640,6 +23656,11 @@ class KritzelSelectionHandler extends KritzelBaseHandler {
|
|
|
23640
23656
|
// Resolve child objects to their parent groups
|
|
23641
23657
|
const parentGroup = KritzelGroup.findParentGroup(this._core, object.id);
|
|
23642
23658
|
const objectToSelect = parentGroup || object;
|
|
23659
|
+
// When the click resolves to a parent group, clear the child's own flag so
|
|
23660
|
+
// it does not linger as selected and re-select the group on the next interaction.
|
|
23661
|
+
if (parentGroup) {
|
|
23662
|
+
object.isSelected = false;
|
|
23663
|
+
}
|
|
23643
23664
|
const existingGroup = this._core.store.selectionGroup;
|
|
23644
23665
|
if (shiftKey && existingGroup) {
|
|
23645
23666
|
existingGroup.addOrRemove(objectToSelect);
|
|
@@ -23684,8 +23705,16 @@ class KritzelSelectionHandler extends KritzelBaseHandler {
|
|
|
23684
23705
|
const resolvedObjects = new Map();
|
|
23685
23706
|
for (const obj of selectedObjects) {
|
|
23686
23707
|
const parentGroup = childToParentMap.get(obj.id);
|
|
23687
|
-
|
|
23688
|
-
|
|
23708
|
+
if (parentGroup) {
|
|
23709
|
+
// The child was selected (e.g. via marquee preview) but selection is
|
|
23710
|
+
// represented by its parent group. Clear the child's flag so it does
|
|
23711
|
+
// not linger as selected and re-select the group on the next interaction.
|
|
23712
|
+
obj.isSelected = false;
|
|
23713
|
+
resolvedObjects.set(parentGroup.id, parentGroup);
|
|
23714
|
+
}
|
|
23715
|
+
else {
|
|
23716
|
+
resolvedObjects.set(obj.id, obj);
|
|
23717
|
+
}
|
|
23689
23718
|
}
|
|
23690
23719
|
const unrolledObjects = Array.from(resolvedObjects.values());
|
|
23691
23720
|
const filteredObjects = this.filterDescendantsOfSelectedGroups(unrolledObjects);
|
|
@@ -27939,7 +27968,10 @@ class KritzelAnchorManager {
|
|
|
27939
27968
|
// Snap Detection
|
|
27940
27969
|
// ============================================
|
|
27941
27970
|
/**
|
|
27942
|
-
* Finds
|
|
27971
|
+
* Finds an object that can be snapped to when the cursor is sufficiently close to its center.
|
|
27972
|
+
* Snap is only triggered when the cursor is inside the object's bounds AND within 1/5 of the
|
|
27973
|
+
* shorter side from the center, so snapping activates only as the user approaches the center
|
|
27974
|
+
* and releases quickly as they move away.
|
|
27943
27975
|
* Returns null if no suitable snap target is found.
|
|
27944
27976
|
*
|
|
27945
27977
|
* @param worldX - X coordinate in world space
|
|
@@ -27968,7 +28000,15 @@ class KritzelAnchorManager {
|
|
|
27968
28000
|
const polygon = object.rotatedPolygon;
|
|
27969
28001
|
const points = [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
|
|
27970
28002
|
if (KritzelGeometryHelper.isPointInPolygon({ x: worldX, y: worldY }, points)) {
|
|
27971
|
-
//
|
|
28003
|
+
// Only snap when the cursor is near the object's center (within 1/5 of the shorter side)
|
|
28004
|
+
const snapRadius = Math.min(object.width, object.height) / 5;
|
|
28005
|
+
const dx = worldX - object.centerX;
|
|
28006
|
+
const dy = worldY - object.centerY;
|
|
28007
|
+
const distanceToCenter = Math.sqrt(dx * dx + dy * dy);
|
|
28008
|
+
if (distanceToCenter > snapRadius) {
|
|
28009
|
+
continue;
|
|
28010
|
+
}
|
|
28011
|
+
// If inside and close enough to center, check if this object is "above" the current best target
|
|
27972
28012
|
if (object.zIndex > highestZIndex) {
|
|
27973
28013
|
highestZIndex = object.zIndex;
|
|
27974
28014
|
bestTarget = {
|
|
@@ -404,6 +404,11 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
|
|
|
404
404
|
// Resolve child objects to their parent groups
|
|
405
405
|
const parentGroup = KritzelGroup.findParentGroup(this._core, object.id);
|
|
406
406
|
const objectToSelect = parentGroup || object;
|
|
407
|
+
// When the click resolves to a parent group, clear the child's own flag so
|
|
408
|
+
// it does not linger as selected and re-select the group on the next interaction.
|
|
409
|
+
if (parentGroup) {
|
|
410
|
+
object.isSelected = false;
|
|
411
|
+
}
|
|
407
412
|
const existingGroup = this._core.store.selectionGroup;
|
|
408
413
|
if (shiftKey && existingGroup) {
|
|
409
414
|
existingGroup.addOrRemove(objectToSelect);
|
|
@@ -448,8 +453,16 @@ export class KritzelSelectionHandler extends KritzelBaseHandler {
|
|
|
448
453
|
const resolvedObjects = new Map();
|
|
449
454
|
for (const obj of selectedObjects) {
|
|
450
455
|
const parentGroup = childToParentMap.get(obj.id);
|
|
451
|
-
|
|
452
|
-
|
|
456
|
+
if (parentGroup) {
|
|
457
|
+
// The child was selected (e.g. via marquee preview) but selection is
|
|
458
|
+
// represented by its parent group. Clear the child's flag so it does
|
|
459
|
+
// not linger as selected and re-select the group on the next interaction.
|
|
460
|
+
obj.isSelected = false;
|
|
461
|
+
resolvedObjects.set(parentGroup.id, parentGroup);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
resolvedObjects.set(obj.id, obj);
|
|
465
|
+
}
|
|
453
466
|
}
|
|
454
467
|
const unrolledObjects = Array.from(resolvedObjects.values());
|
|
455
468
|
const filteredObjects = this.filterDescendantsOfSelectedGroups(unrolledObjects);
|
|
@@ -199,7 +199,10 @@ export class KritzelAnchorManager {
|
|
|
199
199
|
// Snap Detection
|
|
200
200
|
// ============================================
|
|
201
201
|
/**
|
|
202
|
-
* Finds
|
|
202
|
+
* Finds an object that can be snapped to when the cursor is sufficiently close to its center.
|
|
203
|
+
* Snap is only triggered when the cursor is inside the object's bounds AND within 1/5 of the
|
|
204
|
+
* shorter side from the center, so snapping activates only as the user approaches the center
|
|
205
|
+
* and releases quickly as they move away.
|
|
203
206
|
* Returns null if no suitable snap target is found.
|
|
204
207
|
*
|
|
205
208
|
* @param worldX - X coordinate in world space
|
|
@@ -228,7 +231,15 @@ export class KritzelAnchorManager {
|
|
|
228
231
|
const polygon = object.rotatedPolygon;
|
|
229
232
|
const points = [polygon.topLeft, polygon.topRight, polygon.bottomRight, polygon.bottomLeft];
|
|
230
233
|
if (KritzelGeometryHelper.isPointInPolygon({ x: worldX, y: worldY }, points)) {
|
|
231
|
-
//
|
|
234
|
+
// Only snap when the cursor is near the object's center (within 1/5 of the shorter side)
|
|
235
|
+
const snapRadius = Math.min(object.width, object.height) / 5;
|
|
236
|
+
const dx = worldX - object.centerX;
|
|
237
|
+
const dy = worldY - object.centerY;
|
|
238
|
+
const distanceToCenter = Math.sqrt(dx * dx + dy * dy);
|
|
239
|
+
if (distanceToCenter > snapRadius) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
// If inside and close enough to center, check if this object is "above" the current best target
|
|
232
243
|
if (object.zIndex > highestZIndex) {
|
|
233
244
|
highestZIndex = object.zIndex;
|
|
234
245
|
bestTarget = {
|
|
@@ -103,6 +103,7 @@ export class KritzelClipboardManager {
|
|
|
103
103
|
*/
|
|
104
104
|
regenerateIdsAndRemapAnchors(topLevel) {
|
|
105
105
|
const all = [];
|
|
106
|
+
const hasOnlyTopLevelLines = topLevel.length > 0 && topLevel.every(object => KritzelClassHelper.isInstanceOf(object, 'KritzelLine'));
|
|
106
107
|
const collect = (objects) => {
|
|
107
108
|
for (const object of objects) {
|
|
108
109
|
all.push(object);
|
|
@@ -127,9 +128,15 @@ export class KritzelClipboardManager {
|
|
|
127
128
|
if (object.startAnchor && idMap.has(object.startAnchor.objectId)) {
|
|
128
129
|
object.startAnchor = { objectId: idMap.get(object.startAnchor.objectId) };
|
|
129
130
|
}
|
|
131
|
+
else if (hasOnlyTopLevelLines) {
|
|
132
|
+
object.startAnchor = undefined;
|
|
133
|
+
}
|
|
130
134
|
if (object.endAnchor && idMap.has(object.endAnchor.objectId)) {
|
|
131
135
|
object.endAnchor = { objectId: idMap.get(object.endAnchor.objectId) };
|
|
132
136
|
}
|
|
137
|
+
else if (hasOnlyTopLevelLines) {
|
|
138
|
+
object.endAnchor = undefined;
|
|
139
|
+
}
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { KritzelGeometryHelper } from "../../helpers/geometry.helper";
|
|
2
2
|
import { KritzelBaseObject } from "./base-object.class";
|
|
3
|
+
import { KritzelGroup } from "./group.class";
|
|
3
4
|
import { KritzelClassHelper } from "../../helpers/class.helper";
|
|
4
5
|
import { KritzelMathHelper } from "../../helpers/math.helper";
|
|
5
6
|
/**
|
|
@@ -197,11 +198,27 @@ export class KritzelSelectionGroup extends KritzelBaseObject {
|
|
|
197
198
|
/**
|
|
198
199
|
* Sets the isSelected flag to false for all objects in the selection group.
|
|
199
200
|
* Used when clearing or changing the selection state.
|
|
201
|
+
*
|
|
202
|
+
* For KritzelGroup members, also clears the isSelected flag on all nested
|
|
203
|
+
* descendants. A descendant can retain a lingering isSelected from the marquee
|
|
204
|
+
* preview before it was resolved to its parent group during selection, which
|
|
205
|
+
* would otherwise leave that object selected after the group is deselected and
|
|
206
|
+
* cause the whole group to be re-selected on the next interaction.
|
|
200
207
|
*/
|
|
201
208
|
deselectAllChildren() {
|
|
202
209
|
const children = this.objects;
|
|
210
|
+
const descendantIds = new Set();
|
|
203
211
|
for (const obj of children) {
|
|
204
212
|
obj.isSelected = false;
|
|
213
|
+
if (KritzelClassHelper.isInstanceOf(obj, 'KritzelGroup')) {
|
|
214
|
+
KritzelGroup.collectDescendantIds(obj, descendantIds);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (descendantIds.size > 0) {
|
|
218
|
+
const descendants = this._core.store.objects.filter(o => descendantIds.has(o.id));
|
|
219
|
+
for (const descendant of descendants) {
|
|
220
|
+
descendant.isSelected = false;
|
|
221
|
+
}
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
/**
|
|
@@ -2155,7 +2155,11 @@ export class KritzelEngine {
|
|
|
2155
2155
|
transform: object?.transformationMatrix,
|
|
2156
2156
|
transformOrigin: 'top left',
|
|
2157
2157
|
position: 'absolute',
|
|
2158
|
-
|
|
2158
|
+
// The object wrapper establishes its own stacking context (transform + z-index),
|
|
2159
|
+
// so the selection-line handles inside it cannot escape above the snap/anchor
|
|
2160
|
+
// layers (z-index 9999/9998) rendered later at the origin level. Lift the
|
|
2161
|
+
// selected line's wrapper above those layers so its handles always paint on top.
|
|
2162
|
+
zIndex: this.core.displaySelectionLineUI(object) ? '10001' : object.zIndex.toString(),
|
|
2159
2163
|
pointerEvents: getObjectWrapperPointerEvents(object),
|
|
2160
2164
|
} }, KritzelClassHelper.isInstanceOf(object, 'KritzelPath') && (h("svg", { ref: el => el && object.mount(el), xmlns: "http://www.w3.org/2000/svg", style: {
|
|
2161
2165
|
height: object?.totalHeight + 'px',
|
|
@@ -2359,10 +2363,19 @@ export class KritzelEngine {
|
|
|
2359
2363
|
}
|
|
2360
2364
|
const selectionBorderColor = remoteUserColor ?? (KritzelColorHelper.resolveThemeColor(object.borderColor, currentTheme) || 'var(--kritzel-selection-border-color, #007AFF)');
|
|
2361
2365
|
const selectionHandleStrokeColor = remoteUserColor ?? 'var(--kritzel-selection-handle-stroke-color, #007AFF)';
|
|
2366
|
+
const lineObject = object;
|
|
2367
|
+
const activeSnapCandidate = this.core.store.state.snapCandidate;
|
|
2368
|
+
const isStartHandleSnapped = lineObject.startAnchor != null;
|
|
2369
|
+
const isEndHandleSnapped = lineObject.endAnchor != null;
|
|
2370
|
+
const isStartHandleSnapPreview = activeSnapCandidate?.endpoint === 'start';
|
|
2371
|
+
const isEndHandleSnapPreview = activeSnapCandidate?.endpoint === 'end';
|
|
2372
|
+
const isStartHandleHighlighted = isStartHandleSnapped || isStartHandleSnapPreview;
|
|
2373
|
+
const isEndHandleHighlighted = isEndHandleSnapped || isEndHandleSnapPreview;
|
|
2374
|
+
const selectionOverlayZIndex = this.core.displaySelectionLineUI(object) ? '10000' : (object.zIndex + 1).toString();
|
|
2362
2375
|
const handleScale = object.scale ?? 1;
|
|
2363
2376
|
const { canRenderResizeHandles, canRenderRotationHandle } = this.getSelectionHandleCapabilities(object);
|
|
2364
2377
|
return (h("svg", { xmlns: "http://www.w3.org/2000/svg", style: {
|
|
2365
|
-
zIndex:
|
|
2378
|
+
zIndex: selectionOverlayZIndex,
|
|
2366
2379
|
height: object?.totalHeight.toString(),
|
|
2367
2380
|
width: object?.totalWidth.toString(),
|
|
2368
2381
|
left: '0',
|
|
@@ -2453,7 +2466,7 @@ export class KritzelEngine {
|
|
|
2453
2466
|
strokeLinecap: 'round',
|
|
2454
2467
|
fill: 'none',
|
|
2455
2468
|
} })), !this.isSelecting && !isRemoteSelection && (h("g", { class: "selection-line-handles", style: { pointerEvents: 'auto' } }, h("circle", { class: "selection-line-handle start", cx: object.startX - object.x, cy: object.startY - object.y, r: `${((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, style: {
|
|
2456
|
-
fill: 'var(--kritzel-selection-handle-color, #000000)',
|
|
2469
|
+
fill: isStartHandleHighlighted ? selectionHandleStrokeColor : 'var(--kritzel-selection-handle-color, #000000)',
|
|
2457
2470
|
stroke: selectionHandleStrokeColor,
|
|
2458
2471
|
strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
|
|
2459
2472
|
paintOrder: 'fill',
|
|
@@ -2477,7 +2490,7 @@ export class KritzelEngine {
|
|
|
2477
2490
|
fill: 'transparent',
|
|
2478
2491
|
paintOrder: 'fill',
|
|
2479
2492
|
} }), h("circle", { class: "selection-line-handle end", cx: object.endX - object.x, cy: object.endY - object.y, r: `${((baseHandleSize - 1) * object.scale) / this.core.store.state?.scale}`, style: {
|
|
2480
|
-
fill: 'var(--kritzel-selection-handle-color, #000000)',
|
|
2493
|
+
fill: isEndHandleHighlighted ? selectionHandleStrokeColor : 'var(--kritzel-selection-handle-color, #000000)',
|
|
2481
2494
|
stroke: selectionHandleStrokeColor,
|
|
2482
2495
|
strokeWidth: `calc(2px * ${object.scale} / ${this.core.store.state?.scale})`,
|
|
2483
2496
|
paintOrder: 'fill',
|
|
@@ -2487,6 +2500,34 @@ export class KritzelEngine {
|
|
|
2487
2500
|
} })))))));
|
|
2488
2501
|
})()));
|
|
2489
2502
|
}), (() => {
|
|
2503
|
+
const data = this.core.anchorManager.getSnapIndicatorRenderData();
|
|
2504
|
+
if (!data)
|
|
2505
|
+
return null;
|
|
2506
|
+
return (h("svg", { xmlns: "http://www.w3.org/2000/svg", class: "snap-indicator", style: {
|
|
2507
|
+
position: 'absolute',
|
|
2508
|
+
left: '0',
|
|
2509
|
+
top: '0',
|
|
2510
|
+
width: '1px',
|
|
2511
|
+
height: '1px',
|
|
2512
|
+
pointerEvents: 'none',
|
|
2513
|
+
zIndex: '9999',
|
|
2514
|
+
overflow: 'visible',
|
|
2515
|
+
} }, h("g", null, data.snapLinePath ? (h("path", { d: data.snapLinePath, fill: "none", style: {
|
|
2516
|
+
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
2517
|
+
strokeWidth: data.lineStrokeWidth,
|
|
2518
|
+
strokeDasharray: data.dashArray,
|
|
2519
|
+
strokeLinecap: 'round',
|
|
2520
|
+
} })) : (data.edgeX !== undefined &&
|
|
2521
|
+
data.edgeY !== undefined && (h("line", { x1: data.edgeX, y1: data.edgeY, x2: data.centerX, y2: data.centerY, style: {
|
|
2522
|
+
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
2523
|
+
strokeWidth: data.lineStrokeWidth,
|
|
2524
|
+
strokeDasharray: data.dashArray,
|
|
2525
|
+
strokeLinecap: 'round',
|
|
2526
|
+
} }))), h("circle", { cx: data.centerX, cy: data.centerY, r: data.indicatorRadius, style: {
|
|
2527
|
+
fill: 'var(--kritzel-snap-indicator-fill, rgba(59, 130, 246, 0.3))',
|
|
2528
|
+
strokeWidth: data.indicatorStrokeWidth,
|
|
2529
|
+
} }))));
|
|
2530
|
+
})(), (() => {
|
|
2490
2531
|
const data = this.core.anchorManager.getAnchorLinesRenderData();
|
|
2491
2532
|
if (!data)
|
|
2492
2533
|
return null;
|
|
@@ -2511,7 +2552,7 @@ export class KritzelEngine {
|
|
|
2511
2552
|
strokeDasharray: data.dashArray,
|
|
2512
2553
|
strokeLinecap: 'round',
|
|
2513
2554
|
} })), h("circle", { cx: data.startAnchorViz.centerX, cy: data.startAnchorViz.centerY, r: data.indicatorRadius, style: {
|
|
2514
|
-
fill: 'var(--kritzel-snap-indicator-fill, rgba(
|
|
2555
|
+
fill: 'var(--kritzel-snap-indicator-fill, rgba(59, 130, 246, 0.3))',
|
|
2515
2556
|
stroke: 'var(--kritzel-snap-indicator-stroke, #007bff)',
|
|
2516
2557
|
strokeWidth: data.indicatorStrokeWidth,
|
|
2517
2558
|
} }))), data.endAnchorViz && (h("g", { class: "anchor-line-end" }, data.endAnchorViz.pathD ? (h("path", { d: data.endAnchorViz.pathD, style: {
|
|
@@ -2530,35 +2571,6 @@ export class KritzelEngine {
|
|
|
2530
2571
|
stroke: 'var(--kritzel-snap-indicator-stroke, #007bff)',
|
|
2531
2572
|
strokeWidth: data.indicatorStrokeWidth,
|
|
2532
2573
|
} })))));
|
|
2533
|
-
})(), (() => {
|
|
2534
|
-
const data = this.core.anchorManager.getSnapIndicatorRenderData();
|
|
2535
|
-
if (!data)
|
|
2536
|
-
return null;
|
|
2537
|
-
return (h("svg", { xmlns: "http://www.w3.org/2000/svg", class: "snap-indicator", style: {
|
|
2538
|
-
position: 'absolute',
|
|
2539
|
-
left: '0',
|
|
2540
|
-
top: '0',
|
|
2541
|
-
width: '1px',
|
|
2542
|
-
height: '1px',
|
|
2543
|
-
pointerEvents: 'none',
|
|
2544
|
-
zIndex: '9999',
|
|
2545
|
-
overflow: 'visible',
|
|
2546
|
-
} }, h("g", null, data.snapLinePath ? (h("path", { d: data.snapLinePath, fill: "none", style: {
|
|
2547
|
-
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
2548
|
-
strokeWidth: data.lineStrokeWidth,
|
|
2549
|
-
strokeDasharray: data.dashArray,
|
|
2550
|
-
strokeLinecap: 'round',
|
|
2551
|
-
} })) : (data.edgeX !== undefined &&
|
|
2552
|
-
data.edgeY !== undefined && (h("line", { x1: data.edgeX, y1: data.edgeY, x2: data.centerX, y2: data.centerY, style: {
|
|
2553
|
-
stroke: 'var(--kritzel-snap-line-stroke, rgba(0, 0, 0, 0.2))',
|
|
2554
|
-
strokeWidth: data.lineStrokeWidth,
|
|
2555
|
-
strokeDasharray: data.dashArray,
|
|
2556
|
-
strokeLinecap: 'round',
|
|
2557
|
-
} }))), h("circle", { cx: data.centerX, cy: data.centerY, r: data.indicatorRadius, style: {
|
|
2558
|
-
fill: 'var(--kritzel-snap-indicator-fill, rgba(59, 130, 246, 0.3))',
|
|
2559
|
-
stroke: 'var(--kritzel-snap-indicator-stroke, #007bff)',
|
|
2560
|
-
strokeWidth: data.indicatorStrokeWidth,
|
|
2561
|
-
} }))));
|
|
2562
2574
|
})()), this.core.store.state.isContextMenuVisible && (h("kritzel-context-menu", { class: "context-menu", ref: el => (this.contextMenuElement = el ?? null), items: this.core.store.state.contextMenuItems, objects: this.core.store.selectionGroup?.objects || [], style: {
|
|
2563
2575
|
position: 'absolute',
|
|
2564
2576
|
left: `${this.core.store.state.contextMenuX}px`,
|