mapshaper 0.7.1 → 0.7.3

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.
Files changed (82) hide show
  1. package/LICENSE +1 -1
  2. package/mapshaper.js +1 -1
  3. package/package.json +5 -2
  4. package/www/.nojekyll +0 -0
  5. package/www/index.html +22 -6
  6. package/www/mapshaper-gui.js +292 -4
  7. package/www/mapshaper.js +1 -1
  8. package/www/page.css +188 -10
  9. package/www/docs/_assets/cmd-search.js +0 -213
  10. package/www/docs/_assets/docs.css +0 -712
  11. package/www/docs/_assets/docs.js +0 -75
  12. package/www/docs/_assets/highlight.css +0 -10
  13. package/www/docs/essentials/command-line.html +0 -127
  14. package/www/docs/essentials/command-line.html.md +0 -112
  15. package/www/docs/essentials/web-app.html +0 -138
  16. package/www/docs/essentials/web-app.html.md +0 -106
  17. package/www/docs/examples/basics.html +0 -276
  18. package/www/docs/examples/basics.html.md +0 -371
  19. package/www/docs/examples/data/Makefile +0 -31
  20. package/www/docs/examples/data/globe.msx +0 -0
  21. package/www/docs/examples/data/globe.svg +0 -616
  22. package/www/docs/examples/data/globe.txt +0 -21
  23. package/www/docs/examples/data/globe.zip +0 -0
  24. package/www/docs/examples/data/ne_50m_admin_0_countries.geojson +0 -1
  25. package/www/docs/examples/data/ne_50m_admin_1_states_provinces_lakes.geojson +0 -1
  26. package/www/docs/examples/data/us-states.msx +0 -0
  27. package/www/docs/examples/data/us-states.svg +0 -56
  28. package/www/docs/examples/data/us-states.txt +0 -6
  29. package/www/docs/examples/data/us-states.zip +0 -0
  30. package/www/docs/examples/globe.html +0 -108
  31. package/www/docs/examples/globe.html.md +0 -64
  32. package/www/docs/examples/us-states.html +0 -88
  33. package/www/docs/examples/us-states.html.md +0 -44
  34. package/www/docs/formats/csv.html +0 -127
  35. package/www/docs/formats/csv.html.md +0 -97
  36. package/www/docs/formats/dbf.html +0 -87
  37. package/www/docs/formats/dbf.html.md +0 -39
  38. package/www/docs/formats/flatgeobuf.html +0 -86
  39. package/www/docs/formats/flatgeobuf.html.md +0 -42
  40. package/www/docs/formats/geojson.html +0 -107
  41. package/www/docs/formats/geojson.html.md +0 -65
  42. package/www/docs/formats/geopackage.html +0 -87
  43. package/www/docs/formats/geopackage.html.md +0 -42
  44. package/www/docs/formats/json.html +0 -83
  45. package/www/docs/formats/json.html.md +0 -35
  46. package/www/docs/formats/kml.html +0 -82
  47. package/www/docs/formats/kml.html.md +0 -39
  48. package/www/docs/formats/overview.html +0 -192
  49. package/www/docs/formats/overview.html.md +0 -35
  50. package/www/docs/formats/shapefile.html +0 -123
  51. package/www/docs/formats/shapefile.html.md +0 -84
  52. package/www/docs/formats/snapshot.html +0 -87
  53. package/www/docs/formats/snapshot.html.md +0 -39
  54. package/www/docs/formats/svg.html +0 -99
  55. package/www/docs/formats/svg.html.md +0 -51
  56. package/www/docs/formats/topojson.html +0 -102
  57. package/www/docs/formats/topojson.html.md +0 -54
  58. package/www/docs/gallery/index.html +0 -80
  59. package/www/docs/gallery/index.html.md +0 -29
  60. package/www/docs/guides/combining-layers.html +0 -105
  61. package/www/docs/guides/combining-layers.html.md +0 -81
  62. package/www/docs/guides/expressions.html +0 -600
  63. package/www/docs/guides/expressions.html.md +0 -376
  64. package/www/docs/guides/programmatic.html +0 -117
  65. package/www/docs/guides/programmatic.html.md +0 -91
  66. package/www/docs/guides/projections.html +0 -158
  67. package/www/docs/guides/projections.html.md +0 -118
  68. package/www/docs/guides/simplification.html +0 -110
  69. package/www/docs/guides/simplification.html.md +0 -94
  70. package/www/docs/guides/topology.html +0 -90
  71. package/www/docs/guides/topology.html.md +0 -63
  72. package/www/docs/images/simplification-detail.png +0 -0
  73. package/www/docs/images/simplification-dp.png +0 -0
  74. package/www/docs/images/simplification-mod2.png +0 -0
  75. package/www/docs/index.html +0 -101
  76. package/www/docs/index.html.md +0 -59
  77. package/www/docs/reference.html +0 -1302
  78. package/www/docs/reference.html.md +0 -1817
  79. package/www/docs/whats-new.html +0 -76
  80. package/www/docs/whats-new.html.md +0 -53
  81. package/www/llms-full.txt +0 -4040
  82. package/www/llms.txt +0 -55
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2025, Matthew Bloch
1
+ Copyright (c) 2026, Matthew Bloch
2
2
 
3
3
  This Source Code Form is subject to the terms of the Mozilla Public
4
4
  License, v. 2.0. If a copy of the MPL was not distributed with this
package/mapshaper.js CHANGED
@@ -52127,7 +52127,7 @@ ${svg}
52127
52127
  });
52128
52128
  }
52129
52129
 
52130
- var version = "0.7.1";
52130
+ var version = "0.7.3";
52131
52131
 
52132
52132
  // Parse command line args into commands and run them
52133
52133
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapshaper",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "A tool for editing vector datasets for mapping and GIS.",
5
5
  "keywords": [
6
6
  "shapefile",
@@ -26,6 +26,7 @@
26
26
  "test": "mocha test",
27
27
  "build": "rollup --config",
28
28
  "docs": "node build-docs.mjs",
29
+ "roadmap": "node build-roadmap.mjs",
29
30
  "lint": "eslint --ext mjs src/",
30
31
  "prepublishOnly": "npm test; ./pre-publish",
31
32
  "postpublish": "./release_web_ui; ./release_github_version",
@@ -37,7 +38,9 @@
37
38
  "/www/**",
38
39
  "!/www/nacis/**",
39
40
  "/mapshaper.js",
40
- "!.DS_Store"
41
+ "!.DS_Store",
42
+ "!/www/docs/**",
43
+ "!/www/llms*"
41
44
  ],
42
45
  "dependencies": {
43
46
  "@ngageoint/geopackage": "^4.2.6",
package/www/.nojekyll ADDED
File without changes
package/www/index.html CHANGED
@@ -43,6 +43,14 @@
43
43
  <polygon points="8 14.5 13 19 13 4 3 4 3 19 8 14.5" fill="#30d4ef"/>
44
44
  </svg>
45
45
 
46
+ <svg id="undo-icon" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
47
+ <path d="M1.5,8 L7,4.5 L7,7 C10,7 12,9 12,11 L12,13 L10,13 L10,11 C10,10 9,9 7,9 L7,11.5 Z"/>
48
+ </svg>
49
+
50
+ <svg id="redo-icon" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
51
+ <path d="M14.5,8 L9,4.5 L9,7 C6,7 4,9 4,11 L4,13 L6,13 L6,11 C6,10 7,9 9,9 L9,11.5 Z"/>
52
+ </svg>
53
+
46
54
  </div>
47
55
 
48
56
  <div class="page-header">
@@ -60,10 +68,17 @@
60
68
  </div></div>
61
69
  <div id="mode-buttons" class="page-header-buttons">
62
70
  <!-- <span class="project-btn header-btn btn">Project</span><span class="separator"> -->
63
- <span class="console-btn header-btn btn">Console</span><span class="display-btn header-btn btn">Display</span><span class="separator"></span><span class="separator"></span></span><span class="simplify-btn header-btn btn">Simplify</span><span class="separator"></span><span class="export-btn header-btn btn">Export</span>
71
+ <span class="console-btn header-btn btn">Console</span><span class="display-btn header-btn btn">Display</span><span class="separator"></span><span class="separator"></span></span><span class="simplify-btn header-btn btn">Simplify</span><span class="separator"></span><span class="export-btn header-btn btn">Export</span><span class="separator"></span><span id="header-menu-btn" class="header-btn btn" role="button" aria-label="More links" aria-haspopup="true" aria-expanded="false" tabindex="0"><svg viewBox="0 0 16 16" aria-hidden="true" width="16" height="14"><rect x="1" y="3" width="14" height="2"/><rect x="1" y="7" width="14" height="2"/><rect x="1" y="11" width="14" height="2"/></svg></span>
72
+ <div id="header-menu-dropdown" class="hidden" role="menu" aria-labelledby="header-menu-btn">
73
+ <a class="header-menu-item" href="/docs/" target="_blank" rel="noopener" role="menuitem">Docs</a>
74
+ <a class="header-menu-item" href="https://github.com/mbloch/mapshaper" target="_blank" rel="noopener" role="menuitem">GitHub</a>
75
+ <a class="header-menu-item survey-en-link" href="https://tally.so/r/44Njok" target="_blank" rel="noopener" role="menuitem">1-minute survey</a>
76
+ <a class="header-menu-item survey-zh-link" href="https://tally.so/r/vGx7KQ" target="_blank" rel="noopener" lang="zh-CN" style="display:none" role="menuitem">意见反馈</a>
77
+ <a class="header-menu-item header-menu-sponsor" href="sponsor.html" target="_blank" rel="noopener" role="menuitem"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>Become a sponsor</a>
78
+ </div>
64
79
  </div>
65
80
  <div id="splash-buttons" class="page-header-buttons">
66
- <a href="/docs/"><span id="wiki-btn" class="header-btn btn">Docs</span></a><span class="separator"></span><a href="https://github.com/mbloch/mapshaper"><span id="github-btn" class="header-btn btn">GitHub</span></a><span class="separator"></span><a id="survey-en-link" href="https://tally.so/r/44Njok" target="_blank" rel="noopener"><span id="survey-en-btn" class="header-btn btn">1-minute survey</span></a><a id="survey-zh-link" href="https://tally.so/r/vGx7KQ" target="_blank" rel="noopener" style="display:none"><span id="survey-zh-btn" class="header-btn btn" lang="zh-CN">意见反馈</span></a><span class="separator"></span><a href="sponsor.html"><span id="sponsor-btn" class="header-btn btn"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg> Become a sponsor</span></a>
81
+ <a href="/docs/"><span id="wiki-btn" class="header-btn btn">Docs</span></a><span class="separator"></span><a href="https://github.com/mbloch/mapshaper"><span id="github-btn" class="header-btn btn">GitHub</span></a><span class="separator"></span><a class="survey-en-link" href="https://tally.so/r/44Njok" target="_blank" rel="noopener"><span id="survey-en-btn" class="header-btn btn">1-minute survey</span></a><a class="survey-zh-link" href="https://tally.so/r/vGx7KQ" target="_blank" rel="noopener" style="display:none"><span id="survey-zh-btn" class="header-btn btn" lang="zh-CN">意见反馈</span></a><span class="separator"></span><a href="sponsor.html"><span id="sponsor-btn" class="header-btn btn"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg> Become a sponsor</span></a>
67
82
  </div>
68
83
  </div>
69
84
 
@@ -351,10 +366,11 @@ encoding=big5</span>. Click to see all options.</div></div></div>
351
366
  if (/^zh(-Hans)?-CN\b/i.test(langs[i])) isChina = true;
352
367
  }
353
368
  if (isChina) {
354
- var zh = document.getElementById('survey-zh-link');
355
- var en = document.getElementById('survey-en-link');
356
- if (zh) zh.style.display = '';
357
- if (en) en.style.display = 'none';
369
+ // Swap every copy of the survey link (splash bar + hamburger menu)
370
+ var zhLinks = document.querySelectorAll('.survey-zh-link');
371
+ var enLinks = document.querySelectorAll('.survey-en-link');
372
+ for (var j = 0; j < zhLinks.length; j++) zhLinks[j].style.display = '';
373
+ for (var k = 0; k < enLinks.length; k++) enLinks[k].style.display = 'none';
358
374
  }
359
375
  })();
360
376
  </script>
@@ -5655,6 +5655,56 @@
5655
5655
  }
5656
5656
  }
5657
5657
 
5658
+ // Hamburger menu in the top-right of the header. After the user starts
5659
+ // editing, the splash bar (Docs / GitHub / Survey / Sponsor) is hidden by
5660
+ // gui.mjs; this menu is the post-edit replacement, exposing the same links
5661
+ // from a single button so the editing controls don't have to share the row.
5662
+
5663
+
5664
+ function HeaderMenu() {
5665
+ var btn = El('#header-menu-btn');
5666
+ var dropdown = El('#header-menu-dropdown');
5667
+ if (!btn.node() || !dropdown.node()) return; // markup missing; nothing to do
5668
+
5669
+ var open = false;
5670
+
5671
+ function setOpen(state) {
5672
+ if (state === open) return;
5673
+ open = state;
5674
+ dropdown.classed('hidden', !open);
5675
+ btn.attr('aria-expanded', open ? 'true' : 'false');
5676
+ }
5677
+
5678
+ btn.on('click', function(e) {
5679
+ e.stopPropagation();
5680
+ setOpen(!open);
5681
+ });
5682
+
5683
+ // Keyboard activation matches button semantics for the role="button" span.
5684
+ btn.on('keydown', function(e) {
5685
+ if (e.key === 'Enter' || e.key === ' ') {
5686
+ e.preventDefault();
5687
+ setOpen(!open);
5688
+ }
5689
+ });
5690
+
5691
+ // Outside click closes the menu. Clicks on menu items also close it (they
5692
+ // navigate or open in a new tab; either way the menu shouldn't linger).
5693
+ document.addEventListener('click', function(e) {
5694
+ if (!open) return;
5695
+ var target = e.target;
5696
+ if (btn.node().contains(target)) return;
5697
+ setOpen(false);
5698
+ });
5699
+
5700
+ document.addEventListener('keydown', function(e) {
5701
+ if (open && e.key === 'Escape') {
5702
+ setOpen(false);
5703
+ btn.node().focus();
5704
+ }
5705
+ });
5706
+ }
5707
+
5658
5708
  function SessionHistory(gui) {
5659
5709
  var commands = [];
5660
5710
  // index of first command after the last "save" boundary; commands at indices
@@ -5993,6 +6043,15 @@
5993
6043
 
5994
6044
  this.clear = function() {
5995
6045
  reset();
6046
+ fireHistoryChange();
6047
+ };
6048
+
6049
+ this.canUndo = function() {
6050
+ return history.length - offset > 0;
6051
+ };
6052
+
6053
+ this.canRedo = function() {
6054
+ return offset > 0;
5996
6055
  };
5997
6056
 
5998
6057
  function addHistoryState(undo, redo) {
@@ -6001,6 +6060,14 @@
6001
6060
  offset = 0;
6002
6061
  }
6003
6062
  history.push({undo, redo});
6063
+ fireHistoryChange();
6064
+ }
6065
+
6066
+ function fireHistoryChange() {
6067
+ gui.dispatchEvent('history_change', {
6068
+ canUndo: history.length - offset > 0,
6069
+ canRedo: offset > 0
6070
+ });
6004
6071
  }
6005
6072
 
6006
6073
  this.undo = function() {
@@ -6013,6 +6080,7 @@
6013
6080
  item.undo();
6014
6081
  gui.dispatchEvent('undo_redo_post', {type: 'undo'});
6015
6082
  gui.dispatchEvent('map-needs-refresh');
6083
+ fireHistoryChange();
6016
6084
  }
6017
6085
  };
6018
6086
 
@@ -6024,6 +6092,7 @@
6024
6092
  item.redo();
6025
6093
  gui.dispatchEvent('undo_redo_post', {type: 'redo'});
6026
6094
  gui.dispatchEvent('map-needs-refresh');
6095
+ fireHistoryChange();
6027
6096
  };
6028
6097
 
6029
6098
  function getHistoryItem() {
@@ -6337,6 +6406,10 @@
6337
6406
  return ['info', 'selection', 'data', 'box', 'labels', 'edit_points', 'rectangles'].includes(mode);
6338
6407
  };
6339
6408
 
6409
+ this.modeSupportsUndo = function(mode) {
6410
+ return ['data', 'labels', 'edit_points', 'edit_lines', 'edit_polygons', 'vertices', 'rectangles'].includes(mode);
6411
+ };
6412
+
6340
6413
  this.getMode = getInteractionMode;
6341
6414
 
6342
6415
  this.setMode = function(mode) {
@@ -6479,6 +6552,214 @@
6479
6552
  }
6480
6553
  }
6481
6554
 
6555
+ // A reusable floating toolbar anchored at the bottom-center of the map area.
6556
+ //
6557
+ // Multiple toolbars stack vertically inside a shared container, so additional
6558
+ // per-mode toolbars (e.g. feature styling) can coexist with the edit toolbar.
6559
+ //
6560
+ // The DOM is structured to leave room for a future drag handle without
6561
+ // requiring rework: the toolbar element is a flex row with a content slot,
6562
+ // and a sibling drag-handle slot can be added later.
6563
+ //
6564
+ // Constructor options:
6565
+ // name: optional CSS class added to the toolbar element
6566
+ // transition: ms for the show/hide transition (default 150)
6567
+ //
6568
+ // API:
6569
+ // toolbar.addButton(iconRef, opts) -> ToolbarButton
6570
+ // toolbar.addSeparator()
6571
+ // toolbar.show()
6572
+ // toolbar.hide()
6573
+ // toolbar.visible()
6574
+ // toolbar.node()
6575
+
6576
+ function FloatingToolbar(gui, opts) {
6577
+ opts = opts || {};
6578
+ var transitionMs = opts.transition || 150;
6579
+ var root = gui.container.findChild('.mshp-main-map');
6580
+ var stack = root.findChild('.floating-toolbar-stack');
6581
+ if (!stack) {
6582
+ stack = El('div').addClass('floating-toolbar-stack').appendTo(root);
6583
+ }
6584
+ var el = El('div').addClass('floating-toolbar');
6585
+ if (opts.name) el.addClass(opts.name);
6586
+ var content = El('div').addClass('floating-toolbar-content').appendTo(el);
6587
+ var visible = false;
6588
+ var hideTimer = null;
6589
+
6590
+ el.appendTo(stack);
6591
+ el.css('display', 'none');
6592
+
6593
+ // Hide when this gui instance becomes inactive (e.g. multi-instance mode)
6594
+ gui.on('active', updateVisibility);
6595
+ gui.on('inactive', updateVisibility);
6596
+
6597
+ this.addButton = function(iconRef, btnOpts) {
6598
+ return new ToolbarButton(content, iconRef, btnOpts || {});
6599
+ };
6600
+
6601
+ this.addSeparator = function() {
6602
+ return El('div').addClass('floating-toolbar-separator').appendTo(content);
6603
+ };
6604
+
6605
+ this.show = function() {
6606
+ if (visible) return;
6607
+ visible = true;
6608
+ updateVisibility();
6609
+ };
6610
+
6611
+ this.hide = function() {
6612
+ if (!visible) return;
6613
+ visible = false;
6614
+ updateVisibility();
6615
+ };
6616
+
6617
+ this.visible = function() {
6618
+ return visible;
6619
+ };
6620
+
6621
+ this.node = function() {
6622
+ return el.node();
6623
+ };
6624
+
6625
+ function updateVisibility() {
6626
+ var shouldShow = visible && GUI.isActiveInstance(gui);
6627
+ if (shouldShow) {
6628
+ clearTimeout(hideTimer);
6629
+ hideTimer = null;
6630
+ el.css('display', 'flex');
6631
+ // wait one frame so the browser registers the initial state before
6632
+ // the transition kicks in
6633
+ requestAnimationFrame(function() {
6634
+ el.addClass('visible');
6635
+ });
6636
+ } else {
6637
+ el.removeClass('visible');
6638
+ // wait for the transition to finish before hiding completely
6639
+ clearTimeout(hideTimer);
6640
+ hideTimer = setTimeout(function() {
6641
+ if (!(visible && GUI.isActiveInstance(gui))) {
6642
+ el.css('display', 'none');
6643
+ }
6644
+ }, transitionMs);
6645
+ }
6646
+ }
6647
+ }
6648
+
6649
+ function ToolbarButton(parent, iconRef, opts) {
6650
+ var btn = El('div').addClass('floating-toolbar-btn').appendTo(parent);
6651
+ if (iconRef) {
6652
+ var iconNode = El('body').findChild(iconRef);
6653
+ if (iconNode) {
6654
+ var icon = iconNode.node().cloneNode(true);
6655
+ if (icon.hasAttribute('id')) icon.removeAttribute('id');
6656
+ btn.node().appendChild(icon);
6657
+ }
6658
+ }
6659
+ var enabled = true;
6660
+ var clickHandlers = [];
6661
+
6662
+ if (opts.tooltip) setTooltip(opts.tooltip);
6663
+
6664
+ // Block native dblclick to avoid the map's double-click zoom
6665
+ btn.on('dblclick', function(e) { e.stopPropagation(); });
6666
+
6667
+ btn.on('click', function(e) {
6668
+ if (!enabled) return;
6669
+ for (var i = 0; i < clickHandlers.length; i++) {
6670
+ clickHandlers[i](e);
6671
+ }
6672
+ });
6673
+
6674
+ this.on = function(event, fn) {
6675
+ if (event == 'click') {
6676
+ clickHandlers.push(fn);
6677
+ } else {
6678
+ btn.on(event, fn);
6679
+ }
6680
+ return this;
6681
+ };
6682
+
6683
+ this.enable = function() { return this.setEnabled(true); };
6684
+ this.disable = function() { return this.setEnabled(false); };
6685
+
6686
+ this.setEnabled = function(b) {
6687
+ enabled = !!b;
6688
+ btn.classed('disabled', !enabled);
6689
+ return this;
6690
+ };
6691
+
6692
+ this.setTooltip = setTooltip;
6693
+
6694
+ this.node = function() {
6695
+ return btn.node();
6696
+ };
6697
+
6698
+ function setTooltip(text) {
6699
+ btn.attr('data-tooltip', text);
6700
+ }
6701
+ }
6702
+
6703
+ // Floating toolbar that exposes undo/redo while the user is in an editing
6704
+ // interaction mode. The toolbar is the first consumer of FloatingToolbar;
6705
+ // future per-mode toolbars (e.g. feature styling) can follow the same pattern.
6706
+
6707
+ function EditToolbar(gui) {
6708
+ var toolbar = new FloatingToolbar(gui, { name: 'edit-toolbar' });
6709
+ var isMac = navigator.userAgent.includes('Mac');
6710
+ var modKey = isMac ? '\u2318' : 'Ctrl+';
6711
+ var shiftKey = isMac ? '\u21e7' : 'Shift+';
6712
+
6713
+ var undoBtn = toolbar.addButton('#undo-icon', {
6714
+ tooltip: 'Undo (' + modKey + 'Z)'
6715
+ }).on('click', function() {
6716
+ gui.undo.undo();
6717
+ });
6718
+
6719
+ var redoBtn = toolbar.addButton('#redo-icon', {
6720
+ tooltip: 'Redo (' + shiftKey + modKey + 'Z)'
6721
+ }).on('click', function() {
6722
+ gui.undo.redo();
6723
+ });
6724
+
6725
+ updateButtons();
6726
+
6727
+ gui.on('interaction_mode_change', function() {
6728
+ updateVisibility();
6729
+ // history is cleared on mode change; refresh button states next tick
6730
+ updateButtons();
6731
+ });
6732
+
6733
+ gui.on('history_change', function(e) {
6734
+ undoBtn.setEnabled(!!e.canUndo);
6735
+ redoBtn.setEnabled(!!e.canRedo);
6736
+ // Visibility may also depend on history (e.g. attribute edits via popup
6737
+ // happen in modes that don't otherwise support undo).
6738
+ updateVisibility();
6739
+ });
6740
+
6741
+ updateVisibility();
6742
+
6743
+ function updateVisibility() {
6744
+ if (!gui.interaction) {
6745
+ toolbar.hide();
6746
+ return;
6747
+ }
6748
+ var mode = gui.interaction.getMode();
6749
+ var hasHistory = gui.undo.canUndo() || gui.undo.canRedo();
6750
+ if (gui.interaction.modeSupportsUndo(mode) || hasHistory) {
6751
+ toolbar.show();
6752
+ } else {
6753
+ toolbar.hide();
6754
+ }
6755
+ }
6756
+
6757
+ function updateButtons() {
6758
+ undoBtn.setEnabled(gui.undo.canUndo());
6759
+ redoBtn.setEnabled(gui.undo.canRedo());
6760
+ }
6761
+ }
6762
+
6482
6763
  function Model(gui) {
6483
6764
  var self = new internal.Catalog();
6484
6765
  var deleteLayer = self.deleteLayer;
@@ -9576,6 +9857,10 @@
9576
9857
 
9577
9858
  box.setDataCoords = function(bbox) {
9578
9859
  boxCoords = bbox;
9860
+ // Box is being placed programmatically (e.g. around a pinned rectangle);
9861
+ // enable pointer events on the handles so they can be dragged. Without
9862
+ // this, mousedowns fall through to the map and trigger pan.
9863
+ el.classed('hittable', true);
9579
9864
  redraw();
9580
9865
  };
9581
9866
 
@@ -9599,6 +9884,7 @@
9599
9884
  // remove the current box (if any)
9600
9885
  box.hide = function() {
9601
9886
  el.hide();
9887
+ el.classed('hittable', false);
9602
9888
  boxCoords = null;
9603
9889
  _visible = false;
9604
9890
  clickStartPoint = null;
@@ -10949,8 +11235,8 @@
10949
11235
  function showInstructions() {
10950
11236
  var isMac = navigator.userAgent.includes('Mac');
10951
11237
  var symbol = isMac ? '⌘' : '^';
10952
- var msg = `Instructions: Click on the map to add points. Move points by dragging. Type ${symbol}Z/${symbol}Y to undo/redo.`;
10953
- alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '360px'});
11238
+ var msg = `Instructions: click on the map to add points. Move points by dragging.`;
11239
+ alert = showPopupAlert(msg, null, { non_blocking: true, max_width: '290px'});
10954
11240
  }
10955
11241
 
10956
11242
  gui.on('interaction_mode_change', function(e) {
@@ -11167,9 +11453,9 @@
11167
11453
  function showInstructions() {
11168
11454
  var isMac = navigator.userAgent.includes('Mac');
11169
11455
  var undoKey = isMac ? '⌘' : '^';
11170
- var msg = `Instructions: click to start a path, click or drag to keep drawing. Drag vertices to reshape a path. Type ${undoKey}Z/${undoKey}Y to undo/redo.`;
11456
+ var msg = `Instructions: click to start a path, click or drag to keep drawing. Drag vertices to reshape a path.`;
11171
11457
  alert = showPopupAlert(msg, null, {
11172
- non_blocking: true, max_width: '388px'});
11458
+ non_blocking: true, max_width: '350px'});
11173
11459
  }
11174
11460
 
11175
11461
  function hideInstructions() {
@@ -14631,6 +14917,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
14631
14917
  new SessionSnapshots(gui);
14632
14918
  }
14633
14919
  gui.interaction = new InteractionMode(gui);
14920
+ gui.editToolbar = new EditToolbar(gui);
14634
14921
 
14635
14922
  gui.showProgressMessage = function(msg) {
14636
14923
  if (!gui.progressMessage) {
@@ -14755,6 +15042,7 @@ GUI and setting the size and crop of SVG output.</p><div><input type="text" clas
14755
15042
  new ImportControl(gui, importOpts);
14756
15043
  new ExportControl(gui);
14757
15044
  new LayerControl(gui);
15045
+ HeaderMenu();
14758
15046
  gui.console = new Console(gui);
14759
15047
 
14760
15048
  startEditing = function() {};
package/www/mapshaper.js CHANGED
@@ -52127,7 +52127,7 @@ ${svg}
52127
52127
  });
52128
52128
  }
52129
52129
 
52130
- var version = "0.7.1";
52130
+ var version = "0.7.3";
52131
52131
 
52132
52132
  // Parse command line args into commands and run them
52133
52133
  // Function takes an optional Node-style callback. A Promise is returned if no callback is given.