partforge 0.46.3 → 0.46.4
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
|
@@ -22,6 +22,20 @@ function btn(className, text, label) {
|
|
|
22
22
|
return b;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// Where the transport bar may sit, given the stage width, the bar's natural
|
|
26
|
+
// width, and the viewbar's left edge (all px, viewbarLeft stage-relative).
|
|
27
|
+
// null → the CSS default (centered) already clears the viewbar. Otherwise
|
|
28
|
+
// inline overrides: `left` slides the bar toward the stage's `margin`, and
|
|
29
|
+
// when even that isn't enough, `maxWidth` caps the bar so the `gap` holds.
|
|
30
|
+
export function planAnimBarPlacement({ stageWidth, barWidth, viewbarLeft }, { gap = 10, margin = 12 } = {}) {
|
|
31
|
+
const centeredLeft = (stageWidth - barWidth) / 2;
|
|
32
|
+
const limit = viewbarLeft - gap - barWidth;
|
|
33
|
+
if (centeredLeft <= limit) return null;
|
|
34
|
+
const left = Math.max(margin, limit);
|
|
35
|
+
const available = Math.max(0, viewbarLeft - gap - margin);
|
|
36
|
+
return barWidth > available ? { left, maxWidth: available } : { left };
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
// A setter for an element's text that MUTATES its existing text node instead of
|
|
26
40
|
// replacing it. `el.textContent = x` always replaces the node, and WebKit will
|
|
27
41
|
// not dispatch a `click` on an element whose text node was replaced between
|
|
@@ -260,6 +274,65 @@ export function attachAnimationControls(viewer, part, { container, applyValues,
|
|
|
260
274
|
syncStructure();
|
|
261
275
|
syncUi();
|
|
262
276
|
|
|
277
|
+
// --- placement: keep clear of the viewbar ---------------------------------
|
|
278
|
+
// chrome.css centers the bar (left: 50% / translateX(-50%)), and nothing in
|
|
279
|
+
// CSS can stop that centered position sliding under #viewbar when the stage
|
|
280
|
+
// narrows — the viewbar's width is dynamic (cutaway's Flip/Reset appear and
|
|
281
|
+
// disappear), so a static reservation would either overlap or waste centre
|
|
282
|
+
// space. Measure instead: when the two bars' vertical bands intersect, clamp
|
|
283
|
+
// the bar's left so a 10px gap to the viewbar holds, capping its width if
|
|
284
|
+
// even the stage's 12px margin isn't enough. Overrides are inline and
|
|
285
|
+
// cleared at the top of every pass, so chrome.css (or a host that
|
|
286
|
+
// re-anchors either bar out of the shared band) stays authoritative the
|
|
287
|
+
// moment the constraint stops binding. The clear-measure-apply sequence is
|
|
288
|
+
// loop-safe: it settles within one frame, so ResizeObserver — which reports
|
|
289
|
+
// rendered sizes at frame boundaries — never sees the intermediate state.
|
|
290
|
+
const viewbarEl = container.querySelector("#viewbar");
|
|
291
|
+
let placementRaf = 0;
|
|
292
|
+
function applyPlacement() {
|
|
293
|
+
placementRaf = 0;
|
|
294
|
+
bar.style.left = "";
|
|
295
|
+
bar.style.transform = "";
|
|
296
|
+
bar.style.maxWidth = "";
|
|
297
|
+
bar.style.overflow = "";
|
|
298
|
+
const vb = viewbarEl?.getBoundingClientRect();
|
|
299
|
+
const barRect = bar.getBoundingClientRect();
|
|
300
|
+
if (!vb || barRect.top >= vb.bottom || barRect.bottom <= vb.top) return;
|
|
301
|
+
const stageRect = container.getBoundingClientRect();
|
|
302
|
+
const plan = planAnimBarPlacement({
|
|
303
|
+
stageWidth: stageRect.width,
|
|
304
|
+
barWidth: barRect.width,
|
|
305
|
+
viewbarLeft: vb.left - stageRect.left,
|
|
306
|
+
});
|
|
307
|
+
if (!plan) return;
|
|
308
|
+
bar.style.left = `${plan.left}px`;
|
|
309
|
+
bar.style.transform = "none";
|
|
310
|
+
// maxWidth is the last resort, only reached when even the margin can't
|
|
311
|
+
// hold the gap — and the bar's flex children have hard minimums (~320px)
|
|
312
|
+
// that don't shrink to fit a tighter cap. overflow:hidden only applies
|
|
313
|
+
// here, inline, because a static rule in app.css would also clip the ⓘ
|
|
314
|
+
// info popover in the (far more common) uncapped state.
|
|
315
|
+
if (plan.maxWidth != null) {
|
|
316
|
+
bar.style.maxWidth = `${plan.maxWidth}px`;
|
|
317
|
+
bar.style.overflow = "hidden";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function schedulePlacement() {
|
|
321
|
+
if (typeof requestAnimationFrame !== "function") return applyPlacement();
|
|
322
|
+
if (!placementRaf) placementRaf = requestAnimationFrame(applyPlacement);
|
|
323
|
+
}
|
|
324
|
+
// Observing the bar itself catches content-driven width changes (step label
|
|
325
|
+
// text, animation switch); the viewbar, cutaway's actions; the stage, rail
|
|
326
|
+
// drags and window resizes.
|
|
327
|
+
const placementObserver = typeof ResizeObserver === "function"
|
|
328
|
+
? new ResizeObserver(schedulePlacement) : null;
|
|
329
|
+
if (placementObserver) {
|
|
330
|
+
placementObserver.observe(container);
|
|
331
|
+
placementObserver.observe(bar);
|
|
332
|
+
if (viewbarEl) placementObserver.observe(viewbarEl);
|
|
333
|
+
}
|
|
334
|
+
schedulePlacement();
|
|
335
|
+
|
|
263
336
|
const runtime = {
|
|
264
337
|
// An unknown name is a host bug, not a request to play whatever happens to
|
|
265
338
|
// be selected — say so and do nothing rather than silently animating
|
|
@@ -306,6 +379,8 @@ export function attachAnimationControls(viewer, part, { container, applyValues,
|
|
|
306
379
|
pick.removeEventListener("change", onPick);
|
|
307
380
|
resetBtn.removeEventListener("click", onResetClick);
|
|
308
381
|
info.dispose();
|
|
382
|
+
placementObserver?.disconnect();
|
|
383
|
+
if (placementRaf && typeof cancelAnimationFrame === "function") cancelAnimationFrame(placementRaf);
|
|
309
384
|
bar.remove();
|
|
310
385
|
},
|
|
311
386
|
__viewer: viewer, // test hook only
|
package/src/framework/app.css
CHANGED
|
@@ -222,6 +222,9 @@ button.action:focus-visible, .adv-toggle:focus-visible, #viewbar button:focus-vi
|
|
|
222
222
|
.pf-anim-bar {
|
|
223
223
|
display: flex; align-items: center; gap: 8px;
|
|
224
224
|
padding: 6px 10px;
|
|
225
|
+
/* Never shorter than #viewbar's card sharing the same bottom edge:
|
|
226
|
+
34px button + 2×4px padding + 2×1px border (border-box). */
|
|
227
|
+
min-height: 44px;
|
|
225
228
|
background: var(--pf-surface); border: 1px solid var(--pf-border);
|
|
226
229
|
border-radius: var(--pf-radius-control); box-shadow: var(--pf-shadow-float);
|
|
227
230
|
}
|
|
@@ -249,6 +252,11 @@ button.action:focus-visible, .adv-toggle:focus-visible, #viewbar button:focus-vi
|
|
|
249
252
|
background: var(--pf-muted); pointer-events: none;
|
|
250
253
|
}
|
|
251
254
|
|
|
255
|
+
@media (max-width: 360px) {
|
|
256
|
+
/* #viewbar's card is 40px here (30px buttons) — keep the floor in step. */
|
|
257
|
+
.pf-anim-bar { min-height: 40px; }
|
|
258
|
+
}
|
|
259
|
+
|
|
252
260
|
/* Legacy id-only markup only: classed markup's viewbar lives inside .pf-stage
|
|
253
261
|
(bottom-right, see chrome.css's .pf-float-viewbar) so it never meets the
|
|
254
262
|
top-left floating #panel card. Legacy markup still floats #viewbar top-right
|
package/src/framework/chrome.css
CHANGED
|
@@ -163,7 +163,10 @@
|
|
|
163
163
|
|
|
164
164
|
/* --- animation transport bar (generated by animation-controls.js) ----------
|
|
165
165
|
PLACEMENT ONLY, per the rule above; appearance lives in app.css next to
|
|
166
|
-
#viewbar's, so a host that re-anchors this bar still inherits its chrome.
|
|
166
|
+
#viewbar's, so a host that re-anchors this bar still inherits its chrome.
|
|
167
|
+
animation-controls.js may inline-override left/transform/max-width (and
|
|
168
|
+
overflow while width-capped) to hold a 10px gap to #viewbar, and clears
|
|
169
|
+
the overrides whenever centered placement fits. */
|
|
167
170
|
.pf-anim-bar {
|
|
168
171
|
position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
|
|
169
172
|
z-index: 15; max-width: calc(100% - 24px);
|