@agent-native/core-corpus 0.217.0 → 0.219.0

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.
Files changed (81) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +5 -3
  3. package/corpus/templates/analytics/CHANGELOG.md +2 -0
  4. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
  5. package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  6. package/corpus/templates/assets/CHANGELOG.md +6 -0
  7. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
  8. package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  9. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
  10. package/corpus/templates/brain/app/routes/sources.tsx +1 -1
  11. package/corpus/templates/brain/vitest.config.ts +7 -0
  12. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  13. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
  14. package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
  15. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  16. package/corpus/templates/clips/CHANGELOG.md +7 -20
  17. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
  18. package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
  19. package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
  20. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
  21. package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
  22. package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
  23. package/corpus/templates/community/qr/package.json +1 -1
  24. package/corpus/templates/content/CHANGELOG.md +7 -0
  25. package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
  26. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +770 -179
  27. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +75 -27
  28. package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
  29. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
  30. package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
  31. package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
  32. package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
  33. package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
  34. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
  35. package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
  36. package/corpus/templates/content/app/global.css +31 -0
  37. package/corpus/templates/content/app/i18n-data.ts +8 -0
  38. package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
  39. package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
  40. package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
  41. package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
  42. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
  43. package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
  44. package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
  45. package/corpus/templates/content/shared/suggestion-text.ts +20 -5
  46. package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
  47. package/corpus/templates/design/CHANGELOG.md +8 -0
  48. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
  49. package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
  50. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
  51. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  52. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  53. package/corpus/templates/design/app/pages/Index.tsx +18 -64
  54. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
  55. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
  56. package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
  57. package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
  58. package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
  59. package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
  60. package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
  61. package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
  62. package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
  63. package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
  64. package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
  65. package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
  66. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  67. package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
  68. package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  69. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
  70. package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
  71. package/corpus/templates/plan/CHANGELOG.md +6 -0
  72. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
  73. package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  74. package/corpus/templates/slides/CHANGELOG.md +1 -0
  75. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  76. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
  77. package/corpus/templates/slides/app/pages/Index.tsx +7 -2
  78. package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
  79. package/package.json +1 -1
  80. package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
  81. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
@@ -34,6 +34,11 @@ export type MotionAuthoredCssTargetResolver = (
34
34
  selector: string,
35
35
  ) => MotionAuthoredCssTargetResolution;
36
36
 
37
+ export interface MotionAuthoredCssImportOptions {
38
+ /** Ordered static style-block contents used to evaluate transition conflicts. */
39
+ transitionCssBlocks?: readonly string[];
40
+ }
41
+
37
42
  export type MotionAuthoredCssSimpleSelector =
38
43
  | { kind: "type"; name: string }
39
44
  | { kind: "id"; value: string }
@@ -101,8 +106,118 @@ const SUPPORTED_PROPERTIES = new Set([
101
106
  "border-width",
102
107
  "box-shadow",
103
108
  ]);
109
+ const BORDER_SIDE_COLOR_PROPERTIES = [
110
+ "border-top-color",
111
+ "border-right-color",
112
+ "border-bottom-color",
113
+ "border-left-color",
114
+ ];
115
+ const BORDER_SIDE_WIDTH_PROPERTIES = [
116
+ "border-top-width",
117
+ "border-right-width",
118
+ "border-bottom-width",
119
+ "border-left-width",
120
+ ];
121
+ const LOGICAL_BORDER_COLOR_PROPERTIES = [
122
+ "border-inline-color",
123
+ "border-block-color",
124
+ "border-inline-start-color",
125
+ "border-inline-end-color",
126
+ "border-block-start-color",
127
+ "border-block-end-color",
128
+ ];
129
+ const LOGICAL_BORDER_WIDTH_PROPERTIES = [
130
+ "border-inline-width",
131
+ "border-block-width",
132
+ "border-inline-start-width",
133
+ "border-inline-end-width",
134
+ "border-block-start-width",
135
+ "border-block-end-width",
136
+ ];
137
+ const BORDER_COLOR_PROPERTIES = [
138
+ ...BORDER_SIDE_COLOR_PROPERTIES,
139
+ ...LOGICAL_BORDER_COLOR_PROPERTIES,
140
+ ];
141
+ const BORDER_WIDTH_PROPERTIES = [
142
+ ...BORDER_SIDE_WIDTH_PROPERTIES,
143
+ ...LOGICAL_BORDER_WIDTH_PROPERTIES,
144
+ ];
145
+ const BORDER_COLOR_AND_WIDTH_PROPERTIES = [
146
+ "border-color",
147
+ "border-width",
148
+ ...BORDER_COLOR_PROPERTIES,
149
+ ...BORDER_WIDTH_PROPERTIES,
150
+ ];
151
+ const BORDER_RADIUS_CORNER_PROPERTIES = [
152
+ "border-top-left-radius",
153
+ "border-top-right-radius",
154
+ "border-bottom-right-radius",
155
+ "border-bottom-left-radius",
156
+ "border-start-start-radius",
157
+ "border-start-end-radius",
158
+ "border-end-start-radius",
159
+ "border-end-end-radius",
160
+ ];
161
+ const TRANSITION_SHORTHAND_TRACK_PROPERTIES: Readonly<
162
+ Record<string, readonly string[]>
163
+ > = {
164
+ background: ["background-color"],
165
+ border: BORDER_COLOR_AND_WIDTH_PROPERTIES,
166
+ "border-top": [
167
+ "border-color",
168
+ "border-width",
169
+ "border-top-color",
170
+ "border-top-width",
171
+ ],
172
+ "border-right": [
173
+ "border-color",
174
+ "border-width",
175
+ "border-right-color",
176
+ "border-right-width",
177
+ ],
178
+ "border-bottom": [
179
+ "border-color",
180
+ "border-width",
181
+ "border-bottom-color",
182
+ "border-bottom-width",
183
+ ],
184
+ "border-left": [
185
+ "border-color",
186
+ "border-width",
187
+ "border-left-color",
188
+ "border-left-width",
189
+ ],
190
+ "border-color": BORDER_COLOR_PROPERTIES,
191
+ "border-width": BORDER_WIDTH_PROPERTIES,
192
+ "border-inline": BORDER_COLOR_AND_WIDTH_PROPERTIES,
193
+ "border-block": BORDER_COLOR_AND_WIDTH_PROPERTIES,
194
+ "border-inline-start": BORDER_COLOR_AND_WIDTH_PROPERTIES,
195
+ "border-inline-end": BORDER_COLOR_AND_WIDTH_PROPERTIES,
196
+ "border-block-start": BORDER_COLOR_AND_WIDTH_PROPERTIES,
197
+ "border-block-end": BORDER_COLOR_AND_WIDTH_PROPERTIES,
198
+ "border-inline-color": BORDER_COLOR_PROPERTIES,
199
+ "border-block-color": BORDER_COLOR_PROPERTIES,
200
+ "border-inline-start-color": BORDER_COLOR_PROPERTIES,
201
+ "border-inline-end-color": BORDER_COLOR_PROPERTIES,
202
+ "border-block-start-color": BORDER_COLOR_PROPERTIES,
203
+ "border-block-end-color": BORDER_COLOR_PROPERTIES,
204
+ "border-inline-width": BORDER_WIDTH_PROPERTIES,
205
+ "border-block-width": BORDER_WIDTH_PROPERTIES,
206
+ "border-inline-start-width": BORDER_WIDTH_PROPERTIES,
207
+ "border-inline-end-width": BORDER_WIDTH_PROPERTIES,
208
+ "border-block-start-width": BORDER_WIDTH_PROPERTIES,
209
+ "border-block-end-width": BORDER_WIDTH_PROPERTIES,
210
+ "border-radius": BORDER_RADIUS_CORNER_PROPERTIES,
211
+ };
104
212
 
105
213
  const CSS_IDENTIFIER = /^-?[_a-zA-Z][_a-zA-Z0-9-]*$/;
214
+ const CSS_WIDE_KEYWORDS = new Set([
215
+ "inherit",
216
+ "initial",
217
+ "revert",
218
+ "revert-layer",
219
+ "unset",
220
+ ]);
106
221
  const RESERVED_ANIMATION_NAMES = new Set([
107
222
  "default",
108
223
  "inherit",
@@ -121,6 +236,7 @@ const RESERVED_ANIMATION_NAMES = new Set([
121
236
  export function importAuthoredCssAnimations(
122
237
  css: string,
123
238
  resolveTarget: MotionAuthoredCssTargetResolver,
239
+ options?: MotionAuthoredCssImportOptions,
124
240
  ): MotionAuthoredCssImportResult {
125
241
  let root: postcss.Root;
126
242
  try {
@@ -142,6 +258,7 @@ export function importAuthoredCssAnimations(
142
258
  });
143
259
 
144
260
  const reducedMotionOverrides = findReducedMotionOverrideDeclarations(root);
261
+
145
262
  const animationDeclarations: postcss.Declaration[] = [];
146
263
  root.walkDecls((declaration) => {
147
264
  if (
@@ -377,6 +494,62 @@ export function importAuthoredCssAnimations(
377
494
  const durationMs = motionTimelineDurationForTracks(0, tracks);
378
495
  const validation = validateTracks(tracks, durationMs);
379
496
  if (validation) return unsupported(validation);
497
+ const transitionCssBlocks = options?.transitionCssBlocks ?? [css];
498
+ if (
499
+ !Array.isArray(transitionCssBlocks) ||
500
+ !transitionCssBlocks.some((block) => block === css)
501
+ ) {
502
+ return unsupported(
503
+ "CSS transition analysis must include the current static style block.",
504
+ );
505
+ }
506
+ const transitionRoots: postcss.Root[] = [];
507
+ const transitionReducedMotionOverrides = new Set(reducedMotionOverrides);
508
+ for (const block of transitionCssBlocks) {
509
+ if (typeof block !== "string") {
510
+ return unsupported(
511
+ "CSS transition analysis requires static style-block contents.",
512
+ );
513
+ }
514
+ let transitionRoot: postcss.Root;
515
+ try {
516
+ transitionRoot = postcss.parse(block);
517
+ } catch {
518
+ return unsupported(
519
+ "CSS transition rules across style blocks could not be parsed safely.",
520
+ );
521
+ }
522
+ let hasImportedTransitionStylesheet = false;
523
+ transitionRoot.walkAtRules((atRule) => {
524
+ if (atRule.name.toLowerCase() === "import") {
525
+ hasImportedTransitionStylesheet = true;
526
+ }
527
+ });
528
+ if (hasImportedTransitionStylesheet) {
529
+ return unsupported(
530
+ "CSS transitions with unresolved @import stylesheets cannot be analyzed losslessly.",
531
+ );
532
+ }
533
+ for (const declaration of findReducedMotionOverrideDeclarations(
534
+ transitionRoot,
535
+ )) {
536
+ transitionReducedMotionOverrides.add(declaration);
537
+ }
538
+ transitionRoots.push(transitionRoot);
539
+ }
540
+
541
+ if (
542
+ hasConflictingCssTransitions(
543
+ transitionRoots,
544
+ tracks,
545
+ resolveTarget,
546
+ transitionReducedMotionOverrides,
547
+ )
548
+ ) {
549
+ return unsupported(
550
+ "CSS transitions overlap a Motion target property and cannot be synchronized with the Motion timeline.",
551
+ );
552
+ }
380
553
 
381
554
  const rangesToRemove: MotionAuthoredCssSourceRange[] = [];
382
555
  for (const [rule, declarations] of declarationsByRule) {
@@ -543,6 +716,749 @@ function isAnimationProperty(property: string): boolean {
543
716
  );
544
717
  }
545
718
 
719
+ function isTransitionProperty(property: string): boolean {
720
+ const normalized = decodeMotionCssIdentifier(property).toLowerCase();
721
+ return (
722
+ normalized === "transition" ||
723
+ normalized.startsWith("transition-") ||
724
+ /^-(?:webkit|moz|ms|o)-transition(?:-|$)/.test(normalized)
725
+ );
726
+ }
727
+
728
+ function normalizedTransitionProperty(property: string): string {
729
+ return decodeMotionCssIdentifier(property)
730
+ .toLowerCase()
731
+ .replace(/^-(?:webkit|moz|ms|o)-/, "");
732
+ }
733
+
734
+ function isTransitionTimingToken(token: string): boolean {
735
+ return (
736
+ /^(?:\d+(?:\.\d*)?|\.\d+)(?:ms|s)?$/i.test(token) ||
737
+ /^(?:ease|ease-in|ease-out|ease-in-out|linear|step-start|step-end|allow-discrete|normal)$/i.test(
738
+ token,
739
+ )
740
+ );
741
+ }
742
+
743
+ function hasConflictingCssTransitions(
744
+ roots: readonly postcss.Root[],
745
+ tracks: readonly MotionTrack[],
746
+ resolveTarget: MotionAuthoredCssTargetResolver,
747
+ reducedMotionOverrides: ReadonlySet<postcss.Declaration>,
748
+ ): boolean {
749
+ const propertiesByTarget = new Map<string, Set<string>>();
750
+ for (const track of tracks) {
751
+ const properties = propertiesByTarget.get(track.targetNodeId) ?? new Set();
752
+ properties.add(track.property.toLowerCase());
753
+ propertiesByTarget.set(track.targetNodeId, properties);
754
+ }
755
+ const declarationsByTarget = new Map<
756
+ string,
757
+ TransitionCascadeDeclaration[]
758
+ >();
759
+ const declarationsByTargetAndState = new Map<
760
+ string,
761
+ Map<string, TransitionCascadeDeclaration[]>
762
+ >();
763
+ const declarationGroupsByTarget = new Map<string, postcss.Declaration[][]>();
764
+ const uncertainDeclarationsByTarget = new Map<
765
+ string,
766
+ postcss.Declaration[][]
767
+ >();
768
+ let sourceOrder = 0;
769
+ let conflict = false;
770
+
771
+ for (const root of roots) {
772
+ let rootOrder = 0;
773
+ root.walkRules((rule) => {
774
+ if (conflict) return;
775
+ const currentSourceOrder = sourceOrder + rootOrder++;
776
+ if (isInsideKeyframes(rule)) return;
777
+ const declarations = (rule.nodes ?? []).filter(
778
+ (node): node is postcss.Declaration =>
779
+ node.type === "decl" &&
780
+ isTransitionProperty(node.prop) &&
781
+ !reducedMotionOverrides.has(node),
782
+ );
783
+ if (declarations.length === 0) return;
784
+
785
+ for (const candidate of splitCssSelectorList(rule.selector)) {
786
+ const simpleSelector = transitionTargetSelector(candidate);
787
+ const targetNodeIds = resolveTransitionTargets(
788
+ candidate,
789
+ simpleSelector,
790
+ resolveTarget,
791
+ );
792
+ const matchedTargets = targetNodeIds.includes("*")
793
+ ? [...propertiesByTarget.keys()]
794
+ : targetNodeIds;
795
+ if (matchedTargets.length === 0) continue;
796
+
797
+ const specificity =
798
+ rule.parent === root && simpleSelector !== null
799
+ ? transitionSelectorSpecificity(candidate, simpleSelector)
800
+ : null;
801
+ const state =
802
+ rule.parent === root && simpleSelector !== null
803
+ ? transitionSelectorState(candidate, simpleSelector)
804
+ : null;
805
+
806
+ for (const targetNodeId of matchedTargets) {
807
+ const targetProperties = propertiesByTarget.get(targetNodeId);
808
+ if (!targetProperties) continue;
809
+ const declarationGroups =
810
+ declarationGroupsByTarget.get(targetNodeId) ?? [];
811
+ declarationGroups.push(declarations);
812
+ declarationGroupsByTarget.set(targetNodeId, declarationGroups);
813
+
814
+ // Nested conditions and selectors outside the static target subset
815
+ // cannot safely participate in cross-rule cascade resolution.
816
+ if (specificity === null || state === null) {
817
+ if (
818
+ transitionDeclarationsConflict(declarations, targetProperties)
819
+ ) {
820
+ conflict = true;
821
+ return;
822
+ }
823
+ const uncertainDeclarations =
824
+ uncertainDeclarationsByTarget.get(targetNodeId) ?? [];
825
+ uncertainDeclarations.push(declarations);
826
+ uncertainDeclarationsByTarget.set(
827
+ targetNodeId,
828
+ uncertainDeclarations,
829
+ );
830
+ continue;
831
+ }
832
+
833
+ const candidates = declarations.map(
834
+ (declaration, declarationOrder) => ({
835
+ declaration,
836
+ specificity,
837
+ sourceOrder: currentSourceOrder,
838
+ declarationOrder,
839
+ }),
840
+ );
841
+ const targetDeclarations =
842
+ declarationsByTarget.get(targetNodeId) ?? [];
843
+ targetDeclarations.push(...candidates);
844
+ declarationsByTarget.set(targetNodeId, targetDeclarations);
845
+
846
+ const states =
847
+ declarationsByTargetAndState.get(targetNodeId) ?? new Map();
848
+ const stateDeclarations = states.get(state) ?? [];
849
+ stateDeclarations.push(...candidates);
850
+ states.set(state, stateDeclarations);
851
+ declarationsByTargetAndState.set(targetNodeId, states);
852
+ }
853
+ }
854
+ });
855
+ if (conflict) return true;
856
+ sourceOrder += rootOrder;
857
+ }
858
+
859
+ for (const [
860
+ targetNodeId,
861
+ declarationGroups,
862
+ ] of uncertainDeclarationsByTarget) {
863
+ const properties = propertiesByTarget.get(targetNodeId);
864
+ if (
865
+ properties &&
866
+ transitionDeclarationGroupsConflict(
867
+ declarationGroupsByTarget.get(targetNodeId) ?? declarationGroups,
868
+ properties,
869
+ )
870
+ ) {
871
+ return true;
872
+ }
873
+ }
874
+
875
+ for (const [targetNodeId, states] of declarationsByTargetAndState) {
876
+ const properties = propertiesByTarget.get(targetNodeId);
877
+ if (!properties) continue;
878
+ for (const declarations of states.values()) {
879
+ if (
880
+ transitionDeclarationsConflict(
881
+ sortTransitionCascade(declarations).map((entry) => entry.declaration),
882
+ properties,
883
+ )
884
+ ) {
885
+ return true;
886
+ }
887
+ }
888
+
889
+ // Different pseudo-class states can be active together. This combined
890
+ // check is conservative; per-state checks above prevent one state from
891
+ // hiding a conflict in another state through a later override.
892
+ const declarations = declarationsByTarget.get(targetNodeId) ?? [];
893
+ if (
894
+ transitionDeclarationsConflict(
895
+ sortTransitionCascade(declarations).map((entry) => entry.declaration),
896
+ properties,
897
+ )
898
+ ) {
899
+ return true;
900
+ }
901
+ }
902
+ return false;
903
+ }
904
+
905
+ interface TransitionCascadeDeclaration {
906
+ declaration: postcss.Declaration;
907
+ specificity: readonly [number, number, number];
908
+ sourceOrder: number;
909
+ declarationOrder: number;
910
+ }
911
+
912
+ function sortTransitionCascade(
913
+ declarations: readonly TransitionCascadeDeclaration[],
914
+ ): TransitionCascadeDeclaration[] {
915
+ return [...declarations].sort((left, right) => {
916
+ if (left.declaration.important !== right.declaration.important) {
917
+ return left.declaration.important ? 1 : -1;
918
+ }
919
+ for (let index = 0; index < 3; index += 1) {
920
+ const difference = left.specificity[index]! - right.specificity[index]!;
921
+ if (difference !== 0) return difference;
922
+ }
923
+ return (
924
+ left.sourceOrder - right.sourceOrder ||
925
+ left.declarationOrder - right.declarationOrder
926
+ );
927
+ });
928
+ }
929
+
930
+ function transitionSelectorState(
931
+ selector: string,
932
+ simpleSelector: string,
933
+ ): string | null {
934
+ if (simpleSelector === "*") {
935
+ return selector.trim().slice(1).toLowerCase();
936
+ }
937
+ const trimmed = selector.trim();
938
+ if (!trimmed.startsWith(simpleSelector)) return null;
939
+ const suffix = trimmed.slice(simpleSelector.length).trim();
940
+ if (
941
+ suffix &&
942
+ !/^(?::[_a-zA-Z][-_a-zA-Z0-9]*(?:\([^()]*\))?)+$/.test(suffix)
943
+ ) {
944
+ return null;
945
+ }
946
+ return suffix.toLowerCase();
947
+ }
948
+
949
+ function transitionSelectorSpecificity(
950
+ selector: string,
951
+ simpleSelector: string,
952
+ ): readonly [number, number, number] | null {
953
+ let specificity: [number, number, number] = [0, 0, 0];
954
+ const simple = parseMotionAuthoredCssSimpleSelector(simpleSelector);
955
+ if (simple?.kind === "id") specificity[0] = 1;
956
+ else if (simple?.kind === "class" || simple?.kind === "attribute") {
957
+ specificity[1] = 1;
958
+ } else if (simple?.kind === "type") specificity[2] = 1;
959
+ else if (simpleSelector !== "*") return null;
960
+
961
+ const state = transitionSelectorState(selector, simpleSelector);
962
+ if (state === null || state.length === 0)
963
+ return state === null ? null : specificity;
964
+ const pseudoClasses = /:([_a-zA-Z][-_a-zA-Z0-9]*)(?:\(([^()]*)\))?/g;
965
+ let matched = "";
966
+ for (const match of state.matchAll(pseudoClasses)) {
967
+ matched += match[0];
968
+ const name = match[1]!.toLowerCase();
969
+ if (match[2] !== undefined) {
970
+ if (name !== "where") return null;
971
+ continue;
972
+ }
973
+ specificity[1] += 1;
974
+ }
975
+ return matched === state ? specificity : null;
976
+ }
977
+
978
+ function resolveTransitionTargets(
979
+ selectorText: string,
980
+ simpleSelector: string | null,
981
+ resolveTarget: MotionAuthoredCssTargetResolver,
982
+ ): string[] {
983
+ if (
984
+ /(?:^|[^\\]):{2}[_a-zA-Z]|:(?:before|after|first-line|first-letter)(?:$|[^-_a-zA-Z0-9])/i.test(
985
+ selectorText,
986
+ )
987
+ ) {
988
+ return [];
989
+ }
990
+ const selectors = simpleSelector
991
+ ? [simpleSelector]
992
+ : selectorCompoundAtoms(lastSelectorCompound(selectorText));
993
+ if (selectors.length === 0) return ["*"];
994
+ const targetNodeIds = new Set<string>();
995
+ for (const selector of selectors) {
996
+ if (selector === "*") return ["*"];
997
+ if (!parseMotionAuthoredCssSimpleSelector(selector)) return ["*"];
998
+ try {
999
+ const resolution = resolveTarget(selector);
1000
+ if (!resolution || typeof resolution !== "object") return ["*"];
1001
+ if (resolution.status === "resolved") {
1002
+ if (
1003
+ typeof resolution.targetNodeId !== "string" ||
1004
+ resolution.targetNodeId.trim().length === 0
1005
+ ) {
1006
+ return ["*"];
1007
+ }
1008
+ targetNodeIds.add(resolution.targetNodeId);
1009
+ } else if (resolution.status === "unresolved") {
1010
+ return [];
1011
+ } else {
1012
+ return ["*"];
1013
+ }
1014
+ } catch {
1015
+ return ["*"];
1016
+ }
1017
+ }
1018
+ return [...targetNodeIds];
1019
+ }
1020
+
1021
+ function lastSelectorCompound(selector: string): string {
1022
+ let start = 0;
1023
+ let parentheses = 0;
1024
+ let brackets = 0;
1025
+ let quote: "'" | '"' | null = null;
1026
+ for (let index = 0; index < selector.length; index += 1) {
1027
+ const character = selector[index]!;
1028
+ if (quote) {
1029
+ if (character === "\\") index += 1;
1030
+ else if (character === quote) quote = null;
1031
+ continue;
1032
+ }
1033
+ if (character === "\\") {
1034
+ index += 1;
1035
+ } else if (character === "'" || character === '"') {
1036
+ quote = character;
1037
+ } else if (character === "(") {
1038
+ parentheses += 1;
1039
+ } else if (character === ")") {
1040
+ parentheses = Math.max(0, parentheses - 1);
1041
+ } else if (character === "[") {
1042
+ brackets += 1;
1043
+ } else if (character === "]") {
1044
+ brackets = Math.max(0, brackets - 1);
1045
+ } else if (
1046
+ parentheses === 0 &&
1047
+ brackets === 0 &&
1048
+ (/\s/.test(character) || ">+~".includes(character))
1049
+ ) {
1050
+ start = index + 1;
1051
+ }
1052
+ }
1053
+ return selector.slice(start).trim();
1054
+ }
1055
+
1056
+ function selectorCompoundAtoms(compound: string): string[] {
1057
+ const atoms: string[] = [];
1058
+ let index = 0;
1059
+ while (index < compound.length) {
1060
+ const character = compound[index]!;
1061
+ if (character === "\\") {
1062
+ index += 2;
1063
+ continue;
1064
+ }
1065
+ if (character === "[") {
1066
+ const start = index;
1067
+ let quote: "'" | '"' | null = null;
1068
+ index += 1;
1069
+ while (index < compound.length) {
1070
+ const current = compound[index]!;
1071
+ if (quote) {
1072
+ if (current === "\\") index += 1;
1073
+ else if (current === quote) quote = null;
1074
+ } else if (current === "\\") {
1075
+ index += 1;
1076
+ } else if (current === "'" || current === '"') {
1077
+ quote = current;
1078
+ } else if (current === "]") {
1079
+ index += 1;
1080
+ break;
1081
+ }
1082
+ index += 1;
1083
+ }
1084
+ atoms.push(compound.slice(start, index));
1085
+ continue;
1086
+ }
1087
+ if (character === ":") {
1088
+ index += compound[index + 1] === ":" ? 2 : 1;
1089
+ while (/[_a-zA-Z0-9-]/.test(compound[index] ?? "")) index += 1;
1090
+ if (compound[index] === "(") {
1091
+ let depth = 0;
1092
+ do {
1093
+ const current = compound[index]!;
1094
+ if (current === "\\") index += 1;
1095
+ else if (current === "(") depth += 1;
1096
+ else if (current === ")") depth -= 1;
1097
+ index += 1;
1098
+ } while (index < compound.length && depth > 0);
1099
+ }
1100
+ continue;
1101
+ }
1102
+ if (character === "#" || character === ".") {
1103
+ const start = index++;
1104
+ while (/[_a-zA-Z0-9-]/.test(compound[index] ?? "")) index += 1;
1105
+ if (index > start + 1) atoms.push(compound.slice(start, index));
1106
+ continue;
1107
+ }
1108
+ if (character === "*" && index === 0) {
1109
+ atoms.push("*");
1110
+ index += 1;
1111
+ continue;
1112
+ }
1113
+ if (index === 0 && /[_a-zA-Z]/.test(character)) {
1114
+ const start = index++;
1115
+ while (/[_a-zA-Z0-9-]/.test(compound[index] ?? "")) index += 1;
1116
+ atoms.push(compound.slice(start, index));
1117
+ continue;
1118
+ }
1119
+ index += 1;
1120
+ }
1121
+ return atoms;
1122
+ }
1123
+
1124
+ function transitionTargetSelector(selector: string): string | null {
1125
+ const trimmed = selector.trim();
1126
+ if (trimmed === "*") return trimmed;
1127
+ if (
1128
+ /(?:^|[^\\])::[_a-zA-Z]/.test(trimmed) ||
1129
+ /:(?:before|after|first-line|first-letter)(?:$|[^-_a-zA-Z0-9])/i.test(
1130
+ trimmed,
1131
+ )
1132
+ ) {
1133
+ return null;
1134
+ }
1135
+ if (isStaticSimpleSelector(trimmed)) return trimmed;
1136
+ const match = /^(.*?)(?::[_a-zA-Z][-_a-zA-Z0-9]*(?:\([^()]*\))?)+$/.exec(
1137
+ trimmed,
1138
+ );
1139
+ const base = match?.[1]?.trim();
1140
+ if (!base || (base !== "*" && !isStaticSimpleSelector(base))) return null;
1141
+ return base;
1142
+ }
1143
+
1144
+ function splitCssSelectorList(value: string): string[] {
1145
+ const selectors: string[] = [];
1146
+ let start = 0;
1147
+ let parentheses = 0;
1148
+ let brackets = 0;
1149
+ let quote: "'" | '"' | null = null;
1150
+ for (let index = 0; index < value.length; index += 1) {
1151
+ const character = value[index]!;
1152
+ if (quote) {
1153
+ if (character === "\\") index += 1;
1154
+ else if (character === quote) quote = null;
1155
+ continue;
1156
+ }
1157
+ if (character === "\\") {
1158
+ index += 1;
1159
+ } else if (character === "'" || character === '"') {
1160
+ quote = character;
1161
+ } else if (character === "(") {
1162
+ parentheses += 1;
1163
+ } else if (character === ")") {
1164
+ parentheses = Math.max(0, parentheses - 1);
1165
+ } else if (character === "[") {
1166
+ brackets += 1;
1167
+ } else if (character === "]") {
1168
+ brackets = Math.max(0, brackets - 1);
1169
+ } else if (character === "," && parentheses === 0 && brackets === 0) {
1170
+ selectors.push(value.slice(start, index).trim());
1171
+ start = index + 1;
1172
+ }
1173
+ }
1174
+ selectors.push(value.slice(start).trim());
1175
+ return selectors.filter(Boolean);
1176
+ }
1177
+
1178
+ interface TransitionDeclarationEffect {
1179
+ properties: string[];
1180
+ propertyListMayBeInherited: boolean;
1181
+ durations: Array<number | null>;
1182
+ hasPropertyDeclaration: boolean;
1183
+ hasDurationDeclaration: boolean;
1184
+ }
1185
+
1186
+ function analyzeTransitionDeclarations(
1187
+ declarations: readonly postcss.Declaration[],
1188
+ ): TransitionDeclarationEffect | null {
1189
+ let effectiveProperties: string[] | null = null;
1190
+ let effectiveImportant = false;
1191
+ let propertyListMayBeInherited = false;
1192
+ let effectiveDurations: Array<number | null> | null = null;
1193
+ let durationsImportant = false;
1194
+ let hasTransitionBehavior = false;
1195
+ let hasPropertyDeclaration = false;
1196
+ let hasDurationDeclaration = false;
1197
+ for (const declaration of declarations) {
1198
+ const property = normalizedTransitionProperty(declaration.prop);
1199
+ if (property === "transition") {
1200
+ hasTransitionBehavior = true;
1201
+ hasPropertyDeclaration = true;
1202
+ hasDurationDeclaration = true;
1203
+ if (
1204
+ effectiveProperties === null ||
1205
+ declaration.important ||
1206
+ !effectiveImportant
1207
+ ) {
1208
+ effectiveProperties = transitionPropertyList(declaration.value, true);
1209
+ effectiveImportant = declaration.important;
1210
+ propertyListMayBeInherited = hasInheritedTransitionPropertyList(
1211
+ declaration.value,
1212
+ );
1213
+ }
1214
+ if (
1215
+ effectiveDurations === null ||
1216
+ declaration.important ||
1217
+ !durationsImportant
1218
+ ) {
1219
+ effectiveDurations = parseTransitionShorthandDurations(
1220
+ declaration.value,
1221
+ );
1222
+ durationsImportant = declaration.important;
1223
+ }
1224
+ } else if (property === "transition-property") {
1225
+ hasTransitionBehavior = true;
1226
+ hasPropertyDeclaration = true;
1227
+ if (
1228
+ effectiveProperties === null ||
1229
+ declaration.important ||
1230
+ !effectiveImportant
1231
+ ) {
1232
+ effectiveProperties = transitionPropertyList(declaration.value, false);
1233
+ effectiveImportant = declaration.important;
1234
+ propertyListMayBeInherited = hasInheritedTransitionPropertyList(
1235
+ declaration.value,
1236
+ );
1237
+ }
1238
+ } else if (property === "transition-duration") {
1239
+ hasTransitionBehavior = true;
1240
+ hasDurationDeclaration = true;
1241
+ if (
1242
+ effectiveDurations === null ||
1243
+ declaration.important ||
1244
+ !durationsImportant
1245
+ ) {
1246
+ effectiveDurations = parseTransitionDurationList(declaration.value);
1247
+ durationsImportant = declaration.important;
1248
+ }
1249
+ } else if (property.startsWith("transition-")) {
1250
+ hasTransitionBehavior = true;
1251
+ }
1252
+ }
1253
+ if (!hasTransitionBehavior) return null;
1254
+ return {
1255
+ properties: effectiveProperties ?? ["*"],
1256
+ propertyListMayBeInherited,
1257
+ durations: effectiveDurations ?? [0],
1258
+ hasPropertyDeclaration,
1259
+ hasDurationDeclaration,
1260
+ };
1261
+ }
1262
+
1263
+ function transitionDeclarationsConflict(
1264
+ declarations: readonly postcss.Declaration[],
1265
+ animatedProperties: ReadonlySet<string>,
1266
+ ): boolean {
1267
+ const effect = analyzeTransitionDeclarations(declarations);
1268
+ return effect
1269
+ ? transitionEffectsConflict(effect, effect, animatedProperties)
1270
+ : false;
1271
+ }
1272
+
1273
+ function transitionDeclarationGroupsConflict(
1274
+ declarationGroups: readonly (readonly postcss.Declaration[])[],
1275
+ animatedProperties: ReadonlySet<string>,
1276
+ ): boolean {
1277
+ const effects = declarationGroups
1278
+ .map(analyzeTransitionDeclarations)
1279
+ .filter((effect): effect is TransitionDeclarationEffect => effect !== null);
1280
+ for (const [propertyIndex, propertyEffect] of effects.entries()) {
1281
+ if (!propertyEffect.hasPropertyDeclaration) continue;
1282
+ for (const [durationIndex, durationEffect] of effects.entries()) {
1283
+ if (
1284
+ propertyIndex === durationIndex ||
1285
+ !durationEffect.hasDurationDeclaration
1286
+ ) {
1287
+ continue;
1288
+ }
1289
+ if (
1290
+ transitionEffectsConflict(
1291
+ propertyEffect,
1292
+ durationEffect,
1293
+ animatedProperties,
1294
+ )
1295
+ ) {
1296
+ return true;
1297
+ }
1298
+ }
1299
+ }
1300
+ return false;
1301
+ }
1302
+
1303
+ function transitionEffectsConflict(
1304
+ propertyEffect: TransitionDeclarationEffect,
1305
+ durationEffect: TransitionDeclarationEffect,
1306
+ animatedProperties: ReadonlySet<string>,
1307
+ ): boolean {
1308
+ const properties = propertyEffect.properties;
1309
+ const durations = durationEffect.durations;
1310
+ if (
1311
+ propertyEffect.propertyListMayBeInherited &&
1312
+ durations.some((duration) => duration !== 0)
1313
+ ) {
1314
+ return true;
1315
+ }
1316
+ for (const animatedProperty of animatedProperties) {
1317
+ let matchingDuration: number | null | undefined;
1318
+ for (const [index, property] of properties.entries()) {
1319
+ if (property === "none") continue;
1320
+ if (!transitionPropertyOverlapsTrack(property, animatedProperty)) {
1321
+ continue;
1322
+ }
1323
+ matchingDuration = durations[index % durations.length] ?? null;
1324
+ }
1325
+ if (matchingDuration === undefined) continue;
1326
+ if (matchingDuration !== 0) return true;
1327
+ }
1328
+ return false;
1329
+ }
1330
+
1331
+ function transitionPropertyOverlapsTrack(
1332
+ transitionProperty: string,
1333
+ animatedProperty: string,
1334
+ ): boolean {
1335
+ if (transitionProperty === "*" || transitionProperty === animatedProperty) {
1336
+ return true;
1337
+ }
1338
+ return (
1339
+ TRANSITION_SHORTHAND_TRACK_PROPERTIES[transitionProperty]?.includes(
1340
+ animatedProperty,
1341
+ ) === true ||
1342
+ TRANSITION_SHORTHAND_TRACK_PROPERTIES[animatedProperty]?.includes(
1343
+ transitionProperty,
1344
+ ) === true
1345
+ );
1346
+ }
1347
+
1348
+ function hasInheritedTransitionPropertyList(value: string): boolean {
1349
+ return /^(?:inherit|revert|revert-layer)$/i.test(value.trim());
1350
+ }
1351
+
1352
+ function transitionPropertyList(value: string, shorthand: boolean): string[] {
1353
+ const values = splitCssValueList(value);
1354
+ if (!shorthand) {
1355
+ return values.map((part) => {
1356
+ const property = normalizedTransitionProperty(part.trim());
1357
+ if (property === "none") return "none";
1358
+ if (property === "all") return "*";
1359
+ if (CSS_WIDE_KEYWORDS.has(property)) return "*";
1360
+ return CSS_IDENTIFIER.test(property) || property.startsWith("--")
1361
+ ? property
1362
+ : "*";
1363
+ });
1364
+ }
1365
+
1366
+ return values.map((part) => {
1367
+ const withoutFunctions = part.replace(
1368
+ /[_a-zA-Z-][-_a-zA-Z0-9]*\([^()]*\)/g,
1369
+ " ",
1370
+ );
1371
+ const identifiers = withoutFunctions.trim().split(/\s+/).filter(Boolean);
1372
+ const candidates = identifiers
1373
+ .map(normalizedTransitionProperty)
1374
+ .filter(
1375
+ (identifier) =>
1376
+ !isTransitionTimingToken(identifier) &&
1377
+ (CSS_IDENTIFIER.test(identifier) || identifier.startsWith("--")),
1378
+ );
1379
+ if (candidates.length === 1) {
1380
+ if (candidates[0] === "none") return "none";
1381
+ if (CSS_WIDE_KEYWORDS.has(candidates[0]!)) return "*";
1382
+ return candidates[0] === "all" ? "*" : candidates[0]!;
1383
+ }
1384
+ return candidates.length === 0 && /\bnone\b/i.test(part) ? "none" : "*";
1385
+ });
1386
+ }
1387
+
1388
+ function parseTransitionDurationList(value: string): Array<number | null> {
1389
+ const normalized = value.trim().toLowerCase();
1390
+ if (normalized === "initial" || normalized === "unset") return [0];
1391
+ if (CSS_WIDE_KEYWORDS.has(normalized)) return [null];
1392
+ return splitCssValueList(value).map(parseTransitionDurationMs);
1393
+ }
1394
+
1395
+ function parseTransitionShorthandDurations(
1396
+ value: string,
1397
+ ): Array<number | null> {
1398
+ const normalized = value.trim().toLowerCase();
1399
+ if (normalized === "initial" || normalized === "unset") return [0];
1400
+ if (CSS_WIDE_KEYWORDS.has(normalized)) return [null];
1401
+
1402
+ return splitCssValueList(value).map((part) => {
1403
+ const withoutEasingFunctions = part.replace(
1404
+ /(?:cubic-bezier|steps|linear)\s*\([^)]*\)/gi,
1405
+ " ",
1406
+ );
1407
+ if (/[_a-zA-Z-][-_a-zA-Z0-9]*\s*\(/.test(withoutEasingFunctions)) {
1408
+ return null;
1409
+ }
1410
+
1411
+ const tokens = withoutEasingFunctions.trim().split(/\s+/);
1412
+ const times: string[] = [];
1413
+ for (const token of tokens) {
1414
+ if (/^[+-]?(?:\d|\.)/.test(token)) {
1415
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:ms|s)$/i.test(token)) {
1416
+ return null;
1417
+ }
1418
+ times.push(token);
1419
+ }
1420
+ }
1421
+ if (times.length > 2) return null;
1422
+ if (times.length === 0) return 0;
1423
+ return parseTransitionDurationMs(times[0]!);
1424
+ });
1425
+ }
1426
+
1427
+ function parseTransitionDurationMs(value: string): number | null {
1428
+ const match = /^\+?((?:\d+(?:\.\d*)?|\.\d+))(ms|s)$/i.exec(value.trim());
1429
+ if (!match) return null;
1430
+ const durationMs =
1431
+ Number(match[1]) * (match[2]!.toLowerCase() === "s" ? 1000 : 1);
1432
+ return Number.isFinite(durationMs) && durationMs >= 0 ? durationMs : null;
1433
+ }
1434
+
1435
+ function splitCssValueList(value: string): string[] {
1436
+ const parts: string[] = [];
1437
+ let start = 0;
1438
+ let depth = 0;
1439
+ let quote: "'" | '"' | null = null;
1440
+ for (let index = 0; index < value.length; index += 1) {
1441
+ const character = value[index]!;
1442
+ if (quote) {
1443
+ if (character === "\\") index += 1;
1444
+ else if (character === quote) quote = null;
1445
+ continue;
1446
+ }
1447
+ if (character === "'" || character === '"') {
1448
+ quote = character;
1449
+ } else if (character === "(") {
1450
+ depth += 1;
1451
+ } else if (character === ")") {
1452
+ depth = Math.max(0, depth - 1);
1453
+ } else if (character === "," && depth === 0) {
1454
+ parts.push(value.slice(start, index));
1455
+ start = index + 1;
1456
+ }
1457
+ }
1458
+ parts.push(value.slice(start));
1459
+ return parts;
1460
+ }
1461
+
546
1462
  function findReducedMotionOverrideDeclarations(
547
1463
  root: postcss.Root,
548
1464
  ): Set<postcss.Declaration> {
@@ -567,26 +1483,40 @@ function findReducedMotionOverrideDeclarations(
567
1483
 
568
1484
  const declarations = rule.nodes.filter(
569
1485
  (node): node is postcss.Declaration =>
570
- node.type === "decl" && isAnimationProperty(node.prop),
1486
+ node.type === "decl" &&
1487
+ (isAnimationProperty(node.prop) || isTransitionProperty(node.prop)),
571
1488
  );
572
- const durations = declarations.filter(
1489
+ const animationDeclarations = declarations.filter((declaration) =>
1490
+ isAnimationProperty(declaration.prop),
1491
+ );
1492
+ const durations = animationDeclarations.filter(
573
1493
  (declaration) =>
574
1494
  decodeMotionCssIdentifier(declaration.prop).toLowerCase() ===
575
1495
  "animation-duration",
576
1496
  );
577
- const iterations = declarations.filter(
1497
+ const iterations = animationDeclarations.filter(
578
1498
  (declaration) =>
579
1499
  decodeMotionCssIdentifier(declaration.prop).toLowerCase() ===
580
1500
  "animation-iteration-count",
581
1501
  );
1502
+ const transitionDurations = declarations.filter(
1503
+ (declaration) =>
1504
+ decodeMotionCssIdentifier(declaration.prop).toLowerCase() ===
1505
+ "transition-duration",
1506
+ );
582
1507
  if (
583
- declarations.length !== 2 ||
1508
+ animationDeclarations.length !== 2 ||
584
1509
  durations.length !== 1 ||
585
1510
  iterations.length !== 1 ||
1511
+ transitionDurations.length > 1 ||
1512
+ declarations.length !== 2 + transitionDurations.length ||
586
1513
  durations[0]!.important !== true ||
587
1514
  !isReducedMotionDuration(durations[0]!.value) ||
588
1515
  iterations[0]!.important !== true ||
589
- iterations[0]!.value.trim() !== "1"
1516
+ iterations[0]!.value.trim() !== "1" ||
1517
+ (transitionDurations.length === 1 &&
1518
+ (!transitionDurations[0]!.important ||
1519
+ !isReducedMotionDuration(transitionDurations[0]!.value)))
590
1520
  ) {
591
1521
  return;
592
1522
  }
@@ -604,9 +1534,10 @@ function isReducedMotionDuration(value: string): boolean {
604
1534
  return Number.isFinite(durationMs) && durationMs >= 0 && durationMs <= 1;
605
1535
  }
606
1536
 
607
- function isInsideKeyframes(declaration: postcss.Declaration): boolean {
1537
+ function isInsideKeyframes(node: postcss.Node): boolean {
608
1538
  for (
609
- let parent: postcss.Node | undefined = declaration.parent;
1539
+ let parent: postcss.Node | undefined =
1540
+ node.type === "decl" ? node.parent : node;
610
1541
  parent;
611
1542
  parent = parent.parent ?? undefined
612
1543
  ) {