kuinetic 0.2.1 → 0.2.2

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/README.md CHANGED
@@ -123,16 +123,23 @@ its readable, unminified twin, about twice the transfer size; use it only for de
123
123
  `kuinetic.advanced.min.js` is a tier: it needs core on the page, in either order, and never carries
124
124
  a copy of it.
125
125
 
126
- Every one of those browser bundles starts itself once the document is ready. `data-kui-manual` on a
127
- `<script>` tag turns that off for that file. Importing from npm does **not** start anything — a
128
- module import stays side-effect-free, which is the guarantee the bundler path is built on.
126
+ Every one of those browser bundles starts itself once the document is ready. Importing from npm does
127
+ **not** start anything — a module import stays side-effect-free, which is the guarantee the bundler
128
+ path is built on.
129
+
130
+ To drive it by hand, put `data-kui-manual` on the tag. On `kuinetic.js` that turns off the
131
+ *auto-start* only: nothing is created or started until you call `kuinetic.kuinetic({ ... }).start()`,
132
+ and any tier bundle you load (`kuinetic.advanced.js`, ...) still registers into the animator you
133
+ build, in either tag order. On a tier's own tag it takes that tier out entirely.
129
134
 
130
135
  > **Changed in 0.2.** These bundles used to do nothing until you called
131
136
  > `kuinetic.kuinetic({ observe: true }).start()` yourself, and `kuinetic.all.js` started itself the
132
137
  > instant it parsed rather than at `DOMContentLoaded`. If you already write that call, it keeps
133
- > working and your animator is still the one the page uses — you will see one console line saying
134
- > the call is now redundant. Add `data-kui-manual` to the tag to silence it and go back to driving
135
- > everything by hand.
138
+ > working and your animator is still the one the page uses. A call that names its own options — a
139
+ > `reporter`, a scoped `root`, a `registry` — is how you configure the auto-started animator, and it
140
+ > is adopted without comment. A call with no options, or only `{ observe: true }`, does nothing the
141
+ > script tag has not already done, so you will see one console line saying it is now redundant. Delete
142
+ > it, or add `data-kui-manual` to the tag to go back to driving everything by hand.
136
143
 
137
144
  ## Your first animation
138
145
 
@@ -1836,7 +1836,7 @@ function parseAngleRadians(raw) {
1836
1836
  if (!match) return 0;
1837
1837
  const value = parseFloat(match[1]);
1838
1838
  if (!Number.isFinite(value)) return 0;
1839
- return value * (ANGLE_UNITS[(match[2] ?? "deg").toLowerCase()] ?? ANGLE_UNITS.deg);
1839
+ return value * ANGLE_UNITS[(match[2] ?? "deg").toLowerCase()];
1840
1840
  }
1841
1841
  var PALETTE_SIZE = 5;
1842
1842
  var DEFAULT_PALETTE = [
@@ -2067,7 +2067,7 @@ function paintBoxOf(content, natural, cs) {
2067
2067
  if (bw <= 0 || bh <= 0) return content;
2068
2068
  const [dw, dh] = fittedSize(cs.objectFit || "fill", bw, bh, natural);
2069
2069
  const tokens = (cs.objectPosition || "50% 50%").trim().split(/\s+/);
2070
- const left = content.left + positionOffset(tokens[0] ?? "50%", bw - dw);
2070
+ const left = content.left + positionOffset(tokens[0], bw - dw);
2071
2071
  const top = content.top + positionOffset(tokens[1] ?? "50%", bh - dh);
2072
2072
  return { left, top, right: left + dw, bottom: top + dh };
2073
2073
  }
@@ -2333,7 +2333,7 @@ function extractShaderOptions(params, createCanvas) {
2333
2333
  ...readDuotoneColors(c1Text, c2Text, createCanvas),
2334
2334
  blendMode: blendMap[params.text("blend", "normal")] ?? 0,
2335
2335
  to: params.text("to", ""),
2336
- progress: params.num ? params.num("progress", -1) : -1,
2336
+ progress: params.num("progress", -1),
2337
2337
  // Compared against the one enabling word rather than "not `off`", so — exactly as with
2338
2338
  // `audioBand` below — a caller passing an unvalidated accessor, or an author who typed
2339
2339
  // `scrub: scrol`, cannot turn the bridge on by accident.
@@ -2770,7 +2770,7 @@ function restoreInstanceOpacity(el, state) {
2770
2770
  if (!state.hiddenByRenderer) return;
2771
2771
  state.hiddenByRenderer = false;
2772
2772
  const style = styleOf(state.ledgers, el);
2773
- if (style) style.set("opacity", style.peek("opacity") ?? "");
2773
+ if (style) style.set("opacity", style.peek("opacity"));
2774
2774
  }
2775
2775
  var SHADER_TEXTURE_GRACE_MS = 5e3;
2776
2776
  function acquireRenderer(info, held) {
@@ -3938,7 +3938,7 @@ function prepareCameraScene(el, params, ctx) {
3938
3938
  const controller = new CameraController(htmlEl, options, resolvedEnv);
3939
3939
  const layerEls = ownedDescendants(htmlEl, "camera-scene", "camera-layer");
3940
3940
  for (const layer of layerEls) {
3941
- const kui = layer.getAttribute("data-kui") || "";
3941
+ const kui = layer.getAttribute("data-kui");
3942
3942
  const zMatch = /z:([+-]?[0-9.]+)(?:px)?/.exec(kui);
3943
3943
  const depthZ = zMatch ? parseDepth(zMatch[1]) : 0;
3944
3944
  controller.addLayer(layer, depthZ);
@@ -41,7 +41,7 @@ import {
41
41
  toMilliseconds,
42
42
  warnAboutActivation,
43
43
  warnAboutToggleActions
44
- } from "./chunk-U43BX5O2.mjs";
44
+ } from "./chunk-K2M7O6W5.mjs";
45
45
  import {
46
46
  createLedgerSet
47
47
  } from "./chunk-AEQGC2KM.mjs";
@@ -900,6 +900,7 @@ function emptyPlan(unknown, warnings) {
900
900
  unknown,
901
901
  reducedMotion: "shorten",
902
902
  supportedActivations: [],
903
+ activationClaims: [],
903
904
  supportedTimelines: [],
904
905
  channels: [],
905
906
  warnings
@@ -1033,6 +1034,10 @@ function buildPlan(entries, timeline, unknown, warnings) {
1033
1034
  plan.fxNames.push(preset.name);
1034
1035
  plan.reducedMotion = strictestPolicy(plan.reducedMotion, primitive.reducedMotion);
1035
1036
  activations = intersect(activations, primitive.supportedActivations);
1037
+ plan.activationClaims.push({
1038
+ supported: primitive.supportedActivations,
1039
+ entrance: phaseOf(entry) === "entrance"
1040
+ });
1036
1041
  timelines = intersect(timelines, primitive.supportedTimelines);
1037
1042
  for (const channel of channelsFor(entry)) channels.add(channel);
1038
1043
  warnUnsupportedTimeline(preset.name, primitive.supportedTimelines, timeline, warnings);
@@ -2533,6 +2538,7 @@ var Animator = class {
2533
2538
  el,
2534
2539
  spec: resolveActivationSpec(config.activation),
2535
2540
  supported: plan.supportedActivations,
2541
+ claims: plan.activationClaims,
2536
2542
  reporter: this.reporter
2537
2543
  });
2538
2544
  return config.activation;
@@ -541,18 +541,25 @@ function defaultObserverFactory() {
541
541
  if (typeof IntersectionObserver === "undefined") return void 0;
542
542
  return (callback, init) => new IntersectionObserver(callback, init);
543
543
  }
544
+ function onlyBoundLate(name, claims) {
545
+ const authorising = authorisingActivations(name);
546
+ if (!authorising.includes("enter")) return false;
547
+ const suits = (claim) => authorising.some((named) => claim.supported.includes(named));
548
+ if (!claims.some((claim) => claim.entrance && suits(claim))) return false;
549
+ return claims.every((claim) => suits(claim) || claim.supported.includes("load"));
550
+ }
544
551
  function warnAboutActivation(request) {
545
552
  warnUnsupported(request);
546
553
  warnUnknownEvents(request);
547
554
  }
548
- function warnUnsupported({ el, spec, supported, reporter }) {
549
- if (supported.length === 0) return;
555
+ function warnUnsupported({ el, spec, supported, claims, reporter }) {
556
+ const declaredAny = supported.length > 0 || (claims?.some((claim) => claim.supported.length > 0) ?? false);
557
+ if (!declaredAny) return;
558
+ const shared = supported.length > 0 ? supported.join(", ") : "none in common across the composed effects";
550
559
  for (const name of spec.names) {
551
560
  if (authorisingActivations(name).some((named) => supported.includes(named))) continue;
552
- reporter.warn(
553
- `activation "${name}" is not supported by this effect (supports: ${supported.join(", ")})`,
554
- el
555
- );
561
+ if (claims && onlyBoundLate(name, claims)) continue;
562
+ reporter.warn(`activation "${name}" is not supported by this effect (supports: ${shared})`, el);
556
563
  }
557
564
  }
558
565
  function warnUnknownEvents({ el, spec, reporter }) {
@@ -2682,11 +2689,20 @@ function createRootResolver(options) {
2682
2689
  const windowRoot = windowScrollRoot(win);
2683
2690
  return (el) => {
2684
2691
  for (let node = el.parentElement; node; node = node.parentElement) {
2692
+ if (scrollsTheViewport(node, win)) return windowRoot;
2685
2693
  if (isScrollable(node)) return elementScrollRoot(node, win);
2686
2694
  }
2687
2695
  return windowRoot;
2688
2696
  };
2689
2697
  }
2698
+ function scrollsTheViewport(node, win) {
2699
+ const root = win.document.documentElement;
2700
+ if (node === root) return true;
2701
+ if (node !== win.document.body) return false;
2702
+ const rootStyle = win.getComputedStyle(root);
2703
+ return !CLIPPING_OVERFLOW.has(rootStyle.overflowX) && !CLIPPING_OVERFLOW.has(rootStyle.overflowY);
2704
+ }
2705
+ var CLIPPING_OVERFLOW = /* @__PURE__ */ new Set(["auto", "scroll", "overlay", "hidden", "clip"]);
2690
2706
  var SCROLLABLE_OVERFLOW = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
2691
2707
  function defaultIsScrollable(win) {
2692
2708
  return (el) => {
@@ -26,7 +26,7 @@ import {
26
26
  toPixels,
27
27
  validate,
28
28
  waapiEasingValue
29
- } from "./chunk-U43BX5O2.mjs";
29
+ } from "./chunk-K2M7O6W5.mjs";
30
30
  import {
31
31
  createAttributeLedger,
32
32
  createStyleLedger
@@ -4643,6 +4643,10 @@ function recognise(el, handlers, options = {}, deps = defaultGestureDeps()) {
4643
4643
  let active = false;
4644
4644
  let longPressTimer = null;
4645
4645
  let longPressFired = false;
4646
+ let captured = false;
4647
+ let pointerId = null;
4648
+ let lastHandled = null;
4649
+ let documentListening = null;
4646
4650
  function sampleOf(event) {
4647
4651
  return { x: event.clientX, y: event.clientY, time: deps.now() };
4648
4652
  }
@@ -4660,7 +4664,9 @@ function recognise(el, handlers, options = {}, deps = defaultGestureDeps()) {
4660
4664
  samples = [origin];
4661
4665
  active = false;
4662
4666
  longPressFired = false;
4663
- if (capturePointer) el.setPointerCapture?.(event.pointerId);
4667
+ pointerId = event.pointerId;
4668
+ captured = capturePointer === true && takeCapture(event.pointerId);
4669
+ if (!captured) listenOnDocument();
4664
4670
  if (longPressMs > 0) {
4665
4671
  longPressTimer = deps.setTimer(() => {
4666
4672
  longPressFired = true;
@@ -4668,8 +4674,43 @@ function recognise(el, handlers, options = {}, deps = defaultGestureDeps()) {
4668
4674
  }, longPressMs);
4669
4675
  }
4670
4676
  }
4677
+ function takeCapture(id) {
4678
+ if (typeof el.setPointerCapture !== "function") return false;
4679
+ try {
4680
+ el.setPointerCapture(id);
4681
+ return true;
4682
+ } catch {
4683
+ return false;
4684
+ }
4685
+ }
4686
+ function captureOnDrag(event) {
4687
+ if (capturePointer !== "drag") return;
4688
+ captured = takeCapture(event.pointerId);
4689
+ if (captured) stopListeningOnDocument();
4690
+ }
4691
+ function listenOnDocument() {
4692
+ const doc = el.ownerDocument;
4693
+ if (!doc || documentListening) return;
4694
+ documentListening = doc;
4695
+ doc.addEventListener("pointermove", onMove, { passive: true });
4696
+ doc.addEventListener("pointerup", onUp, { passive: true });
4697
+ doc.addEventListener("pointercancel", onUp, { passive: true });
4698
+ }
4699
+ function stopListeningOnDocument() {
4700
+ const doc = documentListening;
4701
+ if (!doc) return;
4702
+ documentListening = null;
4703
+ doc.removeEventListener("pointermove", onMove);
4704
+ doc.removeEventListener("pointerup", onUp);
4705
+ doc.removeEventListener("pointercancel", onUp);
4706
+ }
4707
+ function firstHearing(event) {
4708
+ if (event === lastHandled) return false;
4709
+ lastHandled = event;
4710
+ return true;
4711
+ }
4671
4712
  function onMove(event) {
4672
- if (!origin) return;
4713
+ if (!origin || !firstHearing(event)) return;
4673
4714
  const sample = sampleOf(event);
4674
4715
  samples.push(sample);
4675
4716
  if (samples.length > MAX_SAMPLES) samples.shift();
@@ -4678,17 +4719,17 @@ function recognise(el, handlers, options = {}, deps = defaultGestureDeps()) {
4678
4719
  if (!active) {
4679
4720
  active = true;
4680
4721
  clearLongPress();
4722
+ captureOnDrag(event);
4681
4723
  handlers.onStart?.(origin);
4682
4724
  }
4683
4725
  handlers.onMove?.(vector, sample);
4684
4726
  }
4685
- function onUp(event) {
4686
- clearLongPress();
4687
- if (capturePointer) el.releasePointerCapture?.(event.pointerId);
4688
- if (!origin) return;
4689
- const sample = sampleOf(event);
4690
- samples.push(sample);
4691
- const vector = vectorNow(sample);
4727
+ function releaseCapture(id) {
4728
+ if (captured) el.releasePointerCapture?.(id);
4729
+ captured = false;
4730
+ pointerId = null;
4731
+ }
4732
+ function reportEnd(vector, sample) {
4692
4733
  if (active) {
4693
4734
  handlers.onEnd?.(vector, sample);
4694
4735
  const direction = swipeDirection(vector, swipeVelocity);
@@ -4696,21 +4737,38 @@ function recognise(el, handlers, options = {}, deps = defaultGestureDeps()) {
4696
4737
  } else if (longPressFired) {
4697
4738
  handlers.onEnd?.(vector, sample);
4698
4739
  }
4740
+ }
4741
+ function onUp(event) {
4742
+ clearLongPress();
4743
+ stopListeningOnDocument();
4744
+ releaseCapture(event.pointerId);
4745
+ if (!origin) return;
4746
+ const sample = sampleOf(event);
4747
+ samples.push(sample);
4748
+ reportEnd(vectorNow(sample), sample);
4699
4749
  origin = null;
4700
4750
  active = false;
4701
4751
  longPressFired = false;
4702
4752
  samples = [];
4703
4753
  }
4754
+ function onLostCapture(event) {
4755
+ if (!captured || event.pointerId !== pointerId) return;
4756
+ captured = false;
4757
+ onUp(event);
4758
+ }
4704
4759
  el.addEventListener("pointerdown", onDown);
4705
4760
  el.addEventListener("pointermove", onMove, { passive: true });
4706
4761
  el.addEventListener("pointerup", onUp, { passive: true });
4707
4762
  el.addEventListener("pointercancel", onUp, { passive: true });
4763
+ el.addEventListener("lostpointercapture", onLostCapture, { passive: true });
4708
4764
  return () => {
4709
4765
  clearLongPress();
4766
+ stopListeningOnDocument();
4710
4767
  el.removeEventListener("pointerdown", onDown);
4711
4768
  el.removeEventListener("pointermove", onMove);
4712
4769
  el.removeEventListener("pointerup", onUp);
4713
4770
  el.removeEventListener("pointercancel", onUp);
4771
+ el.removeEventListener("lostpointercapture", onLostCapture);
4714
4772
  };
4715
4773
  }
4716
4774
  function defaultGestureDeps() {
@@ -4859,11 +4917,12 @@ function prepareSwipeable(el, params) {
4859
4917
  {
4860
4918
  axis: params.text("axis", "both"),
4861
4919
  swipeVelocity: params.num("velocity", 300),
4862
- // This primitive publishes an attribute and moves nothing, so it has no reason to hold the
4863
- // pointer — and holding it retargets the following `click` at this element, which silently
4864
- // breaks every interactive child. A `swipe-x` on a carousel shell killed its own dots and
4865
- // buttons that way. See `capturePointer` in `core/gesture.ts`.
4866
- capturePointer: false
4920
+ // This primitive moves nothing, so it must not hold the pointer from `pointerdown` — that
4921
+ // retargets the following `click` at this element and kills every interactive child (a
4922
+ // `swipe-x` on a carousel shell killed its own dots and buttons). But a flick that leaves a
4923
+ // small element never delivers `pointerup` to it unless captured, so capture only once the
4924
+ // drag starts. See `capturePointer` in `core/gesture.ts`.
4925
+ capturePointer: "drag"
4867
4926
  }
4868
4927
  );
4869
4928
  return () => {
@@ -8937,7 +8996,7 @@ function bindSwipe(el, params, index) {
8937
8996
  return recognise(el, { onSwipe: (direction) => {
8938
8997
  if (direction === "left") index.next();
8939
8998
  if (direction === "right") index.prev();
8940
- } }, { axis: "x", capturePointer: false });
8999
+ } }, { axis: "x", capturePointer: "drag" });
8941
9000
  }
8942
9001
  function prepareSlideshow(el, params, ctx) {
8943
9002
  const selector = resolveTarget(
@@ -5,7 +5,7 @@ import {
5
5
  createAnimator,
6
6
  detect,
7
7
  silentReporter
8
- } from "../chunk-RVTYN6NZ.mjs";
8
+ } from "../chunk-BBQ22D7A.mjs";
9
9
  import {
10
10
  ATTR,
11
11
  CHANNEL,
@@ -18,7 +18,7 @@ import {
18
18
  resolveActivationSpec,
19
19
  resolveTargets,
20
20
  toAttributeValue
21
- } from "../chunk-U43BX5O2.mjs";
21
+ } from "../chunk-K2M7O6W5.mjs";
22
22
  import "../chunk-AEQGC2KM.mjs";
23
23
  export {
24
24
  ATTR,
@@ -17,8 +17,8 @@ import {
17
17
  registerThreeD,
18
18
  registerTween,
19
19
  registerViewTransitions
20
- } from "../chunk-BN2Y4UKB.mjs";
21
- import "../chunk-U43BX5O2.mjs";
20
+ } from "../chunk-XDDJVANU.mjs";
21
+ import "../chunk-K2M7O6W5.mjs";
22
22
  import "../chunk-AEQGC2KM.mjs";
23
23
  export {
24
24
  COMBOS,
@@ -5,13 +5,13 @@ import {
5
5
  createAnimator,
6
6
  detect,
7
7
  silentReporter
8
- } from "./chunk-RVTYN6NZ.mjs";
8
+ } from "./chunk-BBQ22D7A.mjs";
9
9
  import {
10
10
  COMBOS,
11
11
  PRESETS,
12
12
  PRIMITIVES,
13
13
  createRegistry
14
- } from "./chunk-BN2Y4UKB.mjs";
14
+ } from "./chunk-XDDJVANU.mjs";
15
15
  import {
16
16
  ATTR,
17
17
  CHANNEL,
@@ -24,7 +24,7 @@ import {
24
24
  resolveActivationSpec,
25
25
  resolveTargets,
26
26
  toAttributeValue
27
- } from "./chunk-U43BX5O2.mjs";
27
+ } from "./chunk-K2M7O6W5.mjs";
28
28
  import "./chunk-AEQGC2KM.mjs";
29
29
 
30
30
  // src/index.ts
@@ -2005,7 +2005,7 @@ void main() {
2005
2005
  if (!match) return 0;
2006
2006
  const value = parseFloat(match[1]);
2007
2007
  if (!Number.isFinite(value)) return 0;
2008
- return value * (ANGLE_UNITS[(match[2] ?? "deg").toLowerCase()] ?? ANGLE_UNITS.deg);
2008
+ return value * ANGLE_UNITS[(match[2] ?? "deg").toLowerCase()];
2009
2009
  }
2010
2010
  var PALETTE_SIZE = 5;
2011
2011
  var DEFAULT_PALETTE = [
@@ -2236,7 +2236,7 @@ void main() {
2236
2236
  if (bw <= 0 || bh <= 0) return content;
2237
2237
  const [dw, dh] = fittedSize(cs.objectFit || "fill", bw, bh, natural);
2238
2238
  const tokens = (cs.objectPosition || "50% 50%").trim().split(/\s+/);
2239
- const left = content.left + positionOffset(tokens[0] ?? "50%", bw - dw);
2239
+ const left = content.left + positionOffset(tokens[0], bw - dw);
2240
2240
  const top = content.top + positionOffset(tokens[1] ?? "50%", bh - dh);
2241
2241
  return { left, top, right: left + dw, bottom: top + dh };
2242
2242
  }
@@ -2502,7 +2502,7 @@ void main() {
2502
2502
  ...readDuotoneColors(c1Text, c2Text, createCanvas),
2503
2503
  blendMode: blendMap[params.text("blend", "normal")] ?? 0,
2504
2504
  to: params.text("to", ""),
2505
- progress: params.num ? params.num("progress", -1) : -1,
2505
+ progress: params.num("progress", -1),
2506
2506
  // Compared against the one enabling word rather than "not `off`", so — exactly as with
2507
2507
  // `audioBand` below — a caller passing an unvalidated accessor, or an author who typed
2508
2508
  // `scrub: scrol`, cannot turn the bridge on by accident.
@@ -2939,7 +2939,7 @@ void main() {
2939
2939
  if (!state.hiddenByRenderer) return;
2940
2940
  state.hiddenByRenderer = false;
2941
2941
  const style = styleOf(state.ledgers, el);
2942
- if (style) style.set("opacity", style.peek("opacity") ?? "");
2942
+ if (style) style.set("opacity", style.peek("opacity"));
2943
2943
  }
2944
2944
  var SHADER_TEXTURE_GRACE_MS = 5e3;
2945
2945
  function acquireRenderer(info, held) {
@@ -4107,7 +4107,7 @@ void main() {
4107
4107
  const controller = new CameraController(htmlEl, options, resolvedEnv);
4108
4108
  const layerEls = ownedDescendants(htmlEl, "camera-scene", "camera-layer");
4109
4109
  for (const layer of layerEls) {
4110
- const kui = layer.getAttribute("data-kui") || "";
4110
+ const kui = layer.getAttribute("data-kui");
4111
4111
  const zMatch = /z:([+-]?[0-9.]+)(?:px)?/.exec(kui);
4112
4112
  const depthZ = zMatch ? parseDepth(zMatch[1]) : 0;
4113
4113
  controller.addLayer(layer, depthZ);
@@ -4945,17 +4945,24 @@ var __kuineticBoot = (() => {
4945
4945
  }
4946
4946
  }
4947
4947
  function guarded(runtime, original, core, options) {
4948
+ const wantsShared = !options || Object.entries(options).every(([key, value]) => key === "observe" && value === true);
4949
+ if (runtime.manual) {
4950
+ const made = original.call(core, options);
4951
+ adoptManual(runtime, made);
4952
+ return made;
4953
+ }
4948
4954
  if (!runtime.started) {
4949
4955
  const replacement = original.call(core, options);
4950
4956
  adopt(runtime, replacement);
4951
- warnManualCall(
4952
- runtime,
4953
- "yours is now the one this page uses, exactly as it would have been before the script tag started one, so the call is safe to delete"
4954
- );
4957
+ if (wantsShared) {
4958
+ warnManualCall(
4959
+ runtime,
4960
+ "yours is now the one this page uses, exactly as it would have been before the script tag started one, so the call is safe to delete"
4961
+ );
4962
+ }
4955
4963
  return replacement;
4956
4964
  }
4957
4965
  const shared = runtime.animator;
4958
- const wantsShared = !options || Object.entries(options).every(([key, value]) => key === "observe" && value === true);
4959
4966
  if (shared && wantsShared) {
4960
4967
  warnManualCall(runtime, "you have been handed the one it already made, so this call is safe to delete");
4961
4968
  return shared;
@@ -4982,6 +4989,40 @@ var __kuineticBoot = (() => {
4982
4989
  runtime.animator = animator;
4983
4990
  globalScope().__kuinetic = animator;
4984
4991
  }
4992
+ function adoptManual(runtime, animator) {
4993
+ if (!runtime.animator) adopt(runtime, animator);
4994
+ const entry = { animator, started: false };
4995
+ const list = runtime.handBuilt ??= [];
4996
+ list.push(entry);
4997
+ const start = animator.start.bind(animator);
4998
+ animator.start = () => {
4999
+ entry.started = true;
5000
+ return start();
5001
+ };
5002
+ if (typeof animator.destroy === "function") {
5003
+ const destroy = animator.destroy.bind(animator);
5004
+ animator.destroy = () => {
5005
+ const at = list.indexOf(entry);
5006
+ if (at >= 0) list.splice(at, 1);
5007
+ return destroy();
5008
+ };
5009
+ }
5010
+ for (const tier of runtime.pending) registerTier(tier, animator);
5011
+ }
5012
+ function registerTier(entry, animator) {
5013
+ try {
5014
+ entry.register(animator);
5015
+ } catch (error) {
5016
+ warn(`the "${entry.tier}" tier failed to register: ${String(error)}`);
5017
+ }
5018
+ }
5019
+ function enlistOnManual(runtime, entry) {
5020
+ runtime.pending.push(entry);
5021
+ for (const { animator, started } of [...runtime.handBuilt ?? []]) {
5022
+ registerTier(entry, animator);
5023
+ if (started) recompile(animator);
5024
+ }
5025
+ }
4985
5026
  function createAnimator(runtime, core) {
4986
5027
  adopt(runtime, core.kuinetic({ observe: true }));
4987
5028
  }
@@ -5011,13 +5052,7 @@ var __kuineticBoot = (() => {
5011
5052
  }
5012
5053
  function drain(runtime, animator) {
5013
5054
  const pending = runtime.pending.splice(0, runtime.pending.length);
5014
- for (const entry of pending) {
5015
- try {
5016
- entry.register(animator);
5017
- } catch (error) {
5018
- warn(`the "${entry.tier}" tier failed to register: ${String(error)}`);
5019
- }
5020
- }
5055
+ for (const entry of pending) registerTier(entry, animator);
5021
5056
  return pending.length;
5022
5057
  }
5023
5058
  function runBoot(runtime) {
@@ -5039,13 +5074,6 @@ var __kuineticBoot = (() => {
5039
5074
  }
5040
5075
  if (registered) recompile(animator);
5041
5076
  }
5042
- function optedOut(runtime, options) {
5043
- if (taggedManual()) {
5044
- if (options.core) runtime.manual = true;
5045
- return true;
5046
- }
5047
- return runtime.manual;
5048
- }
5049
5077
  function enlist(runtime, options) {
5050
5078
  if (options.core) {
5051
5079
  if (runtime.animator) return;
@@ -5059,7 +5087,17 @@ var __kuineticBoot = (() => {
5059
5087
  if (typeof document === "undefined" || typeof globalThis === "undefined") return;
5060
5088
  const runtime = getRuntime();
5061
5089
  checkVersion(runtime, options.tier);
5062
- if (optedOut(runtime, options)) return;
5090
+ if (taggedManual()) {
5091
+ if (options.core) {
5092
+ runtime.manual = true;
5093
+ installGuard(runtime, options.core, options.globalName);
5094
+ }
5095
+ return;
5096
+ }
5097
+ if (runtime.manual) {
5098
+ if (options.register) enlistOnManual(runtime, { tier: options.tier, register: options.register });
5099
+ return;
5100
+ }
5063
5101
  enlist(runtime, options);
5064
5102
  schedule(runtime);
5065
5103
  }