@pokit/reporter-web 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/dist/hooks.js ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Reporter Web React Hooks
3
+ *
4
+ * React hooks for subscribing to reporter state using useSyncExternalStore.
5
+ * Provides full state subscription and selective subscriptions for individual
6
+ * activities and groups.
7
+ */
8
+ import { useSyncExternalStore, useCallback, useRef } from 'react';
9
+ /**
10
+ * Subscribe to the full reporter state
11
+ *
12
+ * @param store - The reporter store
13
+ * @returns Current reporter state
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * const state = useReporterState(store);
18
+ * return <div>Status: {state.root.status}</div>;
19
+ * ```
20
+ */
21
+ export function useReporterState(store) {
22
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getServerSnapshot);
23
+ }
24
+ /**
25
+ * Subscribe to a single activity by ID
26
+ * Returns undefined if activity doesn't exist
27
+ *
28
+ * @param store - The reporter store
29
+ * @param id - Activity ID to subscribe to
30
+ * @returns Activity state or undefined
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * const activity = useActivity(store, 'task-1');
35
+ * if (!activity) return null;
36
+ * return <div>{activity.label}: {activity.status}</div>;
37
+ * ```
38
+ */
39
+ export function useActivity(store, id) {
40
+ // Track the previous activity reference for shallow comparison
41
+ const prevActivityRef = useRef(undefined);
42
+ const getSnapshot = useCallback(() => {
43
+ const state = store.getSnapshot();
44
+ const activity = state.activities.get(id);
45
+ // Return same reference if activity hasn't changed (shallow comparison)
46
+ if (prevActivityRef.current === activity) {
47
+ return prevActivityRef.current;
48
+ }
49
+ // Check if activity content is the same (for Map recreation scenarios)
50
+ if (prevActivityRef.current &&
51
+ activity &&
52
+ prevActivityRef.current.id === activity.id &&
53
+ prevActivityRef.current.status === activity.status &&
54
+ prevActivityRef.current.progress === activity.progress &&
55
+ prevActivityRef.current.message === activity.message &&
56
+ prevActivityRef.current.justStarted === activity.justStarted &&
57
+ prevActivityRef.current.justCompleted === activity.justCompleted &&
58
+ prevActivityRef.current.justFailed === activity.justFailed &&
59
+ prevActivityRef.current.completedAt === activity.completedAt) {
60
+ return prevActivityRef.current;
61
+ }
62
+ prevActivityRef.current = activity;
63
+ return activity;
64
+ }, [store, id]);
65
+ const getServerSnapshot = useCallback(() => {
66
+ return store.getServerSnapshot().activities.get(id);
67
+ }, [store, id]);
68
+ return useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot);
69
+ }
70
+ /**
71
+ * Subscribe to a single group by ID
72
+ * Returns undefined if group doesn't exist
73
+ *
74
+ * @param store - The reporter store
75
+ * @param id - Group ID to subscribe to
76
+ * @returns Group state or undefined
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * const group = useGroup(store, 'checks');
81
+ * if (!group) return null;
82
+ * return <div>{group.label} ({group.activityIds.length} tasks)</div>;
83
+ * ```
84
+ */
85
+ export function useGroup(store, id) {
86
+ // Track the previous group reference for shallow comparison
87
+ const prevGroupRef = useRef(undefined);
88
+ const getSnapshot = useCallback(() => {
89
+ const state = store.getSnapshot();
90
+ const group = state.groups.get(id);
91
+ // Return same reference if group hasn't changed
92
+ if (prevGroupRef.current === group) {
93
+ return prevGroupRef.current;
94
+ }
95
+ // Check if group content is the same (for Map recreation scenarios)
96
+ if (prevGroupRef.current &&
97
+ group &&
98
+ prevGroupRef.current.id === group.id &&
99
+ prevGroupRef.current.label === group.label &&
100
+ prevGroupRef.current.hasFailure === group.hasFailure &&
101
+ prevGroupRef.current.justStarted_group === group.justStarted_group &&
102
+ prevGroupRef.current.justEnded === group.justEnded &&
103
+ prevGroupRef.current.endedAt === group.endedAt &&
104
+ prevGroupRef.current.activityIds.length === group.activityIds.length &&
105
+ prevGroupRef.current.childGroupIds.length === group.childGroupIds.length) {
106
+ return prevGroupRef.current;
107
+ }
108
+ prevGroupRef.current = group;
109
+ return group;
110
+ }, [store, id]);
111
+ const getServerSnapshot = useCallback(() => {
112
+ return store.getServerSnapshot().groups.get(id);
113
+ }, [store, id]);
114
+ return useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot);
115
+ }
116
+ /**
117
+ * Subscribe to root state only
118
+ * More efficient than useReporterState when you only need root info
119
+ *
120
+ * @param store - The reporter store
121
+ * @returns Root state
122
+ *
123
+ * @example
124
+ * ```tsx
125
+ * const root = useRootState(store);
126
+ * return <div>App: {root.appName} - {root.status}</div>;
127
+ * ```
128
+ */
129
+ export function useRootState(store) {
130
+ const prevRootRef = useRef(undefined);
131
+ const getSnapshot = useCallback(() => {
132
+ const state = store.getSnapshot();
133
+ const root = state.root;
134
+ // Return same reference if root hasn't changed
135
+ if (prevRootRef.current &&
136
+ prevRootRef.current.status === root.status &&
137
+ prevRootRef.current.appName === root.appName &&
138
+ prevRootRef.current.exitCode === root.exitCode) {
139
+ return prevRootRef.current;
140
+ }
141
+ prevRootRef.current = root;
142
+ return root;
143
+ }, [store]);
144
+ const getServerSnapshot = useCallback(() => {
145
+ return store.getServerSnapshot().root;
146
+ }, [store]);
147
+ return useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot);
148
+ }
149
+ /**
150
+ * Subscribe to logs
151
+ * Returns the full log array
152
+ *
153
+ * @param store - The reporter store
154
+ * @returns Array of log entries
155
+ *
156
+ * @example
157
+ * ```tsx
158
+ * const logs = useLogs(store);
159
+ * return (
160
+ * <ul>
161
+ * {logs.map(log => <li key={log.id}>{log.message}</li>)}
162
+ * </ul>
163
+ * );
164
+ * ```
165
+ */
166
+ export function useLogs(store) {
167
+ const prevLogsRef = useRef(undefined);
168
+ const getSnapshot = useCallback(() => {
169
+ const state = store.getSnapshot();
170
+ const logs = state.logs;
171
+ // Return same reference if logs haven't changed
172
+ if (prevLogsRef.current && prevLogsRef.current.length === logs.length) {
173
+ // Quick check - if lengths match and last item is same, assume unchanged
174
+ if (logs.length === 0 ||
175
+ prevLogsRef.current[logs.length - 1]?.id === logs[logs.length - 1]?.id) {
176
+ return prevLogsRef.current;
177
+ }
178
+ }
179
+ prevLogsRef.current = logs;
180
+ return logs;
181
+ }, [store]);
182
+ const getServerSnapshot = useCallback(() => {
183
+ return store.getServerSnapshot().logs;
184
+ }, [store]);
185
+ return useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot);
186
+ }
187
+ /**
188
+ * Subscribe to suspended state
189
+ *
190
+ * @param store - The reporter store
191
+ * @returns Whether reporter is suspended
192
+ */
193
+ export function useSuspended(store) {
194
+ const getSnapshot = useCallback(() => {
195
+ return store.getSnapshot().suspended;
196
+ }, [store]);
197
+ const getServerSnapshot = useCallback(() => {
198
+ return store.getServerSnapshot().suspended;
199
+ }, [store]);
200
+ return useSyncExternalStore(store.subscribe, getSnapshot, getServerSnapshot);
201
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @pokit/reporter-web
3
+ *
4
+ * Web/React implementation of the ReporterAdapter interface.
5
+ * Provides a store for state management and React hooks for subscription.
6
+ */
7
+ export { createReporterStore } from './store';
8
+ export type { CreateReporterStoreOptions, ReporterStoreWithHandler } from './store';
9
+ export { createWebReporterAdapter } from './adapter';
10
+ export { useReporterState, useActivity, useGroup, useRootState, useLogs, useSuspended, } from './hooks';
11
+ export type { RootStatus, ActivityStatus, TemporalMarkers, ActivityState, GroupState, LogEntry, RootState, ReporterState, StateListener, ReporterStore, } from './types';
12
+ export { TutorialStep, FilePreview, CommandBlock, ProgressIndicator, ContentBox, } from './components';
13
+ export type { TutorialStepProps, TutorialStepStatus, FilePreviewProps, FilePreviewStatus, FilePreviewActionProps, CommandBlockProps, CommandBlockStatus, CommandBlockActionProps, ProgressIndicatorProps, ContentBoxProps, ContentBoxVariant, } from './components';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAC9C,YAAY,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAGpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAGrD,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,aAAa,EACb,UAAU,EACV,QAAQ,EACR,SAAS,EACT,aAAa,EACb,aAAa,EACb,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,UAAU,GACX,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,sBAAsB,EACtB,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @pokit/reporter-web
3
+ *
4
+ * Web/React implementation of the ReporterAdapter interface.
5
+ * Provides a store for state management and React hooks for subscription.
6
+ */
7
+ // Store
8
+ export { createReporterStore } from './store';
9
+ // Adapter
10
+ export { createWebReporterAdapter } from './adapter';
11
+ // Hooks
12
+ export { useReporterState, useActivity, useGroup, useRootState, useLogs, useSuspended, } from './hooks';
13
+ // Components
14
+ export { TutorialStep, FilePreview, CommandBlock, ProgressIndicator, ContentBox, } from './components';
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Reporter Web Store
3
+ *
4
+ * Creates an external store for React integration.
5
+ * Handles all CLIEvent types and maintains normalized state.
6
+ */
7
+ import type { CLIEvent } from '@pokit/core';
8
+ import type { ReporterStore } from './types';
9
+ /**
10
+ * Options for creating a reporter store
11
+ */
12
+ export type CreateReporterStoreOptions = {
13
+ /** Custom delay for clearing temporal markers (default: 600ms) */
14
+ temporalMarkerDelay?: number;
15
+ /** Disable temporal marker auto-clearing (useful for testing) */
16
+ disableTemporalMarkerClearing?: boolean;
17
+ };
18
+ /**
19
+ * Type for the store with internal event handler exposed
20
+ */
21
+ export type ReporterStoreWithHandler = ReporterStore & {
22
+ _handleEvent: (event: CLIEvent) => void;
23
+ };
24
+ /**
25
+ * Create a reporter store for React integration
26
+ *
27
+ * @param options - Optional configuration
28
+ * @returns Store compatible with useSyncExternalStore
29
+ */
30
+ export declare function createReporterStore(options?: CreateReporterStoreOptions): ReporterStoreWithHandler;
31
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAuB,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAEV,aAAa,EAKd,MAAM,SAAS,CAAC;AAuBjB;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,kEAAkE;IAClE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,aAAa,GAAG;IACrD,YAAY,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CACzC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,wBAAwB,CAgWlG"}
package/dist/store.js ADDED
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Reporter Web Store
3
+ *
4
+ * Creates an external store for React integration.
5
+ * Handles all CLIEvent types and maintains normalized state.
6
+ */
7
+ /** Default delay for clearing temporal markers (ms) */
8
+ const TEMPORAL_MARKER_DELAY = 600;
9
+ /** Counter for generating unique log IDs */
10
+ let logIdCounter = 0;
11
+ /**
12
+ * Create initial reporter state
13
+ */
14
+ function createInitialState() {
15
+ return {
16
+ root: {
17
+ status: 'idle',
18
+ },
19
+ groups: new Map(),
20
+ activities: new Map(),
21
+ logs: [],
22
+ suspended: false,
23
+ };
24
+ }
25
+ /**
26
+ * Create a reporter store for React integration
27
+ *
28
+ * @param options - Optional configuration
29
+ * @returns Store compatible with useSyncExternalStore
30
+ */
31
+ export function createReporterStore(options) {
32
+ const temporalMarkerDelay = options?.temporalMarkerDelay ?? TEMPORAL_MARKER_DELAY;
33
+ const disableTemporalMarkerClearing = options?.disableTemporalMarkerClearing ?? false;
34
+ let state = createInitialState();
35
+ const listeners = new Set();
36
+ /**
37
+ * Notify all listeners of state change
38
+ */
39
+ function notifyListeners() {
40
+ for (const listener of listeners) {
41
+ listener();
42
+ }
43
+ }
44
+ /**
45
+ * Schedule clearing of temporal markers for an activity
46
+ */
47
+ function scheduleActivityMarkerClear(activityId, markers) {
48
+ if (disableTemporalMarkerClearing)
49
+ return;
50
+ setTimeout(() => {
51
+ const activity = state.activities.get(activityId);
52
+ if (!activity)
53
+ return;
54
+ // Check if any markers are still set
55
+ const hasMarkers = markers.some((marker) => activity[marker]);
56
+ if (!hasMarkers)
57
+ return;
58
+ // Create new state with cleared markers
59
+ const updatedActivity = { ...activity };
60
+ for (const marker of markers) {
61
+ delete updatedActivity[marker];
62
+ }
63
+ const newActivities = new Map(state.activities);
64
+ newActivities.set(activityId, updatedActivity);
65
+ state = {
66
+ ...state,
67
+ activities: newActivities,
68
+ };
69
+ notifyListeners();
70
+ }, temporalMarkerDelay);
71
+ }
72
+ /**
73
+ * Schedule clearing of temporal markers for a group
74
+ */
75
+ function scheduleGroupMarkerClear(groupId, markers) {
76
+ if (disableTemporalMarkerClearing)
77
+ return;
78
+ setTimeout(() => {
79
+ const group = state.groups.get(groupId);
80
+ if (!group)
81
+ return;
82
+ // Check if any markers are still set
83
+ const hasMarkers = markers.some((marker) => group[marker]);
84
+ if (!hasMarkers)
85
+ return;
86
+ // Create new state with cleared markers
87
+ const updatedGroup = { ...group };
88
+ for (const marker of markers) {
89
+ delete updatedGroup[marker];
90
+ }
91
+ const newGroups = new Map(state.groups);
92
+ newGroups.set(groupId, updatedGroup);
93
+ state = {
94
+ ...state,
95
+ groups: newGroups,
96
+ };
97
+ notifyListeners();
98
+ }, temporalMarkerDelay);
99
+ }
100
+ /**
101
+ * Handle a CLI event and update state
102
+ */
103
+ function handleEvent(event) {
104
+ switch (event.type) {
105
+ case 'root:start': {
106
+ state = {
107
+ ...state,
108
+ root: {
109
+ status: 'running',
110
+ appName: event.appName,
111
+ version: event.version,
112
+ startedAt: Date.now(),
113
+ },
114
+ };
115
+ notifyListeners();
116
+ break;
117
+ }
118
+ case 'root:end': {
119
+ state = {
120
+ ...state,
121
+ root: {
122
+ ...state.root,
123
+ status: event.exitCode === 0 ? 'complete' : 'error',
124
+ exitCode: event.exitCode,
125
+ endedAt: Date.now(),
126
+ },
127
+ };
128
+ notifyListeners();
129
+ break;
130
+ }
131
+ case 'group:start': {
132
+ const newGroup = {
133
+ id: event.id,
134
+ parentId: event.parentId,
135
+ label: event.label,
136
+ layout: event.layout,
137
+ activityIds: [],
138
+ childGroupIds: [],
139
+ hasFailure: false,
140
+ startedAt: Date.now(),
141
+ justStarted_group: true,
142
+ };
143
+ const newGroups = new Map(state.groups);
144
+ newGroups.set(event.id, newGroup);
145
+ // Add to parent's childGroupIds if parent exists
146
+ if (event.parentId) {
147
+ const parent = state.groups.get(event.parentId);
148
+ if (parent) {
149
+ newGroups.set(event.parentId, {
150
+ ...parent,
151
+ childGroupIds: [...parent.childGroupIds, event.id],
152
+ });
153
+ }
154
+ }
155
+ state = {
156
+ ...state,
157
+ groups: newGroups,
158
+ };
159
+ notifyListeners();
160
+ scheduleGroupMarkerClear(event.id, ['justStarted_group']);
161
+ break;
162
+ }
163
+ case 'group:end': {
164
+ const group = state.groups.get(event.id);
165
+ if (!group)
166
+ break;
167
+ const newGroups = new Map(state.groups);
168
+ newGroups.set(event.id, {
169
+ ...group,
170
+ endedAt: Date.now(),
171
+ justEnded: true,
172
+ });
173
+ state = {
174
+ ...state,
175
+ groups: newGroups,
176
+ };
177
+ notifyListeners();
178
+ scheduleGroupMarkerClear(event.id, ['justEnded']);
179
+ break;
180
+ }
181
+ case 'activity:start': {
182
+ const newActivity = {
183
+ id: event.id,
184
+ parentId: event.parentId,
185
+ label: event.label,
186
+ status: 'running',
187
+ meta: event.meta,
188
+ startedAt: Date.now(),
189
+ justStarted: true,
190
+ };
191
+ const newActivities = new Map(state.activities);
192
+ newActivities.set(event.id, newActivity);
193
+ // Add to parent group's activityIds if parent is a group
194
+ const newGroups = new Map(state.groups);
195
+ if (event.parentId) {
196
+ const parentGroup = state.groups.get(event.parentId);
197
+ if (parentGroup) {
198
+ newGroups.set(event.parentId, {
199
+ ...parentGroup,
200
+ activityIds: [...parentGroup.activityIds, event.id],
201
+ });
202
+ }
203
+ }
204
+ state = {
205
+ ...state,
206
+ activities: newActivities,
207
+ groups: newGroups,
208
+ };
209
+ notifyListeners();
210
+ scheduleActivityMarkerClear(event.id, ['justStarted']);
211
+ break;
212
+ }
213
+ case 'activity:update': {
214
+ const activity = state.activities.get(event.id);
215
+ if (!activity)
216
+ break;
217
+ const { progress, message, ...rest } = event.payload;
218
+ const newActivities = new Map(state.activities);
219
+ newActivities.set(event.id, {
220
+ ...activity,
221
+ progress: progress ?? activity.progress,
222
+ message: message ?? activity.message,
223
+ payload: {
224
+ ...activity.payload,
225
+ ...rest,
226
+ },
227
+ });
228
+ state = {
229
+ ...state,
230
+ activities: newActivities,
231
+ };
232
+ notifyListeners();
233
+ break;
234
+ }
235
+ case 'activity:success': {
236
+ const activity = state.activities.get(event.id);
237
+ if (!activity)
238
+ break;
239
+ const newActivities = new Map(state.activities);
240
+ newActivities.set(event.id, {
241
+ ...activity,
242
+ status: 'success',
243
+ completedAt: Date.now(),
244
+ justCompleted: true,
245
+ });
246
+ state = {
247
+ ...state,
248
+ activities: newActivities,
249
+ };
250
+ notifyListeners();
251
+ scheduleActivityMarkerClear(event.id, ['justCompleted']);
252
+ break;
253
+ }
254
+ case 'activity:failure': {
255
+ const activity = state.activities.get(event.id);
256
+ if (!activity)
257
+ break;
258
+ const errorMessage = event.error instanceof Error ? event.error.message : String(event.error);
259
+ const newActivities = new Map(state.activities);
260
+ newActivities.set(event.id, {
261
+ ...activity,
262
+ status: 'failure',
263
+ completedAt: Date.now(),
264
+ justFailed: true,
265
+ error: {
266
+ message: errorMessage,
267
+ remediation: event.remediation,
268
+ documentationUrl: event.documentationUrl,
269
+ },
270
+ });
271
+ // Mark parent group as having failure
272
+ const newGroups = new Map(state.groups);
273
+ if (activity.parentId) {
274
+ const parentGroup = state.groups.get(activity.parentId);
275
+ if (parentGroup) {
276
+ newGroups.set(activity.parentId, {
277
+ ...parentGroup,
278
+ hasFailure: true,
279
+ });
280
+ }
281
+ }
282
+ state = {
283
+ ...state,
284
+ activities: newActivities,
285
+ groups: newGroups,
286
+ };
287
+ notifyListeners();
288
+ scheduleActivityMarkerClear(event.id, ['justFailed']);
289
+ break;
290
+ }
291
+ case 'log': {
292
+ const logEntry = {
293
+ id: `log-${++logIdCounter}`,
294
+ activityId: event.activityId,
295
+ level: event.level,
296
+ message: event.message,
297
+ timestamp: Date.now(),
298
+ };
299
+ state = {
300
+ ...state,
301
+ logs: [...state.logs, logEntry],
302
+ };
303
+ notifyListeners();
304
+ break;
305
+ }
306
+ case 'reporter:suspend': {
307
+ state = {
308
+ ...state,
309
+ suspended: true,
310
+ };
311
+ notifyListeners();
312
+ break;
313
+ }
314
+ case 'reporter:resume': {
315
+ state = {
316
+ ...state,
317
+ suspended: false,
318
+ };
319
+ notifyListeners();
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ return {
325
+ getState() {
326
+ return state;
327
+ },
328
+ getSnapshot() {
329
+ return state;
330
+ },
331
+ getServerSnapshot() {
332
+ return state;
333
+ },
334
+ subscribe(listener) {
335
+ listeners.add(listener);
336
+ return () => {
337
+ listeners.delete(listener);
338
+ };
339
+ },
340
+ /**
341
+ * Internal method to handle events - exposed for adapter use
342
+ */
343
+ _handleEvent: handleEvent,
344
+ };
345
+ }