kuinetic 0.1.2 → 0.1.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.
@@ -114,7 +114,18 @@ var CHANNEL = {
114
114
  background: "background",
115
115
  color: "color",
116
116
  stroke: "stroke",
117
- text: "text"
117
+ text: "text",
118
+ /**
119
+ * Skew is the one transform CSS never gave an independent property to — there is no `skew:`
120
+ * beside `translate:`/`rotate:`/`scale:`, so it can only be written through the `transform`
121
+ * shorthand. That makes it its own channel: anything writing `transform` clobbers the whole
122
+ * shorthand, so every primitive that does is on this channel, whatever the transform is for.
123
+ * `scroll-skew` is one member; `flip-face` (`effects/three-d`) and `flip-3d`
124
+ * (`effects/catalog/core`, the `flip-in-*`/`flip-out-*` family) are the other two — both need
125
+ * the `perspective()` transform *function* for an element to have depth on itself, which
126
+ * likewise only exists inside `transform`.
127
+ */
128
+ skew: "skew"
118
129
  };
119
130
  function inertInstance(destroy = () => {
120
131
  }) {
@@ -146,7 +157,7 @@ var PATTERNS = {
146
157
  var HEX_COLOR = /^#[0-9a-f]{3,8}$/i;
147
158
  var COLOR_FUNCTIONS = /^(?:rgba?|hsla?|okl(?:ch|ab)|l(?:ch|ab)|color)\([^()]*\)$/i;
148
159
  var COLOR_KEYWORD = /^[a-z]+$/i;
149
- var EASING_KEYWORD = /^(?:linear|ease|step-start|step-end|spring|[a-z]+-(?:in|out|in-out))$/i;
160
+ var EASING_KEYWORD = /^(?:linear|ease|step-start|step-end|spring|bounce|[a-z]+-(?:in|out|in-out))$/i;
150
161
  var EASING_FUNCTION = /^(?:cubic-bezier|steps|linear)\([^()]*\)$/i;
151
162
  var CALC_TYPES = /* @__PURE__ */ new Set(["length", "percentage", "number"]);
152
163
  var CALC_CHARACTER = /^[\d.\s+\-*/%a-z,]$/i;
@@ -445,7 +456,22 @@ function windowScrollRoot(win) {
445
456
  viewportLeft: 0
446
457
  }),
447
458
  onScroll: (handler) => listen(win, "scroll", handler),
448
- onResize: (handler) => listen(win, "resize", handler)
459
+ /*
460
+ * The document's own height counts as a resize, not just the window's.
461
+ *
462
+ * `elementScrollRoot` has observed its element's size since it was written, for the reason
463
+ * stated three doc comments down: "images loading, fonts swapping, a container changing size,
464
+ * or content being inserted". Every one of those applies at least as strongly to the page
465
+ * itself, and the page root was the one that only listened for `window.resize` — so on any
466
+ * document with lazy-loaded media below the fold, every cached content offset silently drifted
467
+ * by however much the page grew after it was measured, with no epoch bump to correct it.
468
+ *
469
+ * Measured on `demo/scroll.html` (32,000px of lazy screenshots): the horizontal track's stage
470
+ * moved ~840px between page load and reaching it, which is a third of that effect's whole
471
+ * scroll range. Nothing was wrong with the maths; the number it was doing the maths on was
472
+ * taken before forty images existed.
473
+ */
474
+ onResize: (handler) => observeSize(win.document.documentElement, win, handler)
449
475
  };
450
476
  }
451
477
  function elementScrollRoot(el, win) {
@@ -553,7 +579,7 @@ function restartCssAnimation(el, ledger) {
553
579
  flushComputedAnimationName(el);
554
580
  ledger.set("animation-name", name);
555
581
  }
556
- function createCssInstance(el, ledger, animationNames) {
582
+ function createCssInstance(el, ledger, animationNames, scrubbed = false) {
557
583
  const ownedNames = new Set(animationNames);
558
584
  let settle;
559
585
  let finished = new Promise((resolve) => {
@@ -572,6 +598,10 @@ function createCssInstance(el, ledger, animationNames) {
572
598
  finished = new Promise((resolve) => {
573
599
  settle = resolve;
574
600
  });
601
+ if (scrubbed) {
602
+ settle?.();
603
+ return;
604
+ }
575
605
  let animations = ownedAnimationsOf(el, ownedNames);
576
606
  if (!activatedBefore) {
577
607
  restartCssAnimation(el, ledger);
@@ -604,6 +634,9 @@ function createCssInstance(el, ledger, animationNames) {
604
634
  }
605
635
  };
606
636
  }
637
+ function continuousSetup(cleanup) {
638
+ return { cleanup, continuous: true };
639
+ }
607
640
  function cleanupOf(result) {
608
641
  if (result === void 0) return void 0;
609
642
  return typeof result === "function" ? result : result.cleanup;
@@ -620,7 +653,7 @@ function deferredInstance(setup) {
620
653
  return createJsInstance({
621
654
  activate() {
622
655
  running = setup();
623
- if (typeof running === "function") return;
656
+ if (typeof running === "function" || "continuous" in running) return;
624
657
  const work = running.finished;
625
658
  finished = new Promise((resolve) => {
626
659
  settle = resolve;
@@ -629,11 +662,14 @@ function deferredInstance(setup) {
629
662
  },
630
663
  cancel: teardown,
631
664
  finish() {
632
- if (typeof running === "object") running.finish?.();
665
+ if (typeof running === "object" && "finish" in running) running.finish?.();
633
666
  settle?.();
634
667
  },
635
668
  destroy: teardown,
636
- finished: () => finished
669
+ finished: () => finished,
670
+ // Only an explicit marker counts. A bare `Cleanup` deliberately does not — see
671
+ // `ContinuousSetup` above for why the two cannot be told apart by shape.
672
+ continuous: () => typeof running === "object" && "continuous" in running
637
673
  });
638
674
  }
639
675
  function deferPrepare(setup) {
@@ -657,6 +693,9 @@ function createJsInstance(hooks) {
657
693
  get finished() {
658
694
  return hooks.finished?.() ?? Promise.resolve();
659
695
  },
696
+ get continuous() {
697
+ return hooks.continuous?.() ?? false;
698
+ },
660
699
  destroy() {
661
700
  active = false;
662
701
  hooks.destroy();
@@ -668,6 +707,7 @@ function createJsInstance(hooks) {
668
707
  function createStyleLedger(el) {
669
708
  const style = el.style;
670
709
  const previous = /* @__PURE__ */ new Map();
710
+ const authoredStyleAttribute = el.hasAttribute("style");
671
711
  function remember(property) {
672
712
  if (previous.has(property)) return;
673
713
  const existing = style.getPropertyValue(property);
@@ -685,6 +725,8 @@ function createStyleLedger(el) {
685
725
  else style.setProperty(property, value);
686
726
  }
687
727
  previous.clear();
728
+ if (!authoredStyleAttribute && style.length === 0 && !el.getAttribute("style"))
729
+ el.removeAttribute("style");
688
730
  },
689
731
  owned: () => [...previous.keys()]
690
732
  };
@@ -722,6 +764,7 @@ export {
722
764
  clamp01,
723
765
  createMeasureCache,
724
766
  createCssInstance,
767
+ continuousSetup,
725
768
  deferPrepare,
726
769
  createStyleLedger,
727
770
  createAttributeLedger,
@@ -10,7 +10,7 @@ import {
10
10
  resolveParams,
11
11
  suggest,
12
12
  timingProperty
13
- } from "./chunk-LWS4OSLX.mjs";
13
+ } from "./chunk-JT4PZL3A.mjs";
14
14
 
15
15
  // src/core/attrs.ts
16
16
  var ATTR = {
@@ -30,7 +30,7 @@ var ATTR = {
30
30
 
31
31
  // src/core/element-config.ts
32
32
  var ACTIVATIONS = /* @__PURE__ */ new Set(["load", "enter", "hover", "focus", "click", "manual"]);
33
- var TIMELINES = /* @__PURE__ */ new Set(["time", "view", "scroll", "pointer"]);
33
+ var TIMELINES = /* @__PURE__ */ new Set(["time", "view", "scroll", "pointer", "pin"]);
34
34
  function readAttributes(el) {
35
35
  return {
36
36
  source: el.getAttribute(ATTR.source) ?? "",
@@ -282,7 +282,7 @@ function buildPlan(entries, timeline, unknown, warnings) {
282
282
  plan.vars,
283
283
  resolveParams(entry.spec.params, primitive.parameters, (m) => warnings.push(m))
284
284
  );
285
- if (primitive.renderer === "css-keyframes") pushTrack(tracks, entry);
285
+ if (primitive.renderer === "css-keyframes") pushTrack(tracks, entry, timeline);
286
286
  else plan.jsEffects.push(entry);
287
287
  }
288
288
  Object.assign(plan.declarations, declarationsFor(tracks));
@@ -298,12 +298,13 @@ function intersect(accumulated, supported) {
298
298
  function iterationCountProperty(presetName) {
299
299
  return `--kui-fx-${presetName}-iterations`;
300
300
  }
301
- function pushTrack(tracks, entry) {
301
+ function pushTrack(tracks, entry, timeline) {
302
302
  const { spec, resolved } = entry;
303
303
  const id = resolved.primitive.id;
304
304
  tracks.names.push(resolved.preset.keyframes ?? `kui-${resolved.preset.name}`);
305
- tracks.durations.push(spec.duration ?? `var(${timingProperty(id, "duration")}, 600ms)`);
306
- tracks.delays.push(staggerDelay(spec.delay, id));
305
+ const duration = spec.duration ?? `var(${timingProperty(id, "duration")}, 600ms)`;
306
+ tracks.durations.push(duration);
307
+ tracks.delays.push(staggerDelay(spec.delay, id, timeline, duration));
307
308
  tracks.easings.push(easingValue(spec.easing, id));
308
309
  tracks.iterationCounts.push(`var(${iterationCountProperty(resolved.preset.name)}, 1)`);
309
310
  }
@@ -327,9 +328,12 @@ function warnUnsupportedTimeline(name, supported, timeline, warnings) {
327
328
  `"${name}" does not support timeline "${timeline}" (supports: ${supported.join(", ")})`
328
329
  );
329
330
  }
330
- function staggerDelay(delay, primitiveId) {
331
+ function staggerDelay(delay, primitiveId, timeline, duration) {
331
332
  const base = delay ?? `var(${timingProperty(primitiveId, "delay")}, 0ms)`;
332
- return `calc(${base} + var(--kui-i, 0) * var(--kui-stagger, 0ms))`;
333
+ const staggered = `${base} + var(--kui-i, 0) * var(--kui-stagger, 0ms)`;
334
+ if (timeline !== "pin") return `calc(${staggered})`;
335
+ const span = `${duration} + (var(--kui-stagger-count, 1) - 1) * var(--kui-stagger, 0ms)`;
336
+ return `calc(${staggered} - var(--kui-progress, 0) * (${span}))`;
333
337
  }
334
338
  function easingValue(easing, primitiveId) {
335
339
  if (!easing) return `var(${timingProperty(primitiveId, "ease")}, ease-out)`;
@@ -499,7 +503,8 @@ var EASING_KEYWORDS = /* @__PURE__ */ new Set([
499
503
  "back-in-out",
500
504
  "quart-out",
501
505
  "circ-out",
502
- "spring"
506
+ "spring",
507
+ "bounce"
503
508
  ]);
504
509
  function splitTopLevel(input, delimiter, warnings = []) {
505
510
  const parts = [];
@@ -775,6 +780,8 @@ function indexStaggerGroup(group) {
775
780
  index++;
776
781
  }
777
782
  }
783
+ ;
784
+ group.style.setProperty("--kui-stagger-count", String(Math.max(index, 1)));
778
785
  }
779
786
  function applyStagger(root) {
780
787
  const selector = `[${ATTR.stagger}]`;
@@ -786,12 +793,14 @@ function applyStagger(root) {
786
793
  function planStyles(input) {
787
794
  const { plan, config, capabilities, respectReducedMotion } = input;
788
795
  const reduce = respectReducedMotion && capabilities.reducedMotion;
789
- const useNativeTimeline = supportsTimeline(config.timeline, capabilities) && plan.supportedTimelines.includes(config.timeline);
796
+ const scrubbed = config.timeline === "pin" && plan.supportedTimelines.includes("pin");
797
+ const useNativeTimeline = !scrubbed && supportsTimeline(config.timeline, capabilities) && plan.supportedTimelines.includes(config.timeline);
790
798
  const properties = { ...plan.vars, ...plan.declarations };
791
799
  Object.assign(properties, timelineProperties(config, capabilities, useNativeTimeline));
792
800
  const hasCssAnimation = Object.keys(plan.declarations).length > 0;
793
801
  const gate = resolveGate({
794
802
  useNativeTimeline,
803
+ scrubbed: scrubbed && hasCssAnimation,
795
804
  reduce,
796
805
  activation: config.activation,
797
806
  // JS effects are gated too. They emit no `animation` declaration, so only the play-state
@@ -806,7 +815,7 @@ function planStyles(input) {
806
815
  // already applied under reduced motion.
807
816
  unsupportedTransform: needsIndividualTransforms(plan.channels, capabilities)
808
817
  });
809
- if (gate === "deferred") properties["animation-play-state"] = "paused";
818
+ if (gate === "deferred" || gate === "scrubbed") properties["animation-play-state"] = "paused";
810
819
  return {
811
820
  properties,
812
821
  attributes: {
@@ -830,6 +839,7 @@ function timelineProperties(config, capabilities, useNativeTimeline) {
830
839
  }
831
840
  function resolveGate(input) {
832
841
  if (input.useNativeTimeline) return "native-timeline";
842
+ if (input.scrubbed) return "scrubbed";
833
843
  if (!input.hasWork) return "immediate";
834
844
  if (input.reduce || input.activation === "load" || input.unsupportedTransform) return "immediate";
835
845
  return "deferred";
@@ -1010,7 +1020,9 @@ var Animator = class {
1010
1020
  this.liveElements.add(el);
1011
1021
  if (Object.keys(stylePlan.properties).some((property) => property.startsWith("animation-"))) {
1012
1022
  const animationNames = (plan.declarations["animation-name"] ?? "").split(",").map((name) => name.trim()).filter(Boolean);
1013
- state.instances.push(createCssInstance(el, ledger, animationNames));
1023
+ state.instances.push(
1024
+ createCssInstance(el, ledger, animationNames, stylePlan.gate === "scrubbed")
1025
+ );
1014
1026
  }
1015
1027
  state.instances.push(
1016
1028
  ...this.jsEffectPreparer.prepare({ el, plan, signal: controller.signal, ledger })
@@ -1071,7 +1083,9 @@ var Animator = class {
1071
1083
  state.attributes.set(ATTR.state, "failed");
1072
1084
  return;
1073
1085
  }
1074
- void Promise.all(started.map((instance) => instance.finished)).then(() => {
1086
+ const timed = started.filter((instance) => !instance.continuous);
1087
+ if (timed.length === 0 && started.length > 0) return;
1088
+ void Promise.all(timed.map((instance) => instance.finished)).then(() => {
1075
1089
  if (this.states.get(el) !== state || state.status !== "running") return;
1076
1090
  state.status = "finished";
1077
1091
  state.attributes.set(ATTR.state, "finished");
@@ -10,12 +10,12 @@ import {
10
10
  resolveTargets,
11
11
  silentReporter,
12
12
  toAttributeValue
13
- } from "../chunk-5EVG7CIY.mjs";
13
+ } from "../chunk-QUVFODSQ.mjs";
14
14
  import {
15
15
  CHANNEL,
16
16
  Registry,
17
17
  inertInstance
18
- } from "../chunk-LWS4OSLX.mjs";
18
+ } from "../chunk-JT4PZL3A.mjs";
19
19
  export {
20
20
  ATTR,
21
21
  Animator,
@@ -10,8 +10,8 @@ import {
10
10
  registerScrollMechanics,
11
11
  registerSvg,
12
12
  registerThreeD
13
- } from "../chunk-WPU33HDL.mjs";
14
- import "../chunk-LWS4OSLX.mjs";
13
+ } from "../chunk-5DON3UFQ.mjs";
14
+ import "../chunk-JT4PZL3A.mjs";
15
15
  export {
16
16
  COMBOS,
17
17
  PRESETS,
@@ -10,18 +10,18 @@ import {
10
10
  resolveTargets,
11
11
  silentReporter,
12
12
  toAttributeValue
13
- } from "./chunk-5EVG7CIY.mjs";
13
+ } from "./chunk-QUVFODSQ.mjs";
14
14
  import {
15
15
  COMBOS,
16
16
  PRESETS,
17
17
  PRIMITIVES,
18
18
  createRegistry
19
- } from "./chunk-WPU33HDL.mjs";
19
+ } from "./chunk-5DON3UFQ.mjs";
20
20
  import {
21
21
  CHANNEL,
22
22
  Registry,
23
23
  inertInstance
24
- } from "./chunk-LWS4OSLX.mjs";
24
+ } from "./chunk-JT4PZL3A.mjs";
25
25
 
26
26
  // src/index.ts
27
27
  function kuinetic(options = {}) {