nudge-ui 0.2.0 → 0.2.1
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/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +872 -12
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +60 -11
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +49 -6
- package/dist/hosts/next/wrapper.js +30 -2
- package/dist/hosts/projectBridge.d.ts +20 -0
- package/dist/hosts/projectBridge.js +97 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +44 -3
- package/dist/hosts/vite/vite.d.ts +5 -1
- package/dist/hosts/vite/vite.js +78 -14
- package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
- package/dist/inspector/agent/McpConnectionDialog.js +11 -35
- package/dist/inspector/agent/client.d.ts +10 -3
- package/dist/inspector/agent/client.js +80 -9
- package/dist/inspector/agent/connectionStatus.js +3 -3
- package/dist/inspector/agent/httpTransport.d.ts +2 -0
- package/dist/inspector/agent/httpTransport.js +37 -17
- package/dist/inspector/agent/protocol.d.ts +8 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +246 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +392 -73
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +69 -5
- package/dist/inspector/canvas/frameProtocol.js +56 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +29 -29
- package/dist/inspector/canvas/rendererElementSelector.js +341 -30
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/clientManifest.d.ts +5 -0
- package/dist/inspector/clientManifest.js +11 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +74 -24
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +353 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.js +10 -0
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +8 -4
- package/dist/inspector/shell/CopyPromptButton.js +95 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +37 -38
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +409 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +23 -30
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +91 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +111 -7
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/styles.js +13 -11
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +261 -57
- package/dist/transport/editor.d.ts +28 -0
- package/dist/transport/editor.js +132 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +7 -0
- package/dist/transport/routes.js +7 -0
- package/package.json +3 -1
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { INTERACTION_STATES } from "../../shell/styleState.js";
|
|
2
|
+
import { collectRules as collectCssomRules, documentRevisions as getDocumentRevisions } from "./cssomCollector.js";
|
|
3
|
+
import { computeSpecificity, hasSelectorPseudoClass, isElementSensitiveSelector, canMatchSelectorSubject, selectorPseudoClasses, splitSelectorAtTopLevel, } from "./selectorSemantics.js";
|
|
4
|
+
const TRANSIENT_PSEUDO_CLASSES = new Set([
|
|
5
|
+
"hover",
|
|
6
|
+
"active",
|
|
7
|
+
"focus",
|
|
8
|
+
"focus-visible",
|
|
9
|
+
"focus-within",
|
|
10
|
+
"visited",
|
|
11
|
+
"target",
|
|
12
|
+
]);
|
|
13
|
+
const INTERACTION_PSEUDO_CLASSES = new Set(INTERACTION_STATES);
|
|
14
|
+
function selectorForState(selector, state) {
|
|
15
|
+
const pseudoClasses = selectorPseudoClasses(selector)
|
|
16
|
+
.filter(({ name }) => INTERACTION_PSEUDO_CLASSES.has(name));
|
|
17
|
+
const states = new Set(pseudoClasses.map(({ name }) => name));
|
|
18
|
+
if (state === "base")
|
|
19
|
+
return states.size === 0 ? selector : null;
|
|
20
|
+
if (!states.has(state))
|
|
21
|
+
return states.size === 0 ? selector : null;
|
|
22
|
+
// CSSOM cannot ask the browser whether a hypothetical pseudo-class matches.
|
|
23
|
+
// Removing interaction pseudo-classes gives the authored rule a stable
|
|
24
|
+
// element match for inspection. The real rule remains untouched.
|
|
25
|
+
let transformed = selector;
|
|
26
|
+
for (let index = pseudoClasses.length - 1; index >= 0; index--) {
|
|
27
|
+
const pseudoClass = pseudoClasses[index];
|
|
28
|
+
transformed = transformed.slice(0, pseudoClass.start) + transformed.slice(pseudoClass.end);
|
|
29
|
+
}
|
|
30
|
+
return transformed;
|
|
31
|
+
}
|
|
32
|
+
function specificityForBranch(rule, branch) {
|
|
33
|
+
// Single-branch selectors reuse the specificity collected from CSSOM; the
|
|
34
|
+
// matched branch is the whole selector. Multi-branch selectors need the
|
|
35
|
+
// matched branch's own weight, so recompute per branch.
|
|
36
|
+
return rule.selectorText.includes(",") ? computeSpecificity(branch) : rule.specificity;
|
|
37
|
+
}
|
|
38
|
+
const transformedRulesMemo = new WeakMap();
|
|
39
|
+
function rulesForTransform(rules, transform) {
|
|
40
|
+
let byTransform = transformedRulesMemo.get(rules);
|
|
41
|
+
if (!byTransform) {
|
|
42
|
+
byTransform = new Map();
|
|
43
|
+
transformedRulesMemo.set(rules, byTransform);
|
|
44
|
+
}
|
|
45
|
+
const cached = byTransform.get(transform);
|
|
46
|
+
if (cached)
|
|
47
|
+
return cached;
|
|
48
|
+
let transformed;
|
|
49
|
+
if (transform === "live") {
|
|
50
|
+
transformed = rules.map((rule) => ({ rule, selectorText: rule.selectorText }));
|
|
51
|
+
}
|
|
52
|
+
else if (transform === "stable") {
|
|
53
|
+
transformed = rules
|
|
54
|
+
.filter((rule) => !hasSelectorPseudoClass(rule.selectorText, TRANSIENT_PSEUDO_CLASSES))
|
|
55
|
+
.map((rule) => ({ rule, selectorText: rule.selectorText }));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
transformed = rules.flatMap((rule) => {
|
|
59
|
+
const selectorText = selectorForState(rule.selectorText, transform);
|
|
60
|
+
return selectorText ? [{ rule, selectorText }] : [];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
byTransform.set(transform, transformed);
|
|
64
|
+
return transformed;
|
|
65
|
+
}
|
|
66
|
+
const sourceSiteMatchBucketsMemo = new WeakMap();
|
|
67
|
+
function sourceSiteMatchBuckets(rules, transform) {
|
|
68
|
+
let byTransform = sourceSiteMatchBucketsMemo.get(rules);
|
|
69
|
+
if (!byTransform) {
|
|
70
|
+
byTransform = new Map();
|
|
71
|
+
sourceSiteMatchBucketsMemo.set(rules, byTransform);
|
|
72
|
+
}
|
|
73
|
+
const cached = byTransform.get(transform);
|
|
74
|
+
if (cached)
|
|
75
|
+
return cached;
|
|
76
|
+
const cacheable = [];
|
|
77
|
+
const elementSensitive = [];
|
|
78
|
+
for (const entry of rulesForTransform(rules, transform)) {
|
|
79
|
+
(isElementSensitiveSelector(entry.selectorText) ? elementSensitive : cacheable).push(entry);
|
|
80
|
+
}
|
|
81
|
+
const buckets = { cacheable, elementSensitive };
|
|
82
|
+
byTransform.set(transform, buckets);
|
|
83
|
+
return buckets;
|
|
84
|
+
}
|
|
85
|
+
const SOURCE_SITE_CACHE_MAX = 4096;
|
|
86
|
+
let sourceSiteMatchCaches = new WeakMap();
|
|
87
|
+
let sourceSiteMatchCacheEntries = 0;
|
|
88
|
+
const concreteElementMatchCaches = new WeakMap();
|
|
89
|
+
// The all-rules sweep shares the active sweep's revision cache for stable
|
|
90
|
+
// selectors. Element-sensitive selectors stay fresh because browser state can
|
|
91
|
+
// change without a mutation observed by the registry.
|
|
92
|
+
const allMatchesMemo = new WeakMap();
|
|
93
|
+
/** Test-only cache reset. */
|
|
94
|
+
export function resetSourceSiteMatchCache() {
|
|
95
|
+
sourceSiteMatchCaches = new WeakMap();
|
|
96
|
+
sourceSiteMatchCacheEntries = 0;
|
|
97
|
+
}
|
|
98
|
+
/** Test-only cache size. */
|
|
99
|
+
export function sourceSiteMatchCacheSize() {
|
|
100
|
+
return sourceSiteMatchCacheEntries;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Captures selector-relevant state without serializing the whole document.
|
|
104
|
+
* Element and ancestor attributes cover the common source-site selectors while
|
|
105
|
+
* child/sibling-sensitive selectors are handled conservatively below.
|
|
106
|
+
*/
|
|
107
|
+
function sourceSiteContextKey(el) {
|
|
108
|
+
const lineage = [];
|
|
109
|
+
let current = el;
|
|
110
|
+
while (current) {
|
|
111
|
+
const attributes = Array.from(current.attributes)
|
|
112
|
+
.filter((attribute) => attribute.name !== "data-renderer-id")
|
|
113
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
114
|
+
.map((attribute) => `${attribute.name}=${attribute.value}`)
|
|
115
|
+
.join("\u0002");
|
|
116
|
+
lineage.push(`${current.tagName}\u0003${attributes}`);
|
|
117
|
+
current = current.parentElement;
|
|
118
|
+
}
|
|
119
|
+
return lineage.join("\u0001");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Source-site identity key for the matched-rule cache. The element's class is
|
|
123
|
+
* part of the key because sibling instances of a source site can carry
|
|
124
|
+
* different classes (for example rows in a large list), which changes which
|
|
125
|
+
* rules match even though their `data-cid`/`data-src` are identical.
|
|
126
|
+
*/
|
|
127
|
+
function sourceSiteKey(el, transform) {
|
|
128
|
+
const cid = el.getAttribute("data-cid") ?? "";
|
|
129
|
+
const src = el.getAttribute("data-src") ?? "";
|
|
130
|
+
const instance = el.getAttribute("data-projection-instance") ?? "";
|
|
131
|
+
const className = typeof el.className === "string" ? el.className : "";
|
|
132
|
+
return `${cid}\u0000${src}\u0000${instance}\u0000${className}\u0000${transform}`;
|
|
133
|
+
}
|
|
134
|
+
export function matchRuleForElement(el, entry) {
|
|
135
|
+
const branch = matchingSelectorBranch(el, entry.selectorText);
|
|
136
|
+
if (!branch)
|
|
137
|
+
return null;
|
|
138
|
+
return {
|
|
139
|
+
rule: entry.rule,
|
|
140
|
+
selectorText: entry.selectorText,
|
|
141
|
+
branch,
|
|
142
|
+
specificity: specificityForBranch({ ...entry.rule, selectorText: entry.selectorText }, branch),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Captures the current match outcome for selectors whose truth can change
|
|
147
|
+
* without a document revision. This keeps the expensive resolved-row cache
|
|
148
|
+
* usable while invalidating it when states such as `checked` actually change.
|
|
149
|
+
*/
|
|
150
|
+
function elementSensitiveMatchKey(el, rules, transform, sensitiveMatchesByElement) {
|
|
151
|
+
const entries = sourceSiteMatchBuckets(rules, transform).elementSensitive;
|
|
152
|
+
if (entries.length === 0)
|
|
153
|
+
return "";
|
|
154
|
+
const matches = [];
|
|
155
|
+
let current = el;
|
|
156
|
+
let depth = 0;
|
|
157
|
+
while (current) {
|
|
158
|
+
const elementMatches = [];
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
const match = matchRuleForElement(current, entry);
|
|
161
|
+
if (!match)
|
|
162
|
+
continue;
|
|
163
|
+
elementMatches.push(match);
|
|
164
|
+
matches.push(`${depth}:${entry.rule.sourceOrder ?? 0}:${match.branch}`);
|
|
165
|
+
}
|
|
166
|
+
sensitiveMatchesByElement?.set(current, elementMatches);
|
|
167
|
+
current = current.parentElement;
|
|
168
|
+
depth++;
|
|
169
|
+
}
|
|
170
|
+
return matches.join("\u0001");
|
|
171
|
+
}
|
|
172
|
+
function sensitiveMatchesForElement(el, buckets, matchesByElement) {
|
|
173
|
+
const cached = matchesByElement.get(el);
|
|
174
|
+
if (cached)
|
|
175
|
+
return cached;
|
|
176
|
+
const matched = buckets.elementSensitive.flatMap((entry) => {
|
|
177
|
+
const match = matchRuleForElement(el, entry);
|
|
178
|
+
return match ? [match] : [];
|
|
179
|
+
});
|
|
180
|
+
matchesByElement.set(el, matched);
|
|
181
|
+
return matched;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Matches transformed rules against one element. Stable source-site identities
|
|
185
|
+
* reuse revision-keyed matches; unstable identities and relationship-sensitive
|
|
186
|
+
* selectors are matched on each call.
|
|
187
|
+
*/
|
|
188
|
+
function getCachedElementMatches(el, rules, transform, sensitiveMatchesByElement = new Map()) {
|
|
189
|
+
const buckets = sourceSiteMatchBuckets(rules, transform);
|
|
190
|
+
const { cacheable, elementSensitive } = buckets;
|
|
191
|
+
const collect = (entries) => {
|
|
192
|
+
const matched = [];
|
|
193
|
+
for (const entry of entries) {
|
|
194
|
+
if (entry.rule.active === false)
|
|
195
|
+
continue;
|
|
196
|
+
const match = matchRuleForElement(el, entry);
|
|
197
|
+
if (match)
|
|
198
|
+
matched.push(match);
|
|
199
|
+
}
|
|
200
|
+
return matched;
|
|
201
|
+
};
|
|
202
|
+
const cid = el.getAttribute("data-cid");
|
|
203
|
+
const doc = el.ownerDocument ?? document;
|
|
204
|
+
const revisions = getDocumentRevisions(doc);
|
|
205
|
+
if (!cid || cacheable.length === 0) {
|
|
206
|
+
let cachedMatches = [];
|
|
207
|
+
if (cacheable.length > 0) {
|
|
208
|
+
let cache = concreteElementMatchCaches.get(el);
|
|
209
|
+
if (!cache) {
|
|
210
|
+
cache = new Map();
|
|
211
|
+
concreteElementMatchCaches.set(el, cache);
|
|
212
|
+
}
|
|
213
|
+
const cached = cache.get(transform);
|
|
214
|
+
if (cached
|
|
215
|
+
&& cached.elementRevision === revisions.element
|
|
216
|
+
&& cached.stylesheetRevision === revisions.stylesheet) {
|
|
217
|
+
cachedMatches = cached.matched;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
cachedMatches = collect(cacheable);
|
|
221
|
+
cache.set(transform, {
|
|
222
|
+
elementRevision: revisions.element,
|
|
223
|
+
stylesheetRevision: revisions.stylesheet,
|
|
224
|
+
matched: cachedMatches,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const sensitiveMatches = elementSensitive.length > 0
|
|
229
|
+
? sensitiveMatchesForElement(el, buckets, sensitiveMatchesByElement).filter((match) => match.rule.active !== false)
|
|
230
|
+
: [];
|
|
231
|
+
return sensitiveMatches.length === 0 ? cachedMatches : [...cachedMatches, ...sensitiveMatches];
|
|
232
|
+
}
|
|
233
|
+
const key = `${sourceSiteKey(el, transform)}\u0000${sourceSiteContextKey(el)}`;
|
|
234
|
+
let cache = sourceSiteMatchCaches.get(doc);
|
|
235
|
+
if (!cache) {
|
|
236
|
+
cache = new Map();
|
|
237
|
+
sourceSiteMatchCaches.set(doc, cache);
|
|
238
|
+
}
|
|
239
|
+
const cached = cache.get(key);
|
|
240
|
+
let cachedMatches;
|
|
241
|
+
if (cached
|
|
242
|
+
&& cached.elementRevision === revisions.element
|
|
243
|
+
&& cached.stylesheetRevision === revisions.stylesheet) {
|
|
244
|
+
cachedMatches = cached.matched;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
cachedMatches = collect(cacheable);
|
|
248
|
+
if (sourceSiteMatchCacheEntries >= SOURCE_SITE_CACHE_MAX) {
|
|
249
|
+
sourceSiteMatchCaches = new WeakMap();
|
|
250
|
+
sourceSiteMatchCacheEntries = 0;
|
|
251
|
+
cache = new Map();
|
|
252
|
+
sourceSiteMatchCaches.set(doc, cache);
|
|
253
|
+
}
|
|
254
|
+
if (!cache.has(key))
|
|
255
|
+
sourceSiteMatchCacheEntries++;
|
|
256
|
+
cache.set(key, {
|
|
257
|
+
elementRevision: revisions.element,
|
|
258
|
+
stylesheetRevision: revisions.stylesheet,
|
|
259
|
+
matched: cachedMatches,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Browser state such as `checked` or `target` can change without a DOM or
|
|
263
|
+
// stylesheet revision, so element-sensitive matches must stay fresh.
|
|
264
|
+
const elementMatches = elementSensitive.length > 0
|
|
265
|
+
? sensitiveMatchesForElement(el, buckets, sensitiveMatchesByElement).filter((match) => match.rule.active !== false)
|
|
266
|
+
: [];
|
|
267
|
+
return elementMatches.length === 0 ? cachedMatches : [...cachedMatches, ...elementMatches];
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Matches selectors without applying conditional wrappers. The result retains
|
|
271
|
+
* inactive media alternatives for attribution, while transient selectors and
|
|
272
|
+
* the `live` transform retain their freshness requirements.
|
|
273
|
+
*/
|
|
274
|
+
function getAllElementMatches(el, rules, transform, sensitiveMatchesByElement = new Map()) {
|
|
275
|
+
const buckets = sourceSiteMatchBuckets(rules, transform);
|
|
276
|
+
if (transform === "live") {
|
|
277
|
+
const cacheableMatches = buckets.cacheable.flatMap((entry) => {
|
|
278
|
+
const match = matchRuleForElement(el, entry);
|
|
279
|
+
return match ? [match] : [];
|
|
280
|
+
});
|
|
281
|
+
return [...cacheableMatches, ...sensitiveMatchesForElement(el, buckets, sensitiveMatchesByElement)];
|
|
282
|
+
}
|
|
283
|
+
const { cacheable, elementSensitive } = buckets;
|
|
284
|
+
const collect = (entries) => entries.flatMap((entry) => {
|
|
285
|
+
const match = matchRuleForElement(el, entry);
|
|
286
|
+
return match ? [match] : [];
|
|
287
|
+
});
|
|
288
|
+
const doc = el.ownerDocument ?? document;
|
|
289
|
+
const revisions = getDocumentRevisions(doc);
|
|
290
|
+
let cachedMatches = [];
|
|
291
|
+
if (cacheable.length > 0) {
|
|
292
|
+
let byTransform = allMatchesMemo.get(el);
|
|
293
|
+
if (!byTransform) {
|
|
294
|
+
byTransform = new Map();
|
|
295
|
+
allMatchesMemo.set(el, byTransform);
|
|
296
|
+
}
|
|
297
|
+
const cached = byTransform.get(transform);
|
|
298
|
+
if (cached
|
|
299
|
+
&& cached.elementRevision === revisions.element
|
|
300
|
+
&& cached.stylesheetRevision === revisions.stylesheet) {
|
|
301
|
+
cachedMatches = cached.matched;
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
cachedMatches = collect(cacheable);
|
|
305
|
+
byTransform.set(transform, {
|
|
306
|
+
elementRevision: revisions.element,
|
|
307
|
+
stylesheetRevision: revisions.stylesheet,
|
|
308
|
+
matched: cachedMatches,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const sensitiveMatches = elementSensitive.length > 0
|
|
313
|
+
? sensitiveMatchesForElement(el, buckets, sensitiveMatchesByElement)
|
|
314
|
+
: [];
|
|
315
|
+
return sensitiveMatches.length === 0 ? cachedMatches : [...cachedMatches, ...sensitiveMatches];
|
|
316
|
+
}
|
|
317
|
+
// Branch splits repeat across sweeps, so memoize them for the finite selector
|
|
318
|
+
// set in a stylesheet snapshot.
|
|
319
|
+
const SELECTOR_SPLIT_CACHE_MAX = 8192;
|
|
320
|
+
const selectorSplitMemo = new Map();
|
|
321
|
+
// Branch matches are reusable for state-independent selectors. Transient
|
|
322
|
+
// selectors stay uncached because their state is not represented by document
|
|
323
|
+
// revisions.
|
|
324
|
+
const ELEMENT_BRANCH_CACHE_MAX = 16384;
|
|
325
|
+
const elementBranchMemo = new WeakMap();
|
|
326
|
+
function isBranchMemoizable(selectorText) {
|
|
327
|
+
return !hasSelectorPseudoClass(selectorText, TRANSIENT_PSEUDO_CLASSES)
|
|
328
|
+
&& !isElementSensitiveSelector(selectorText);
|
|
329
|
+
}
|
|
330
|
+
function matchingSelectorBranch(el, selectorText) {
|
|
331
|
+
const doc = el.ownerDocument ?? document;
|
|
332
|
+
const revisions = getDocumentRevisions(doc);
|
|
333
|
+
let bySelector = elementBranchMemo.get(el);
|
|
334
|
+
const memoizable = isBranchMemoizable(selectorText);
|
|
335
|
+
if (memoizable) {
|
|
336
|
+
if (!bySelector) {
|
|
337
|
+
bySelector = new Map();
|
|
338
|
+
elementBranchMemo.set(el, bySelector);
|
|
339
|
+
}
|
|
340
|
+
const cached = bySelector.get(selectorText);
|
|
341
|
+
if (cached
|
|
342
|
+
&& cached.elementRevision === revisions.element
|
|
343
|
+
&& cached.stylesheetRevision === revisions.stylesheet) {
|
|
344
|
+
return cached.branch;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
let branches = selectorSplitMemo.get(selectorText);
|
|
348
|
+
if (!branches) {
|
|
349
|
+
branches = splitSelectorAtTopLevel(selectorText, ",");
|
|
350
|
+
if (selectorSplitMemo.size >= SELECTOR_SPLIT_CACHE_MAX)
|
|
351
|
+
selectorSplitMemo.clear();
|
|
352
|
+
selectorSplitMemo.set(selectorText, branches);
|
|
353
|
+
}
|
|
354
|
+
let branch = null;
|
|
355
|
+
for (const candidate of branches) {
|
|
356
|
+
const trimmedCandidate = candidate.trim();
|
|
357
|
+
if (!canMatchSelectorSubject(el, trimmedCandidate))
|
|
358
|
+
continue;
|
|
359
|
+
try {
|
|
360
|
+
if (el.matches(trimmedCandidate)) {
|
|
361
|
+
branch = trimmedCandidate;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch { /* invalid/unsupported selector */ }
|
|
366
|
+
}
|
|
367
|
+
if (memoizable) {
|
|
368
|
+
if (bySelector.size >= ELEMENT_BRANCH_CACHE_MAX)
|
|
369
|
+
bySelector.clear();
|
|
370
|
+
bySelector.set(selectorText, {
|
|
371
|
+
elementRevision: revisions.element,
|
|
372
|
+
stylesheetRevision: revisions.stylesheet,
|
|
373
|
+
branch,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return branch;
|
|
377
|
+
}
|
|
378
|
+
/** Creates a matcher for one CSSOM snapshot. */
|
|
379
|
+
export function createRuleMatcher(rules, transform) {
|
|
380
|
+
const sensitiveMatches = new Map();
|
|
381
|
+
return {
|
|
382
|
+
dynamicMatchKey: (element) => elementSensitiveMatchKey(element, rules, transform, sensitiveMatches),
|
|
383
|
+
matchesForElement: (element) => ({
|
|
384
|
+
matched: getCachedElementMatches(element, rules, transform, sensitiveMatches),
|
|
385
|
+
allMatched: getAllElementMatches(element, rules, transform, sensitiveMatches),
|
|
386
|
+
}),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Interaction states whose pseudo-class appears in the snapshot. Skipping
|
|
391
|
+
* absent states avoids a full match sweep after each stylesheet revision.
|
|
392
|
+
*/
|
|
393
|
+
const statesWithPseudoInSnapshot = new WeakMap();
|
|
394
|
+
function statesWithPseudoInSelectors(rules) {
|
|
395
|
+
let present = statesWithPseudoInSnapshot.get(rules);
|
|
396
|
+
if (!present) {
|
|
397
|
+
present = new Set();
|
|
398
|
+
for (const rule of rules) {
|
|
399
|
+
for (const state of INTERACTION_STATES) {
|
|
400
|
+
if (!present.has(state) && rule.selectorText.includes(`:${state}`)) {
|
|
401
|
+
present.add(state);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
statesWithPseudoInSnapshot.set(rules, present);
|
|
406
|
+
}
|
|
407
|
+
return present;
|
|
408
|
+
}
|
|
409
|
+
export function getAvailableInteractionStates(el) {
|
|
410
|
+
const doc = el.ownerDocument ?? document;
|
|
411
|
+
const { rules } = collectCssomRules(doc);
|
|
412
|
+
const present = statesWithPseudoInSelectors(rules);
|
|
413
|
+
const available = ["base"];
|
|
414
|
+
for (const state of INTERACTION_STATES) {
|
|
415
|
+
if (!present.has(state))
|
|
416
|
+
continue;
|
|
417
|
+
const matched = getCachedElementMatches(el, rules, state);
|
|
418
|
+
const relevant = matched.some((m) => m.rule.selectorText.includes(`:${state}`));
|
|
419
|
+
if (relevant)
|
|
420
|
+
available.push(state);
|
|
421
|
+
}
|
|
422
|
+
return available;
|
|
423
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
export declare function splitSelectorAtTopLevel(selector: string, separator?: string): string[];
|
|
2
|
+
/** Test-only specificity cache size. */
|
|
2
3
|
export declare function specificityComputationCount(): number;
|
|
3
|
-
/**
|
|
4
|
+
/** Test-only specificity cache reset. */
|
|
4
5
|
export declare function resetSpecificityMemo(): void;
|
|
5
6
|
/**
|
|
6
7
|
* Computes the cascade weight for a selector without asking the DOM to match
|
|
@@ -8,5 +9,20 @@ export declare function resetSpecificityMemo(): void;
|
|
|
8
9
|
* still recompute branch specificity (multi-branch selectors).
|
|
9
10
|
*/
|
|
10
11
|
export declare function computeSpecificity(selectorText: string): number;
|
|
11
|
-
/**
|
|
12
|
+
/** Unmemoized implementation used by equivalence tests. */
|
|
12
13
|
export declare function computeSpecificityCore(selectorText: string): number;
|
|
14
|
+
export interface SelectorPseudoClass {
|
|
15
|
+
name: string;
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function selectorPseudoClasses(selector: string): readonly SelectorPseudoClass[];
|
|
20
|
+
export declare function hasSelectorPseudoClass(selector: string, names: ReadonlySet<string>): boolean;
|
|
21
|
+
export declare function isElementSensitiveSelector(selector: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Rejects selectors whose rightmost compound requires a class the element
|
|
24
|
+
* does not have. This is a conservative prefilter: it only returns false for
|
|
25
|
+
* an impossible positive class requirement and leaves all other selectors to
|
|
26
|
+
* the browser's selector engine.
|
|
27
|
+
*/
|
|
28
|
+
export declare function canMatchSelectorSubject(el: Element, selector: string): boolean;
|