mce 0.22.2 → 0.24.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.
package/dist/index.css CHANGED
@@ -842,6 +842,61 @@
842
842
  height: 1px;
843
843
  width: 1px;
844
844
  }
845
+ .m-smart-guides__dashed {
846
+ position: absolute;
847
+ background-image: repeating-linear-gradient(to right, rgba(var(--m-theme-secondary), 0.6) 0 4px, transparent 4px 7px);
848
+ }
849
+ .m-smart-guides__dashed--vertical {
850
+ background-image: repeating-linear-gradient(to bottom, rgba(var(--m-theme-secondary), 0.6) 0 4px, transparent 4px 7px);
851
+ }
852
+ .m-smart-guides__distance {
853
+ position: absolute;
854
+ display: flex;
855
+ align-items: center;
856
+ justify-content: center;
857
+ background-color: rgb(var(--m-theme-primary));
858
+ }
859
+ .m-smart-guides__distance::before, .m-smart-guides__distance::after {
860
+ content: "";
861
+ position: absolute;
862
+ background-color: rgb(var(--m-theme-primary));
863
+ }
864
+ .m-smart-guides__distance::before {
865
+ inset: 50% auto auto 0;
866
+ width: 1px;
867
+ height: 8px;
868
+ transform: translateY(-50%);
869
+ }
870
+ .m-smart-guides__distance::after {
871
+ inset: 50% 0 auto auto;
872
+ width: 1px;
873
+ height: 8px;
874
+ transform: translateY(-50%);
875
+ }
876
+ .m-smart-guides__distance--vertical::before {
877
+ inset: 0 auto auto 50%;
878
+ width: 8px;
879
+ height: 1px;
880
+ transform: translateX(-50%);
881
+ }
882
+ .m-smart-guides__distance--vertical::after {
883
+ inset: auto auto 0 50%;
884
+ width: 8px;
885
+ height: 1px;
886
+ transform: translateX(-50%);
887
+ }
888
+ .m-smart-guides__label {
889
+ position: absolute;
890
+ padding: 1px 5px;
891
+ border-radius: 8px;
892
+ font-size: 10px;
893
+ font-weight: 600;
894
+ line-height: 1.4;
895
+ white-space: nowrap;
896
+ color: rgb(var(--m-theme-on-primary));
897
+ background-color: rgb(var(--m-theme-primary));
898
+ pointer-events: none;
899
+ }
845
900
  .m-smart-guides__area {
846
901
  position: absolute;
847
902
  display: flex;
package/dist/index.js CHANGED
@@ -5767,7 +5767,7 @@ var _0_context_default = defineMixin((editor, options) => {
5767
5767
  const textSelection = ref();
5768
5768
  const hoverElement = ref();
5769
5769
  const state = ref();
5770
- const mode = ref("canvas");
5770
+ const mode = ref(options.mode ?? "canvas");
5771
5771
  function setCursor(mode) {
5772
5772
  renderEngine.value.input.setCursor(mode);
5773
5773
  }
@@ -7228,53 +7228,115 @@ var snapper_default = defineMixin((editor) => {
7228
7228
  const unregisterSnapper = (key) => {
7229
7229
  snappers.delete(key);
7230
7230
  };
7231
- const snap = (box) => {
7231
+ function getSnapAxes() {
7232
7232
  const axisX = /* @__PURE__ */ new Set();
7233
7233
  const axisY = /* @__PURE__ */ new Set();
7234
+ const gutterX = /* @__PURE__ */ new Set();
7235
+ const gutterY = /* @__PURE__ */ new Set();
7234
7236
  snappers.forEach((snapper) => {
7235
- const { xLines, yLines } = snapper.getLines();
7237
+ const { xLines, yLines, xGutters, yGutters } = snapper.getLines();
7236
7238
  xLines?.forEach((v) => axisX.add(v));
7237
7239
  yLines?.forEach((v) => axisY.add(v));
7240
+ xGutters?.forEach((v) => gutterX.add(v));
7241
+ yGutters?.forEach((v) => gutterY.add(v));
7238
7242
  });
7243
+ return {
7244
+ axisX,
7245
+ axisY,
7246
+ gutterX,
7247
+ gutterY
7248
+ };
7249
+ }
7250
+ function closestLine(lines, position) {
7251
+ let closest;
7252
+ let minDist = Infinity;
7253
+ for (const num of lines) {
7254
+ const absDist = Math.abs(num - position);
7255
+ if (absDist < minDist) {
7256
+ minDist = absDist;
7257
+ closest = num;
7258
+ }
7259
+ }
7260
+ return minDist < snapThreshold.value ? closest : void 0;
7261
+ }
7262
+ const snap = (box) => {
7263
+ const { axisX, axisY, gutterX, gutterY } = getSnapAxes();
7239
7264
  const posList = [
7240
- [0, "x"],
7241
- [box.width / 2, "x"],
7242
- [box.width, "x"],
7243
- [0, "y"],
7244
- [box.height / 2, "y"],
7245
- [box.height, "y"]
7265
+ [
7266
+ 0,
7267
+ "x",
7268
+ true
7269
+ ],
7270
+ [
7271
+ box.width / 2,
7272
+ "x",
7273
+ false
7274
+ ],
7275
+ [
7276
+ box.width,
7277
+ "x",
7278
+ true
7279
+ ],
7280
+ [
7281
+ 0,
7282
+ "y",
7283
+ true
7284
+ ],
7285
+ [
7286
+ box.height / 2,
7287
+ "y",
7288
+ false
7289
+ ],
7290
+ [
7291
+ box.height,
7292
+ "y",
7293
+ true
7294
+ ]
7246
7295
  ];
7247
7296
  for (let i = 0; i < posList.length; i++) {
7248
- const [offset, axis] = posList[i];
7249
- let position;
7250
- let numArray;
7251
- if (axis === "x") {
7252
- position = box.left + offset;
7253
- numArray = axisX;
7254
- } else {
7255
- position = box.top + offset;
7256
- numArray = axisY;
7297
+ const [offset, axis, isEdge] = posList[i];
7298
+ const position = (axis === "x" ? box.left : box.top) + offset;
7299
+ let closest = closestLine(axis === "x" ? axisX : axisY, position);
7300
+ if (closest === void 0 && isEdge) closest = closestLine(axis === "x" ? gutterX : gutterY, position);
7301
+ if (closest === void 0) continue;
7302
+ if (axis === "x") box.left = closest - offset;
7303
+ else box.top = closest - offset;
7304
+ }
7305
+ };
7306
+ const snapResize = (box, dir) => {
7307
+ if (dir.length !== 1) return;
7308
+ const { axisX, axisY, gutterX, gutterY } = getSnapAxes();
7309
+ const snapEdge = (main, gutter, pos) => {
7310
+ return closestLine(main, pos) ?? closestLine(gutter, pos);
7311
+ };
7312
+ const right = box.left + box.width;
7313
+ const bottom = box.top + box.height;
7314
+ if (dir === "l") {
7315
+ const s = snapEdge(axisX, gutterX, box.left);
7316
+ if (s !== void 0 && right - s >= 1) {
7317
+ box.left = s;
7318
+ box.width = right - s;
7257
7319
  }
7258
- let closest;
7259
- let minDist = Infinity;
7260
- for (const num of numArray) {
7261
- const dist = num - position;
7262
- const absDist = Math.abs(dist);
7263
- if (absDist < minDist) {
7264
- minDist = absDist;
7265
- closest = num;
7266
- }
7320
+ } else if (dir === "r") {
7321
+ const s = snapEdge(axisX, gutterX, right);
7322
+ if (s !== void 0 && s - box.left >= 1) box.width = s - box.left;
7323
+ } else if (dir === "t") {
7324
+ const s = snapEdge(axisY, gutterY, box.top);
7325
+ if (s !== void 0 && bottom - s >= 1) {
7326
+ box.top = s;
7327
+ box.height = bottom - s;
7267
7328
  }
7268
- if (minDist < snapThreshold.value) position = closest ?? position;
7269
- if (axis === "x") box.left = position - offset;
7270
- else box.top = position - offset;
7329
+ } else if (dir === "b") {
7330
+ const s = snapEdge(axisY, gutterY, bottom);
7331
+ if (s !== void 0 && s - box.top >= 1) box.height = s - box.top;
7271
7332
  }
7272
7333
  };
7273
7334
  Object.assign(editor, {
7274
7335
  snappers,
7275
7336
  registerSnapper,
7276
7337
  unregisterSnapper,
7277
- snap
7338
+ snap,
7339
+ snapResize
7278
7340
  });
7279
7341
  });
7280
7342
  //#endregion
@@ -9469,7 +9531,7 @@ var Btn_default = /* @__PURE__ */ defineComponent({
9469
9531
  //#endregion
9470
9532
  //#region src/components/Layer.vue?vue&type=script&setup=true&lang.ts
9471
9533
  var _hoisted_1$30 = ["data-id"];
9472
- var _hoisted_2$16 = { class: "m-layer__content" };
9534
+ var _hoisted_2$17 = { class: "m-layer__content" };
9473
9535
  var _hoisted_3$15 = { class: "m-layer__prepend" };
9474
9536
  var _hoisted_4$9 = {
9475
9537
  key: 0,
@@ -9622,7 +9684,7 @@ var Layer_default = /* @__PURE__ */ defineComponent({
9622
9684
  }, [
9623
9685
  _cache[5] || (_cache[5] = createElementVNode("span", { class: "m-layer__underlay" }, null, -1)),
9624
9686
  _cache[6] || (_cache[6] = createElementVNode("span", { class: "m-layer__overlay" }, null, -1)),
9625
- createElementVNode("div", _hoisted_2$16, [
9687
+ createElementVNode("div", _hoisted_2$17, [
9626
9688
  createElementVNode("div", _hoisted_3$15, [childrenLength.value ? (openBlock(), createBlock(unref(Icon_default), {
9627
9689
  key: 0,
9628
9690
  class: "m-layer__arrow",
@@ -9699,7 +9761,7 @@ var Layer_default = /* @__PURE__ */ defineComponent({
9699
9761
  //#endregion
9700
9762
  //#region src/components/Layers.vue?vue&type=script&setup=true&lang.ts
9701
9763
  var _hoisted_1$29 = { class: "m-layers" };
9702
- var _hoisted_2$15 = { class: "m-layers__wrapper" };
9764
+ var _hoisted_2$16 = { class: "m-layers__wrapper" };
9703
9765
  //#endregion
9704
9766
  //#region src/components/Layers.vue
9705
9767
  var Layers_default = /* @__PURE__ */ defineComponent({
@@ -9733,7 +9795,7 @@ var Layers_default = /* @__PURE__ */ defineComponent({
9733
9795
  layerScrollIntoView();
9734
9796
  });
9735
9797
  return (_ctx, _cache) => {
9736
- return openBlock(), createElementBlock("div", _hoisted_1$29, [createElementVNode("div", _hoisted_2$15, [createVNode(Layer_default, {
9798
+ return openBlock(), createElementBlock("div", _hoisted_1$29, [createElementVNode("div", _hoisted_2$16, [createVNode(Layer_default, {
9737
9799
  root: true,
9738
9800
  node: unref(root),
9739
9801
  opened: true
@@ -9954,7 +10016,7 @@ var Overlay_default = /* @__PURE__ */ defineComponent({
9954
10016
  //#endregion
9955
10017
  //#region src/components/shared/Menu.vue?vue&type=script&setup=true&lang.ts
9956
10018
  var _hoisted_1$26 = ["onMouseenter"];
9957
- var _hoisted_2$14 = ["onClick"];
10019
+ var _hoisted_2$15 = ["onClick"];
9958
10020
  var _hoisted_3$14 = {
9959
10021
  key: 0,
9960
10022
  class: "m-list-item__checked"
@@ -10091,7 +10153,7 @@ var Menu_default = /* @__PURE__ */ defineComponent({
10091
10153
  key: 0,
10092
10154
  icon: "$arrowRight"
10093
10155
  })) : createCommentVNode("", true)])) : createCommentVNode("", true)
10094
- ], 10, _hoisted_2$14)], 40, _hoisted_1$26))], 64);
10156
+ ], 10, _hoisted_2$15)], 40, _hoisted_1$26))], 64);
10095
10157
  }), 128)), opened.value > -1 && __props.items?.[opened.value]?.children?.length ? (openBlock(), createBlock(_component_MceMenu, {
10096
10158
  key: 0,
10097
10159
  "open-on-hover": "",
@@ -10616,7 +10678,7 @@ var menu_default = definePlugin((editor, options) => {
10616
10678
  //#endregion
10617
10679
  //#region src/components/Creator.vue?vue&type=script&setup=true&lang.ts
10618
10680
  var _hoisted_1$25 = { class: "m-creator" };
10619
- var _hoisted_2$13 = { class: "m-creator__tree" };
10681
+ var _hoisted_2$14 = { class: "m-creator__tree" };
10620
10682
  var _hoisted_3$13 = { class: "m-creator__actions" };
10621
10683
  //#endregion
10622
10684
  //#region src/components/Creator.vue
@@ -10688,7 +10750,7 @@ var Creator_default = /* @__PURE__ */ defineComponent({
10688
10750
  })];
10689
10751
  }
10690
10752
  return (_ctx, _cache) => {
10691
- return openBlock(), createElementBlock("div", _hoisted_1$25, [createElementVNode("div", _hoisted_2$13, [(openBlock(true), createElementBlock(Fragment, null, renderList(tree.value, (node, index) => {
10753
+ return openBlock(), createElementBlock("div", _hoisted_1$25, [createElementVNode("div", _hoisted_2$14, [(openBlock(true), createElementBlock(Fragment, null, renderList(tree.value, (node, index) => {
10692
10754
  return openBlock(), createBlock(CreatorNode, {
10693
10755
  key: index,
10694
10756
  node
@@ -10872,7 +10934,7 @@ var _hoisted_1$24 = {
10872
10934
  key: 0,
10873
10935
  class: "m-tooltip__arrow"
10874
10936
  };
10875
- var _hoisted_2$12 = { class: "m-tooltip__content" };
10937
+ var _hoisted_2$13 = { class: "m-tooltip__content" };
10876
10938
  var _hoisted_3$12 = {
10877
10939
  key: 0,
10878
10940
  class: "m-tooltip__kbd"
@@ -10916,7 +10978,7 @@ var Tooltip_default = /* @__PURE__ */ defineComponent({
10916
10978
  target: props.target,
10917
10979
  attach: props.attach
10918
10980
  }, createSlots({
10919
- default: withCtx(() => [isActive.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [__props.showArrow ? (openBlock(), createElementBlock("div", _hoisted_1$24)) : createCommentVNode("", true), createElementVNode("div", _hoisted_2$12, [renderSlot(_ctx.$slots, "default"), _ctx.$slots.kbd ? (openBlock(), createElementBlock("div", _hoisted_3$12, [renderSlot(_ctx.$slots, "kbd")])) : createCommentVNode("", true)])], 64)) : createCommentVNode("", true)]),
10981
+ default: withCtx(() => [isActive.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [__props.showArrow ? (openBlock(), createElementBlock("div", _hoisted_1$24)) : createCommentVNode("", true), createElementVNode("div", _hoisted_2$13, [renderSlot(_ctx.$slots, "default"), _ctx.$slots.kbd ? (openBlock(), createElementBlock("div", _hoisted_3$12, [renderSlot(_ctx.$slots, "kbd")])) : createCommentVNode("", true)])], 64)) : createCommentVNode("", true)]),
10920
10982
  _: 2
10921
10983
  }, [_ctx.$slots.activator ? {
10922
10984
  name: "activator",
@@ -10940,7 +11002,7 @@ var Tooltip_default = /* @__PURE__ */ defineComponent({
10940
11002
  //#endregion
10941
11003
  //#region src/components/shared/Ruler.vue?vue&type=script&setup=true&lang.ts
10942
11004
  var _hoisted_1$23 = ["width", "height"];
10943
- var _hoisted_2$11 = [
11005
+ var _hoisted_2$12 = [
10944
11006
  "onDblclick",
10945
11007
  "onMousedown",
10946
11008
  "onMousemove"
@@ -11237,7 +11299,7 @@ var Ruler_default = /* @__PURE__ */ defineComponent({
11237
11299
  onMousedown: ($event) => onReflineMousedown($event, index),
11238
11300
  onMousemove: (e) => updateTip(e, item),
11239
11301
  onMouseleave: onLeave
11240
- }, null, 46, _hoisted_2$11);
11302
+ }, null, 46, _hoisted_2$12);
11241
11303
  }), 128)),
11242
11304
  createVNode(Tooltip_default, {
11243
11305
  "model-value": !!tipText.value,
@@ -11635,7 +11697,7 @@ var ScrollToSelection_default = /* @__PURE__ */ defineComponent({
11635
11697
  //#endregion
11636
11698
  //#region src/components/shared/Transform.vue?vue&type=script&setup=true&lang.ts
11637
11699
  var _hoisted_1$19 = ["rx", "ry"];
11638
- var _hoisted_2$10 = { "pointer-events": "none" };
11700
+ var _hoisted_2$11 = { "pointer-events": "none" };
11639
11701
  var _hoisted_3$10 = [
11640
11702
  "x",
11641
11703
  "y",
@@ -12273,7 +12335,7 @@ var Transform_default = /* @__PURE__ */ defineComponent({
12273
12335
  ry: model.value.borderRadius
12274
12336
  }, null, 8, _hoisted_1$19),
12275
12337
  createVNode(Diagonal),
12276
- createElementVNode("g", _hoisted_2$10, [(openBlock(true), createElementBlock(Fragment, null, renderList(computedHandles.value, (handle, index) => {
12338
+ createElementVNode("g", _hoisted_2$11, [(openBlock(true), createElementBlock(Fragment, null, renderList(computedHandles.value, (handle, index) => {
12277
12339
  return openBlock(), createElementBlock(Fragment, { key: index }, [handle.shape ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [handle.shape === "rect" ? (openBlock(), createElementBlock("rect", {
12278
12340
  key: 0,
12279
12341
  x: handle.x,
@@ -12610,7 +12672,7 @@ var _hoisted_1$17 = {
12610
12672
  class: "m-path-editor",
12611
12673
  style: { overflow: "visible" }
12612
12674
  };
12613
- var _hoisted_2$9 = ["d"];
12675
+ var _hoisted_2$10 = ["d"];
12614
12676
  var _hoisted_3$9 = [
12615
12677
  "x1",
12616
12678
  "y1",
@@ -13315,7 +13377,7 @@ var PathEditor_default = /* @__PURE__ */ defineComponent({
13315
13377
  class: "m-path-editor__hit",
13316
13378
  d: screenPath.value,
13317
13379
  onDblclick: onInsert
13318
- }, null, 40, _hoisted_2$9),
13380
+ }, null, 40, _hoisted_2$10),
13319
13381
  (openBlock(true), createElementBlock(Fragment, null, renderList(controls.value, (c, i) => {
13320
13382
  return openBlock(), createElementBlock("line", {
13321
13383
  key: `l${i}`,
@@ -14084,6 +14146,10 @@ var _hoisted_1$15 = {
14084
14146
  key: 0,
14085
14147
  class: "m-smart-guides"
14086
14148
  };
14149
+ var _hoisted_2$9 = {
14150
+ key: 0,
14151
+ class: "m-smart-guides__label"
14152
+ };
14087
14153
  //#endregion
14088
14154
  //#region src/components/SmartGuides.vue
14089
14155
  var SmartGuides_default = /* @__PURE__ */ defineComponent({
@@ -14102,7 +14168,7 @@ var SmartGuides_default = /* @__PURE__ */ defineComponent({
14102
14168
  width: `${item.style.width}px`,
14103
14169
  height: `${item.style.height}px`
14104
14170
  })
14105
- }, null, 6);
14171
+ }, [item.label ? (openBlock(), createElementBlock("span", _hoisted_2$9, toDisplayString(item.label), 1)) : createCommentVNode("", true)], 6);
14106
14172
  }), 128))])) : createCommentVNode("", true);
14107
14173
  };
14108
14174
  }
@@ -14960,7 +15026,7 @@ var BSTree = class extends BSTreeKV {};
14960
15026
  //#endregion
14961
15027
  //#region src/plugins/smartGuides.ts
14962
15028
  var smartGuides_default = definePlugin((editor) => {
14963
- const { isNode, isElement, elementSelection, selectionAabb, getAabb, root, camera, viewportAabb, registerSnapper } = editor;
15029
+ const { isNode, isElement, isFrameNode, elementSelection, selectionAabb, getAabb, root, camera, viewportAabb, registerSnapper } = editor;
14964
15030
  const snapThreshold = computed(() => Math.max(1, 5 / camera.value.zoom.x));
14965
15031
  const parent = computed(() => elementSelection.value[0]?.parent ?? root.value);
14966
15032
  const parentBox = computed(() => createBox(parent.value));
@@ -14988,6 +15054,13 @@ var smartGuides_default = definePlugin((editor) => {
14988
15054
  box.hr = createLine(left + width, "hr", box);
14989
15055
  return box;
14990
15056
  }
15057
+ function createCanvasBox() {
15058
+ const p = parent.value;
15059
+ if (!isNode(p) || !isFrameNode(p)) return void 0;
15060
+ const box = createBox(p);
15061
+ if (box) box.id = -1;
15062
+ return box;
15063
+ }
14991
15064
  function isCanvasLine(line) {
14992
15065
  return line.box?.id === -1;
14993
15066
  }
@@ -15050,15 +15123,82 @@ var smartGuides_default = definePlugin((editor) => {
15050
15123
  };
15051
15124
  return areas;
15052
15125
  }
15126
+ function findDistancePairs(box, boxes) {
15127
+ const moving = toBoundingBox(box);
15128
+ let bestV;
15129
+ let bestH;
15130
+ for (const nb of boxes) {
15131
+ if (nb.id === -1) continue;
15132
+ const b = toBoundingBox(nb);
15133
+ if (moving.overlap(b, "x")) {
15134
+ const below = nb.vt.pos - box.vb.pos;
15135
+ const above = box.vt.pos - nb.vb.pos;
15136
+ if (below > 0 && (!bestV || below < bestV.gap)) bestV = {
15137
+ gap: below,
15138
+ below: true,
15139
+ nb
15140
+ };
15141
+ if (above > 0 && (!bestV || above < bestV.gap)) bestV = {
15142
+ gap: above,
15143
+ below: false,
15144
+ nb
15145
+ };
15146
+ }
15147
+ if (moving.overlap(b, "y")) {
15148
+ const after = nb.hl.pos - box.hr.pos;
15149
+ const before = box.hl.pos - nb.hr.pos;
15150
+ if (after > 0 && (!bestH || after < bestH.gap)) bestH = {
15151
+ gap: after,
15152
+ after: true,
15153
+ nb
15154
+ };
15155
+ if (before > 0 && (!bestH || before < bestH.gap)) bestH = {
15156
+ gap: before,
15157
+ after: false,
15158
+ nb
15159
+ };
15160
+ }
15161
+ }
15162
+ const pairs = [];
15163
+ if (bestV) pairs.push({
15164
+ source: bestV.below ? box.vb : box.vt,
15165
+ target: bestV.below ? bestV.nb.vt : bestV.nb.vb,
15166
+ type: "distance",
15167
+ distance: bestV.gap
15168
+ });
15169
+ if (bestH) pairs.push({
15170
+ source: bestH.after ? box.hr : box.hl,
15171
+ target: bestH.after ? bestH.nb.hl : bestH.nb.hr,
15172
+ type: "distance",
15173
+ distance: bestH.gap
15174
+ });
15175
+ return pairs;
15176
+ }
15053
15177
  const linePairs = ref([]);
15054
- function updateSmartGuides() {
15178
+ const gutterPoints = ref({
15179
+ x: [],
15180
+ y: []
15181
+ });
15182
+ const GUTTERS = [
15183
+ 4,
15184
+ 8,
15185
+ 12,
15186
+ 16
15187
+ ];
15188
+ function updateSmartGuides(handle = "move") {
15055
15189
  const _linePairs = [];
15190
+ const _gutterX = [];
15191
+ const _gutterY = [];
15192
+ const resizeDir = handle.startsWith("resize") ? handle.split("-")[1] ?? "" : "";
15056
15193
  const box = createBox(selectionAabb.value);
15057
15194
  if (box) {
15058
15195
  const excluded = new Set(elementSelection.value.map((el) => el.instanceId));
15059
- const { vLines, hLines } = parent.value.children.filter((node) => {
15196
+ const boxes = parent.value.children.filter((node) => {
15060
15197
  return !excluded.has(node.instanceId) && isElement(node) && !node.connection?.isValid() && viewportAabb.value.overlap(node.globalAabb);
15061
- }).map((node) => createBox(node)).filter(Boolean).reduce((store, box) => {
15198
+ }).map((node) => createBox(node)).filter(Boolean);
15199
+ const canvasBox = createCanvasBox();
15200
+ if (canvasBox) boxes.push(canvasBox);
15201
+ const { vLines, hLines } = boxes.reduce((store, box) => {
15062
15202
  [
15063
15203
  box.vt,
15064
15204
  box.vm,
@@ -15074,6 +15214,14 @@ var smartGuides_default = definePlugin((editor) => {
15074
15214
  vLines: new BSTree((a, b) => a.pos - b.pos),
15075
15215
  hLines: new BSTree((a, b) => a.pos - b.pos)
15076
15216
  });
15217
+ hLines.inorderTraversal((line) => {
15218
+ if (line.type === "hm") return;
15219
+ for (const g of GUTTERS) _gutterX.push(line.pos + g, line.pos - g);
15220
+ });
15221
+ vLines.inorderTraversal((line) => {
15222
+ if (line.type === "vm") return;
15223
+ for (const g of GUTTERS) _gutterY.push(line.pos + g, line.pos - g);
15224
+ });
15077
15225
  const areaLine = {
15078
15226
  vt: [],
15079
15227
  vb: [],
@@ -15081,14 +15229,14 @@ var smartGuides_default = definePlugin((editor) => {
15081
15229
  hr: []
15082
15230
  };
15083
15231
  [{
15084
- sources: [
15232
+ sources: resizeDir ? [resizeDir.includes("t") && box.vt, resizeDir.includes("b") && box.vb].filter(Boolean) : [
15085
15233
  box.vt,
15086
15234
  box.vm,
15087
15235
  box.vb
15088
15236
  ],
15089
15237
  targets: vLines
15090
15238
  }, {
15091
- sources: [
15239
+ sources: resizeDir ? [resizeDir.includes("l") && box.hl, resizeDir.includes("r") && box.hr].filter(Boolean) : [
15092
15240
  box.hl,
15093
15241
  box.hm,
15094
15242
  box.hr
@@ -15124,13 +15272,13 @@ var smartGuides_default = definePlugin((editor) => {
15124
15272
  });
15125
15273
  areaLine.vt = areaLine.vt.sort((a, b) => b.pos - a.pos);
15126
15274
  areaLine.hl = areaLine.hl.sort((a, b) => b.pos - a.pos);
15127
- [{
15275
+ (resizeDir ? [] : [{
15128
15276
  targets: [areaLine.vt, areaLine.vb],
15129
15277
  sources: [box.vt, box.vb]
15130
15278
  }, {
15131
15279
  targets: [areaLine.hl, areaLine.hr],
15132
15280
  sources: [box.hl, box.hr]
15133
- }].forEach(({ sources, targets }) => {
15281
+ }]).forEach(({ sources, targets }) => {
15134
15282
  const targetA = targets[0][0];
15135
15283
  const sourceA = sources[0];
15136
15284
  const targetB = targets[1][0];
@@ -15167,14 +15315,24 @@ var smartGuides_default = definePlugin((editor) => {
15167
15315
  }
15168
15316
  }
15169
15317
  });
15318
+ if (!resizeDir) {
15319
+ const isV = (t) => t === "vt" || t === "vm" || t === "vb";
15320
+ const hasV = _linePairs.some((p) => isV(p.target.type));
15321
+ const hasH = _linePairs.some((p) => !isV(p.target.type));
15322
+ for (const pair of findDistancePairs(box, boxes)) if (isV(pair.target.type) ? !hasV : !hasH) _linePairs.push(pair);
15323
+ }
15170
15324
  }
15171
15325
  linePairs.value = _linePairs;
15326
+ gutterPoints.value = {
15327
+ x: _gutterX,
15328
+ y: _gutterY
15329
+ };
15172
15330
  }
15173
15331
  const snapLines = computed(() => {
15174
15332
  const { zoom, position } = camera.value;
15175
15333
  const scaleX = (v) => v * zoom.x;
15176
15334
  const scaleY = (v) => v * zoom.y;
15177
- return linePairs.value.map((linePair) => {
15335
+ return linePairs.value.flatMap((linePair) => {
15178
15336
  const { target, source, type } = linePair;
15179
15337
  const boxSource = source.box;
15180
15338
  const boxTarget = target.box;
@@ -15184,6 +15342,7 @@ var smartGuides_default = definePlugin((editor) => {
15184
15342
  "vb"
15185
15343
  ].includes(target.type);
15186
15344
  const itemProps = {};
15345
+ const extra = [];
15187
15346
  switch (type) {
15188
15347
  case "alignment":
15189
15348
  itemProps.class = ["alignment"];
@@ -15232,10 +15391,54 @@ var smartGuides_default = definePlugin((editor) => {
15232
15391
  height: scaleY(bottom - top)
15233
15392
  };
15234
15393
  }
15394
+ itemProps.label = String(Math.round(linePair.distance));
15235
15395
  break;
15236
15396
  }
15397
+ case "distance":
15398
+ itemProps.class = ["distance"];
15399
+ if (vertical) {
15400
+ itemProps.class.push("distance--vertical");
15401
+ const top = Math.min(source.pos, target.pos);
15402
+ itemProps.style = {
15403
+ left: scaleX(boxSource.hm.pos) - position.left,
15404
+ top: scaleY(top) - position.top,
15405
+ width: 1,
15406
+ height: scaleY(linePair.distance)
15407
+ };
15408
+ const auxLeft = Math.min(boxSource.hl.pos, boxTarget.hl.pos);
15409
+ const auxRight = Math.max(boxSource.hr.pos, boxTarget.hr.pos);
15410
+ extra.push({
15411
+ class: ["dashed"],
15412
+ style: {
15413
+ left: scaleX(auxLeft) - position.left,
15414
+ top: scaleY(target.pos) - position.top,
15415
+ width: scaleX(auxRight - auxLeft),
15416
+ height: 1
15417
+ }
15418
+ });
15419
+ } else {
15420
+ itemProps.style = {
15421
+ left: scaleX(Math.min(source.pos, target.pos)) - position.left,
15422
+ top: scaleY(boxSource.vm.pos) - position.top,
15423
+ width: scaleX(linePair.distance),
15424
+ height: 1
15425
+ };
15426
+ const auxTop = Math.min(boxSource.vt.pos, boxTarget.vt.pos);
15427
+ const auxBottom = Math.max(boxSource.vb.pos, boxTarget.vb.pos);
15428
+ extra.push({
15429
+ class: ["dashed", "dashed--vertical"],
15430
+ style: {
15431
+ left: scaleX(target.pos) - position.left,
15432
+ top: scaleY(auxTop) - position.top,
15433
+ width: 1,
15434
+ height: scaleY(auxBottom - auxTop)
15435
+ }
15436
+ });
15437
+ }
15438
+ itemProps.label = String(Math.round(linePair.distance));
15439
+ break;
15237
15440
  }
15238
- return itemProps;
15441
+ return [itemProps, ...extra];
15239
15442
  });
15240
15443
  });
15241
15444
  function getSnapPoints() {
@@ -15269,17 +15472,23 @@ var smartGuides_default = definePlugin((editor) => {
15269
15472
  const lines = getSnapPoints();
15270
15473
  return {
15271
15474
  xLines: lines.x,
15272
- yLines: lines.y
15475
+ yLines: lines.y,
15476
+ xGutters: gutterPoints.value.x,
15477
+ yGutters: gutterPoints.value.y
15273
15478
  };
15274
15479
  } });
15275
15480
  return {
15276
15481
  name: "mce:smartGuides",
15277
15482
  events: {
15278
15483
  selectionTransformed: ({ handle }) => {
15279
- if (handle === "move") updateSmartGuides();
15484
+ if (handle === "move" || /^resize-[tlrb]$/.test(handle)) updateSmartGuides(handle);
15280
15485
  },
15281
15486
  selectionTransformEnded: () => {
15282
15487
  linePairs.value = [];
15488
+ gutterPoints.value = {
15489
+ x: [],
15490
+ y: []
15491
+ };
15283
15492
  }
15284
15493
  },
15285
15494
  components: [{
@@ -18307,7 +18516,7 @@ var toolbelt_default = definePlugin((editor) => {
18307
18516
  //#endregion
18308
18517
  //#region src/plugins/transform.ts
18309
18518
  var transform_default = definePlugin((editor) => {
18310
- const { selectionObb, selectionAabb, elementSelection, exec, inEditorIs, resizeElement, state, registerConfig, snap } = editor;
18519
+ const { selectionObb, selectionAabb, elementSelection, exec, inEditorIs, resizeElement, state, registerConfig, snap, snapResize } = editor;
18311
18520
  registerConfig("interaction.transform", { default: {
18312
18521
  handleShape: "rect",
18313
18522
  handleStyle: "4-points",
@@ -18365,7 +18574,7 @@ var transform_default = definePlugin((editor) => {
18365
18574
  return transform.value;
18366
18575
  }
18367
18576
  const setTransform = (type, value, options = {}) => {
18368
- const { event, isCorner } = options;
18577
+ const { event, isCorner, direction = "" } = options;
18369
18578
  if (!context) initContext();
18370
18579
  const _context = context;
18371
18580
  const oldTransform = getTransform();
@@ -18397,7 +18606,7 @@ var transform_default = definePlugin((editor) => {
18397
18606
  else transform.left = ctx.startPoint.x;
18398
18607
  }
18399
18608
  if (!transform.rotate) snap(transform);
18400
- }
18609
+ } else if (type === "resize" && !transform.rotate && !isMultiple) snapResize(transform, direction);
18401
18610
  const offsetStyle = {
18402
18611
  left: transform.left - oldTransform.left,
18403
18612
  top: transform.top - oldTransform.top,
@@ -18624,7 +18833,8 @@ var transform_default = definePlugin((editor) => {
18624
18833
  const [type, direction = ""] = handle.split("-");
18625
18834
  setTransform(type, value, {
18626
18835
  event,
18627
- isCorner: direction.length > 1
18836
+ isCorner: direction.length > 1,
18837
+ direction
18628
18838
  });
18629
18839
  },
18630
18840
  selectionTransformEnded: () => {
@@ -6,6 +6,9 @@ declare global {
6
6
  xLines?: number[];
7
7
  yLines?: number[];
8
8
  points?: Vector2Like[];
9
+ /** 间距卡点候选位(如 4/8/12/16 间距)。仅当主吸附线(对齐/区域)未命中时作为次选。 */
10
+ xGutters?: number[];
11
+ yGutters?: number[];
9
12
  }
10
13
  interface Snapper {
11
14
  getLines: () => SnapperData;
@@ -20,6 +23,13 @@ declare global {
20
23
  width: number;
21
24
  height: number;
22
25
  }) => void;
26
+ /** 缩放吸附:按被拖动的边(dir 含 t/l/r/b)将该边对齐到最近的吸附线,调整对应宽高。 */
27
+ snapResize: (box: {
28
+ left: number;
29
+ top: number;
30
+ width: number;
31
+ height: number;
32
+ }, dir: string) => void;
23
33
  }
24
34
  }
25
35
  }
@@ -32,6 +32,8 @@ declare global {
32
32
  interface TransformOptions {
33
33
  event?: MouseEvent;
34
34
  isCorner?: boolean;
35
+ /** resize 时被拖动的方向(t/l/r/b/tl/...),用于缩放吸附定位被拖动的边。 */
36
+ direction?: string;
35
37
  }
36
38
  interface Commands {
37
39
  enter: () => void;
@@ -28,7 +28,8 @@ declare global {
28
28
  interface Exporters {}
29
29
  interface Config {}
30
30
  interface Options extends DeepMaybe<Config> {
31
- //
31
+ /** 编辑器初始模式,默认 'canvas'。见 {@link Mode}。 */
32
+ mode?: Mode
32
33
  }
33
34
 
34
35
  // Persistent editor mode, orthogonal to the transient `State`. 'canvas' is
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mce",
3
3
  "type": "module",
4
- "version": "0.22.2",
4
+ "version": "0.24.0",
5
5
  "description": "A headless infinite canvas editor framework built on WebGL rendering, supports exporting to image, video, and PPT. Only the ESM.",
6
6
  "author": "wxm",
7
7
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "@vueuse/core": "^14.3.0",
61
61
  "diff": "^9.0.0",
62
62
  "lodash-es": "^4.18.1",
63
- "modern-canvas": "^0.21.2",
63
+ "modern-canvas": "^0.22.0",
64
64
  "modern-font": "^0.6.0",
65
65
  "modern-idoc": "^0.11.8",
66
66
  "modern-text": "^2.0.3",