@omrips/viewstl 2.0.0-rc.6 → 2.0.0-rc.7

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/build/viewstl.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ //=========== Stl Viewer v2.0.0-rc.7, by Omri Rips, Viewstl.com, November 2026 ; admin@viewstl.com ===========
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -2369,7 +2370,7 @@ function modelOptions(input) {
2369
2370
  return out;
2370
2371
  }
2371
2372
  var StlViewer = class _StlViewer {
2372
- static version = "2.0.0-rc.6";
2373
+ static version = "2.0.0-rc.7";
2373
2374
  static workerURL = null;
2374
2375
  constructor(parent, options = {}) {
2375
2376
  if (!parent?.appendChild)
@@ -3823,7 +3824,7 @@ var StlViewer = class _StlViewer {
3823
3824
  null,
3824
3825
  scene.files.map((f) => f.bin)
3825
3826
  );
3826
- return new Blob([await job], { type: "application/zip" });
3827
+ return new Blob([await job], { type: "application/octet-stream" });
3827
3828
  }
3828
3829
  download_vsb(filename = "1.vsb") {
3829
3830
  return this.get_vsb_blob().then(
@@ -5593,7 +5594,7 @@ function mountViewstlApp(root, options = {}) {
5593
5594
  let animationUI;
5594
5595
  let toolsUI;
5595
5596
  let sceneImport;
5596
- let draining = false, busy = false, gridEnabled = false, gridHeight = 0, gridSpacing = 1, moveMode = false, transformTool = "move", modelMover, dragArrangement, background = "transparent";
5597
+ let draining = false, busy = false, gridEnabled = false, gridHeight = 0, gridSpacing = 1, moveMode = false, selectionMode = false, selectionPointer = null, transformTool = "move", modelMover, dragArrangement, background = "transparent";
5597
5598
  const viewNames = {
5598
5599
  flat: "Solid",
5599
5600
  edges: "Solid + edges",
@@ -5604,6 +5605,7 @@ function mountViewstlApp(root, options = {}) {
5604
5605
  orientation: "Face orientation"
5605
5606
  };
5606
5607
  const modeInputs = [...root.querySelectorAll('[name="view-mode"]')];
5608
+ const shortcutButtons = [...root.querySelectorAll("[data-viewstl-shortcut]")];
5607
5609
  if (options.id_prefix)
5608
5610
  modeInputs.forEach(
5609
5611
  (input) => input.name = options.id_prefix + "view-mode"
@@ -5623,7 +5625,44 @@ function mountViewstlApp(root, options = {}) {
5623
5625
  }
5624
5626
  function syncSelection() {
5625
5627
  viewer?.set_selection(
5626
- !busy && records.has(modelID) && (moveMode || !$("scene-panel").hidden) ? modelID : null
5628
+ !busy && records.has(modelID) && (selectionMode || moveMode || !$("scene-panel").hidden) ? modelID : null
5629
+ );
5630
+ }
5631
+ function syncShortcuts() {
5632
+ for (const button of shortcutButtons) {
5633
+ const action = button.dataset.viewstlShortcut;
5634
+ button.disabled = busy || !records.size || action === "save" && !exportUI;
5635
+ if (action === "select")
5636
+ button.setAttribute("aria-pressed", String(selectionMode));
5637
+ }
5638
+ }
5639
+ function setSelectionMode(enabled) {
5640
+ if (enabled) setMoveMode(false);
5641
+ selectionMode = !!enabled && !busy && records.size > 0;
5642
+ selectionPointer = null;
5643
+ if (viewer) {
5644
+ viewer.controls.enabled = !selectionMode;
5645
+ viewer.canvas.setAttribute(
5646
+ "aria-label",
5647
+ selectionMode ? "Select a model with a click. Escape returns to viewing." : "3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."
5648
+ );
5649
+ }
5650
+ $("workspace").classList.toggle("selecting-model-mode", selectionMode);
5651
+ syncShortcuts();
5652
+ syncSelection();
5653
+ }
5654
+ function editModelAt(event) {
5655
+ const hit = viewer.pick_model(event.clientX, event.clientY);
5656
+ if (!hit || !records.has(hit.id)) return;
5657
+ event.preventDefault();
5658
+ selectModel(hit.id);
5659
+ closePanels();
5660
+ $("scene-panel").hidden = false;
5661
+ $("scene-button").setAttribute("aria-expanded", "true");
5662
+ $("position-details").open = true;
5663
+ syncSelection();
5664
+ announce(
5665
+ records.get(hit.id).name + " selected. Choose Move, Rotate or Depth."
5627
5666
  );
5628
5667
  }
5629
5668
  function closePanel(name) {
@@ -5699,6 +5738,7 @@ function mountViewstlApp(root, options = {}) {
5699
5738
  printUI?.sync();
5700
5739
  animationUI?.sync();
5701
5740
  toolsUI?.sync();
5741
+ syncShortcuts();
5702
5742
  }
5703
5743
  function populateInfo() {
5704
5744
  if (modelID === null) return;
@@ -5739,6 +5779,7 @@ function mountViewstlApp(root, options = {}) {
5739
5779
  }
5740
5780
  function renderScene() {
5741
5781
  const has = records.size > 0;
5782
+ if (!has && selectionMode) setSelectionMode(false);
5742
5783
  $("workspace").classList.toggle("has-models", has);
5743
5784
  $("viewer").hidden = !has && !busy;
5744
5785
  $("empty-state").hidden = has || busy;
@@ -5757,6 +5798,7 @@ function mountViewstlApp(root, options = {}) {
5757
5798
  function setBusy(value) {
5758
5799
  if (value) exportUI?.close();
5759
5800
  if (disposed) return;
5801
+ if (value && selectionMode) setSelectionMode(false);
5760
5802
  if (value) setMoveMode(false);
5761
5803
  busy = value;
5762
5804
  $("loading-state").hidden = !value;
@@ -5894,6 +5936,7 @@ function mountViewstlApp(root, options = {}) {
5894
5936
  $("move-gesture-message").textContent = toolHints[transformTool] + " \xB7 Scroll to adjust depth";
5895
5937
  }
5896
5938
  function setMoveMode(enabled, tool = transformTool, retainPaused = false) {
5939
+ if (enabled && selectionMode) setSelectionMode(false);
5897
5940
  modelMover?.setEnabled(false);
5898
5941
  transformTool = tool;
5899
5942
  moveMode = !!enabled && modelID !== null && !busy;
@@ -5906,7 +5949,7 @@ function mountViewstlApp(root, options = {}) {
5906
5949
  if (viewer?.canvas)
5907
5950
  viewer.canvas.setAttribute(
5908
5951
  "aria-label",
5909
- moveMode ? toolHints[transformTool] + ". Scroll changes only its depth. Escape exits." : "3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."
5952
+ moveMode ? toolHints[transformTool] + ". Scroll changes only its depth. Escape exits." : selectionMode ? "Select a model with a click. Escape returns to viewing." : "3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."
5910
5953
  );
5911
5954
  if (moveMode) {
5912
5955
  closePanels();
@@ -6144,20 +6187,39 @@ function mountViewstlApp(root, options = {}) {
6144
6187
  }
6145
6188
  });
6146
6189
  listen(viewer.canvas, "dblclick", (event) => {
6147
- if (busy || event.button !== 0) return;
6148
- const hit = viewer.pick_model(event.clientX, event.clientY);
6149
- if (!hit || !records.has(hit.id)) return;
6190
+ if (busy || selectionMode || event.button !== 0) return;
6191
+ editModelAt(event);
6192
+ });
6193
+ listen(viewer.canvas, "pointerdown", (event) => {
6194
+ if (!selectionMode || busy || event.button !== 0 || !event.isPrimary)
6195
+ return;
6196
+ selectionPointer = {
6197
+ id: event.pointerId,
6198
+ x: event.clientX,
6199
+ y: event.clientY,
6200
+ moved: false
6201
+ };
6202
+ viewer.canvas.focus({ preventScroll: true });
6203
+ viewer.canvas.setPointerCapture(event.pointerId);
6150
6204
  event.preventDefault();
6151
- selectModel(hit.id);
6152
- closePanels();
6153
- $("scene-panel").hidden = false;
6154
- $("scene-button").setAttribute("aria-expanded", "true");
6155
- $("position-details").open = true;
6156
- syncSelection();
6157
- announce(
6158
- records.get(hit.id).name + " selected. Choose Move, Rotate or Depth."
6159
- );
6160
6205
  });
6206
+ listen(viewer.canvas, "pointermove", (event) => {
6207
+ if (selectionPointer?.id === event.pointerId && Math.hypot(
6208
+ event.clientX - selectionPointer.x,
6209
+ event.clientY - selectionPointer.y
6210
+ ) > 6)
6211
+ selectionPointer.moved = true;
6212
+ });
6213
+ listen(viewer.canvas, "pointerup", (event) => {
6214
+ const pointer = selectionPointer;
6215
+ selectionPointer = null;
6216
+ if (selectionMode && !busy && pointer?.id === event.pointerId && !pointer.moved)
6217
+ editModelAt(event);
6218
+ });
6219
+ for (const event of ["pointercancel", "lostpointercapture"])
6220
+ listen(viewer.canvas, event, () => {
6221
+ selectionPointer = null;
6222
+ });
6161
6223
  return viewer;
6162
6224
  }).catch((error) => {
6163
6225
  modelMover?.dispose();
@@ -6422,6 +6484,39 @@ function mountViewstlApp(root, options = {}) {
6422
6484
  }
6423
6485
  }
6424
6486
  root.querySelectorAll("[data-open-file]").forEach((b) => listen(b, "click", () => $("file-input").click()));
6487
+ for (const button of shortcutButtons)
6488
+ listen(button, "click", () => {
6489
+ if (button.disabled || busy || !records.size) return;
6490
+ const action = button.dataset.viewstlShortcut;
6491
+ if (action === "save") $("save-button")?.click();
6492
+ else if (action === "select") {
6493
+ setSelectionMode(!selectionMode);
6494
+ closePanels();
6495
+ announce(
6496
+ selectionMode ? "Selection mode. Click a model; Escape returns to viewing." : "Viewing mode. Drag to rotate the scene."
6497
+ );
6498
+ } else if ([
6499
+ "front",
6500
+ "back",
6501
+ "left",
6502
+ "right",
6503
+ "top",
6504
+ "bottom",
6505
+ "isometric"
6506
+ ].includes(action)) {
6507
+ setMoveMode(false);
6508
+ setSelectionMode(false);
6509
+ viewer.set_camera_view(action);
6510
+ toolsUI?.sync();
6511
+ announce(button.title || action + " view");
6512
+ }
6513
+ });
6514
+ listen(window2, "resize", () => {
6515
+ if (selectionMode && !shortcutButtons.some(
6516
+ (button) => button.dataset.viewstlShortcut === "select" && button.getClientRects().length
6517
+ ))
6518
+ setSelectionMode(false);
6519
+ });
6425
6520
  listen($("file-input"), "change", () => {
6426
6521
  const files = [...$("file-input").files];
6427
6522
  $("file-input").value = "";
@@ -6633,6 +6728,12 @@ function mountViewstlApp(root, options = {}) {
6633
6728
  listen(root, "keydown", (event) => {
6634
6729
  if (event.key !== "Escape") return;
6635
6730
  if ($("save-dialog")?.open) return;
6731
+ if (selectionMode) {
6732
+ setSelectionMode(false);
6733
+ closePanels();
6734
+ announce("Viewing mode. Drag to rotate the scene.");
6735
+ return;
6736
+ }
6636
6737
  if (moveMode) {
6637
6738
  setMoveMode(false);
6638
6739
  announce("Move tool closed.");
package/build/viewstl.js CHANGED
@@ -1,3 +1,5 @@
1
+ //=========== Stl Viewer v2.0.0-rc.7, by Omri Rips, Viewstl.com, November 2026 ; admin@viewstl.com ===========
2
+
1
3
  // src/math.js
2
4
  var add = (a, b) => a.map((v, i) => v + b[i]);
3
5
  var sub = (a, b) => a.map((v, i) => v - b[i]);
@@ -2342,7 +2344,7 @@ function modelOptions(input) {
2342
2344
  return out;
2343
2345
  }
2344
2346
  var StlViewer = class _StlViewer {
2345
- static version = "2.0.0-rc.6";
2347
+ static version = "2.0.0-rc.7";
2346
2348
  static workerURL = null;
2347
2349
  constructor(parent, options = {}) {
2348
2350
  if (!parent?.appendChild)
@@ -3796,7 +3798,7 @@ var StlViewer = class _StlViewer {
3796
3798
  null,
3797
3799
  scene.files.map((f) => f.bin)
3798
3800
  );
3799
- return new Blob([await job], { type: "application/zip" });
3801
+ return new Blob([await job], { type: "application/octet-stream" });
3800
3802
  }
3801
3803
  download_vsb(filename = "1.vsb") {
3802
3804
  return this.get_vsb_blob().then(
@@ -5565,7 +5567,7 @@ function mountViewstlApp(root, options = {}) {
5565
5567
  let animationUI;
5566
5568
  let toolsUI;
5567
5569
  let sceneImport;
5568
- let draining = false, busy = false, gridEnabled = false, gridHeight = 0, gridSpacing = 1, moveMode = false, transformTool = "move", modelMover, dragArrangement, background = "transparent";
5570
+ let draining = false, busy = false, gridEnabled = false, gridHeight = 0, gridSpacing = 1, moveMode = false, selectionMode = false, selectionPointer = null, transformTool = "move", modelMover, dragArrangement, background = "transparent";
5569
5571
  const viewNames = {
5570
5572
  flat: "Solid",
5571
5573
  edges: "Solid + edges",
@@ -5576,6 +5578,7 @@ function mountViewstlApp(root, options = {}) {
5576
5578
  orientation: "Face orientation"
5577
5579
  };
5578
5580
  const modeInputs = [...root.querySelectorAll('[name="view-mode"]')];
5581
+ const shortcutButtons = [...root.querySelectorAll("[data-viewstl-shortcut]")];
5579
5582
  if (options.id_prefix)
5580
5583
  modeInputs.forEach(
5581
5584
  (input) => input.name = options.id_prefix + "view-mode"
@@ -5595,7 +5598,44 @@ function mountViewstlApp(root, options = {}) {
5595
5598
  }
5596
5599
  function syncSelection() {
5597
5600
  viewer?.set_selection(
5598
- !busy && records.has(modelID) && (moveMode || !$("scene-panel").hidden) ? modelID : null
5601
+ !busy && records.has(modelID) && (selectionMode || moveMode || !$("scene-panel").hidden) ? modelID : null
5602
+ );
5603
+ }
5604
+ function syncShortcuts() {
5605
+ for (const button of shortcutButtons) {
5606
+ const action = button.dataset.viewstlShortcut;
5607
+ button.disabled = busy || !records.size || action === "save" && !exportUI;
5608
+ if (action === "select")
5609
+ button.setAttribute("aria-pressed", String(selectionMode));
5610
+ }
5611
+ }
5612
+ function setSelectionMode(enabled) {
5613
+ if (enabled) setMoveMode(false);
5614
+ selectionMode = !!enabled && !busy && records.size > 0;
5615
+ selectionPointer = null;
5616
+ if (viewer) {
5617
+ viewer.controls.enabled = !selectionMode;
5618
+ viewer.canvas.setAttribute(
5619
+ "aria-label",
5620
+ selectionMode ? "Select a model with a click. Escape returns to viewing." : "3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."
5621
+ );
5622
+ }
5623
+ $("workspace").classList.toggle("selecting-model-mode", selectionMode);
5624
+ syncShortcuts();
5625
+ syncSelection();
5626
+ }
5627
+ function editModelAt(event) {
5628
+ const hit = viewer.pick_model(event.clientX, event.clientY);
5629
+ if (!hit || !records.has(hit.id)) return;
5630
+ event.preventDefault();
5631
+ selectModel(hit.id);
5632
+ closePanels();
5633
+ $("scene-panel").hidden = false;
5634
+ $("scene-button").setAttribute("aria-expanded", "true");
5635
+ $("position-details").open = true;
5636
+ syncSelection();
5637
+ announce(
5638
+ records.get(hit.id).name + " selected. Choose Move, Rotate or Depth."
5599
5639
  );
5600
5640
  }
5601
5641
  function closePanel(name) {
@@ -5671,6 +5711,7 @@ function mountViewstlApp(root, options = {}) {
5671
5711
  printUI?.sync();
5672
5712
  animationUI?.sync();
5673
5713
  toolsUI?.sync();
5714
+ syncShortcuts();
5674
5715
  }
5675
5716
  function populateInfo() {
5676
5717
  if (modelID === null) return;
@@ -5711,6 +5752,7 @@ function mountViewstlApp(root, options = {}) {
5711
5752
  }
5712
5753
  function renderScene() {
5713
5754
  const has = records.size > 0;
5755
+ if (!has && selectionMode) setSelectionMode(false);
5714
5756
  $("workspace").classList.toggle("has-models", has);
5715
5757
  $("viewer").hidden = !has && !busy;
5716
5758
  $("empty-state").hidden = has || busy;
@@ -5729,6 +5771,7 @@ function mountViewstlApp(root, options = {}) {
5729
5771
  function setBusy(value) {
5730
5772
  if (value) exportUI?.close();
5731
5773
  if (disposed) return;
5774
+ if (value && selectionMode) setSelectionMode(false);
5732
5775
  if (value) setMoveMode(false);
5733
5776
  busy = value;
5734
5777
  $("loading-state").hidden = !value;
@@ -5866,6 +5909,7 @@ function mountViewstlApp(root, options = {}) {
5866
5909
  $("move-gesture-message").textContent = toolHints[transformTool] + " \xB7 Scroll to adjust depth";
5867
5910
  }
5868
5911
  function setMoveMode(enabled, tool = transformTool, retainPaused = false) {
5912
+ if (enabled && selectionMode) setSelectionMode(false);
5869
5913
  modelMover?.setEnabled(false);
5870
5914
  transformTool = tool;
5871
5915
  moveMode = !!enabled && modelID !== null && !busy;
@@ -5878,7 +5922,7 @@ function mountViewstlApp(root, options = {}) {
5878
5922
  if (viewer?.canvas)
5879
5923
  viewer.canvas.setAttribute(
5880
5924
  "aria-label",
5881
- moveMode ? toolHints[transformTool] + ". Scroll changes only its depth. Escape exits." : "3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."
5925
+ moveMode ? toolHints[transformTool] + ". Scroll changes only its depth. Escape exits." : selectionMode ? "Select a model with a click. Escape returns to viewing." : "3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."
5882
5926
  );
5883
5927
  if (moveMode) {
5884
5928
  closePanels();
@@ -6116,20 +6160,39 @@ function mountViewstlApp(root, options = {}) {
6116
6160
  }
6117
6161
  });
6118
6162
  listen(viewer.canvas, "dblclick", (event) => {
6119
- if (busy || event.button !== 0) return;
6120
- const hit = viewer.pick_model(event.clientX, event.clientY);
6121
- if (!hit || !records.has(hit.id)) return;
6163
+ if (busy || selectionMode || event.button !== 0) return;
6164
+ editModelAt(event);
6165
+ });
6166
+ listen(viewer.canvas, "pointerdown", (event) => {
6167
+ if (!selectionMode || busy || event.button !== 0 || !event.isPrimary)
6168
+ return;
6169
+ selectionPointer = {
6170
+ id: event.pointerId,
6171
+ x: event.clientX,
6172
+ y: event.clientY,
6173
+ moved: false
6174
+ };
6175
+ viewer.canvas.focus({ preventScroll: true });
6176
+ viewer.canvas.setPointerCapture(event.pointerId);
6122
6177
  event.preventDefault();
6123
- selectModel(hit.id);
6124
- closePanels();
6125
- $("scene-panel").hidden = false;
6126
- $("scene-button").setAttribute("aria-expanded", "true");
6127
- $("position-details").open = true;
6128
- syncSelection();
6129
- announce(
6130
- records.get(hit.id).name + " selected. Choose Move, Rotate or Depth."
6131
- );
6132
6178
  });
6179
+ listen(viewer.canvas, "pointermove", (event) => {
6180
+ if (selectionPointer?.id === event.pointerId && Math.hypot(
6181
+ event.clientX - selectionPointer.x,
6182
+ event.clientY - selectionPointer.y
6183
+ ) > 6)
6184
+ selectionPointer.moved = true;
6185
+ });
6186
+ listen(viewer.canvas, "pointerup", (event) => {
6187
+ const pointer = selectionPointer;
6188
+ selectionPointer = null;
6189
+ if (selectionMode && !busy && pointer?.id === event.pointerId && !pointer.moved)
6190
+ editModelAt(event);
6191
+ });
6192
+ for (const event of ["pointercancel", "lostpointercapture"])
6193
+ listen(viewer.canvas, event, () => {
6194
+ selectionPointer = null;
6195
+ });
6133
6196
  return viewer;
6134
6197
  }).catch((error) => {
6135
6198
  modelMover?.dispose();
@@ -6394,6 +6457,39 @@ function mountViewstlApp(root, options = {}) {
6394
6457
  }
6395
6458
  }
6396
6459
  root.querySelectorAll("[data-open-file]").forEach((b) => listen(b, "click", () => $("file-input").click()));
6460
+ for (const button of shortcutButtons)
6461
+ listen(button, "click", () => {
6462
+ if (button.disabled || busy || !records.size) return;
6463
+ const action = button.dataset.viewstlShortcut;
6464
+ if (action === "save") $("save-button")?.click();
6465
+ else if (action === "select") {
6466
+ setSelectionMode(!selectionMode);
6467
+ closePanels();
6468
+ announce(
6469
+ selectionMode ? "Selection mode. Click a model; Escape returns to viewing." : "Viewing mode. Drag to rotate the scene."
6470
+ );
6471
+ } else if ([
6472
+ "front",
6473
+ "back",
6474
+ "left",
6475
+ "right",
6476
+ "top",
6477
+ "bottom",
6478
+ "isometric"
6479
+ ].includes(action)) {
6480
+ setMoveMode(false);
6481
+ setSelectionMode(false);
6482
+ viewer.set_camera_view(action);
6483
+ toolsUI?.sync();
6484
+ announce(button.title || action + " view");
6485
+ }
6486
+ });
6487
+ listen(window2, "resize", () => {
6488
+ if (selectionMode && !shortcutButtons.some(
6489
+ (button) => button.dataset.viewstlShortcut === "select" && button.getClientRects().length
6490
+ ))
6491
+ setSelectionMode(false);
6492
+ });
6397
6493
  listen($("file-input"), "change", () => {
6398
6494
  const files = [...$("file-input").files];
6399
6495
  $("file-input").value = "";
@@ -6605,6 +6701,12 @@ function mountViewstlApp(root, options = {}) {
6605
6701
  listen(root, "keydown", (event) => {
6606
6702
  if (event.key !== "Escape") return;
6607
6703
  if ($("save-dialog")?.open) return;
6704
+ if (selectionMode) {
6705
+ setSelectionMode(false);
6706
+ closePanels();
6707
+ announce("Viewing mode. Drag to rotate the scene.");
6708
+ return;
6709
+ }
6608
6710
  if (moveMode) {
6609
6711
  setMoveMode(false);
6610
6712
  announce("Move tool closed.");