@pixodesk/svg-animator-react 1.0.19 → 1.0.20

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/index.umd.js CHANGED
@@ -2728,6 +2728,10 @@ var PixodeskAnimatorReact = (() => {
2728
2728
  maskUnits: px.string().optional(),
2729
2729
  maskContentUnits: px.string().optional()
2730
2730
  }));
2731
+ var PxClipPathEffectSchema = implementsInterface()(px.object({
2732
+ d: px.string().optional(),
2733
+ animate: PxPropertyAnimationSchema.optional()
2734
+ }));
2731
2735
  var PxTrimPathEffectSchema = implementsInterface()(px.object({
2732
2736
  offset: PxAnimatableNumberSchema.optional(),
2733
2737
  range: PxAnimatableVec2Schema.optional(),
@@ -2786,6 +2790,7 @@ var PixodeskAnimatorReact = (() => {
2786
2790
  transformation: PxTransformationEffectSchema.optional(),
2787
2791
  repeater: PxRepeaterEffectSchema.optional(),
2788
2792
  maskedBy: PxMaskedByEffectSchema.optional(),
2793
+ clipPath: PxClipPathEffectSchema.optional(),
2789
2794
  trimPath: PxTrimPathEffectSchema.optional(),
2790
2795
  clone: PxCloneEffectSchema.optional(),
2791
2796
  isCombinedShape: px.boolean().optional(),
@@ -3078,6 +3083,15 @@ var PixodeskAnimatorReact = (() => {
3078
3083
  const pct = Math.round(o * 1e3) / 10;
3079
3084
  return pct + "%";
3080
3085
  }
3086
+ function applyClipPathEffect(node, fx, ctx) {
3087
+ if (!fx || !fx.d && !fx.animate) return node;
3088
+ const clipId = genId(ctx, "clip");
3089
+ const pathChild = { type: "path", d: fx.d };
3090
+ if (fx.animate) pathChild.animate = { d: fx.animate };
3091
+ ctx.defs.push({ type: "clipPath", id: clipId, children: [pathChild] });
3092
+ node.clipPath = "url(#" + clipId + ")";
3093
+ return node;
3094
+ }
3081
3095
  function applyMaskedByEffect(node, fx, transformation, ctx) {
3082
3096
  if (!fx) return node;
3083
3097
  if (!fx.href) {
@@ -3513,6 +3527,9 @@ var PixodeskAnimatorReact = (() => {
3513
3527
  var RETIME_MATERIALISATION_MODE_INLINE_G = false;
3514
3528
  function asRetime(r) {
3515
3529
  var _a, _b;
3530
+ if (r.timeCrop) {
3531
+ console.warn("retime: `timeCrop` is not supported yet and will be ignored");
3532
+ }
3516
3533
  return { start: (_a = r.start) != null ? _a : 0, stretch: (_b = r.stretch) != null ? _b : 1 };
3517
3534
  }
3518
3535
  function concatRetime(child, parent) {
@@ -3929,6 +3946,8 @@ var PixodeskAnimatorReact = (() => {
3929
3946
  // Filter
3930
3947
  "filterUnits",
3931
3948
  "primitiveUnits",
3949
+ "tableValues",
3950
+ // feFuncR/G/B/A transfer table (type="table")
3932
3951
  "stdDeviation",
3933
3952
  "baseFrequency",
3934
3953
  "numOctaves",
@@ -4185,6 +4204,9 @@ var PixodeskAnimatorReact = (() => {
4185
4204
  totalLength,
4186
4205
  closed,
4187
4206
  sampleAtDistance(dist) {
4207
+ if (closed && totalLength > 0 && (dist < 0 || dist > totalLength)) {
4208
+ return sampleOn((dist % totalLength + totalLength) % totalLength);
4209
+ }
4188
4210
  if (!closed && (dist < 0 || dist > totalLength)) {
4189
4211
  const edge = dist < 0 ? 0 : totalLength;
4190
4212
  const p = sampleOn(edge);
@@ -4433,6 +4455,19 @@ var PixodeskAnimatorReact = (() => {
4433
4455
  const names = Object.keys(glyphs);
4434
4456
  return names.length === 1 ? glyphs[names[0]] : void 0;
4435
4457
  }
4458
+ var MISSING_GLYPH_ADVANCE_EM = 0.6;
4459
+ function missingGlyphBoxEm(advanceEm, ascentEm) {
4460
+ if (advanceEm <= 0 || ascentEm <= 0) return "";
4461
+ const inset = 0.08;
4462
+ const x0 = advanceEm * inset, x1 = advanceEm * (1 - inset);
4463
+ const y1 = -ascentEm * (1 - inset);
4464
+ const bw = x1 - x0, bh = -y1;
4465
+ const t = Math.max(1, Math.min(bw, bh) * 0.12);
4466
+ const outer = "M" + x0 + " 0L" + x1 + " 0L" + x1 + " " + y1 + "L" + x0 + " " + y1 + "Z";
4467
+ if (bw <= 2 * t || bh <= 2 * t) return outer;
4468
+ const ix0 = x0 + t, ix1 = x1 - t, iyb = -t, iyt = y1 + t;
4469
+ return outer + "M" + ix0 + " " + iyb + "L" + ix0 + " " + iyt + "L" + ix1 + " " + iyt + "L" + ix1 + " " + iyb + "Z";
4470
+ }
4436
4471
  function materialiseGlyphTextHorizontal(node, opts) {
4437
4472
  var _a, _b, _c, _d;
4438
4473
  const { glyphs, create = jsonElementFactory, warnings } = opts;
@@ -4442,15 +4477,26 @@ var PixodeskAnimatorReact = (() => {
4442
4477
  const lines = [{ start: pen.x, end: pen.x }];
4443
4478
  let line = 0;
4444
4479
  const renderChars = (content, s) => {
4480
+ var _a2;
4445
4481
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4446
- if (!gf) return;
4447
- const scale = s.fontSize / gf.unitsPerEm;
4482
+ const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
4483
+ const scale = s.fontSize / upm;
4484
+ const ascentEm = (_a2 = gf == null ? void 0 : gf.ascent) != null ? _a2 : 0.9 * upm;
4448
4485
  const paint = paintOf(s);
4449
4486
  for (let i = 0; i < content.length; i++) {
4450
4487
  const ch = content.charAt(i);
4451
- const g = gf.glyphs[ch];
4452
- if (g && g.d) placements.push({ glyphD: g.d, m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
4453
- pen.x += (g ? g.width : 0) * scale + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4488
+ const g = gf == null ? void 0 : gf.glyphs[ch];
4489
+ if (g && g.d) {
4490
+ placements.push({ glyphD: g.d, m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
4491
+ pen.x += g.width * scale;
4492
+ } else if (/\S/.test(ch)) {
4493
+ const advEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * upm;
4494
+ placements.push({ glyphD: missingGlyphBoxEm(advEm, ascentEm), m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
4495
+ pen.x += advEm * scale;
4496
+ } else {
4497
+ pen.x += (g ? g.width : 0) * scale;
4498
+ }
4499
+ pen.x += s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4454
4500
  lines[line].end = pen.x;
4455
4501
  }
4456
4502
  };
@@ -4489,20 +4535,31 @@ var PixodeskAnimatorReact = (() => {
4489
4535
  const cells = [];
4490
4536
  let adv = 0;
4491
4537
  const walk = (el, parentStyle) => {
4492
- var _a, _b;
4538
+ var _a, _b, _c;
4493
4539
  const s = resolveStyle(el, parentStyle);
4494
4540
  const content = (_a = str(el[TEXT_ATTR])) != null ? _a : str(el[TEXT_CONTENT_ATTR]);
4495
4541
  if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
4496
4542
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4497
4543
  if (gf) {
4498
4544
  const scale = s.fontSize / gf.unitsPerEm;
4545
+ const ascentEm = (_c = gf.ascent) != null ? _c : 0.9 * gf.unitsPerEm;
4499
4546
  const paint = paintOf(s);
4500
4547
  for (let i = 0; i < content.length; i++) {
4501
4548
  const ch = content.charAt(i);
4502
4549
  const g = gf.glyphs[ch];
4503
- const glyphAdv = (g ? g.width : 0) * scale;
4504
- if (g && g.d) cells.push({ glyphD: g.d, widthEm: g.width, scale, paint, midBase: adv + glyphAdv / 2 });
4505
- adv += glyphAdv + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4550
+ if (g && g.d) {
4551
+ const glyphAdv = g.width * scale;
4552
+ cells.push({ glyphD: g.d, widthEm: g.width, scale, paint, midBase: adv + glyphAdv / 2 });
4553
+ adv += glyphAdv;
4554
+ } else if (/\S/.test(ch)) {
4555
+ const wEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * gf.unitsPerEm;
4556
+ const boxAdv = wEm * scale;
4557
+ cells.push({ glyphD: missingGlyphBoxEm(wEm, ascentEm), widthEm: wEm, scale, paint, midBase: adv + boxAdv / 2 });
4558
+ adv += boxAdv;
4559
+ } else {
4560
+ adv += (g ? g.width : 0) * scale;
4561
+ }
4562
+ adv += s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4506
4563
  }
4507
4564
  }
4508
4565
  }
@@ -4511,10 +4568,17 @@ var PixodeskAnimatorReact = (() => {
4511
4568
  walk(node, rootStyleOf(node));
4512
4569
  return { cells, width: adv };
4513
4570
  }
4514
- function alongAffine(sampler, dist, scale, widthEm) {
4571
+ function alongAffine(sampler, dist, scale, widthEm, perp = 0) {
4515
4572
  const { x, y, angle } = sampler.sampleAtDistance(dist);
4516
4573
  const cos = Math.cos(angle), sin = Math.sin(angle), hw = widthEm / 2;
4517
- return [scale * cos, scale * sin, -scale * sin, scale * cos, x - scale * cos * hw, y - scale * sin * hw];
4574
+ return [scale * cos, scale * sin, -scale * sin, scale * cos, x - scale * cos * hw - perp * sin, y - scale * sin * hw + perp * cos];
4575
+ }
4576
+ function alongPathNodeOffsets(node) {
4577
+ var _a, _b, _c;
4578
+ return {
4579
+ along: ((_a = parseLen(node.x)) != null ? _a : 0) + ((_b = parseLen(node.dx)) != null ? _b : 0),
4580
+ perp: (_c = parseLen(node.dy)) != null ? _c : 0
4581
+ };
4518
4582
  }
4519
4583
  function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLength, pathOverflow) {
4520
4584
  var _a;
@@ -4527,6 +4591,7 @@ var PixodeskAnimatorReact = (() => {
4527
4591
  const soleFont = soleFontOf(glyphs);
4528
4592
  const { cells, width } = collectAlongPathCells(node, glyphs, soleFont, warnings);
4529
4593
  if (!cells.length) return toGroup(node, [], create);
4594
+ const { along: alongOffset, perp } = alongPathNodeOffsets(node);
4530
4595
  if (textLength && textLength > 0 && width > 0) {
4531
4596
  const k = textLength / width;
4532
4597
  for (const c of cells) c.midBase *= k;
@@ -4534,9 +4599,9 @@ var PixodeskAnimatorReact = (() => {
4534
4599
  const isClip = pathOverflow === "clip" && !sampler.closed;
4535
4600
  const so = readAnimatable(startOffset);
4536
4601
  if (so.kind === "animated" && so.keyframes.length >= 2) {
4537
- return toGroup(node, buildAnimatedAlongPath(cells, sampler, so.keyframes, so.loop, create, isClip), create);
4602
+ return toGroup(node, buildAnimatedAlongPath(cells, sampler, so.keyframes, so.loop, create, isClip, alongOffset, perp), create);
4538
4603
  }
4539
- const base = so.kind === "animated" ? Number((_a = so.keyframes[0]) == null ? void 0 : _a.value) || 0 : so.kind === "static" ? Number(so.value) || 0 : 0;
4604
+ const base = alongOffset + (so.kind === "animated" ? Number((_a = so.keyframes[0]) == null ? void 0 : _a.value) || 0 : so.kind === "static" ? Number(so.value) || 0 : 0);
4540
4605
  const placeCells = isClip ? cells.filter((c) => {
4541
4606
  const d = base + c.midBase;
4542
4607
  return d >= 0 && d <= sampler.totalLength;
@@ -4544,7 +4609,7 @@ var PixodeskAnimatorReact = (() => {
4544
4609
  const placements = placeCells.map((c) => ({
4545
4610
  glyphD: c.glyphD,
4546
4611
  paint: c.paint,
4547
- m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm)
4612
+ m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm, perp)
4548
4613
  }));
4549
4614
  return toGroup(node, buildPaths(placements, create, warnings), create);
4550
4615
  }
@@ -4554,10 +4619,10 @@ var PixodeskAnimatorReact = (() => {
4554
4619
  const f = __pow(10, n);
4555
4620
  return Math.round(v * f) / f;
4556
4621
  }
4557
- function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create, isClip) {
4622
+ function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create, isClip, alongOffset = 0, perp = 0) {
4558
4623
  const step = Math.max(sampler.totalLength / ALONG_PATH_MAX_STEPS, 0.5);
4559
4624
  const timeOf = (kf) => Number(kf.time) || 0;
4560
- const offOf = (kf) => Number(kf.value) || 0;
4625
+ const offOf = (kf) => alongOffset + (Number(kf.value) || 0);
4561
4626
  const onPath = (dist) => dist >= 0 && dist <= sampler.totalLength;
4562
4627
  const out = [];
4563
4628
  for (const c of cells) {
@@ -4565,13 +4630,14 @@ var PixodeskAnimatorReact = (() => {
4565
4630
  const d = transformPathData(c.glyphD, centred);
4566
4631
  const sampleKf = (dist, time) => {
4567
4632
  const { x, y, angle } = sampler.sampleAtDistance(dist);
4633
+ const cos = Math.cos(angle), sin = Math.sin(angle);
4568
4634
  return {
4569
4635
  time,
4570
4636
  value: {
4571
4637
  [
4572
4638
  "translate"
4573
4639
  /* Translate */
4574
- ]: [roundN(x, 3), roundN(y, 3)],
4640
+ ]: [roundN(x - perp * sin, 3), roundN(y + perp * cos, 3)],
4575
4641
  [
4576
4642
  "rotate"
4577
4643
  /* Rotate */
@@ -4838,9 +4904,18 @@ var PixodeskAnimatorReact = (() => {
4838
4904
  if (pv === void 0 && nv === void 0) continue;
4839
4905
  value[k] = interpolatePart(pv, nv, p);
4840
4906
  }
4841
- if (rotateDegFromAutoOrient !== void 0) value.rotate = rotateDegFromAutoOrient;
4907
+ if (rotateDegFromAutoOrient !== void 0) {
4908
+ value.rotate = rotateDegFromAutoOrient + explicitRotateAt(prevV, nextV, p);
4909
+ }
4842
4910
  return value;
4843
4911
  }
4912
+ function explicitRotateAt(prevV, nextV, p) {
4913
+ const pv = typeof (prevV == null ? void 0 : prevV.rotate) === "number" ? prevV.rotate : void 0;
4914
+ const nv = typeof (nextV == null ? void 0 : nextV.rotate) === "number" ? nextV.rotate : void 0;
4915
+ if (pv === void 0 && nv === void 0) return 0;
4916
+ const r = interpolatePart(pv, nv, p);
4917
+ return typeof r === "number" ? r : 0;
4918
+ }
4844
4919
  function derivAngleForFirstKf(kf0, kf1) {
4845
4920
  const p0 = getKfTranslate(kf0);
4846
4921
  const p1 = getKfTranslate(kf1);
@@ -4861,10 +4936,12 @@ var PixodeskAnimatorReact = (() => {
4861
4936
  const lastV = (_a = lastKf.v) != null ? _a : lastKf.value;
4862
4937
  const prevExit = lastV == null ? void 0 : lastV.rotate;
4863
4938
  if (typeof prevExit !== "number") return;
4939
+ const boundaryV = getKfValueParts(prevKf);
4940
+ const prevExitTangent = prevExit - explicitRotateAt(boundaryV, boundaryV, 0);
4864
4941
  const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);
4865
4942
  const tanAtStart = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);
4866
4943
  const nextEntry = Math.atan2(tanAtStart[1], tanAtStart[0]) * 180 / Math.PI;
4867
- const delta = wrappedAngleDelta(nextEntry, prevExit);
4944
+ const delta = wrappedAngleDelta(nextEntry, prevExitTangent);
4868
4945
  if (Math.abs(delta) <= rotationTol) return;
4869
4946
  const dupValue = buildOutKfValue(
4870
4947
  getKfValueParts(prevKf),
@@ -6057,7 +6134,7 @@ var PixodeskAnimatorReact = (() => {
6057
6134
  const originalId = typeof node.id === "string" ? node.id : void 0;
6058
6135
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
6059
6136
  if (!fx && !innerIdForContentRef) return node;
6060
- const { transformation, repeater, maskedBy, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
6137
+ const { transformation, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
6061
6138
  const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
6062
6139
  if (fx) delete node.effects;
6063
6140
  let n = node;
@@ -6083,6 +6160,7 @@ var PixodeskAnimatorReact = (() => {
6083
6160
  n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
6084
6161
  n = applyRepeaterEffect(n, repeater, ctx);
6085
6162
  n = applyMaskedByEffect(n, maskedBy, transformation, ctx);
6163
+ n = applyClipPathEffect(n, clipPath, ctx);
6086
6164
  if (innerIdForContentRef) {
6087
6165
  applyRefHref(n, cloneFx, ctx);
6088
6166
  n = splitForContentRef(n, transformation, originalId, innerIdForContentRef, ctx);
@@ -6405,6 +6483,8 @@ var PixodeskAnimatorReact = (() => {
6405
6483
  function renderNode(node, defs) {
6406
6484
  if (!node) return null;
6407
6485
  const _a = node, { type, children, style } = _a, props = __objRest(_a, ["type", "children", "style"]);
6486
+ const feFuncType = props.funcType;
6487
+ if (feFuncType !== void 0) delete props.funcType;
6408
6488
  const nodeDefs = getDefs(node) || defs;
6409
6489
  const resolvedStyle = resolveStyle2(style, nodeDefs);
6410
6490
  let childElements;
@@ -6417,13 +6497,15 @@ var PixodeskAnimatorReact = (() => {
6417
6497
  }
6418
6498
  }
6419
6499
  }
6420
- return createElement(
6500
+ const element = createElement(
6421
6501
  type || "g",
6422
6502
  getNormalizedProps(props),
6423
6503
  resolvedStyle,
6424
6504
  childElements,
6425
6505
  props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR]
6426
6506
  );
6507
+ if (element && feFuncType !== void 0) element.setAttribute("type", feFuncType);
6508
+ return element;
6427
6509
  }
6428
6510
  function setupAnimationTriggers(api, config) {
6429
6511
  const { startOn, outAction = "continue", scrollIntoViewThreshold = 0.5 } = config;
@@ -6432,7 +6514,12 @@ var PixodeskAnimatorReact = (() => {
6432
6514
  console.warn("setupAnimationTriggers: No root element found for animation.");
6433
6515
  return api;
6434
6516
  }
6517
+ let reversed = false;
6435
6518
  const start = () => {
6519
+ if (reversed) {
6520
+ reversed = false;
6521
+ api.setPlaybackRate(1);
6522
+ }
6436
6523
  api.play();
6437
6524
  };
6438
6525
  const handleEndAction = () => {
@@ -6444,6 +6531,8 @@ var PixodeskAnimatorReact = (() => {
6444
6531
  api.cancel();
6445
6532
  break;
6446
6533
  case "reverse":
6534
+ reversed = true;
6535
+ api.setPlaybackRate(-1);
6447
6536
  api.play();
6448
6537
  break;
6449
6538
  case "continue":
@@ -6504,6 +6593,7 @@ var PixodeskAnimatorReact = (() => {
6504
6593
  return "#" + id;
6505
6594
  }
6506
6595
  function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
6596
+ var _a;
6507
6597
  const config = getAnimatorConfig(doc) || {};
6508
6598
  const bindings = getNormalisedBindings(doc, PxAnimatorEngine.frames);
6509
6599
  const _iterations = config.iterations;
@@ -6514,18 +6604,24 @@ var PixodeskAnimatorReact = (() => {
6514
6604
  const duration = +(config.duration || DEFAULT_DURATION_MS);
6515
6605
  const totalDuration = duration && iterations ? duration * (iterations === Infinity ? Infinity : iterations) : duration ? (iterations != null ? iterations : 1) * duration : 0;
6516
6606
  const direction = config.direction || "normal";
6607
+ const fill = (_a = config.fill) != null ? _a : "forwards";
6608
+ const fillsForwards = fill === "forwards" || fill === "both";
6609
+ const fillsBackwards = fill === "backwards" || fill === "both";
6517
6610
  let timerId = null;
6518
6611
  let playing = false;
6519
- let timeBeforeLastStartMs = config.delay || 0;
6612
+ let timeBeforeLastStartMs = -(config.delay || 0);
6520
6613
  let lastStartedTs = 0;
6521
6614
  let playbackRate = 1;
6522
6615
  let finishCalled = false;
6523
6616
  const frameRate = config.frameRate;
6524
6617
  const minFrameIntervalMs = frameRate && frameRate > 0 ? 1e3 / frameRate : 0;
6525
6618
  let lastRenderTs = 0;
6526
- const getAnimCurrentTime = () => {
6619
+ const getRawAnimTime = () => {
6527
6620
  const runningElapsed = lastStartedTs ? (Date.now() - lastStartedTs) * playbackRate : 0;
6528
- let time = timeBeforeLastStartMs + runningElapsed;
6621
+ return timeBeforeLastStartMs + runningElapsed;
6622
+ };
6623
+ const getAnimCurrentTime = () => {
6624
+ let time = getRawAnimTime();
6529
6625
  if (Number.isFinite(totalDuration) && time > totalDuration) time = totalDuration;
6530
6626
  if (time < 0) time = 0;
6531
6627
  return time;
@@ -6570,40 +6666,59 @@ var PixodeskAnimatorReact = (() => {
6570
6666
  }
6571
6667
  }
6572
6668
  const tick = () => {
6573
- var _a;
6669
+ var _a2, _b;
6574
6670
  if (!adapter.isConnected()) {
6575
6671
  pauseAnim();
6576
6672
  return;
6577
6673
  }
6578
6674
  const currentTime = getAnimCurrentTime();
6579
- if (minFrameIntervalMs > 0) {
6580
- const now = Date.now();
6581
- if (lastRenderTs && now - lastRenderTs < minFrameIntervalMs) {
6582
- return;
6583
- }
6584
- lastRenderTs = now;
6585
- }
6586
- if (duration <= 0) {
6675
+ const rawTime = getRawAnimTime();
6676
+ if (rawTime < 0 && playbackRate > 0) {
6677
+ if (fillsBackwards) renderFrame(0);
6678
+ return;
6587
6679
  }
6588
- if (totalDuration && Number.isFinite(totalDuration) && currentTime >= totalDuration) {
6589
- renderFrame(totalDuration);
6680
+ if (playbackRate < 0 && rawTime <= 0) {
6681
+ pauseAnim();
6682
+ renderFrame(0);
6590
6683
  if (!finishCalled) {
6591
6684
  finishCalled = true;
6592
- (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
6685
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6593
6686
  }
6687
+ return;
6688
+ }
6689
+ if (playbackRate > 0 && totalDuration && Number.isFinite(totalDuration) && currentTime >= totalDuration) {
6594
6690
  pauseAnim();
6691
+ renderFrame(fillsForwards ? totalDuration : 0);
6692
+ if (!finishCalled) {
6693
+ finishCalled = true;
6694
+ (_b = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _b.call(callbacks);
6695
+ }
6595
6696
  return;
6596
6697
  }
6698
+ if (minFrameIntervalMs > 0) {
6699
+ const now = Date.now();
6700
+ if (lastRenderTs && now - lastRenderTs < minFrameIntervalMs) {
6701
+ return;
6702
+ }
6703
+ lastRenderTs = now;
6704
+ }
6597
6705
  renderFrame(currentTime);
6598
6706
  };
6599
6707
  if (config.delay) {
6600
- renderFrame(getAnimCurrentTime());
6708
+ if (config.delay < 0) {
6709
+ renderFrame(getAnimCurrentTime());
6710
+ } else if (fillsBackwards) {
6711
+ renderFrame(0);
6712
+ }
6601
6713
  }
6602
6714
  const _isPlaying = () => {
6603
6715
  if (!playing) return false;
6604
6716
  if (!adapter.isConnected()) {
6605
6717
  return false;
6606
6718
  }
6719
+ if (playbackRate < 0) {
6720
+ return getRawAnimTime() > 0;
6721
+ }
6607
6722
  if (Number.isFinite(totalDuration) && getAnimCurrentTime() >= totalDuration) return false;
6608
6723
  return true;
6609
6724
  };
@@ -6623,37 +6738,53 @@ var PixodeskAnimatorReact = (() => {
6623
6738
  };
6624
6739
  const startAnim = () => {
6625
6740
  if (playing) return;
6626
- if (Number.isFinite(totalDuration) && timeBeforeLastStartMs >= totalDuration) {
6627
- timeBeforeLastStartMs = 0;
6628
- finishCalled = false;
6741
+ if (playbackRate >= 0) {
6742
+ if (Number.isFinite(totalDuration) && timeBeforeLastStartMs >= totalDuration) {
6743
+ timeBeforeLastStartMs = 0;
6744
+ }
6745
+ } else {
6746
+ if (timeBeforeLastStartMs <= 0) {
6747
+ if (!Number.isFinite(totalDuration)) {
6748
+ console.warn("play: cannot start reverse playback of an infinite animation from time 0");
6749
+ return;
6750
+ }
6751
+ timeBeforeLastStartMs = totalDuration;
6752
+ }
6629
6753
  }
6754
+ finishCalled = false;
6630
6755
  playing = true;
6631
6756
  lastStartedTs = Date.now();
6632
6757
  loopAnim(true);
6633
6758
  };
6634
6759
  const pauseAnim = () => {
6635
6760
  if (!playing) return;
6636
- timeBeforeLastStartMs = getAnimCurrentTime();
6761
+ let raw = getRawAnimTime();
6762
+ if (Number.isFinite(totalDuration) && raw > totalDuration) raw = totalDuration;
6763
+ timeBeforeLastStartMs = raw;
6637
6764
  lastStartedTs = 0;
6638
6765
  playing = false;
6639
6766
  if (timerId !== null) {
6640
6767
  cancelAnimationFrame(timerId);
6641
6768
  timerId = null;
6642
6769
  }
6643
- renderFrame(timeBeforeLastStartMs);
6770
+ if (raw >= 0) {
6771
+ renderFrame(raw);
6772
+ } else if (fillsBackwards) {
6773
+ renderFrame(0);
6774
+ }
6644
6775
  };
6645
6776
  const cancelAnim = () => {
6646
- var _a;
6777
+ var _a2;
6647
6778
  pauseAnim();
6648
6779
  timeBeforeLastStartMs = 0;
6649
6780
  lastStartedTs = 0;
6650
6781
  playing = false;
6651
6782
  finishCalled = false;
6652
6783
  renderFrame(timeBeforeLastStartMs);
6653
- (_a = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a.call(callbacks);
6784
+ (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
6654
6785
  };
6655
6786
  const finishAnim = (callOnFinish = true) => {
6656
- var _a;
6787
+ var _a2;
6657
6788
  if (Number.isFinite(totalDuration)) {
6658
6789
  timeBeforeLastStartMs = totalDuration;
6659
6790
  } else {
@@ -6661,10 +6792,14 @@ var PixodeskAnimatorReact = (() => {
6661
6792
  }
6662
6793
  lastStartedTs = 0;
6663
6794
  playing = false;
6664
- renderFrame(timeBeforeLastStartMs);
6795
+ if (timerId !== null) {
6796
+ cancelAnimationFrame(timerId);
6797
+ timerId = null;
6798
+ }
6799
+ renderFrame(fillsForwards ? timeBeforeLastStartMs : 0);
6665
6800
  if (callOnFinish && !finishCalled) {
6666
6801
  finishCalled = true;
6667
- (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
6802
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6668
6803
  }
6669
6804
  };
6670
6805
  const api = {
@@ -6674,14 +6809,14 @@ var PixodeskAnimatorReact = (() => {
6674
6809
  return _isPlaying();
6675
6810
  },
6676
6811
  "play": () => {
6677
- var _a;
6812
+ var _a2;
6678
6813
  startAnim();
6679
- (_a = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a.call(callbacks);
6814
+ (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
6680
6815
  },
6681
6816
  "pause": () => {
6682
- var _a;
6817
+ var _a2;
6683
6818
  pauseAnim();
6684
- (_a = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a.call(callbacks);
6819
+ (_a2 = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a2.call(callbacks);
6685
6820
  },
6686
6821
  "cancel": () => {
6687
6822
  cancelAnim();
@@ -6694,7 +6829,9 @@ var PixodeskAnimatorReact = (() => {
6694
6829
  console.warn("setPlaybackRate: rate must be finite and non-zero");
6695
6830
  return;
6696
6831
  }
6697
- const current = getAnimCurrentTime();
6832
+ let current = getRawAnimTime();
6833
+ if (Number.isFinite(totalDuration) && current > totalDuration) current = totalDuration;
6834
+ if (rate < 0 !== playbackRate < 0) finishCalled = false;
6698
6835
  playbackRate = rate;
6699
6836
  timeBeforeLastStartMs = current;
6700
6837
  if (playing) lastStartedTs = Date.now();
@@ -6707,10 +6844,13 @@ var PixodeskAnimatorReact = (() => {
6707
6844
  if (Number.isFinite(totalDuration) && newTime > totalDuration) newTime = totalDuration;
6708
6845
  timeBeforeLastStartMs = newTime;
6709
6846
  if (playing) lastStartedTs = Date.now();
6847
+ if (!Number.isFinite(totalDuration) || newTime < totalDuration) finishCalled = false;
6710
6848
  renderFrame(getAnimCurrentTime());
6711
6849
  },
6712
6850
  "destroy": () => {
6851
+ var _a2;
6713
6852
  api.cancel();
6853
+ (_a2 = callbacks == null ? void 0 : callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
6714
6854
  }
6715
6855
  };
6716
6856
  return api;
@@ -6873,6 +7013,8 @@ var PixodeskAnimatorReact = (() => {
6873
7013
  if (typeof _iterations === "number") iterations = _iterations;
6874
7014
  if (_iterations === "infinite") iterations = Infinity;
6875
7015
  const unsupportedSet = /* @__PURE__ */ new Set();
7016
+ let finishNotified = false;
7017
+ let removeCalled = false;
6876
7018
  if (!(bindings == null ? void 0 : bindings.length)) {
6877
7019
  console.warn("createWebApiAnimator: No animation bindings defined");
6878
7020
  }
@@ -6889,7 +7031,11 @@ var PixodeskAnimatorReact = (() => {
6889
7031
  }
6890
7032
  const keyframesMap = convertToWebApiKeyframes(animDef, unsupportedSet, config);
6891
7033
  const positiveDelay = config.delay && config.delay > 0 ? config.delay : void 0;
6892
- const seekPosition = config.delay && config.delay < 0 && config.duration ? -config.delay % config.duration : void 0;
7034
+ let seekPosition;
7035
+ if (config.delay && config.delay < 0 && config.duration) {
7036
+ const rawSeek = -config.delay;
7037
+ seekPosition = iterations === Infinity ? rawSeek % config.duration : Math.min(rawSeek, config.duration * (iterations != null ? iterations : 1));
7038
+ }
6893
7039
  const effectOptions = {
6894
7040
  duration: config.duration,
6895
7041
  delay: positiveDelay,
@@ -6910,13 +7056,17 @@ var PixodeskAnimatorReact = (() => {
6910
7056
  const anim = new Animation(effect, document.timeline);
6911
7057
  if (callbacks == null ? void 0 : callbacks.onFinish) anim.onfinish = () => {
6912
7058
  var _a2;
6913
- return (_a2 = callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
7059
+ if (finishNotified) return;
7060
+ finishNotified = true;
7061
+ (_a2 = callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6914
7062
  };
6915
7063
  if (callbacks == null ? void 0 : callbacks.onRemove) anim.onremove = () => {
6916
7064
  var _a2;
6917
- return (_a2 = callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
7065
+ if (removeCalled) return;
7066
+ removeCalled = true;
7067
+ (_a2 = callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
6918
7068
  };
6919
- if (seekPosition) {
7069
+ if (seekPosition !== void 0) {
6920
7070
  anim.currentTime = seekPosition;
6921
7071
  }
6922
7072
  animations.push(anim);
@@ -6940,6 +7090,7 @@ var PixodeskAnimatorReact = (() => {
6940
7090
  },
6941
7091
  "play": () => {
6942
7092
  var _a2;
7093
+ finishNotified = false;
6943
7094
  animations.forEach((a) => a.play());
6944
7095
  (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
6945
7096
  },
@@ -6950,6 +7101,7 @@ var PixodeskAnimatorReact = (() => {
6950
7101
  },
6951
7102
  "cancel": () => {
6952
7103
  var _a2;
7104
+ finishNotified = false;
6953
7105
  animations.forEach((a) => a.cancel());
6954
7106
  (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
6955
7107
  },
@@ -6979,13 +7131,19 @@ var PixodeskAnimatorReact = (() => {
6979
7131
  return res !== null ? +res : null;
6980
7132
  },
6981
7133
  "setCurrentTime": (time) => {
7134
+ finishNotified = false;
6982
7135
  animations.forEach((a) => {
6983
7136
  a.currentTime = time;
6984
7137
  });
6985
7138
  },
6986
7139
  "destroy": () => {
7140
+ var _a2;
6987
7141
  api.cancel();
6988
7142
  animations.splice(0, animations.length);
7143
+ if (!removeCalled) {
7144
+ removeCalled = true;
7145
+ (_a2 = callbacks == null ? void 0 : callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
7146
+ }
6989
7147
  }
6990
7148
  };
6991
7149
  if (config.trigger) {
@@ -7127,7 +7285,6 @@ var PixodeskAnimatorReact = (() => {
7127
7285
  const effectsWarnings = validateNodeEffects(doc);
7128
7286
  for (const w of effectsWarnings) console.warn("[PxAnimator] effects shape warning:", w);
7129
7287
  const animatorConfig = getAnimatorConfig(doc) || {};
7130
- animatorConfig.debug = true;
7131
7288
  const engine = animatorConfig.mode === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi;
7132
7289
  doc = materialiseAllInTree(doc, engine);
7133
7290
  let rootElement = null;
@@ -7155,37 +7312,55 @@ var PixodeskAnimatorReact = (() => {
7155
7312
  return createAnimatorImpl(data, adapter, callbacks, container);
7156
7313
  }
7157
7314
  let animator = null;
7315
+ let pending = [];
7316
+ let destroyed = false;
7317
+ const enqueue = (call) => {
7318
+ if (animator) {
7319
+ call(animator);
7320
+ } else if (pending) {
7321
+ pending.push(call);
7322
+ }
7323
+ };
7158
7324
  fetch(src).then((res) => res.json()).then((json) => {
7325
+ if (destroyed) return;
7159
7326
  if (isPxElementFileFormat(json)) {
7160
7327
  animator = createAnimatorImpl(json, adapter, callbacks, container);
7328
+ const queued = pending;
7329
+ pending = null;
7330
+ queued == null ? void 0 : queued.forEach((call) => call(animator));
7161
7331
  } else {
7162
- console.error("Invalid animation document format");
7332
+ console.error('createAnimator: invalid animation document format at "' + src + '"');
7163
7333
  }
7334
+ }).catch((err) => {
7335
+ pending = null;
7336
+ console.error('createAnimator: failed to load "' + src + '"', err);
7164
7337
  });
7165
7338
  return {
7166
7339
  "isReady": () => !!animator,
7167
7340
  "getRootElement": () => animator ? animator.getRootElement() : null,
7168
7341
  "isPlaying": () => (animator == null ? void 0 : animator.isPlaying()) || false,
7169
7342
  "play": () => {
7170
- animator == null ? void 0 : animator.play();
7343
+ enqueue((api) => api.play());
7171
7344
  },
7172
7345
  "pause": () => {
7173
- animator == null ? void 0 : animator.pause();
7346
+ enqueue((api) => api.pause());
7174
7347
  },
7175
7348
  "cancel": () => {
7176
- animator == null ? void 0 : animator.cancel();
7349
+ enqueue((api) => api.cancel());
7177
7350
  },
7178
7351
  "finish": () => {
7179
- animator == null ? void 0 : animator.finish();
7352
+ enqueue((api) => api.finish());
7180
7353
  },
7181
7354
  "setPlaybackRate": (rate) => {
7182
- animator == null ? void 0 : animator.setPlaybackRate(rate);
7355
+ enqueue((api) => api.setPlaybackRate(rate));
7183
7356
  },
7184
- "getCurrentTime": () => (animator == null ? void 0 : animator.getCurrentTime()) || 0,
7357
+ "getCurrentTime": () => animator ? animator.getCurrentTime() : null,
7185
7358
  "setCurrentTime": (time) => {
7186
- animator == null ? void 0 : animator.setCurrentTime(time);
7359
+ enqueue((api) => api.setCurrentTime(time));
7187
7360
  },
7188
7361
  "destroy": () => {
7362
+ destroyed = true;
7363
+ pending = null;
7189
7364
  animator == null ? void 0 : animator.destroy();
7190
7365
  }
7191
7366
  };
@@ -7308,22 +7483,40 @@ var PixodeskAnimatorReact = (() => {
7308
7483
  style,
7309
7484
  doc,
7310
7485
  compMode,
7311
- apiHolderRef
7486
+ apiHolderRef,
7487
+ callbacksRef
7312
7488
  }) => {
7313
7489
  doc = generateNewIds(doc);
7314
7490
  const elementRefs = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
7315
- const renderNode2 = (node) => {
7491
+ const renderNode2 = (node, isRoot = false) => {
7316
7492
  if (!node) return null;
7317
7493
  const { type, animate, meta, children, ...props } = node;
7318
7494
  const normProps = getNormalizedProps(props);
7319
7495
  normProps["ref"] = (domEl) => {
7320
7496
  if (node["id"]) elementRefs.current.set(node["id"], domEl);
7321
7497
  };
7498
+ if (isRoot) {
7499
+ if (className) {
7500
+ normProps["className"] = normProps["className"] ? normProps["className"] + " " + className : className;
7501
+ }
7502
+ if (style) normProps["style"] = style;
7503
+ }
7322
7504
  return (0, import_react2.createElement)(type, normProps, children?.map((child) => renderNode2(child)));
7323
7505
  };
7324
- const root = doc ? renderNode2(doc) : null;
7506
+ const root = doc ? renderNode2(doc, true) : null;
7325
7507
  (0, import_react2.useEffect)(() => {
7326
- let api = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs) });
7508
+ const cb = (name, alsoStop = false) => () => {
7509
+ callbacksRef.current?.[name]?.();
7510
+ if (alsoStop) callbacksRef.current?.onStop?.();
7511
+ };
7512
+ const callbacks = {
7513
+ onPlay: cb("onPlay"),
7514
+ onPause: cb("onPause", true),
7515
+ onCancel: cb("onCancel", true),
7516
+ onFinish: cb("onFinish", true),
7517
+ onRemove: cb("onRemove", true)
7518
+ };
7519
+ let api = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs), callbacks });
7327
7520
  apiHolderRef.current = api;
7328
7521
  return () => {
7329
7522
  api?.destroy();
@@ -7347,7 +7540,6 @@ var PixodeskAnimatorReact = (() => {
7347
7540
  time,
7348
7541
  timeMs,
7349
7542
  apiRef,
7350
- timeline,
7351
7543
  // Overrides
7352
7544
  mode,
7353
7545
  delay,
@@ -7358,7 +7550,13 @@ var PixodeskAnimatorReact = (() => {
7358
7550
  frameRate,
7359
7551
  startOn,
7360
7552
  outAction,
7361
- scrollIntoViewThreshold
7553
+ scrollIntoViewThreshold,
7554
+ onPlay,
7555
+ onStop,
7556
+ onPause,
7557
+ onCancel,
7558
+ onFinish,
7559
+ onRemove
7362
7560
  }) => {
7363
7561
  let compMode = "static" /* static */;
7364
7562
  if (apiRef) {
@@ -7367,7 +7565,7 @@ var PixodeskAnimatorReact = (() => {
7367
7565
  compMode = "autoplay" /* autoplay */;
7368
7566
  } else if (time !== void 0 || timeMs !== void 0) {
7369
7567
  compMode = "fixedTime" /* fixedTime */;
7370
- } else {
7568
+ } else if (play !== void 0 || pause !== void 0) {
7371
7569
  compMode = "play" /* play */;
7372
7570
  }
7373
7571
  if (compMode !== "autoplay" /* autoplay */) {
@@ -7417,20 +7615,20 @@ var PixodeskAnimatorReact = (() => {
7417
7615
  }
7418
7616
  };
7419
7617
  }
7618
+ let seekMs;
7420
7619
  if (compMode === "fixedTime" /* fixedTime */) {
7421
- let delay2 = 0;
7422
- if (time !== void 0) delay2 = -time;
7423
- if (timeMs !== void 0) delay2 = -timeMs;
7424
7620
  const animator = doc.animator || {};
7425
- doc = {
7426
- ...doc,
7427
- animator: {
7428
- ...animator,
7429
- delay: delay2
7430
- }
7431
- };
7621
+ if (time !== void 0) {
7622
+ const iterationsValue = iterations ?? animator.iterations;
7623
+ const iterationsCount = typeof iterationsValue === "number" && iterationsValue >= 1 ? iterationsValue : 1;
7624
+ const singleDuration = duration ?? animator.duration ?? 1e3;
7625
+ seekMs = time * singleDuration * iterationsCount;
7626
+ }
7627
+ if (timeMs !== void 0) seekMs = timeMs;
7432
7628
  }
7433
7629
  const apiHolderRef = (0, import_react2.useRef)(null);
7630
+ const callbacksRef = (0, import_react2.useRef)({});
7631
+ callbacksRef.current = { onPlay, onStop, onPause, onCancel, onFinish, onRemove };
7434
7632
  (0, import_react2.useImperativeHandle)(apiRef, () => {
7435
7633
  return {
7436
7634
  isPlaying: () => apiHolderRef.current?.isPlaying() || false,
@@ -7438,18 +7636,22 @@ var PixodeskAnimatorReact = (() => {
7438
7636
  pause: () => apiHolderRef.current?.pause(),
7439
7637
  cancel: () => apiHolderRef.current?.cancel(),
7440
7638
  finish: () => apiHolderRef.current?.finish(),
7441
- getCurrentTime: () => apiHolderRef.current?.getCurrentTime() || null,
7639
+ setPlaybackRate: (rate) => apiHolderRef.current?.setPlaybackRate(rate),
7640
+ getCurrentTime: () => apiHolderRef.current?.getCurrentTime() ?? null,
7442
7641
  setCurrentTime: (time2) => apiHolderRef.current?.setCurrentTime(time2)
7443
7642
  };
7444
7643
  }, []);
7644
+ const key = useDepsVersion(compMode, doc, className, style);
7445
7645
  (0, import_react2.useEffect)(() => {
7446
7646
  if (compMode === "play" /* play */) {
7447
7647
  if (play && !pause) {
7448
7648
  apiHolderRef.current?.play();
7449
7649
  } else if (pause) {
7450
7650
  apiHolderRef.current?.pause();
7451
- } else {
7651
+ } else if (play === false) {
7452
7652
  apiHolderRef.current?.finish();
7653
+ } else {
7654
+ apiHolderRef.current?.play();
7453
7655
  }
7454
7656
  }
7455
7657
  return () => {
@@ -7457,14 +7659,22 @@ var PixodeskAnimatorReact = (() => {
7457
7659
  apiHolderRef.current?.pause();
7458
7660
  }
7459
7661
  };
7460
- }, [compMode, play, pause]);
7461
- const key = useDepsVersion(compMode, doc);
7662
+ }, [compMode, play, pause, key]);
7663
+ (0, import_react2.useEffect)(() => {
7664
+ if (compMode === "fixedTime" /* fixedTime */ && seekMs !== void 0) {
7665
+ apiHolderRef.current?.setCurrentTime(seekMs);
7666
+ apiHolderRef.current?.pause();
7667
+ }
7668
+ }, [compMode, seekMs, key]);
7462
7669
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
7463
7670
  PixodeskSvgAnimatorImplOnce,
7464
7671
  {
7672
+ className,
7673
+ style,
7465
7674
  compMode,
7466
7675
  doc,
7467
- apiHolderRef
7676
+ apiHolderRef,
7677
+ callbacksRef
7468
7678
  },
7469
7679
  key
7470
7680
  );
@@ -7497,7 +7707,10 @@ var PixodeskAnimatorReact = (() => {
7497
7707
  "div",
7498
7708
  {
7499
7709
  ref,
7500
- className: className + (state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""),
7710
+ className: [
7711
+ className,
7712
+ state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""
7713
+ ].filter(Boolean).join(" "),
7501
7714
  style,
7502
7715
  ...handlers,
7503
7716
  children