uidex 0.1.1 → 0.2.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.
@@ -782,49 +782,56 @@ body.uidex-inspecting * {
782
782
  color: var(--uidex-color-text-muted);
783
783
  }
784
784
 
785
- .uidex-form-select {
786
- appearance: none;
787
- padding: 6px 10px;
785
+ .uidex-form-select,
786
+ .uidex-form-input,
787
+ .uidex-form-textarea {
788
788
  border: 1px solid var(--uidex-color-border);
789
789
  border-radius: 0;
790
790
  background: transparent;
791
791
  color: var(--uidex-color-text);
792
792
  font-size: var(--uidex-font-size-sm);
793
793
  font-family: var(--uidex-font-mono);
794
- cursor: pointer;
795
794
  outline: none;
795
+ }
796
+
797
+ .uidex-form-select:focus,
798
+ .uidex-form-input:focus,
799
+ .uidex-form-textarea:focus {
800
+ border-color: rgba(255, 255, 255, 0.3);
801
+ }
802
+
803
+ .uidex-form-input::placeholder,
804
+ .uidex-form-textarea::placeholder {
805
+ color: var(--uidex-color-text-muted);
806
+ }
807
+
808
+ .uidex-form-select {
809
+ appearance: none;
810
+ padding: 6px 10px;
811
+ cursor: pointer;
796
812
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ababab' d='M3 5l3 3 3-3'/%3E%3C/svg%3E");
797
813
  background-repeat: no-repeat;
798
814
  background-position: right 8px center;
799
815
  padding-right: 26px;
800
816
  }
801
817
 
802
- .uidex-form-select:focus {
803
- border-color: rgba(255, 255, 255, 0.3);
818
+ .uidex-form-input {
819
+ padding: 6px 10px;
820
+ width: 100%;
821
+ box-sizing: border-box;
822
+ }
823
+
824
+ .uidex-form-input + .uidex-form-input {
825
+ margin-top: 6px;
804
826
  }
805
827
 
806
828
  .uidex-form-textarea {
807
829
  padding: 8px 10px;
808
- border: 1px solid var(--uidex-color-border);
809
- border-radius: 0;
810
- background: transparent;
811
- color: var(--uidex-color-text);
812
- font-size: var(--uidex-font-size-sm);
813
- font-family: var(--uidex-font-mono);
814
830
  line-height: 1.5;
815
831
  resize: vertical;
816
- outline: none;
817
832
  min-height: 80px;
818
833
  }
819
834
 
820
- .uidex-form-textarea::placeholder {
821
- color: var(--uidex-color-text-muted);
822
- }
823
-
824
- .uidex-form-textarea:focus {
825
- border-color: rgba(255, 255, 255, 0.3);
826
- }
827
-
828
835
  .uidex-form-submit {
829
836
  padding: 6px 16px;
830
837
  border: 1px solid var(--uidex-color-border);
@@ -843,7 +850,7 @@ body.uidex-inspecting * {
843
850
  background: var(--uidex-color-primary-hover);
844
851
  }
845
852
 
846
- .uidex-form-submit:active {
853
+ .uidex-form-submit:not(:disabled):active {
847
854
  transform: translateY(1px);
848
855
  }
849
856
 
package/dist/index.cjs CHANGED
@@ -273,7 +273,7 @@ function resolveColor(color, colorMap) {
273
273
  }
274
274
 
275
275
  // src/core/overlay.ts
276
- var DEFAULT_COLOR = "#e5e5e5";
276
+ var DEFAULT_COLOR = "#3b82f6";
277
277
  var DEFAULT_BORDER_STYLE = "solid";
278
278
  var DEFAULT_BORDER_WIDTH = 2;
279
279
  var DEFAULT_LABEL_POSITION = "top-left";
@@ -424,6 +424,23 @@ var Overlay = class {
424
424
  this.element.style.left = `${rect.left}px`;
425
425
  this.element.style.width = `${rect.width}px`;
426
426
  this.element.style.height = `${rect.height}px`;
427
+ this.clampLabel(rect);
428
+ }
429
+ /** Move the label inside the overlay when there is no room outside. */
430
+ clampLabel(rect) {
431
+ if (!this.labelElement || this.labelElement.style.display === "none") return;
432
+ const {
433
+ labelPosition = DEFAULT_LABEL_POSITION
434
+ } = this.options;
435
+ const isTop = labelPosition === "top-left" || labelPosition === "top-right";
436
+ const labelHeight = 20;
437
+ if (isTop && rect.top < labelHeight) {
438
+ this.labelElement.style.top = "4px";
439
+ this.labelElement.style.transform = "";
440
+ } else if (!isTop && window.innerHeight - rect.bottom < labelHeight) {
441
+ this.labelElement.style.bottom = "4px";
442
+ this.labelElement.style.transform = "";
443
+ }
427
444
  }
428
445
  addListeners() {
429
446
  window.addEventListener("resize", this.boundUpdatePosition);
@@ -564,6 +581,154 @@ var Inspector = class {
564
581
  }
565
582
  };
566
583
 
584
+ // src/core/ingest.ts
585
+ var MAX_CONSOLE_LOGS = 50;
586
+ var MAX_NETWORK_ERRORS = 20;
587
+ var nativeFetch = null;
588
+ function safeStringify(value) {
589
+ if (typeof value === "string") return value;
590
+ try {
591
+ return JSON.stringify(value);
592
+ } catch {
593
+ return String(value);
594
+ }
595
+ }
596
+ var IngestCapture = class {
597
+ constructor(captureConsole, captureNetwork) {
598
+ this.captureConsole = captureConsole;
599
+ this.captureNetwork = captureNetwork;
600
+ }
601
+ consoleLogs = [];
602
+ networkErrors = [];
603
+ originalConsoleWarn = null;
604
+ originalConsoleError = null;
605
+ originalFetch = null;
606
+ start() {
607
+ this.consoleLogs = [];
608
+ this.networkErrors = [];
609
+ if (this.captureConsole) this.interceptConsole();
610
+ if (this.captureNetwork) this.interceptNetwork();
611
+ }
612
+ stop() {
613
+ this.restoreConsole();
614
+ this.restoreNetwork();
615
+ }
616
+ getConsoleLogs() {
617
+ return [...this.consoleLogs];
618
+ }
619
+ getNetworkErrors() {
620
+ return [...this.networkErrors];
621
+ }
622
+ interceptConsole() {
623
+ if (this.originalConsoleWarn) return;
624
+ this.originalConsoleWarn = console.warn;
625
+ this.originalConsoleError = console.error;
626
+ console.warn = (...args) => {
627
+ this.addConsoleLog("warn", args);
628
+ this.originalConsoleWarn.apply(console, args);
629
+ };
630
+ console.error = (...args) => {
631
+ this.addConsoleLog("error", args);
632
+ this.originalConsoleError.apply(console, args);
633
+ };
634
+ }
635
+ addConsoleLog(level, args) {
636
+ this.consoleLogs.push({
637
+ level,
638
+ message: args.map(safeStringify).join(" "),
639
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
640
+ });
641
+ if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
642
+ this.consoleLogs.shift();
643
+ }
644
+ }
645
+ restoreConsole() {
646
+ if (this.originalConsoleWarn) {
647
+ console.warn = this.originalConsoleWarn;
648
+ this.originalConsoleWarn = null;
649
+ }
650
+ if (this.originalConsoleError) {
651
+ console.error = this.originalConsoleError;
652
+ this.originalConsoleError = null;
653
+ }
654
+ }
655
+ interceptNetwork() {
656
+ if (this.originalFetch) return;
657
+ this.originalFetch = window.fetch;
658
+ if (!nativeFetch) nativeFetch = this.originalFetch;
659
+ window.fetch = async (...args) => {
660
+ try {
661
+ const response = await this.originalFetch.apply(window, args);
662
+ if (!response.ok) {
663
+ this.addNetworkError(args[0], args[1]?.method, response.status, response.statusText);
664
+ }
665
+ return response;
666
+ } catch (error) {
667
+ this.addNetworkError(
668
+ args[0],
669
+ args[1]?.method,
670
+ null,
671
+ error instanceof Error ? error.message : "Network error"
672
+ );
673
+ throw error;
674
+ }
675
+ };
676
+ }
677
+ addNetworkError(input, method, status, statusText) {
678
+ let url;
679
+ if (typeof input === "string") {
680
+ url = input;
681
+ } else if (input instanceof URL) {
682
+ url = input.href;
683
+ } else {
684
+ url = input.url;
685
+ method ??= input.method;
686
+ }
687
+ this.networkErrors.push({
688
+ url,
689
+ method: method ?? "GET",
690
+ status,
691
+ statusText,
692
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
693
+ });
694
+ if (this.networkErrors.length > MAX_NETWORK_ERRORS) {
695
+ this.networkErrors.shift();
696
+ }
697
+ }
698
+ restoreNetwork() {
699
+ if (this.originalFetch) {
700
+ window.fetch = this.originalFetch;
701
+ this.originalFetch = null;
702
+ }
703
+ }
704
+ };
705
+ function generateSessionId() {
706
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
707
+ return crypto.randomUUID();
708
+ }
709
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
710
+ const r = Math.random() * 16 | 0;
711
+ const v = c === "x" ? r : r & 3 | 8;
712
+ return v.toString(16);
713
+ });
714
+ }
715
+ async function submitFeedback(endpoint, apiKey, report) {
716
+ const fetchFn = nativeFetch ?? fetch;
717
+ const response = await fetchFn(endpoint, {
718
+ method: "POST",
719
+ headers: {
720
+ "Content-Type": "application/json",
721
+ Authorization: `Bearer ${apiKey}`
722
+ },
723
+ body: JSON.stringify(report)
724
+ });
725
+ if (!response.ok) {
726
+ const text = await response.text().catch(() => "");
727
+ throw new Error(`Ingest failed (${response.status}): ${text}`);
728
+ }
729
+ return response.json();
730
+ }
731
+
567
732
  // src/core/modal.ts
568
733
  var Modal = class _Modal {
569
734
  backdrop = null;
@@ -1546,6 +1711,14 @@ var Modal = class _Modal {
1546
1711
  "medium"
1547
1712
  );
1548
1713
  form.appendChild(severitySelect.group);
1714
+ const titleGroup = this.createFormGroup("Title");
1715
+ const titleInput = document.createElement("input");
1716
+ titleInput.type = "text";
1717
+ titleInput.className = "uidex-form-input";
1718
+ titleInput.placeholder = "Brief summary (optional)";
1719
+ titleInput.maxLength = 200;
1720
+ titleGroup.appendChild(titleInput);
1721
+ form.appendChild(titleGroup);
1549
1722
  const descGroup = this.createFormGroup("Description");
1550
1723
  const textarea = document.createElement("textarea");
1551
1724
  textarea.className = "uidex-form-textarea";
@@ -1553,6 +1726,22 @@ var Modal = class _Modal {
1553
1726
  textarea.rows = 4;
1554
1727
  descGroup.appendChild(textarea);
1555
1728
  form.appendChild(descGroup);
1729
+ let emailInput;
1730
+ let nameInput;
1731
+ if (this.options.ingest && !this.options.ingest.reporter) {
1732
+ const reporterGroup = this.createFormGroup("Reporter");
1733
+ nameInput = document.createElement("input");
1734
+ nameInput.type = "text";
1735
+ nameInput.className = "uidex-form-input";
1736
+ nameInput.placeholder = "Name (optional)";
1737
+ reporterGroup.appendChild(nameInput);
1738
+ emailInput = document.createElement("input");
1739
+ emailInput.type = "email";
1740
+ emailInput.className = "uidex-form-input";
1741
+ emailInput.placeholder = "Email (optional)";
1742
+ reporterGroup.appendChild(emailInput);
1743
+ form.appendChild(reporterGroup);
1744
+ }
1556
1745
  const screenshotGroup = document.createElement("div");
1557
1746
  screenshotGroup.className = "uidex-form-group";
1558
1747
  const screenshotLabel = document.createElement("label");
@@ -1588,9 +1777,14 @@ var Modal = class _Modal {
1588
1777
  }
1589
1778
  const env = this.collectEnv();
1590
1779
  const page = this.data.pages.find((p) => p.componentIds.includes(id));
1780
+ const { ingest } = this.options;
1781
+ const reporterEmail = ingest?.reporter?.email || emailInput?.value.trim() || void 0;
1782
+ const reporterName = ingest?.reporter?.name || nameInput?.value.trim() || void 0;
1783
+ const titleValue = titleInput.value.trim();
1591
1784
  const report = {
1592
1785
  type: typeSelect.select.value,
1593
1786
  severity: severitySelect.select.value,
1787
+ ...titleValue ? { title: titleValue } : {},
1594
1788
  description: textarea.value.trim(),
1595
1789
  componentId: id,
1596
1790
  element: element ? this.describeElement(element) : null,
@@ -1599,17 +1793,66 @@ var Modal = class _Modal {
1599
1793
  path: window.location.pathname,
1600
1794
  route: page?.dir ?? null,
1601
1795
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1796
+ pageTitle: document.title,
1797
+ locale: navigator.language,
1798
+ sessionId: this.options.sessionId ?? "",
1602
1799
  viewport: env.viewport,
1603
1800
  screen: env.screen,
1604
1801
  userAgent: env.userAgent,
1605
- screenshot
1802
+ screenshot,
1803
+ ...reporterEmail ? { reporterEmail } : {},
1804
+ ...reporterName ? { reporterName } : {},
1805
+ ...ingest?.environment ? { environment: ingest.environment } : {},
1806
+ ...ingest?.appVersion ? { appVersion: ingest.appVersion } : {},
1807
+ ...ingest?.metadata ? { metadata: ingest.metadata } : {}
1808
+ };
1809
+ const consoleLogs = this.options.getConsoleLogs?.();
1810
+ if (consoleLogs && consoleLogs.length > 0) {
1811
+ report.consoleLogs = consoleLogs;
1812
+ }
1813
+ const networkErrors = this.options.getNetworkErrors?.();
1814
+ if (networkErrors && networkErrors.length > 0) {
1815
+ report.networkErrors = networkErrors;
1816
+ }
1817
+ const showSuccess = (autoClose) => {
1818
+ submitBtn.textContent = "Submitted!";
1819
+ if (autoClose) {
1820
+ setTimeout(() => this.hide(), 1500);
1821
+ } else {
1822
+ setTimeout(() => {
1823
+ submitBtn.textContent = "Submit";
1824
+ submitBtn.disabled = false;
1825
+ }, 1500);
1826
+ }
1606
1827
  };
1607
- console.log("[uidex] Feedback submitted:", report);
1608
- submitBtn.textContent = "Submitted!";
1609
- setTimeout(() => {
1610
- submitBtn.textContent = "Submit";
1611
- submitBtn.disabled = false;
1612
- }, 1500);
1828
+ if (ingest) {
1829
+ submitBtn.textContent = "Submitting\u2026";
1830
+ try {
1831
+ const serverResult = await submitFeedback(
1832
+ ingest.endpoint,
1833
+ ingest.apiKey,
1834
+ report
1835
+ );
1836
+ this.options.onSubmit?.(report, {
1837
+ ok: true,
1838
+ id: serverResult.id,
1839
+ sequenceNumber: serverResult.sequenceNumber
1840
+ });
1841
+ showSuccess(true);
1842
+ } catch (err) {
1843
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
1844
+ console.warn("[uidex] Feedback submission failed:", errorMessage);
1845
+ this.options.onSubmit?.(report, { ok: false, error: errorMessage });
1846
+ submitBtn.textContent = "Failed \u2014 retry?";
1847
+ submitBtn.disabled = false;
1848
+ }
1849
+ } else {
1850
+ if (!this.options.onSubmit) {
1851
+ console.log("[uidex] Feedback submitted:", report);
1852
+ }
1853
+ this.options.onSubmit?.(report, { ok: true, id: "", sequenceNumber: 0 });
1854
+ showSuccess(false);
1855
+ }
1613
1856
  });
1614
1857
  form.appendChild(submitBtn);
1615
1858
  this.mainContent.appendChild(form);
@@ -2729,49 +2972,56 @@ body.uidex-inspecting * {
2729
2972
  color: var(--uidex-color-text-muted);
2730
2973
  }
2731
2974
 
2732
- .uidex-form-select {
2733
- appearance: none;
2734
- padding: 6px 10px;
2975
+ .uidex-form-select,
2976
+ .uidex-form-input,
2977
+ .uidex-form-textarea {
2735
2978
  border: 1px solid var(--uidex-color-border);
2736
2979
  border-radius: 0;
2737
2980
  background: transparent;
2738
2981
  color: var(--uidex-color-text);
2739
2982
  font-size: var(--uidex-font-size-sm);
2740
2983
  font-family: var(--uidex-font-mono);
2741
- cursor: pointer;
2742
2984
  outline: none;
2985
+ }
2986
+
2987
+ .uidex-form-select:focus,
2988
+ .uidex-form-input:focus,
2989
+ .uidex-form-textarea:focus {
2990
+ border-color: rgba(255, 255, 255, 0.3);
2991
+ }
2992
+
2993
+ .uidex-form-input::placeholder,
2994
+ .uidex-form-textarea::placeholder {
2995
+ color: var(--uidex-color-text-muted);
2996
+ }
2997
+
2998
+ .uidex-form-select {
2999
+ appearance: none;
3000
+ padding: 6px 10px;
3001
+ cursor: pointer;
2743
3002
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ababab' d='M3 5l3 3 3-3'/%3E%3C/svg%3E");
2744
3003
  background-repeat: no-repeat;
2745
3004
  background-position: right 8px center;
2746
3005
  padding-right: 26px;
2747
3006
  }
2748
3007
 
2749
- .uidex-form-select:focus {
2750
- border-color: rgba(255, 255, 255, 0.3);
3008
+ .uidex-form-input {
3009
+ padding: 6px 10px;
3010
+ width: 100%;
3011
+ box-sizing: border-box;
3012
+ }
3013
+
3014
+ .uidex-form-input + .uidex-form-input {
3015
+ margin-top: 6px;
2751
3016
  }
2752
3017
 
2753
3018
  .uidex-form-textarea {
2754
3019
  padding: 8px 10px;
2755
- border: 1px solid var(--uidex-color-border);
2756
- border-radius: 0;
2757
- background: transparent;
2758
- color: var(--uidex-color-text);
2759
- font-size: var(--uidex-font-size-sm);
2760
- font-family: var(--uidex-font-mono);
2761
3020
  line-height: 1.5;
2762
3021
  resize: vertical;
2763
- outline: none;
2764
3022
  min-height: 80px;
2765
3023
  }
2766
3024
 
2767
- .uidex-form-textarea::placeholder {
2768
- color: var(--uidex-color-text-muted);
2769
- }
2770
-
2771
- .uidex-form-textarea:focus {
2772
- border-color: rgba(255, 255, 255, 0.3);
2773
- }
2774
-
2775
3025
  .uidex-form-submit {
2776
3026
  padding: 6px 16px;
2777
3027
  border: 1px solid var(--uidex-color-border);
@@ -2790,7 +3040,7 @@ body.uidex-inspecting * {
2790
3040
  background: var(--uidex-color-primary-hover);
2791
3041
  }
2792
3042
 
2793
- .uidex-form-submit:active {
3043
+ .uidex-form-submit:not(:disabled):active {
2794
3044
  transform: translateY(1px);
2795
3045
  }
2796
3046
 
@@ -2934,8 +3184,17 @@ var UidexUI = class {
2934
3184
  copyTimer = null;
2935
3185
  currentPresentIds = [];
2936
3186
  activeMode = null;
3187
+ sessionId;
3188
+ capture = null;
2937
3189
  constructor(options = {}) {
2938
3190
  this.options = options;
3191
+ this.sessionId = generateSessionId();
3192
+ if (options.ingest?.captureConsole || options.ingest?.captureNetwork) {
3193
+ this.capture = new IngestCapture(
3194
+ options.ingest.captureConsole ?? false,
3195
+ options.ingest.captureNetwork ?? false
3196
+ );
3197
+ }
2939
3198
  this.overlay = new Overlay({
2940
3199
  color: options.config?.defaults?.color,
2941
3200
  borderStyle: options.config?.defaults?.borderStyle,
@@ -2952,7 +3211,12 @@ var UidexUI = class {
2952
3211
  }
2953
3212
  this.options.onSelect?.(id);
2954
3213
  },
2955
- elementGetter: (id) => this.findElement(id)
3214
+ elementGetter: (id) => this.findElement(id),
3215
+ ingest: options.ingest,
3216
+ onSubmit: options.onSubmit,
3217
+ sessionId: this.sessionId,
3218
+ getConsoleLogs: () => this.capture?.getConsoleLogs() ?? [],
3219
+ getNetworkErrors: () => this.capture?.getNetworkErrors() ?? []
2956
3220
  });
2957
3221
  this.menu = new Menu({
2958
3222
  onInspectToggle: () => this.toggleMode("inspect"),
@@ -2991,6 +3255,7 @@ var UidexUI = class {
2991
3255
  this.shadowHost.style.width = "0";
2992
3256
  this.shadowHost.style.height = "0";
2993
3257
  this.shadowHost.style.pointerEvents = "none";
3258
+ this.shadowHost.style.zIndex = "2147483646";
2994
3259
  this.shadowRoot = this.shadowHost.attachShadow({ mode: "open" });
2995
3260
  injectStyles(this.shadowRoot);
2996
3261
  const presentIds = this.scanPresentIds(componentIds);
@@ -3003,12 +3268,14 @@ var UidexUI = class {
3003
3268
  this.modal.setShadowRoot(this.shadowRoot);
3004
3269
  this.updateModalData(presentIds);
3005
3270
  this.inspector?.mount();
3271
+ this.capture?.start();
3006
3272
  this.startObserving();
3007
3273
  this.mounted = true;
3008
3274
  }
3009
3275
  destroy() {
3010
3276
  if (!this.mounted) return;
3011
3277
  this.stopObserving();
3278
+ this.capture?.stop();
3012
3279
  if (this.copyTimer !== null) {
3013
3280
  clearTimeout(this.copyTimer);
3014
3281
  this.copyTimer = null;
@@ -3198,7 +3465,9 @@ function UidexDevtools({
3198
3465
  buttonPosition = "bottom-right",
3199
3466
  disabled = false,
3200
3467
  onSelect,
3201
- inspectShortcut
3468
+ inspectShortcut,
3469
+ ingest,
3470
+ onSubmit
3202
3471
  }) {
3203
3472
  const uiRef = (0, import_react.useRef)(null);
3204
3473
  const stableShortcut = (0, import_react.useMemo)(
@@ -3208,6 +3477,15 @@ function UidexDevtools({
3208
3477
  inspectShortcut === false ? false : `${inspectShortcut?.key}:${inspectShortcut?.ctrlKey}:${inspectShortcut?.shiftKey}:${inspectShortcut?.altKey}:${inspectShortcut?.metaKey}`
3209
3478
  ]
3210
3479
  );
3480
+ const stableIngest = (0, import_react.useMemo)(
3481
+ () => ingest,
3482
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3483
+ [
3484
+ ingest ? `${ingest.endpoint}:${ingest.apiKey}:${ingest.environment}:${ingest.appVersion}:${ingest.captureConsole}:${ingest.captureNetwork}:${ingest.reporter?.email}:${ingest.reporter?.name}` : void 0
3485
+ ]
3486
+ );
3487
+ const onSubmitRef = (0, import_react.useRef)(onSubmit);
3488
+ onSubmitRef.current = onSubmit;
3211
3489
  (0, import_react.useEffect)(() => {
3212
3490
  if (disabled) {
3213
3491
  return;
@@ -3217,7 +3495,9 @@ function UidexDevtools({
3217
3495
  config,
3218
3496
  buttonPosition,
3219
3497
  onSelect,
3220
- inspectShortcut: stableShortcut
3498
+ inspectShortcut: stableShortcut,
3499
+ ingest: stableIngest,
3500
+ onSubmit: (...args) => onSubmitRef.current?.(...args)
3221
3501
  });
3222
3502
  ui.mount();
3223
3503
  uiRef.current = ui;
@@ -3225,7 +3505,7 @@ function UidexDevtools({
3225
3505
  ui.destroy();
3226
3506
  uiRef.current = null;
3227
3507
  };
3228
- }, [components, config, buttonPosition, disabled, onSelect, stableShortcut]);
3508
+ }, [components, config, buttonPosition, disabled, onSelect, stableShortcut, stableIngest]);
3229
3509
  return null;
3230
3510
  }
3231
3511