ofs-object 0.1.7 → 0.1.8

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.
@@ -33345,7 +33345,7 @@ class uf {
33345
33345
  y: (b.y + S.y) / 2
33346
33346
  });
33347
33347
  const T = ((r.x - b.x) * P + (r.y - b.y) * N) / C, I = Math.max(0, Math.min(1, T));
33348
- if (Math.hypot(r.x - (b.x + I * P), r.y - (b.y + I * N)) <= o && h.push({ a: E, b: L }), this.modes.edge && T >= 0.08 && T <= 0.92 && l(
33348
+ if (Math.hypot(r.x - (b.x + I * P), r.y - (b.y + I * N)) <= o && h.push({ a: E, b: L }), this.modes.edge && T > 0 && T < 1 && l(
33349
33349
  "edge",
33350
33350
  [E[0] + T * (L[0] - E[0]), E[1] + T * (L[1] - E[1])],
33351
33351
  { x: b.x + T * P, y: b.y + T * N },
@@ -33421,7 +33421,7 @@ class uf {
33421
33421
  }
33422
33422
  n("center", o.toGeographic(l.center)), n("midpoint", o.toGeographic(hx(l)));
33423
33423
  const h = um(l, o.toPlanar(e));
33424
- if (h.t > 0.08 && h.t < 0.92 && n("edge", o.toGeographic(h.point)), r) {
33424
+ if (h.t > 0 && h.t < 1 && n("edge", o.toGeographic(h.point)), r) {
33425
33425
  const f = um(l, o.toPlanar(r));
33426
33426
  f.t > 0 && f.t < 1 && n("perpendicular", o.toGeographic(f.point));
33427
33427
  }
@@ -33589,6 +33589,7 @@ class V4 extends zo {
33589
33589
  q(this, "currentSnapResult", null);
33590
33590
  q(this, "sourceId", "_ofs_grips_temp");
33591
33591
  q(this, "activeFeatureId", null);
33592
+ q(this, "editFilter", null);
33592
33593
  q(this, "activeLayerId", null);
33593
33594
  q(this, "activeGrip", null);
33594
33595
  q(this, "isDragging", !1);
@@ -33635,23 +33636,37 @@ class V4 extends zo {
33635
33636
  /**
33636
33637
  * Start editing a feature's geometry with grips
33637
33638
  */
33639
+ /**
33640
+ * Which features may be edited (null = any). Checked here, in the one method every way into
33641
+ * editing goes through - clicking, double-clicking, startEdit, the `grips` command, a box selection
33642
+ * that lands on one feature - so a new way in cannot forget it.
33643
+ */
33644
+ setEditFilter(e) {
33645
+ this.editFilter = e;
33646
+ }
33647
+ /**
33648
+ * Start editing a feature. Returns false when it cannot be edited: it does not exist, its geometry
33649
+ * type has no grips, or the edit filter rejects it.
33650
+ */
33638
33651
  editFeature(e, n) {
33652
+ if (this.editFilter && !this.editFilter(e, n)) return !1;
33639
33653
  if (this.isEnabled && this.activeLayerId === e && this.activeFeatureId === n)
33640
- return;
33654
+ return !0;
33641
33655
  const s = this.dataSource.getFeature(e, n);
33642
- !s || !["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"].includes(s.geometry.type) || (this.activeLayerId = e, this.activeFeatureId = n, this.isEnabled = !0, this.activeGrip = null, this.isDragging = !1, this.dragHasMoved = !1, this.initialFeatureSnapshot = null, this.activationTimestamp = Date.now(), this.setupLayers(), this.renderGrips(), this.bindEvents());
33656
+ return !s || !["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"].includes(s.geometry.type) ? !1 : (this.isEnabled && this.activeLayerId !== null && this.activeFeatureId !== null && this.emit("edit:end", { layerId: this.activeLayerId, featureId: this.activeFeatureId }), this.activeLayerId = e, this.activeFeatureId = n, this.isEnabled = !0, this.activeGrip = null, this.isDragging = !1, this.dragHasMoved = !1, this.initialFeatureSnapshot = null, this.activationTimestamp = Date.now(), this.setupLayers(), this.renderGrips(), this.bindEvents(), this.emit("edit:start", { layerId: e, featureId: n }), !0);
33643
33657
  }
33644
33658
  /**
33645
33659
  * Stop editing and clear grips
33646
33660
  */
33647
33661
  clear() {
33662
+ const e = this.isEnabled && this.activeLayerId !== null && this.activeFeatureId !== null ? { layerId: this.activeLayerId, featureId: this.activeFeatureId } : null;
33648
33663
  try {
33649
- const e = xe(this.mapAdapter);
33650
- e && e.dragPan && e.dragPan.enable(), e && e.getCanvas() && (e.getCanvas().style.cursor = "default");
33651
- } catch (e) {
33652
- Wt("OFSGripsEditor.clear", e);
33664
+ const n = xe(this.mapAdapter);
33665
+ n && n.dragPan && n.dragPan.enable(), n && n.getCanvas() && (n.getCanvas().style.cursor = "default");
33666
+ } catch (n) {
33667
+ Wt("OFSGripsEditor.clear", n);
33653
33668
  }
33654
- this.isEnabled = !1, this.activeFeatureId = null, this.activeLayerId = null, this.activeGrip = null, this.isDragging = !1, this.dragHasMoved = !1, this.initialFeatureSnapshot = null, this.currentSnapResult = null, this.unsubClick && (this.unsubClick(), this.unsubClick = null), this.unsubMouseDown && (this.unsubMouseDown(), this.unsubMouseDown = null), this.unsubMouseUp && (this.unsubMouseUp(), this.unsubMouseUp = null), this.unsubMove && (this.unsubMove(), this.unsubMove = null), this.unsubContextMenu && (this.unsubContextMenu(), this.unsubContextMenu = null), this.clearSource(), this.emit("grip:clear", void 0);
33669
+ this.isEnabled = !1, this.activeFeatureId = null, this.activeLayerId = null, this.activeGrip = null, this.isDragging = !1, this.dragHasMoved = !1, this.initialFeatureSnapshot = null, this.currentSnapResult = null, this.unsubClick && (this.unsubClick(), this.unsubClick = null), this.unsubMouseDown && (this.unsubMouseDown(), this.unsubMouseDown = null), this.unsubMouseUp && (this.unsubMouseUp(), this.unsubMouseUp = null), this.unsubMove && (this.unsubMove(), this.unsubMove = null), this.unsubContextMenu && (this.unsubContextMenu(), this.unsubContextMenu = null), this.clearSource(), this.emit("grip:clear", void 0), e && this.emit("edit:end", e);
33655
33670
  }
33656
33671
  getActiveFeature() {
33657
33672
  return !this.activeLayerId || this.activeFeatureId === null ? null : this.dataSource.getFeature(this.activeLayerId, this.activeFeatureId) || null;
@@ -35305,6 +35320,8 @@ class c3 extends zo {
35305
35320
  /** How far stored geometry may sag from the curves it samples */
35306
35321
  q(this, "sagToleranceMeters", jr);
35307
35322
  q(this, "targetLayerId", null);
35323
+ /** Which features Grips may edit (null = any) - see setEditFilter */
35324
+ q(this, "editFilter", null);
35308
35325
  q(this, "tempSourceId", "_ofs_draw_temp");
35309
35326
  q(this, "unsubClick", null);
35310
35327
  q(this, "unsubDblClick", null);
@@ -35351,7 +35368,16 @@ class c3 extends zo {
35351
35368
  }
35352
35369
  }
35353
35370
  });
35354
- this.dataSource = e, this.mapAdapter = n, this.snapping = new uf(e, n), this.grips = new V4(e, n, this.snapping), this.selectionOverlay = new r3(e, n), this.preview = new W4(n), this.bindMapEvents();
35371
+ q(this, "dataUnsubscribers", []);
35372
+ this.dataSource = e, this.mapAdapter = n, this.snapping = new uf(e, n), this.grips = new V4(e, n, this.snapping), this.selectionOverlay = new r3(e, n), this.preview = new W4(n), this.bindMapEvents(), this.dataUnsubscribers.push(
35373
+ this.grips.on("edit:start", (s) => this.emit("edit:start", s)),
35374
+ this.grips.on("edit:end", (s) => this.emit("edit:end", s)),
35375
+ // An edit filter usually depends on the selection and the active layer ("only the selected
35376
+ // features of the active layer"); when either changes, an edit it no longer allows ends here,
35377
+ // rather than every application wiring that up itself
35378
+ e.on("selection:changed", () => this.enforceEditFilter()),
35379
+ e.on("active-layer:changed", () => this.enforceEditFilter())
35380
+ );
35355
35381
  }
35356
35382
  setOrtho(e) {
35357
35383
  this.orthoEnabled = e, this.emit("draw:ortho-change", { enabled: e });
@@ -36065,11 +36091,13 @@ class c3 extends zo {
36065
36091
  if (this.currentMode === "modify") {
36066
36092
  if (this.grips.getIsEditing() && this.grips.hasHandleAt(e.coordinate, 18))
36067
36093
  return;
36068
- const h = !!((a = e.originalEvent) != null && a.shiftKey);
36069
- if (e.features && e.features.length > 0) {
36070
- const f = e.features[0];
36071
- if (f.id !== void 0 && f.layerId) {
36072
- this.dataSource.selectFeature(f.layerId, f.id, !1), this.grips.editFeature(f.layerId, f.id);
36094
+ const h = !!((a = e.originalEvent) != null && a.shiftKey), f = this.pickable(e.features).filter(
36095
+ (d) => d.id !== void 0 && !!d.layerId && this.canEdit(d.layerId, d.id)
36096
+ );
36097
+ if (f.length > 0) {
36098
+ const d = f[0];
36099
+ if (d.id !== void 0 && d.layerId) {
36100
+ this.isSelected(d.layerId, d.id) || this.dataSource.selectFeature(d.layerId, d.id, !1), this.grips.editFeature(d.layerId, d.id);
36073
36101
  return;
36074
36102
  }
36075
36103
  } else {
@@ -36151,19 +36179,17 @@ class c3 extends zo {
36151
36179
  * Handle double click on map (finishes LineString or Polygon)
36152
36180
  */
36153
36181
  handleMapDblClick(e) {
36182
+ var r;
36154
36183
  if (!this.currentMode) return;
36155
- const n = this.currentMode === "select" ? this.pickable(e.features) : [];
36156
- if (n.length > 0) {
36157
- const s = n[0];
36158
- if (s.id !== void 0 && s.layerId) {
36159
- this.startEdit(s.layerId, s.id);
36160
- return;
36161
- }
36184
+ const s = (this.currentMode === "select" ? this.pickable(e.features) : []).find((o) => o.id !== void 0 && !!o.layerId && this.canEdit(o.layerId, o.id));
36185
+ if (s && this.startEdit(s.layerId, s.id)) {
36186
+ (r = e.preventDefault) == null || r.call(e);
36187
+ return;
36162
36188
  }
36163
36189
  if (this.isDrawing && (this.currentMode === "line" || this.currentMode === "polygon" || this.currentMode === "select-polygon")) {
36164
36190
  if (this.activeCoordinates.length > 1) {
36165
- const s = this.activeCoordinates[this.activeCoordinates.length - 1], r = this.activeCoordinates[this.activeCoordinates.length - 2];
36166
- s[0] === r[0] && s[1] === r[1] && this.activeCoordinates.pop();
36191
+ const o = this.activeCoordinates[this.activeCoordinates.length - 1], a = this.activeCoordinates[this.activeCoordinates.length - 2];
36192
+ o[0] === a[0] && o[1] === a[1] && this.activeCoordinates.pop();
36167
36193
  }
36168
36194
  this.finishDrawing();
36169
36195
  }
@@ -36207,12 +36233,12 @@ class c3 extends zo {
36207
36233
  return;
36208
36234
  }
36209
36235
  if (this.currentMode === "select" || this.currentMode === "modify") {
36210
- const a = this.pickable(e.features)[0] ?? null, l = xe(this.mapAdapter), h = l && typeof l.getCanvas == "function" ? l.getCanvas() : null;
36211
- if (a && a.id !== void 0 && a.layerId) {
36212
- const f = this.dataSource.getLayer(a.layerId), d = f == null ? void 0 : f.getFeature(a.id);
36213
- this.selectionOverlay.setHoverFeature(d || a), h && (h.style.cursor = "pointer");
36236
+ const a = this.pickable(e.features), l = (this.currentMode === "modify" ? a.find((d) => d.id !== void 0 && !!d.layerId && this.canEdit(d.layerId, d.id)) : a[0]) ?? null, h = xe(this.mapAdapter), f = h && typeof h.getCanvas == "function" ? h.getCanvas() : null;
36237
+ if (l && l.id !== void 0 && l.layerId) {
36238
+ const d = this.dataSource.getLayer(l.layerId), p = d == null ? void 0 : d.getFeature(l.id);
36239
+ this.selectionOverlay.setHoverFeature(p || l), f && (f.style.cursor = "pointer");
36214
36240
  } else
36215
- this.selectionOverlay.setHoverFeature(null), h && (h.style.cursor = "default");
36241
+ this.selectionOverlay.setHoverFeature(null), f && (f.style.cursor = "default");
36216
36242
  return;
36217
36243
  }
36218
36244
  if (!this.currentMode) return;
@@ -36559,7 +36585,7 @@ class c3 extends zo {
36559
36585
  * Start interactive Grips editing on a feature
36560
36586
  */
36561
36587
  startEdit(e, n) {
36562
- return this.dataSource.getFeature(e, n) ? (this.dataSource.selectFeature(e, n), this.grips.editFeature(e, n), this.currentMode !== "modify" && (this.currentMode = "modify", this.emit("draw:mode-change", { mode: "modify" })), !0) : !1;
36588
+ return !this.dataSource.getFeature(e, n) || !this.canEdit(e, n) || (this.isSelected(e, n) || this.dataSource.selectFeature(e, n), !this.grips.editFeature(e, n)) ? !1 : (this.currentMode !== "modify" && (this.currentMode = "modify", this.emit("draw:mode-change", { mode: "modify" })), !0);
36563
36589
  }
36564
36590
  /**
36565
36591
  * Stop interactive Grips editing
@@ -36567,6 +36593,38 @@ class c3 extends zo {
36567
36593
  stopEdit() {
36568
36594
  this.grips.clear(), this.currentMode === "modify" && (this.currentMode = "select", this.emit("draw:mode-change", { mode: "select" }));
36569
36595
  }
36596
+ /**
36597
+ * Limit which features Grips may edit, e.g. only the selected features of the active layer:
36598
+ *
36599
+ * ```ts
36600
+ * draw.setEditFilter((layerId, id) =>
36601
+ * layerId === ds.getActiveLayerId() && ds.getSelectedFeatureIds(layerId).includes(id));
36602
+ * ```
36603
+ * Applies to every way into Grips: startEdit, clicking in modify mode (a rejected feature counts as
36604
+ * empty space), double-click to edit, the hover cursor, the `grips` command and a box selection
36605
+ * that lands on one feature. It limits Grips only - move, rotate, offset and the other commands
36606
+ * are not affected.
36607
+ *
36608
+ * An edit in progress is ended when the filter stops allowing it: when a new filter is set, and
36609
+ * when the selection or the active layer changes. If the filter depends on other application state,
36610
+ * call `stopEdit()` (or set the filter again) when that changes. `null` = any feature.
36611
+ */
36612
+ setEditFilter(e) {
36613
+ this.editFilter = e, this.grips.setEditFilter(e), this.enforceEditFilter();
36614
+ }
36615
+ /** End the current edit when the edit filter no longer allows it */
36616
+ enforceEditFilter() {
36617
+ if (!this.editFilter || !this.isEditing()) return;
36618
+ const e = this.getActiveEditingLayerId(), n = this.getActiveEditingFeatureId();
36619
+ e !== null && n !== null && !this.canEdit(e, n) && this.stopEdit();
36620
+ }
36621
+ isSelected(e, n) {
36622
+ return this.dataSource.getSelectedFeatureIds(e).some((s) => String(s) === String(n));
36623
+ }
36624
+ /** Whether Grips may edit this feature under the current edit filter */
36625
+ canEdit(e, n) {
36626
+ return !this.editFilter || this.editFilter(e, n);
36627
+ }
36570
36628
  /**
36571
36629
  * Check if geometry editing is currently active
36572
36630
  */
@@ -37316,7 +37374,7 @@ class c3 extends zo {
37316
37374
  }
37317
37375
  }
37318
37376
  destroy() {
37319
- this.unsubClick && this.unsubClick(), this.unsubDblClick && this.unsubDblClick(), this.unsubContextMenu && this.unsubContextMenu(), this.unsubMove && this.unsubMove(), this.unsubMouseDown && this.unsubMouseDown(), this.unsubMouseUp && this.unsubMouseUp(), typeof window < "u" && window.removeEventListener("keydown", this.handleKeyDown), this.cancel(), this.grips.clear(), this.preview.destroy(), this.selectionOverlay.destroy(), this.removeAllListeners();
37377
+ this.unsubClick && this.unsubClick(), this.unsubDblClick && this.unsubDblClick(), this.unsubContextMenu && this.unsubContextMenu(), this.unsubMove && this.unsubMove(), this.unsubMouseDown && this.unsubMouseDown(), this.unsubMouseUp && this.unsubMouseUp(), typeof window < "u" && window.removeEventListener("keydown", this.handleKeyDown), this.cancel(), this.grips.clear(), this.preview.destroy(), this.selectionOverlay.destroy(), this.dataUnsubscribers.forEach((e) => e()), this.dataUnsubscribers = [], this.removeAllListeners();
37320
37378
  }
37321
37379
  }
37322
37380
  const E_ = {
@@ -40984,50 +41042,57 @@ class Gx {
40984
41042
  }, "BaseGLAdapter.off");
40985
41043
  });
40986
41044
  }
41045
+ /**
41046
+ * Data features under a map event: rendered features within 6 px, falling back to a spatial point
41047
+ * query when nothing rendered matched. Click and double-click both use it.
41048
+ */
41049
+ featuresAtEvent(t, e) {
41050
+ const n = [
41051
+ [t.point.x - 6, t.point.y - 6],
41052
+ [t.point.x + 6, t.point.y + 6]
41053
+ ], r = (this.map.queryRenderedFeatures(n) || []).filter((o) => o.layer && !o.layer.id.startsWith("_ofs_")).map((o) => {
41054
+ var h;
41055
+ const a = this.dataLayerIdFor(o.layer.id);
41056
+ let l = ((h = o.properties) == null ? void 0 : h._ofs_id) ?? o.id;
41057
+ if (l == null && this.dataSource) {
41058
+ const f = this.dataSource.getLayer(a), d = f ? el.queryPoint(f, e) : [];
41059
+ d.length > 0 && (l = d[0].id);
41060
+ }
41061
+ return {
41062
+ id: l,
41063
+ layerId: a,
41064
+ properties: o.properties || {},
41065
+ geometry: o.geometry
41066
+ };
41067
+ }).filter((o) => this.dataSource ? this.dataSource.hasLayer(o.layerId) : !0);
41068
+ if (r.length === 0 && this.dataSource) {
41069
+ const o = this.dataSource.getAllLayers().filter((a) => a.visible);
41070
+ for (let a = o.length - 1; a >= 0; a--) {
41071
+ const l = o[a], h = el.queryPoint(l, e);
41072
+ if (h.length > 0) {
41073
+ r.push({
41074
+ id: h[0].id,
41075
+ layerId: l.id,
41076
+ properties: h[0].properties || {},
41077
+ geometry: h[0].geometry
41078
+ });
41079
+ break;
41080
+ }
41081
+ }
41082
+ }
41083
+ return r;
41084
+ }
40987
41085
  setupMapEventListeners() {
40988
41086
  this.map && (this.listen("style.load", () => {
40989
41087
  this.styleFontCache = void 0;
40990
41088
  }), this.listen("click", (t) => {
40991
- const e = [t.lngLat.lng, t.lngLat.lat], n = [
40992
- [t.point.x - 6, t.point.y - 6],
40993
- [t.point.x + 6, t.point.y + 6]
40994
- ], r = (this.map.queryRenderedFeatures(n) || []).filter((a) => a.layer && !a.layer.id.startsWith("_ofs_")).map((a) => {
40995
- var f;
40996
- const l = this.dataLayerIdFor(a.layer.id);
40997
- let h = ((f = a.properties) == null ? void 0 : f._ofs_id) ?? a.id;
40998
- if (h == null && this.dataSource) {
40999
- const d = this.dataSource.getLayer(l), p = d ? el.queryPoint(d, e) : [];
41000
- p.length > 0 && (h = p[0].id);
41001
- }
41002
- return {
41003
- id: h,
41004
- layerId: l,
41005
- properties: a.properties || {},
41006
- geometry: a.geometry
41007
- };
41008
- }).filter((a) => this.dataSource ? this.dataSource.hasLayer(a.layerId) : !0);
41009
- if (r.length === 0 && this.dataSource) {
41010
- const a = this.dataSource.getAllLayers().filter((l) => l.visible);
41011
- for (let l = a.length - 1; l >= 0; l--) {
41012
- const h = a[l], f = el.queryPoint(h, e);
41013
- if (f.length > 0) {
41014
- r.push({
41015
- id: f[0].id,
41016
- layerId: h.id,
41017
- properties: f[0].properties || {},
41018
- geometry: f[0].geometry
41019
- });
41020
- break;
41021
- }
41022
- }
41023
- }
41024
- const o = {
41089
+ const e = [t.lngLat.lng, t.lngLat.lat], n = this.featuresAtEvent(t, e), s = {
41025
41090
  coordinate: e,
41026
41091
  point: { x: t.point.x, y: t.point.y },
41027
- features: r,
41092
+ features: n,
41028
41093
  originalEvent: t.originalEvent
41029
41094
  };
41030
- [...this.clickHandlers].forEach((a) => a(o));
41095
+ [...this.clickHandlers].forEach((r) => r(s));
41031
41096
  }), this.listen("mousedown", (t) => {
41032
41097
  const n = {
41033
41098
  coordinate: [t.lngLat.lng, t.lngLat.lat],
@@ -41045,11 +41110,16 @@ class Gx {
41045
41110
  };
41046
41111
  [...this.mouseUpHandlers].forEach((s) => s(n));
41047
41112
  }), this.listen("dblclick", (t) => {
41048
- const n = {
41049
- coordinate: [t.lngLat.lng, t.lngLat.lat],
41113
+ const e = [t.lngLat.lng, t.lngLat.lat], n = {
41114
+ coordinate: e,
41050
41115
  point: { x: t.point.x, y: t.point.y },
41051
- features: [],
41052
- originalEvent: t.originalEvent
41116
+ // Features too, or "double-click a feature to edit its vertices" never finds one
41117
+ features: this.featuresAtEvent(t, e),
41118
+ originalEvent: t.originalEvent,
41119
+ // MapLibre / Mapbox: stops DoubleClickZoomHandler for this one double-click
41120
+ preventDefault: () => {
41121
+ typeof t.preventDefault == "function" && t.preventDefault();
41122
+ }
41053
41123
  };
41054
41124
  [...this.dblClickHandlers].forEach((s) => s(n));
41055
41125
  }), this.listen("contextmenu", (t) => {
@@ -42280,7 +42350,9 @@ function VO(i, t) {
42280
42350
  if (!H)
42281
42351
  return i.grips.clear(), it.print("Grips cleared. Please select a feature to edit.", "output"), K;
42282
42352
  const { layerId: J, featureId: nt } = H;
42283
- return hn(it, J, t.getActiveLayerId()), i.grips.editFeature(J, nt), it.print(`Grips editing active for feature [${nt}]. Drag blue vertices or midpoint handles to reshape.`, "success"), K;
42353
+ if (hn(it, J, t.getActiveLayerId()), !i.grips.editFeature(J, nt))
42354
+ throw new Error(`Feature [${nt}] in "${J}" cannot be edited here.`);
42355
+ return it.print(`Grips editing active for feature [${nt}]. Drag blue vertices or midpoint handles to reshape.`, "success"), K;
42284
42356
  }
42285
42357
  }, _ = {
42286
42358
  name: "move",
package/docs/API.md CHANGED
@@ -141,6 +141,21 @@ ofs.drawTools.startEdit('parcels', 'plot-1');
141
141
  // - กดปุ่ม Delete / Backspace: ลบจุดมุมที่กำลังเลือก
142
142
  // - หมายเหตุรอยต่อแปลง: เมื่อวาดหรือลาก Snap ชนกัน จุดพิกัดจะแนบสนิท 100%
143
143
  // เส้นสีขาวที่เห็นระหว่างแปลงคือขอบเส้น Outline Stroke (${layerId}_stroke) ที่ระบบสร้างขึ้นอัตโนมัติ
144
+
145
+ // จำกัดว่า Grips แก้ feature ไหนได้ (0.1.8) — เช่น เฉพาะตัวที่เลือกอยู่ใน active layer
146
+ ofs.drawTools.setEditFilter((layerId, id) =>
147
+ layerId === ofs.dataSource.getActiveLayerId() &&
148
+ ofs.dataSource.getSelectedFeatureIds(layerId).some((s) => String(s) === String(id)));
149
+ ofs.drawTools.canEdit('parcels', 'plot-1'); // ตรวจก่อนเปิดปุ่ม "แก้รูปทรง"
150
+ ofs.drawTools.setEditFilter(null); // แก้ได้ทุกตัว (ค่าเริ่มต้น)
151
+ // - ครอบทุกทางเข้า Grips: startEdit (คืน false), คลิกในโหมด modify (ตัวที่ห้าม = คลิกที่ว่าง),
152
+ // ดับเบิลคลิก, cursor ตอน hover, คำสั่ง `grips` และลากกรอบเลือกแล้วได้ตัวเดียว
153
+ // - จำกัดเฉพาะ Grips — move / rotate / offset และคำสั่งอื่นไม่เกี่ยว
154
+ // - จบการแก้ให้เองเมื่อ filter ไม่ยอมแล้ว: ตอนตั้ง filter ใหม่ และเมื่อ selection / active layer เปลี่ยน
155
+ // - เริ่มแก้ตัวที่เลือกอยู่แล้ว selection เดิมไม่ถูกล้าง (สลับไปแก้ตัวอื่นที่เลือกไว้ได้)
156
+ ofs.drawTools.on('edit:start', ({ layerId, featureId }) => { /* ... */ });
157
+ ofs.drawTools.on('edit:end', ({ layerId, featureId }) => { /* ... */ }); // สลับตัว = end แล้ว start
158
+ // ดับเบิลคลิก feature ในโหมด select = เข้า Grips โดยแผนที่ไม่ซูม · ดับเบิลคลิกที่ว่างยังซูมตามเดิม
144
159
  ```
145
160
 
146
161
  #### การใช้ฟังก์ชัน CAD ขั้นสูง (`CADOperations`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofs-object",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Framework-agnostic TypeScript Library for GIS Map Tools (Data Adapter, Draw Tools, Legend & Thematic Symbology, Map Adapters, Web CLI & AI Bridge)",
5
5
  "license": "MIT",
6
6
  "author": "On-Farm Solution Co.,Ltd.",