ghostfill 0.2.2 → 0.2.4
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/LICENSE +121 -0
- package/README.md +1 -1
- package/dist/{chunk-VMCU3BNJ.mjs → chunk-CKZ6PWBH.mjs} +2 -1
- package/dist/chunk-CKZ6PWBH.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +100 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -32
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/dist/{types-B3DGbZyx.d.mts → types-BYkjRHCV.d.mts} +1 -1
- package/dist/{types-B3DGbZyx.d.ts → types-BYkjRHCV.d.ts} +1 -1
- package/package.json +2 -2
- package/dist/chunk-VMCU3BNJ.mjs.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# PolyForm Shield License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/shield/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree
|
|
8
|
+
to them as both strict obligations and conditions to all
|
|
9
|
+
your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the
|
|
14
|
+
software to do everything you might do with the software
|
|
15
|
+
that would otherwise infringe the licensor's copyright
|
|
16
|
+
in it for any permitted purpose. However, you may only
|
|
17
|
+
distribute the software according to [Distribution
|
|
18
|
+
License](#distribution-license) and make changes or new works
|
|
19
|
+
based on the software according to [Changes and New Works
|
|
20
|
+
License](#changes-and-new-works-license).
|
|
21
|
+
|
|
22
|
+
## Distribution License
|
|
23
|
+
|
|
24
|
+
The licensor grants you an additional copyright license
|
|
25
|
+
to distribute copies of the software. Your license
|
|
26
|
+
to distribute covers distributing the software with
|
|
27
|
+
changes and new works permitted by [Changes and New Works
|
|
28
|
+
License](#changes-and-new-works-license).
|
|
29
|
+
|
|
30
|
+
## Notices
|
|
31
|
+
|
|
32
|
+
You must ensure that anyone who gets a copy of any part of
|
|
33
|
+
the software from you also gets a copy of these terms or the
|
|
34
|
+
URL for them above, as well as copies of any plain-text lines
|
|
35
|
+
beginning with `Required Notice:` that the licensor provided
|
|
36
|
+
with the software. For example:
|
|
37
|
+
|
|
38
|
+
> Required Notice: Copyright Ahmed Fathy (https://github.com/afkhalid)
|
|
39
|
+
|
|
40
|
+
## Changes and New Works License
|
|
41
|
+
|
|
42
|
+
The licensor grants you an additional copyright license to
|
|
43
|
+
make changes and new works based on the software for any
|
|
44
|
+
permitted purpose.
|
|
45
|
+
|
|
46
|
+
## Patent License
|
|
47
|
+
|
|
48
|
+
The licensor grants you a patent license for the software that
|
|
49
|
+
covers patent claims the licensor can license, or becomes able
|
|
50
|
+
to license, that you would infringe by using the software.
|
|
51
|
+
|
|
52
|
+
## Noncompete
|
|
53
|
+
|
|
54
|
+
Any purpose is a permitted purpose, except for providing any
|
|
55
|
+
product that competes with the software or any product the
|
|
56
|
+
licensor or any of its affiliates provides using the software.
|
|
57
|
+
|
|
58
|
+
## Competition
|
|
59
|
+
|
|
60
|
+
Offering a product that competes means offering a product
|
|
61
|
+
or service that is substantially similar to, or serves as a
|
|
62
|
+
substitute for, the software or a product or service that
|
|
63
|
+
the licensor provides using the software. Products and
|
|
64
|
+
services need not be identical to compete.
|
|
65
|
+
|
|
66
|
+
## No Other Rights
|
|
67
|
+
|
|
68
|
+
These terms do not allow you to sublicense or transfer any of
|
|
69
|
+
your licenses to anyone else, or prevent the licensor from
|
|
70
|
+
granting licenses to anyone else. These terms do not imply
|
|
71
|
+
any other licenses.
|
|
72
|
+
|
|
73
|
+
## Patent Defense
|
|
74
|
+
|
|
75
|
+
If you make any written claim that the software infringes or
|
|
76
|
+
contributes to infringement of any patent, your patent license
|
|
77
|
+
for the software granted under these terms ends immediately. If
|
|
78
|
+
your company makes such a claim, your patent license ends
|
|
79
|
+
immediately for work on behalf of your company.
|
|
80
|
+
|
|
81
|
+
## Violations
|
|
82
|
+
|
|
83
|
+
The first time you are notified in writing that you have
|
|
84
|
+
violated any of these terms, or done anything with the software
|
|
85
|
+
not covered by your licenses, your licenses can nonetheless
|
|
86
|
+
continue if you come into full compliance with these terms,
|
|
87
|
+
and take practical steps to correct past violations, within
|
|
88
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
89
|
+
end immediately.
|
|
90
|
+
|
|
91
|
+
## No Liability
|
|
92
|
+
|
|
93
|
+
***As far as the law allows, the software comes as is, without
|
|
94
|
+
any warranty or condition, and the licensor will not be liable
|
|
95
|
+
to you for any damages arising out of these terms or the use
|
|
96
|
+
or nature of the software, under any kind of legal claim.***
|
|
97
|
+
|
|
98
|
+
## Definitions
|
|
99
|
+
|
|
100
|
+
The **licensor** is the individual or entity offering these
|
|
101
|
+
terms, and the **software** is the software the licensor makes
|
|
102
|
+
available under these terms.
|
|
103
|
+
|
|
104
|
+
**You** refers to the individual or entity agreeing to these
|
|
105
|
+
terms.
|
|
106
|
+
|
|
107
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
108
|
+
or other kind of organization that you work for, plus all
|
|
109
|
+
organizations that have control over, are under the control of,
|
|
110
|
+
or are under common control with that organization. **Control**
|
|
111
|
+
means ownership of substantially all the assets of an entity,
|
|
112
|
+
or the power to direct its management and policies by vote,
|
|
113
|
+
contract, or otherwise. Control can be direct or indirect.
|
|
114
|
+
|
|
115
|
+
**Your licenses** are all the licenses granted to you for the
|
|
116
|
+
software under these terms.
|
|
117
|
+
|
|
118
|
+
**Use** means anything you do with the software requiring one
|
|
119
|
+
of your licenses.
|
|
120
|
+
|
|
121
|
+
Required Notice: Copyright 2026 Ahmed Fathy (https://github.com/afkhalid)
|
package/README.md
CHANGED
|
@@ -264,6 +264,7 @@ async function generateFillData(fields, userPrompt, provider, transport, systemP
|
|
|
264
264
|
|
|
265
265
|
export {
|
|
266
266
|
PROVIDERS,
|
|
267
|
+
findLabel,
|
|
267
268
|
detectFields,
|
|
268
269
|
describeFields,
|
|
269
270
|
SYSTEM_PROMPT,
|
|
@@ -272,4 +273,4 @@ export {
|
|
|
272
273
|
parseFillDataPayload,
|
|
273
274
|
generateFillData
|
|
274
275
|
};
|
|
275
|
-
//# sourceMappingURL=chunk-
|
|
276
|
+
//# sourceMappingURL=chunk-CKZ6PWBH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/detector.ts","../src/ai.ts"],"sourcesContent":["/** Configuration options for GhostFill */\nexport interface GhostFillOptions {\n /**\n * @deprecated Browser API keys are insecure and ignored.\n * Configure `ai` and keep provider credentials on your backend instead.\n */\n apiKey?: string;\n /** Keyboard shortcut to toggle GhostFill (default: \"Alt+G\") */\n shortcut?: string;\n /** Custom system prompt to prepend */\n systemPrompt?: string;\n /** Secure AI transport configuration */\n ai?: GhostFillAIOptions;\n}\n\nexport type Provider = \"openai\" | \"xai\" | \"moonshot\";\n\nexport const PROVIDERS: Record<Provider, { label: string; model: string; baseURL: string; helpText: string }> = {\n openai: { label: \"OpenAI\", model: \"gpt-4o-mini\", baseURL: \"https://api.openai.com/v1\", helpText: \"Uses gpt-4o-mini — fast & cheap\" },\n xai: { label: \"xAI\", model: \"grok-4-fast\", baseURL: \"https://api.x.ai/v1\", helpText: \"Uses Grok 4 Fast\" },\n moonshot: { label: \"Moonshot\", model: \"kimi-k2\", baseURL: \"https://api.moonshot.ai/v1\", helpText: \"Uses Kimi K2 — fast & cheap\" },\n};\n\n/** Non-secret field metadata sent to an AI backend */\nexport interface GhostFillPromptField {\n index: number;\n type: string;\n name: string;\n label: string;\n options?: string[];\n required: boolean;\n min?: string;\n max?: string;\n pattern?: string;\n}\n\n/** Secure AI request payload for a backend route or callback */\nexport interface GhostFillAIRequest {\n provider: Provider;\n prompt: string;\n systemPrompt?: string;\n fields: GhostFillPromptField[];\n}\n\nexport type GhostFillAIHandler = (\n request: GhostFillAIRequest\n) => Promise<FieldFillData[]>;\n\n/** Secure AI configuration. Requests must go through a backend or custom handler. */\nexport interface GhostFillAIOptions {\n /** Same-origin backend route. Defaults to `/api/ghostfill` when `ai` is enabled. */\n endpoint?: string;\n /** Optional custom transport that forwards requests to a secure backend. */\n requestFillData?: GhostFillAIHandler;\n /** Default provider shown in the UI. */\n provider?: Provider;\n}\n\n/** A saved prompt preset */\nexport interface Preset {\n id: string;\n name: string;\n prompt: string;\n}\n\n/** Persisted settings (localStorage) */\nexport interface GhostFillSettings {\n apiKey: string;\n provider: Provider;\n highlightColor: string;\n theme: \"dark\" | \"light\";\n useAI: boolean;\n presets: Preset[];\n activePresetId: string | null;\n}\n\n/** A detected form field */\nexport interface DetectedField {\n /** The DOM element */\n element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLElement;\n /** Field type: text, email, number, select, textarea, checkbox, radio, date, etc. */\n type: string;\n /** Field name attribute */\n name: string;\n /** Field label (from <label>, aria-label, or placeholder) */\n label: string;\n /** For <select>, the available options */\n options?: string[];\n /** Whether the field is required */\n required: boolean;\n /** Current value */\n currentValue: string;\n /** Min/max for number/date fields */\n min?: string;\n max?: string;\n /** Pattern attribute */\n pattern?: string;\n}\n\n/** Field data returned by the AI */\nexport interface FieldFillData {\n /** Index matching the DetectedField array */\n index: number;\n /** Value to fill */\n value: string;\n /** For checkboxes: whether to check */\n checked?: boolean;\n}\n\n/** Internal state */\nexport interface GhostFillState {\n active: boolean;\n selecting: boolean;\n selectedBlock: HTMLElement | null;\n fields: DetectedField[];\n}\n","import type { DetectedField, GhostFillPromptField } from \"./types\";\n\nconst INPUT_SELECTORS = [\n \"input:not([type=hidden]):not([type=submit]):not([type=button]):not([type=reset]):not([type=image])\",\n \"textarea\",\n \"select\",\n // Custom dropdown triggers (Headless UI Listbox, Radix, etc.)\n \"button[role=combobox]\",\n \"[role=combobox]:not(input)\",\n \"button[aria-haspopup=listbox]\",\n].join(\", \");\n\n/** Find the label text for a form field */\nexport function findLabel(el: HTMLElement): string {\n // 1. Explicit <label for=\"id\">\n if (el.id) {\n const label = document.querySelector<HTMLLabelElement>(\n `label[for=\"${CSS.escape(el.id)}\"]`\n );\n if (label?.textContent?.trim()) return label.textContent.trim();\n }\n\n // 2. Wrapping <label>\n const parentLabel = el.closest(\"label\");\n if (parentLabel) {\n // Get text content excluding the input itself\n const clone = parentLabel.cloneNode(true) as HTMLElement;\n clone.querySelectorAll(\"input, textarea, select\").forEach((c) => c.remove());\n const text = clone.textContent?.trim();\n if (text) return text;\n }\n\n // 3. aria-label\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel?.trim()) return ariaLabel.trim();\n\n // 4. aria-labelledby\n const labelledBy = el.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const parts = labelledBy\n .split(/\\s+/)\n .map((id) => document.getElementById(id)?.textContent?.trim())\n .filter(Boolean);\n if (parts.length) return parts.join(\" \");\n }\n\n // 5. placeholder\n if (\"placeholder\" in el) {\n const ph = (el as HTMLInputElement).placeholder;\n if (ph?.trim()) return ph.trim();\n }\n\n // 6. title\n const title = el.getAttribute(\"title\");\n if (title?.trim()) return title.trim();\n\n // 7. Preceding sibling text (common pattern: <span>Label</span><input>)\n const prev = el.previousElementSibling;\n if (prev && ![\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(prev.tagName)) {\n const prevText = prev.textContent?.trim();\n if (prevText && prevText.length < 60) return prevText;\n }\n\n // 8. Parent's first text node or heading\n const parent = el.parentElement;\n if (parent) {\n // Look for a heading or label-like element before this input in the parent\n for (const child of Array.from(parent.children)) {\n if (child === el) break;\n if ([\"LABEL\", \"SPAN\", \"P\", \"H1\", \"H2\", \"H3\", \"H4\", \"H5\", \"H6\", \"LEGEND\", \"DIV\"].includes(child.tagName)) {\n const text = child.textContent?.trim();\n if (text && text.length < 60 && !child.querySelector(\"input, textarea, select\")) {\n return text;\n }\n }\n }\n }\n\n // 9. id attribute humanized\n if (el.id) return el.id.replace(/[_\\-]/g, \" \").replace(/([a-z])([A-Z])/g, \"$1 $2\").trim();\n\n // 10. name attribute as fallback\n const name = el.getAttribute(\"name\");\n if (name) return name.replace(/[_\\-[\\]]/g, \" \").replace(/([a-z])([A-Z])/g, \"$1 $2\").trim();\n\n return \"unknown\";\n}\n\n/** Detect all fillable fields within a container element */\nexport function detectFields(container: HTMLElement): DetectedField[] {\n const elements = container.querySelectorAll<\n HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n >(INPUT_SELECTORS);\n\n const fields: DetectedField[] = [];\n\n elements.forEach((el) => {\n // Skip disabled fields\n if ((el as HTMLInputElement).disabled) return;\n if ((el as HTMLInputElement).readOnly) return;\n // Skip hidden elements\n if (el.offsetParent === null && (el as HTMLInputElement).type !== \"hidden\") return;\n\n // Custom combobox / listbox (Headless UI, Radix, etc.)\n const isCustomDropdown =\n (el.getAttribute(\"role\") === \"combobox\" && !(el instanceof HTMLInputElement)) ||\n el.getAttribute(\"aria-haspopup\") === \"listbox\";\n if (isCustomDropdown) {\n // Find listbox options: aria-controls, sibling [role=listbox], or Headless UI pattern\n const listboxId = el.getAttribute(\"aria-controls\");\n let listbox: Element | null = listboxId ? document.getElementById(listboxId) : null;\n if (!listbox) {\n // Headless UI puts the listbox as a sibling inside the same relative container\n listbox = el.parentElement?.querySelector(\"[role=listbox]\") || null;\n }\n\n const options: string[] = [];\n if (listbox) {\n listbox.querySelectorAll(\"[role=option]\").forEach((opt) => {\n const text = opt.textContent?.trim();\n if (text) options.push(text);\n });\n }\n\n // Get current display value from button text\n const buttonText = el.textContent?.trim() || \"\";\n // Check if it looks like a placeholder\n const looksLikePlaceholder = buttonText.toLowerCase().startsWith(\"select\") || buttonText === \"\";\n\n const field: DetectedField = {\n element: el as HTMLElement,\n type: \"select\",\n name: el.id || el.getAttribute(\"name\") || \"\",\n label: findLabel(el),\n required: el.getAttribute(\"aria-required\") === \"true\",\n currentValue: looksLikePlaceholder ? \"\" : buttonText,\n options: options.length > 0 ? options : undefined,\n };\n fields.push(field);\n return;\n }\n\n const field: DetectedField = {\n element: el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n type: el instanceof HTMLSelectElement ? \"select\" : (el as HTMLInputElement).type || \"text\",\n name: (el as HTMLInputElement).name || el.id || \"\",\n label: findLabel(el),\n required: (el as HTMLInputElement).required || el.getAttribute(\"aria-required\") === \"true\",\n currentValue: (el as HTMLInputElement).value,\n };\n\n // Collect select options\n if (el instanceof HTMLSelectElement) {\n field.options = Array.from(el.options)\n .filter((opt) => opt.value && !opt.disabled)\n .map((opt) => opt.textContent?.trim() || opt.value);\n }\n\n // Collect constraints\n if (\"min\" in el && (el as HTMLInputElement).min) {\n field.min = (el as HTMLInputElement).min;\n }\n if (\"max\" in el && (el as HTMLInputElement).max) {\n field.max = (el as HTMLInputElement).max;\n }\n if (\"pattern\" in el && (el as HTMLInputElement).pattern) {\n field.pattern = (el as HTMLInputElement).pattern;\n }\n\n fields.push(field);\n });\n\n return fields;\n}\n\n/** Build a description of fields for the AI prompt */\nexport function describeFields(\n fields: Array<\n Pick<\n DetectedField | GhostFillPromptField,\n \"type\" | \"label\" | \"required\" | \"options\" | \"min\" | \"max\" | \"pattern\"\n >\n >\n): string {\n return fields\n .map((f, i) => {\n let desc = `[${i}] \"${f.label}\" (type: ${f.type}`;\n if (f.required) desc += \", required\";\n if (f.options?.length) desc += `, options: [${f.options.join(\", \")}]`;\n if (f.min) desc += `, min: ${f.min}`;\n if (f.max) desc += `, max: ${f.max}`;\n if (f.pattern) desc += `, pattern: ${f.pattern}`;\n desc += \")\";\n return desc;\n })\n .join(\"\\n\");\n}\n","import { describeFields } from \"./detector\";\nimport type {\n DetectedField,\n FieldFillData,\n GhostFillAIOptions,\n GhostFillAIRequest,\n GhostFillPromptField,\n Provider,\n} from \"./types\";\n\nexport const SYSTEM_PROMPT = `You are a form-filling assistant. Given a list of form fields and an optional user prompt, generate realistic fake data to fill ALL fields.\n\nRules:\n- Return ONLY a JSON object with a \"fields\" array of objects, each with \"index\" and \"value\" keys\n- You MUST fill EVERY field — do not skip any\n- Match the field type (email → valid email, phone → valid phone with country code, date → YYYY-MM-DD, datetime-local → YYYY-MM-DDTHH:MM, etc.)\n- For select/dropdown fields: you MUST pick one of the listed options EXACTLY as written\n- For checkboxes: add a \"checked\" boolean (true or false)\n- For radio buttons: only fill one per group, use the option value\n- Respect min/max constraints and patterns\n- Generate contextually coherent data (same person's name, matching city/state/zip, etc.)\n- If no user prompt is given, infer appropriate data from the field labels\n- Do NOT request or rely on secrets, tokens, passwords, or existing form values\n- Do NOT wrap the JSON in markdown code blocks — return raw JSON only`;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction toFieldFillData(value: unknown): FieldFillData {\n if (!isRecord(value)) {\n throw new Error(\"AI response item is not an object\");\n }\n\n const index = value.index;\n const rawValue = value.value;\n const checked = value.checked;\n\n if (typeof index !== \"number\" || !Number.isInteger(index)) {\n throw new Error(\"AI response item is missing a numeric index\");\n }\n\n if (typeof rawValue !== \"string\") {\n throw new Error(\"AI response item is missing a string value\");\n }\n\n return {\n index,\n value: rawValue,\n checked: typeof checked === \"boolean\" ? checked : undefined,\n };\n}\n\nexport function toPromptFields(fields: DetectedField[]): GhostFillPromptField[] {\n return fields.map((field, index) => ({\n index,\n type: field.type,\n name: field.name,\n label: field.label,\n options: field.options,\n required: field.required,\n min: field.min,\n max: field.max,\n pattern: field.pattern,\n }));\n}\n\nexport function buildFillMessages(\n request: GhostFillAIRequest\n): Array<{ role: \"system\" | \"user\"; content: string }> {\n const fieldDescription = describeFields(request.fields);\n let userContent = `Form fields:\\n${fieldDescription}`;\n\n if (request.prompt) {\n userContent += `\\n\\nUser instructions: ${request.prompt}`;\n } else {\n userContent +=\n \"\\n\\nNo specific instructions — generate realistic, contextually appropriate data for all fields.\";\n }\n\n return [\n {\n role: \"system\",\n content: request.systemPrompt\n ? `${request.systemPrompt}\\n\\n${SYSTEM_PROMPT}`\n : SYSTEM_PROMPT,\n },\n {\n role: \"user\",\n content: userContent,\n },\n ];\n}\n\nexport function parseFillDataPayload(payload: unknown): FieldFillData[] {\n if (typeof payload === \"string\") {\n const jsonMatch =\n payload.match(/```(?:json)?\\s*([\\s\\S]*?)```/) || [null, payload];\n return parseFillDataPayload(JSON.parse(jsonMatch[1]!.trim()));\n }\n\n if (Array.isArray(payload)) {\n return payload.map(toFieldFillData);\n }\n\n if (isRecord(payload)) {\n if (Array.isArray(payload.choices)) {\n const content = (payload.choices[0] as Record<string, unknown> | undefined)\n ?.message as Record<string, unknown> | undefined;\n if (typeof content?.content === \"string\") {\n return parseFillDataPayload(content.content);\n }\n }\n\n const candidate =\n payload.fields ?? payload.data ?? payload.items ?? payload.result;\n if (Array.isArray(candidate)) {\n return candidate.map(toFieldFillData);\n }\n }\n\n throw new Error(\"AI response is not an array of field fills\");\n}\n\nfunction createRequest(\n fields: DetectedField[],\n userPrompt: string,\n provider: Provider,\n systemPrompt?: string\n): GhostFillAIRequest {\n return {\n provider,\n prompt: userPrompt,\n systemPrompt,\n fields: toPromptFields(fields),\n };\n}\n\n/** Call a secure backend or callback to generate fill data. */\nexport async function generateFillData(\n fields: DetectedField[],\n userPrompt: string,\n provider: Provider,\n transport: GhostFillAIOptions,\n systemPrompt?: string\n): Promise<FieldFillData[]> {\n const request = createRequest(fields, userPrompt, provider, systemPrompt);\n\n if (transport.requestFillData) {\n return transport.requestFillData(request);\n }\n\n const endpoint = transport.endpoint ?? \"/api/ghostfill\";\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`AI API error (${response.status}): ${error}`);\n }\n\n const contentType = response.headers.get(\"content-type\") || \"\";\n const payload = contentType.includes(\"application/json\")\n ? await response.json()\n : await response.text();\n\n return parseFillDataPayload(payload);\n}\n"],"mappings":";AAiBO,IAAM,YAAmG;AAAA,EAC9G,QAAQ,EAAE,OAAO,UAAU,OAAO,eAAe,SAAS,6BAA6B,UAAU,uCAAkC;AAAA,EACnI,KAAK,EAAE,OAAO,OAAO,OAAO,eAAe,SAAS,uBAAuB,UAAU,mBAAmB;AAAA,EACxG,UAAU,EAAE,OAAO,YAAY,OAAO,WAAW,SAAS,8BAA8B,UAAU,mCAA8B;AAClI;;;ACnBA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAGJ,SAAS,UAAU,IAAyB;AAEjD,MAAI,GAAG,IAAI;AACT,UAAM,QAAQ,SAAS;AAAA,MACrB,cAAc,IAAI,OAAO,GAAG,EAAE,CAAC;AAAA,IACjC;AACA,QAAI,OAAO,aAAa,KAAK,EAAG,QAAO,MAAM,YAAY,KAAK;AAAA,EAChE;AAGA,QAAM,cAAc,GAAG,QAAQ,OAAO;AACtC,MAAI,aAAa;AAEf,UAAM,QAAQ,YAAY,UAAU,IAAI;AACxC,UAAM,iBAAiB,yBAAyB,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;AAC3E,UAAM,OAAO,MAAM,aAAa,KAAK;AACrC,QAAI,KAAM,QAAO;AAAA,EACnB;AAGA,QAAM,YAAY,GAAG,aAAa,YAAY;AAC9C,MAAI,WAAW,KAAK,EAAG,QAAO,UAAU,KAAK;AAG7C,QAAM,aAAa,GAAG,aAAa,iBAAiB;AACpD,MAAI,YAAY;AACd,UAAM,QAAQ,WACX,MAAM,KAAK,EACX,IAAI,CAAC,OAAO,SAAS,eAAe,EAAE,GAAG,aAAa,KAAK,CAAC,EAC5D,OAAO,OAAO;AACjB,QAAI,MAAM,OAAQ,QAAO,MAAM,KAAK,GAAG;AAAA,EACzC;AAGA,MAAI,iBAAiB,IAAI;AACvB,UAAM,KAAM,GAAwB;AACpC,QAAI,IAAI,KAAK,EAAG,QAAO,GAAG,KAAK;AAAA,EACjC;AAGA,QAAM,QAAQ,GAAG,aAAa,OAAO;AACrC,MAAI,OAAO,KAAK,EAAG,QAAO,MAAM,KAAK;AAGrC,QAAM,OAAO,GAAG;AAChB,MAAI,QAAQ,CAAC,CAAC,SAAS,YAAY,QAAQ,EAAE,SAAS,KAAK,OAAO,GAAG;AACnE,UAAM,WAAW,KAAK,aAAa,KAAK;AACxC,QAAI,YAAY,SAAS,SAAS,GAAI,QAAO;AAAA,EAC/C;AAGA,QAAM,SAAS,GAAG;AAClB,MAAI,QAAQ;AAEV,eAAW,SAAS,MAAM,KAAK,OAAO,QAAQ,GAAG;AAC/C,UAAI,UAAU,GAAI;AAClB,UAAI,CAAC,SAAS,QAAQ,KAAK,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,UAAU,KAAK,EAAE,SAAS,MAAM,OAAO,GAAG;AACvG,cAAM,OAAO,MAAM,aAAa,KAAK;AACrC,YAAI,QAAQ,KAAK,SAAS,MAAM,CAAC,MAAM,cAAc,yBAAyB,GAAG;AAC/E,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAI,GAAG,GAAI,QAAO,GAAG,GAAG,QAAQ,UAAU,GAAG,EAAE,QAAQ,mBAAmB,OAAO,EAAE,KAAK;AAGxF,QAAM,OAAO,GAAG,aAAa,MAAM;AACnC,MAAI,KAAM,QAAO,KAAK,QAAQ,aAAa,GAAG,EAAE,QAAQ,mBAAmB,OAAO,EAAE,KAAK;AAEzF,SAAO;AACT;AAGO,SAAS,aAAa,WAAyC;AACpE,QAAM,WAAW,UAAU,iBAEzB,eAAe;AAEjB,QAAM,SAA0B,CAAC;AAEjC,WAAS,QAAQ,CAAC,OAAO;AAEvB,QAAK,GAAwB,SAAU;AACvC,QAAK,GAAwB,SAAU;AAEvC,QAAI,GAAG,iBAAiB,QAAS,GAAwB,SAAS,SAAU;AAG5E,UAAM,mBACH,GAAG,aAAa,MAAM,MAAM,cAAc,EAAE,cAAc,qBAC3D,GAAG,aAAa,eAAe,MAAM;AACvC,QAAI,kBAAkB;AAEpB,YAAM,YAAY,GAAG,aAAa,eAAe;AACjD,UAAI,UAA0B,YAAY,SAAS,eAAe,SAAS,IAAI;AAC/E,UAAI,CAAC,SAAS;AAEZ,kBAAU,GAAG,eAAe,cAAc,gBAAgB,KAAK;AAAA,MACjE;AAEA,YAAM,UAAoB,CAAC;AAC3B,UAAI,SAAS;AACX,gBAAQ,iBAAiB,eAAe,EAAE,QAAQ,CAAC,QAAQ;AACzD,gBAAM,OAAO,IAAI,aAAa,KAAK;AACnC,cAAI,KAAM,SAAQ,KAAK,IAAI;AAAA,QAC7B,CAAC;AAAA,MACH;AAGA,YAAM,aAAa,GAAG,aAAa,KAAK,KAAK;AAE7C,YAAM,uBAAuB,WAAW,YAAY,EAAE,WAAW,QAAQ,KAAK,eAAe;AAE7F,YAAMA,SAAuB;AAAA,QAC3B,SAAS;AAAA,QACT,MAAM;AAAA,QACN,MAAM,GAAG,MAAM,GAAG,aAAa,MAAM,KAAK;AAAA,QAC1C,OAAO,UAAU,EAAE;AAAA,QACnB,UAAU,GAAG,aAAa,eAAe,MAAM;AAAA,QAC/C,cAAc,uBAAuB,KAAK;AAAA,QAC1C,SAAS,QAAQ,SAAS,IAAI,UAAU;AAAA,MAC1C;AACA,aAAO,KAAKA,MAAK;AACjB;AAAA,IACF;AAEA,UAAM,QAAuB;AAAA,MAC3B,SAAS;AAAA,MACT,MAAM,cAAc,oBAAoB,WAAY,GAAwB,QAAQ;AAAA,MACpF,MAAO,GAAwB,QAAQ,GAAG,MAAM;AAAA,MAChD,OAAO,UAAU,EAAE;AAAA,MACnB,UAAW,GAAwB,YAAY,GAAG,aAAa,eAAe,MAAM;AAAA,MACpF,cAAe,GAAwB;AAAA,IACzC;AAGA,QAAI,cAAc,mBAAmB;AACnC,YAAM,UAAU,MAAM,KAAK,GAAG,OAAO,EAClC,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC,IAAI,QAAQ,EAC1C,IAAI,CAAC,QAAQ,IAAI,aAAa,KAAK,KAAK,IAAI,KAAK;AAAA,IACtD;AAGA,QAAI,SAAS,MAAO,GAAwB,KAAK;AAC/C,YAAM,MAAO,GAAwB;AAAA,IACvC;AACA,QAAI,SAAS,MAAO,GAAwB,KAAK;AAC/C,YAAM,MAAO,GAAwB;AAAA,IACvC;AACA,QAAI,aAAa,MAAO,GAAwB,SAAS;AACvD,YAAM,UAAW,GAAwB;AAAA,IAC3C;AAEA,WAAO,KAAK,KAAK;AAAA,EACnB,CAAC;AAED,SAAO;AACT;AAGO,SAAS,eACd,QAMQ;AACR,SAAO,OACJ,IAAI,CAAC,GAAG,MAAM;AACb,QAAI,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,IAAI;AAC/C,QAAI,EAAE,SAAU,SAAQ;AACxB,QAAI,EAAE,SAAS,OAAQ,SAAQ,eAAe,EAAE,QAAQ,KAAK,IAAI,CAAC;AAClE,QAAI,EAAE,IAAK,SAAQ,UAAU,EAAE,GAAG;AAClC,QAAI,EAAE,IAAK,SAAQ,UAAU,EAAE,GAAG;AAClC,QAAI,EAAE,QAAS,SAAQ,cAAc,EAAE,OAAO;AAC9C,YAAQ;AACR,WAAO;AAAA,EACT,CAAC,EACA,KAAK,IAAI;AACd;;;AC1LO,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAe7B,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,gBAAgB,OAA+B;AACtD,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAEA,QAAM,QAAQ,MAAM;AACpB,QAAM,WAAW,MAAM;AACvB,QAAM,UAAU,MAAM;AAEtB,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,UAAU,KAAK,GAAG;AACzD,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,MAAI,OAAO,aAAa,UAAU;AAChC,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AAEA,SAAO;AAAA,IACL;AAAA,IACA,OAAO;AAAA,IACP,SAAS,OAAO,YAAY,YAAY,UAAU;AAAA,EACpD;AACF;AAEO,SAAS,eAAe,QAAiD;AAC9E,SAAO,OAAO,IAAI,CAAC,OAAO,WAAW;AAAA,IACnC;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,OAAO,MAAM;AAAA,IACb,SAAS,MAAM;AAAA,IACf,UAAU,MAAM;AAAA,IAChB,KAAK,MAAM;AAAA,IACX,KAAK,MAAM;AAAA,IACX,SAAS,MAAM;AAAA,EACjB,EAAE;AACJ;AAEO,SAAS,kBACd,SACqD;AACrD,QAAM,mBAAmB,eAAe,QAAQ,MAAM;AACtD,MAAI,cAAc;AAAA,EAAiB,gBAAgB;AAEnD,MAAI,QAAQ,QAAQ;AAClB,mBAAe;AAAA;AAAA,qBAA0B,QAAQ,MAAM;AAAA,EACzD,OAAO;AACL,mBACE;AAAA,EACJ;AAEA,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SAAS,QAAQ,eACb,GAAG,QAAQ,YAAY;AAAA;AAAA,EAAO,aAAa,KAC3C;AAAA,IACN;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB,SAAmC;AACtE,MAAI,OAAO,YAAY,UAAU;AAC/B,UAAM,YACJ,QAAQ,MAAM,8BAA8B,KAAK,CAAC,MAAM,OAAO;AACjE,WAAO,qBAAqB,KAAK,MAAM,UAAU,CAAC,EAAG,KAAK,CAAC,CAAC;AAAA,EAC9D;AAEA,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,QAAQ,IAAI,eAAe;AAAA,EACpC;AAEA,MAAI,SAAS,OAAO,GAAG;AACrB,QAAI,MAAM,QAAQ,QAAQ,OAAO,GAAG;AAClC,YAAM,UAAW,QAAQ,QAAQ,CAAC,GAC9B;AACJ,UAAI,OAAO,SAAS,YAAY,UAAU;AACxC,eAAO,qBAAqB,QAAQ,OAAO;AAAA,MAC7C;AAAA,IACF;AAEA,UAAM,YACJ,QAAQ,UAAU,QAAQ,QAAQ,QAAQ,SAAS,QAAQ;AAC7D,QAAI,MAAM,QAAQ,SAAS,GAAG;AAC5B,aAAO,UAAU,IAAI,eAAe;AAAA,IACtC;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,4CAA4C;AAC9D;AAEA,SAAS,cACP,QACA,YACA,UACA,cACoB;AACpB,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ,eAAe,MAAM;AAAA,EAC/B;AACF;AAGA,eAAsB,iBACpB,QACA,YACA,UACA,WACA,cAC0B;AAC1B,QAAM,UAAU,cAAc,QAAQ,YAAY,UAAU,YAAY;AAExE,MAAI,UAAU,iBAAiB;AAC7B,WAAO,UAAU,gBAAgB,OAAO;AAAA,EAC1C;AAEA,QAAM,WAAW,UAAU,YAAY;AACvC,QAAM,WAAW,MAAM,MAAM,UAAU;AAAA,IACrC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU,OAAO;AAAA,EAC9B,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,QAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,IAAI,MAAM,iBAAiB,SAAS,MAAM,MAAM,KAAK,EAAE;AAAA,EAC/D;AAEA,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,QAAM,UAAU,YAAY,SAAS,kBAAkB,IACnD,MAAM,SAAS,KAAK,IACpB,MAAM,SAAS,KAAK;AAExB,SAAO,qBAAqB,OAAO;AACrC;","names":["field"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as GhostFillAIOptions, P as Provider, a as GhostFillOptions } from './types-
|
|
2
|
-
export { D as DetectedField, F as FieldFillData, b as GhostFillAIRequest, c as GhostFillPromptField, d as PROVIDERS } from './types-
|
|
1
|
+
import { G as GhostFillAIOptions, P as Provider, a as GhostFillOptions } from './types-BYkjRHCV.mjs';
|
|
2
|
+
export { D as DetectedField, F as FieldFillData, b as GhostFillAIRequest, c as GhostFillPromptField, d as PROVIDERS } from './types-BYkjRHCV.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Initialize GhostFill — adds a floating ghost icon to the page.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as GhostFillAIOptions, P as Provider, a as GhostFillOptions } from './types-
|
|
2
|
-
export { D as DetectedField, F as FieldFillData, b as GhostFillAIRequest, c as GhostFillPromptField, d as PROVIDERS } from './types-
|
|
1
|
+
import { G as GhostFillAIOptions, P as Provider, a as GhostFillOptions } from './types-BYkjRHCV.js';
|
|
2
|
+
export { D as DetectedField, F as FieldFillData, b as GhostFillAIRequest, c as GhostFillPromptField, d as PROVIDERS } from './types-BYkjRHCV.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Initialize GhostFill — adds a floating ghost icon to the page.
|
package/dist/index.js
CHANGED
|
@@ -214,8 +214,8 @@ function generateForField(field, context) {
|
|
|
214
214
|
return `https://www.${context.company.toLowerCase().replace(/\s+/g, "")}.com`;
|
|
215
215
|
}
|
|
216
216
|
if (field.type === "number" || field.type === "range") {
|
|
217
|
-
const min = field.min ? parseInt(field.min) : 1;
|
|
218
|
-
const max = field.max ? parseInt(field.max) : 100;
|
|
217
|
+
const min = field.min ? parseInt(field.min, 10) || 1 : 1;
|
|
218
|
+
const max = field.max ? parseInt(field.max, 10) || 100 : 100;
|
|
219
219
|
return String(randInt(min, max));
|
|
220
220
|
}
|
|
221
221
|
if (label.includes("first name") || label.includes("firstname")) return context.firstName;
|
|
@@ -275,14 +275,96 @@ function focusBlur(el) {
|
|
|
275
275
|
}
|
|
276
276
|
async function fillCustomSelect(button, value) {
|
|
277
277
|
button.click();
|
|
278
|
-
|
|
278
|
+
let waited = 0;
|
|
279
|
+
const step = 50;
|
|
280
|
+
while (waited < 500) {
|
|
281
|
+
await new Promise((r) => setTimeout(r, step));
|
|
282
|
+
waited += step;
|
|
283
|
+
const lb = button.getAttribute("aria-controls") ? document.getElementById(button.getAttribute("aria-controls")) : button.parentElement?.querySelector("[role=listbox]") || document.querySelector("[role=listbox]");
|
|
284
|
+
if (lb) break;
|
|
285
|
+
const cont = button.closest("[class*='relative']") || button.parentElement;
|
|
286
|
+
if (cont) {
|
|
287
|
+
const divs = cont.querySelectorAll("div");
|
|
288
|
+
for (const d of divs) {
|
|
289
|
+
if (d === button || d.contains(button) || button.contains(d)) continue;
|
|
290
|
+
if (d.classList.toString().includes("absolute") || d.classList.toString().includes("z-50")) {
|
|
291
|
+
waited = 999;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
279
297
|
const listboxId = button.getAttribute("aria-controls");
|
|
280
298
|
let listbox = listboxId ? document.getElementById(listboxId) : null;
|
|
281
299
|
if (!listbox) {
|
|
282
|
-
listbox = button.parentElement?.querySelector("[role=listbox]") ||
|
|
300
|
+
listbox = button.parentElement?.querySelector("[role=listbox]") || null;
|
|
301
|
+
}
|
|
302
|
+
if (!listbox) {
|
|
303
|
+
const all = document.querySelectorAll("[role=listbox]");
|
|
304
|
+
for (const lb of all) {
|
|
305
|
+
if (lb.offsetParent !== null || lb.offsetHeight > 0) {
|
|
306
|
+
listbox = lb;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
let options = listbox ? listbox.querySelectorAll("[role=option]") : [];
|
|
312
|
+
if (!listbox || options.length === 0) {
|
|
313
|
+
let panel = null;
|
|
314
|
+
const container = button.closest("[class*='relative']") || button.parentElement;
|
|
315
|
+
if (container) {
|
|
316
|
+
for (const child of Array.from(container.children)) {
|
|
317
|
+
if (child === button || child.contains(button)) continue;
|
|
318
|
+
if (child.tagName === "LABEL" || child.tagName === "P" || child.tagName === "SPAN") continue;
|
|
319
|
+
const el = child;
|
|
320
|
+
const style = window.getComputedStyle(el);
|
|
321
|
+
if (el.classList.contains("absolute") || el.classList.contains("z-50") || style.position === "absolute" || style.position === "fixed") {
|
|
322
|
+
panel = el;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (!panel) {
|
|
328
|
+
let sibling = button.nextElementSibling;
|
|
329
|
+
while (sibling) {
|
|
330
|
+
const tag = sibling.tagName;
|
|
331
|
+
if (tag !== "P" && tag !== "LABEL" && tag !== "SPAN" && tag === "DIV") {
|
|
332
|
+
panel = sibling;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
sibling = sibling.nextElementSibling;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (panel && panel !== button) {
|
|
339
|
+
let contentWait = 0;
|
|
340
|
+
while (contentWait < 1e3) {
|
|
341
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
342
|
+
contentWait += 100;
|
|
343
|
+
const btns = panel.querySelectorAll("button");
|
|
344
|
+
let realBtns = 0;
|
|
345
|
+
for (const b of btns) {
|
|
346
|
+
if (!b.querySelector("input")) realBtns++;
|
|
347
|
+
}
|
|
348
|
+
if (realBtns > 0) break;
|
|
349
|
+
}
|
|
350
|
+
const clickables = panel.querySelectorAll("button, [role=option], [data-value], li");
|
|
351
|
+
const filtered = [];
|
|
352
|
+
for (const c of clickables) {
|
|
353
|
+
const text = c.textContent?.trim() || "";
|
|
354
|
+
if (!text || text === "\xD7" || text === "\u2715") continue;
|
|
355
|
+
if (c.querySelector("input")) continue;
|
|
356
|
+
if (c === button) continue;
|
|
357
|
+
const hasInput = c.querySelector("input[type='text'], input[type='search']");
|
|
358
|
+
if (hasInput) continue;
|
|
359
|
+
filtered.push(c);
|
|
360
|
+
}
|
|
361
|
+
if (filtered.length > 0) {
|
|
362
|
+
options = filtered;
|
|
363
|
+
listbox = panel;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
283
366
|
}
|
|
284
|
-
if (
|
|
285
|
-
const options = listbox.querySelectorAll("[role=option]");
|
|
367
|
+
if (options.length > 0) {
|
|
286
368
|
for (const opt of options) {
|
|
287
369
|
const text = opt.textContent?.trim();
|
|
288
370
|
if (text === value || text?.toLowerCase() === value.toLowerCase()) {
|
|
@@ -301,7 +383,7 @@ async function fillCustomSelect(button, value) {
|
|
|
301
383
|
}
|
|
302
384
|
for (const opt of options) {
|
|
303
385
|
const text = (opt.textContent?.trim() || "").toLowerCase();
|
|
304
|
-
const isPlaceholder = text.startsWith("select") || text === "" || text === "---" || text === "choose" || text.startsWith("choose");
|
|
386
|
+
const isPlaceholder = text.startsWith("select") || text === "" || text === "---" || text === "choose" || text.startsWith("choose") || text.startsWith("search");
|
|
305
387
|
if (!isPlaceholder) {
|
|
306
388
|
opt.click();
|
|
307
389
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -354,7 +436,7 @@ async function fillFields(fields, fillData) {
|
|
|
354
436
|
`input[type="radio"][name="${CSS.escape(radio.name)}"]`
|
|
355
437
|
);
|
|
356
438
|
for (const r of group) {
|
|
357
|
-
if (r.value === item.value ||
|
|
439
|
+
if (r.value === item.value || findLabel(r) === item.value) {
|
|
358
440
|
const nativeCheckedSetter = Object.getOwnPropertyDescriptor(
|
|
359
441
|
HTMLInputElement.prototype,
|
|
360
442
|
"checked"
|
|
@@ -423,26 +505,11 @@ async function fillFields(fields, fillData) {
|
|
|
423
505
|
}
|
|
424
506
|
return { filled, errors };
|
|
425
507
|
}
|
|
426
|
-
function findLabel2(el) {
|
|
427
|
-
if (el.id) {
|
|
428
|
-
const label = document.querySelector(
|
|
429
|
-
`label[for="${CSS.escape(el.id)}"]`
|
|
430
|
-
);
|
|
431
|
-
if (label?.textContent?.trim()) return label.textContent.trim();
|
|
432
|
-
}
|
|
433
|
-
const parent = el.closest("label");
|
|
434
|
-
if (parent) {
|
|
435
|
-
const clone = parent.cloneNode(true);
|
|
436
|
-
clone.querySelectorAll("input").forEach((c) => c.remove());
|
|
437
|
-
return clone.textContent?.trim() || "";
|
|
438
|
-
}
|
|
439
|
-
return "";
|
|
440
|
-
}
|
|
441
508
|
|
|
442
509
|
// src/selector.ts
|
|
443
510
|
var currentHighlight = null;
|
|
444
511
|
var overlay = null;
|
|
445
|
-
function
|
|
512
|
+
function createSelectorOverlay(color) {
|
|
446
513
|
const div = document.createElement("div");
|
|
447
514
|
div.id = "ghostfill-selector-overlay";
|
|
448
515
|
const r = parseInt(color.slice(1, 3), 16);
|
|
@@ -461,7 +528,7 @@ function createOverlay(color) {
|
|
|
461
528
|
return div;
|
|
462
529
|
}
|
|
463
530
|
function positionOverlay(el, color) {
|
|
464
|
-
if (!overlay) overlay =
|
|
531
|
+
if (!overlay) overlay = createSelectorOverlay(color);
|
|
465
532
|
const rect = el.getBoundingClientRect();
|
|
466
533
|
Object.assign(overlay.style, {
|
|
467
534
|
top: `${rect.top}px`,
|
|
@@ -533,6 +600,9 @@ function startSelection(onSelect, onCancel, ghostfillRoot, highlightColor = "#63
|
|
|
533
600
|
return cleanup;
|
|
534
601
|
}
|
|
535
602
|
|
|
603
|
+
// package.json
|
|
604
|
+
var version = "0.2.4";
|
|
605
|
+
|
|
536
606
|
// src/overlay.ts
|
|
537
607
|
var STORAGE_KEY = "ghostfill_settings";
|
|
538
608
|
var POS_KEY = "ghostfill_pos";
|
|
@@ -757,7 +827,7 @@ var CSS2 = `
|
|
|
757
827
|
font-size: 13px; font-weight: 600; color: #fff;
|
|
758
828
|
letter-spacing: -0.0094em;
|
|
759
829
|
}
|
|
760
|
-
.gf-pop-header .gf-slash { color:
|
|
830
|
+
.gf-pop-header .gf-slash { color: #6366f1; }
|
|
761
831
|
.gf-pop-header .gf-header-right {
|
|
762
832
|
display: flex; align-items: center; gap: 6px;
|
|
763
833
|
}
|
|
@@ -1022,7 +1092,7 @@ var CSS2 = `
|
|
|
1022
1092
|
line-height: 1.5;
|
|
1023
1093
|
}
|
|
1024
1094
|
`;
|
|
1025
|
-
function
|
|
1095
|
+
function createOverlay(options) {
|
|
1026
1096
|
const aiConfig = options.ai || null;
|
|
1027
1097
|
const saved = loadSettings(aiConfig?.provider || "openai");
|
|
1028
1098
|
if (options.apiKey) {
|
|
@@ -1043,9 +1113,7 @@ function createOverlay2(options) {
|
|
|
1043
1113
|
active: false,
|
|
1044
1114
|
selecting: false,
|
|
1045
1115
|
selectedBlock: null,
|
|
1046
|
-
fields: []
|
|
1047
|
-
overlay: host,
|
|
1048
|
-
shadowRoot: shadow
|
|
1116
|
+
fields: []
|
|
1049
1117
|
};
|
|
1050
1118
|
const bar = document.createElement("div");
|
|
1051
1119
|
bar.className = "gf-bar";
|
|
@@ -1192,7 +1260,7 @@ function createOverlay2(options) {
|
|
|
1192
1260
|
<div class="gf-pop-header">
|
|
1193
1261
|
<h3><span class="gf-slash">/</span>ghostfill</h3>
|
|
1194
1262
|
<div class="gf-header-right">
|
|
1195
|
-
<span class="gf-version">
|
|
1263
|
+
<span class="gf-version">v${version}</span>
|
|
1196
1264
|
<button class="gf-theme-btn" id="gf-s-theme" title="Toggle theme">
|
|
1197
1265
|
${saved.theme === "dark" ? ICONS.sun : ICONS.moon}
|
|
1198
1266
|
</button>
|
|
@@ -2122,7 +2190,7 @@ function init(options = {}) {
|
|
|
2122
2190
|
instance.destroy();
|
|
2123
2191
|
instance = null;
|
|
2124
2192
|
}
|
|
2125
|
-
const { state, destroy } =
|
|
2193
|
+
const { state, destroy } = createOverlay(options);
|
|
2126
2194
|
instance = { destroy };
|
|
2127
2195
|
return { destroy };
|
|
2128
2196
|
}
|