@phyto/mantine 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +282 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +265 -0
- package/dist/index.js.map +1 -0
- package/dist/register.cjs +273 -0
- package/dist/register.cjs.map +1 -0
- package/dist/register.d.cts +2 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.js +277 -0
- package/dist/register.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/probe.ts","../src/select.ts","../src/index.ts"],"sourcesContent":["/**\n * Page-level + element-level fingerprinting for the Mantine adapter.\n *\n * Cheap probes that don't trigger any DOM mutation. The page-level\n * check is cached so non-Mantine pages short-circuit to `decline`\n * in nanoseconds across every probe call.\n */\n\nimport type { Locator, PhytoHarnessAccessor } from \"phyto\";\nimport {\n Autocomplete,\n Combobox,\n MultiSelect,\n Select,\n TagsInput,\n} from \"@mantine/core\";\n\n// ─── Page-level Mantine detection ───────────────────────────────────\n\nlet cachedPageHasMantine: boolean | null = null;\n\n/**\n * Has the current page been initialized with `<MantineProvider>` or\n * shipped any Mantine-built styles?\n *\n * Detection priority:\n * 1. `<html data-mantine-color-scheme=\"...\">` — MantineProvider sets this.\n * 2. Any element carrying a hashed `m_xxxxxxxx` class (v7+).\n * 3. Legacy `mantine-*` classes (warn — adapter doesn't claim).\n *\n * Result is cached on first call; on a fresh non-Mantine page this\n * resolves to `false` immediately and every subsequent probe is O(1).\n */\nexport function isMantinePage(): boolean {\n if (cachedPageHasMantine !== null) return cachedPageHasMantine;\n\n // Fastest check: the color-scheme attribute set by MantineProvider.\n const hasColorScheme = document.querySelector(\"[data-mantine-color-scheme]\");\n if (hasColorScheme) {\n cachedPageHasMantine = true;\n return true;\n }\n\n // Fallback: look for any hashed `m_*` class. Restrict to `body` so we\n // don't walk the whole document.\n const hashedClassEl = document.body?.querySelector?.('[class*=\" m_\"], [class^=\"m_\"]');\n if (hashedClassEl) {\n cachedPageHasMantine = true;\n return true;\n }\n\n // Legacy detection: pre-v7 used unhashed `mantine-*` class names.\n const legacyEl = document.body?.querySelector?.('[class*=\"mantine-\"]:not([class*=\" m_\"]):not([class^=\"m_\"])');\n if (legacyEl) {\n if (!warnedLegacy) {\n console.error(\n '[@phyto/mantine] Detected legacy Mantine class names (pre-v7). ' +\n \"This adapter requires Mantine v7+. Upgrade Mantine or pin a v6-compatible adapter.\"\n );\n warnedLegacy = true;\n }\n // We don't claim legacy pages — fingerprints are different.\n cachedPageHasMantine = false;\n return false;\n }\n\n // The page may not be done mounting on first call. Don't cache `false`\n // until the document has fully loaded.\n if (document.readyState === \"complete\") {\n cachedPageHasMantine = false;\n return false;\n }\n return false;\n}\n\nlet warnedLegacy = false;\n\n/** Reset the cache. Exposed for tests; not part of the public API. */\nexport function __resetMantineProbeCache(): void {\n cachedPageHasMantine = null;\n warnedLegacy = false;\n}\n\n// ─── Element-level fingerprinting ───────────────────────────────────\n\n/**\n * Find the visible label and follow it to its associated input.\n *\n * Exact match takes priority; falls back to \"label contains text\" so\n * tests that pass partial label text still resolve.\n */\nexport function findInputByLabel(labelText: string): HTMLElement | null {\n const labels = Array.from(document.querySelectorAll(\"label\"));\n let labelEl: HTMLLabelElement | null = null;\n for (const l of labels) {\n if ((l.textContent || \"\").trim() === labelText) {\n labelEl = l as HTMLLabelElement;\n break;\n }\n }\n if (!labelEl) {\n for (const l of labels) {\n if ((l.textContent || \"\").includes(labelText)) {\n labelEl = l as HTMLLabelElement;\n break;\n }\n }\n }\n if (!labelEl) return null;\n\n const htmlFor = labelEl.getAttribute(\"for\");\n if (htmlFor) {\n const target = document.getElementById(htmlFor);\n if (target) return target as HTMLElement;\n }\n // Fall back to a nested input in the label's subtree.\n return (labelEl.querySelector(\"input, select, textarea\") as HTMLElement) || null;\n}\n\n/**\n * Resolve any `Locator` to the underlying form control. Used by both\n * the Mantine probe (to fingerprint it) and the Mantine select\n * execute path (to operate it). Mirrors the DOM adapter's\n * locator-resolution semantics so the same locator vocabulary works\n * across the chain.\n */\nexport function resolveSelectInput(locator: Locator): HTMLElement | null {\n let el: Element | null = null;\n\n if (\"label\" in locator) {\n return findInputByLabel(locator.label);\n }\n if (\"css\" in locator) {\n el = document.querySelector(locator.css);\n } else if (\"testId\" in locator) {\n el = document.querySelector(`[data-testid=\"${locator.testId}\"]`);\n } else if (\"text\" in locator) {\n // Best-effort: look for any element containing the text.\n const all = Array.from(\n document.querySelectorAll<HTMLElement>(\n \"input, select, [role='combobox']\"\n )\n );\n for (const candidate of all) {\n if ((candidate.textContent || \"\").includes(locator.text)) {\n el = candidate;\n break;\n }\n }\n } else if (\"role\" in locator) {\n const candidates = Array.from(\n document.querySelectorAll<HTMLElement>(`[role=\"${locator.role}\"]`)\n );\n if (locator.name) {\n for (const candidate of candidates) {\n const name =\n candidate.getAttribute(\"aria-label\") ||\n candidate.getAttribute(\"aria-labelledby\") ||\n (candidate.textContent || \"\").trim();\n if (name === locator.name) {\n el = candidate;\n break;\n }\n }\n } else {\n el = candidates[0] || null;\n }\n }\n // No `{ component }` branch — action `Locator` excludes component\n // locators; `get-component-state` is the only consumer of those, and\n // it's handled by the framework adapter. The exhaustive `Locator`\n // union means TypeScript catches any future strategy that's added\n // without handling here.\n\n if (!el) return null;\n if (\n el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement ||\n el instanceof HTMLTextAreaElement\n ) {\n return el;\n }\n // The locator hit a wrapper — descend to a nested control.\n const nested = el.querySelector(\"input, select, textarea\");\n return (nested as HTMLElement) || null;\n}\n\n/**\n * The set of `@mantine/core` component references whose rendered\n * inputs we recognize as comboboxes. Compared by **reference equality**\n * against React fiber types via the bridge — same `import { Select }\n * from \"@mantine/core\"` reference the application uses ends up as\n * `fiber.type` in React's internals, so set membership is provably\n * correct identification (no name collisions, no DOM markup\n * heuristics, no version-specific class names).\n *\n * Add a new combobox-family component here when extending coverage.\n */\nconst MANTINE_COMBOBOX_TYPES = new Set<unknown>([\n Combobox,\n Select,\n Autocomplete,\n MultiSelect,\n TagsInput,\n]);\n\n/**\n * Typed accessor for the global harness installed by the Vite plugin.\n * Library adapter packages reach it via `window.__phyto_harness__`.\n */\nfunction getHarnessAccessor(): PhytoHarnessAccessor | null {\n if (typeof window === \"undefined\") return null;\n const w = window as unknown as { __phyto_harness__?: PhytoHarnessAccessor };\n return w.__phyto_harness__ ?? null;\n}\n\n/**\n * Is the given input rendered by a Mantine combobox component?\n *\n * Primary (and only authoritative) signal: walk the React fiber tree\n * upward from the input via `bridge.isElementFromAnyComponent` and\n * check whether any ancestor fiber's `type` matches one of the\n * imported `@mantine/core` component references in\n * `MANTINE_COMBOBOX_TYPES`. This is ground truth — same component\n * reference = same component definition.\n *\n * Fallback: if the bridge isn't available yet (framework detection\n * hasn't completed, or no React on the page), fall back to DOM-attribute\n * heuristics. These are not exclusive to Mantine (any ARIA combobox\n * hits them), but on a page that passed the cached `isMantinePage()`\n * gate they're a reasonable best-effort while detection is still in\n * flight. Once the bridge is available the fiber check takes over.\n *\n * A native `<select>` is never a Mantine combobox — early-returned.\n */\nexport function isMantineCombobox(input: HTMLElement): boolean {\n if (input instanceof HTMLSelectElement) return false;\n if (!(input instanceof HTMLInputElement)) return false;\n\n // Primary: ground-truth component-identity check via the bridge.\n const bridge = getHarnessAccessor()?.getBridge() ?? null;\n if (bridge) {\n return bridge.isElementFromAnyComponent(input, MANTINE_COMBOBOX_TYPES);\n }\n\n // Fallback: DOM-attribute heuristics for the brief window before the\n // framework bridge has been detected. These match any ARIA combobox,\n // not strictly Mantine — acceptable because the page-level\n // `isMantinePage()` gate already filtered non-Mantine pages out\n // before we got here.\n if (input.getAttribute(\"role\") === \"combobox\") return true;\n if (input.getAttribute(\"aria-haspopup\") === \"listbox\") return true;\n if (input.closest('[role=\"combobox\"]')) return true;\n if (input.closest(\"[data-combobox-target]\")) return true;\n\n return false;\n}\n","/**\n * Execute path for a Mantine Select / Autocomplete.\n *\n * The dispatcher only calls this after `probe()` has claimed, so the\n * locator is guaranteed to resolve to a Mantine combobox at the time\n * of the claim. The work here is to:\n *\n * 1. Resolve the locator → input.\n * 2. Click the input to open the dropdown.\n * 3. Read `aria-controls` on the input to find the portal mount id.\n * 4. Wait for the portal element to appear.\n * 5. Find the option with matching text via `[data-combobox-option]`.\n * 6. Click the option.\n * 7. Wait for the dropdown to unmount.\n * 8. Confirm the input value reflects the choice.\n */\n\nimport type { CommandResult, Locator } from \"phyto\";\nimport { resolveSelectInput } from \"./probe.js\";\n\ninterface SelectArgs {\n locator: Locator;\n value: string;\n timeout: number;\n}\n\nconst POLL_INTERVAL_MS = 30;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function waitFor<T>(\n fn: () => T | null | undefined,\n timeoutMs: number\n): Promise<T | null> {\n const deadline = Date.now() + timeoutMs;\n while (true) {\n const result = fn();\n if (result) return result;\n if (Date.now() >= deadline) return null;\n await sleep(POLL_INTERVAL_MS);\n }\n}\n\nexport async function executeSelect(args: SelectArgs): Promise<CommandResult> {\n const { locator, value, timeout } = args;\n const input = resolveSelectInput(locator);\n if (!(input instanceof HTMLInputElement)) {\n return {\n ok: false,\n error: `Mantine Select: locator did not resolve to a combobox input`,\n context: { reason: \"not-found\", locator },\n };\n }\n\n // Open the dropdown. Mantine wires click → openDropdown via the\n // Combobox primitive, but a focus-then-click sequence is more robust\n // against intermediate ref forwarding.\n input.focus();\n input.click();\n\n // Find the portal mount id via aria-controls.\n const portalId =\n input.getAttribute(\"aria-controls\") ??\n (await waitFor(\n () => input.getAttribute(\"aria-controls\"),\n Math.min(timeout, 500)\n ));\n\n // Wait for the portal element. Use the id if we have it; otherwise\n // fall back to scanning for a freshly-mounted listbox.\n const dropdown = await waitFor<HTMLElement>(() => {\n if (portalId) {\n const el = document.getElementById(portalId);\n if (el) return el;\n }\n const lb = document.body.querySelector<HTMLElement>('[role=\"listbox\"]');\n return lb ?? null;\n }, timeout);\n\n if (!dropdown) {\n return {\n ok: false,\n error: `Mantine Select dropdown did not open within ${timeout}ms`,\n context: { reason: \"timeout\", locator },\n };\n }\n\n // Find the matching option. v7 uses `[data-combobox-option]`; also\n // accept `role=\"option\"` for robustness across versions.\n const collectOptions = (): HTMLElement[] =>\n Array.from(\n dropdown.querySelectorAll<HTMLElement>(\n '[data-combobox-option], [role=\"option\"]'\n )\n );\n\n let options = collectOptions();\n if (options.length === 0) {\n await waitFor(() => {\n options = collectOptions();\n return options.length > 0 ? options : null;\n }, Math.min(timeout, 500));\n }\n\n const trimmed = value.trim();\n const match =\n options.find((o) => (o.textContent || \"\").trim() === trimmed) ??\n options.find(\n (o) => (o.textContent || \"\").trim().toLowerCase() === trimmed.toLowerCase()\n ) ??\n options.find((o) => (o.getAttribute(\"data-value\") || \"\") === trimmed);\n\n if (!match) {\n return {\n ok: false,\n error: `Option \"${value}\" not found in Mantine Select`,\n context: { reason: \"not-found\", locator },\n value: {\n availableOptions: options.map((o) => (o.textContent || \"\").trim()),\n },\n };\n }\n\n match.click();\n\n // Wait for the dropdown to unmount.\n await waitFor(() => {\n if (portalId) {\n const el = document.getElementById(portalId);\n if (!el) return true;\n const stillHasOptions = el.querySelector(\n '[data-combobox-option], [role=\"option\"]'\n );\n if (!stillHasOptions) return true;\n }\n return null;\n }, Math.min(timeout, 1000));\n\n const selectedValue = input.value || (match.textContent || \"\").trim();\n\n return {\n ok: true,\n value: {\n component: \"Select\",\n selectedValue,\n },\n };\n}\n","/**\n * `@phyto/mantine` — Mantine adapter for the Phyto in-page harness.\n *\n * Usage:\n *\n * // Auto-register on import (preferred):\n * import \"@phyto/mantine/register\";\n *\n * // Or wire it up manually:\n * import { mantineAdapter } from \"@phyto/mantine\";\n * window.__phyto_harness__.registerAdapter(mantineAdapter);\n * // or with an explicit precedence override:\n * // window.__phyto_harness__.registerAdapter(mantineAdapter, { precedence: 200 });\n *\n * The adapter claims `select` commands when the locator resolves to a\n * Mantine combobox (Select / Autocomplete share the primitive). The\n * adapter publishes a `defaultPrecedence` of 100; the consumer's\n * `registerAdapter()` call has the final word on tie-breaking.\n */\n\nimport type { Adapter, Command, ProbeResult, CommandResult, Locator } from \"phyto\";\nimport { isMantineCombobox, isMantinePage, resolveSelectInput } from \"./probe.js\";\nimport { executeSelect } from \"./select.js\";\n\n/**\n * Adapter-published default precedence. Conventionally:\n * 0 — DOM fallback\n * 50 — framework-level (component locators)\n * 100 — library-level (this adapter)\n *\n * Consumers override at `registerAdapter()` time when two libraries\n * collide on the same page.\n */\nconst MANTINE_DEFAULT_PRECEDENCE = 100;\nconst DEFAULT_TIMEOUT_MS = 5000;\n\nexport const mantineAdapter: Adapter = {\n name: \"mantine\",\n defaultPrecedence: MANTINE_DEFAULT_PRECEDENCE,\n\n probe(command: Command): ProbeResult {\n // The adapter currently only handles the generic `select` action.\n // Future components (MultiSelect, DatePicker, Modal, Notifications)\n // will add to this set.\n if (command.action !== \"select\") return { state: \"decline\" };\n\n // Page-level early-exit. Non-Mantine pages bail in O(1).\n if (!isMantinePage()) {\n return document.readyState === \"complete\"\n ? { state: \"decline\" }\n : { state: \"pending\" };\n }\n\n const cmd = command as { locator: Locator };\n const input = resolveSelectInput(cmd.locator);\n\n // Target hasn't rendered yet — we may own this once it mounts.\n if (!input) return { state: \"pending\" };\n\n // Element is here. If it's a Mantine combobox, claim. Otherwise\n // step aside — the DOM adapter will handle native `<select>` or\n // another library adapter will claim.\n if (isMantineCombobox(input)) {\n return { state: \"claim\" };\n }\n return { state: \"decline\" };\n },\n\n async execute(command: Command): Promise<CommandResult> {\n if (command.action !== \"select\") {\n return {\n ok: false,\n error: `Mantine adapter does not handle: ${command.action}`,\n };\n }\n const cmd = command as {\n locator: Locator;\n value: string;\n options?: { timeout?: number };\n };\n return executeSelect({\n locator: cmd.locator,\n value: cmd.value,\n timeout: cmd.options?.timeout ?? DEFAULT_TIMEOUT_MS,\n });\n },\n};\n\nexport type {\n Adapter,\n Command,\n CommandResult,\n ProbeResult,\n RegisterAdapterOptions,\n} from \"phyto\";\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/probe.ts
|
|
4
|
+
var import_core = require("@mantine/core");
|
|
5
|
+
var cachedPageHasMantine = null;
|
|
6
|
+
function isMantinePage() {
|
|
7
|
+
if (cachedPageHasMantine !== null) return cachedPageHasMantine;
|
|
8
|
+
const hasColorScheme = document.querySelector("[data-mantine-color-scheme]");
|
|
9
|
+
if (hasColorScheme) {
|
|
10
|
+
cachedPageHasMantine = true;
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const hashedClassEl = document.body?.querySelector?.('[class*=" m_"], [class^="m_"]');
|
|
14
|
+
if (hashedClassEl) {
|
|
15
|
+
cachedPageHasMantine = true;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
const legacyEl = document.body?.querySelector?.('[class*="mantine-"]:not([class*=" m_"]):not([class^="m_"])');
|
|
19
|
+
if (legacyEl) {
|
|
20
|
+
if (!warnedLegacy) {
|
|
21
|
+
console.error(
|
|
22
|
+
"[@phyto/mantine] Detected legacy Mantine class names (pre-v7). This adapter requires Mantine v7+. Upgrade Mantine or pin a v6-compatible adapter."
|
|
23
|
+
);
|
|
24
|
+
warnedLegacy = true;
|
|
25
|
+
}
|
|
26
|
+
cachedPageHasMantine = false;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (document.readyState === "complete") {
|
|
30
|
+
cachedPageHasMantine = false;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
var warnedLegacy = false;
|
|
36
|
+
function findInputByLabel(labelText) {
|
|
37
|
+
const labels = Array.from(document.querySelectorAll("label"));
|
|
38
|
+
let labelEl = null;
|
|
39
|
+
for (const l of labels) {
|
|
40
|
+
if ((l.textContent || "").trim() === labelText) {
|
|
41
|
+
labelEl = l;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!labelEl) {
|
|
46
|
+
for (const l of labels) {
|
|
47
|
+
if ((l.textContent || "").includes(labelText)) {
|
|
48
|
+
labelEl = l;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!labelEl) return null;
|
|
54
|
+
const htmlFor = labelEl.getAttribute("for");
|
|
55
|
+
if (htmlFor) {
|
|
56
|
+
const target = document.getElementById(htmlFor);
|
|
57
|
+
if (target) return target;
|
|
58
|
+
}
|
|
59
|
+
return labelEl.querySelector("input, select, textarea") || null;
|
|
60
|
+
}
|
|
61
|
+
function resolveSelectInput(locator) {
|
|
62
|
+
let el = null;
|
|
63
|
+
if ("label" in locator) {
|
|
64
|
+
return findInputByLabel(locator.label);
|
|
65
|
+
}
|
|
66
|
+
if ("css" in locator) {
|
|
67
|
+
el = document.querySelector(locator.css);
|
|
68
|
+
} else if ("testId" in locator) {
|
|
69
|
+
el = document.querySelector(`[data-testid="${locator.testId}"]`);
|
|
70
|
+
} else if ("text" in locator) {
|
|
71
|
+
const all = Array.from(
|
|
72
|
+
document.querySelectorAll(
|
|
73
|
+
"input, select, [role='combobox']"
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
for (const candidate of all) {
|
|
77
|
+
if ((candidate.textContent || "").includes(locator.text)) {
|
|
78
|
+
el = candidate;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} else if ("role" in locator) {
|
|
83
|
+
const candidates = Array.from(
|
|
84
|
+
document.querySelectorAll(`[role="${locator.role}"]`)
|
|
85
|
+
);
|
|
86
|
+
if (locator.name) {
|
|
87
|
+
for (const candidate of candidates) {
|
|
88
|
+
const name = candidate.getAttribute("aria-label") || candidate.getAttribute("aria-labelledby") || (candidate.textContent || "").trim();
|
|
89
|
+
if (name === locator.name) {
|
|
90
|
+
el = candidate;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
el = candidates[0] || null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!el) return null;
|
|
99
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
|
|
100
|
+
return el;
|
|
101
|
+
}
|
|
102
|
+
const nested = el.querySelector("input, select, textarea");
|
|
103
|
+
return nested || null;
|
|
104
|
+
}
|
|
105
|
+
var MANTINE_COMBOBOX_TYPES = /* @__PURE__ */ new Set([
|
|
106
|
+
import_core.Combobox,
|
|
107
|
+
import_core.Select,
|
|
108
|
+
import_core.Autocomplete,
|
|
109
|
+
import_core.MultiSelect,
|
|
110
|
+
import_core.TagsInput
|
|
111
|
+
]);
|
|
112
|
+
function getHarnessAccessor() {
|
|
113
|
+
if (typeof window === "undefined") return null;
|
|
114
|
+
const w = window;
|
|
115
|
+
return w.__phyto_harness__ ?? null;
|
|
116
|
+
}
|
|
117
|
+
function isMantineCombobox(input) {
|
|
118
|
+
if (input instanceof HTMLSelectElement) return false;
|
|
119
|
+
if (!(input instanceof HTMLInputElement)) return false;
|
|
120
|
+
const bridge = getHarnessAccessor()?.getBridge() ?? null;
|
|
121
|
+
if (bridge) {
|
|
122
|
+
return bridge.isElementFromAnyComponent(input, MANTINE_COMBOBOX_TYPES);
|
|
123
|
+
}
|
|
124
|
+
if (input.getAttribute("role") === "combobox") return true;
|
|
125
|
+
if (input.getAttribute("aria-haspopup") === "listbox") return true;
|
|
126
|
+
if (input.closest('[role="combobox"]')) return true;
|
|
127
|
+
if (input.closest("[data-combobox-target]")) return true;
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/select.ts
|
|
132
|
+
var POLL_INTERVAL_MS = 30;
|
|
133
|
+
function sleep(ms) {
|
|
134
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
135
|
+
}
|
|
136
|
+
async function waitFor(fn, timeoutMs) {
|
|
137
|
+
const deadline = Date.now() + timeoutMs;
|
|
138
|
+
while (true) {
|
|
139
|
+
const result = fn();
|
|
140
|
+
if (result) return result;
|
|
141
|
+
if (Date.now() >= deadline) return null;
|
|
142
|
+
await sleep(POLL_INTERVAL_MS);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function executeSelect(args) {
|
|
146
|
+
const { locator, value, timeout } = args;
|
|
147
|
+
const input = resolveSelectInput(locator);
|
|
148
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
error: `Mantine Select: locator did not resolve to a combobox input`,
|
|
152
|
+
context: { reason: "not-found", locator }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
input.focus();
|
|
156
|
+
input.click();
|
|
157
|
+
const portalId = input.getAttribute("aria-controls") ?? await waitFor(
|
|
158
|
+
() => input.getAttribute("aria-controls"),
|
|
159
|
+
Math.min(timeout, 500)
|
|
160
|
+
);
|
|
161
|
+
const dropdown = await waitFor(() => {
|
|
162
|
+
if (portalId) {
|
|
163
|
+
const el = document.getElementById(portalId);
|
|
164
|
+
if (el) return el;
|
|
165
|
+
}
|
|
166
|
+
const lb = document.body.querySelector('[role="listbox"]');
|
|
167
|
+
return lb ?? null;
|
|
168
|
+
}, timeout);
|
|
169
|
+
if (!dropdown) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
error: `Mantine Select dropdown did not open within ${timeout}ms`,
|
|
173
|
+
context: { reason: "timeout", locator }
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const collectOptions = () => Array.from(
|
|
177
|
+
dropdown.querySelectorAll(
|
|
178
|
+
'[data-combobox-option], [role="option"]'
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
let options = collectOptions();
|
|
182
|
+
if (options.length === 0) {
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
options = collectOptions();
|
|
185
|
+
return options.length > 0 ? options : null;
|
|
186
|
+
}, Math.min(timeout, 500));
|
|
187
|
+
}
|
|
188
|
+
const trimmed = value.trim();
|
|
189
|
+
const match = options.find((o) => (o.textContent || "").trim() === trimmed) ?? options.find(
|
|
190
|
+
(o) => (o.textContent || "").trim().toLowerCase() === trimmed.toLowerCase()
|
|
191
|
+
) ?? options.find((o) => (o.getAttribute("data-value") || "") === trimmed);
|
|
192
|
+
if (!match) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
error: `Option "${value}" not found in Mantine Select`,
|
|
196
|
+
context: { reason: "not-found", locator },
|
|
197
|
+
value: {
|
|
198
|
+
availableOptions: options.map((o) => (o.textContent || "").trim())
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
match.click();
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
if (portalId) {
|
|
205
|
+
const el = document.getElementById(portalId);
|
|
206
|
+
if (!el) return true;
|
|
207
|
+
const stillHasOptions = el.querySelector(
|
|
208
|
+
'[data-combobox-option], [role="option"]'
|
|
209
|
+
);
|
|
210
|
+
if (!stillHasOptions) return true;
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}, Math.min(timeout, 1e3));
|
|
214
|
+
const selectedValue = input.value || (match.textContent || "").trim();
|
|
215
|
+
return {
|
|
216
|
+
ok: true,
|
|
217
|
+
value: {
|
|
218
|
+
component: "Select",
|
|
219
|
+
selectedValue
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/index.ts
|
|
225
|
+
var MANTINE_DEFAULT_PRECEDENCE = 100;
|
|
226
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
227
|
+
var mantineAdapter = {
|
|
228
|
+
name: "mantine",
|
|
229
|
+
defaultPrecedence: MANTINE_DEFAULT_PRECEDENCE,
|
|
230
|
+
probe(command) {
|
|
231
|
+
if (command.action !== "select") return { state: "decline" };
|
|
232
|
+
if (!isMantinePage()) {
|
|
233
|
+
return document.readyState === "complete" ? { state: "decline" } : { state: "pending" };
|
|
234
|
+
}
|
|
235
|
+
const cmd = command;
|
|
236
|
+
const input = resolveSelectInput(cmd.locator);
|
|
237
|
+
if (!input) return { state: "pending" };
|
|
238
|
+
if (isMantineCombobox(input)) {
|
|
239
|
+
return { state: "claim" };
|
|
240
|
+
}
|
|
241
|
+
return { state: "decline" };
|
|
242
|
+
},
|
|
243
|
+
async execute(command) {
|
|
244
|
+
if (command.action !== "select") {
|
|
245
|
+
return {
|
|
246
|
+
ok: false,
|
|
247
|
+
error: `Mantine adapter does not handle: ${command.action}`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const cmd = command;
|
|
251
|
+
return executeSelect({
|
|
252
|
+
locator: cmd.locator,
|
|
253
|
+
value: cmd.value,
|
|
254
|
+
timeout: cmd.options?.timeout ?? DEFAULT_TIMEOUT_MS
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// src/register.ts
|
|
260
|
+
function register() {
|
|
261
|
+
const harness = window.__phyto_harness__;
|
|
262
|
+
if (!harness || typeof harness.registerAdapter !== "function") {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
harness.registerAdapter(mantineAdapter);
|
|
266
|
+
}
|
|
267
|
+
if (typeof window !== "undefined") {
|
|
268
|
+
register();
|
|
269
|
+
if (document.readyState !== "complete") {
|
|
270
|
+
window.addEventListener("DOMContentLoaded", register, { once: true });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=register.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/probe.ts","../src/select.ts","../src/index.ts","../src/register.ts"],"sourcesContent":["/**\n * Page-level + element-level fingerprinting for the Mantine adapter.\n *\n * Cheap probes that don't trigger any DOM mutation. The page-level\n * check is cached so non-Mantine pages short-circuit to `decline`\n * in nanoseconds across every probe call.\n */\n\nimport type { Locator, PhytoHarnessAccessor } from \"phyto\";\nimport {\n Autocomplete,\n Combobox,\n MultiSelect,\n Select,\n TagsInput,\n} from \"@mantine/core\";\n\n// ─── Page-level Mantine detection ───────────────────────────────────\n\nlet cachedPageHasMantine: boolean | null = null;\n\n/**\n * Has the current page been initialized with `<MantineProvider>` or\n * shipped any Mantine-built styles?\n *\n * Detection priority:\n * 1. `<html data-mantine-color-scheme=\"...\">` — MantineProvider sets this.\n * 2. Any element carrying a hashed `m_xxxxxxxx` class (v7+).\n * 3. Legacy `mantine-*` classes (warn — adapter doesn't claim).\n *\n * Result is cached on first call; on a fresh non-Mantine page this\n * resolves to `false` immediately and every subsequent probe is O(1).\n */\nexport function isMantinePage(): boolean {\n if (cachedPageHasMantine !== null) return cachedPageHasMantine;\n\n // Fastest check: the color-scheme attribute set by MantineProvider.\n const hasColorScheme = document.querySelector(\"[data-mantine-color-scheme]\");\n if (hasColorScheme) {\n cachedPageHasMantine = true;\n return true;\n }\n\n // Fallback: look for any hashed `m_*` class. Restrict to `body` so we\n // don't walk the whole document.\n const hashedClassEl = document.body?.querySelector?.('[class*=\" m_\"], [class^=\"m_\"]');\n if (hashedClassEl) {\n cachedPageHasMantine = true;\n return true;\n }\n\n // Legacy detection: pre-v7 used unhashed `mantine-*` class names.\n const legacyEl = document.body?.querySelector?.('[class*=\"mantine-\"]:not([class*=\" m_\"]):not([class^=\"m_\"])');\n if (legacyEl) {\n if (!warnedLegacy) {\n console.error(\n '[@phyto/mantine] Detected legacy Mantine class names (pre-v7). ' +\n \"This adapter requires Mantine v7+. Upgrade Mantine or pin a v6-compatible adapter.\"\n );\n warnedLegacy = true;\n }\n // We don't claim legacy pages — fingerprints are different.\n cachedPageHasMantine = false;\n return false;\n }\n\n // The page may not be done mounting on first call. Don't cache `false`\n // until the document has fully loaded.\n if (document.readyState === \"complete\") {\n cachedPageHasMantine = false;\n return false;\n }\n return false;\n}\n\nlet warnedLegacy = false;\n\n/** Reset the cache. Exposed for tests; not part of the public API. */\nexport function __resetMantineProbeCache(): void {\n cachedPageHasMantine = null;\n warnedLegacy = false;\n}\n\n// ─── Element-level fingerprinting ───────────────────────────────────\n\n/**\n * Find the visible label and follow it to its associated input.\n *\n * Exact match takes priority; falls back to \"label contains text\" so\n * tests that pass partial label text still resolve.\n */\nexport function findInputByLabel(labelText: string): HTMLElement | null {\n const labels = Array.from(document.querySelectorAll(\"label\"));\n let labelEl: HTMLLabelElement | null = null;\n for (const l of labels) {\n if ((l.textContent || \"\").trim() === labelText) {\n labelEl = l as HTMLLabelElement;\n break;\n }\n }\n if (!labelEl) {\n for (const l of labels) {\n if ((l.textContent || \"\").includes(labelText)) {\n labelEl = l as HTMLLabelElement;\n break;\n }\n }\n }\n if (!labelEl) return null;\n\n const htmlFor = labelEl.getAttribute(\"for\");\n if (htmlFor) {\n const target = document.getElementById(htmlFor);\n if (target) return target as HTMLElement;\n }\n // Fall back to a nested input in the label's subtree.\n return (labelEl.querySelector(\"input, select, textarea\") as HTMLElement) || null;\n}\n\n/**\n * Resolve any `Locator` to the underlying form control. Used by both\n * the Mantine probe (to fingerprint it) and the Mantine select\n * execute path (to operate it). Mirrors the DOM adapter's\n * locator-resolution semantics so the same locator vocabulary works\n * across the chain.\n */\nexport function resolveSelectInput(locator: Locator): HTMLElement | null {\n let el: Element | null = null;\n\n if (\"label\" in locator) {\n return findInputByLabel(locator.label);\n }\n if (\"css\" in locator) {\n el = document.querySelector(locator.css);\n } else if (\"testId\" in locator) {\n el = document.querySelector(`[data-testid=\"${locator.testId}\"]`);\n } else if (\"text\" in locator) {\n // Best-effort: look for any element containing the text.\n const all = Array.from(\n document.querySelectorAll<HTMLElement>(\n \"input, select, [role='combobox']\"\n )\n );\n for (const candidate of all) {\n if ((candidate.textContent || \"\").includes(locator.text)) {\n el = candidate;\n break;\n }\n }\n } else if (\"role\" in locator) {\n const candidates = Array.from(\n document.querySelectorAll<HTMLElement>(`[role=\"${locator.role}\"]`)\n );\n if (locator.name) {\n for (const candidate of candidates) {\n const name =\n candidate.getAttribute(\"aria-label\") ||\n candidate.getAttribute(\"aria-labelledby\") ||\n (candidate.textContent || \"\").trim();\n if (name === locator.name) {\n el = candidate;\n break;\n }\n }\n } else {\n el = candidates[0] || null;\n }\n }\n // No `{ component }` branch — action `Locator` excludes component\n // locators; `get-component-state` is the only consumer of those, and\n // it's handled by the framework adapter. The exhaustive `Locator`\n // union means TypeScript catches any future strategy that's added\n // without handling here.\n\n if (!el) return null;\n if (\n el instanceof HTMLInputElement ||\n el instanceof HTMLSelectElement ||\n el instanceof HTMLTextAreaElement\n ) {\n return el;\n }\n // The locator hit a wrapper — descend to a nested control.\n const nested = el.querySelector(\"input, select, textarea\");\n return (nested as HTMLElement) || null;\n}\n\n/**\n * The set of `@mantine/core` component references whose rendered\n * inputs we recognize as comboboxes. Compared by **reference equality**\n * against React fiber types via the bridge — same `import { Select }\n * from \"@mantine/core\"` reference the application uses ends up as\n * `fiber.type` in React's internals, so set membership is provably\n * correct identification (no name collisions, no DOM markup\n * heuristics, no version-specific class names).\n *\n * Add a new combobox-family component here when extending coverage.\n */\nconst MANTINE_COMBOBOX_TYPES = new Set<unknown>([\n Combobox,\n Select,\n Autocomplete,\n MultiSelect,\n TagsInput,\n]);\n\n/**\n * Typed accessor for the global harness installed by the Vite plugin.\n * Library adapter packages reach it via `window.__phyto_harness__`.\n */\nfunction getHarnessAccessor(): PhytoHarnessAccessor | null {\n if (typeof window === \"undefined\") return null;\n const w = window as unknown as { __phyto_harness__?: PhytoHarnessAccessor };\n return w.__phyto_harness__ ?? null;\n}\n\n/**\n * Is the given input rendered by a Mantine combobox component?\n *\n * Primary (and only authoritative) signal: walk the React fiber tree\n * upward from the input via `bridge.isElementFromAnyComponent` and\n * check whether any ancestor fiber's `type` matches one of the\n * imported `@mantine/core` component references in\n * `MANTINE_COMBOBOX_TYPES`. This is ground truth — same component\n * reference = same component definition.\n *\n * Fallback: if the bridge isn't available yet (framework detection\n * hasn't completed, or no React on the page), fall back to DOM-attribute\n * heuristics. These are not exclusive to Mantine (any ARIA combobox\n * hits them), but on a page that passed the cached `isMantinePage()`\n * gate they're a reasonable best-effort while detection is still in\n * flight. Once the bridge is available the fiber check takes over.\n *\n * A native `<select>` is never a Mantine combobox — early-returned.\n */\nexport function isMantineCombobox(input: HTMLElement): boolean {\n if (input instanceof HTMLSelectElement) return false;\n if (!(input instanceof HTMLInputElement)) return false;\n\n // Primary: ground-truth component-identity check via the bridge.\n const bridge = getHarnessAccessor()?.getBridge() ?? null;\n if (bridge) {\n return bridge.isElementFromAnyComponent(input, MANTINE_COMBOBOX_TYPES);\n }\n\n // Fallback: DOM-attribute heuristics for the brief window before the\n // framework bridge has been detected. These match any ARIA combobox,\n // not strictly Mantine — acceptable because the page-level\n // `isMantinePage()` gate already filtered non-Mantine pages out\n // before we got here.\n if (input.getAttribute(\"role\") === \"combobox\") return true;\n if (input.getAttribute(\"aria-haspopup\") === \"listbox\") return true;\n if (input.closest('[role=\"combobox\"]')) return true;\n if (input.closest(\"[data-combobox-target]\")) return true;\n\n return false;\n}\n","/**\n * Execute path for a Mantine Select / Autocomplete.\n *\n * The dispatcher only calls this after `probe()` has claimed, so the\n * locator is guaranteed to resolve to a Mantine combobox at the time\n * of the claim. The work here is to:\n *\n * 1. Resolve the locator → input.\n * 2. Click the input to open the dropdown.\n * 3. Read `aria-controls` on the input to find the portal mount id.\n * 4. Wait for the portal element to appear.\n * 5. Find the option with matching text via `[data-combobox-option]`.\n * 6. Click the option.\n * 7. Wait for the dropdown to unmount.\n * 8. Confirm the input value reflects the choice.\n */\n\nimport type { CommandResult, Locator } from \"phyto\";\nimport { resolveSelectInput } from \"./probe.js\";\n\ninterface SelectArgs {\n locator: Locator;\n value: string;\n timeout: number;\n}\n\nconst POLL_INTERVAL_MS = 30;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function waitFor<T>(\n fn: () => T | null | undefined,\n timeoutMs: number\n): Promise<T | null> {\n const deadline = Date.now() + timeoutMs;\n while (true) {\n const result = fn();\n if (result) return result;\n if (Date.now() >= deadline) return null;\n await sleep(POLL_INTERVAL_MS);\n }\n}\n\nexport async function executeSelect(args: SelectArgs): Promise<CommandResult> {\n const { locator, value, timeout } = args;\n const input = resolveSelectInput(locator);\n if (!(input instanceof HTMLInputElement)) {\n return {\n ok: false,\n error: `Mantine Select: locator did not resolve to a combobox input`,\n context: { reason: \"not-found\", locator },\n };\n }\n\n // Open the dropdown. Mantine wires click → openDropdown via the\n // Combobox primitive, but a focus-then-click sequence is more robust\n // against intermediate ref forwarding.\n input.focus();\n input.click();\n\n // Find the portal mount id via aria-controls.\n const portalId =\n input.getAttribute(\"aria-controls\") ??\n (await waitFor(\n () => input.getAttribute(\"aria-controls\"),\n Math.min(timeout, 500)\n ));\n\n // Wait for the portal element. Use the id if we have it; otherwise\n // fall back to scanning for a freshly-mounted listbox.\n const dropdown = await waitFor<HTMLElement>(() => {\n if (portalId) {\n const el = document.getElementById(portalId);\n if (el) return el;\n }\n const lb = document.body.querySelector<HTMLElement>('[role=\"listbox\"]');\n return lb ?? null;\n }, timeout);\n\n if (!dropdown) {\n return {\n ok: false,\n error: `Mantine Select dropdown did not open within ${timeout}ms`,\n context: { reason: \"timeout\", locator },\n };\n }\n\n // Find the matching option. v7 uses `[data-combobox-option]`; also\n // accept `role=\"option\"` for robustness across versions.\n const collectOptions = (): HTMLElement[] =>\n Array.from(\n dropdown.querySelectorAll<HTMLElement>(\n '[data-combobox-option], [role=\"option\"]'\n )\n );\n\n let options = collectOptions();\n if (options.length === 0) {\n await waitFor(() => {\n options = collectOptions();\n return options.length > 0 ? options : null;\n }, Math.min(timeout, 500));\n }\n\n const trimmed = value.trim();\n const match =\n options.find((o) => (o.textContent || \"\").trim() === trimmed) ??\n options.find(\n (o) => (o.textContent || \"\").trim().toLowerCase() === trimmed.toLowerCase()\n ) ??\n options.find((o) => (o.getAttribute(\"data-value\") || \"\") === trimmed);\n\n if (!match) {\n return {\n ok: false,\n error: `Option \"${value}\" not found in Mantine Select`,\n context: { reason: \"not-found\", locator },\n value: {\n availableOptions: options.map((o) => (o.textContent || \"\").trim()),\n },\n };\n }\n\n match.click();\n\n // Wait for the dropdown to unmount.\n await waitFor(() => {\n if (portalId) {\n const el = document.getElementById(portalId);\n if (!el) return true;\n const stillHasOptions = el.querySelector(\n '[data-combobox-option], [role=\"option\"]'\n );\n if (!stillHasOptions) return true;\n }\n return null;\n }, Math.min(timeout, 1000));\n\n const selectedValue = input.value || (match.textContent || \"\").trim();\n\n return {\n ok: true,\n value: {\n component: \"Select\",\n selectedValue,\n },\n };\n}\n","/**\n * `@phyto/mantine` — Mantine adapter for the Phyto in-page harness.\n *\n * Usage:\n *\n * // Auto-register on import (preferred):\n * import \"@phyto/mantine/register\";\n *\n * // Or wire it up manually:\n * import { mantineAdapter } from \"@phyto/mantine\";\n * window.__phyto_harness__.registerAdapter(mantineAdapter);\n * // or with an explicit precedence override:\n * // window.__phyto_harness__.registerAdapter(mantineAdapter, { precedence: 200 });\n *\n * The adapter claims `select` commands when the locator resolves to a\n * Mantine combobox (Select / Autocomplete share the primitive). The\n * adapter publishes a `defaultPrecedence` of 100; the consumer's\n * `registerAdapter()` call has the final word on tie-breaking.\n */\n\nimport type { Adapter, Command, ProbeResult, CommandResult, Locator } from \"phyto\";\nimport { isMantineCombobox, isMantinePage, resolveSelectInput } from \"./probe.js\";\nimport { executeSelect } from \"./select.js\";\n\n/**\n * Adapter-published default precedence. Conventionally:\n * 0 — DOM fallback\n * 50 — framework-level (component locators)\n * 100 — library-level (this adapter)\n *\n * Consumers override at `registerAdapter()` time when two libraries\n * collide on the same page.\n */\nconst MANTINE_DEFAULT_PRECEDENCE = 100;\nconst DEFAULT_TIMEOUT_MS = 5000;\n\nexport const mantineAdapter: Adapter = {\n name: \"mantine\",\n defaultPrecedence: MANTINE_DEFAULT_PRECEDENCE,\n\n probe(command: Command): ProbeResult {\n // The adapter currently only handles the generic `select` action.\n // Future components (MultiSelect, DatePicker, Modal, Notifications)\n // will add to this set.\n if (command.action !== \"select\") return { state: \"decline\" };\n\n // Page-level early-exit. Non-Mantine pages bail in O(1).\n if (!isMantinePage()) {\n return document.readyState === \"complete\"\n ? { state: \"decline\" }\n : { state: \"pending\" };\n }\n\n const cmd = command as { locator: Locator };\n const input = resolveSelectInput(cmd.locator);\n\n // Target hasn't rendered yet — we may own this once it mounts.\n if (!input) return { state: \"pending\" };\n\n // Element is here. If it's a Mantine combobox, claim. Otherwise\n // step aside — the DOM adapter will handle native `<select>` or\n // another library adapter will claim.\n if (isMantineCombobox(input)) {\n return { state: \"claim\" };\n }\n return { state: \"decline\" };\n },\n\n async execute(command: Command): Promise<CommandResult> {\n if (command.action !== \"select\") {\n return {\n ok: false,\n error: `Mantine adapter does not handle: ${command.action}`,\n };\n }\n const cmd = command as {\n locator: Locator;\n value: string;\n options?: { timeout?: number };\n };\n return executeSelect({\n locator: cmd.locator,\n value: cmd.value,\n timeout: cmd.options?.timeout ?? DEFAULT_TIMEOUT_MS,\n });\n },\n};\n\nexport type {\n Adapter,\n Command,\n CommandResult,\n ProbeResult,\n RegisterAdapterOptions,\n} from \"phyto\";\n","/**\n * Side-effect entrypoint for `@phyto/mantine`.\n *\n * Importing this module from your app boot code registers the Mantine\n * adapter on the Phyto harness. The import is safe in production\n * builds where the harness isn't on the page — it simply no-ops.\n *\n * import \"@phyto/mantine/register\";\n */\n\nimport { mantineAdapter } from \"./index.js\";\n\ninterface PhytoHarnessLike {\n registerAdapter?: (adapter: typeof mantineAdapter) => void;\n}\n\nfunction register(): void {\n const harness = (window as unknown as {\n __phyto_harness__?: PhytoHarnessLike;\n }).__phyto_harness__;\n\n if (!harness || typeof harness.registerAdapter !== \"function\") {\n // No harness — production build or harness not yet loaded. The Vite\n // plugin injects the harness into `main.tsx` ahead of user code, so\n // in practice this shouldn't fire under PHYTO_HARNESS=1.\n return;\n }\n harness.registerAdapter(mantineAdapter);\n}\n\nif (typeof window !== \"undefined\") {\n // Try immediately, then retry on DOMContentLoaded in case the import\n // races the harness injection ordering.\n register();\n if (document.readyState !== \"complete\") {\n window.addEventListener(\"DOMContentLoaded\", register, { once: true });\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// src/probe.ts
|
|
2
|
+
import {
|
|
3
|
+
Autocomplete,
|
|
4
|
+
Combobox,
|
|
5
|
+
MultiSelect,
|
|
6
|
+
Select,
|
|
7
|
+
TagsInput
|
|
8
|
+
} from "@mantine/core";
|
|
9
|
+
var cachedPageHasMantine = null;
|
|
10
|
+
function isMantinePage() {
|
|
11
|
+
if (cachedPageHasMantine !== null) return cachedPageHasMantine;
|
|
12
|
+
const hasColorScheme = document.querySelector("[data-mantine-color-scheme]");
|
|
13
|
+
if (hasColorScheme) {
|
|
14
|
+
cachedPageHasMantine = true;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
const hashedClassEl = document.body?.querySelector?.('[class*=" m_"], [class^="m_"]');
|
|
18
|
+
if (hashedClassEl) {
|
|
19
|
+
cachedPageHasMantine = true;
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
const legacyEl = document.body?.querySelector?.('[class*="mantine-"]:not([class*=" m_"]):not([class^="m_"])');
|
|
23
|
+
if (legacyEl) {
|
|
24
|
+
if (!warnedLegacy) {
|
|
25
|
+
console.error(
|
|
26
|
+
"[@phyto/mantine] Detected legacy Mantine class names (pre-v7). This adapter requires Mantine v7+. Upgrade Mantine or pin a v6-compatible adapter."
|
|
27
|
+
);
|
|
28
|
+
warnedLegacy = true;
|
|
29
|
+
}
|
|
30
|
+
cachedPageHasMantine = false;
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (document.readyState === "complete") {
|
|
34
|
+
cachedPageHasMantine = false;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
var warnedLegacy = false;
|
|
40
|
+
function findInputByLabel(labelText) {
|
|
41
|
+
const labels = Array.from(document.querySelectorAll("label"));
|
|
42
|
+
let labelEl = null;
|
|
43
|
+
for (const l of labels) {
|
|
44
|
+
if ((l.textContent || "").trim() === labelText) {
|
|
45
|
+
labelEl = l;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!labelEl) {
|
|
50
|
+
for (const l of labels) {
|
|
51
|
+
if ((l.textContent || "").includes(labelText)) {
|
|
52
|
+
labelEl = l;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!labelEl) return null;
|
|
58
|
+
const htmlFor = labelEl.getAttribute("for");
|
|
59
|
+
if (htmlFor) {
|
|
60
|
+
const target = document.getElementById(htmlFor);
|
|
61
|
+
if (target) return target;
|
|
62
|
+
}
|
|
63
|
+
return labelEl.querySelector("input, select, textarea") || null;
|
|
64
|
+
}
|
|
65
|
+
function resolveSelectInput(locator) {
|
|
66
|
+
let el = null;
|
|
67
|
+
if ("label" in locator) {
|
|
68
|
+
return findInputByLabel(locator.label);
|
|
69
|
+
}
|
|
70
|
+
if ("css" in locator) {
|
|
71
|
+
el = document.querySelector(locator.css);
|
|
72
|
+
} else if ("testId" in locator) {
|
|
73
|
+
el = document.querySelector(`[data-testid="${locator.testId}"]`);
|
|
74
|
+
} else if ("text" in locator) {
|
|
75
|
+
const all = Array.from(
|
|
76
|
+
document.querySelectorAll(
|
|
77
|
+
"input, select, [role='combobox']"
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
for (const candidate of all) {
|
|
81
|
+
if ((candidate.textContent || "").includes(locator.text)) {
|
|
82
|
+
el = candidate;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else if ("role" in locator) {
|
|
87
|
+
const candidates = Array.from(
|
|
88
|
+
document.querySelectorAll(`[role="${locator.role}"]`)
|
|
89
|
+
);
|
|
90
|
+
if (locator.name) {
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
const name = candidate.getAttribute("aria-label") || candidate.getAttribute("aria-labelledby") || (candidate.textContent || "").trim();
|
|
93
|
+
if (name === locator.name) {
|
|
94
|
+
el = candidate;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
el = candidates[0] || null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!el) return null;
|
|
103
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement || el instanceof HTMLTextAreaElement) {
|
|
104
|
+
return el;
|
|
105
|
+
}
|
|
106
|
+
const nested = el.querySelector("input, select, textarea");
|
|
107
|
+
return nested || null;
|
|
108
|
+
}
|
|
109
|
+
var MANTINE_COMBOBOX_TYPES = /* @__PURE__ */ new Set([
|
|
110
|
+
Combobox,
|
|
111
|
+
Select,
|
|
112
|
+
Autocomplete,
|
|
113
|
+
MultiSelect,
|
|
114
|
+
TagsInput
|
|
115
|
+
]);
|
|
116
|
+
function getHarnessAccessor() {
|
|
117
|
+
if (typeof window === "undefined") return null;
|
|
118
|
+
const w = window;
|
|
119
|
+
return w.__phyto_harness__ ?? null;
|
|
120
|
+
}
|
|
121
|
+
function isMantineCombobox(input) {
|
|
122
|
+
if (input instanceof HTMLSelectElement) return false;
|
|
123
|
+
if (!(input instanceof HTMLInputElement)) return false;
|
|
124
|
+
const bridge = getHarnessAccessor()?.getBridge() ?? null;
|
|
125
|
+
if (bridge) {
|
|
126
|
+
return bridge.isElementFromAnyComponent(input, MANTINE_COMBOBOX_TYPES);
|
|
127
|
+
}
|
|
128
|
+
if (input.getAttribute("role") === "combobox") return true;
|
|
129
|
+
if (input.getAttribute("aria-haspopup") === "listbox") return true;
|
|
130
|
+
if (input.closest('[role="combobox"]')) return true;
|
|
131
|
+
if (input.closest("[data-combobox-target]")) return true;
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/select.ts
|
|
136
|
+
var POLL_INTERVAL_MS = 30;
|
|
137
|
+
function sleep(ms) {
|
|
138
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
139
|
+
}
|
|
140
|
+
async function waitFor(fn, timeoutMs) {
|
|
141
|
+
const deadline = Date.now() + timeoutMs;
|
|
142
|
+
while (true) {
|
|
143
|
+
const result = fn();
|
|
144
|
+
if (result) return result;
|
|
145
|
+
if (Date.now() >= deadline) return null;
|
|
146
|
+
await sleep(POLL_INTERVAL_MS);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function executeSelect(args) {
|
|
150
|
+
const { locator, value, timeout } = args;
|
|
151
|
+
const input = resolveSelectInput(locator);
|
|
152
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
error: `Mantine Select: locator did not resolve to a combobox input`,
|
|
156
|
+
context: { reason: "not-found", locator }
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
input.focus();
|
|
160
|
+
input.click();
|
|
161
|
+
const portalId = input.getAttribute("aria-controls") ?? await waitFor(
|
|
162
|
+
() => input.getAttribute("aria-controls"),
|
|
163
|
+
Math.min(timeout, 500)
|
|
164
|
+
);
|
|
165
|
+
const dropdown = await waitFor(() => {
|
|
166
|
+
if (portalId) {
|
|
167
|
+
const el = document.getElementById(portalId);
|
|
168
|
+
if (el) return el;
|
|
169
|
+
}
|
|
170
|
+
const lb = document.body.querySelector('[role="listbox"]');
|
|
171
|
+
return lb ?? null;
|
|
172
|
+
}, timeout);
|
|
173
|
+
if (!dropdown) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
error: `Mantine Select dropdown did not open within ${timeout}ms`,
|
|
177
|
+
context: { reason: "timeout", locator }
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const collectOptions = () => Array.from(
|
|
181
|
+
dropdown.querySelectorAll(
|
|
182
|
+
'[data-combobox-option], [role="option"]'
|
|
183
|
+
)
|
|
184
|
+
);
|
|
185
|
+
let options = collectOptions();
|
|
186
|
+
if (options.length === 0) {
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
options = collectOptions();
|
|
189
|
+
return options.length > 0 ? options : null;
|
|
190
|
+
}, Math.min(timeout, 500));
|
|
191
|
+
}
|
|
192
|
+
const trimmed = value.trim();
|
|
193
|
+
const match = options.find((o) => (o.textContent || "").trim() === trimmed) ?? options.find(
|
|
194
|
+
(o) => (o.textContent || "").trim().toLowerCase() === trimmed.toLowerCase()
|
|
195
|
+
) ?? options.find((o) => (o.getAttribute("data-value") || "") === trimmed);
|
|
196
|
+
if (!match) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
error: `Option "${value}" not found in Mantine Select`,
|
|
200
|
+
context: { reason: "not-found", locator },
|
|
201
|
+
value: {
|
|
202
|
+
availableOptions: options.map((o) => (o.textContent || "").trim())
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
match.click();
|
|
207
|
+
await waitFor(() => {
|
|
208
|
+
if (portalId) {
|
|
209
|
+
const el = document.getElementById(portalId);
|
|
210
|
+
if (!el) return true;
|
|
211
|
+
const stillHasOptions = el.querySelector(
|
|
212
|
+
'[data-combobox-option], [role="option"]'
|
|
213
|
+
);
|
|
214
|
+
if (!stillHasOptions) return true;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}, Math.min(timeout, 1e3));
|
|
218
|
+
const selectedValue = input.value || (match.textContent || "").trim();
|
|
219
|
+
return {
|
|
220
|
+
ok: true,
|
|
221
|
+
value: {
|
|
222
|
+
component: "Select",
|
|
223
|
+
selectedValue
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/index.ts
|
|
229
|
+
var MANTINE_DEFAULT_PRECEDENCE = 100;
|
|
230
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
231
|
+
var mantineAdapter = {
|
|
232
|
+
name: "mantine",
|
|
233
|
+
defaultPrecedence: MANTINE_DEFAULT_PRECEDENCE,
|
|
234
|
+
probe(command) {
|
|
235
|
+
if (command.action !== "select") return { state: "decline" };
|
|
236
|
+
if (!isMantinePage()) {
|
|
237
|
+
return document.readyState === "complete" ? { state: "decline" } : { state: "pending" };
|
|
238
|
+
}
|
|
239
|
+
const cmd = command;
|
|
240
|
+
const input = resolveSelectInput(cmd.locator);
|
|
241
|
+
if (!input) return { state: "pending" };
|
|
242
|
+
if (isMantineCombobox(input)) {
|
|
243
|
+
return { state: "claim" };
|
|
244
|
+
}
|
|
245
|
+
return { state: "decline" };
|
|
246
|
+
},
|
|
247
|
+
async execute(command) {
|
|
248
|
+
if (command.action !== "select") {
|
|
249
|
+
return {
|
|
250
|
+
ok: false,
|
|
251
|
+
error: `Mantine adapter does not handle: ${command.action}`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const cmd = command;
|
|
255
|
+
return executeSelect({
|
|
256
|
+
locator: cmd.locator,
|
|
257
|
+
value: cmd.value,
|
|
258
|
+
timeout: cmd.options?.timeout ?? DEFAULT_TIMEOUT_MS
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/register.ts
|
|
264
|
+
function register() {
|
|
265
|
+
const harness = window.__phyto_harness__;
|
|
266
|
+
if (!harness || typeof harness.registerAdapter !== "function") {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
harness.registerAdapter(mantineAdapter);
|
|
270
|
+
}
|
|
271
|
+
if (typeof window !== "undefined") {
|
|
272
|
+
register();
|
|
273
|
+
if (document.readyState !== "complete") {
|
|
274
|
+
window.addEventListener("DOMContentLoaded", register, { once: true });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=register.js.map
|