kuinetic 0.2.0 → 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/dist/kuinetic.css CHANGED
@@ -2523,7 +2523,7 @@
2523
2523
  padding-block: var(--kui-mask-bleed, 0px);
2524
2524
  margin-block: calc(-1 * var(--kui-mask-bleed, 0px));
2525
2525
  }
2526
- [data-kui-state=finished] [data-kui-split-fx] .kui-split-line {
2526
+ [data-kui-state=finished] > [data-kui-split-fx] .kui-split-line {
2527
2527
  animation-name: none;
2528
2528
  opacity: 1;
2529
2529
  clip-path: none;
package/dist/kuinetic.js CHANGED
@@ -589,18 +589,25 @@ var kuinetic = (() => {
589
589
  if (typeof IntersectionObserver === "undefined") return void 0;
590
590
  return (callback, init) => new IntersectionObserver(callback, init);
591
591
  }
592
+ function onlyBoundLate(name, claims) {
593
+ const authorising = authorisingActivations(name);
594
+ if (!authorising.includes("enter")) return false;
595
+ const suits = (claim2) => authorising.some((named) => claim2.supported.includes(named));
596
+ if (!claims.some((claim2) => claim2.entrance && suits(claim2))) return false;
597
+ return claims.every((claim2) => suits(claim2) || claim2.supported.includes("load"));
598
+ }
592
599
  function warnAboutActivation(request) {
593
600
  warnUnsupported(request);
594
601
  warnUnknownEvents(request);
595
602
  }
596
- function warnUnsupported({ el, spec, supported, reporter }) {
597
- if (supported.length === 0) return;
603
+ function warnUnsupported({ el, spec, supported, claims, reporter }) {
604
+ const declaredAny = supported.length > 0 || (claims?.some((claim2) => claim2.supported.length > 0) ?? false);
605
+ if (!declaredAny) return;
606
+ const shared = supported.length > 0 ? supported.join(", ") : "none in common across the composed effects";
598
607
  for (const name of spec.names) {
599
608
  if (authorisingActivations(name).some((named) => supported.includes(named))) continue;
600
- reporter.warn(
601
- `activation "${name}" is not supported by this effect (supports: ${supported.join(", ")})`,
602
- el
603
- );
609
+ if (claims && onlyBoundLate(name, claims)) continue;
610
+ reporter.warn(`activation "${name}" is not supported by this effect (supports: ${shared})`, el);
604
611
  }
605
612
  }
606
613
  function warnUnknownEvents({ el, spec, reporter }) {
@@ -3427,6 +3434,7 @@ var kuinetic = (() => {
3427
3434
  unknown,
3428
3435
  reducedMotion: "shorten",
3429
3436
  supportedActivations: [],
3437
+ activationClaims: [],
3430
3438
  supportedTimelines: [],
3431
3439
  channels: [],
3432
3440
  warnings
@@ -3560,6 +3568,10 @@ var kuinetic = (() => {
3560
3568
  plan.fxNames.push(preset.name);
3561
3569
  plan.reducedMotion = strictestPolicy(plan.reducedMotion, primitive.reducedMotion);
3562
3570
  activations = intersect(activations, primitive.supportedActivations);
3571
+ plan.activationClaims.push({
3572
+ supported: primitive.supportedActivations,
3573
+ entrance: phaseOf(entry) === "entrance"
3574
+ });
3563
3575
  timelines = intersect(timelines, primitive.supportedTimelines);
3564
3576
  for (const channel of channelsFor(entry)) channels.add(channel);
3565
3577
  warnUnsupportedTimeline(preset.name, primitive.supportedTimelines, timeline, warnings);
@@ -5125,11 +5137,20 @@ var kuinetic = (() => {
5125
5137
  const windowRoot = windowScrollRoot(win);
5126
5138
  return (el) => {
5127
5139
  for (let node = el.parentElement; node; node = node.parentElement) {
5140
+ if (scrollsTheViewport(node, win)) return windowRoot;
5128
5141
  if (isScrollable(node)) return elementScrollRoot(node, win);
5129
5142
  }
5130
5143
  return windowRoot;
5131
5144
  };
5132
5145
  }
5146
+ function scrollsTheViewport(node, win) {
5147
+ const root = win.document.documentElement;
5148
+ if (node === root) return true;
5149
+ if (node !== win.document.body) return false;
5150
+ const rootStyle = win.getComputedStyle(root);
5151
+ return !CLIPPING_OVERFLOW.has(rootStyle.overflowX) && !CLIPPING_OVERFLOW.has(rootStyle.overflowY);
5152
+ }
5153
+ var CLIPPING_OVERFLOW = /* @__PURE__ */ new Set(["auto", "scroll", "overlay", "hidden", "clip"]);
5133
5154
  var SCROLLABLE_OVERFLOW = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
5134
5155
  function defaultIsScrollable(win) {
5135
5156
  return (el) => {
@@ -5632,6 +5653,7 @@ var kuinetic = (() => {
5632
5653
  el,
5633
5654
  spec: resolveActivationSpec(config.activation),
5634
5655
  supported: plan.supportedActivations,
5656
+ claims: plan.activationClaims,
5635
5657
  reporter: this.reporter
5636
5658
  });
5637
5659
  return config.activation;
@@ -11146,6 +11168,10 @@ var kuinetic = (() => {
11146
11168
  let active = false;
11147
11169
  let longPressTimer = null;
11148
11170
  let longPressFired = false;
11171
+ let captured = false;
11172
+ let pointerId = null;
11173
+ let lastHandled = null;
11174
+ let documentListening = null;
11149
11175
  function sampleOf(event) {
11150
11176
  return { x: event.clientX, y: event.clientY, time: deps.now() };
11151
11177
  }
@@ -11163,7 +11189,9 @@ var kuinetic = (() => {
11163
11189
  samples = [origin];
11164
11190
  active = false;
11165
11191
  longPressFired = false;
11166
- if (capturePointer) el.setPointerCapture?.(event.pointerId);
11192
+ pointerId = event.pointerId;
11193
+ captured = capturePointer === true && takeCapture(event.pointerId);
11194
+ if (!captured) listenOnDocument();
11167
11195
  if (longPressMs > 0) {
11168
11196
  longPressTimer = deps.setTimer(() => {
11169
11197
  longPressFired = true;
@@ -11171,8 +11199,43 @@ var kuinetic = (() => {
11171
11199
  }, longPressMs);
11172
11200
  }
11173
11201
  }
11202
+ function takeCapture(id) {
11203
+ if (typeof el.setPointerCapture !== "function") return false;
11204
+ try {
11205
+ el.setPointerCapture(id);
11206
+ return true;
11207
+ } catch {
11208
+ return false;
11209
+ }
11210
+ }
11211
+ function captureOnDrag(event) {
11212
+ if (capturePointer !== "drag") return;
11213
+ captured = takeCapture(event.pointerId);
11214
+ if (captured) stopListeningOnDocument();
11215
+ }
11216
+ function listenOnDocument() {
11217
+ const doc = el.ownerDocument;
11218
+ if (!doc || documentListening) return;
11219
+ documentListening = doc;
11220
+ doc.addEventListener("pointermove", onMove, { passive: true });
11221
+ doc.addEventListener("pointerup", onUp, { passive: true });
11222
+ doc.addEventListener("pointercancel", onUp, { passive: true });
11223
+ }
11224
+ function stopListeningOnDocument() {
11225
+ const doc = documentListening;
11226
+ if (!doc) return;
11227
+ documentListening = null;
11228
+ doc.removeEventListener("pointermove", onMove);
11229
+ doc.removeEventListener("pointerup", onUp);
11230
+ doc.removeEventListener("pointercancel", onUp);
11231
+ }
11232
+ function firstHearing(event) {
11233
+ if (event === lastHandled) return false;
11234
+ lastHandled = event;
11235
+ return true;
11236
+ }
11174
11237
  function onMove(event) {
11175
- if (!origin) return;
11238
+ if (!origin || !firstHearing(event)) return;
11176
11239
  const sample = sampleOf(event);
11177
11240
  samples.push(sample);
11178
11241
  if (samples.length > MAX_SAMPLES2) samples.shift();
@@ -11181,17 +11244,17 @@ var kuinetic = (() => {
11181
11244
  if (!active) {
11182
11245
  active = true;
11183
11246
  clearLongPress();
11247
+ captureOnDrag(event);
11184
11248
  handlers.onStart?.(origin);
11185
11249
  }
11186
11250
  handlers.onMove?.(vector, sample);
11187
11251
  }
11188
- function onUp(event) {
11189
- clearLongPress();
11190
- if (capturePointer) el.releasePointerCapture?.(event.pointerId);
11191
- if (!origin) return;
11192
- const sample = sampleOf(event);
11193
- samples.push(sample);
11194
- const vector = vectorNow(sample);
11252
+ function releaseCapture(id) {
11253
+ if (captured) el.releasePointerCapture?.(id);
11254
+ captured = false;
11255
+ pointerId = null;
11256
+ }
11257
+ function reportEnd(vector, sample) {
11195
11258
  if (active) {
11196
11259
  handlers.onEnd?.(vector, sample);
11197
11260
  const direction = swipeDirection(vector, swipeVelocity);
@@ -11199,21 +11262,38 @@ var kuinetic = (() => {
11199
11262
  } else if (longPressFired) {
11200
11263
  handlers.onEnd?.(vector, sample);
11201
11264
  }
11265
+ }
11266
+ function onUp(event) {
11267
+ clearLongPress();
11268
+ stopListeningOnDocument();
11269
+ releaseCapture(event.pointerId);
11270
+ if (!origin) return;
11271
+ const sample = sampleOf(event);
11272
+ samples.push(sample);
11273
+ reportEnd(vectorNow(sample), sample);
11202
11274
  origin = null;
11203
11275
  active = false;
11204
11276
  longPressFired = false;
11205
11277
  samples = [];
11206
11278
  }
11279
+ function onLostCapture(event) {
11280
+ if (!captured || event.pointerId !== pointerId) return;
11281
+ captured = false;
11282
+ onUp(event);
11283
+ }
11207
11284
  el.addEventListener("pointerdown", onDown);
11208
11285
  el.addEventListener("pointermove", onMove, { passive: true });
11209
11286
  el.addEventListener("pointerup", onUp, { passive: true });
11210
11287
  el.addEventListener("pointercancel", onUp, { passive: true });
11288
+ el.addEventListener("lostpointercapture", onLostCapture, { passive: true });
11211
11289
  return () => {
11212
11290
  clearLongPress();
11291
+ stopListeningOnDocument();
11213
11292
  el.removeEventListener("pointerdown", onDown);
11214
11293
  el.removeEventListener("pointermove", onMove);
11215
11294
  el.removeEventListener("pointerup", onUp);
11216
11295
  el.removeEventListener("pointercancel", onUp);
11296
+ el.removeEventListener("lostpointercapture", onLostCapture);
11217
11297
  };
11218
11298
  }
11219
11299
  function defaultGestureDeps() {
@@ -11362,11 +11442,12 @@ var kuinetic = (() => {
11362
11442
  {
11363
11443
  axis: params.text("axis", "both"),
11364
11444
  swipeVelocity: params.num("velocity", 300),
11365
- // This primitive publishes an attribute and moves nothing, so it has no reason to hold the
11366
- // pointer — and holding it retargets the following `click` at this element, which silently
11367
- // breaks every interactive child. A `swipe-x` on a carousel shell killed its own dots and
11368
- // buttons that way. See `capturePointer` in `core/gesture.ts`.
11369
- capturePointer: false
11445
+ // This primitive moves nothing, so it must not hold the pointer from `pointerdown` — that
11446
+ // retargets the following `click` at this element and kills every interactive child (a
11447
+ // `swipe-x` on a carousel shell killed its own dots and buttons). But a flick that leaves a
11448
+ // small element never delivers `pointerup` to it unless captured, so capture only once the
11449
+ // drag starts. See `capturePointer` in `core/gesture.ts`.
11450
+ capturePointer: "drag"
11370
11451
  }
11371
11452
  );
11372
11453
  return () => {
@@ -14663,7 +14744,8 @@ var kuinetic = (() => {
14663
14744
  duration: { type: "time", default: "280ms", cssProperty: "--kui-lightbox-duration" },
14664
14745
  ease: { type: "easing", default: "cubic-bezier(0.22, 1, 0.36, 1)", cssProperty: "--kui-lightbox-ease" },
14665
14746
  loop: { type: "keyword", default: "true", cssProperty: "--kui-lightbox-loop", keywords: ["true", "false"] },
14666
- aspect: { type: "keyword", default: "", cssProperty: "--kui-lightbox-aspect", keywords: ["wide", "tall", "square"] }
14747
+ aspect: { type: "keyword", default: "", cssProperty: "--kui-lightbox-aspect", keywords: ["wide", "tall", "square"] },
14748
+ caption: { type: "keyword", default: "figcaption", cssProperty: "--kui-lightbox-caption", keywords: ["figcaption", "alt", "title", "none"] }
14667
14749
  };
14668
14750
  function defaultTargets(el, media) {
14669
14751
  if (el.matches("a[href]") || media === "image" && el.matches("img")) return [el];
@@ -14681,25 +14763,30 @@ var kuinetic = (() => {
14681
14763
  function imageIn(trigger) {
14682
14764
  return trigger instanceof HTMLImageElement ? trigger : trigger.querySelector("img");
14683
14765
  }
14684
- function imageItem(trigger) {
14766
+ function captionFor(trigger, image, source) {
14767
+ if (source === "none") return "";
14768
+ if (source === "alt") return image.alt;
14769
+ if (source === "title") return (trigger.getAttribute("title") || image.title).trim();
14770
+ return trigger.closest("figure")?.querySelector("figcaption")?.textContent?.trim() || image.alt;
14771
+ }
14772
+ function imageItem(trigger, captionSource) {
14685
14773
  const image = imageIn(trigger);
14686
14774
  if (!image) return null;
14687
14775
  const link = trigger instanceof HTMLAnchorElement ? trigger : null;
14688
- const caption = trigger.closest("figure")?.querySelector("figcaption")?.textContent?.trim();
14689
14776
  return {
14690
14777
  trigger,
14691
14778
  image,
14692
14779
  src: link?.href ?? (image.currentSrc || image.src),
14693
- caption: caption || image.alt
14780
+ caption: captionFor(trigger, image, captionSource)
14694
14781
  };
14695
14782
  }
14696
- function imageItems(targets2) {
14783
+ function imageItems(targets2, captionSource) {
14697
14784
  const seen = /* @__PURE__ */ new Set();
14698
14785
  const items = [];
14699
14786
  for (const target of targets2) {
14700
14787
  const trigger = imageTrigger(target);
14701
14788
  if (seen.has(trigger)) continue;
14702
- const item = imageItem(trigger);
14789
+ const item = imageItem(trigger, captionSource);
14703
14790
  if (!item) continue;
14704
14791
  seen.add(trigger);
14705
14792
  items.push(item);
@@ -14803,7 +14890,7 @@ var kuinetic = (() => {
14803
14890
  const frame = doc.createElement("div");
14804
14891
  frame.className = `kui-lightbox-frame kui-lightbox-frame--${aspect}`;
14805
14892
  const poster = link.querySelector("img");
14806
- const title = link.title || poster?.getAttribute("alt") || link.textContent?.trim() || "Video";
14893
+ const title = link.title || poster?.getAttribute("alt") || link.getAttribute("aria-label")?.trim() || link.textContent?.trim() || "Video";
14807
14894
  if (source.kind === "file") {
14808
14895
  const video = doc.createElement("video");
14809
14896
  video.src = source.embedUrl;
@@ -14879,7 +14966,7 @@ var kuinetic = (() => {
14879
14966
  if (media === "video") {
14880
14967
  wireVideos(found, wiring, params.text("aspect"), ctx);
14881
14968
  } else {
14882
- const items = imageItems(found);
14969
+ const items = imageItems(found, params.text("caption", "figcaption"));
14883
14970
  restore = wireImages(items, wiring, params.is("loop"));
14884
14971
  }
14885
14972
  return continuousSetup(() => {
@@ -15434,7 +15521,7 @@ var kuinetic = (() => {
15434
15521
  return recognise(el, { onSwipe: (direction) => {
15435
15522
  if (direction === "left") index.next();
15436
15523
  if (direction === "right") index.prev();
15437
- } }, { axis: "x", capturePointer: false });
15524
+ } }, { axis: "x", capturePointer: "drag" });
15438
15525
  }
15439
15526
  function prepareSlideshow(el, params, ctx) {
15440
15527
  const selector = resolveTarget(
@@ -15903,17 +15990,24 @@ var __kuineticBoot = (() => {
15903
15990
  }
15904
15991
  }
15905
15992
  function guarded(runtime, original, core, options) {
15993
+ const wantsShared = !options || Object.entries(options).every(([key, value]) => key === "observe" && value === true);
15994
+ if (runtime.manual) {
15995
+ const made = original.call(core, options);
15996
+ adoptManual(runtime, made);
15997
+ return made;
15998
+ }
15906
15999
  if (!runtime.started) {
15907
16000
  const replacement = original.call(core, options);
15908
16001
  adopt(runtime, replacement);
15909
- warnManualCall(
15910
- runtime,
15911
- "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"
15912
- );
16002
+ if (wantsShared) {
16003
+ warnManualCall(
16004
+ runtime,
16005
+ "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"
16006
+ );
16007
+ }
15913
16008
  return replacement;
15914
16009
  }
15915
16010
  const shared = runtime.animator;
15916
- const wantsShared = !options || Object.entries(options).every(([key, value]) => key === "observe" && value === true);
15917
16011
  if (shared && wantsShared) {
15918
16012
  warnManualCall(runtime, "you have been handed the one it already made, so this call is safe to delete");
15919
16013
  return shared;
@@ -15940,6 +16034,40 @@ var __kuineticBoot = (() => {
15940
16034
  runtime.animator = animator;
15941
16035
  globalScope().__kuinetic = animator;
15942
16036
  }
16037
+ function adoptManual(runtime, animator) {
16038
+ if (!runtime.animator) adopt(runtime, animator);
16039
+ const entry = { animator, started: false };
16040
+ const list = runtime.handBuilt ??= [];
16041
+ list.push(entry);
16042
+ const start = animator.start.bind(animator);
16043
+ animator.start = () => {
16044
+ entry.started = true;
16045
+ return start();
16046
+ };
16047
+ if (typeof animator.destroy === "function") {
16048
+ const destroy = animator.destroy.bind(animator);
16049
+ animator.destroy = () => {
16050
+ const at = list.indexOf(entry);
16051
+ if (at >= 0) list.splice(at, 1);
16052
+ return destroy();
16053
+ };
16054
+ }
16055
+ for (const tier of runtime.pending) registerTier(tier, animator);
16056
+ }
16057
+ function registerTier(entry, animator) {
16058
+ try {
16059
+ entry.register(animator);
16060
+ } catch (error) {
16061
+ warn(`the "${entry.tier}" tier failed to register: ${String(error)}`);
16062
+ }
16063
+ }
16064
+ function enlistOnManual(runtime, entry) {
16065
+ runtime.pending.push(entry);
16066
+ for (const { animator, started } of [...runtime.handBuilt ?? []]) {
16067
+ registerTier(entry, animator);
16068
+ if (started) recompile(animator);
16069
+ }
16070
+ }
15943
16071
  function createAnimator(runtime, core) {
15944
16072
  adopt(runtime, core.kuinetic({ observe: true }));
15945
16073
  }
@@ -15969,13 +16097,7 @@ var __kuineticBoot = (() => {
15969
16097
  }
15970
16098
  function drain(runtime, animator) {
15971
16099
  const pending = runtime.pending.splice(0, runtime.pending.length);
15972
- for (const entry of pending) {
15973
- try {
15974
- entry.register(animator);
15975
- } catch (error) {
15976
- warn(`the "${entry.tier}" tier failed to register: ${String(error)}`);
15977
- }
15978
- }
16100
+ for (const entry of pending) registerTier(entry, animator);
15979
16101
  return pending.length;
15980
16102
  }
15981
16103
  function runBoot(runtime) {
@@ -15997,13 +16119,6 @@ var __kuineticBoot = (() => {
15997
16119
  }
15998
16120
  if (registered) recompile(animator);
15999
16121
  }
16000
- function optedOut(runtime, options) {
16001
- if (taggedManual()) {
16002
- if (options.core) runtime.manual = true;
16003
- return true;
16004
- }
16005
- return runtime.manual;
16006
- }
16007
16122
  function enlist(runtime, options) {
16008
16123
  if (options.core) {
16009
16124
  if (runtime.animator) return;
@@ -16017,7 +16132,17 @@ var __kuineticBoot = (() => {
16017
16132
  if (typeof document === "undefined" || typeof globalThis === "undefined") return;
16018
16133
  const runtime = getRuntime();
16019
16134
  checkVersion(runtime, options.tier);
16020
- if (optedOut(runtime, options)) return;
16135
+ if (taggedManual()) {
16136
+ if (options.core) {
16137
+ runtime.manual = true;
16138
+ installGuard(runtime, options.core, options.globalName);
16139
+ }
16140
+ return;
16141
+ }
16142
+ if (runtime.manual) {
16143
+ if (options.register) enlistOnManual(runtime, { tier: options.tier, register: options.register });
16144
+ return;
16145
+ }
16021
16146
  enlist(runtime, options);
16022
16147
  schedule(runtime);
16023
16148
  }