OzzyGT HF Staff commited on
Commit
d8d0dbe
·
1 Parent(s): 1df326c

ctrl shift bbox modifiers

Browse files
Files changed (1) hide show
  1. app.py +184 -58
app.py CHANGED
@@ -547,6 +547,20 @@ gradio-app { height: 100vh; overflow: hidden; }
547
  .ib-sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
548
  .ib-se { right: -6px; bottom: -6px; cursor: nwse-resize; }
549
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  /* ---- Right-side element cards (rendered by JS into #panel-root) ---- */
551
  .ib-panel-head { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; }
552
  .ib-add-wrap { margin-left: auto; display: flex; gap: 6px; align-items: center; }
@@ -714,7 +728,7 @@ CANVAS_JS = r"""
714
  canvasRoot.appendChild(stage);
715
 
716
  let els = []; // {id,type,ymin,xmin,ymax,xmax,desc,text,palette}
717
- let selected = null;
718
  let drag = null;
719
  let jsCounter = 0;
720
  let emitTimer = null;
@@ -761,7 +775,7 @@ CANVAS_JS = r"""
761
  text: e.text || "",
762
  colors: e.colors || [],
763
  })),
764
- selected: selected,
765
  };
766
  setNativeValue(el, JSON.stringify(payload));
767
  el.dispatchEvent(new Event("input", { bubbles: true }));
@@ -777,10 +791,11 @@ CANVAS_JS = r"""
777
  }
778
 
779
  function renderBoxes() {
780
- stage.querySelectorAll(".ib-box").forEach((n) => n.remove());
 
781
  els.forEach((e, i) => {
782
  const d = document.createElement("div");
783
- d.className = "ib-box" + (e.id === selected ? " sel" : "");
784
  d.style.left = e.xmin / 10 + "%";
785
  d.style.top = e.ymin / 10 + "%";
786
  d.style.width = (e.xmax - e.xmin) / 10 + "%";
@@ -790,20 +805,39 @@ CANVAS_JS = r"""
790
  lab.className = "ib-label";
791
  lab.textContent = labelText(e, i);
792
  d.appendChild(lab);
793
- ["nw", "ne", "sw", "se"].forEach((h) => {
794
- const hd = document.createElement("div");
795
- hd.className = "ib-h ib-" + h;
796
- hd.dataset.h = h;
797
- d.appendChild(hd);
798
- });
 
 
799
  stage.appendChild(d);
800
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
  }
802
 
803
  function cardHTML(e, i) {
804
  const isText = e.type === "text";
805
  return (
806
- '<div class="ib-card' + (e.id === selected ? " sel" : "") + '" data-id="' + e.id + '">' +
807
  '<div class="ib-card-head">' +
808
  '<span class="ib-badge">' + (i + 1) + "</span>" +
809
  '<select class="ib-type" data-f="type">' +
@@ -1055,16 +1089,37 @@ CANVAS_JS = r"""
1055
  renderPanel();
1056
  }
1057
 
1058
- function setSelected(id) {
1059
- selected = id;
1060
- stage.querySelectorAll(".ib-box").forEach((n) =>
1061
- n.classList.toggle("sel", n.dataset.id === id));
1062
- const panelRoot = document.getElementById("panel-root");
1063
- if (!panelRoot) return;
1064
- panelRoot.querySelectorAll(".ib-card").forEach((n) =>
1065
- n.classList.toggle("sel", n.dataset.id === id));
1066
- const card = panelRoot.querySelector('.ib-card[data-id="' + id + '"]');
1067
- if (card) card.scrollIntoView({ block: "nearest" });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1068
  }
1069
 
1070
  function toNorm(ev) {
@@ -1075,26 +1130,86 @@ CANVAS_JS = r"""
1075
  };
1076
  }
1077
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1078
  // ---- canvas interaction ----
1079
  stage.addEventListener("pointerdown", (ev) => {
1080
  if (ev.button !== 0 || !img.src || eyedropSample) return;
 
 
1081
  const boxEl = ev.target.closest(".ib-box");
1082
  const handle = ev.target.classList.contains("ib-h") ? ev.target.dataset.h : null;
1083
- const p = toNorm(ev);
1084
- if (handle && boxEl) {
1085
- drag = { mode: "resize", h: handle, e: byId(boxEl.dataset.id) };
1086
- setSelected(boxEl.dataset.id);
1087
- } else if (boxEl) {
1088
  const e = byId(boxEl.dataset.id);
1089
- drag = { mode: "move", e: e, start: p, orig: Object.assign({}, e) };
1090
- setSelected(e.id);
 
 
 
 
 
 
 
 
 
 
1091
  } else {
 
1092
  const id = "j" + jsCounter++;
1093
  const e = { id: id, type: "obj", ymin: p.y, xmin: p.x, ymax: p.y, xmax: p.x,
1094
  desc: "", text: "", colors: [] };
1095
  els.push(e);
 
1096
  drag = { mode: "draw", e: e, start: p };
1097
- selected = id;
1098
  renderAll();
1099
  }
1100
  stage.setPointerCapture(ev.pointerId);
@@ -1104,45 +1219,54 @@ CANVAS_JS = r"""
1104
  stage.addEventListener("pointermove", (ev) => {
1105
  if (!drag) return;
1106
  const p = toNorm(ev);
1107
- const e = drag.e;
1108
  if (drag.mode === "move") {
1109
- const o = drag.orig;
1110
- const w = o.xmax - o.xmin;
1111
- const h = o.ymax - o.ymin;
1112
- e.xmin = clamp(o.xmin + (p.x - drag.start.x));
1113
- e.ymin = clamp(o.ymin + (p.y - drag.start.y));
1114
- e.xmax = e.xmin + w;
1115
- e.ymax = e.ymin + h;
1116
- if (e.xmax > 1000) { e.xmax = 1000; e.xmin = 1000 - w; }
1117
- if (e.ymax > 1000) { e.ymax = 1000; e.ymin = 1000 - h; }
 
 
 
 
 
 
1118
  } else if (drag.mode === "draw") {
 
1119
  e.xmin = Math.min(drag.start.x, p.x);
1120
  e.xmax = Math.max(drag.start.x, p.x);
1121
  e.ymin = Math.min(drag.start.y, p.y);
1122
  e.ymax = Math.max(drag.start.y, p.y);
1123
  } else if (drag.mode === "resize") {
1124
- const h = drag.h;
1125
- if (h.includes("w")) e.xmin = Math.min(p.x, e.xmax - 1);
1126
- if (h.includes("e")) e.xmax = Math.max(p.x, e.xmin + 1);
1127
- if (h.includes("n")) e.ymin = Math.min(p.y, e.ymax - 1);
1128
- if (h.includes("s")) e.ymax = Math.max(p.y, e.ymin + 1);
1129
  }
1130
  renderBoxes();
1131
  });
1132
 
1133
  function endDrag() {
1134
  if (!drag) return;
1135
- const e = drag.e;
1136
- if (drag.mode === "draw" && (e.xmax - e.xmin < 8 || e.ymax - e.ymin < 8)) {
1137
- els = els.filter((x) => x !== e);
1138
- selected = null;
 
 
1139
  }
1140
  const wasDraw = drag.mode === "draw";
1141
  drag = null;
1142
  renderAll();
 
1143
  emitNow();
1144
  if (wasDraw) {
1145
- const card = panelRoot.querySelector('.ib-card.sel .ib-desc');
 
1146
  if (card) card.focus();
1147
  }
1148
  }
@@ -1150,11 +1274,11 @@ CANVAS_JS = r"""
1150
  stage.addEventListener("pointercancel", endDrag);
1151
 
1152
  window.addEventListener("keydown", (ev) => {
1153
- if ((ev.key === "Delete") && selected) {
1154
  const t = document.activeElement;
1155
  if (t && (t.tagName === "TEXTAREA" || t.tagName === "INPUT")) return;
1156
- els = els.filter((x) => x.id !== selected);
1157
- selected = null;
1158
  renderAll();
1159
  emitNow();
1160
  }
@@ -1184,7 +1308,7 @@ CANVAS_JS = r"""
1184
  if (!e) return;
1185
  e.type = ev.target.value;
1186
  renderAll();
1187
- setSelected(e.id);
1188
  emitNow();
1189
  });
1190
  document.addEventListener("click", (ev) => {
@@ -1196,7 +1320,7 @@ CANVAS_JS = r"""
1196
  const e = byId(card.dataset.id);
1197
  if (ev.target.classList.contains("ib-del")) {
1198
  els = els.filter((x) => x.id !== card.dataset.id);
1199
- if (selected === card.dataset.id) selected = null;
1200
  renderAll();
1201
  emitNow();
1202
  return;
@@ -1229,16 +1353,18 @@ CANVAS_JS = r"""
1229
  });
1230
  return;
1231
  }
1232
- if (!ev.target.closest(".ib-f, .ib-type, .ib-pal")) setSelected(card.dataset.id);
 
 
 
1233
  });
1234
 
1235
  function addElement(type) {
1236
  const id = "j" + jsCounter++;
1237
  els.push({ id: id, type: type, ymin: 350, xmin: 350, ymax: 650, xmax: 650,
1238
  desc: "", text: "", colors: [] });
1239
- selected = id;
1240
  renderAll();
1241
- setSelected(id);
1242
  emitNow();
1243
  }
1244
 
@@ -1251,7 +1377,7 @@ CANVAS_JS = r"""
1251
  if (Array.isArray(p.elements)) {
1252
  els = p.elements.map((e) => Object.assign({}, e));
1253
  }
1254
- if ("selected" in p) selected = p.selected;
1255
  renderAll();
1256
  } catch (e) {
1257
  console.error("IB.load", e);
 
547
  .ib-sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
548
  .ib-se { right: -6px; bottom: -6px; cursor: nwse-resize; }
549
 
550
+ /* group-transform box (shown when 2+ boxes selected): outline is click-through, handles aren't */
551
+ .ib-group {
552
+ position: absolute; border: 1px dashed #ff8c00; box-sizing: border-box;
553
+ pointer-events: none; z-index: 6;
554
+ }
555
+ .ib-gh {
556
+ position: absolute; width: 11px; height: 11px; background: #fff; border: 1px solid #ff8c00;
557
+ box-sizing: border-box; pointer-events: auto;
558
+ }
559
+ .ib-gh.ib-n { left: 50%; top: -6px; transform: translateX(-50%); cursor: ns-resize; }
560
+ .ib-gh.ib-s { left: 50%; bottom: -6px; transform: translateX(-50%); cursor: ns-resize; }
561
+ .ib-gh.ib-e { right: -6px; top: 50%; transform: translateY(-50%); cursor: ew-resize; }
562
+ .ib-gh.ib-w { left: -6px; top: 50%; transform: translateY(-50%); cursor: ew-resize; }
563
+
564
  /* ---- Right-side element cards (rendered by JS into #panel-root) ---- */
565
  .ib-panel-head { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; }
566
  .ib-add-wrap { margin-left: auto; display: flex; gap: 6px; align-items: center; }
 
728
  canvasRoot.appendChild(stage);
729
 
730
  let els = []; // {id,type,ymin,xmin,ymax,xmax,desc,text,palette}
731
+ let sel = new Set(); // selected ids (multi-select via ctrl/cmd-click)
732
  let drag = null;
733
  let jsCounter = 0;
734
  let emitTimer = null;
 
775
  text: e.text || "",
776
  colors: e.colors || [],
777
  })),
778
+ selected: Array.from(sel),
779
  };
780
  setNativeValue(el, JSON.stringify(payload));
781
  el.dispatchEvent(new Event("input", { bubbles: true }));
 
791
  }
792
 
793
  function renderBoxes() {
794
+ stage.querySelectorAll(".ib-box, .ib-group").forEach((n) => n.remove());
795
+ const single = sel.size === 1;
796
  els.forEach((e, i) => {
797
  const d = document.createElement("div");
798
+ d.className = "ib-box" + (sel.has(e.id) ? " sel" : "");
799
  d.style.left = e.xmin / 10 + "%";
800
  d.style.top = e.ymin / 10 + "%";
801
  d.style.width = (e.xmax - e.xmin) / 10 + "%";
 
805
  lab.className = "ib-label";
806
  lab.textContent = labelText(e, i);
807
  d.appendChild(lab);
808
+ if (single && sel.has(e.id)) {
809
+ ["nw", "ne", "sw", "se"].forEach((h) => {
810
+ const hd = document.createElement("div");
811
+ hd.className = "ib-h ib-" + h;
812
+ hd.dataset.h = h;
813
+ d.appendChild(hd);
814
+ });
815
+ }
816
  stage.appendChild(d);
817
  });
818
+ // A single group box with 8 handles when 2+ are selected — drag a handle to scale them together.
819
+ if (sel.size >= 2) {
820
+ const g = groupBBox();
821
+ const go = document.createElement("div");
822
+ go.className = "ib-group";
823
+ go.style.left = g.x0 / 10 + "%";
824
+ go.style.top = g.y0 / 10 + "%";
825
+ go.style.width = (g.x1 - g.x0) / 10 + "%";
826
+ go.style.height = (g.y1 - g.y0) / 10 + "%";
827
+ ["nw", "n", "ne", "e", "se", "s", "sw", "w"].forEach((h) => {
828
+ const hd = document.createElement("div");
829
+ hd.className = "ib-gh ib-" + h;
830
+ hd.dataset.gh = h;
831
+ go.appendChild(hd);
832
+ });
833
+ stage.appendChild(go);
834
+ }
835
  }
836
 
837
  function cardHTML(e, i) {
838
  const isText = e.type === "text";
839
  return (
840
+ '<div class="ib-card' + (sel.has(e.id) ? " sel" : "") + '" data-id="' + e.id + '">' +
841
  '<div class="ib-card-head">' +
842
  '<span class="ib-badge">' + (i + 1) + "</span>" +
843
  '<select class="ib-type" data-f="type">' +
 
1089
  renderPanel();
1090
  }
1091
 
1092
+ const selArr = () => els.filter((e) => sel.has(e.id));
1093
+ function groupBBox() {
1094
+ const s = selArr();
1095
+ if (!s.length) return null;
1096
+ let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
1097
+ s.forEach((e) => {
1098
+ x0 = Math.min(x0, e.xmin); y0 = Math.min(y0, e.ymin);
1099
+ x1 = Math.max(x1, e.xmax); y1 = Math.max(y1, e.ymax);
1100
+ });
1101
+ return { x0: x0, y0: y0, x1: x1, y1: y1 };
1102
+ }
1103
+ function applySelClasses() {
1104
+ stage.querySelectorAll(".ib-box").forEach((n) => n.classList.toggle("sel", sel.has(n.dataset.id)));
1105
+ const pr = document.getElementById("panel-root");
1106
+ if (pr) pr.querySelectorAll(".ib-card").forEach((n) => n.classList.toggle("sel", sel.has(n.dataset.id)));
1107
+ }
1108
+ function scrollToCard(id) {
1109
+ const pr = document.getElementById("panel-root");
1110
+ const c = pr && pr.querySelector('.ib-card[data-id="' + id + '"]');
1111
+ if (c) c.scrollIntoView({ block: "nearest" });
1112
+ }
1113
+ function selectOnly(id) {
1114
+ sel = new Set(id ? [id] : []);
1115
+ renderBoxes();
1116
+ applySelClasses();
1117
+ if (id) scrollToCard(id);
1118
+ }
1119
+ function toggleSelect(id) {
1120
+ if (sel.has(id)) sel.delete(id); else sel.add(id);
1121
+ renderBoxes();
1122
+ applySelClasses();
1123
  }
1124
 
1125
  function toNorm(ev) {
 
1130
  };
1131
  }
1132
 
1133
+ // snapshot the current geometry of the selected boxes (for move/group-resize deltas)
1134
+ const snapshot = () => {
1135
+ const o = {};
1136
+ selArr().forEach((e) => { o[e.id] = { xmin: e.xmin, ymin: e.ymin, xmax: e.xmax, ymax: e.ymax }; });
1137
+ return o;
1138
+ };
1139
+
1140
+ function resizeSingle(dr, p, shift) {
1141
+ const e = dr.e, o = dr.orig, h = dr.h;
1142
+ let xmin = o.xmin, ymin = o.ymin, xmax = o.xmax, ymax = o.ymax;
1143
+ if (h.includes("w")) xmin = Math.min(p.x, o.xmax - 1);
1144
+ if (h.includes("e")) xmax = Math.max(p.x, o.xmin + 1);
1145
+ if (h.includes("n")) ymin = Math.min(p.y, o.ymax - 1);
1146
+ if (h.includes("s")) ymax = Math.max(p.y, o.ymin + 1);
1147
+ if (shift) { // preserve aspect ratio, anchored at the opposite corner
1148
+ const ow = o.xmax - o.xmin, oh = o.ymax - o.ymin;
1149
+ const scale = Math.max((xmax - xmin) / ow, (ymax - ymin) / oh);
1150
+ const nw = ow * scale, nh = oh * scale;
1151
+ if (h.includes("w")) xmin = o.xmax - nw; else xmax = o.xmin + nw;
1152
+ if (h.includes("n")) ymin = o.ymax - nh; else ymax = o.ymin + nh;
1153
+ }
1154
+ e.xmin = clamp(xmin); e.ymin = clamp(ymin); e.xmax = clamp(xmax); e.ymax = clamp(ymax);
1155
+ }
1156
+
1157
+ function groupResize(dr, p, shift) {
1158
+ const h = dr.h, g = dr.g;
1159
+ let nx0 = g.x0, ny0 = g.y0, nx1 = g.x1, ny1 = g.y1;
1160
+ if (h.includes("w")) nx0 = Math.min(p.x, g.x1 - 1);
1161
+ if (h.includes("e")) nx1 = Math.max(p.x, g.x0 + 1);
1162
+ if (h.includes("n")) ny0 = Math.min(p.y, g.y1 - 1);
1163
+ if (h.includes("s")) ny1 = Math.max(p.y, g.y0 + 1);
1164
+ let sx = (nx1 - nx0) / (g.x1 - g.x0);
1165
+ let sy = (ny1 - ny0) / (g.y1 - g.y0);
1166
+ if (h === "n" || h === "s") sx = 1;
1167
+ if (h === "e" || h === "w") sy = 1;
1168
+ if (shift && h.length === 2) { const f = Math.max(sx, sy); sx = f; sy = f; }
1169
+ const ax = h.includes("w") ? g.x1 : g.x0; // anchor = opposite side
1170
+ const ay = h.includes("n") ? g.y1 : g.y0;
1171
+ selArr().forEach((e) => {
1172
+ const o = dr.orig[e.id];
1173
+ e.xmin = clamp(ax + (o.xmin - ax) * sx);
1174
+ e.xmax = clamp(ax + (o.xmax - ax) * sx);
1175
+ e.ymin = clamp(ay + (o.ymin - ay) * sy);
1176
+ e.ymax = clamp(ay + (o.ymax - ay) * sy);
1177
+ });
1178
+ }
1179
+
1180
  // ---- canvas interaction ----
1181
  stage.addEventListener("pointerdown", (ev) => {
1182
  if (ev.button !== 0 || !img.src || eyedropSample) return;
1183
+ const p = toNorm(ev);
1184
+ const gh = ev.target.classList.contains("ib-gh") ? ev.target.dataset.gh : null;
1185
  const boxEl = ev.target.closest(".ib-box");
1186
  const handle = ev.target.classList.contains("ib-h") ? ev.target.dataset.h : null;
1187
+ const additive = ev.ctrlKey || ev.metaKey;
1188
+
1189
+ if (gh) {
1190
+ drag = { mode: "gresize", h: gh, g: groupBBox(), orig: snapshot() };
1191
+ } else if (handle && boxEl) {
1192
  const e = byId(boxEl.dataset.id);
1193
+ drag = { mode: "resize", h: handle, e: e,
1194
+ orig: { xmin: e.xmin, ymin: e.ymin, xmax: e.xmax, ymax: e.ymax } };
1195
+ } else if (boxEl) {
1196
+ const id = boxEl.dataset.id;
1197
+ if (additive) {
1198
+ toggleSelect(id);
1199
+ stage.setPointerCapture(ev.pointerId);
1200
+ ev.preventDefault();
1201
+ return;
1202
+ }
1203
+ if (!sel.has(id)) selectOnly(id);
1204
+ drag = { mode: "move", start: p, orig: snapshot(), ids: Array.from(sel) };
1205
  } else {
1206
+ sel = new Set();
1207
  const id = "j" + jsCounter++;
1208
  const e = { id: id, type: "obj", ymin: p.y, xmin: p.x, ymax: p.y, xmax: p.x,
1209
  desc: "", text: "", colors: [] };
1210
  els.push(e);
1211
+ sel = new Set([id]);
1212
  drag = { mode: "draw", e: e, start: p };
 
1213
  renderAll();
1214
  }
1215
  stage.setPointerCapture(ev.pointerId);
 
1219
  stage.addEventListener("pointermove", (ev) => {
1220
  if (!drag) return;
1221
  const p = toNorm(ev);
1222
+ const shift = ev.shiftKey;
1223
  if (drag.mode === "move") {
1224
+ let dx = p.x - drag.start.x, dy = p.y - drag.start.y;
1225
+ let gx0 = 1e9, gy0 = 1e9, gx1 = -1e9, gy1 = -1e9;
1226
+ drag.ids.forEach((id) => {
1227
+ const o = drag.orig[id];
1228
+ gx0 = Math.min(gx0, o.xmin); gy0 = Math.min(gy0, o.ymin);
1229
+ gx1 = Math.max(gx1, o.xmax); gy1 = Math.max(gy1, o.ymax);
1230
+ });
1231
+ dx = Math.max(-gx0, Math.min(1000 - gx1, dx));
1232
+ dy = Math.max(-gy0, Math.min(1000 - gy1, dy));
1233
+ drag.ids.forEach((id) => {
1234
+ const o = drag.orig[id], e = byId(id);
1235
+ if (!e) return;
1236
+ e.xmin = o.xmin + dx; e.ymin = o.ymin + dy;
1237
+ e.xmax = o.xmax + dx; e.ymax = o.ymax + dy;
1238
+ });
1239
  } else if (drag.mode === "draw") {
1240
+ const e = drag.e;
1241
  e.xmin = Math.min(drag.start.x, p.x);
1242
  e.xmax = Math.max(drag.start.x, p.x);
1243
  e.ymin = Math.min(drag.start.y, p.y);
1244
  e.ymax = Math.max(drag.start.y, p.y);
1245
  } else if (drag.mode === "resize") {
1246
+ resizeSingle(drag, p, shift);
1247
+ } else if (drag.mode === "gresize") {
1248
+ groupResize(drag, p, shift);
 
 
1249
  }
1250
  renderBoxes();
1251
  });
1252
 
1253
  function endDrag() {
1254
  if (!drag) return;
1255
+ if (drag.mode === "draw") {
1256
+ const e = drag.e;
1257
+ if (e.xmax - e.xmin < 8 || e.ymax - e.ymin < 8) {
1258
+ els = els.filter((x) => x !== e);
1259
+ sel = new Set();
1260
+ }
1261
  }
1262
  const wasDraw = drag.mode === "draw";
1263
  drag = null;
1264
  renderAll();
1265
+ applySelClasses();
1266
  emitNow();
1267
  if (wasDraw) {
1268
+ const pr = document.getElementById("panel-root");
1269
+ const card = pr && pr.querySelector(".ib-card.sel .ib-desc");
1270
  if (card) card.focus();
1271
  }
1272
  }
 
1274
  stage.addEventListener("pointercancel", endDrag);
1275
 
1276
  window.addEventListener("keydown", (ev) => {
1277
+ if (ev.key === "Delete" && sel.size) {
1278
  const t = document.activeElement;
1279
  if (t && (t.tagName === "TEXTAREA" || t.tagName === "INPUT")) return;
1280
+ els = els.filter((x) => !sel.has(x.id));
1281
+ sel = new Set();
1282
  renderAll();
1283
  emitNow();
1284
  }
 
1308
  if (!e) return;
1309
  e.type = ev.target.value;
1310
  renderAll();
1311
+ selectOnly(e.id);
1312
  emitNow();
1313
  });
1314
  document.addEventListener("click", (ev) => {
 
1320
  const e = byId(card.dataset.id);
1321
  if (ev.target.classList.contains("ib-del")) {
1322
  els = els.filter((x) => x.id !== card.dataset.id);
1323
+ sel.delete(card.dataset.id);
1324
  renderAll();
1325
  emitNow();
1326
  return;
 
1353
  });
1354
  return;
1355
  }
1356
+ if (!ev.target.closest(".ib-f, .ib-type, .ib-pal")) {
1357
+ if (ev.ctrlKey || ev.metaKey) toggleSelect(card.dataset.id);
1358
+ else selectOnly(card.dataset.id);
1359
+ }
1360
  });
1361
 
1362
  function addElement(type) {
1363
  const id = "j" + jsCounter++;
1364
  els.push({ id: id, type: type, ymin: 350, xmin: 350, ymax: 650, xmax: 650,
1365
  desc: "", text: "", colors: [] });
 
1366
  renderAll();
1367
+ selectOnly(id);
1368
  emitNow();
1369
  }
1370
 
 
1377
  if (Array.isArray(p.elements)) {
1378
  els = p.elements.map((e) => Object.assign({}, e));
1379
  }
1380
+ sel = new Set(Array.isArray(p.selected) ? p.selected : []);
1381
  renderAll();
1382
  } catch (e) {
1383
  console.error("IB.load", e);