@kelet-ai/feedback-ui 1.0.2 → 1.1.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/README.md +19 -19
- package/dist/components/vote-feedback.d.ts +1 -1
- package/dist/feedback-ui.es.js +120 -22
- package/dist/feedback-ui.es.js.map +1 -1
- package/dist/feedback-ui.es.min.js +690 -617
- package/dist/feedback-ui.es.min.js.map +1 -1
- package/dist/feedback-ui.umd.js +119 -21
- package/dist/feedback-ui.umd.js.map +1 -1
- package/dist/feedback-ui.umd.min.js +12 -12
- package/dist/feedback-ui.umd.min.js.map +1 -1
- package/dist/hooks/feedback-state/use-feedback-reducer.d.ts +5 -5
- package/dist/hooks/feedback-state/use-feedback-state.d.ts +6 -6
- package/dist/hooks/feedback-state/use-state-change-tracking.d.ts +2 -2
- package/dist/lib/event-capture.d.ts +22 -0
- package/dist/types/index.d.ts +22 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ import { ShadcnVoteFeedback } from '@/components/ui/vote-feedback';
|
|
|
28
28
|
function App() {
|
|
29
29
|
return (
|
|
30
30
|
<ShadcnVoteFeedback
|
|
31
|
-
|
|
31
|
+
session_id="my-feature"
|
|
32
32
|
onFeedback={feedback => console.log(feedback)}
|
|
33
33
|
variant="outline"
|
|
34
34
|
/>
|
|
@@ -91,7 +91,7 @@ Users explicitly vote and provide comments:
|
|
|
91
91
|
```tsx
|
|
92
92
|
import { VoteFeedback } from '@kelet-ai/feedback-ui';
|
|
93
93
|
|
|
94
|
-
<VoteFeedback.Root onFeedback={handleFeedback}
|
|
94
|
+
<VoteFeedback.Root onFeedback={handleFeedback} session_id="ai-response">
|
|
95
95
|
<VoteFeedback.UpvoteButton>👍 Helpful</VoteFeedback.UpvoteButton>
|
|
96
96
|
<VoteFeedback.DownvoteButton>👎 Not helpful</VoteFeedback.DownvoteButton>
|
|
97
97
|
<VoteFeedback.Popover>
|
|
@@ -158,14 +158,14 @@ Understanding these fundamental concepts will help you implement feedback collec
|
|
|
158
158
|
**Best Practice**: Use traceable IDs from your logging system (session ID, trace ID, request ID)
|
|
159
159
|
|
|
160
160
|
```tsx
|
|
161
|
-
// ✅ Good: Traceable
|
|
162
|
-
<VoteFeedback.Root
|
|
161
|
+
// ✅ Good: Traceable session_id
|
|
162
|
+
<VoteFeedback.Root session_id="session-abc123-ai-response-456"/>
|
|
163
163
|
|
|
164
|
-
// ✅ Good: Content-based
|
|
165
|
-
<VoteFeedback.Root
|
|
164
|
+
// ✅ Good: Content-based session_id
|
|
165
|
+
<VoteFeedback.Root session_id={`article-${articleId}-section-${sectionId}`}/>
|
|
166
166
|
|
|
167
|
-
// ❌ Poor: Generic
|
|
168
|
-
<VoteFeedback.Root
|
|
167
|
+
// ❌ Poor: Generic session_id
|
|
168
|
+
<VoteFeedback.Root session_id="feedback"/>
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
### **📊 Feedback Sources**
|
|
@@ -296,8 +296,8 @@ const [data, setData] = useFeedbackState(initial, 'tracker', {
|
|
|
296
296
|
#### **Identifiers**
|
|
297
297
|
|
|
298
298
|
✅ Use traceable session/request IDs
|
|
299
|
-
✅ Include context in
|
|
300
|
-
✅ Keep
|
|
299
|
+
✅ Include context in session_id structure
|
|
300
|
+
✅ Keep session_ids consistent across related actions
|
|
301
301
|
|
|
302
302
|
#### **Feedback Sources**
|
|
303
303
|
|
|
@@ -327,7 +327,7 @@ Main container component that manages feedback state.
|
|
|
327
327
|
|
|
328
328
|
```tsx
|
|
329
329
|
<VoteFeedback.Root
|
|
330
|
-
|
|
330
|
+
session_id="unique-id" // Required: Unique tracking ID
|
|
331
331
|
onFeedback={handleFeedback} // Required: Callback function
|
|
332
332
|
trigger_name="user_feedback" // Optional: Categorization
|
|
333
333
|
extra_metadata={{ page: 'home' }} // Optional: Additional data
|
|
@@ -376,7 +376,7 @@ const [count, setCount] = useFeedbackState(0, 'counter-widget');
|
|
|
376
376
|
```tsx
|
|
377
377
|
const [profile, setProfile] = useFeedbackState(
|
|
378
378
|
{ name: '', email: '' },
|
|
379
|
-
state => `profile-${state.email}`, // Dynamic
|
|
379
|
+
state => `profile-${state.email}`, // Dynamic session_id
|
|
380
380
|
{
|
|
381
381
|
debounceMs: 2000, // Wait time before sending feedback
|
|
382
382
|
diffType: 'object', // Format: 'git' | 'object' | 'json'
|
|
@@ -466,7 +466,7 @@ dispatch({ type: 'custom' }, 'override'); // Custom trigger name
|
|
|
466
466
|
|
|
467
467
|
```tsx
|
|
468
468
|
<VoteFeedback.Root
|
|
469
|
-
|
|
469
|
+
session_id="ai-response-123"
|
|
470
470
|
onFeedback={handleFeedback}
|
|
471
471
|
trigger_name="ai_evaluation"
|
|
472
472
|
extra_metadata={{
|
|
@@ -492,7 +492,7 @@ dispatch({ type: 'custom' }, 'override'); // Custom trigger name
|
|
|
492
492
|
|
|
493
493
|
```typescript
|
|
494
494
|
interface FeedbackData {
|
|
495
|
-
|
|
495
|
+
session_id: string; // Unique tracking ID
|
|
496
496
|
vote: 'upvote' | 'downvote'; // User's vote
|
|
497
497
|
explanation?: string; // Optional user comment
|
|
498
498
|
extra_metadata?: Record<string, any>; // Additional context data
|
|
@@ -507,7 +507,7 @@ interface FeedbackData {
|
|
|
507
507
|
|
|
508
508
|
| Prop | Type | Required | Description |
|
|
509
509
|
| ---------------- | ------------------------------ | -------- | ----------------------------------- |
|
|
510
|
-
| `
|
|
510
|
+
| `session_id` | `string` | ✅ | Unique session ID for tracking |
|
|
511
511
|
| `onFeedback` | `(data: FeedbackData) => void` | ✅ | Callback when feedback is submitted |
|
|
512
512
|
| `trigger_name` | `string` | ❌ | Optional categorization tag |
|
|
513
513
|
| `extra_metadata` | `object` | ❌ | Additional context data |
|
|
@@ -593,9 +593,9 @@ bun run checks # Run all quality checks (lint, format, typecheck, tests)
|
|
|
593
593
|
<VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
|
|
594
594
|
</VoteFeedback.Root>
|
|
595
595
|
|
|
596
|
-
// ✅ Include required
|
|
596
|
+
// ✅ Include required session_id and onFeedback
|
|
597
597
|
<VoteFeedback.Root
|
|
598
|
-
|
|
598
|
+
session_id="my-feature"
|
|
599
599
|
onFeedback={handleFeedback}
|
|
600
600
|
>
|
|
601
601
|
<VoteFeedback.UpvoteButton>👍</VoteFeedback.UpvoteButton>
|
|
@@ -631,14 +631,14 @@ const [value, setValue] = useFeedbackState('initial', 'test', {
|
|
|
631
631
|
|
|
632
632
|
### **Best Practices**
|
|
633
633
|
|
|
634
|
-
✅ **Use unique
|
|
634
|
+
✅ **Use unique session_ids** for each feedback instance - the session_id should be traceable back to the session's log
|
|
635
635
|
and allow us to understand the context of the feedback.
|
|
636
636
|
✅ **Handle feedback data asynchronously** in your callback
|
|
637
637
|
✅ **Test keyboard navigation** in your implementation
|
|
638
638
|
✅ **Provide meaningful trigger names** for categorization
|
|
639
639
|
✅ **Include relevant metadata** for context
|
|
640
640
|
|
|
641
|
-
❌ **Don't use the same
|
|
641
|
+
❌ **Don't use the same session_id** for multiple components. A session_id should be traced back to the session's log -
|
|
642
642
|
allows us to understand the context of the feedback.
|
|
643
643
|
|
|
644
644
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DownvoteButtonProps, PopoverProps, SubmitButtonProps, TextareaProps, UpvoteButtonProps, VoteFeedbackRootProps } from '../types';
|
|
2
2
|
export declare const VoteFeedback: {
|
|
3
|
-
Root: ({ children, onFeedback, defaultText,
|
|
3
|
+
Root: ({ children, onFeedback, defaultText, session_id: sessionIdProp, extra_metadata, trigger_name, }: VoteFeedbackRootProps) => import("react/jsx-runtime").JSX.Element;
|
|
4
4
|
UpvoteButton: ({ asChild, children, onClick, ...props }: UpvoteButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
5
|
DownvoteButton: ({ asChild, children, onClick, ...props }: DownvoteButtonProps) => string | number | bigint | true | Iterable<import('react').ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element;
|
|
6
6
|
Popover: ({ asChild, children, ...props }: PopoverProps) => import("react/jsx-runtime").JSX.Element | null;
|
package/dist/feedback-ui.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import require$$0$1, { createContext, useContext, useCallback, isValidElement, cloneElement, useState, useRef, useId
|
|
1
|
+
import require$$0$1, { createContext, useEffect, useContext, useCallback, isValidElement, cloneElement, useState, useRef, useId } from "react";
|
|
2
2
|
function getDefaultExportFromCjs(x) {
|
|
3
3
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
4
4
|
}
|
|
@@ -352,6 +352,88 @@ function requireJsxRuntime() {
|
|
|
352
352
|
return jsxRuntime.exports;
|
|
353
353
|
}
|
|
354
354
|
var jsxRuntimeExports = requireJsxRuntime();
|
|
355
|
+
let latestEvent = null;
|
|
356
|
+
let isInitialized = false;
|
|
357
|
+
function serializeTarget(target) {
|
|
358
|
+
if (!target || !(target instanceof Element)) return "unknown";
|
|
359
|
+
const el = target;
|
|
360
|
+
if (el.id) return `#${el.id}`;
|
|
361
|
+
const dataId = el.getAttribute("data-feedback-id");
|
|
362
|
+
if (dataId) return `[data-feedback-id="${dataId}"]`;
|
|
363
|
+
const path = [];
|
|
364
|
+
let current = el;
|
|
365
|
+
let depth = 0;
|
|
366
|
+
const MAX_DEPTH = 5;
|
|
367
|
+
while (current && current !== document.body && depth < MAX_DEPTH) {
|
|
368
|
+
let selector = current.tagName.toLowerCase();
|
|
369
|
+
if (current.classList.length > 0) {
|
|
370
|
+
const className = Array.from(current.classList)[0];
|
|
371
|
+
selector += `.${className}`;
|
|
372
|
+
}
|
|
373
|
+
const parent = current.parentElement;
|
|
374
|
+
if (parent) {
|
|
375
|
+
const siblings = Array.from(parent.children);
|
|
376
|
+
const index = siblings.indexOf(current) + 1;
|
|
377
|
+
if (siblings.length > 1) {
|
|
378
|
+
selector += `:nth-child(${index})`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
path.unshift(selector);
|
|
382
|
+
current = parent;
|
|
383
|
+
depth++;
|
|
384
|
+
}
|
|
385
|
+
return path.join(" > ");
|
|
386
|
+
}
|
|
387
|
+
function getTargetText(target) {
|
|
388
|
+
if (!target || !(target instanceof Element)) return "";
|
|
389
|
+
const el = target;
|
|
390
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
391
|
+
if (ariaLabel) return ariaLabel.substring(0, 50);
|
|
392
|
+
const text = el.textContent?.trim() || "";
|
|
393
|
+
return text.substring(0, 50);
|
|
394
|
+
}
|
|
395
|
+
function captureEvent(e) {
|
|
396
|
+
const captured = {
|
|
397
|
+
type: e.type,
|
|
398
|
+
targetSelector: serializeTarget(e.target),
|
|
399
|
+
targetText: getTargetText(e.target),
|
|
400
|
+
timestamp: Date.now()
|
|
401
|
+
};
|
|
402
|
+
if (e instanceof MouseEvent) {
|
|
403
|
+
captured.coordinates = { x: e.clientX, y: e.clientY };
|
|
404
|
+
} else if (e instanceof KeyboardEvent) {
|
|
405
|
+
captured.key = e.key;
|
|
406
|
+
}
|
|
407
|
+
return captured;
|
|
408
|
+
}
|
|
409
|
+
function initEventCapture() {
|
|
410
|
+
if (isInitialized || typeof window === "undefined") return;
|
|
411
|
+
const eventTypes = ["click", "keydown", "submit", "change"];
|
|
412
|
+
eventTypes.forEach((type) => {
|
|
413
|
+
window.addEventListener(
|
|
414
|
+
type,
|
|
415
|
+
(e) => {
|
|
416
|
+
latestEvent = captureEvent(e);
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
capture: true,
|
|
420
|
+
// Intercept before React handlers
|
|
421
|
+
passive: true
|
|
422
|
+
// Better scroll performance
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
isInitialized = true;
|
|
427
|
+
}
|
|
428
|
+
function getLatestEvent() {
|
|
429
|
+
if (!latestEvent) return null;
|
|
430
|
+
const age = Date.now() - latestEvent.timestamp;
|
|
431
|
+
if (age > 1e4) {
|
|
432
|
+
latestEvent = null;
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
return { ...latestEvent };
|
|
436
|
+
}
|
|
355
437
|
const KeletContext = createContext(null);
|
|
356
438
|
const DefaultKeletBaseUrl = "https://api.kelet.ai/api";
|
|
357
439
|
const useKelet = () => {
|
|
@@ -374,6 +456,9 @@ const useDefaultFeedbackHandler = () => {
|
|
|
374
456
|
}
|
|
375
457
|
};
|
|
376
458
|
const KeletProvider = ({ apiKey, project, baseUrl, children }) => {
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
initEventCapture();
|
|
461
|
+
}, []);
|
|
377
462
|
const parentContext = useContext(KeletContext);
|
|
378
463
|
const resolvedApiKey = apiKey || parentContext?.api_key;
|
|
379
464
|
if (!resolvedApiKey) {
|
|
@@ -383,15 +468,21 @@ const KeletProvider = ({ apiKey, project, baseUrl, children }) => {
|
|
|
383
468
|
}
|
|
384
469
|
const feedback = async (data) => {
|
|
385
470
|
const resolvedBaseUrl = baseUrl || DefaultKeletBaseUrl;
|
|
386
|
-
const url = `${resolvedBaseUrl}/projects/${project}/
|
|
471
|
+
const url = `${resolvedBaseUrl}/projects/${project}/signal`;
|
|
472
|
+
const capturedEvent = getLatestEvent();
|
|
473
|
+
const metadata = {
|
|
474
|
+
...data.extra_metadata ?? {},
|
|
475
|
+
...capturedEvent && { $dom_event: capturedEvent }
|
|
476
|
+
};
|
|
387
477
|
const req = {
|
|
388
|
-
|
|
478
|
+
session_id: data.session_id,
|
|
389
479
|
source: data.source || "EXPLICIT",
|
|
390
480
|
vote: data.vote,
|
|
391
481
|
explanation: data.explanation,
|
|
392
482
|
correction: data.correction,
|
|
393
|
-
selection: data.selection
|
|
394
|
-
|
|
483
|
+
selection: data.selection,
|
|
484
|
+
trigger_name: data.trigger_name,
|
|
485
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
395
486
|
};
|
|
396
487
|
const response = await fetch(url, {
|
|
397
488
|
method: "POST",
|
|
@@ -453,11 +544,11 @@ const VoteFeedbackRoot = ({
|
|
|
453
544
|
children,
|
|
454
545
|
onFeedback,
|
|
455
546
|
defaultText = "",
|
|
456
|
-
|
|
547
|
+
session_id: sessionIdProp,
|
|
457
548
|
extra_metadata,
|
|
458
549
|
trigger_name
|
|
459
550
|
}) => {
|
|
460
|
-
const
|
|
551
|
+
const session_id = typeof sessionIdProp === "function" ? sessionIdProp() : sessionIdProp;
|
|
461
552
|
const [showPopover, setShowPopover] = useState(false);
|
|
462
553
|
const [feedbackText, setFeedbackText] = useState(defaultText);
|
|
463
554
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
@@ -474,11 +565,11 @@ const VoteFeedbackRoot = ({
|
|
|
474
565
|
setIsSubmitting(false);
|
|
475
566
|
setVote(null);
|
|
476
567
|
setTimeout(() => triggerRef.current?.focus(), 0);
|
|
477
|
-
}, [
|
|
568
|
+
}, [session_id, defaultText]);
|
|
478
569
|
const handleUpvote = useCallback(async () => {
|
|
479
570
|
setVote("upvote");
|
|
480
571
|
const data = {
|
|
481
|
-
|
|
572
|
+
session_id,
|
|
482
573
|
vote: "upvote",
|
|
483
574
|
...extra_metadata && { extra_metadata },
|
|
484
575
|
...trigger_name && { trigger_name }
|
|
@@ -489,12 +580,12 @@ const VoteFeedbackRoot = ({
|
|
|
489
580
|
} finally {
|
|
490
581
|
setIsSubmitting(false);
|
|
491
582
|
}
|
|
492
|
-
}, [handler,
|
|
583
|
+
}, [handler, session_id, extra_metadata, trigger_name]);
|
|
493
584
|
const handleDownvote = useCallback(async () => {
|
|
494
585
|
setVote("downvote");
|
|
495
586
|
if (handler) {
|
|
496
587
|
const data = {
|
|
497
|
-
|
|
588
|
+
session_id,
|
|
498
589
|
vote: "downvote",
|
|
499
590
|
...extra_metadata && { extra_metadata },
|
|
500
591
|
...trigger_name && { trigger_name }
|
|
@@ -517,7 +608,7 @@ const VoteFeedbackRoot = ({
|
|
|
517
608
|
document.body.appendChild(announcement);
|
|
518
609
|
setTimeout(() => document.body.removeChild(announcement), 1e3);
|
|
519
610
|
}, 0);
|
|
520
|
-
}, [handler,
|
|
611
|
+
}, [handler, session_id, extra_metadata, trigger_name]);
|
|
521
612
|
const handleTextareaChange = useCallback(
|
|
522
613
|
(e) => {
|
|
523
614
|
setFeedbackText(e.target.value);
|
|
@@ -528,7 +619,7 @@ const VoteFeedbackRoot = ({
|
|
|
528
619
|
const hasText = feedbackText.trim().length > 0;
|
|
529
620
|
if (hasText) {
|
|
530
621
|
const data = {
|
|
531
|
-
|
|
622
|
+
session_id,
|
|
532
623
|
vote: "downvote",
|
|
533
624
|
explanation: feedbackText,
|
|
534
625
|
...extra_metadata && { extra_metadata },
|
|
@@ -552,7 +643,14 @@ const VoteFeedbackRoot = ({
|
|
|
552
643
|
document.body.appendChild(announcement);
|
|
553
644
|
setTimeout(() => document.body.removeChild(announcement), 1e3);
|
|
554
645
|
}
|
|
555
|
-
}, [
|
|
646
|
+
}, [
|
|
647
|
+
handler,
|
|
648
|
+
feedbackText,
|
|
649
|
+
defaultText,
|
|
650
|
+
session_id,
|
|
651
|
+
extra_metadata,
|
|
652
|
+
trigger_name
|
|
653
|
+
]);
|
|
556
654
|
const handleKeyDown = useCallback(
|
|
557
655
|
(e) => {
|
|
558
656
|
if (e.key === "Escape") {
|
|
@@ -601,7 +699,7 @@ const VoteFeedbackRoot = ({
|
|
|
601
699
|
triggerRef,
|
|
602
700
|
popoverId,
|
|
603
701
|
triggerId,
|
|
604
|
-
|
|
702
|
+
session_id,
|
|
605
703
|
extra_metadata,
|
|
606
704
|
trigger_name
|
|
607
705
|
};
|
|
@@ -2016,7 +2114,7 @@ function stringify(value) {
|
|
|
2016
2114
|
return String(value);
|
|
2017
2115
|
}
|
|
2018
2116
|
}
|
|
2019
|
-
function useStateChangeTracking(currentState,
|
|
2117
|
+
function useStateChangeTracking(currentState, session_id, options) {
|
|
2020
2118
|
const defaultFeedbackHandler = useDefaultFeedbackHandler();
|
|
2021
2119
|
const feedbackHandler = options?.onFeedback || defaultFeedbackHandler;
|
|
2022
2120
|
const debounceMs = options?.debounceMs ?? 3e3;
|
|
@@ -2051,9 +2149,9 @@ function useStateChangeTracking(currentState, tx_id, options) {
|
|
|
2051
2149
|
} else {
|
|
2052
2150
|
vote = diffPercentage > 0.5 ? "downvote" : "upvote";
|
|
2053
2151
|
}
|
|
2054
|
-
const idString = typeof
|
|
2152
|
+
const idString = typeof session_id === "function" ? session_id(endState) : session_id;
|
|
2055
2153
|
feedbackHandler({
|
|
2056
|
-
|
|
2154
|
+
session_id: idString,
|
|
2057
2155
|
vote,
|
|
2058
2156
|
explanation: `State change with diff percentage: ${(diffPercentage * 100).toFixed(1)}%`,
|
|
2059
2157
|
correction: diffString,
|
|
@@ -2062,7 +2160,7 @@ function useStateChangeTracking(currentState, tx_id, options) {
|
|
|
2062
2160
|
trigger_name: triggerName
|
|
2063
2161
|
});
|
|
2064
2162
|
},
|
|
2065
|
-
[options,
|
|
2163
|
+
[options, session_id, diffType, feedbackHandler]
|
|
2066
2164
|
);
|
|
2067
2165
|
const notifyChange = useCallback(
|
|
2068
2166
|
(trigger_name) => {
|
|
@@ -2131,7 +2229,7 @@ function useStateChangeTracking(currentState, tx_id, options) {
|
|
|
2131
2229
|
};
|
|
2132
2230
|
}, [
|
|
2133
2231
|
currentState,
|
|
2134
|
-
|
|
2232
|
+
session_id,
|
|
2135
2233
|
options,
|
|
2136
2234
|
feedbackHandler,
|
|
2137
2235
|
diffType,
|
|
@@ -2145,9 +2243,9 @@ function useStateChangeTracking(currentState, tx_id, options) {
|
|
|
2145
2243
|
notifyChange
|
|
2146
2244
|
};
|
|
2147
2245
|
}
|
|
2148
|
-
function useFeedbackState(initialState,
|
|
2246
|
+
function useFeedbackState(initialState, session_id, options) {
|
|
2149
2247
|
const [state, setStateInternal] = useState(initialState);
|
|
2150
|
-
const { notifyChange } = useStateChangeTracking(state,
|
|
2248
|
+
const { notifyChange } = useStateChangeTracking(state, session_id, options);
|
|
2151
2249
|
const setState = useCallback(
|
|
2152
2250
|
(value, trigger_name) => {
|
|
2153
2251
|
notifyChange(trigger_name);
|