partforge 0.104.0 → 0.105.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "partforge",
3
- "version": "0.104.0",
3
+ "version": "0.105.0",
4
4
  "description": "Turn a declarative part definition into a parametric-CAD web app (three.js + Manifold/Replicad). Requires a Vite-based consumer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -237,7 +237,16 @@ export function attachAnimationControls(viewer, part, {
237
237
  const nextAnimBtn = btn("pf-anim-page", "›", "Next animation");
238
238
  bar.append(prevAnimBtn, pick, title);
239
239
  const infoSlot = el("span", "pf-anim-info");
240
- const playBtn = btn("pf-anim-play", "▶", "Play animation");
240
+ // Inline SVGs, not the ▶/⏸ characters: U+23F8 takes its EMOJI presentation
241
+ // on iOS, so the pause glyph rendered as a color sticker in an otherwise
242
+ // monochrome bar. Both glyphs are PERSISTENT children toggled by the
243
+ // data-playing attribute (app.css) — the WebKit click-loss rule below
244
+ // forbids replacing the button's children while a press may be down, and an
245
+ // attribute write never eats a click.
246
+ const playBtn = btn("pf-anim-play", null, "Play animation");
247
+ playBtn.innerHTML =
248
+ '<svg class="pf-anim-glyph-play" viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M7 4.5v15l13-7.5z"/></svg>'
249
+ + '<svg class="pf-anim-glyph-pause" viewBox="0 0 24 24" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M7 4.5h3.4v15H7zM13.6 4.5H17v15h-3.6z"/></svg>';
241
250
  const scrubWrap = el("span", "pf-anim-scrub-wrap");
242
251
  const scrub = document.createElement("input");
243
252
  scrub.type = "range";
@@ -404,10 +413,12 @@ export function attachAnimationControls(viewer, part, {
404
413
  // syncUi() runs on every playback frame. Two rules keep that from eating the
405
414
  // user's clicks, and they are independent:
406
415
  //
407
- // 1. Text goes through textSetter (see above), so the button's text node is
408
- // mutated and never replaced. This is the one that matters, because it
409
- // holds even when the glyph legitimately changes under a held finger —
410
- // playback ending mid-press.
416
+ // 1. The play button's glyph flip is an ATTRIBUTE toggle over two persistent
417
+ // SVG children (app.css keys which one shows on [data-playing]) — its
418
+ // children are never touched after construction. This is the one that
419
+ // matters, because it holds even when the glyph legitimately changes
420
+ // under a held finger — playback ending mid-press. (The bubble's text
421
+ // still goes through textSetter, the text-node form of the same rule.)
411
422
  // 2. Each element has its own renderer that leaves early when its own value
412
423
  // is unchanged, so a playing transport touches only what actually moved:
413
424
  // the scrubber's value, and aria-valuetext when it crosses a reporting
@@ -420,13 +431,12 @@ export function attachAnimationControls(viewer, part, {
420
431
  // for. Measured in WebKit, any press held >= 40ms lost it (a real click is
421
432
  // ~100ms; a synthetic 0ms one survives, which is why automated clicking never
422
433
  // saw it). Reset was never affected: nothing rewrites that button per frame.
423
- const setPlayGlyph = textSetter(playBtn);
424
434
  let shownActive = null;
425
435
 
426
436
  function renderPlayButton(active) {
427
437
  if (active === shownActive) return;
428
438
  shownActive = active;
429
- setPlayGlyph(active ? "" : "▶");
439
+ playBtn.toggleAttribute("data-playing", active);
430
440
  const label = active ? "Pause animation" : "Play animation";
431
441
  playBtn.setAttribute("aria-label", label);
432
442
  playBtn.title = label;
@@ -737,6 +737,14 @@ button.action:focus-visible, .adv-toggle:focus-visible, .sec-title:focus-visible
737
737
  cursor: pointer; font-size: 13px; padding: 2px 4px;
738
738
  }
739
739
  .pf-anim-bar button:hover { color: var(--pf-text-2); }
740
+ /* Play/pause is two PERSISTENT inline SVGs toggled by [data-playing] — never
741
+ the ▶/⏸ characters (U+23F8 renders as a color emoji on iOS) and never a
742
+ child swap (WebKit drops a click whose press started on a replaced node —
743
+ see animation-controls.js). */
744
+ .pf-anim-play { display: inline-flex; align-items: center; justify-content: center; }
745
+ .pf-anim-play .pf-anim-glyph-pause { display: none; }
746
+ .pf-anim-play[data-playing] .pf-anim-glyph-play { display: none; }
747
+ .pf-anim-play[data-playing] .pf-anim-glyph-pause { display: block; }
740
748
  .pf-anim-title, .pf-anim-pick {
741
749
  font-family: var(--pf-mono); font-size: 11px; color: var(--pf-text-2);
742
750
  }
@@ -845,6 +853,20 @@ button.action:focus-visible, .adv-toggle:focus-visible, .sec-title:focus-visible
845
853
  .pf-anim-scrub { min-height: 44px; }
846
854
  }
847
855
 
856
+ /* On a phone the full transport is a two-row player squatting on a small
857
+ stage. Collapse it to the decisions that matter — which animation, and
858
+ play/pause: the timeline, reset and ⓘ stand down below the narrow
859
+ breakpoint. WIDTH-keyed, deliberately not the coarse-pointer condition the
860
+ target-size block above shares: a coarse-pointer iPad has the room and
861
+ keeps the full transport; only the small screen loses the player bar.
862
+ Playback itself still visits every chapter; scrubbing wants a real
863
+ screen. */
864
+ @media (max-width: 719px) {
865
+ .pf-anim-bar .pf-anim-scrub-wrap,
866
+ .pf-anim-bar .pf-anim-reset,
867
+ .pf-anim-bar .pf-anim-info { display: none; }
868
+ }
869
+
848
870
  /* Legacy id-only markup only: classed markup's viewbar lives inside .pf-stage
849
871
  (bottom-right, see chrome.css's .pf-float-viewbar) so it never meets the
850
872
  top-left floating #panel card. Legacy markup still floats #viewbar top-right