nudge-ui 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/bin/nudge-ui.mjs +3 -1
  2. package/dist/cli/agentCommand.d.ts +8 -0
  3. package/dist/cli/agentCommand.js +59 -0
  4. package/dist/client.mjs +878 -11
  5. package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
  6. package/dist/hosts/astro/clientTransport.js +63 -11
  7. package/dist/hosts/astro/integration.d.ts +2 -2
  8. package/dist/hosts/astro/integration.js +4 -3
  9. package/dist/hosts/environment.d.ts +15 -0
  10. package/dist/hosts/environment.js +16 -0
  11. package/dist/hosts/next/sidecar.d.ts +4 -0
  12. package/dist/hosts/next/sidecar.js +56 -7
  13. package/dist/hosts/next/wrapper.d.ts +2 -0
  14. package/dist/hosts/next/wrapper.js +32 -3
  15. package/dist/hosts/projectBridge.d.ts +21 -0
  16. package/dist/hosts/projectBridge.js +98 -0
  17. package/dist/hosts/static/cli.d.ts +1 -1
  18. package/dist/hosts/static/cli.js +6 -1
  19. package/dist/hosts/static/manifest.d.ts +1 -1
  20. package/dist/hosts/static/manifest.js +3 -1
  21. package/dist/hosts/static/server.js +45 -3
  22. package/dist/hosts/vite/vite.d.ts +6 -1
  23. package/dist/hosts/vite/vite.js +82 -15
  24. package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
  25. package/dist/inspector/agent/McpConnectionDialog.js +20 -37
  26. package/dist/inspector/agent/client.d.ts +16 -3
  27. package/dist/inspector/agent/client.js +114 -11
  28. package/dist/inspector/agent/connectionStatus.d.ts +3 -3
  29. package/dist/inspector/agent/connectionStatus.js +11 -4
  30. package/dist/inspector/agent/httpTransport.d.ts +8 -0
  31. package/dist/inspector/agent/httpTransport.js +78 -18
  32. package/dist/inspector/agent/protocol.d.ts +11 -3
  33. package/dist/inspector/agent/protocol.js +1 -1
  34. package/dist/inspector/agent/verification.d.ts +1 -1
  35. package/dist/inspector/agent/verification.js +69 -12
  36. package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
  37. package/dist/inspector/canvas/CanvasCard.js +181 -91
  38. package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
  39. package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
  40. package/dist/inspector/canvas/CanvasToolbar.js +24 -0
  41. package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
  42. package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
  43. package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
  44. package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
  45. package/dist/inspector/canvas/canvasResize.d.ts +30 -0
  46. package/dist/inspector/canvas/canvasResize.js +52 -0
  47. package/dist/inspector/canvas/canvasStore.d.ts +30 -6
  48. package/dist/inspector/canvas/canvasStore.js +128 -61
  49. package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
  50. package/dist/inspector/canvas/frameProtocol.js +63 -2
  51. package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
  52. package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
  53. package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
  54. package/dist/inspector/canvas/linkEligibility.js +0 -29
  55. package/dist/inspector/canvas/projection.d.ts +7 -0
  56. package/dist/inspector/canvas/projection.js +118 -25
  57. package/dist/inspector/canvas/rendererBootstrap.js +42 -29
  58. package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
  59. package/dist/inspector/canvas/rendererElementSelector.js +385 -58
  60. package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
  61. package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
  62. package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
  63. package/dist/inspector/canvas/sessionStore.d.ts +1 -0
  64. package/dist/inspector/canvas/sessionStore.js +12 -4
  65. package/dist/inspector/canvas/staleChangeDetector.js +16 -14
  66. package/dist/inspector/changes/changesLog.js +31 -1
  67. package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
  68. package/dist/inspector/changes/managedStyleProjection.js +3 -3
  69. package/dist/inspector/changes/structuralTypes.d.ts +4 -0
  70. package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
  71. package/dist/inspector/changes/workspaceChanges.js +29 -3
  72. package/dist/inspector/clientManifest.d.ts +7 -0
  73. package/dist/inspector/clientManifest.js +12 -0
  74. package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
  75. package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
  76. package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
  77. package/dist/inspector/index.d.ts +2 -0
  78. package/dist/inspector/index.js +80 -32
  79. package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
  80. package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
  81. package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
  82. package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
  83. package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
  84. package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
  85. package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
  86. package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
  87. package/dist/inspector/inline-text/textEditChange.js +165 -0
  88. package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
  89. package/dist/inspector/inspection/selectionProperty.js +1 -0
  90. package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
  91. package/dist/inspector/overlay/DomNavigator.js +40 -0
  92. package/dist/inspector/overlay/InspectorOverlay.js +6 -2
  93. package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
  94. package/dist/inspector/overlay/clickPolicy.js +34 -3
  95. package/dist/inspector/overlay/elementSelector.d.ts +1 -1
  96. package/dist/inspector/overlay/elementSelector.js +18 -7
  97. package/dist/inspector/overlay/selectedGeometry.js +21 -2
  98. package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
  99. package/dist/inspector/overlay/spacingGestures.js +351 -0
  100. package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
  101. package/dist/inspector/overlay/structuralGestures.js +12 -10
  102. package/dist/inspector/projection/managedStylesheet.js +4 -1
  103. package/dist/inspector/projection/renderedInstance.d.ts +2 -0
  104. package/dist/inspector/projection/renderedInstance.js +5 -1
  105. package/dist/inspector/projection/structuralProjection.d.ts +2 -0
  106. package/dist/inspector/projection/structuralProjection.js +85 -1
  107. package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
  108. package/dist/inspector/projection/textProjection.d.ts +2 -0
  109. package/dist/inspector/projection/textProjection.js +10 -4
  110. package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
  111. package/dist/inspector/prompt/clipboardHandoff.js +3 -3
  112. package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
  113. package/dist/inspector/prompt/generatePrompt.js +19 -2
  114. package/dist/inspector/runtime/editorShell.d.ts +2 -0
  115. package/dist/inspector/runtime/editorShell.js +4 -0
  116. package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
  117. package/dist/inspector/runtime/runtimeConfig.js +4 -0
  118. package/dist/inspector/selection/hierarchy.d.ts +6 -0
  119. package/dist/inspector/selection/hierarchy.js +8 -0
  120. package/dist/inspector/selection/selectionStore.js +2 -0
  121. package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
  122. package/dist/inspector/settings/SettingsDialog.js +12 -2
  123. package/dist/inspector/shell/AppShell.js +5 -1
  124. package/dist/inspector/shell/ChangesLog.js +26 -22
  125. package/dist/inspector/shell/CopyPromptButton.js +102 -19
  126. package/dist/inspector/shell/EmptyState.js +1 -1
  127. package/dist/inspector/shell/InspectorShell.d.ts +0 -1
  128. package/dist/inspector/shell/InspectorShell.js +38 -42
  129. package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
  130. package/dist/inspector/shell/PeekOriginalButton.js +57 -0
  131. package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
  132. package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
  133. package/dist/inspector/shell/originalPreview.d.ts +9 -0
  134. package/dist/inspector/shell/originalPreview.js +61 -0
  135. package/dist/inspector/shell/shortcuts.d.ts +3 -0
  136. package/dist/inspector/shell/shortcuts.js +15 -0
  137. package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
  138. package/dist/inspector/sketch/SketchChanges.js +72 -0
  139. package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
  140. package/dist/inspector/sketch/SketchEditor.js +402 -0
  141. package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
  142. package/dist/inspector/sketch/SketchEntryButton.js +11 -0
  143. package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
  144. package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
  145. package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
  146. package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
  147. package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
  148. package/dist/inspector/sketch/SketchOverlay.js +420 -0
  149. package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
  150. package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
  151. package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
  152. package/dist/inspector/sketch/SketchWorkspace.js +152 -0
  153. package/dist/inspector/sketch/capture.d.ts +55 -0
  154. package/dist/inspector/sketch/capture.js +350 -0
  155. package/dist/inspector/sketch/freehand.d.ts +13 -0
  156. package/dist/inspector/sketch/freehand.js +64 -0
  157. package/dist/inspector/sketch/geometry.d.ts +17 -0
  158. package/dist/inspector/sketch/geometry.js +98 -0
  159. package/dist/inspector/sketch/handoff.d.ts +21 -0
  160. package/dist/inspector/sketch/handoff.js +72 -0
  161. package/dist/inspector/sketch/interaction.d.ts +21 -0
  162. package/dist/inspector/sketch/interaction.js +98 -0
  163. package/dist/inspector/sketch/model.d.ts +104 -0
  164. package/dist/inspector/sketch/model.js +282 -0
  165. package/dist/inspector/sketch/persistence.d.ts +21 -0
  166. package/dist/inspector/sketch/persistence.js +205 -0
  167. package/dist/inspector/sketch/raster.d.ts +22 -0
  168. package/dist/inspector/sketch/raster.js +203 -0
  169. package/dist/inspector/sketch/sketchNote.d.ts +10 -0
  170. package/dist/inspector/sketch/sketchNote.js +30 -0
  171. package/dist/inspector/sketch/store.d.ts +47 -0
  172. package/dist/inspector/sketch/store.js +434 -0
  173. package/dist/inspector/styleEditors/BorderEditor.js +15 -19
  174. package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
  175. package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
  176. package/dist/inspector/styleEditors/ColorPicker.js +11 -36
  177. package/dist/inspector/styleEditors/GapField.d.ts +3 -2
  178. package/dist/inspector/styleEditors/GapField.js +2 -2
  179. package/dist/inspector/styleEditors/GridSection.js +1 -1
  180. package/dist/inspector/styleEditors/LayoutSection.js +11 -8
  181. package/dist/inspector/styleEditors/SpacingBox.js +31 -33
  182. package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
  183. package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
  184. package/dist/inspector/styleEditors/layoutValue.js +3 -0
  185. package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
  186. package/dist/inspector/styleEditors/nudgeValue.js +93 -5
  187. package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
  188. package/dist/inspector/styleEditors/stylePresence.js +22 -0
  189. package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
  190. package/dist/inspector/styleEditors/textRelevance.js +35 -0
  191. package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
  192. package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
  193. package/dist/inspector/tokens/TokenField.js +130 -7
  194. package/dist/inspector/tokens/TokensPanel.js +2 -1
  195. package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
  196. package/dist/inspector/tokens/dragPointerLock.js +70 -0
  197. package/dist/inspector/tokens/editActions.d.ts +2 -0
  198. package/dist/inspector/tokens/editActions.js +2 -2
  199. package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
  200. package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
  201. package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
  202. package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
  203. package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
  204. package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
  205. package/dist/inspector/tokens/resolution.d.ts +3 -26
  206. package/dist/inspector/tokens/resolution.js +44 -735
  207. package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
  208. package/dist/inspector/tokens/tokenAvailability.js +204 -0
  209. package/dist/inspector/ui/Button.d.ts +1 -1
  210. package/dist/inspector/ui/Disclosure.d.ts +22 -0
  211. package/dist/inspector/ui/Disclosure.js +13 -0
  212. package/dist/inspector/ui/SideValuesField.js +2 -2
  213. package/dist/inspector/ui/StatusCallout.d.ts +4 -1
  214. package/dist/inspector/ui/StatusCallout.js +2 -2
  215. package/dist/inspector/ui/dragCursor.d.ts +8 -0
  216. package/dist/inspector/ui/dragCursor.js +21 -0
  217. package/dist/inspector/ui/styles.js +21 -17
  218. package/dist/internal/agent-protocol.d.ts +65 -1
  219. package/dist/internal/agent-protocol.js +183 -3
  220. package/dist/nudge-ui.mjs +276 -58
  221. package/dist/transport/editor.d.ts +47 -0
  222. package/dist/transport/editor.js +163 -0
  223. package/dist/transport/index.d.ts +1 -0
  224. package/dist/transport/index.js +1 -0
  225. package/dist/transport/routes.d.ts +10 -0
  226. package/dist/transport/routes.js +10 -0
  227. package/package.json +3 -1
  228. package/dist/inspector/shell/DomNavigation.d.ts +0 -8
  229. package/dist/inspector/shell/DomNavigation.js +0 -30
  230. package/dist/inspector/shell/panelLayout.d.ts +0 -2
  231. package/dist/inspector/shell/panelLayout.js +0 -29
@@ -1,13 +1,13 @@
1
1
  import { readTailwindV4AlphaUtility, tailwindV4AlphaExpression } from "../../css/dialects/index.js";
2
- import { INTERACTION_STATES } from "../shell/styleState.js";
3
2
  import { getElementComputedStyle } from "../runtime/domRealm.js";
4
- import { collectRules as collectCssomRules, documentRevisions as getDocumentRevisions, registerResolutionElement, } from "./resolution/cssomCollector.js";
5
- import { computeSpecificity } from "./resolution/selectorSemantics.js";
3
+ import { collectRules as collectCssomRules, documentRevisions as getDocumentRevisions, registerResolutionLineage, } from "./resolution/cssomCollector.js";
4
+ import { createRuleMatcher, matchRuleForElement } from "./resolution/ruleMatching.js";
6
5
  import { compareAuthorCascade } from "./resolution/cascade.js";
7
- export { invalidateStyleResolutionCache } from "./resolution/cssomCollector.js";
8
6
  import { interpretValue, normalizeOpacityPercent, } from "../../css/value-semantics/index.js";
9
7
  import { createInspectorValueContext, inspectorTokenOrigin } from "./valueSemanticsAdapter.js";
10
- import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
8
+ export { getAvailableInteractionStates, resetSourceSiteMatchCache, sourceSiteMatchCacheSize } from "./resolution/ruleMatching.js";
9
+ export { buildTokenTable, getTokenTable, getAvailableTokenEntriesForElement, getAvailableTokenCatalog } from "./tokenAvailability.js";
10
+ export { invalidateStyleResolutionCache } from "./resolution/cssomCollector.js";
11
11
  /**
12
12
  * Builds (or reuses) the document's CSSOM rule snapshot ahead of a resolution
13
13
  * sweep. Idempotent: the walk runs once per stylesheet revision and is a cache
@@ -19,20 +19,6 @@ export function prewarmCssomRuleSnapshot(doc) {
19
19
  }
20
20
  const MAX_PROPERTIES = 100;
21
21
  const EMPTY_LOCAL_ALIASES = new Map();
22
- const tokenTableMemo = new WeakMap();
23
- export function buildTokenTable(entries) {
24
- const cached = tokenTableMemo.get(entries);
25
- if (cached)
26
- return cached;
27
- const table = {};
28
- for (const entry of entries) {
29
- table[entry.name] = entry;
30
- if (entry.cssName)
31
- table[entry.cssName] = entry;
32
- }
33
- tokenTableMemo.set(entries, table);
34
- return table;
35
- }
36
22
  /**
37
23
  * Returns true when a `calc()` expression is safe to treat as a simple
38
24
  * numeric value. We exclude percentages, viewport units, and font-relative
@@ -50,200 +36,6 @@ function canBecomeNumeric(value) {
50
36
  const withoutVars = inner.replace(/var\([^)]+\)/g, "");
51
37
  return !/\b\d+(?:\.\d+)?(?:%|vw|vh|vmin|vmax|dvw|dvh|sv[lw]h|lv[lw]h|[ce]m|ex|ch)\b/i.test(withoutVars);
52
38
  }
53
- let tableCache = null;
54
- let tableSource = null;
55
- export function getTokenTable() {
56
- const { tokens } = getNudgeUiRuntimeConfig();
57
- if (tableCache !== null && tableSource === tokens)
58
- return tableCache;
59
- tableCache = buildTokenTable(tokens);
60
- tableSource = tokens;
61
- return tableCache;
62
- }
63
- function isCustomPropertyToken(definition) {
64
- return definition.cssName.startsWith("--");
65
- }
66
- function entryFromDefinition(definition, value) {
67
- return {
68
- name: definition.name,
69
- cssName: definition.cssName,
70
- value,
71
- source: definition.declarations[0]?.source ?? "",
72
- cssValue: definition.cssValue,
73
- adapter: definition.adapter,
74
- origin: definition.origin,
75
- editable: definition.editable,
76
- };
77
- }
78
- const tokenEntriesCache = new WeakMap();
79
- function registerWithAncestors(el) {
80
- let current = el;
81
- while (current) {
82
- registerResolutionElement(current);
83
- current = current.parentElement;
84
- }
85
- }
86
- /**
87
- * The build-time catalog is intentionally an inventory of every project token.
88
- * Element edits must instead use only custom properties resolved in that
89
- * element's cascade; a token defined by a lazy stylesheet is not usable until
90
- * that stylesheet is attached to the document. Entries are cached per element
91
- * until the element or stylesheet revision changes; the derived table reuses
92
- * `buildTokenTable`'s input-identity memo.
93
- */
94
- export function getAvailableTokenEntriesForElement(el, definitions = getNudgeUiRuntimeConfig().tokenCatalog) {
95
- const revisions = getDocumentRevisions(el.ownerDocument ?? document);
96
- const cached = tokenEntriesCache.get(el);
97
- if (cached && cached.elementRevision === revisions.element
98
- && cached.stylesheetRevision === revisions.stylesheet
99
- && cached.definitions === definitions) {
100
- return cached.entries;
101
- }
102
- registerWithAncestors(el);
103
- const computed = getElementComputedStyle(el);
104
- const available = definitions.flatMap((definition) => {
105
- if (!isCustomPropertyToken(definition)) {
106
- // Literal adapters (for example Tailwind v3) do not have a browser
107
- // custom property to probe. Their adapter owns their availability.
108
- return [entryFromDefinition(definition, definition.declarations[0]?.value ?? "")];
109
- }
110
- const value = computed.getPropertyValue(definition.cssName).trim();
111
- return value ? [entryFromDefinition(definition, value)] : [];
112
- });
113
- const intermediateTable = buildTokenTable(available);
114
- const entries = available.map((entry) => ({
115
- ...entry,
116
- value: resolveTokenValue(entry.value, intermediateTable).resolvedValue,
117
- }));
118
- tokenEntriesCache.set(el, {
119
- elementRevision: revisions.element,
120
- stylesheetRevision: revisions.stylesheet,
121
- definitions,
122
- entries,
123
- });
124
- return entries;
125
- }
126
- /**
127
- * Replaces build-time token declaration hints with declarations serialized by
128
- * the browser from the stylesheets currently attached to this document.
129
- */
130
- function hydrateTokenCatalogFromCssom(definitions, doc = document) {
131
- const needsHydration = (definition) => definition.declarations.length === 0
132
- || (definition.adapter === "vanilla-extract" && definition.declarations.every((declaration) => declaration.value.trim() === `var(${definition.cssName})`
133
- && !declaration.context.selector
134
- && !declaration.context.wrappers?.length));
135
- const known = new Set(definitions.filter(needsHydration).map((definition) => definition.cssName));
136
- const declarations = new Map();
137
- for (const rule of collectCssomRules(doc).rules) {
138
- if (rule.source === "#nudge-ui-styles")
139
- continue;
140
- for (const declaration of rule.declarations) {
141
- if (!known.has(declaration.property))
142
- continue;
143
- const wrappers = [
144
- ...(rule.layer ? [{ kind: "layer", params: rule.layer }] : []),
145
- ...(rule.atRules ?? []).flatMap((atRule) => atRule.kind === "media" || atRule.kind === "supports"
146
- ? [{ kind: atRule.kind, params: atRule.params }]
147
- : []),
148
- ];
149
- const list = declarations.get(declaration.property) ?? [];
150
- list.push({
151
- id: `${declaration.property}\u0000${rule.source ?? "cssom"}\u0000${rule.sourceOrder ?? list.length}`,
152
- order: rule.sourceOrder,
153
- value: declaration.value,
154
- source: rule.source ?? "cssom",
155
- important: Boolean(declaration.important),
156
- context: {
157
- selector: rule.selectorText,
158
- ...(wrappers.length > 0 ? { wrappers } : {}),
159
- },
160
- });
161
- declarations.set(declaration.property, list);
162
- }
163
- }
164
- return definitions.map((definition) => {
165
- if (!needsHydration(definition))
166
- return definition;
167
- const accepted = declarations.get(definition.cssName);
168
- return accepted?.length ? { ...definition, declarations: accepted } : definition;
169
- });
170
- }
171
- function sourceFile(source) {
172
- return source.replace(/:\d+$/, "").split(/[?#]/, 1)[0] ?? source;
173
- }
174
- function normalizedPath(value) {
175
- return decodeURIComponent(value).replace(/\\/g, "/").replace(/^file:\/\//, "").replace(/\/+$/, "");
176
- }
177
- function loadedStylesheetSources(doc) {
178
- const sourceIdentityByPath = new Map();
179
- for (const node of doc.querySelectorAll('style[data-vite-dev-id], link[rel~="stylesheet"][href]')) {
180
- const providesAuthoredIdentity = node.tagName === "STYLE";
181
- const source = providesAuthoredIdentity
182
- ? node.dataset.viteDevId
183
- : node.getAttribute("href");
184
- if (!source)
185
- continue;
186
- const path = normalizedPath(source.split(/[?#]/, 1)[0] ?? source);
187
- sourceIdentityByPath.set(path, providesAuthoredIdentity || sourceIdentityByPath.get(path) === true);
188
- }
189
- return Array.from(sourceIdentityByPath, ([path, providesAuthoredIdentity]) => ({
190
- path,
191
- providesAuthoredIdentity,
192
- }));
193
- }
194
- function isLoadedCssSource(source, loadedSources) {
195
- const file = sourceFile(source);
196
- if (!/\.css$/i.test(file) || loadedSources.length === 0)
197
- return true;
198
- const normalizedFile = normalizedPath(file);
199
- return loadedSources.some((loaded) => loaded === normalizedFile
200
- || loaded.endsWith(`/${normalizedFile}`)
201
- || normalizedFile.endsWith(`/${loaded}`));
202
- }
203
- /**
204
- * Returns stylesheet identities only when the browser gave us a complete,
205
- * authored-source mapping. A compiled host can expose a mixture of source-like
206
- * and opaque CSS URLs; treating the mapped subset as complete would make
207
- * declarations behind the opaque URLs look lazy even while their variables are
208
- * live in the document.
209
- */
210
- function confidentlyLoadedStylesheetSources(definitions, loadedSources) {
211
- const cssSources = definitions.flatMap((definition) => definition.declarations
212
- .map((declaration) => declaration.source)
213
- .filter((source) => /\.css$/i.test(sourceFile(source))));
214
- const known = new Set();
215
- for (const loaded of loadedSources) {
216
- if (loaded.providesAuthoredIdentity
217
- || cssSources.some((source) => isLoadedCssSource(source, [loaded.path]))) {
218
- known.add(loaded.path);
219
- continue;
220
- }
221
- // One opaque compiled URL can contain any authored declaration, so the
222
- // document does not provide enough evidence to remove unloaded sources.
223
- return [];
224
- }
225
- return [...known];
226
- }
227
- /**
228
- * Produces the page catalog used by the global Tokens settings section. It retains the
229
- * build-time inventory separately, while removing declarations from CSS files
230
- * that have not loaded when the document exposes mappable source identities
231
- * (such as Vite's lazy route stylesheets). Opaque compiled URLs do not narrow
232
- * the adapter-supplied catalog.
233
- */
234
- export function getAvailableTokenCatalog(root = document.documentElement, definitions = getNudgeUiRuntimeConfig().tokenCatalog) {
235
- const computed = getElementComputedStyle(root);
236
- const hydrated = hydrateTokenCatalogFromCssom(definitions, root.ownerDocument ?? document);
237
- const loadedSources = confidentlyLoadedStylesheetSources(hydrated, loadedStylesheetSources(root.ownerDocument ?? document));
238
- return hydrated.flatMap((definition) => {
239
- if (!isCustomPropertyToken(definition))
240
- return [definition];
241
- if (!computed.getPropertyValue(definition.cssName).trim())
242
- return [];
243
- const declarations = definition.declarations.filter((declaration) => isLoadedCssSource(declaration.source, loadedSources));
244
- return declarations.length > 0 ? [{ ...definition, declarations }] : [];
245
- });
246
- }
247
39
  function normalizeInElementContext(el, property, value) {
248
40
  const doc = el.ownerDocument;
249
41
  const probe = doc.createElement(el.tagName.toLowerCase());
@@ -265,8 +57,8 @@ function candidateMatchesPainted(el, row, painted) {
265
57
  }
266
58
  /**
267
59
  * Resolves one authored value through the value-semantics Module using the
268
- * inspector's integration context. Used by the token-availability path; the
269
- * value Module is the sole interpretation authority.
60
+ * inspector's integration context. Value semantics remains the sole
61
+ * interpretation authority.
270
62
  */
271
63
  function resolveTokenValue(value, tokenTable, localAliases = EMPTY_LOCAL_ALIASES) {
272
64
  return interpretValue("--nudge-ui-token", value, createInspectorValueContext(tokenTable, localAliases))[0];
@@ -373,12 +165,6 @@ function atRulesApplyToElement(el, atRules) {
373
165
  return true;
374
166
  });
375
167
  }
376
- function specificityForBranch(rule, branch) {
377
- // Single-branch selectors reuse the specificity collected from CSSOM; the
378
- // matched branch is the whole selector. Multi-branch selectors need the
379
- // matched branch's own weight, so recompute per branch.
380
- return rule.selectorText.includes(",") ? computeSpecificity(branch) : rule.specificity;
381
- }
382
168
  function collectLocalAliases(el, rules, ruleApplies) {
383
169
  // Custom properties inherit independently of the property being resolved.
384
170
  // Resolve the winning declaration separately for each element in the
@@ -399,11 +185,11 @@ function collectLocalAliases(el, rules, ruleApplies) {
399
185
  return;
400
186
  if (!ruleApplies(rule))
401
187
  return;
402
- const branch = matchingSelectorBranch(element, rule.selectorText);
403
- if (!branch)
188
+ const match = matchRuleForElement(element, { rule, selectorText: rule.selectorText });
189
+ if (!match)
404
190
  return;
405
191
  const sourceOrder = rule.sourceOrder ?? index;
406
- const specificity = specificityForBranch(rule, branch);
192
+ const { specificity } = match;
407
193
  for (const declaration of rule.declarations) {
408
194
  if (!declaration.property.startsWith("--"))
409
195
  continue;
@@ -459,324 +245,13 @@ function makeRuleApplies(el) {
459
245
  return result;
460
246
  };
461
247
  }
462
- const TRANSIENT_SELECTOR = /:(?:hover|active|focus|focus-visible|focus-within|visited|target)(?:\b|\()/;
463
- const transformedRulesMemo = new WeakMap();
464
- function rulesForTransform(rules, transform) {
465
- let byTransform = transformedRulesMemo.get(rules);
466
- if (!byTransform) {
467
- byTransform = new Map();
468
- transformedRulesMemo.set(rules, byTransform);
469
- }
470
- const cached = byTransform.get(transform);
471
- if (cached)
472
- return cached;
473
- let transformed;
474
- if (transform === "live") {
475
- transformed = rules.map((rule) => ({ rule, selectorText: rule.selectorText }));
476
- }
477
- else if (transform === "stable") {
478
- transformed = rules
479
- .filter((rule) => !TRANSIENT_SELECTOR.test(rule.selectorText))
480
- .map((rule) => ({ rule, selectorText: rule.selectorText }));
481
- }
482
- else {
483
- transformed = rules.flatMap((rule) => {
484
- const selectorText = selectorForState(rule.selectorText, transform);
485
- return selectorText ? [{ rule, selectorText }] : [];
486
- });
487
- }
488
- byTransform.set(transform, transformed);
489
- return transformed;
490
- }
491
- function isElementSensitiveSelector(selector) {
492
- let attributeDepth = 0;
493
- let quote = null;
494
- let escaped = false;
495
- for (let index = 0; index < selector.length; index++) {
496
- const character = selector[index];
497
- if (quote) {
498
- if (escaped)
499
- escaped = false;
500
- else if (character === "\\")
501
- escaped = true;
502
- else if (character === quote)
503
- quote = null;
504
- continue;
505
- }
506
- if (attributeDepth > 0) {
507
- if (character === '"' || character === "'")
508
- quote = character;
509
- else if (character === "[")
510
- attributeDepth++;
511
- else if (character === "]")
512
- attributeDepth--;
513
- continue;
514
- }
515
- if (character === "[") {
516
- attributeDepth = 1;
517
- continue;
518
- }
519
- if (character === "+" || character === "~")
520
- return true;
521
- if (character !== ":")
522
- continue;
523
- if (selector[index + 1] === ":")
524
- return true;
525
- const pseudo = /^[A-Za-z-]+/.exec(selector.slice(index + 1))?.[0];
526
- // :root is represented by the complete ancestor lineage. Other
527
- // pseudo-classes can vary per concrete element without an attribute or
528
- // sibling mutation (for example :checked, :visited, and :target).
529
- if (pseudo && pseudo.toLowerCase() !== "root")
530
- return true;
531
- }
532
- return false;
533
- }
534
- const sourceSiteMatchBucketsMemo = new WeakMap();
535
- function sourceSiteMatchBuckets(rules, transform) {
536
- let byTransform = sourceSiteMatchBucketsMemo.get(rules);
537
- if (!byTransform) {
538
- byTransform = new Map();
539
- sourceSiteMatchBucketsMemo.set(rules, byTransform);
540
- }
541
- const cached = byTransform.get(transform);
542
- if (cached)
543
- return cached;
544
- const cacheable = [];
545
- const elementSensitive = [];
546
- for (const entry of rulesForTransform(rules, transform)) {
547
- (isElementSensitiveSelector(entry.selectorText) ? elementSensitive : cacheable).push(entry);
548
- }
549
- const buckets = { cacheable, elementSensitive };
550
- byTransform.set(transform, buckets);
551
- return buckets;
552
- }
553
- const SOURCE_SITE_CACHE_MAX = 4096;
554
- let sourceSiteMatchCaches = new WeakMap();
555
- let sourceSiteMatchCacheEntries = 0;
556
- const concreteElementMatchCaches = new WeakMap();
557
- // The "all matched" set (inactive rules included) is matched fresh against
558
- // every lineage element on every resolution otherwise, duplicating the
559
- // cached sweep's cost. State-independent selectors use the same revision-keyed
560
- // memo shape as the active sweep. Element-sensitive selectors stay fresh: a
561
- // checked state, sibling relationship, or similar browser state can change
562
- // without a DOM mutation that this registry observes.
563
- const allMatchesMemo = new WeakMap();
564
- /** Test hook: clears the per-source-site matched-rule cache. */
565
- export function resetSourceSiteMatchCache() {
566
- sourceSiteMatchCaches = new WeakMap();
567
- sourceSiteMatchCacheEntries = 0;
568
- }
569
- /** Test hook: number of distinct cached source-site match sets. */
570
- export function sourceSiteMatchCacheSize() {
571
- return sourceSiteMatchCacheEntries;
572
- }
573
- /**
574
- * Captures selector-relevant state without serializing the whole document.
575
- * Element and ancestor attributes cover the common source-site selectors while
576
- * child/sibling-sensitive selectors are handled conservatively below.
577
- */
578
- function sourceSiteContextKey(el) {
579
- const lineage = [];
580
- let current = el;
581
- while (current) {
582
- const attributes = Array.from(current.attributes)
583
- .filter((attribute) => attribute.name !== "data-renderer-id")
584
- .sort((a, b) => a.name.localeCompare(b.name))
585
- .map((attribute) => `${attribute.name}=${attribute.value}`)
586
- .join("\u0002");
587
- lineage.push(`${current.tagName}\u0003${attributes}`);
588
- current = current.parentElement;
589
- }
590
- return lineage.join("\u0001");
591
- }
592
- /**
593
- * Source-site identity key for the matched-rule cache. The element's class is
594
- * part of the key because sibling instances of a source site can carry
595
- * different classes (for example rows in a large list), which changes which
596
- * rules match even though their `data-cid`/`data-src` are identical.
597
- */
598
- function sourceSiteKey(el, transform) {
599
- const cid = el.getAttribute("data-cid") ?? "";
600
- const src = el.getAttribute("data-src") ?? "";
601
- const instance = el.getAttribute("data-projection-instance") ?? "";
602
- const className = typeof el.className === "string" ? el.className : "";
603
- return `${cid}\u0000${src}\u0000${instance}\u0000${className}\u0000${transform}`;
604
- }
605
- function matchRuleForElement(el, entry) {
606
- const branch = matchingSelectorBranch(el, entry.selectorText);
607
- if (!branch)
608
- return null;
609
- return {
610
- rule: entry.rule,
611
- selectorText: entry.selectorText,
612
- branch,
613
- specificity: specificityForBranch({ ...entry.rule, selectorText: entry.selectorText }, branch),
614
- };
615
- }
616
- /**
617
- * Captures the current match outcome for selectors whose truth can change
618
- * without a document revision. This keeps the expensive resolved-row cache
619
- * usable while invalidating it when states such as `checked` actually change.
620
- */
621
- function elementSensitiveMatchKey(el, rules, transform) {
622
- const entries = sourceSiteMatchBuckets(rules, transform).elementSensitive;
623
- if (entries.length === 0)
624
- return "";
625
- const matches = [];
626
- let current = el;
627
- let depth = 0;
628
- while (current) {
629
- for (const entry of entries) {
630
- const branch = matchingSelectorBranch(current, entry.selectorText);
631
- if (branch)
632
- matches.push(`${depth}:${entry.rule.sourceOrder ?? 0}:${branch}`);
633
- }
634
- current = current.parentElement;
635
- depth++;
636
- }
637
- return matches.join("\u0001");
638
- }
639
- /**
640
- * Matches every transformed rule against one element. For elements carrying a
641
- * source-site identity (`data-cid`) the selector-match result is cached per
642
- * `(document, data-cid, data-src, data-projection-instance, transform, element and
643
- * stylesheet revision, context)` so repeated selections and equivalent sibling instances of the
644
- * same source site reuse the matched rule set. Elements without a stable
645
- * identity, or selectors that depend on unsupported relationships, are matched
646
- * fresh on every call.
647
- */
648
- function getCachedElementMatches(el, rules, transform) {
649
- const { cacheable, elementSensitive } = sourceSiteMatchBuckets(rules, transform);
650
- const collect = (entries) => {
651
- const matched = [];
652
- for (const entry of entries) {
653
- if (entry.rule.active === false)
654
- continue;
655
- const match = matchRuleForElement(el, entry);
656
- if (match)
657
- matched.push(match);
658
- }
659
- return matched;
660
- };
661
- const cid = el.getAttribute("data-cid");
662
- const doc = el.ownerDocument ?? document;
663
- const revisions = getDocumentRevisions(doc);
664
- if (!cid || cacheable.length === 0) {
665
- let cachedMatches = [];
666
- if (cacheable.length > 0) {
667
- let cache = concreteElementMatchCaches.get(el);
668
- if (!cache) {
669
- cache = new Map();
670
- concreteElementMatchCaches.set(el, cache);
671
- }
672
- const cached = cache.get(transform);
673
- if (cached
674
- && cached.elementRevision === revisions.element
675
- && cached.stylesheetRevision === revisions.stylesheet) {
676
- cachedMatches = cached.matched;
677
- }
678
- else {
679
- cachedMatches = collect(cacheable);
680
- cache.set(transform, {
681
- elementRevision: revisions.element,
682
- stylesheetRevision: revisions.stylesheet,
683
- matched: cachedMatches,
684
- });
685
- }
686
- }
687
- const sensitiveMatches = elementSensitive.length > 0 ? collect(elementSensitive) : [];
688
- return sensitiveMatches.length === 0 ? cachedMatches : [...cachedMatches, ...sensitiveMatches];
689
- }
690
- const key = `${sourceSiteKey(el, transform)}\u0000${sourceSiteContextKey(el)}`;
691
- let cache = sourceSiteMatchCaches.get(doc);
692
- if (!cache) {
693
- cache = new Map();
694
- sourceSiteMatchCaches.set(doc, cache);
695
- }
696
- const cached = cache.get(key);
697
- let cachedMatches;
698
- if (cached
699
- && cached.elementRevision === revisions.element
700
- && cached.stylesheetRevision === revisions.stylesheet) {
701
- cachedMatches = cached.matched;
702
- }
703
- else {
704
- cachedMatches = collect(cacheable);
705
- if (sourceSiteMatchCacheEntries >= SOURCE_SITE_CACHE_MAX) {
706
- sourceSiteMatchCaches = new WeakMap();
707
- sourceSiteMatchCacheEntries = 0;
708
- cache = new Map();
709
- sourceSiteMatchCaches.set(doc, cache);
710
- }
711
- if (!cache.has(key))
712
- sourceSiteMatchCacheEntries++;
713
- cache.set(key, {
714
- elementRevision: revisions.element,
715
- stylesheetRevision: revisions.stylesheet,
716
- matched: cachedMatches,
717
- });
718
- }
719
- // Match element-sensitive selectors on every resolution. Browser state such
720
- // as `checked`, `target`, or a sibling relationship can change without a DOM
721
- // or stylesheet revision, so a revision-keyed entry can return stale rows.
722
- const elementMatches = elementSensitive.length > 0 ? collect(elementSensitive) : [];
723
- return elementMatches.length === 0 ? cachedMatches : [...cachedMatches, ...elementMatches];
724
- }
725
- /**
726
- * Matches selectors without applying conditional wrappers. Resolution still
727
- * uses the active-only set for cascade decisions; this set preserves inactive
728
- * media-query alternatives for property attribution in the inspector.
729
- * Memoized per (element, transform, revisions) like the cached sweep for
730
- * state-independent selectors. Element-sensitive selectors stay fresh because
731
- * their state can change without a revision bump (for example, `:checked`),
732
- * and the "live" transform always keeps its raw selector set fresh.
733
- */
734
- function getAllElementMatches(el, rules, transform) {
735
- if (transform === "live") {
736
- return rulesForTransform(rules, transform).flatMap((entry) => {
737
- const match = matchRuleForElement(el, entry);
738
- return match ? [match] : [];
739
- });
740
- }
741
- const { cacheable, elementSensitive } = sourceSiteMatchBuckets(rules, transform);
742
- const collect = (entries) => entries.flatMap((entry) => {
743
- const match = matchRuleForElement(el, entry);
744
- return match ? [match] : [];
745
- });
746
- const doc = el.ownerDocument ?? document;
747
- const revisions = getDocumentRevisions(doc);
748
- let cachedMatches = [];
749
- if (cacheable.length > 0) {
750
- let byTransform = allMatchesMemo.get(el);
751
- if (!byTransform) {
752
- byTransform = new Map();
753
- allMatchesMemo.set(el, byTransform);
754
- }
755
- const cached = byTransform.get(transform);
756
- if (cached
757
- && cached.elementRevision === revisions.element
758
- && cached.stylesheetRevision === revisions.stylesheet) {
759
- cachedMatches = cached.matched;
760
- }
761
- else {
762
- cachedMatches = collect(cacheable);
763
- byTransform.set(transform, {
764
- elementRevision: revisions.element,
765
- stylesheetRevision: revisions.stylesheet,
766
- matched: cachedMatches,
767
- });
768
- }
769
- }
770
- const sensitiveMatches = elementSensitive.length > 0 ? collect(elementSensitive) : [];
771
- return sensitiveMatches.length === 0 ? cachedMatches : [...cachedMatches, ...sensitiveMatches];
772
- }
773
248
  /**
774
249
  * One matching pass over the rules for the element and its ancestors, with the
775
250
  * selector-match results shared between the element's own resolution and the
776
251
  * inherited phase. Local aliases are collected per element so no ancestor
777
252
  * re-walks the lineage × rules.
778
253
  */
779
- function resolveLineage(el, rules, transform) {
254
+ function resolveLineage(el, matcher) {
780
255
  const lineage = [];
781
256
  let current = el;
782
257
  while (current) {
@@ -787,8 +262,9 @@ function resolveLineage(el, rules, transform) {
787
262
  const selectorMatches = new Map();
788
263
  const allSelectorMatches = new Map();
789
264
  for (const element of lineage) {
790
- selectorMatches.set(element, getCachedElementMatches(element, rules, transform));
791
- allSelectorMatches.set(element, getAllElementMatches(element, rules, transform));
265
+ const { matched, allMatched } = matcher.matchesForElement(element);
266
+ selectorMatches.set(element, matched);
267
+ allSelectorMatches.set(element, allMatched);
792
268
  }
793
269
  const byElement = new Map();
794
270
  for (let index = 0; index < lineage.length; index++) {
@@ -980,72 +456,6 @@ export function resolveRuleFixture(el, rules, tokenTable) {
980
456
  const localAliases = collectLocalAliases(el, rules, ruleApplies);
981
457
  return rowsFromMatches(el, matched, localAliases, tokenTable, allMatched);
982
458
  }
983
- // Selector branch splits are pure functions of the selector text and repeat
984
- // for every element and resolution sweep. Memoize them so repeated matching
985
- // passes skip re-scanning the same selector strings. Bounded like the other
986
- // match caches; the snapshot's selector set is finite per stylesheet revision.
987
- const SELECTOR_SPLIT_CACHE_MAX = 8192;
988
- const selectorSplitMemo = new Map();
989
- // The matched branch for one (element, selector) pair is a pure function of
990
- // the element's selector-relevant state and the CSSOM snapshot — except for
991
- // selectors whose match depends on transient state the revisions cannot see
992
- // (`:hover`, `:checked`, sibling combinators, …). Those keep the freshness
993
- // contract of the element-sensitive bucket and are matched uncached.
994
- // Resolution sweeps re-match the same state-independent selector strings
995
- // against the same lineage elements across cascades (authored base, stable,
996
- // interaction states) and across the active/all rule sets, so memoizing the
997
- // branch turns repeat sweeps into cache hits. Invalidation follows the
998
- // document revisions, the same strategy as the per-element match caches.
999
- const ELEMENT_BRANCH_CACHE_MAX = 16384;
1000
- const elementBranchMemo = new WeakMap();
1001
- function isBranchMemoizable(selectorText) {
1002
- return !TRANSIENT_SELECTOR.test(selectorText) && !isElementSensitiveSelector(selectorText);
1003
- }
1004
- function matchingSelectorBranch(el, selectorText) {
1005
- const doc = el.ownerDocument ?? document;
1006
- const revisions = getDocumentRevisions(doc);
1007
- let bySelector = elementBranchMemo.get(el);
1008
- const memoizable = isBranchMemoizable(selectorText);
1009
- if (memoizable) {
1010
- if (!bySelector) {
1011
- bySelector = new Map();
1012
- elementBranchMemo.set(el, bySelector);
1013
- }
1014
- const cached = bySelector.get(selectorText);
1015
- if (cached
1016
- && cached.elementRevision === revisions.element
1017
- && cached.stylesheetRevision === revisions.stylesheet) {
1018
- return cached.branch;
1019
- }
1020
- }
1021
- let branches = selectorSplitMemo.get(selectorText);
1022
- if (!branches) {
1023
- branches = splitTopLevel(selectorText, ",");
1024
- if (selectorSplitMemo.size >= SELECTOR_SPLIT_CACHE_MAX)
1025
- selectorSplitMemo.clear();
1026
- selectorSplitMemo.set(selectorText, branches);
1027
- }
1028
- let branch = null;
1029
- for (const candidate of branches) {
1030
- try {
1031
- if (el.matches(candidate.trim())) {
1032
- branch = candidate.trim();
1033
- break;
1034
- }
1035
- }
1036
- catch { /* invalid/unsupported selector */ }
1037
- }
1038
- if (memoizable) {
1039
- if (bySelector.size >= ELEMENT_BRANCH_CACHE_MAX)
1040
- bySelector.clear();
1041
- bySelector.set(selectorText, {
1042
- elementRevision: revisions.element,
1043
- stylesheetRevision: revisions.stylesheet,
1044
- branch,
1045
- });
1046
- }
1047
- return branch;
1048
- }
1049
459
  function compareCandidate(a, b) {
1050
460
  return compareAuthorCascade({
1051
461
  important: a.evidence.important,
@@ -1091,34 +501,6 @@ function inferTailwindV4ColorOpacity(el, tokenTable, rows) {
1091
501
  return;
1092
502
  }
1093
503
  }
1094
- // Splits CSS values at top-level combinators or an optional delimiter.
1095
- function splitTopLevel(s, sep) {
1096
- const parts = [];
1097
- let depth = 0;
1098
- let start = 0;
1099
- for (let i = 0; i < s.length; i++) {
1100
- if (s[i] === "(")
1101
- depth++;
1102
- else if (s[i] === ")")
1103
- depth--;
1104
- else if (depth === 0 && sep && s[i] === sep) {
1105
- parts.push(s.slice(start, i));
1106
- start = i + 1;
1107
- }
1108
- else if (depth === 0 && !sep && (s[i] === " " || s[i] === ">" || s[i] === "+" || s[i] === "~")) {
1109
- // Only split on combinators that have non-space around them
1110
- // If previous char was also a combinator, skip
1111
- if (i > start) {
1112
- parts.push(s.slice(start, i).trim());
1113
- }
1114
- start = i + 1;
1115
- }
1116
- }
1117
- const last = s.slice(start).trim();
1118
- if (last)
1119
- parts.push(last);
1120
- return parts;
1121
- }
1122
504
  const stateResolutionSnapshots = new WeakMap();
1123
505
  const INHERITED_PROPERTIES = new Set([
1124
506
  "color", "font", "font-family", "font-size", "font-style", "font-variant", "font-weight",
@@ -1156,19 +538,28 @@ function resolveInheritedProperties(el, tokenTable, lineage, result, inaccessibl
1156
538
  }
1157
539
  return result;
1158
540
  }
1159
- export function getResolvedProperties(el, tokenTable) {
1160
- const doc = el.ownerDocument ?? document;
1161
- const { rules, inaccessible } = collectCssomRules(doc);
1162
- const lineage = resolveLineage(el, rules, "live");
541
+ function resolveMatchedProperties(el, tokenTable, lineage, inaccessible, mode) {
1163
542
  const entry = lineage.byElement.get(el);
1164
543
  const result = rowsFromMatches(el, entry.matched, entry.aliases, tokenTable, entry.allMatched);
1165
544
  const computed = getElementComputedStyle(el);
545
+ if (mode !== "stable")
546
+ annotatePaintedValues(el, result, computed, inaccessible, mode);
547
+ applyInlineDeclarations(el, result, tokenTable, computed, inaccessible);
548
+ hydratePropertyOpacityRows(result, computed);
549
+ promoteNumericCalcRows(result, tokenTable);
550
+ return resolveInheritedProperties(el, tokenTable, lineage, result, inaccessible);
551
+ }
552
+ function annotatePaintedValues(el, result, computed, inaccessible, mode) {
1166
553
  for (const prop of result) {
1167
554
  const cv = computed.getPropertyValue(prop.property);
1168
555
  if (cv && !/\b(?:var|calc)\s*\(/.test(cv)) {
1169
- prop.resolvedValue = cv;
556
+ // A hypothetical state retains its authored value while `computed`
557
+ // records what the browser is painting. Live and base use painted values.
558
+ if (mode === "live" || mode === "base" || !prop.resolvedValue)
559
+ prop.resolvedValue = cv;
1170
560
  prop.computed = cv;
1171
- hydratePropertyOpacity(prop, cv);
561
+ if (mode === "live")
562
+ hydratePropertyOpacity(prop, cv);
1172
563
  const validated = candidateMatchesPainted(el, prop, cv);
1173
564
  prop.confidence = validated && !inaccessible && !prop.evidence.layer ? "exact" : prop.tokenName ? "probable" : "unknown";
1174
565
  prop.evidence.inaccessibleStylesheet = inaccessible || undefined;
@@ -1177,10 +568,12 @@ export function getResolvedProperties(el, tokenTable) {
1177
568
  : "painted value has no attributable catalog token";
1178
569
  }
1179
570
  }
1180
- applyInlineDeclarations(el, result, tokenTable, computed, inaccessible);
1181
- hydratePropertyOpacityRows(result, computed);
1182
- promoteNumericCalcRows(result, tokenTable);
1183
- return resolveInheritedProperties(el, tokenTable, lineage, result, inaccessible);
571
+ }
572
+ export function getResolvedProperties(el, tokenTable) {
573
+ const doc = el.ownerDocument ?? document;
574
+ const { rules, inaccessible } = collectCssomRules(doc);
575
+ const lineage = resolveLineage(el, createRuleMatcher(rules, "live"));
576
+ return resolveMatchedProperties(el, tokenTable, lineage, inaccessible, "live");
1184
577
  }
1185
578
  function applyInlineDeclarations(el, result, tokenTable, computed, inaccessible) {
1186
579
  // Inline declarations participate in the cascade and are explicit evidence,
@@ -1247,22 +640,6 @@ function promoteNumericCalcRows(result, tokenTable) {
1247
640
  prop.capability = capabilityFor(prop.property, numeric, tokenTable);
1248
641
  }
1249
642
  }
1250
- const INTERACTION_SELECTOR = /:(hover|active|focus-visible|focus|disabled)(?:\b|\()/g;
1251
- function selectorForState(selector, state) {
1252
- const states = new Set();
1253
- INTERACTION_SELECTOR.lastIndex = 0;
1254
- let match;
1255
- while ((match = INTERACTION_SELECTOR.exec(selector)) !== null)
1256
- states.add(match[1]);
1257
- if (state === "base")
1258
- return states.size === 0 ? selector : null;
1259
- if (!states.has(state))
1260
- return states.size === 0 ? selector : null;
1261
- // CSSOM cannot ask the browser whether a hypothetical pseudo-class matches.
1262
- // Removing interaction pseudo-classes gives the authored rule a stable
1263
- // element match for inspection. The real rule remains untouched.
1264
- return selector.replace(INTERACTION_SELECTOR, "");
1265
- }
1266
643
  /**
1267
644
  * Resolve the authored cascade for a chosen interaction state without relying
1268
645
  * on the pointer's current location. This is intentionally attribution-first:
@@ -1273,7 +650,8 @@ export function getResolvedPropertiesForState(el, tokenTable, state) {
1273
650
  const doc = el.ownerDocument ?? document;
1274
651
  const revisions = getDocumentRevisions(doc);
1275
652
  const { rules, inaccessible } = collectCssomRules(doc);
1276
- const dynamicMatchKey = elementSensitiveMatchKey(el, rules, state);
653
+ const matcher = createRuleMatcher(rules, state);
654
+ const dynamicMatchKey = matcher.dynamicMatchKey(el);
1277
655
  let snapshots = stateResolutionSnapshots.get(el);
1278
656
  if (!snapshots) {
1279
657
  snapshots = new Map();
@@ -1287,33 +665,9 @@ export function getResolvedPropertiesForState(el, tokenTable, state) {
1287
665
  && cached.dynamicMatchKey === dynamicMatchKey) {
1288
666
  return cached.rows;
1289
667
  }
1290
- const lineage = resolveLineage(el, rules, state);
1291
- const entry = lineage.byElement.get(el);
1292
- const result = rowsFromMatches(el, entry.matched, entry.aliases, tokenTable, entry.allMatched);
1293
- const computed = getElementComputedStyle(el);
1294
- for (const prop of result) {
1295
- const cv = computed.getPropertyValue(prop.property);
1296
- if (cv && !/\b(?:var|calc)\s*\(/.test(cv)) {
1297
- // A selected interaction state is hypothetical: the element may still
1298
- // be painting its Base styles. `rowsFromMatches` has already resolved
1299
- // the authored declaration for the requested state, so preserve that
1300
- // value for editor controls and keep the live CSSOM value in `computed`.
1301
- if (state === "base" || !prop.resolvedValue)
1302
- prop.resolvedValue = cv;
1303
- prop.computed = cv;
1304
- const validated = candidateMatchesPainted(el, prop, cv);
1305
- prop.confidence = validated && !inaccessible && !prop.evidence.layer ? "exact" : prop.tokenName ? "probable" : "unknown";
1306
- prop.evidence.inaccessibleStylesheet = inaccessible || undefined;
1307
- prop.evidence.reason = prop.tokenName
1308
- ? (validated ? "authored token declaration validated against computed style" : "authored token candidate could not be proven uniquely")
1309
- : "painted value has no attributable catalog token";
1310
- }
1311
- }
1312
- applyInlineDeclarations(el, result, tokenTable, computed, inaccessible);
1313
- hydratePropertyOpacityRows(result, computed);
1314
- promoteNumericCalcRows(result, tokenTable);
1315
- const rows = resolveInheritedProperties(el, tokenTable, lineage, result, inaccessible);
1316
- registerWithAncestors(el);
668
+ const lineage = resolveLineage(el, matcher);
669
+ const rows = resolveMatchedProperties(el, tokenTable, lineage, inaccessible, state);
670
+ registerResolutionLineage(el);
1317
671
  snapshots.set(state, {
1318
672
  elementRevision: revisions.element,
1319
673
  stylesheetRevision: revisions.stylesheet,
@@ -1323,46 +677,6 @@ export function getResolvedPropertiesForState(el, tokenTable, state) {
1323
677
  });
1324
678
  return rows;
1325
679
  }
1326
- /**
1327
- * Interaction states whose pseudo-class substring appears in at least one
1328
- * snapshot selector, memoized per CSSOM snapshot. The availability check for a
1329
- * state is `matched.some((m) => m.rule.selectorText.includes(":state"))`, so a
1330
- * state whose substring appears nowhere can never become available for any
1331
- * element; skipping its per-state match sweep is exactly equivalent and
1332
- * removes the dominant cost of re-scanning interaction states after every
1333
- * stylesheet revision.
1334
- */
1335
- const statesWithPseudoInSnapshot = new WeakMap();
1336
- function statesWithPseudoInSelectors(rules) {
1337
- let present = statesWithPseudoInSnapshot.get(rules);
1338
- if (!present) {
1339
- present = new Set();
1340
- for (const rule of rules) {
1341
- for (const state of INTERACTION_STATES) {
1342
- if (!present.has(state) && rule.selectorText.includes(`:${state}`)) {
1343
- present.add(state);
1344
- }
1345
- }
1346
- }
1347
- statesWithPseudoInSnapshot.set(rules, present);
1348
- }
1349
- return present;
1350
- }
1351
- export function getAvailableInteractionStates(el) {
1352
- const doc = el.ownerDocument ?? document;
1353
- const { rules } = collectCssomRules(doc);
1354
- const present = statesWithPseudoInSelectors(rules);
1355
- const available = ["base"];
1356
- for (const state of INTERACTION_STATES) {
1357
- if (!present.has(state))
1358
- continue;
1359
- const matched = getCachedElementMatches(el, rules, state);
1360
- const relevant = matched.some((m) => m.rule.selectorText.includes(`:${state}`));
1361
- if (relevant)
1362
- available.push(state);
1363
- }
1364
- return available;
1365
- }
1366
680
  const stableTokenCache = new WeakMap();
1367
681
  /**
1368
682
  * Resolves the cascade with transient interaction selectors removed. Used when
@@ -1373,7 +687,8 @@ export function getResolvedPropertiesStable(el, tokenTable) {
1373
687
  const doc = el.ownerDocument ?? document;
1374
688
  const revisions = getDocumentRevisions(doc);
1375
689
  const { rules, inaccessible } = collectCssomRules(doc);
1376
- const dynamicMatchKey = elementSensitiveMatchKey(el, rules, "stable");
690
+ const matcher = createRuleMatcher(rules, "stable");
691
+ const dynamicMatchKey = matcher.dynamicMatchKey(el);
1377
692
  const cached = stableTokenCache.get(el);
1378
693
  if (cached
1379
694
  && cached.elementRevision === revisions.element
@@ -1382,14 +697,8 @@ export function getResolvedPropertiesStable(el, tokenTable) {
1382
697
  && cached.dynamicMatchKey === dynamicMatchKey) {
1383
698
  return cached.rows;
1384
699
  }
1385
- const lineage = resolveLineage(el, rules, "stable");
1386
- const entry = lineage.byElement.get(el);
1387
- const result = rowsFromMatches(el, entry.matched, entry.aliases, tokenTable, entry.allMatched);
1388
- const computed = getElementComputedStyle(el);
1389
- applyInlineDeclarations(el, result, tokenTable, computed, inaccessible);
1390
- hydratePropertyOpacityRows(result, computed);
1391
- promoteNumericCalcRows(result, tokenTable);
1392
- const rows = resolveInheritedProperties(el, tokenTable, lineage, result, inaccessible);
700
+ const lineage = resolveLineage(el, matcher);
701
+ const rows = resolveMatchedProperties(el, tokenTable, lineage, inaccessible, "stable");
1393
702
  stableTokenCache.set(el, {
1394
703
  elementRevision: revisions.element,
1395
704
  stylesheetRevision: revisions.stylesheet,