@ai11y/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +87 -0
- package/CHANGELOG.md +29 -0
- package/README.md +37 -0
- package/dist/agent/agent-adapter.d.mts +17 -0
- package/dist/agent/agent-adapter.d.mts.map +1 -0
- package/dist/agent/agent-adapter.mjs +45 -0
- package/dist/agent/agent-adapter.mjs.map +1 -0
- package/dist/agent/llm-agent.d.mts +13 -0
- package/dist/agent/llm-agent.d.mts.map +1 -0
- package/dist/agent/llm-agent.mjs +41 -0
- package/dist/agent/llm-agent.mjs.map +1 -0
- package/dist/agent/plan.d.mts +27 -0
- package/dist/agent/plan.d.mts.map +1 -0
- package/dist/agent/plan.mjs +28 -0
- package/dist/agent/plan.mjs.map +1 -0
- package/dist/agent/rule-based-agent.d.mts +13 -0
- package/dist/agent/rule-based-agent.d.mts.map +1 -0
- package/dist/agent/rule-based-agent.mjs +152 -0
- package/dist/agent/rule-based-agent.mjs.map +1 -0
- package/dist/agent/tool-contract.d.mts +19 -0
- package/dist/agent/tool-contract.d.mts.map +1 -0
- package/dist/agent/types.d.mts +77 -0
- package/dist/agent/types.d.mts.map +1 -0
- package/dist/client-api.d.mts +49 -0
- package/dist/client-api.d.mts.map +1 -0
- package/dist/client-api.mjs +68 -0
- package/dist/client-api.mjs.map +1 -0
- package/dist/context.d.mts +29 -0
- package/dist/context.d.mts.map +1 -0
- package/dist/dom-actions/click.d.mts +15 -0
- package/dist/dom-actions/click.d.mts.map +1 -0
- package/dist/dom-actions/click.mjs +36 -0
- package/dist/dom-actions/click.mjs.map +1 -0
- package/dist/dom-actions/fill-input.d.mts +17 -0
- package/dist/dom-actions/fill-input.d.mts.map +1 -0
- package/dist/dom-actions/fill-input.mjs +69 -0
- package/dist/dom-actions/fill-input.mjs.map +1 -0
- package/dist/dom-actions/find-element.mjs +17 -0
- package/dist/dom-actions/find-element.mjs.map +1 -0
- package/dist/dom-actions/highlight.d.mts +34 -0
- package/dist/dom-actions/highlight.d.mts.map +1 -0
- package/dist/dom-actions/highlight.mjs +60 -0
- package/dist/dom-actions/highlight.mjs.map +1 -0
- package/dist/dom-actions/navigate.d.mts +16 -0
- package/dist/dom-actions/navigate.d.mts.map +1 -0
- package/dist/dom-actions/navigate.mjs +22 -0
- package/dist/dom-actions/navigate.mjs.map +1 -0
- package/dist/dom-actions/scroll.d.mts +16 -0
- package/dist/dom-actions/scroll.d.mts.map +1 -0
- package/dist/dom-actions/scroll.mjs +32 -0
- package/dist/dom-actions/scroll.mjs.map +1 -0
- package/dist/dom.d.mts +23 -0
- package/dist/dom.d.mts.map +1 -0
- package/dist/dom.mjs +60 -0
- package/dist/dom.mjs.map +1 -0
- package/dist/events.d.mts +35 -0
- package/dist/events.d.mts.map +1 -0
- package/dist/events.mjs +49 -0
- package/dist/events.mjs.map +1 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +18 -0
- package/dist/instruction.d.mts +21 -0
- package/dist/instruction.d.mts.map +1 -0
- package/dist/marker.d.mts +35 -0
- package/dist/marker.d.mts.map +1 -0
- package/dist/marker.mjs +137 -0
- package/dist/marker.mjs.map +1 -0
- package/dist/store.d.mts +56 -0
- package/dist/store.d.mts.map +1 -0
- package/dist/store.mjs +114 -0
- package/dist/store.mjs.map +1 -0
- package/dist/util/attributes.d.mts +57 -0
- package/dist/util/attributes.d.mts.map +1 -0
- package/dist/util/attributes.mjs +68 -0
- package/dist/util/attributes.mjs.map +1 -0
- package/dist/util/format.d.mts +18 -0
- package/dist/util/format.d.mts.map +1 -0
- package/dist/util/format.mjs +21 -0
- package/dist/util/format.mjs.map +1 -0
- package/package.json +26 -0
- package/src/agent/agent-adapter.ts +75 -0
- package/src/agent/index.ts +21 -0
- package/src/agent/llm-agent.ts +64 -0
- package/src/agent/plan.ts +41 -0
- package/src/agent/rule-based-agent.ts +269 -0
- package/src/agent/tool-contract.ts +22 -0
- package/src/agent/types.ts +83 -0
- package/src/client-api.ts +107 -0
- package/src/context.ts +28 -0
- package/src/dom-actions/click.ts +39 -0
- package/src/dom-actions/fill-input.ts +113 -0
- package/src/dom-actions/find-element.ts +14 -0
- package/src/dom-actions/highlight.ts +93 -0
- package/src/dom-actions/index.ts +5 -0
- package/src/dom-actions/navigate.ts +17 -0
- package/src/dom-actions/scroll.ts +29 -0
- package/src/dom.ts +89 -0
- package/src/events.ts +55 -0
- package/src/index.ts +55 -0
- package/src/instruction.ts +6 -0
- package/src/marker.ts +237 -0
- package/src/store.ts +138 -0
- package/src/util/attributes.ts +68 -0
- package/src/util/format.ts +16 -0
- package/src/util/index.ts +2 -0
- package/tsconfig.json +18 -0
- package/tsdown.config.ts +10 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ai11y/core
|
|
3
|
+
*
|
|
4
|
+
* Core types and utilities shared across all ai11y packages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
type AgentAdapterConfig,
|
|
9
|
+
type AgentConfig,
|
|
10
|
+
type AgentMode,
|
|
11
|
+
type AgentRequest,
|
|
12
|
+
type AgentResponse,
|
|
13
|
+
type ConversationMessage,
|
|
14
|
+
type LLMAgentConfig,
|
|
15
|
+
runAgentAdapter,
|
|
16
|
+
runLLMAgent,
|
|
17
|
+
runRuleBasedAgent,
|
|
18
|
+
} from "./agent/index.js";
|
|
19
|
+
export { plan } from "./agent/plan.js";
|
|
20
|
+
export type { ToolDefinition, ToolExecutor } from "./agent/tool-contract.js";
|
|
21
|
+
export { type Ai11yClient, createClient } from "./client-api.js";
|
|
22
|
+
export type {
|
|
23
|
+
Ai11yContext,
|
|
24
|
+
Ai11yError,
|
|
25
|
+
Ai11yEvent,
|
|
26
|
+
Ai11yState,
|
|
27
|
+
} from "./context.js";
|
|
28
|
+
export { getContext } from "./dom.js";
|
|
29
|
+
export {
|
|
30
|
+
clickMarker,
|
|
31
|
+
fillInputMarker,
|
|
32
|
+
type HighlightOptions,
|
|
33
|
+
highlightMarker,
|
|
34
|
+
navigateToRoute,
|
|
35
|
+
scrollToMarker,
|
|
36
|
+
} from "./dom-actions/index.js";
|
|
37
|
+
export { getSubscriberCount, notify, subscribe } from "./events.js";
|
|
38
|
+
export type { Instruction } from "./instruction.js";
|
|
39
|
+
export type { Marker } from "./marker.js";
|
|
40
|
+
export { getMarkers } from "./marker.js";
|
|
41
|
+
export {
|
|
42
|
+
clearContext,
|
|
43
|
+
clearEvents,
|
|
44
|
+
clearState,
|
|
45
|
+
getError,
|
|
46
|
+
getEvents,
|
|
47
|
+
getRoute,
|
|
48
|
+
getState,
|
|
49
|
+
setError,
|
|
50
|
+
setRoute,
|
|
51
|
+
setState,
|
|
52
|
+
subscribeToStore,
|
|
53
|
+
track,
|
|
54
|
+
} from "./store.js";
|
|
55
|
+
export * from "./util/index.js";
|
package/src/marker.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ATTRIBUTE_SENSITIVE,
|
|
3
|
+
getAllMarkersSelector,
|
|
4
|
+
getMarkerId,
|
|
5
|
+
getMarkerIntent,
|
|
6
|
+
getMarkerLabel,
|
|
7
|
+
} from "./util/attributes.js";
|
|
8
|
+
import { formatMarkerId } from "./util/format.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Marker information for UI elements
|
|
12
|
+
*/
|
|
13
|
+
export interface Marker {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
intent: string;
|
|
17
|
+
elementType: string;
|
|
18
|
+
/** Current value for input/textarea elements */
|
|
19
|
+
value?: string;
|
|
20
|
+
/** Selected value(s) for select elements */
|
|
21
|
+
selectedOptions?: string[];
|
|
22
|
+
/** All available options for select elements */
|
|
23
|
+
options?: Array<{ value: string; label: string }>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Gets the document body in a type-safe way
|
|
28
|
+
* Returns null if not in a browser environment
|
|
29
|
+
*/
|
|
30
|
+
function getDocumentBody(): Element | null {
|
|
31
|
+
if (typeof document === "undefined") {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return document.body;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Finds an input or textarea element within a marked element
|
|
39
|
+
* Handles both direct input elements and nested inputs (when Mark wraps the input)
|
|
40
|
+
*/
|
|
41
|
+
function findInputElement(
|
|
42
|
+
element: Element,
|
|
43
|
+
): HTMLInputElement | HTMLTextAreaElement | null {
|
|
44
|
+
if (
|
|
45
|
+
element instanceof HTMLInputElement ||
|
|
46
|
+
element instanceof HTMLTextAreaElement
|
|
47
|
+
) {
|
|
48
|
+
return element;
|
|
49
|
+
}
|
|
50
|
+
if (element instanceof HTMLElement) {
|
|
51
|
+
const nestedInput = element.querySelector("input, textarea");
|
|
52
|
+
if (
|
|
53
|
+
nestedInput instanceof HTMLInputElement ||
|
|
54
|
+
nestedInput instanceof HTMLTextAreaElement
|
|
55
|
+
) {
|
|
56
|
+
return nestedInput;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Finds a select element within a marked element
|
|
64
|
+
* Handles both direct select elements and nested selects (when Mark wraps the select)
|
|
65
|
+
*/
|
|
66
|
+
function findSelectElement(element: Element): HTMLSelectElement | null {
|
|
67
|
+
if (element instanceof HTMLSelectElement) {
|
|
68
|
+
return element;
|
|
69
|
+
}
|
|
70
|
+
if (element instanceof HTMLElement) {
|
|
71
|
+
const nestedSelect = element.querySelector("select");
|
|
72
|
+
if (nestedSelect instanceof HTMLSelectElement) {
|
|
73
|
+
return nestedSelect;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Checks if an input element contains sensitive data that should be redacted
|
|
81
|
+
* from the UI context (passwords, credit cards, hidden fields, etc.)
|
|
82
|
+
*
|
|
83
|
+
* @param inputElement - The input or textarea element to check
|
|
84
|
+
* @returns True if the input contains sensitive data
|
|
85
|
+
*/
|
|
86
|
+
function isSensitiveInput(
|
|
87
|
+
inputElement: HTMLInputElement | HTMLTextAreaElement,
|
|
88
|
+
): boolean {
|
|
89
|
+
// Check input type
|
|
90
|
+
if (inputElement instanceof HTMLInputElement) {
|
|
91
|
+
const type = inputElement.type.toLowerCase();
|
|
92
|
+
if (type === "password" || type === "hidden") {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check autocomplete attribute for sensitive patterns
|
|
98
|
+
const autocomplete = inputElement.getAttribute("autocomplete")?.toLowerCase();
|
|
99
|
+
if (autocomplete) {
|
|
100
|
+
const sensitiveAutocompletePaths = [
|
|
101
|
+
"current-password",
|
|
102
|
+
"new-password",
|
|
103
|
+
"cc-number",
|
|
104
|
+
"cc-csc",
|
|
105
|
+
"cc-exp",
|
|
106
|
+
"cc-exp-month",
|
|
107
|
+
"cc-exp-year",
|
|
108
|
+
];
|
|
109
|
+
if (
|
|
110
|
+
sensitiveAutocompletePaths.some((path) => autocomplete.includes(path))
|
|
111
|
+
) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for custom sensitive marker attribute
|
|
117
|
+
if (inputElement.getAttribute(ATTRIBUTE_SENSITIVE) === "true") {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check parent element for sensitive marker attribute
|
|
122
|
+
const parent = inputElement.parentElement;
|
|
123
|
+
if (parent && parent.getAttribute(ATTRIBUTE_SENSITIVE) === "true") {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extracts the value from an input or textarea element
|
|
132
|
+
* Redacts sensitive values (passwords, credit cards, etc.) for privacy
|
|
133
|
+
*/
|
|
134
|
+
function extractInputValue(element: Element): string | undefined {
|
|
135
|
+
const inputElement = findInputElement(element);
|
|
136
|
+
if (!inputElement) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
if (isSensitiveInput(inputElement)) {
|
|
140
|
+
return "[REDACTED]";
|
|
141
|
+
}
|
|
142
|
+
return inputElement.value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Extracts options and selected values from a select element
|
|
147
|
+
*/
|
|
148
|
+
function extractSelectData(element: Element): {
|
|
149
|
+
options?: Array<{ value: string; label: string }>;
|
|
150
|
+
selectedOptions?: string[];
|
|
151
|
+
} {
|
|
152
|
+
const selectElement = findSelectElement(element);
|
|
153
|
+
if (!selectElement) {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const options: Array<{ value: string; label: string }> = [];
|
|
158
|
+
for (let i = 0; i < selectElement.options.length; i++) {
|
|
159
|
+
const option = selectElement.options[i];
|
|
160
|
+
options.push({
|
|
161
|
+
value: option.value,
|
|
162
|
+
label: option.text,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const selectedOptions: string[] = [];
|
|
167
|
+
if (selectElement.multiple) {
|
|
168
|
+
for (let i = 0; i < selectElement.selectedOptions.length; i++) {
|
|
169
|
+
selectedOptions.push(selectElement.selectedOptions[i].value);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
if (selectElement.value) {
|
|
173
|
+
selectedOptions.push(selectElement.value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
options: options.length > 0 ? options : undefined,
|
|
179
|
+
selectedOptions: selectedOptions.length > 0 ? selectedOptions : undefined,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Scans the DOM for elements with data-ai-* attributes and returns markers
|
|
185
|
+
*
|
|
186
|
+
* @param root - Optional DOM root element to scan (defaults to document.body)
|
|
187
|
+
* @returns Array of Marker objects found in the DOM
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* const markers = getMarkers();
|
|
192
|
+
* // Scans document.body for data-ai-* attributes
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
export function getMarkers(root?: Element): Marker[] {
|
|
196
|
+
const scanRoot = root ?? getDocumentBody();
|
|
197
|
+
|
|
198
|
+
if (!scanRoot) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const elements = scanRoot.querySelectorAll(getAllMarkersSelector());
|
|
203
|
+
const markers: Marker[] = [];
|
|
204
|
+
for (let i = 0; i < elements.length; i++) {
|
|
205
|
+
const element = elements[i];
|
|
206
|
+
const id = getMarkerId(element);
|
|
207
|
+
if (!id) continue;
|
|
208
|
+
|
|
209
|
+
const label = getMarkerLabel(element) || formatMarkerId(id);
|
|
210
|
+
const intent = getMarkerIntent(element) || "";
|
|
211
|
+
const elementType = element.tagName.toLowerCase();
|
|
212
|
+
|
|
213
|
+
const marker: Marker = {
|
|
214
|
+
id,
|
|
215
|
+
label,
|
|
216
|
+
intent,
|
|
217
|
+
elementType,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const inputValue = extractInputValue(element);
|
|
221
|
+
if (inputValue !== undefined) {
|
|
222
|
+
marker.value = inputValue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const selectData = extractSelectData(element);
|
|
226
|
+
if (selectData.options !== undefined) {
|
|
227
|
+
marker.options = selectData.options;
|
|
228
|
+
}
|
|
229
|
+
if (selectData.selectedOptions !== undefined) {
|
|
230
|
+
marker.selectedOptions = selectData.selectedOptions;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
markers.push(marker);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return markers;
|
|
237
|
+
}
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { Ai11yError, Ai11yEvent, Ai11yState } from "./context.js";
|
|
2
|
+
import { notify } from "./events.js";
|
|
3
|
+
|
|
4
|
+
let route: string | undefined;
|
|
5
|
+
let state: Ai11yState | undefined;
|
|
6
|
+
let error: Ai11yError | null | undefined;
|
|
7
|
+
let events: Ai11yEvent[] = [];
|
|
8
|
+
|
|
9
|
+
type StoreChangeListener = (
|
|
10
|
+
type: "route" | "state" | "error",
|
|
11
|
+
value: unknown,
|
|
12
|
+
) => void;
|
|
13
|
+
|
|
14
|
+
const storeListeners = new Set<StoreChangeListener>();
|
|
15
|
+
|
|
16
|
+
function notifyStoreChange(
|
|
17
|
+
type: "route" | "state" | "error",
|
|
18
|
+
value: unknown,
|
|
19
|
+
): void {
|
|
20
|
+
for (const listener of storeListeners) {
|
|
21
|
+
try {
|
|
22
|
+
listener(type, value);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
// Don't let one listener's error break others
|
|
25
|
+
console.error("Error in store change listener:", err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function setRoute(newRoute: string | undefined): void {
|
|
31
|
+
route = newRoute;
|
|
32
|
+
notifyStoreChange("route", newRoute);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getRoute(): string | undefined {
|
|
36
|
+
return route;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Merges new state with existing state (similar to React's setState)
|
|
41
|
+
* Pass undefined to clear all state
|
|
42
|
+
*
|
|
43
|
+
* @param newState - Partial state to merge, or undefined to clear
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* setState({ userId: '123' }); // Merges with existing state
|
|
48
|
+
* setState({ theme: 'dark' }); // Adds to state, keeps userId
|
|
49
|
+
* setState(undefined); // Clears all state
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function setState(newState: Ai11yState | undefined): void {
|
|
53
|
+
if (newState === undefined) {
|
|
54
|
+
state = undefined;
|
|
55
|
+
} else {
|
|
56
|
+
state = { ...(state || {}), ...newState };
|
|
57
|
+
}
|
|
58
|
+
notifyStoreChange("state", state);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Clears all application state
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* clearState(); // Resets state to empty
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function clearState(): void {
|
|
70
|
+
state = undefined;
|
|
71
|
+
notifyStoreChange("state", undefined);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getState(): Ai11yState | undefined {
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function setError(newError: Ai11yError | null | undefined): void {
|
|
79
|
+
error = newError;
|
|
80
|
+
notifyStoreChange("error", newError);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getError(): Ai11yError | null | undefined {
|
|
84
|
+
return error;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function track(event: string, payload?: unknown): void {
|
|
88
|
+
events = [
|
|
89
|
+
...events.slice(-49), // Keep last 50 events
|
|
90
|
+
{
|
|
91
|
+
type: event,
|
|
92
|
+
payload,
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
notify();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getEvents(): Ai11yEvent[] {
|
|
100
|
+
return events;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function clearEvents(): void {
|
|
104
|
+
events = [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Subscribe to store changes (route, state, error)
|
|
109
|
+
* Returns an unsubscribe function
|
|
110
|
+
*
|
|
111
|
+
* @param listener - Function called when route, state, or error changes
|
|
112
|
+
* @returns Unsubscribe function
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const unsubscribe = subscribeToStore((type, value) => {
|
|
117
|
+
* if (type === 'route') console.log('Route changed:', value);
|
|
118
|
+
* });
|
|
119
|
+
* // Later...
|
|
120
|
+
* unsubscribe();
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export function subscribeToStore(listener: StoreChangeListener): () => void {
|
|
124
|
+
storeListeners.add(listener);
|
|
125
|
+
return () => {
|
|
126
|
+
storeListeners.delete(listener);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function clearContext(): void {
|
|
131
|
+
route = undefined;
|
|
132
|
+
state = undefined;
|
|
133
|
+
error = undefined;
|
|
134
|
+
events = [];
|
|
135
|
+
notifyStoreChange("route", undefined);
|
|
136
|
+
notifyStoreChange("state", undefined);
|
|
137
|
+
notifyStoreChange("error", undefined);
|
|
138
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attribute name constants for data-ai-* attributes
|
|
3
|
+
*/
|
|
4
|
+
export const ATTRIBUTE_ID = "data-ai-id";
|
|
5
|
+
export const ATTRIBUTE_LABEL = "data-ai-label";
|
|
6
|
+
export const ATTRIBUTE_INTENT = "data-ai-intent";
|
|
7
|
+
export const ATTRIBUTE_SENSITIVE = "data-ai-sensitive";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Gets the value of the data-ai-id attribute from an element
|
|
11
|
+
*
|
|
12
|
+
* @param element - The element to get the attribute from
|
|
13
|
+
* @returns The marker ID or null if not found
|
|
14
|
+
*/
|
|
15
|
+
export function getMarkerId(element: Element): string | null {
|
|
16
|
+
return element.getAttribute(ATTRIBUTE_ID);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets the value of the data-ai-label attribute from an element
|
|
21
|
+
*
|
|
22
|
+
* @param element - The element to get the attribute from
|
|
23
|
+
* @returns The marker label or null if not found
|
|
24
|
+
*/
|
|
25
|
+
export function getMarkerLabel(element: Element): string | null {
|
|
26
|
+
return element.getAttribute(ATTRIBUTE_LABEL);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gets the value of the data-ai-intent attribute from an element
|
|
31
|
+
*
|
|
32
|
+
* @param element - The element to get the attribute from
|
|
33
|
+
* @returns The marker intent or null if not found
|
|
34
|
+
*/
|
|
35
|
+
export function getMarkerIntent(element: Element): string | null {
|
|
36
|
+
return element.getAttribute(ATTRIBUTE_INTENT);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a query selector for finding an element by marker ID
|
|
41
|
+
*
|
|
42
|
+
* @param markerId - The marker ID to search for
|
|
43
|
+
* @returns A query selector string
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const selector = getMarkerSelector('connect_stripe');
|
|
48
|
+
* // Returns: '[data-ai-id="connect_stripe"]'
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function getMarkerSelector(markerId: string): string {
|
|
52
|
+
return `[${ATTRIBUTE_ID}="${markerId}"]`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates a query selector for finding all elements with marker IDs
|
|
57
|
+
*
|
|
58
|
+
* @returns A query selector string
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const selector = getAllMarkersSelector();
|
|
63
|
+
* // Returns: '[data-ai-id]'
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function getAllMarkersSelector(): string {
|
|
67
|
+
return `[${ATTRIBUTE_ID}]`;
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a marker ID into a readable label
|
|
3
|
+
* Converts snake_case to Title Case
|
|
4
|
+
*
|
|
5
|
+
* @param id - The marker ID to format
|
|
6
|
+
* @returns A formatted label
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* formatMarkerId('connect_stripe');
|
|
11
|
+
* // Returns: 'Connect Stripe'
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function formatMarkerId(id: string): string {
|
|
15
|
+
return id.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
|
|
16
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"lib": ["ES2022", "DOM"],
|
|
6
|
+
"moduleResolution": "nodenext",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"rootDir": "src",
|
|
14
|
+
"outDir": "dist"
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|