@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +5 -3
- package/corpus/templates/analytics/CHANGELOG.md +2 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/CHANGELOG.md +7 -0
- package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +770 -179
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +75 -27
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
- package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
- package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/i18n-data.ts +8 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
- package/corpus/templates/design/CHANGELOG.md +8 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
- package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
- package/corpus/templates/design/app/i18n/en-US.ts +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +18 -64
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
- package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
- package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
- package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
- package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
- package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
- package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
- package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +1 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- 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" &&
|
|
1486
|
+
node.type === "decl" &&
|
|
1487
|
+
(isAnimationProperty(node.prop) || isTransitionProperty(node.prop)),
|
|
571
1488
|
);
|
|
572
|
-
const
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
1537
|
+
function isInsideKeyframes(node: postcss.Node): boolean {
|
|
608
1538
|
for (
|
|
609
|
-
let parent: postcss.Node | undefined =
|
|
1539
|
+
let parent: postcss.Node | undefined =
|
|
1540
|
+
node.type === "decl" ? node.parent : node;
|
|
610
1541
|
parent;
|
|
611
1542
|
parent = parent.parent ?? undefined
|
|
612
1543
|
) {
|